1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import os
import re
import shutil
import tempfile
import zipfile
from host_controller import common
from vts.runners.host import utils
class BuildProvider(object):
"""The base class for build provider.
Attributes:
_IMAGE_FILE_EXTENSIONS: a list of strings which are common image file
extensions.
_BASIC_IMAGE_FILE_NAMES: a list of strings which are the image names in
an artifact zip.
_CONFIG_FILE_EXTENSION: string, the config file extension.
_additional_files: a dict containing additionally fetched files that
custom features may need. The key is the path
relative to temporary directory and the value is the
full path.
_configs: dict where the key is config type and value is the config file
path.
_device_images: dict where the key is image file name and value is the
path.
_test_suites: dict where the key is test suite type and value is the
test suite package file path.
_host_controller_package: dict where the key is a host controller
package and the value is the path
to a package file.
_tmp_dirpath: string, the temp dir path created to keep artifacts.
_last_fetched_artifact_type: string, stores the type of the last
artifact fetched.
"""
_CONFIG_FILE_EXTENSION = ".zip"
_IMAGE_FILE_EXTENSIONS = [".img", ".bin"]
_BASIC_IMAGE_FILE_NAMES = ["boot.img", "system.img", "vendor.img"]
def __init__(self):
self._additional_files = {}
self._device_images = {}
self._test_suites = {}
self._host_controller_package = {}
self._configs = {}
self._last_fetched_artifact_type = None
tempdir_base = os.path.join(os.getcwd(), "tmp")
if not os.path.exists(tempdir_base):
os.mkdir(tempdir_base)
self._tmp_dirpath = tempfile.mkdtemp(dir=tempdir_base)
def __del__(self):
"""Deletes the temp dir if still set."""
if self._tmp_dirpath:
shutil.rmtree(self._tmp_dirpath)
self._tmp_dirpath = None
@property
def tmp_dirpath(self):
return self._tmp_dirpath
def CreateNewTmpDir(self):
return tempfile.mkdtemp(dir=self._tmp_dirpath)
def SetDeviceImage(self, name, path):
"""Sets device image `path` for the specified `name`."""
self._device_images[name] = path
self._last_fetched_artifact_type = common._ARTIFACT_TYPE_DEVICE
def _IsFullDeviceImage(self, namelist):
"""Returns true if given namelist list has all common device images."""
for image_file in self._BASIC_IMAGE_FILE_NAMES:
if image_file not in namelist:
return False
return True
def _IsImageFile(self, file_path):
"""Returns whether a file is an image.
Args:
file_path: string, the file path.
Returns:
boolean, whether the file is an image.
"""
return any(file_path.endswith(ext)
for ext in self._IMAGE_FILE_EXTENSIONS)
def SetDeviceImageZip(self, path, full_device_images=False):
"""Sets device image(s) using files in a given zip file.
It extracts image files inside the given zip file and selects
known Android image files.
Args:
path: string, the path to a zip file.
"""
dest_path = path + ".dir"
fetch_type = None
with zipfile.ZipFile(path, 'r') as zip_ref:
if full_device_images or self._IsFullDeviceImage(zip_ref.namelist()):
self.SetDeviceImage(common.FULL_ZIPFILE, path)
dir_key = common.FULL_ZIPFILE_DIR
fetch_type = common._ARTIFACT_TYPE_DEVICE
else:
self.SetDeviceImage("gsi-zipfile", path)
dir_key = common.GSI_ZIPFILE_DIR # "gsi-zipfile-dir"
fetch_type = common._ARTIFACT_TYPE_GSI
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
logging.info("%s %s deleted", dir_key, dest_path)
zip_ref.extractall(dest_path)
self.SetFetchedDirectory(dest_path)
self.SetDeviceImage(dir_key, dest_path)
self._last_fetched_artifact_type = fetch_type
def GetDeviceImage(self, name=None):
"""Returns device image info."""
if name is None:
return self._device_images
return self._device_images[name]
def RemoveDeviceImage(self, name):
"""Removes certain device image info.
Args:
name: string, the name of the device image file
that needs to be removed.
"""
if name in self._device_images:
self._device_images.pop(name)
def SetTestSuitePackage(self, test_suite, path):
"""Sets test suite package `path` for the specified `type`.
Args:
test_suite: string, test suite type such as 'vts' or 'cts', etc.
path: string, the path of a file. if a file is a zip file,
it's unziped and its main binary is set.
"""
if re.match("[vcgs]ts", test_suite):
suite_name = "android-%s" % test_suite
tradefed_name = "%s-tradefed" % test_suite
dest_path = os.path.join(self.tmp_dirpath, suite_name)
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
logging.info("test suite %s deleted", dest_path)
with zipfile.ZipFile(path, 'r') as zip_ref:
zip_ref.extractall(dest_path)
bin_path = os.path.join(dest_path, suite_name,
"tools", tradefed_name)
os.chmod(bin_path, 0766)
path = bin_path
else:
logging.info("unsupported zip file %s", path)
self._test_suites[test_suite] = path
self._last_fetched_artifact_type = common._ARTIFACT_TYPE_TEST_SUITE
def GetTestSuitePackage(self, type=None):
"""Returns test suite package info."""
if type is None:
return self._test_suites
return self._test_suites[type]
def SetHostControllerPackage(self, package_type, path):
"""Sets host controller package `path` for the specified `type`.
Args:
package_type: string, host controller type such as 'vtslab'.
path: string, the path of a package file.
"""
self._host_controller_package[package_type] = path
self._last_fetched_artifact_type = common._ARTIFACT_TYPE_INFRA
def GetHostControllerPackage(self, package_type=None):
"""Returns host controller package info.
Args:
package_type: string, key value to self._host_controller_package
dict.
Returns:
the whole dict if package_type is None, otherwise a string which is
the path to the fetched host controller package.
"""
if package_type is None:
return self._host_controller_package
return self._host_controller_package[package_type]
def SetConfigPackage(self, config_type, path):
"""Sets test suite package `path` for the specified `type`.
All valid config files have .zip extension.
Args:
config_type: string, config type such as 'prod' or 'test'.
path: string, the path of a config file.
"""
if path.endswith(self._CONFIG_FILE_EXTENSION):
dest_path = os.path.join(
self.tmp_dirpath, os.path.basename(path) + ".dir")
with zipfile.ZipFile(path, 'r') as zip_ref:
zip_ref.extractall(dest_path)
path = dest_path
else:
logging.info("unsupported config package file %s", path)
self._configs[config_type] = path
self._last_fetched_artifact_type = common._ARTIFACT_TYPE_INFRA
def GetConfigPackage(self, config_type=None):
"""Returns config package info."""
if config_type is None:
return self._configs
return self._configs[config_type]
def SetAdditionalFile(self, rel_path, full_path):
"""Sets the key and value of additionally fetched files.
Args:
rel_path: the file path relative to temporary directory.
abs_path: the file path that this process can access.
"""
self._additional_files[rel_path] = full_path
self._last_fetched_artifact_type = common._ARTIFACT_TYPE_INFRA
def GetAdditionalFile(self, rel_path=None):
"""Returns the paths to fetched files."""
if rel_path is None:
return self._additional_files
return self._additional_files[rel_path]
def SetFetchedDirectory(self,
dir_path,
root_path=None,
full_device_images=False):
"""Adds every file in a directory to one of the dictionaries.
This method follows symlink to file, but skips symlink to directory.
Args:
dir_path: string, the directory to find files in.
root_path: string, the temporary directory that dir_path is in.
The default value is dir_path.
"""
for dir_name, file_name in utils.iterate_files(dir_path):
full_path = os.path.join(dir_name, file_name)
self.SetFetchedFile(full_path, (root_path
if root_path else dir_path),
full_device_images)
def SetFetchedFile(self,
file_path,
root_dir=None,
full_device_images=False,
set_suite_as=None):
"""Adds a file to one of the dictionaries.
Args:
file_path: string, the path to the file.
root_dir: string, the temporary directory that file_path is in.
The default value is file_path if file_path is a
directory. Otherwise, the default value is file_path's
parent directory.
set_suite_as: string, the test suite name to use for the given
artifact. Used when the file name does not follow
the standard "android-*ts.zip" file name pattern.
"""
file_name = os.path.basename(file_path)
if os.path.isdir(file_path):
self.SetFetchedDirectory(file_path, root_dir, full_device_images)
elif self._IsImageFile(file_path):
self.SetDeviceImage(file_name, file_path)
elif re.match("android-[vcgs]ts.zip", file_name):
test_suite = (file_name.split("-")[-1]).split(".")[0]
self.SetTestSuitePackage(test_suite, file_path)
elif file_name == "android-vtslab.zip":
self.SetHostControllerPackage("vtslab", file_path)
elif file_name.startswith("vti-global-config"):
self.SetConfigPackage(
"prod" if "prod" in file_name else "test", file_path)
elif set_suite_as:
self.SetTestSuitePackage(set_suite_as, file_path)
elif file_path.endswith(".zip"):
self.SetDeviceImageZip(file_path, full_device_images)
else:
rel_path = (os.path.relpath(file_path, root_dir) if root_dir else
os.path.basename(file_path))
self.SetAdditionalFile(rel_path, file_path)
def PrintDeviceImageInfo(self):
"""Prints device image info."""
logging.info(self.GetDeviceImage())
def PrintGetTestSuitePackageInfo(self):
"""Prints test suite package info."""
logging.info(self.GetTestSuitePackage())
def GetFetchedArtifactType(self):
"""Gets the most recently fetched artifact type.
Returns:
string, type of the artifact.
"""
return self._last_fetched_artifact_type
|