blob: cab3e6dafd4346389b152f7fb583f672bbcee47e [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
11./cros_mark_android_as_stable
12Returns 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
22
23from chromite.cbuildbot import constants
24from chromite.lib import commandline
25from chromite.lib import cros_build_lib
26from chromite.lib import cros_logging as logging
27from chromite.lib import git
28from chromite.lib import gs
29from chromite.lib import portage_util
30from chromite.scripts import cros_mark_as_stable
31
32
33# Dir where all the action happens.
34_OVERLAY_DIR = '%(srcroot)s/private-overlays/project-cheets-private/'
35
36_GIT_COMMIT_MESSAGE = ('Marking latest for %(android_pn)s ebuild '
37 'with version %(android_version)s as stable.')
38
39# URLs that print lists of Android revisions between two build ids.
40_ANDROID_VERSION_URL = ('http://android-build-uber.corp.google.com/repo.html?'
41 'last_bid=%(old)s&bid=%(new)s&branch=%(branch)s')
42
43
44def IsBuildIdValid(bucket_url, build_branch, build_id):
45 """Checks that a specific build_id is valid.
46
47 Looks for that build_id for all builds. Confirms that the subpath can
48 be found and that the zip file is present in that subdirectory.
49
50 Args:
51 bucket_url: URL of Android build gs bucket
52 build_branch: branch of Android builds
53 build_id: A string. The Android build id number to check.
54
55 Returns:
56 Returns subpaths dictionary if build_id is valid.
57 None if the build_id is not valid.
58 """
59 gs_context = gs.GSContext()
60 subpaths_dict = {}
61 for build, target in constants.ANDROID_BUILD_TARGETS.iteritems():
62 build_dir = '%s-%s' % (build_branch, target)
63 build_id_path = os.path.join(bucket_url, build_dir, build_id)
64
65 # Find name of subpath.
66 try:
67 subpaths = gs_context.List(build_id_path)
68 except gs.GSNoSuchKey:
69 logging.warn(
70 'Directory [%s] does not contain any subpath, ignoring it.',
71 build_id_path)
72 return None
73 if len(subpaths) > 1:
74 logging.warn(
75 'Directory [%s] contains more than one subpath, ignoring it.',
76 build_id_path)
77 return None
78
79 subpath_dir = subpaths[0].url.rstrip('/')
80 subpath_name = os.path.basename(subpath_dir)
81
82 # Look for a zipfile ending in the build_id number.
83 try:
84 for zipfile in gs_context.List(subpath_dir):
David Riley473e6142016-02-17 15:22:16 -080085 if zipfile.url.endswith('.zip'):
David Rileyc0da9d92016-02-01 12:11:01 -080086 break
87 except gs.GSNoSuchKey:
88 logging.warn(
89 'Did not find a zipfile for build id [%s] in directory [%s].',
90 build_id, subpath_dir)
91 return None
92
93 # Record subpath for the build.
94 subpaths_dict[build] = subpath_name
95
96 # If we got here, it means we found an appropriate build for all platforms.
97 return subpaths_dict
98
99
100def GetLatestBuild(bucket_url, build_branch):
101 """Searches the gs bucket for the latest green build.
102
103 Args:
104 bucket_url: URL of Android build gs bucket
105 build_branch: branch of Android builds
106
107 Returns:
108 Tuple of (latest version string, subpaths dictionary)
109 If no latest build can be found, returns None, None
110 """
111 gs_context = gs.GSContext()
112 common_build_ids = None
113 # Find builds for each target.
114 for target in constants.ANDROID_BUILD_TARGETS.itervalues():
115 build_dir = '-'.join((build_branch, target))
116 base_path = os.path.join(bucket_url, build_dir)
117 build_ids = []
118 for gs_result in gs_context.List(base_path):
119 # Remove trailing slashes and get the base name, which is the build_id.
120 build_id = os.path.basename(gs_result.url.rstrip('/'))
121 if not build_id.isdigit():
122 logging.warn('Directory [%s] does not look like a valid build_id.',
123 gs_result.url)
124 continue
125 build_ids.append(build_id)
126
127 # Update current list of builds.
128 if common_build_ids is None:
129 # First run, populate it with the first platform.
130 common_build_ids = set(build_ids)
131 else:
132 # Already populated, find the ones that are common.
133 common_build_ids.intersection_update(build_ids)
134
135 if common_build_ids is None:
136 logging.warn('Did not find a build_id common to all platforms.')
137 return None, None
138
139 # Otherwise, find the most recent one that is valid.
140 for build_id in sorted(common_build_ids, key=int, reverse=True):
141 subpaths = IsBuildIdValid(bucket_url, build_branch, build_id)
142 if subpaths:
143 return build_id, subpaths
144
145 # If not found, no build_id is valid.
146 logging.warn('Did not find a build_id valid on all platforms.')
147 return None, None
148
149
150def FindAndroidCandidates(package_dir):
151 """Return a tuple of Android's unstable ebuild and stable ebuilds.
152
153 Args:
154 package_dir: The path to where the package ebuild is stored.
155
156 Returns:
157 Tuple [unstable_ebuild, stable_ebuilds].
158
159 Raises:
160 Exception: if no unstable ebuild exists for Android.
161 """
162 stable_ebuilds = []
163 unstable_ebuilds = []
164 for path in glob.glob(os.path.join(package_dir, '*.ebuild')):
165 ebuild = portage_util.EBuild(path)
166 if ebuild.version == '9999':
167 unstable_ebuilds.append(ebuild)
168 else:
169 stable_ebuilds.append(ebuild)
170
171 # Apply some sanity checks.
172 if not unstable_ebuilds:
173 raise Exception('Missing 9999 ebuild for %s' % package_dir)
174 if not stable_ebuilds:
175 logging.warning('Missing stable ebuild for %s' % package_dir)
176
177 return portage_util.BestEBuild(unstable_ebuilds), stable_ebuilds
178
179
David Riley73f00d92016-02-16 18:54:20 -0800180def CopyToArcBucket(android_bucket_url, build_branch, build_id, subpaths,
181 arc_bucket_url, acls):
182 """Copies from source Android bucket to ARC++ specific bucket.
183
184 Copies each build to the ARC bucket eliminating the subpath.
185 Applies build specific ACLs for each file.
186
187 Args:
188 android_bucket_url: URL of Android build gs bucket
189 build_branch: branch of Android builds
190 build_id: A string. The Android build id number to check.
191 subpaths: Subpath dictionary for each build to copy.
192 arc_bucket_url: URL of the target ARC build gs bucket
193 acls: ACLs dictionary for each build to copy.
194 """
195 gs_context = gs.GSContext()
196 for build, subpath in subpaths.iteritems():
197 target = constants.ANDROID_BUILD_TARGETS[build]
198 build_dir = '%s-%s' % (build_branch, target)
199 android_dir = os.path.join(android_bucket_url, build_dir, build_id, subpath)
200 arc_dir = os.path.join(arc_bucket_url, build_dir, build_id)
201
202 # Copy all zip files from android_dir to arc_dir, setting ACLs.
203 for zipfile in gs_context.List(android_dir):
David Riley473e6142016-02-17 15:22:16 -0800204 if zipfile.url.endswith('.zip'):
David Riley73f00d92016-02-16 18:54:20 -0800205 zipname = os.path.basename(zipfile.url)
206 arc_path = os.path.join(arc_dir, zipname)
207 acl = acls[build]
208 needs_copy = True
209
210 # Check a pre-existing file with the original source.
211 if gs_context.Exists(arc_path):
212 if (gs_context.Stat(zipfile.url).hash_md5 !=
213 gs_context.Stat(arc_path).hash_md5):
214 logging.warn('Removing incorrect file %s', arc_path)
215 gs_context.Remove(arc_path)
216 else:
217 logging.info('Skipping already copied file %s', arc_path)
218 needs_copy = False
219
220 # Copy if necessary, and set the ACL unconditionally.
221 # The Stat() call above doesn't verify the ACL is correct and
222 # the ChangeACL should be relatively cheap compared to the copy.
223 # This covers the following caes:
224 # - handling an interrupted copy from a previous run.
225 # - rerunning the copy in case one of the googlestorage_acl_X.txt
226 # files changes (e.g. we add a new variant which reuses a build).
227 if needs_copy:
228 logging.info('Copying %s -> %s (acl %s)', zipfile.url, arc_path, acl)
229 gs_context.Copy(zipfile.url, arc_path, version=0)
230 gs_context.ChangeACL(arc_path, acl_args_file=acl)
231
232
233def MakeAclDict(package_dir):
234 """Creates a dictionary of acl files for each build type.
235
236 Args:
237 package_dir: The path to where the package acl files are stored.
238
239 Returns:
240 Returns acls dictionary.
241 """
242 return dict(
243 (k, os.path.join(package_dir, v))
244 for k, v in constants.ARC_BUCKET_ACLS.items()
245 )
246
247
David Rileyc0da9d92016-02-01 12:11:01 -0800248def GetAndroidRevisionListLink(build_branch, old_android, new_android):
249 """Returns a link to the list of revisions between two Android versions
250
251 Given two AndroidEBuilds, generate a link to a page that prints the
252 Android changes between those two revisions, inclusive.
253
254 Args:
255 build_branch: branch of Android builds
256 old_android: ebuild for the version to diff from
257 new_android: ebuild for the version to which to diff
258
259 Returns:
260 The desired URL.
261 """
262 return _ANDROID_VERSION_URL % {'branch': build_branch,
263 'old': old_android.version,
264 'new': new_android.version}
265
266
267def MarkAndroidEBuildAsStable(stable_candidate, unstable_ebuild, android_pn,
David Riley73f00d92016-02-16 18:54:20 -0800268 android_version, package_dir, build_branch,
269 arc_bucket_url):
David Rileyc0da9d92016-02-01 12:11:01 -0800270 r"""Uprevs the Android ebuild.
271
272 This is the main function that uprevs from a stable candidate
273 to its new version.
274
275 Args:
276 stable_candidate: ebuild that corresponds to the stable ebuild we are
277 revving from. If None, builds the a new ebuild given the version
278 with revision set to 1.
279 unstable_ebuild: ebuild corresponding to the unstable ebuild for Android.
280 android_pn: package name.
281 android_version: The \d+ build id of Android.
David Rileyc0da9d92016-02-01 12:11:01 -0800282 package_dir: Path to the android-container package dir.
David Riley73f00d92016-02-16 18:54:20 -0800283 build_branch: branch of Android builds.
284 arc_bucket_url: URL of the target ARC build gs bucket.
David Rileyc0da9d92016-02-01 12:11:01 -0800285
286 Returns:
287 Full portage version atom (including rc's, etc) that was revved.
288 """
289 def IsTheNewEBuildRedundant(new_ebuild, stable_ebuild):
290 """Returns True if the new ebuild is redundant.
291
292 This is True if there if the current stable ebuild is the exact same copy
293 of the new one.
294 """
295 if not stable_ebuild:
296 return False
297
David Riley676f5402016-02-12 17:24:23 -0800298 if stable_candidate.version_no_rev == new_ebuild.version_no_rev:
David Rileyc0da9d92016-02-01 12:11:01 -0800299 return filecmp.cmp(
300 new_ebuild.ebuild_path, stable_ebuild.ebuild_path, shallow=False)
301
302 # Case where we have the last stable candidate with same version just rev.
David Riley676f5402016-02-12 17:24:23 -0800303 if stable_candidate and stable_candidate.version_no_rev == android_version:
David Rileyc0da9d92016-02-01 12:11:01 -0800304 new_ebuild_path = '%s-r%d.ebuild' % (
305 stable_candidate.ebuild_path_no_revision,
306 stable_candidate.current_revision + 1)
307 else:
308 pf = '%s-%s-r1' % (android_pn, android_version)
309 new_ebuild_path = os.path.join(package_dir, '%s.ebuild' % pf)
310
David Riley73f00d92016-02-16 18:54:20 -0800311 variables = {'BASE_URL': arc_bucket_url}
312 for build, target in constants.ANDROID_BUILD_TARGETS.iteritems():
313 variables[build + '_TARGET'] = '%s-%s' % (build_branch, target)
David Rileyc0da9d92016-02-01 12:11:01 -0800314
315 portage_util.EBuild.MarkAsStable(
316 unstable_ebuild.ebuild_path, new_ebuild_path,
317 variables, make_stable=True)
318 new_ebuild = portage_util.EBuild(new_ebuild_path)
319
320 # Determine whether this is ebuild is redundant.
321 if IsTheNewEBuildRedundant(new_ebuild, stable_candidate):
322 msg = 'Previous ebuild with same version found and ebuild is redundant.'
323 logging.info(msg)
324 os.unlink(new_ebuild_path)
325 return None
326
327 if stable_candidate:
328 logging.PrintBuildbotLink('Android revisions',
329 GetAndroidRevisionListLink(build_branch,
330 stable_candidate,
331 new_ebuild))
332
333 git.RunGit(package_dir, ['add', new_ebuild_path])
334 if stable_candidate and not stable_candidate.IsSticky():
335 git.RunGit(package_dir, ['rm', stable_candidate.ebuild_path])
336
337 # Update ebuild manifest and git add it.
338 gen_manifest_cmd = ['ebuild', new_ebuild_path, 'manifest', '--force']
339 cros_build_lib.RunCommand(gen_manifest_cmd,
340 extra_env=None, print_cmd=True)
341 git.RunGit(package_dir, ['add', 'Manifest'])
342
343 portage_util.EBuild.CommitChange(
344 _GIT_COMMIT_MESSAGE % {'android_pn': android_pn,
345 'android_version': android_version},
346 package_dir)
347
348 return '%s-%s' % (new_ebuild.package, new_ebuild.version)
349
350
351def GetParser():
352 """Creates the argument parser."""
353 parser = commandline.ArgumentParser()
354 parser.add_argument('-b', '--boards')
355 parser.add_argument('--android_bucket_url',
David Riley73f00d92016-02-16 18:54:20 -0800356 default=constants.ANDROID_BUCKET_URL,
357 type='gs_path')
David Rileyc0da9d92016-02-01 12:11:01 -0800358 parser.add_argument('--android_build_branch',
359 default=constants.ANDROID_BUILD_BRANCH)
David Riley73f00d92016-02-16 18:54:20 -0800360 parser.add_argument('--arc_bucket_url',
361 default=constants.ARC_BUCKET_URL,
362 type='gs_path')
David Rileyc0da9d92016-02-01 12:11:01 -0800363 parser.add_argument('-f', '--force_version',
364 help='Android build id to use')
365 parser.add_argument('-s', '--srcroot',
366 default=os.path.join(os.environ['HOME'], 'trunk', 'src'),
367 help='Path to the src directory')
368 parser.add_argument('-t', '--tracking_branch', default='cros/master',
369 help='Branch we are tracking changes against')
370 return parser
371
372
373def main(argv):
374 parser = GetParser()
375 options = parser.parse_args(argv)
376 options.Freeze()
377
378 overlay_dir = os.path.abspath(_OVERLAY_DIR % {'srcroot': options.srcroot})
379 android_package_dir = os.path.join(overlay_dir, constants.ANDROID_CP)
380 version_to_uprev = None
381 subpaths = None
382
383 (unstable_ebuild, stable_ebuilds) = FindAndroidCandidates(android_package_dir)
384
385 if options.force_version:
386 version_to_uprev = options.force_version
387 subpaths = IsBuildIdValid(options.android_bucket_url,
388 options.android_build_branch, version_to_uprev)
389 if not subpaths:
390 logging.error('Requested build %s is not valid' % version_to_uprev)
391 else:
392 version_to_uprev, subpaths = GetLatestBuild(options.android_bucket_url,
393 options.android_build_branch)
394
David Riley73f00d92016-02-16 18:54:20 -0800395 acls = MakeAclDict(android_package_dir)
396 CopyToArcBucket(options.android_bucket_url, options.android_build_branch,
397 version_to_uprev, subpaths, options.arc_bucket_url, acls)
398
David Rileyc0da9d92016-02-01 12:11:01 -0800399 stable_candidate = portage_util.BestEBuild(stable_ebuilds)
400
401 if stable_candidate:
David Riley676f5402016-02-12 17:24:23 -0800402 logging.info('Stable candidate found %s' % stable_candidate.version)
David Rileyc0da9d92016-02-01 12:11:01 -0800403 else:
404 logging.info('No stable candidate found.')
405
406 tracking_branch = 'remotes/m/%s' % os.path.basename(options.tracking_branch)
407 existing_branch = git.GetCurrentBranch(android_package_dir)
408 work_branch = cros_mark_as_stable.GitBranch(constants.STABLE_EBUILD_BRANCH,
409 tracking_branch,
410 android_package_dir)
411 work_branch.CreateBranch()
412
413 # In the case of uprevving overlays that have patches applied to them,
414 # include the patched changes in the stabilizing branch.
415 if existing_branch:
416 git.RunGit(overlay_dir, ['rebase', existing_branch])
417
418 android_version_atom = MarkAndroidEBuildAsStable(
419 stable_candidate, unstable_ebuild, constants.ANDROID_PN,
David Riley73f00d92016-02-16 18:54:20 -0800420 version_to_uprev, android_package_dir,
421 options.android_build_branch, options.arc_bucket_url)
David Rileyc0da9d92016-02-01 12:11:01 -0800422 if android_version_atom:
423 if options.boards:
424 cros_mark_as_stable.CleanStalePackages(options.srcroot,
425 options.boards.split(':'),
426 [android_version_atom])
427
428 # Explicit print to communicate to caller.
429 print('ANDROID_VERSION_ATOM=%s' % android_version_atom)