OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """ |
| 7 A script to generate and store the manifests for the canary builders to use. |
| 8 """ |
| 9 |
| 10 import fnmatch |
| 11 import logging |
| 12 import logging.handlers |
| 13 import os |
| 14 import re |
| 15 import shutil |
| 16 import tempfile |
| 17 |
| 18 import chromite.lib.cros_build_lib as cros_lib |
| 19 |
| 20 logging_format = '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s' |
| 21 date_format = '%Y/%m/%d %H:%M:%S' |
| 22 logging.basicConfig(level=logging.INFO, format=logging_format, |
| 23 datefmt=date_format) |
| 24 |
| 25 # Pattern for matching build name format. E.g, 12.3.4.5,1.0.25.3 |
| 26 VER_PATTERN = '(\d+).(\d+).(\d+).(\d+)' |
| 27 |
| 28 class VersionUpdateException(Exception): |
| 29 """Exception gets thrown for failing to update the version file""" |
| 30 pass |
| 31 |
| 32 |
| 33 class InvalidOptionException(Exception): |
| 34 """Exception gets thrown for invalid options""" |
| 35 pass |
| 36 |
| 37 |
| 38 class GitCommandException(Exception): |
| 39 """Exception gets thrown for a git command that fails to execute.""" |
| 40 pass |
| 41 |
| 42 class SrcCheckOutException(Exception): |
| 43 """Exception gets thrown for failure to sync sources""" |
| 44 pass |
| 45 |
| 46 class StatusUpdateException(Exception): |
| 47 """Exception gets thrown for failure to update the status""" |
| 48 pass |
| 49 |
| 50 class GenerateBuildSpecException(Exception): |
| 51 """Exception gets thrown for failure to Generate a buildspec for the build""" |
| 52 pass |
| 53 |
| 54 |
| 55 class GitMirror(object): |
| 56 """ A Class to deal with the source tree/mirror setup and sync repos |
| 57 Args: |
| 58 mirror_dir: location for setting up git mirror |
| 59 repo_url: gitserver URL to fetch manifests from |
| 60 manifest_file: manifest_file to use for syncing |
| 61 """ |
| 62 def __init__(self, mirror_dir, repo_url, manifest_file='full.xml'): |
| 63 self.mirror_dir = mirror_dir |
| 64 self.repo_url = repo_url |
| 65 self.manifest_file = manifest_file |
| 66 self.InitMirrorRepos() |
| 67 |
| 68 def InitMirrorRepos(self): |
| 69 """Initialize the git mirroring""" |
| 70 if not os.path.exists(self.mirror_dir): |
| 71 os.makedirs(self.mirror_dir) |
| 72 logging.debug('git mirror does not exist. Creating it for the first time') |
| 73 |
| 74 if not os.path.exists(os.path.join(self.mirror_dir, '.repo')): |
| 75 cros_lib.RunCommand(['repo', 'init', '-u', self.repo_url, '-m', |
| 76 self.manifest_file, '--mirror'], cwd=self.mirror_dir, |
| 77 input='\n\ny\n') |
| 78 |
| 79 def SyncMirror(self): |
| 80 """Sync/update the git mirror location""" |
| 81 cros_lib.RunCommand(['repo', 'sync'], cwd=self.mirror_dir) |
| 82 |
| 83 |
| 84 class VersionInfo(object): |
| 85 """Class to encapsualte the chrome os version info |
| 86 You can instantiate this class in two ways. |
| 87 1)using a version file, specifically chromeos_version.sh, |
| 88 which contains the version information. |
| 89 2) just passing in the 4 version components (major, minor, sp and patch) |
| 90 Args: |
| 91 ver_maj: major version |
| 92 ver_min: minor version |
| 93 ver_sp: sp version |
| 94 ver_patch: patch version |
| 95 version_file: version file location. |
| 96 """ |
| 97 def __init__(self, ver_maj=0, ver_min=0, ver_sp=0, ver_patch=0, |
| 98 incr_type=None, version_file=None): |
| 99 self.ver_maj = ver_maj |
| 100 self.ver_min = ver_min |
| 101 self.ver_sp = ver_sp |
| 102 self.ver_patch = ver_patch |
| 103 self.incr_type = incr_type |
| 104 if version_file: |
| 105 self.version_file = version_file |
| 106 self.load() |
| 107 |
| 108 def FindValue(self, key, line): |
| 109 """Given the key find the value from the line, if it finds key = value |
| 110 Args: |
| 111 key: key to look for |
| 112 line: string to search |
| 113 returns: |
| 114 None: on a non match |
| 115 value: for a matching key |
| 116 """ |
| 117 regex = '.*(%s)\s*=\s*(\d+)$' % key |
| 118 |
| 119 match = re.match(regex, line) |
| 120 if match: |
| 121 return match.group(2) |
| 122 return None |
| 123 |
| 124 def load(self): |
| 125 """Read the version file and set the version components""" |
| 126 with open(self.version_file, 'r') as version_fh: |
| 127 for line in version_fh: |
| 128 if not line.strip(): |
| 129 continue |
| 130 |
| 131 match = self.FindValue('CHROMEOS_VERSION_MAJOR', line) |
| 132 if match: |
| 133 self.ver_maj = match |
| 134 logging.debug('Set the major version to:%s', self.ver_maj) |
| 135 continue |
| 136 |
| 137 match = self.FindValue('CHROMEOS_VERSION_MINOR', line) |
| 138 if match: |
| 139 self.ver_min = match |
| 140 logging.debug('Set the minor version to:%s', self.ver_min) |
| 141 continue |
| 142 |
| 143 match = self.FindValue('CHROMEOS_VERSION_BRANCH', line) |
| 144 if match: |
| 145 self.ver_sp = match |
| 146 logging.debug('Set the sp version to:%s', self.ver_sp) |
| 147 continue |
| 148 |
| 149 match = self.FindValue('CHROMEOS_VERSION_PATCH', line) |
| 150 if match: |
| 151 self.ver_patch = match |
| 152 logging.debug('Set the patch version to:%s', self.ver_patch) |
| 153 continue |
| 154 logging.debug(self.VersionString()) |
| 155 |
| 156 def IncrementVersion(self): |
| 157 """Updates the version file by incrementing the patch component""" |
| 158 if not self.version_file: |
| 159 raise VersionUpdateException('Cannot call IncrementVersion without ' |
| 160 'an associated version_file') |
| 161 if not self.incr_type: |
| 162 raise VersionUpdateException('Need to specify the part of the version to' |
| 163 'increment') |
| 164 |
| 165 if self.incr_type == 'branch': |
| 166 self.ver_sp = str(int(self.ver_sp) + 1) |
| 167 self.ver_patch = '0' |
| 168 if self.incr_type == 'patch': |
| 169 self.ver_patch = str(int(self.ver_patch) + 1) |
| 170 temp_file = tempfile.mkstemp(suffix='mvp', prefix='tmp', dir=None, |
| 171 text=True)[1] |
| 172 with open(self.version_file, 'r') as source_version_fh: |
| 173 with open(temp_file, 'w') as temp_fh: |
| 174 for line in source_version_fh: |
| 175 old_patch = self.FindValue('CHROMEOS_VERSION_PATCH', line) |
| 176 if old_patch: |
| 177 temp_fh.write(line.replace(old_patch, self.ver_patch, 1)) |
| 178 continue |
| 179 |
| 180 old_sp = self.FindValue('CHROMEOS_VERSION_BRANCH', line) |
| 181 if old_sp: |
| 182 temp_fh.write(line.replace(old_sp, self.ver_sp, 1)) |
| 183 continue |
| 184 |
| 185 temp_fh.write(line) |
| 186 temp_fh.close() |
| 187 source_version_fh.close() |
| 188 shutil.copyfile(temp_file, self.version_file) |
| 189 os.unlink(temp_file) |
| 190 return self.VersionString() |
| 191 |
| 192 def VersionString(self): |
| 193 """returns the version string""" |
| 194 return '%s.%s.%s.%s' % (self.ver_maj, self.ver_min, self.ver_sp, |
| 195 self.ver_patch) |
| 196 def DirPrefix(self): |
| 197 """returns the sub directory suffix in manifest-versions""" |
| 198 return '%s.%s' % (self.ver_maj, self.ver_min) |
| 199 |
| 200 def BuildPrefix(self): |
| 201 """returns the build prefix to match the buildspecs in manifest-versions""" |
| 202 if self.incr_type == 'patch': |
| 203 return '%s.%s.%s' % (self.ver_maj, self.ver_min, self.ver_sp) |
| 204 |
| 205 if self.incr_type == 'branch': |
| 206 return '%s.%s' % (self.ver_maj, self.ver_min) |
| 207 |
| 208 return None |
| 209 |
| 210 |
| 211 class SpecsInfo(object): |
| 212 """Class that contains information about the buildspecs |
| 213 Args: |
| 214 working_dir: location of the buildspecs location |
| 215 build_name: name of the build that we will be dealing with |
| 216 dir_prefix: prefix of the version for sub-directory location |
| 217 build_prefix: prefix of the build version for build specs matching |
| 218 """ |
| 219 def __init__(self, working_dir, ver_manifests, build_name, dir_prefix, |
| 220 build_prefix): |
| 221 self.working_dir = working_dir |
| 222 self.ver_manifests = ver_manifests |
| 223 self.build_name = build_name |
| 224 self.dir_prefix = dir_prefix |
| 225 self.build_prefix = build_prefix |
| 226 self.root_dir = os.path.join(self.working_dir, self.ver_manifests) |
| 227 self.all_specs_dir = os.path.join(self.root_dir, 'buildspecs', |
| 228 self.dir_prefix) |
| 229 self.pass_dir = os.path.join(self.root_dir, 'build-name', self.build_name, |
| 230 'pass', self.dir_prefix) |
| 231 self.fail_dir = os.path.join(self.root_dir, 'build-name', self.build_name, |
| 232 'fail', self.dir_prefix) |
| 233 self.inflight_dir = os.path.join(self.root_dir, 'build-name', |
| 234 self.build_name, 'inflight', |
| 235 self.dir_prefix) |
| 236 logging.debug('Build Specs Dir = %s', self.all_specs_dir) |
| 237 |
| 238 |
| 239 class BuildSpecs(object): |
| 240 """ Class to manage buildspecs |
| 241 Args: |
| 242 working_dir:location where the ver_manifests repo is synced to |
| 243 ver_manifests: Name of the ver_manifests repo. Default: manifest-versions |
| 244 build_name: Name of the build that we are interested in. E.g., x86-alex |
| 245 dir_prefix: prefix of the version numbers for sub directory look up |
| 246 build_prefix: prefix of the build version for build specs matching |
| 247 incr_type: part of the version to increment. 'patch or branch' |
| 248 """ |
| 249 def __init__(self, working_dir, ver_manifests, build_name, dir_prefix, |
| 250 build_prefix, incr_type): |
| 251 self.specs_info = SpecsInfo(working_dir, ver_manifests, build_name, |
| 252 dir_prefix, build_prefix) |
| 253 self.all = self.GetMatchingSpecs(self.specs_info.all_specs_dir, incr_type) |
| 254 self.latest = '0.0.0.0' |
| 255 self.incr_type = incr_type |
| 256 |
| 257 if self.all: |
| 258 self.latest = self.all[-1] |
| 259 self.all = set(self.GetMatchingSpecs(self.specs_info.all_specs_dir, |
| 260 self.incr_type)) |
| 261 self.passed = self.GetMatchingSpecs(self.specs_info.pass_dir, |
| 262 self.incr_type) |
| 263 self.failed = self.GetMatchingSpecs(self.specs_info.fail_dir, |
| 264 self.incr_type) |
| 265 self.inflight = self.GetMatchingSpecs(self.specs_info.inflight_dir, |
| 266 self.incr_type) |
| 267 self.processed = sorted((self.passed + self.failed + self.inflight), |
| 268 key=lambda s: map(int, s.split('.'))) |
| 269 self.last_processed = '0.0.0.0' |
| 270 if self.processed: |
| 271 self.last_processed = self.processed[-1] |
| 272 logging.debug('Last processed build for %s is %s' % |
| 273 (build_name, self.last_processed)) |
| 274 |
| 275 difference = list(self.all.difference(set(self.processed))) |
| 276 |
| 277 for build in difference[:]: |
| 278 build1 = map(int, build.split(".")) |
| 279 build2 = map(int, self.last_processed.split(".")) |
| 280 |
| 281 if cmp(build1 , build2) == 1: |
| 282 logging.debug('Still need to build %s' % build) |
| 283 else: |
| 284 logging.debug('Ignoring build %s less than %s' % |
| 285 (build, self.last_processed)) |
| 286 difference.remove(build) |
| 287 |
| 288 self.unprocessed = sorted(difference, key=lambda s: map(int, s.split('.'))) |
| 289 |
| 290 def GetMatchingSpecs(self, specs_info, incr_type): |
| 291 """Returns the sorted list of buildspecs that match '*.xml' |
| 292 Args: |
| 293 specs_info: SpecsInfo object for the buildspecs location information. |
| 294 """ |
| 295 matched_manifests = [] |
| 296 if os.path.exists(specs_info): |
| 297 all_manifests = os.listdir(specs_info) |
| 298 match_string = self.specs_info.build_prefix + '.*.xml' |
| 299 |
| 300 if incr_type == 'branch': |
| 301 match_string = self.specs_info.build_prefix + '.*.0.xml' |
| 302 |
| 303 matched_manifests = fnmatch.filter( |
| 304 all_manifests, match_string) |
| 305 matched_manifests = [os.path.splitext(m)[0] for m in matched_manifests] |
| 306 |
| 307 return matched_manifests |
| 308 |
| 309 def FindNextBuild(self, latest=False): |
| 310 """"Find the next build to build from unprocessed buildspecs |
| 311 Args: |
| 312 latest: True of False. (Returns the last known build on latest =True |
| 313 Returns: |
| 314 Returns the next build to be built or None |
| 315 """ |
| 316 if not self.unprocessed: |
| 317 return None |
| 318 |
| 319 if latest: |
| 320 return self.unprocessed[-1] |
| 321 |
| 322 return self.unprocessed[0] |
| 323 |
| 324 def ExistsBuildSpec(self, build_number): |
| 325 """"Find out if the build_number exists in existing buildspecs |
| 326 Args: |
| 327 build_number: build number to check for existence of build spec |
| 328 Returns: |
| 329 True if the buildspec exists, False if it doesn't |
| 330 """ |
| 331 return build_number in self.all |
| 332 |
| 333 |
| 334 class BuildSpecsManager(object): |
| 335 """A Class to manage buildspecs and their states |
| 336 Args: |
| 337 working_dir: location of the buildspecs repo |
| 338 build_name: build_name e.g., x86-mario, x86-mario-factory |
| 339 dir_prefix: version prefix to match for the sub directories |
| 340 build_prefix: prefix of the build version for build specs matching |
| 341 """ |
| 342 |
| 343 def __init__(self, working_dir, ver_manifests, build_name, dir_prefix, |
| 344 build_prefix): |
| 345 self.specs_info = SpecsInfo(working_dir, ver_manifests, build_name, |
| 346 dir_prefix, build_prefix) |
| 347 |
| 348 def SetUpSymLinks(self, src_file, dest_file, remove_file=None): |
| 349 """Setting up symlinks for various statuses in the git repo |
| 350 Args: |
| 351 status: status type to set to, (one of inflight, pass, fail) |
| 352 src_file: source for the symlink |
| 353 dest_file: destination for the symlink |
| 354 remove_file: symlink that needs to be deleted for clearing the old state |
| 355 """ |
| 356 dest_dir = os.path.dirname(dest_file) |
| 357 if os.path.lexists(dest_file): |
| 358 os.unlink(dest_file) |
| 359 |
| 360 if not os.path.exists(dest_dir): |
| 361 os.makedirs(dest_dir) |
| 362 rel_src_file = os.path.relpath(src_file, dest_dir) |
| 363 logging.debug('Linking %s to %s', rel_src_file, dest_file) |
| 364 os.symlink(rel_src_file, dest_file) |
| 365 |
| 366 if remove_file and os.path.lexists(remove_file): |
| 367 logging.debug('REMOVE: Removing %s', remove_file) |
| 368 os.unlink(remove_file) |
| 369 |
| 370 |
| 371 def SetInFlight(self, version): |
| 372 """Marks a buildspec as inflight by creating a symlink in 'inflight' dir |
| 373 Args: |
| 374 version: version of the buildspec to mark as inflight |
| 375 """ |
| 376 dest_file = '%s.xml' % os.path.join(self.specs_info.inflight_dir, version) |
| 377 src_file = '%s.xml' % os.path.join(self.specs_info.all_specs_dir, version) |
| 378 logging.debug('Setting build in flight %s: %s', src_file, dest_file) |
| 379 self.SetUpSymLinks(src_file, dest_file) |
| 380 |
| 381 def SetFailed(self, version): |
| 382 """Marks a buildspec as failed by creating a symlink in 'fail' dir |
| 383 Args: |
| 384 version: version of the buildspec to mark as failed |
| 385 """ |
| 386 dest_file = '%s.xml' % os.path.join(self.specs_info.fail_dir, version) |
| 387 src_file = '%s.xml' % os.path.join(self.specs_info.all_specs_dir, version) |
| 388 remove_file = '%s.xml' % os.path.join(self.specs_info.inflight_dir, version) |
| 389 logging.debug('Setting build to failed %s: %s', src_file, dest_file) |
| 390 self.SetUpSymLinks(src_file, dest_file, remove_file) |
| 391 |
| 392 def SetPassed(self, version): |
| 393 """Marks a buildspec as failed by creating a symlink in 'pass' dir |
| 394 Args: |
| 395 version: version of the buildspec to mark as passed |
| 396 """ |
| 397 dest_file = '%s.xml' % os.path.join(self.specs_info.pass_dir, version) |
| 398 src_file = '%s.xml' % os.path.join(self.specs_info.all_specs_dir, version) |
| 399 remove_file = '%s.xml' % os.path.join(self.specs_info.inflight_dir, version) |
| 400 logging.debug('Setting build to passed %s: %s', src_file, dest_file) |
| 401 self.SetUpSymLinks(src_file, dest_file, remove_file) |
| 402 |
| 403 |
| 404 def CloneGitRepo(working_dir, repo_url): |
| 405 """"Clone Given git repo |
| 406 Args: |
| 407 repo_url: git repo to clione |
| 408 repo_dir: location where it should be cloned to |
| 409 """ |
| 410 if not os.path.exists(working_dir): |
| 411 os.makedirs(working_dir) |
| 412 cros_lib.RunCommand(['git', 'clone', repo_url], cwd=working_dir) |
| 413 |
| 414 def PushChanges(git_repo, message, use_repo=False, dry_run=True): |
| 415 """Do the final commit into the git repo |
| 416 Args: |
| 417 git_repo: git repo to push |
| 418 message: Commit message |
| 419 use_repo: use repo tool for pushing changes. Default: False |
| 420 dry_run: If true, don't actually push changes to the server |
| 421 raises: GitCommandException |
| 422 """ |
| 423 branch = 'temp_auto_checkin_branch' |
| 424 try: |
| 425 if use_repo: |
| 426 cros_lib.RunCommand(['repo', 'start', branch, '.'], cwd=git_repo) |
| 427 cros_lib.RunCommand(['repo', 'sync', '.'], cwd=git_repo) |
| 428 cros_lib.RunCommand(['git', 'config', 'push.default', 'tracking'], |
| 429 cwd=git_repo) |
| 430 else: |
| 431 cros_lib.RunCommand(['git', 'pull', '--force'], cwd=git_repo) |
| 432 cros_lib.RunCommand(['git', 'add', '-A'], cwd=git_repo) |
| 433 cros_lib.RunCommand(['git', 'commit', '-am', message], cwd=git_repo) |
| 434 |
| 435 push_cmd = ['git', 'push', '--verbose'] |
| 436 if dry_run: push_cmd.append('--dry-run') |
| 437 cros_lib.RunCommand(push_cmd, cwd=git_repo) |
| 438 except cros_lib.RunCommandError, e: |
| 439 err_msg = 'Failed to commit to %s' % e.message |
| 440 logging.error(err_msg) |
| 441 git_status = cros_lib.RunCommand(['git', 'status'], cwd=git_repo) |
| 442 logging.error('Current repo %s status: %s', git_repo, git_status) |
| 443 CleanGitChanges(git_repo) |
| 444 raise GitCommandException(err_msg) |
| 445 finally: |
| 446 if use_repo: |
| 447 cros_lib.RunCommand(['repo', 'abandon', branch], cwd=git_repo) |
| 448 |
| 449 def CleanGitChanges(git_repo): |
| 450 """"Clean git repo chanages |
| 451 Args: |
| 452 git_repo: location of the git repo to clean |
| 453 raises: GitCommandException: when fails to clean |
| 454 """ |
| 455 try: |
| 456 cros_lib.RunCommand(['git', 'clean', '-d', '-f'], cwd=git_repo) |
| 457 cros_lib.RunCommand(['git', 'reset', '--hard', 'HEAD'], cwd=git_repo) |
| 458 except cros_lib.RunCommandError, e: |
| 459 err_msg = 'Failed to clean git repo %s' % e.message |
| 460 logging.error(err_msg) |
| 461 raise GitCommandException(err_msg) |
| 462 |
| 463 |
| 464 def SetLogFileHandler(logfile): |
| 465 """This sets the logging handler to a file. |
| 466 define a Handler which writes INFO messages or higher to the sys.stderr |
| 467 Add the log message handler to the logger |
| 468 Args: |
| 469 logfile: name of the logfile to open |
| 470 """ |
| 471 logfile_handler = logging.handlers.RotatingFileHandler(logfile, backupCount=5) |
| 472 logfile_handler.setLevel(logging.DEBUG) |
| 473 logfile_handler.setFormatter(logging.Formatter(logging_format)) |
| 474 logging.getLogger().addHandler(logfile_handler) |
| 475 |
| 476 |
| 477 def CheckOutSources(cros_source, source_dir, branch, |
| 478 manifest_file='full.xml', retries=0): |
| 479 """"Fetches the sources from the git server using repo tool |
| 480 Args: |
| 481 cros_source: GitMirror Object |
| 482 source_dir: Location to sync the sources to |
| 483 branch: branch to use for the manifest.xml for repo tool |
| 484 manifest_file: manifest file to use for the file definition |
| 485 retries: Number of times to try to fetch sources |
| 486 """ |
| 487 while True: |
| 488 if not os.path.exists(source_dir): |
| 489 os.makedirs(source_dir) |
| 490 try: |
| 491 cros_source.SyncMirror() |
| 492 cros_lib.RunCommand(['repo', 'init', '-u', cros_source.repo_url, |
| 493 '-b', branch, '-m', |
| 494 manifest_file, '--reference', |
| 495 cros_source.mirror_dir], cwd=source_dir, |
| 496 input='\n\ny\n') |
| 497 cros_lib.RunCommand(['repo', 'sync'], cwd=source_dir) |
| 498 break |
| 499 except cros_lib.RunCommandError, e: |
| 500 if retries < 1: |
| 501 err_msg = 'Failed to sync sources %s' % e.message |
| 502 logging.error(err_msg) |
| 503 raise SrcCheckOutException(err_msg) |
| 504 logging.info('Failed to sync sources %s', e.message) |
| 505 logging.info('But will try %d times', retries) |
| 506 CleanUP(source_dir) |
| 507 retries = retries - 1 |
| 508 |
| 509 def ExportManifest(source_dir, output_file): |
| 510 """Exports manifests file |
| 511 Args: |
| 512 source_dir: repo root |
| 513 output_file: output_file to out put the manifest to |
| 514 """ |
| 515 cros_lib.RunCommand(['repo', 'manifest', '-r', '-o', output_file], |
| 516 cwd=source_dir) |
| 517 |
| 518 |
| 519 def SetupExistingBuildSpec(build_specs_manager, next_build, dry_run): |
| 520 build_specs_manager.SetInFlight(next_build) |
| 521 commit_message = 'Automatic: Start %s %s' % ( |
| 522 build_specs_manager.specs_info.build_name, next_build) |
| 523 PushChanges(build_specs_manager.specs_info.root_dir, |
| 524 commit_message, |
| 525 dry_run=dry_run) |
| 526 |
| 527 |
| 528 def GenerateNewBuildSpec(source_dir, branch, latest_build, build_specs_root, |
| 529 build_specs_manager, cros_source, version_info, |
| 530 increment_version, |
| 531 dry_run): |
| 532 """Generates a new buildspec for the builders to consume |
| 533 Checks to see, if there are new changes that need to be built from the last |
| 534 time another buildspec was created. Updates the version number in version |
| 535 number file. Generates the manifest as the next buildspecs. pushes it to git |
| 536 and returns the next build spec number |
| 537 If there are no new changes returns None |
| 538 Args: |
| 539 source_dir: location of the source tree root |
| 540 branch: branch to sync the sources to |
| 541 latest_build: latest known build to match the latest sources against |
| 542 build_specs_root: location of the build specs root |
| 543 build_specs_manager: BuildSpecsManager object |
| 544 cros_source: GitMirror object |
| 545 version_info: VersionInfo Object |
| 546 increment_version: True or False for incrementing the chromeos_version.sh |
| 547 dry_run: if True don't push changes externally |
| 548 Returns: |
| 549 next build number: on new changes or |
| 550 None: on no new changes |
| 551 """ |
| 552 temp_manifest_file = tempfile.mkstemp(suffix='mvp', text=True)[1] |
| 553 |
| 554 ExportManifest(source_dir, temp_manifest_file) |
| 555 |
| 556 latest_spec_file = '%s.xml' % os.path.join( |
| 557 build_specs_manager.specs_info.all_specs_dir, latest_build) |
| 558 |
| 559 logging.debug('Calling DiffManifests with %s, %s', latest_spec_file, |
| 560 temp_manifest_file) |
| 561 if not (latest_build == '0.0.0.0' or |
| 562 DiffManifests(temp_manifest_file, latest_spec_file)): |
| 563 return None |
| 564 |
| 565 next_version = version_info.VersionString() |
| 566 |
| 567 if increment_version: |
| 568 next_version = version_info.IncrementVersion() |
| 569 logging.debug('Incremented version number to %s', next_version) |
| 570 message = 'Automatic: Updating the new version number %s' % next_version |
| 571 PushChanges(os.path.dirname(version_info.version_file), message, |
| 572 use_repo=True, dry_run=dry_run) |
| 573 CheckOutSources(cros_source, source_dir, branch) |
| 574 |
| 575 next_spec_file = '%s.xml' % os.path.join( |
| 576 build_specs_manager.specs_info.all_specs_dir, next_version) |
| 577 if not os.path.exists(os.path.dirname(next_spec_file)): |
| 578 os.makedirs(os.path.dirname(next_spec_file)) |
| 579 ExportManifest(source_dir, next_spec_file) |
| 580 message = 'Automatic: Buildspec %s. Created by %s' % ( |
| 581 next_version, build_specs_manager.specs_info.build_name) |
| 582 logging.debug('Created New Build Spec %s', message) |
| 583 |
| 584 build_specs_manager.SetInFlight(next_version) |
| 585 PushChanges(build_specs_root, message, dry_run=dry_run) |
| 586 return next_version |
| 587 |
| 588 |
| 589 def DiffManifests(manifest1, manifest2): |
| 590 """Diff two manifest files. excluding the revisions to manifest-verisons.git |
| 591 Args: |
| 592 manifest1: First manifest file to compare |
| 593 manifest2: Second manifest file to compare |
| 594 Returns: |
| 595 True: If the manifests are different |
| 596 False: If the manifests are same |
| 597 """ |
| 598 black_list = ['manifest-versions'] |
| 599 blacklist_pattern = re.compile(r'|'.join(black_list)) |
| 600 with open(manifest1, 'r') as manifest1_fh: |
| 601 with open(manifest2, 'r') as manifest2_fh: |
| 602 for (line1, line2) in zip(manifest1_fh, manifest2_fh): |
| 603 if blacklist_pattern.search(line1): |
| 604 logging.debug('%s ignored %s', line1, line2) |
| 605 continue |
| 606 |
| 607 if line1 != line2: |
| 608 return True |
| 609 return False |
| 610 |
| 611 |
| 612 def SetStatus(build_version, status, working_dir, ver_manifests, build_name, |
| 613 dry_run): |
| 614 """Set the status of a particular build by moving the build_spec files around |
| 615 Args: |
| 616 build_version: version of the build that we should set the status for |
| 617 status: status to set. One of 'pass|fail' |
| 618 working_dir: working dir where our version-manifests repos are synced |
| 619 ver_manifests: name of the version manifests repo |
| 620 build_name: Name of the build that we are settting the status for |
| 621 dry_run: if True don't push changes externally |
| 622 """ |
| 623 logging.info('Setting Status for %s as %s', build_version, status) |
| 624 match = re.search(VER_PATTERN, build_version) |
| 625 logging.debug('Matched %s - %s - %s - %s', match.group(1), match.group(2), |
| 626 match.group(3), match.group(4)) |
| 627 version_info = VersionInfo(ver_maj=match.group(1), ver_min=match.group(2), |
| 628 ver_sp=match.group(3), ver_patch=match.group(4)) |
| 629 logging.debug('Using Version: %s', version_info.VersionString()) |
| 630 logging.debug('Using Directory Prefix: %s', version_info.DirPrefix()) |
| 631 logging.debug('Using Build Prefix: %s', version_info.BuildPrefix()) |
| 632 |
| 633 build_specs_manager = BuildSpecsManager(working_dir, ver_manifests, |
| 634 build_name, version_info.DirPrefix(), |
| 635 version_info.BuildPrefix()) |
| 636 if status == 'pass': |
| 637 build_specs_manager.SetPassed(build_version) |
| 638 if status == 'fail': |
| 639 build_specs_manager.SetFailed(build_version) |
| 640 |
| 641 commit_message = 'Automatic checkin: status = %s build_version %s for %s' % ( |
| 642 status, build_version, build_name) |
| 643 PushChanges(build_specs_manager.specs_info.root_dir, commit_message, |
| 644 dry_run=dry_run) |
| 645 |
| 646 |
| 647 def RemoveDirs(dir_name): |
| 648 """Remove directories recursively, if they exist""" |
| 649 if os.path.exists(dir_name): |
| 650 shutil.rmtree(dir_name) |
| 651 |
| 652 def CleanUP(source_dir=None, working_dir=None, git_mirror_dir=None): |
| 653 """Clean up of all the directories that are created by this script |
| 654 Args: |
| 655 source_dir: directory where the sources are. Default: None |
| 656 working_dir: directory where manifest-versions is. Default: None |
| 657 git_mirror_dir: directory where git mirror is setup. Default: None |
| 658 """ |
| 659 if source_dir: |
| 660 logging.debug('Cleaning source dir = %s', source_dir) |
| 661 RemoveDirs(source_dir) |
| 662 |
| 663 if working_dir: |
| 664 logging.debug('Cleaning working dir = %s', working_dir) |
| 665 RemoveDirs(working_dir) |
| 666 |
| 667 if git_mirror_dir: |
| 668 logging.debug('Cleaning git mirror dir = %s', git_mirror_dir) |
| 669 RemoveDirs(git_mirror_dir) |
| 670 |
| 671 def GenerateWorkload(tmp_dir, source_repo, manifest_repo, |
| 672 branch, version_file, |
| 673 build_name, incr_type, latest=False, |
| 674 dry_run=False, |
| 675 retries=0): |
| 676 """Function to see, if there are any new builds that need to be built. |
| 677 Args: |
| 678 tmp_dir: Temporary working directory |
| 679 source_repo: git URL to repo with source code |
| 680 manifest_repo: git URL to repo with manifests/status values |
| 681 branch: Which branch to build ('master' or mainline) |
| 682 version_file: location of chromeos_version.sh |
| 683 build_name: name of the build_name |
| 684 incr_type: part of the version number to increment 'patch or branch' |
| 685 latest: Whether we need to handout the latest build. Default: False |
| 686 dry_run: if True don't push changes externally |
| 687 retries: Number of retries for updating the status |
| 688 Returns: |
| 689 next_build: a string of the next build number for the builder to consume |
| 690 or None in case of no need to build. |
| 691 Raises: |
| 692 GenerateBuildSpecException in case of failure to generate a buildspec |
| 693 """ |
| 694 |
| 695 ver_manifests=os.path.basename(manifest_repo) |
| 696 |
| 697 git_mirror_dir = os.path.join(tmp_dir, 'mirror') |
| 698 source_dir = os.path.join(tmp_dir, 'source') |
| 699 working_dir = os.path.join(tmp_dir, 'working') |
| 700 |
| 701 while True: |
| 702 try: |
| 703 cros_source = GitMirror(git_mirror_dir, source_repo) |
| 704 CheckOutSources(cros_source, source_dir, branch) |
| 705 |
| 706 # Let's be conservative and remove the version_manifests directory. |
| 707 RemoveDirs(working_dir) |
| 708 CloneGitRepo(working_dir, manifest_repo) |
| 709 |
| 710 version_file = os.path.join(source_dir, version_file) |
| 711 logging.debug('Using VERSION _FILE = %s', version_file) |
| 712 version_info = VersionInfo(version_file=version_file, incr_type=incr_type) |
| 713 build_specs = BuildSpecs(working_dir, ver_manifests, build_name, |
| 714 version_info.DirPrefix(), |
| 715 version_info.BuildPrefix(), |
| 716 version_info.incr_type) |
| 717 current_build = version_info.VersionString() |
| 718 logging.debug('Current build in %s: %s', version_file, current_build) |
| 719 |
| 720 build_specs_manager = BuildSpecsManager( |
| 721 working_dir, ver_manifests, build_name, version_info.DirPrefix(), |
| 722 version_info.BuildPrefix()) |
| 723 |
| 724 next_build = build_specs.FindNextBuild(latest) |
| 725 if next_build: |
| 726 logging.debug('Using an existing build spec: %s', next_build) |
| 727 SetupExistingBuildSpec(build_specs_manager, next_build, dry_run) |
| 728 return next_build |
| 729 |
| 730 # If the build spec doesn't exist for the current version in chromeos_ |
| 731 # version.sh. we should just create the build_spec for that version, |
| 732 # instead of incrementing chromeos_version.sh |
| 733 |
| 734 increment_version = build_specs.ExistsBuildSpec(current_build) |
| 735 return GenerateNewBuildSpec(source_dir, branch, build_specs.latest, |
| 736 build_specs.specs_info.root_dir, |
| 737 build_specs_manager, cros_source, |
| 738 version_info, increment_version, |
| 739 dry_run) |
| 740 |
| 741 except (cros_lib.RunCommandError, GitCommandException), e: |
| 742 if retries < 1: |
| 743 err_msg = 'Failed to generate buildspec. error: %s' % e |
| 744 logging.error(err_msg) |
| 745 raise GenerateBuildSpecException(err_msg) |
| 746 |
| 747 logging.info('Failed to generate a buildspec for consumption: %s', |
| 748 e.message) |
| 749 logging.info('But will try %d times', retries) |
| 750 CleanUP(source_dir, working_dir) |
| 751 retries = retries - 1 |
| 752 |
| 753 def UpdateStatus(tmp_dir, manifest_repo, build_name, |
| 754 build_version, success, dry_run=False, retries=0): |
| 755 """Updates the status of the build |
| 756 Args: |
| 757 tmp_dir: Temporary working directory, best shared with GenerateWorkload |
| 758 manifest_repo: git URL to repo with manifests/status values |
| 759 build_name: name of the build_name |
| 760 build_version: value returned by GenerateWorkload for this build |
| 761 success: True for success, False for failure |
| 762 dry_run: if True don't push changes externally |
| 763 retries: Number of retries for updating the status |
| 764 Raises: |
| 765 GenerateBuildSpecExtension in case of failure to generate a buildspec |
| 766 """ |
| 767 |
| 768 working_dir = os.path.join(tmp_dir, 'working') |
| 769 ver_manifests=os.path.basename(manifest_repo) |
| 770 ver_manifests_dir = os.path.join(working_dir, ver_manifests) |
| 771 |
| 772 if success: |
| 773 status = 'pass' |
| 774 else: |
| 775 status = 'fail' |
| 776 |
| 777 while True: |
| 778 try: |
| 779 if not os.path.exists(ver_manifests_dir): |
| 780 CloneGitRepo(manifest_repo, working_dir) |
| 781 cros_lib.RunCommand(['git', 'checkout', 'master'], cwd=ver_manifests_dir) |
| 782 logging.debug('Updating the status info for %s to %s', build_name, |
| 783 status) |
| 784 SetStatus(build_version, status, working_dir, ver_manifests, |
| 785 build_name, dry_run) |
| 786 logging.debug('Updated the status info for %s to %s', build_name, |
| 787 status) |
| 788 break |
| 789 except (GitCommandException, cros_lib.RunCommandError), e: |
| 790 if retries <= 0: |
| 791 logging.error('Failed to update the status for %s to %s', build_name, |
| 792 status) |
| 793 err_msg = 'with the following error: %s' % e.message |
| 794 logging.error(err_msg) |
| 795 raise StatusUpdateException(err_msg) |
| 796 |
| 797 logging.info('Failed to update the status for %s to %s', build_name, |
| 798 status) |
| 799 logging.info('But will try %d times', retries) |
| 800 RemoveDirs(ver_manifests_dir) |
| 801 retries = retries - 1 |
OLD | NEW |