blob: afba671441a464fdb3fcc03975ae7743fb68790d [file] [log] [blame]
Chris Sosa47a7d4e2012-03-28 11:26:55 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 14:29:08 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Helper class for interacting with the Dev Server."""
6
7import cherrypy
8import distutils.version
9import errno
10import os
Chris Masone816e38c2012-05-02 12:22:36 -070011import random
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070012import re
Frank Farzan37761d12011-12-01 14:29:08 -080013import shutil
Chris Masone816e38c2012-05-02 12:22:36 -070014import time
Frank Farzan37761d12011-12-01 14:29:08 -080015
Chris Sosa47a7d4e2012-03-28 11:26:55 -070016import downloadable_artifact
17import gsutil_util
Frank Farzan37761d12011-12-01 14:29:08 -080018
19AU_BASE = 'au'
20NTON_DIR_SUFFIX = '_nton'
21MTON_DIR_SUFFIX = '_mton'
Frank Farzan37761d12011-12-01 14:29:08 -080022DEV_BUILD_PREFIX = 'dev'
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070023UPLOADED_LIST = 'UPLOADED'
Frank Farzan37761d12011-12-01 14:29:08 -080024
25class DevServerUtilError(Exception):
26 """Exception classes used by this module."""
27 pass
28
29
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070030def ParsePayloadList(archive_url, payload_list):
Frank Farzan37761d12011-12-01 14:29:08 -080031 """Parse and return the full/delta payload URLs.
32
33 Args:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070034 archive_url: The URL of the Google Storage bucket.
35 payload_list: A list filenames.
Frank Farzan37761d12011-12-01 14:29:08 -080036
37 Returns:
38 Tuple of 3 payloads URLs: (full, nton, mton).
39
40 Raises:
41 DevServerUtilError: If payloads missing or invalid.
42 """
43 full_payload_url = None
44 mton_payload_url = None
45 nton_payload_url = None
46 for payload in payload_list:
47 if '_full_' in payload:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070048 full_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080049 elif '_delta_' in payload:
50 # e.g. chromeos_{from_version}_{to_version}_x86-generic_delta_dev.bin
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070051 from_version, to_version = payload.split('_')[1:3]
Frank Farzan37761d12011-12-01 14:29:08 -080052 if from_version == to_version:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070053 nton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080054 else:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070055 mton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080056
Chris Sosa1228a1a2012-05-22 17:12:13 -070057 if not full_payload_url:
Frank Farzan37761d12011-12-01 14:29:08 -080058 raise DevServerUtilError(
Chris Sosa1228a1a2012-05-22 17:12:13 -070059 'Full payload is missing or has unexpected name format.', payload_list)
Frank Farzan37761d12011-12-01 14:29:08 -080060
61 return full_payload_url, nton_payload_url, mton_payload_url
62
63
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070064def IsAvailable(pattern_list, uploaded_list):
65 """Checks whether the target artifacts we wait for are available.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070066
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070067 This method searches the uploaded_list for a match for every pattern
68 in the pattern_list. It aborts and returns false if no filename
69 matches a given pattern.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070070
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070071 Args:
72 pattern_list: List of regular expression patterns to identify
73 the target artifacts.
74 uploaded_list: List of all uploaded files.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070075
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070076 Returns:
77 True if there is a match for every pattern; false otherwise.
78 """
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070079
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070080 # Pre-compile the regular expression patterns
81 compiled_patterns = []
82 for p in pattern_list:
83 compiled_patterns.append(re.compile(p))
84
85 for pattern in compiled_patterns:
86 found = False
87 for filename in uploaded_list:
88 if re.search(pattern, filename):
89 found = True
90 break
91 if not found:
92 return False
93
94 return True
95
96
97def WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=600,
98 delay=10):
99 """Waits until all target artifacts are available in Google Storage or
100 until the request times out.
101
102 This method polls Google Storage until all target artifacts are
103 available or until the timeout occurs. Because we may not know the
104 exact name of the target artifacts, the method accepts to_wait_list, a
105 list of filename patterns, to identify whether an artifact whose name
106 matches the pattern exists (e.g. use pattern '_full_' to search for
107 the full payload 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin').
108
109 Args:
110 to_wait_list: List of regular expression patterns to identify
111 the target artifacts.
112 archive_url: URL of the Google Storage bucket.
113 err_str: String to display in the error message.
114
115 Returns:
116 The list of artifacts in the Google Storage bucket.
117
118 Raises:
119 DevServerUtilError: If timeout occurs.
120 """
121
122 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
123 msg = 'Failed to get a list of uploaded files.'
124
125 deadline = time.time() + timeout
126 while time.time() < deadline:
127 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
128 # Run "gsutil cat" to retrieve the list
129 uploaded_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
130 # Check if all target artifacts are available
131 if IsAvailable(to_wait_list, uploaded_list):
132 return uploaded_list
133 cherrypy.log('Retrying in %f seconds...%s' % (to_delay, err_str))
134 time.sleep(to_delay)
135
136 raise DevServerUtilError('Missing %s for %s.' % (err_str, archive_url))
137
138
139def GatherArtifactDownloads(main_staging_dir, archive_url, build, build_dir,
140 timeout=600, delay=10):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700141 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -0800142
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700143 This method generates the list of artifacts we will need for autotest. These
Chris Masone816e38c2012-05-02 12:22:36 -0700144 artifacts are instances of downloadable_artifact.DownloadableArtifact.
145
146 Note, these artifacts can be downloaded asynchronously iff
147 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700148 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700149
150 # Wait up to 10 minutes for the full payload to be uploaded because we
151 # do not know the exact name of the full payload.
152
153 # We also wait for 'autotest.tar' because we do not know what type of
154 # autotest tarballs (tar or tar.bz2) is available
155 # (crosbug.com/32312). This dependency can be removed once all
156 # branches move to the new 'tar' format.
157 to_wait_list = ['_full_', 'autotest.tar']
158 err_str = 'full payload or autotest tarball'
159 uploaded_list = WaitUntilAvailable(to_wait_list, archive_url, err_str,
160 timeout=600)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700161
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700162 # First we gather the urls/paths for the update payloads.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700163 full_url, nton_url, mton_url = ParsePayloadList(archive_url, uploaded_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700164
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700165 full_payload = os.path.join(build_dir, downloadable_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700166
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700167 artifacts = []
168 artifacts.append(downloadable_artifact.DownloadableArtifact(full_url,
169 main_staging_dir, full_payload, synchronous=True))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700170
171 if nton_url:
172 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
173 downloadable_artifact.ROOT_UPDATE)
174 artifacts.append(downloadable_artifact.AUTestPayload(nton_url,
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700175 main_staging_dir, nton_payload))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700176
Chris Sosa781ba6d2012-04-11 12:44:43 -0700177 if mton_url:
178 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
179 downloadable_artifact.ROOT_UPDATE)
180 artifacts.append(downloadable_artifact.AUTestPayload(
181 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700182
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700183
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700184 # Gather information about autotest tarballs. Use autotest.tar if available.
185 if downloadable_artifact.AUTOTEST_PACKAGE in uploaded_list:
186 autotest_url = '%s/%s' % (archive_url,
187 downloadable_artifact.AUTOTEST_PACKAGE)
188 else:
189 # Use autotest.tar.bz for backward compatibility. This can be
190 # removed once all branches start using "autotest.tar"
191 autotest_url = '%s/%s' % (archive_url,
192 downloadable_artifact.AUTOTEST_ZIPPED_PACKAGE)
193
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700194 # Next we gather the miscellaneous payloads.
195 stateful_url = archive_url + '/' + downloadable_artifact.STATEFUL_UPDATE
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700196 test_suites_url = (archive_url + '/' +
197 downloadable_artifact.TEST_SUITES_PACKAGE)
198
199 stateful_payload = os.path.join(build_dir,
200 downloadable_artifact.STATEFUL_UPDATE)
201
202 artifacts.append(downloadable_artifact.DownloadableArtifact(
203 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
204 artifacts.append(downloadable_artifact.AutotestTarball(
205 autotest_url, main_staging_dir, build_dir))
206 artifacts.append(downloadable_artifact.Tarball(
207 test_suites_url, main_staging_dir, build_dir, synchronous=True))
208 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700209
210
Chris Masone816e38c2012-05-02 12:22:36 -0700211def GatherSymbolArtifactDownloads(temp_download_dir, archive_url, staging_dir,
212 timeout=600, delay=10):
213 """Generates debug symbol artifacts that we mean to download and stage.
214
215 This method generates the list of artifacts we will need to
216 symbolicate crash dumps that occur during autotest runs. These
217 artifacts are instances of downloadable_artifact.DownloadableArtifact.
218
219 This will poll google storage until the debug symbol artifact becomes
220 available, or until the 10 minute timeout is up.
221
222 @param temp_download_dir: the tempdir into which we're downloading artifacts
223 prior to staging them.
224 @param archive_url: the google storage url of the bucket where the debug
225 symbols for the desired build are stored.
226 @param staging_dir: the dir into which to stage the symbols
227
228 @return an iterable of one DebugTarball pointing to the right debug symbols.
229 This is an iterable so that it's similar to GatherArtifactDownloads.
230 Also, it's possible that someday we might have more than one.
231 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700232
233 # Wait up to 10 minutes for the debug symbols to be uploaded.
234 to_wait_list = [downloadable_artifact.DEBUG_SYMBOLS]
235 err_str = 'debug symbols'
236 WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=timeout,
237 delay=delay)
238
Chris Masone816e38c2012-05-02 12:22:36 -0700239 symbol_url = archive_url + '/' + downloadable_artifact.DEBUG_SYMBOLS
Chris Masone816e38c2012-05-02 12:22:36 -0700240 return [downloadable_artifact.DebugTarball(symbol_url, temp_download_dir,
241 staging_dir)]
242
243
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700244def PrepareBuildDirectory(build_dir):
245 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700246
247 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800248 build_dir: Directory to install build components into.
249 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700250 if not os.path.isdir(build_dir):
251 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800252
253 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
254 # try to rebuild it unnecessarily.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700255 test_image = os.path.join(build_dir, downloadable_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800256 open(test_image, 'a').close()
257
Frank Farzan37761d12011-12-01 14:29:08 -0800258
259def SafeSandboxAccess(static_dir, path):
260 """Verify that the path is in static_dir.
261
262 Args:
263 static_dir: Directory where builds are served from.
264 path: Path to verify.
265
266 Returns:
267 True if path is in static_dir, False otherwise
268 """
269 static_dir = os.path.realpath(static_dir)
270 path = os.path.realpath(path)
271 return (path.startswith(static_dir) and path != static_dir)
272
273
274def AcquireLock(static_dir, tag):
275 """Acquires a lock for a given tag.
276
277 Creates a directory for the specified tag, telling other
278 components the resource/task represented by the tag is unavailable.
279
280 Args:
281 static_dir: Directory where builds are served from.
282 tag: Unique resource/task identifier. Use '/' for nested tags.
283
284 Returns:
285 Path to the created directory or None if creation failed.
286
287 Raises:
288 DevServerUtilError: If lock can't be acquired.
289 """
290 build_dir = os.path.join(static_dir, tag)
291 if not SafeSandboxAccess(static_dir, build_dir):
Chris Sosa9164ca32012-03-28 11:04:50 -0700292 raise DevServerUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800293
294 try:
295 os.makedirs(build_dir)
296 except OSError, e:
297 if e.errno == errno.EEXIST:
298 raise DevServerUtilError(str(e))
299 else:
300 raise
301
302 return build_dir
303
304
305def ReleaseLock(static_dir, tag):
306 """Releases the lock for a given tag. Removes lock directory content.
307
308 Args:
309 static_dir: Directory where builds are served from.
310 tag: Unique resource/task identifier. Use '/' for nested tags.
311
312 Raises:
313 DevServerUtilError: If lock can't be released.
314 """
315 build_dir = os.path.join(static_dir, tag)
316 if not SafeSandboxAccess(static_dir, build_dir):
317 raise DevServerUtilError('Invaid tag "%s".' % tag)
318
319 shutil.rmtree(build_dir)
320
321
322def FindMatchingBoards(static_dir, board):
323 """Returns a list of boards given a partial board name.
324
325 Args:
326 static_dir: Directory where builds are served from.
327 board: Partial board name for this build; e.g. x86-generic.
328
329 Returns:
330 Returns a list of boards given a partial board.
331 """
332 return [brd for brd in os.listdir(static_dir) if board in brd]
333
334
335def FindMatchingBuilds(static_dir, board, build):
336 """Returns a list of matching builds given a board and partial build.
337
338 Args:
339 static_dir: Directory where builds are served from.
340 board: Partial board name for this build; e.g. x86-generic-release.
341 build: Partial build string to look for; e.g. R17-1234.
342
343 Returns:
344 Returns a list of (board, build) tuples given a partial board and build.
345 """
346 matches = []
347 for brd in FindMatchingBoards(static_dir, board):
348 a = [(brd, bld) for bld in
349 os.listdir(os.path.join(static_dir, brd)) if build in bld]
350 matches.extend(a)
351 return matches
352
353
Scott Zawalski16954532012-03-20 15:31:36 -0400354def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800355 """Retrieves the latest build version for a given board.
356
357 Args:
358 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400359 target: The build target, typically a combination of the board and the
360 type of build e.g. x86-mario-release.
361 milestone: For latest build set to None, for builds only in a specific
362 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800363
364 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400365 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
366 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800367
368 Raises:
Scott Zawalski16954532012-03-20 15:31:36 -0400369 DevServerUtilError: If for some reason the latest build cannot be
370 deteremined, this could be due to the dir not existing or no builds
371 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800372 """
Scott Zawalski16954532012-03-20 15:31:36 -0400373 target_path = os.path.join(static_dir, target)
374 if not os.path.isdir(target_path):
375 raise DevServerUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800376
Scott Zawalski16954532012-03-20 15:31:36 -0400377 builds = [distutils.version.LooseVersion(build) for build in
378 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800379
Scott Zawalski16954532012-03-20 15:31:36 -0400380 if milestone and builds:
381 # Check if milestone Rxx is in the string representation of the build.
382 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800383
Scott Zawalski16954532012-03-20 15:31:36 -0400384 if not builds:
385 raise DevServerUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800386
Scott Zawalski16954532012-03-20 15:31:36 -0400387 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800388
389
390def CloneBuild(static_dir, board, build, tag, force=False):
391 """Clone an official build into the developer sandbox.
392
393 Developer sandbox directory must already exist.
394
395 Args:
396 static_dir: Directory where builds are served from.
397 board: Fully qualified board name; e.g. x86-generic-release.
398 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
399 tag: Unique resource/task identifier. Use '/' for nested tags.
400 force: Force re-creation of build_dir even if it already exists.
401
402 Returns:
403 The path to the new build.
404 """
405 # Create the developer build directory.
406 dev_static_dir = os.path.join(static_dir, DEV_BUILD_PREFIX)
407 dev_build_dir = os.path.join(dev_static_dir, tag)
408 official_build_dir = os.path.join(static_dir, board, build)
409 cherrypy.log('Cloning %s -> %s' % (official_build_dir, dev_build_dir),
410 'DEVSERVER_UTIL')
411 dev_build_exists = False
412 try:
413 AcquireLock(dev_static_dir, tag)
414 except DevServerUtilError:
415 dev_build_exists = True
416 if force:
417 dev_build_exists = False
418 ReleaseLock(dev_static_dir, tag)
419 AcquireLock(dev_static_dir, tag)
420
421 # Make a copy of the official build, only take necessary files.
422 if not dev_build_exists:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700423 copy_list = [downloadable_artifact.TEST_IMAGE,
424 downloadable_artifact.ROOT_UPDATE,
425 downloadable_artifact.STATEFUL_UPDATE]
Frank Farzan37761d12011-12-01 14:29:08 -0800426 for f in copy_list:
427 shutil.copy(os.path.join(official_build_dir, f), dev_build_dir)
428
429 return dev_build_dir
430
Scott Zawalski84a39c92012-01-13 15:12:42 -0500431
432def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800433 """Attempts to pull the requested control file from the Dev Server.
434
435 Args:
436 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800437 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
438 control_path: Path to control file on Dev Server relative to Autotest root.
439
440 Raises:
441 DevServerUtilError: If lock can't be acquired.
442
443 Returns:
444 Content of the requested control file.
445 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500446 # Be forgiving if the user passes in the control_path with a leading /
447 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500448 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500449 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800450 if not SafeSandboxAccess(static_dir, control_path):
451 raise DevServerUtilError('Invaid control file "%s".' % control_path)
452
Scott Zawalski84a39c92012-01-13 15:12:42 -0500453 if not os.path.exists(control_path):
454 # TODO(scottz): Come up with some sort of error mechanism.
455 # crosbug.com/25040
456 return 'Unknown control path %s' % control_path
457
Frank Farzan37761d12011-12-01 14:29:08 -0800458 with open(control_path, 'r') as control_file:
459 return control_file.read()
460
461
Scott Zawalski84a39c92012-01-13 15:12:42 -0500462def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500463 """List all control|control. files in the specified board/build path.
464
465 Args:
466 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500467 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
468
469 Raises:
470 DevServerUtilError: If path is outside of sandbox.
471
472 Returns:
473 String of each file separated by a newline.
474 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500475 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500476 if not SafeSandboxAccess(static_dir, autotest_dir):
477 raise DevServerUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
478
479 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500480 if not os.path.exists(autotest_dir):
481 # TODO(scottz): Come up with some sort of error mechanism.
482 # crosbug.com/25040
483 return 'Unknown build path %s' % autotest_dir
484
Scott Zawalski4647ce62012-01-03 17:17:28 -0500485 for entry in os.walk(autotest_dir):
486 dir_path, _, files = entry
487 for file_entry in files:
488 if file_entry.startswith('control.') or file_entry == 'control':
489 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800490 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500491
492 return '\n'.join(control_files)
493
494
Frank Farzan37761d12011-12-01 14:29:08 -0800495def ListAutoupdateTargets(static_dir, board, build):
496 """Returns a list of autoupdate test targets for the given board, build.
497
498 Args:
499 static_dir: Directory where builds are served from.
500 board: Fully qualified board name; e.g. x86-generic-release.
501 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
502
503 Returns:
504 List of autoupdate test targets; e.g. ['0.14.747.0-r2bf8859c-b2927_nton']
505 """
506 return os.listdir(os.path.join(static_dir, board, build, AU_BASE))