blob: 18c9e870d05a800431d00a3f9106eacc0f82bc3e [file] [log] [blame]
David Rileyc0da9d92016-02-01 12:11:01 -08001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This module uprevs Android for cbuildbot.
6
7After calling, it prints outs ANDROID_VERSION_ATOM=(version atom string). A
8caller could then use this atom with emerge to build the newly uprevved version
9of Android e.g.
10
Lloyd Pique636f0082017-03-18 06:01:03 +000011./cros_mark_android_as_stable
David Rileyc0da9d92016-02-01 12:11:01 -080012Returns chromeos-base/android-container-2559197
13
14emerge-veyron_minnie-cheets =chromeos-base/android-container-2559197-r1
15"""
16
17from __future__ import print_function
18
19import filecmp
20import glob
21import os
Hidehiko Abe12727dd2016-05-27 23:23:45 +090022import re
David Rileyc0da9d92016-02-01 12:11:01 -080023
Aviv Keshetb7519e12016-10-04 00:50:00 -070024from chromite.lib import constants
David Rileyc0da9d92016-02-01 12:11:01 -080025from chromite.lib import commandline
26from chromite.lib import cros_build_lib
27from chromite.lib import cros_logging as logging
28from chromite.lib import git
29from chromite.lib import gs
30from chromite.lib import portage_util
31from chromite.scripts import cros_mark_as_stable
32
33
34# Dir where all the action happens.
35_OVERLAY_DIR = '%(srcroot)s/private-overlays/project-cheets-private/'
36
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +090037_GIT_COMMIT_MESSAGE = ('Marking latest for %(android_package)s ebuild '
David Rileyc0da9d92016-02-01 12:11:01 -080038 'with version %(android_version)s as stable.')
39
40# URLs that print lists of Android revisions between two build ids.
41_ANDROID_VERSION_URL = ('http://android-build-uber.corp.google.com/repo.html?'
42 'last_bid=%(old)s&bid=%(new)s&branch=%(branch)s')
43
44
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +090045def IsBuildIdValid(bucket_url, build_branch, build_id, targets):
David Rileyc0da9d92016-02-01 12:11:01 -080046 """Checks that a specific build_id is valid.
47
48 Looks for that build_id for all builds. Confirms that the subpath can
49 be found and that the zip file is present in that subdirectory.
50
51 Args:
52 bucket_url: URL of Android build gs bucket
53 build_branch: branch of Android builds
54 build_id: A string. The Android build id number to check.
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +090055 targets: Dict from build key to (targe build suffix, artifact file pattern)
56 pair.
David Rileyc0da9d92016-02-01 12:11:01 -080057
58 Returns:
59 Returns subpaths dictionary if build_id is valid.
60 None if the build_id is not valid.
61 """
62 gs_context = gs.GSContext()
63 subpaths_dict = {}
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +090064 for build, (target, _) in targets.iteritems():
David Rileyc0da9d92016-02-01 12:11:01 -080065 build_dir = '%s-%s' % (build_branch, target)
66 build_id_path = os.path.join(bucket_url, build_dir, build_id)
67
68 # Find name of subpath.
69 try:
70 subpaths = gs_context.List(build_id_path)
71 except gs.GSNoSuchKey:
72 logging.warn(
73 'Directory [%s] does not contain any subpath, ignoring it.',
74 build_id_path)
75 return None
76 if len(subpaths) > 1:
77 logging.warn(
78 'Directory [%s] contains more than one subpath, ignoring it.',
79 build_id_path)
80 return None
81
82 subpath_dir = subpaths[0].url.rstrip('/')
83 subpath_name = os.path.basename(subpath_dir)
84
85 # Look for a zipfile ending in the build_id number.
86 try:
Hidehiko Abe12727dd2016-05-27 23:23:45 +090087 gs_context.List(subpath_dir)
David Rileyc0da9d92016-02-01 12:11:01 -080088 except gs.GSNoSuchKey:
89 logging.warn(
Hidehiko Abe12727dd2016-05-27 23:23:45 +090090 'Did not find a file for build id [%s] in directory [%s].',
David Rileyc0da9d92016-02-01 12:11:01 -080091 build_id, subpath_dir)
92 return None
93
94 # Record subpath for the build.
95 subpaths_dict[build] = subpath_name
96
97 # If we got here, it means we found an appropriate build for all platforms.
98 return subpaths_dict
99
100
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900101def GetLatestBuild(bucket_url, build_branch, targets):
David Rileyc0da9d92016-02-01 12:11:01 -0800102 """Searches the gs bucket for the latest green build.
103
104 Args:
105 bucket_url: URL of Android build gs bucket
106 build_branch: branch of Android builds
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900107 targets: Dict from build key to (targe build suffix, artifact file pattern)
108 pair.
David Rileyc0da9d92016-02-01 12:11:01 -0800109
110 Returns:
111 Tuple of (latest version string, subpaths dictionary)
112 If no latest build can be found, returns None, None
113 """
114 gs_context = gs.GSContext()
115 common_build_ids = None
116 # Find builds for each target.
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900117 for target, _ in targets.itervalues():
David Rileyc0da9d92016-02-01 12:11:01 -0800118 build_dir = '-'.join((build_branch, target))
119 base_path = os.path.join(bucket_url, build_dir)
120 build_ids = []
121 for gs_result in gs_context.List(base_path):
122 # Remove trailing slashes and get the base name, which is the build_id.
123 build_id = os.path.basename(gs_result.url.rstrip('/'))
124 if not build_id.isdigit():
125 logging.warn('Directory [%s] does not look like a valid build_id.',
126 gs_result.url)
127 continue
128 build_ids.append(build_id)
129
130 # Update current list of builds.
131 if common_build_ids is None:
132 # First run, populate it with the first platform.
133 common_build_ids = set(build_ids)
134 else:
135 # Already populated, find the ones that are common.
136 common_build_ids.intersection_update(build_ids)
137
138 if common_build_ids is None:
139 logging.warn('Did not find a build_id common to all platforms.')
140 return None, None
141
142 # Otherwise, find the most recent one that is valid.
143 for build_id in sorted(common_build_ids, key=int, reverse=True):
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900144 subpaths = IsBuildIdValid(bucket_url, build_branch, build_id, targets)
David Rileyc0da9d92016-02-01 12:11:01 -0800145 if subpaths:
146 return build_id, subpaths
147
148 # If not found, no build_id is valid.
149 logging.warn('Did not find a build_id valid on all platforms.')
150 return None, None
151
152
153def FindAndroidCandidates(package_dir):
154 """Return a tuple of Android's unstable ebuild and stable ebuilds.
155
156 Args:
157 package_dir: The path to where the package ebuild is stored.
158
159 Returns:
160 Tuple [unstable_ebuild, stable_ebuilds].
161
162 Raises:
163 Exception: if no unstable ebuild exists for Android.
164 """
165 stable_ebuilds = []
166 unstable_ebuilds = []
167 for path in glob.glob(os.path.join(package_dir, '*.ebuild')):
168 ebuild = portage_util.EBuild(path)
169 if ebuild.version == '9999':
170 unstable_ebuilds.append(ebuild)
171 else:
172 stable_ebuilds.append(ebuild)
173
174 # Apply some sanity checks.
175 if not unstable_ebuilds:
176 raise Exception('Missing 9999 ebuild for %s' % package_dir)
177 if not stable_ebuilds:
178 logging.warning('Missing stable ebuild for %s' % package_dir)
179
180 return portage_util.BestEBuild(unstable_ebuilds), stable_ebuilds
181
182
Nicolas Norvezb08f54d2016-12-05 17:58:54 -0800183def _GetArcBasename(build, basename):
184 """Tweaks filenames between Android bucket and ARC++ bucket.
185
186 Android builders create build artifacts with the same name for -user and
187 -userdebug builds, which breaks the android-container ebuild (b/33072485).
188 When copying the artifacts from the Android bucket to the ARC++ bucket some
189 artifacts will be renamed from the usual pattern
190 *cheets_${ARCH}-target_files-S{VERSION}.zip to
191 cheets_${BUILD_NAME}-target_files-S{VERSION}.zip which will typically look
192 like cheets_(${LABEL})*${ARCH}_userdebug-target_files-S{VERSION}.zip.
193
194 Args:
195 build: the build being mirrored, e.g. 'X86', 'ARM', 'X86_USERDEBUG'.
196 basename: the basename of the artifact to copy.
197
198 Returns:
199 The basename of the destination.
200 """
201 if build not in constants.ARC_BUILDS_NEED_ARTIFACTS_RENAMED:
202 return basename
203 to_discard, sep, to_keep = basename.partition('-')
204 if not sep:
205 logging.error(('Build %s: Could not find separator "-" in artifact'
206 ' basename %s'), build, basename)
207 return basename
208 if 'cheets_' not in to_discard:
209 logging.error('Build %s: Unexpected artifact basename %s',
210 build, basename)
211 return basename
212 return 'cheets_%s-%s' % (build.lower(), to_keep)
213
214
David Riley73f00d92016-02-16 18:54:20 -0800215def CopyToArcBucket(android_bucket_url, build_branch, build_id, subpaths,
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900216 targets, arc_bucket_url, acls):
David Riley73f00d92016-02-16 18:54:20 -0800217 """Copies from source Android bucket to ARC++ specific bucket.
218
219 Copies each build to the ARC bucket eliminating the subpath.
220 Applies build specific ACLs for each file.
221
222 Args:
223 android_bucket_url: URL of Android build gs bucket
224 build_branch: branch of Android builds
225 build_id: A string. The Android build id number to check.
226 subpaths: Subpath dictionary for each build to copy.
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900227 targets: Dict from build key to (targe build suffix, artifact file pattern)
228 pair.
David Riley73f00d92016-02-16 18:54:20 -0800229 arc_bucket_url: URL of the target ARC build gs bucket
230 acls: ACLs dictionary for each build to copy.
231 """
232 gs_context = gs.GSContext()
233 for build, subpath in subpaths.iteritems():
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900234 target, pattern = targets[build]
David Riley73f00d92016-02-16 18:54:20 -0800235 build_dir = '%s-%s' % (build_branch, target)
236 android_dir = os.path.join(android_bucket_url, build_dir, build_id, subpath)
237 arc_dir = os.path.join(arc_bucket_url, build_dir, build_id)
238
Hidehiko Abe12727dd2016-05-27 23:23:45 +0900239 # Copy all target files from android_dir to arc_dir, setting ACLs.
240 for targetfile in gs_context.List(android_dir):
241 if re.search(pattern, targetfile.url):
242 basename = os.path.basename(targetfile.url)
Nicolas Norvezb08f54d2016-12-05 17:58:54 -0800243 arc_path = os.path.join(arc_dir, _GetArcBasename(build, basename))
David Riley73f00d92016-02-16 18:54:20 -0800244 acl = acls[build]
245 needs_copy = True
246
247 # Check a pre-existing file with the original source.
248 if gs_context.Exists(arc_path):
Hidehiko Abe12727dd2016-05-27 23:23:45 +0900249 if (gs_context.Stat(targetfile.url).hash_crc32c !=
Elijah Taylorbfc30692016-04-22 14:05:23 -0700250 gs_context.Stat(arc_path).hash_crc32c):
David Riley73f00d92016-02-16 18:54:20 -0800251 logging.warn('Removing incorrect file %s', arc_path)
252 gs_context.Remove(arc_path)
253 else:
254 logging.info('Skipping already copied file %s', arc_path)
255 needs_copy = False
256
257 # Copy if necessary, and set the ACL unconditionally.
258 # The Stat() call above doesn't verify the ACL is correct and
259 # the ChangeACL should be relatively cheap compared to the copy.
260 # This covers the following caes:
261 # - handling an interrupted copy from a previous run.
262 # - rerunning the copy in case one of the googlestorage_acl_X.txt
263 # files changes (e.g. we add a new variant which reuses a build).
264 if needs_copy:
Hidehiko Abe12727dd2016-05-27 23:23:45 +0900265 logging.info('Copying %s -> %s (acl %s)',
266 targetfile.url, arc_path, acl)
267 gs_context.Copy(targetfile.url, arc_path, version=0)
David Riley73f00d92016-02-16 18:54:20 -0800268 gs_context.ChangeACL(arc_path, acl_args_file=acl)
269
270
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900271def MirrorArtifacts(android_bucket_url, android_build_branch, arc_bucket_url,
272 acls, targets, version=None):
273 """Mirrors artifacts from Android bucket to ARC bucket.
274
275 First, this function identifies which build version should be copied,
276 if not given. Please see GetLatestBuild() and IsBuildIdValid() for details.
277
278 On build version identified, then copies target artifacts to the ARC bucket,
279 with setting ACLs.
280
281 Args:
282 android_bucket_url: URL of Android build gs bucket
283 android_build_branch: branch of Android builds
284 arc_bucket_url: URL of the target ARC build gs bucket
285 acls: ACLs dictionary for each build to copy.
286 targets: Dict from build key to (targe build suffix, artifact file pattern)
287 pair.
288 version: (optional) A string. The Android build id number to check.
289 If not passed, detect latest good build version.
290
291 Returns:
292 Mirrored version.
293 """
294 if version:
295 subpaths = IsBuildIdValid(
296 android_bucket_url, android_build_branch, version, targets)
297 if not subpaths:
298 logging.error('Requested build %s is not valid' % version)
299 else:
300 version, subpaths = GetLatestBuild(
301 android_bucket_url, android_build_branch, targets)
302
303 CopyToArcBucket(android_bucket_url, android_build_branch, version, subpaths,
304 targets, arc_bucket_url, acls)
305 return version
306
307
David Riley73f00d92016-02-16 18:54:20 -0800308def MakeAclDict(package_dir):
309 """Creates a dictionary of acl files for each build type.
310
311 Args:
312 package_dir: The path to where the package acl files are stored.
313
314 Returns:
315 Returns acls dictionary.
316 """
317 return dict(
318 (k, os.path.join(package_dir, v))
319 for k, v in constants.ARC_BUCKET_ACLS.items()
320 )
321
322
David Rileyc0da9d92016-02-01 12:11:01 -0800323def GetAndroidRevisionListLink(build_branch, old_android, new_android):
324 """Returns a link to the list of revisions between two Android versions
325
326 Given two AndroidEBuilds, generate a link to a page that prints the
327 Android changes between those two revisions, inclusive.
328
329 Args:
330 build_branch: branch of Android builds
331 old_android: ebuild for the version to diff from
332 new_android: ebuild for the version to which to diff
333
334 Returns:
335 The desired URL.
336 """
337 return _ANDROID_VERSION_URL % {'branch': build_branch,
338 'old': old_android.version,
339 'new': new_android.version}
340
341
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900342def MarkAndroidEBuildAsStable(stable_candidate, unstable_ebuild,
343 android_package, android_version, package_dir,
344 build_branch, arc_bucket_url):
David Rileyc0da9d92016-02-01 12:11:01 -0800345 r"""Uprevs the Android ebuild.
346
347 This is the main function that uprevs from a stable candidate
348 to its new version.
349
350 Args:
351 stable_candidate: ebuild that corresponds to the stable ebuild we are
352 revving from. If None, builds the a new ebuild given the version
353 with revision set to 1.
354 unstable_ebuild: ebuild corresponding to the unstable ebuild for Android.
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900355 android_package: android package name.
David Rileyc0da9d92016-02-01 12:11:01 -0800356 android_version: The \d+ build id of Android.
David Rileyc0da9d92016-02-01 12:11:01 -0800357 package_dir: Path to the android-container package dir.
David Riley73f00d92016-02-16 18:54:20 -0800358 build_branch: branch of Android builds.
359 arc_bucket_url: URL of the target ARC build gs bucket.
David Rileyc0da9d92016-02-01 12:11:01 -0800360
361 Returns:
362 Full portage version atom (including rc's, etc) that was revved.
363 """
364 def IsTheNewEBuildRedundant(new_ebuild, stable_ebuild):
365 """Returns True if the new ebuild is redundant.
366
367 This is True if there if the current stable ebuild is the exact same copy
368 of the new one.
369 """
370 if not stable_ebuild:
371 return False
372
David Riley676f5402016-02-12 17:24:23 -0800373 if stable_candidate.version_no_rev == new_ebuild.version_no_rev:
David Rileyc0da9d92016-02-01 12:11:01 -0800374 return filecmp.cmp(
375 new_ebuild.ebuild_path, stable_ebuild.ebuild_path, shallow=False)
376
377 # Case where we have the last stable candidate with same version just rev.
David Riley676f5402016-02-12 17:24:23 -0800378 if stable_candidate and stable_candidate.version_no_rev == android_version:
David Rileyc0da9d92016-02-01 12:11:01 -0800379 new_ebuild_path = '%s-r%d.ebuild' % (
380 stable_candidate.ebuild_path_no_revision,
381 stable_candidate.current_revision + 1)
382 else:
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900383 pf = '%s-%s-r1' % (android_package, android_version)
David Rileyc0da9d92016-02-01 12:11:01 -0800384 new_ebuild_path = os.path.join(package_dir, '%s.ebuild' % pf)
385
David Riley73f00d92016-02-16 18:54:20 -0800386 variables = {'BASE_URL': arc_bucket_url}
Hidehiko Abe12727dd2016-05-27 23:23:45 +0900387 for build, (target, _) in constants.ANDROID_BUILD_TARGETS.iteritems():
David Riley73f00d92016-02-16 18:54:20 -0800388 variables[build + '_TARGET'] = '%s-%s' % (build_branch, target)
David Rileyc0da9d92016-02-01 12:11:01 -0800389
390 portage_util.EBuild.MarkAsStable(
391 unstable_ebuild.ebuild_path, new_ebuild_path,
392 variables, make_stable=True)
393 new_ebuild = portage_util.EBuild(new_ebuild_path)
394
395 # Determine whether this is ebuild is redundant.
396 if IsTheNewEBuildRedundant(new_ebuild, stable_candidate):
397 msg = 'Previous ebuild with same version found and ebuild is redundant.'
398 logging.info(msg)
399 os.unlink(new_ebuild_path)
400 return None
401
402 if stable_candidate:
403 logging.PrintBuildbotLink('Android revisions',
404 GetAndroidRevisionListLink(build_branch,
405 stable_candidate,
406 new_ebuild))
407
408 git.RunGit(package_dir, ['add', new_ebuild_path])
409 if stable_candidate and not stable_candidate.IsSticky():
410 git.RunGit(package_dir, ['rm', stable_candidate.ebuild_path])
411
412 # Update ebuild manifest and git add it.
413 gen_manifest_cmd = ['ebuild', new_ebuild_path, 'manifest', '--force']
414 cros_build_lib.RunCommand(gen_manifest_cmd,
415 extra_env=None, print_cmd=True)
416 git.RunGit(package_dir, ['add', 'Manifest'])
417
418 portage_util.EBuild.CommitChange(
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900419 _GIT_COMMIT_MESSAGE % {'android_package': android_package,
David Rileyc0da9d92016-02-01 12:11:01 -0800420 'android_version': android_version},
421 package_dir)
422
423 return '%s-%s' % (new_ebuild.package, new_ebuild.version)
424
425
426def GetParser():
427 """Creates the argument parser."""
428 parser = commandline.ArgumentParser()
429 parser.add_argument('-b', '--boards')
430 parser.add_argument('--android_bucket_url',
David Riley73f00d92016-02-16 18:54:20 -0800431 default=constants.ANDROID_BUCKET_URL,
432 type='gs_path')
David Rileyc0da9d92016-02-01 12:11:01 -0800433 parser.add_argument('--android_build_branch',
Lloyd Pique636f0082017-03-18 06:01:03 +0000434 default=constants.ANDROID_BUILD_BRANCH)
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900435 parser.add_argument('--android_gts_build_branch',
Lloyd Pique636f0082017-03-18 06:01:03 +0000436 default=constants.ANDROID_GTS_BUILD_BRANCH)
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900437 parser.add_argument('--android_package',
438 default=constants.ANDROID_PACKAGE_NAME)
David Riley73f00d92016-02-16 18:54:20 -0800439 parser.add_argument('--arc_bucket_url',
440 default=constants.ARC_BUCKET_URL,
441 type='gs_path')
David Rileyc0da9d92016-02-01 12:11:01 -0800442 parser.add_argument('-f', '--force_version',
443 help='Android build id to use')
444 parser.add_argument('-s', '--srcroot',
445 default=os.path.join(os.environ['HOME'], 'trunk', 'src'),
446 help='Path to the src directory')
447 parser.add_argument('-t', '--tracking_branch', default='cros/master',
448 help='Branch we are tracking changes against')
449 return parser
450
451
452def main(argv):
453 parser = GetParser()
454 options = parser.parse_args(argv)
455 options.Freeze()
456
457 overlay_dir = os.path.abspath(_OVERLAY_DIR % {'srcroot': options.srcroot})
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900458 android_package_dir = os.path.join(
459 overlay_dir,
460 portage_util.GetFullAndroidPortagePackageName(options.android_package))
David Rileyc0da9d92016-02-01 12:11:01 -0800461 version_to_uprev = None
David Rileyc0da9d92016-02-01 12:11:01 -0800462
463 (unstable_ebuild, stable_ebuilds) = FindAndroidCandidates(android_package_dir)
David Riley73f00d92016-02-16 18:54:20 -0800464 acls = MakeAclDict(android_package_dir)
Hidehiko Abe1ebc25d2016-07-28 02:24:37 +0900465 # Mirror artifacts, i.e., images and some sdk tools (e.g., adb, aapt).
466 version_to_uprev = MirrorArtifacts(options.android_bucket_url,
467 options.android_build_branch,
468 options.arc_bucket_url, acls,
469 constants.ANDROID_BUILD_TARGETS,
470 options.force_version)
471
472 # Mirror GTS.
473 MirrorArtifacts(options.android_bucket_url,
474 options.android_gts_build_branch,
475 options.arc_bucket_url, acls,
476 constants.ANDROID_GTS_BUILD_TARGETS)
David Riley73f00d92016-02-16 18:54:20 -0800477
David Rileyc0da9d92016-02-01 12:11:01 -0800478 stable_candidate = portage_util.BestEBuild(stable_ebuilds)
479
480 if stable_candidate:
David Riley676f5402016-02-12 17:24:23 -0800481 logging.info('Stable candidate found %s' % stable_candidate.version)
David Rileyc0da9d92016-02-01 12:11:01 -0800482 else:
483 logging.info('No stable candidate found.')
484
485 tracking_branch = 'remotes/m/%s' % os.path.basename(options.tracking_branch)
486 existing_branch = git.GetCurrentBranch(android_package_dir)
487 work_branch = cros_mark_as_stable.GitBranch(constants.STABLE_EBUILD_BRANCH,
488 tracking_branch,
489 android_package_dir)
490 work_branch.CreateBranch()
491
492 # In the case of uprevving overlays that have patches applied to them,
493 # include the patched changes in the stabilizing branch.
494 if existing_branch:
495 git.RunGit(overlay_dir, ['rebase', existing_branch])
496
497 android_version_atom = MarkAndroidEBuildAsStable(
Hidehiko Abe4fd94ae2017-01-24 18:59:55 +0900498 stable_candidate, unstable_ebuild, options.android_package,
David Riley73f00d92016-02-16 18:54:20 -0800499 version_to_uprev, android_package_dir,
500 options.android_build_branch, options.arc_bucket_url)
David Rileyc0da9d92016-02-01 12:11:01 -0800501 if android_version_atom:
502 if options.boards:
503 cros_mark_as_stable.CleanStalePackages(options.srcroot,
504 options.boards.split(':'),
505 [android_version_atom])
506
507 # Explicit print to communicate to caller.
508 print('ANDROID_VERSION_ATOM=%s' % android_version_atom)