blob: 8dc47b6ae680b48d9b2bc1599c067d529d92df04 [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:
Yu-Ju Hong92784052012-08-22 13:08:54 -0700127 uploaded_list = []
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700128 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
Yu-Ju Hong92784052012-08-22 13:08:54 -0700129 try:
130 # Run "gsutil cat" to retrieve the list.
131 uploaded_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
132 except gsutil_util.GSUtilError:
133 # For backward compatibility, fallling back to use "gsutil ls"
134 # when the manifest file is not present.
135 cmd = 'gsutil ls %s/*' % archive_url
136 msg = 'Failed to list payloads.'
137 payload_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
138 for payload in payload_list:
139 uploaded_list.append(payload.rsplit('/', 1)[1])
140
141 # Check if all target artifacts are available.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700142 if IsAvailable(to_wait_list, uploaded_list):
143 return uploaded_list
144 cherrypy.log('Retrying in %f seconds...%s' % (to_delay, err_str))
145 time.sleep(to_delay)
146
147 raise DevServerUtilError('Missing %s for %s.' % (err_str, archive_url))
148
149
150def GatherArtifactDownloads(main_staging_dir, archive_url, build, build_dir,
151 timeout=600, delay=10):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700152 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -0800153
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700154 This method generates the list of artifacts we will need for autotest. These
Chris Masone816e38c2012-05-02 12:22:36 -0700155 artifacts are instances of downloadable_artifact.DownloadableArtifact.
156
157 Note, these artifacts can be downloaded asynchronously iff
158 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700159 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700160
161 # Wait up to 10 minutes for the full payload to be uploaded because we
162 # do not know the exact name of the full payload.
163
164 # We also wait for 'autotest.tar' because we do not know what type of
165 # autotest tarballs (tar or tar.bz2) is available
166 # (crosbug.com/32312). This dependency can be removed once all
167 # branches move to the new 'tar' format.
168 to_wait_list = ['_full_', 'autotest.tar']
169 err_str = 'full payload or autotest tarball'
170 uploaded_list = WaitUntilAvailable(to_wait_list, archive_url, err_str,
171 timeout=600)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700172
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700173 # First we gather the urls/paths for the update payloads.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700174 full_url, nton_url, mton_url = ParsePayloadList(archive_url, uploaded_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700175
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700176 full_payload = os.path.join(build_dir, downloadable_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700177
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700178 artifacts = []
179 artifacts.append(downloadable_artifact.DownloadableArtifact(full_url,
180 main_staging_dir, full_payload, synchronous=True))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700181
182 if nton_url:
183 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
184 downloadable_artifact.ROOT_UPDATE)
185 artifacts.append(downloadable_artifact.AUTestPayload(nton_url,
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700186 main_staging_dir, nton_payload))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700187
Chris Sosa781ba6d2012-04-11 12:44:43 -0700188 if mton_url:
189 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
190 downloadable_artifact.ROOT_UPDATE)
191 artifacts.append(downloadable_artifact.AUTestPayload(
192 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700193
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700194
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700195 # Gather information about autotest tarballs. Use autotest.tar if available.
196 if downloadable_artifact.AUTOTEST_PACKAGE in uploaded_list:
197 autotest_url = '%s/%s' % (archive_url,
198 downloadable_artifact.AUTOTEST_PACKAGE)
199 else:
200 # Use autotest.tar.bz for backward compatibility. This can be
201 # removed once all branches start using "autotest.tar"
202 autotest_url = '%s/%s' % (archive_url,
203 downloadable_artifact.AUTOTEST_ZIPPED_PACKAGE)
204
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700205 # Next we gather the miscellaneous payloads.
206 stateful_url = archive_url + '/' + downloadable_artifact.STATEFUL_UPDATE
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700207 test_suites_url = (archive_url + '/' +
208 downloadable_artifact.TEST_SUITES_PACKAGE)
209
210 stateful_payload = os.path.join(build_dir,
211 downloadable_artifact.STATEFUL_UPDATE)
212
213 artifacts.append(downloadable_artifact.DownloadableArtifact(
214 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
215 artifacts.append(downloadable_artifact.AutotestTarball(
216 autotest_url, main_staging_dir, build_dir))
217 artifacts.append(downloadable_artifact.Tarball(
218 test_suites_url, main_staging_dir, build_dir, synchronous=True))
219 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700220
221
Chris Masone816e38c2012-05-02 12:22:36 -0700222def GatherSymbolArtifactDownloads(temp_download_dir, archive_url, staging_dir,
223 timeout=600, delay=10):
224 """Generates debug symbol artifacts that we mean to download and stage.
225
226 This method generates the list of artifacts we will need to
227 symbolicate crash dumps that occur during autotest runs. These
228 artifacts are instances of downloadable_artifact.DownloadableArtifact.
229
230 This will poll google storage until the debug symbol artifact becomes
231 available, or until the 10 minute timeout is up.
232
233 @param temp_download_dir: the tempdir into which we're downloading artifacts
234 prior to staging them.
235 @param archive_url: the google storage url of the bucket where the debug
236 symbols for the desired build are stored.
237 @param staging_dir: the dir into which to stage the symbols
238
239 @return an iterable of one DebugTarball pointing to the right debug symbols.
240 This is an iterable so that it's similar to GatherArtifactDownloads.
241 Also, it's possible that someday we might have more than one.
242 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700243
244 # Wait up to 10 minutes for the debug symbols to be uploaded.
245 to_wait_list = [downloadable_artifact.DEBUG_SYMBOLS]
246 err_str = 'debug symbols'
247 WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=timeout,
248 delay=delay)
249
Chris Masone816e38c2012-05-02 12:22:36 -0700250 symbol_url = archive_url + '/' + downloadable_artifact.DEBUG_SYMBOLS
Chris Masone816e38c2012-05-02 12:22:36 -0700251 return [downloadable_artifact.DebugTarball(symbol_url, temp_download_dir,
252 staging_dir)]
253
254
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700255def PrepareBuildDirectory(build_dir):
256 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700257
258 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800259 build_dir: Directory to install build components into.
260 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700261 if not os.path.isdir(build_dir):
262 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800263
264 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
265 # try to rebuild it unnecessarily.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700266 test_image = os.path.join(build_dir, downloadable_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800267 open(test_image, 'a').close()
268
Frank Farzan37761d12011-12-01 14:29:08 -0800269
270def SafeSandboxAccess(static_dir, path):
271 """Verify that the path is in static_dir.
272
273 Args:
274 static_dir: Directory where builds are served from.
275 path: Path to verify.
276
277 Returns:
278 True if path is in static_dir, False otherwise
279 """
280 static_dir = os.path.realpath(static_dir)
281 path = os.path.realpath(path)
282 return (path.startswith(static_dir) and path != static_dir)
283
284
285def AcquireLock(static_dir, tag):
286 """Acquires a lock for a given tag.
287
288 Creates a directory for the specified tag, telling other
289 components the resource/task represented by the tag is unavailable.
290
291 Args:
292 static_dir: Directory where builds are served from.
293 tag: Unique resource/task identifier. Use '/' for nested tags.
294
295 Returns:
296 Path to the created directory or None if creation failed.
297
298 Raises:
299 DevServerUtilError: If lock can't be acquired.
300 """
301 build_dir = os.path.join(static_dir, tag)
302 if not SafeSandboxAccess(static_dir, build_dir):
Chris Sosa9164ca32012-03-28 11:04:50 -0700303 raise DevServerUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800304
305 try:
306 os.makedirs(build_dir)
307 except OSError, e:
308 if e.errno == errno.EEXIST:
309 raise DevServerUtilError(str(e))
310 else:
311 raise
312
313 return build_dir
314
315
316def ReleaseLock(static_dir, tag):
317 """Releases the lock for a given tag. Removes lock directory content.
318
319 Args:
320 static_dir: Directory where builds are served from.
321 tag: Unique resource/task identifier. Use '/' for nested tags.
322
323 Raises:
324 DevServerUtilError: If lock can't be released.
325 """
326 build_dir = os.path.join(static_dir, tag)
327 if not SafeSandboxAccess(static_dir, build_dir):
328 raise DevServerUtilError('Invaid tag "%s".' % tag)
329
330 shutil.rmtree(build_dir)
331
332
333def FindMatchingBoards(static_dir, board):
334 """Returns a list of boards given a partial board name.
335
336 Args:
337 static_dir: Directory where builds are served from.
338 board: Partial board name for this build; e.g. x86-generic.
339
340 Returns:
341 Returns a list of boards given a partial board.
342 """
343 return [brd for brd in os.listdir(static_dir) if board in brd]
344
345
346def FindMatchingBuilds(static_dir, board, build):
347 """Returns a list of matching builds given a board and partial build.
348
349 Args:
350 static_dir: Directory where builds are served from.
351 board: Partial board name for this build; e.g. x86-generic-release.
352 build: Partial build string to look for; e.g. R17-1234.
353
354 Returns:
355 Returns a list of (board, build) tuples given a partial board and build.
356 """
357 matches = []
358 for brd in FindMatchingBoards(static_dir, board):
359 a = [(brd, bld) for bld in
360 os.listdir(os.path.join(static_dir, brd)) if build in bld]
361 matches.extend(a)
362 return matches
363
364
Scott Zawalski16954532012-03-20 15:31:36 -0400365def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800366 """Retrieves the latest build version for a given board.
367
368 Args:
369 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400370 target: The build target, typically a combination of the board and the
371 type of build e.g. x86-mario-release.
372 milestone: For latest build set to None, for builds only in a specific
373 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800374
375 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400376 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
377 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800378
379 Raises:
Scott Zawalski16954532012-03-20 15:31:36 -0400380 DevServerUtilError: If for some reason the latest build cannot be
381 deteremined, this could be due to the dir not existing or no builds
382 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800383 """
Scott Zawalski16954532012-03-20 15:31:36 -0400384 target_path = os.path.join(static_dir, target)
385 if not os.path.isdir(target_path):
386 raise DevServerUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800387
Scott Zawalski16954532012-03-20 15:31:36 -0400388 builds = [distutils.version.LooseVersion(build) for build in
389 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800390
Scott Zawalski16954532012-03-20 15:31:36 -0400391 if milestone and builds:
392 # Check if milestone Rxx is in the string representation of the build.
393 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800394
Scott Zawalski16954532012-03-20 15:31:36 -0400395 if not builds:
396 raise DevServerUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800397
Scott Zawalski16954532012-03-20 15:31:36 -0400398 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800399
400
401def CloneBuild(static_dir, board, build, tag, force=False):
402 """Clone an official build into the developer sandbox.
403
404 Developer sandbox directory must already exist.
405
406 Args:
407 static_dir: Directory where builds are served from.
408 board: Fully qualified board name; e.g. x86-generic-release.
409 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
410 tag: Unique resource/task identifier. Use '/' for nested tags.
411 force: Force re-creation of build_dir even if it already exists.
412
413 Returns:
414 The path to the new build.
415 """
416 # Create the developer build directory.
417 dev_static_dir = os.path.join(static_dir, DEV_BUILD_PREFIX)
418 dev_build_dir = os.path.join(dev_static_dir, tag)
419 official_build_dir = os.path.join(static_dir, board, build)
420 cherrypy.log('Cloning %s -> %s' % (official_build_dir, dev_build_dir),
421 'DEVSERVER_UTIL')
422 dev_build_exists = False
423 try:
424 AcquireLock(dev_static_dir, tag)
425 except DevServerUtilError:
426 dev_build_exists = True
427 if force:
428 dev_build_exists = False
429 ReleaseLock(dev_static_dir, tag)
430 AcquireLock(dev_static_dir, tag)
431
432 # Make a copy of the official build, only take necessary files.
433 if not dev_build_exists:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700434 copy_list = [downloadable_artifact.TEST_IMAGE,
435 downloadable_artifact.ROOT_UPDATE,
436 downloadable_artifact.STATEFUL_UPDATE]
Frank Farzan37761d12011-12-01 14:29:08 -0800437 for f in copy_list:
438 shutil.copy(os.path.join(official_build_dir, f), dev_build_dir)
439
440 return dev_build_dir
441
Scott Zawalski84a39c92012-01-13 15:12:42 -0500442
443def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800444 """Attempts to pull the requested control file from the Dev Server.
445
446 Args:
447 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800448 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
449 control_path: Path to control file on Dev Server relative to Autotest root.
450
451 Raises:
452 DevServerUtilError: If lock can't be acquired.
453
454 Returns:
455 Content of the requested control file.
456 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500457 # Be forgiving if the user passes in the control_path with a leading /
458 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500459 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500460 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800461 if not SafeSandboxAccess(static_dir, control_path):
462 raise DevServerUtilError('Invaid control file "%s".' % control_path)
463
Scott Zawalski84a39c92012-01-13 15:12:42 -0500464 if not os.path.exists(control_path):
465 # TODO(scottz): Come up with some sort of error mechanism.
466 # crosbug.com/25040
467 return 'Unknown control path %s' % control_path
468
Frank Farzan37761d12011-12-01 14:29:08 -0800469 with open(control_path, 'r') as control_file:
470 return control_file.read()
471
472
Scott Zawalski84a39c92012-01-13 15:12:42 -0500473def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500474 """List all control|control. files in the specified board/build path.
475
476 Args:
477 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500478 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
479
480 Raises:
481 DevServerUtilError: If path is outside of sandbox.
482
483 Returns:
484 String of each file separated by a newline.
485 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500486 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500487 if not SafeSandboxAccess(static_dir, autotest_dir):
488 raise DevServerUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
489
490 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500491 if not os.path.exists(autotest_dir):
492 # TODO(scottz): Come up with some sort of error mechanism.
493 # crosbug.com/25040
494 return 'Unknown build path %s' % autotest_dir
495
Scott Zawalski4647ce62012-01-03 17:17:28 -0500496 for entry in os.walk(autotest_dir):
497 dir_path, _, files = entry
498 for file_entry in files:
499 if file_entry.startswith('control.') or file_entry == 'control':
500 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800501 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500502
503 return '\n'.join(control_files)
504
505
Frank Farzan37761d12011-12-01 14:29:08 -0800506def ListAutoupdateTargets(static_dir, board, build):
507 """Returns a list of autoupdate test targets for the given board, build.
508
509 Args:
510 static_dir: Directory where builds are served from.
511 board: Fully qualified board name; e.g. x86-generic-release.
512 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
513
514 Returns:
515 List of autoupdate test targets; e.g. ['0.14.747.0-r2bf8859c-b2927_nton']
516 """
517 return os.listdir(os.path.join(static_dir, board, build, AU_BASE))