| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| A library to generate and store the manifests for cros builders to use. |
| """ |
| |
| import fnmatch |
| import logging |
| import os |
| import re |
| import shutil |
| import tempfile |
| import time |
| |
| from chromite.buildbot import constants |
| from chromite.buildbot import repository |
| from chromite.lib import cros_build_lib as cros_lib |
| |
| logging_format = '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s' |
| date_format = '%Y/%m/%d %H:%M:%S' |
| logging.basicConfig(level=logging.DEBUG, format=logging_format, |
| datefmt=date_format) |
| |
| PUSH_BRANCH = 'temp_auto_checkin_branch' |
| |
| class VersionUpdateException(Exception): |
| """Exception gets thrown for failing to update the version file""" |
| pass |
| |
| |
| class GitCommandException(Exception): |
| """Exception gets thrown for a git command that fails to execute.""" |
| pass |
| |
| |
| class StatusUpdateException(Exception): |
| """Exception gets thrown for failure to update the status""" |
| pass |
| |
| |
| class GenerateBuildSpecException(Exception): |
| """Exception gets thrown for failure to Generate a buildspec for the build""" |
| pass |
| |
| |
| def _GitCleanDirectory(directory): |
| """"Clean git repo chanages. |
| |
| raises: GitCommandException: when fails to clean. |
| """ |
| try: |
| cros_lib.RunCommand(['git', 'clean', '-d', '-f'], cwd=directory) |
| cros_lib.RunCommand(['git', 'reset', '--hard', 'HEAD'], cwd=directory) |
| except cros_lib.RunCommandError, e: |
| err_msg = 'Failed to clean git "%s" %s' % (directory, e.message) |
| logging.error(err_msg) |
| raise GitCommandException(err_msg) |
| |
| |
| def PrepForChanges(git_repo, dry_run): |
| """Prepare a git/repo repository for making changes. It should |
| have no files modified when you call this. |
| Args: |
| git_repo: git repo to push |
| dry_run: Run but we are not planning on pushing changes for real. |
| raises: GitCommandException |
| """ |
| _GitCleanDirectory(git_repo) |
| try: |
| if repository.InARepoRepository(git_repo): |
| cros_lib.RunCommand(['repo', 'abandon', PUSH_BRANCH, '.'], |
| cwd=git_repo, error_ok=True) |
| cros_lib.RunCommand(['repo', 'sync', '.'], cwd=git_repo) |
| cros_lib.RunCommand(['repo', 'start', PUSH_BRANCH, '.'], cwd=git_repo) |
| else: |
| # Attempt the equivalent of repo abandon for retries. Master always |
| # exists for manifest_version git repos. |
| try: |
| cros_lib.RunCommand(['git', 'checkout', 'master'], cwd=git_repo) |
| if not dry_run: |
| cros_lib.RunCommand(['git', 'branch', '-D', PUSH_BRANCH], |
| cwd=git_repo) |
| except: |
| pass |
| |
| remote, branch = cros_lib.GetPushBranch('master', cwd=git_repo) |
| cros_lib.RunCommand(['git', 'remote', 'update'], cwd=git_repo) |
| if not cros_lib.DoesLocalBranchExist(git_repo, PUSH_BRANCH): |
| cros_lib.RunCommand(['git', 'checkout', '-b', PUSH_BRANCH, '-t', |
| '/'.join([remote, branch])], cwd=git_repo) |
| else: |
| # If the branch exists, we must be dry run. Checkout to branch. |
| assert dry_run, 'Failed to previously delete push branch.' |
| cros_lib.RunCommand(['git', 'checkout', PUSH_BRANCH], cwd=git_repo) |
| |
| cros_lib.RunCommand(['git', 'config', 'push.default', 'tracking'], |
| cwd=git_repo) |
| |
| # TODO Test fix for chromium-os:16249 |
| # repository.FixExternalRepoPushUrls(git_repo) |
| cros_lib.RunCommand(['git', |
| 'config', |
| 'url.ssh://gerrit.chromium.org:29418.insteadof', |
| 'http://git.chromium.org'], cwd=git_repo) |
| |
| except cros_lib.RunCommandError, e: |
| err_msg = 'Failed to prep for edit in %s with %s' % (git_repo, e.message) |
| logging.error(err_msg) |
| git_status = cros_lib.RunCommand(['git', 'status'], cwd=git_repo) |
| logging.error('Current repo %s status: %s', git_repo, git_status) |
| _GitCleanDirectory(git_repo) |
| raise GitCommandException(err_msg) |
| |
| |
| def _PushGitChanges(git_repo, message, dry_run=True): |
| """Do the final commit into the git repo |
| Args: |
| git_repo: git repo to push |
| message: Commit message |
| dry_run: If true, don't actually push changes to the server |
| raises: GitCommandException |
| """ |
| try: |
| # TODO(sosa): Move to using cros_lib.GitPushWithRetry. |
| remote, push_branch = cros_lib.GetPushBranch(PUSH_BRANCH, cwd=git_repo) |
| cros_lib.RunCommand(['git', 'add', '-A'], cwd=git_repo) |
| cros_lib.RunCommand(['git', 'commit', '-am', message], cwd=git_repo) |
| push_cmd = ['git', 'push', remote, '%s:%s' % (PUSH_BRANCH, push_branch)] |
| if dry_run: push_cmd.append('--dry-run') |
| cros_lib.RunCommand(push_cmd, cwd=git_repo) |
| except cros_lib.RunCommandError, e: |
| err_msg = 'Failed to commit to %s' % e.message |
| logging.error(err_msg) |
| git_status = cros_lib.RunCommand(['git', 'status'], cwd=git_repo) |
| logging.error('Current repo %s status:\n%s', git_repo, git_status) |
| _GitCleanDirectory(git_repo) |
| raise GitCommandException(err_msg) |
| finally: |
| if repository.InARepoRepository(git_repo): |
| # Needed for chromeos version file. Otherwise on increment, we leave |
| # local commit behind in tree. |
| cros_lib.RunCommand(['repo', 'abandon', PUSH_BRANCH], cwd=git_repo, |
| error_ok=True) |
| |
| |
| def _RemoveDirs(dir_name): |
| """Remove directories recursively, if they exist""" |
| if os.path.exists(dir_name): |
| shutil.rmtree(dir_name) |
| |
| |
| def CreateSymlink(src_file, dest_file, remove_file=None): |
| """Creates a relative symlink from src to dest with optional removal of file. |
| |
| More robust symlink creation that creates a relative symlink from src_file to |
| dest_file. Also if remove_file is set, removes symlink there. |
| |
| This is useful for multiple calls of CreateSymlink where you are using |
| the dest_file location to store information about the status of the src_file. |
| |
| Args: |
| src_file: source for the symlink |
| dest_file: destination for the symlink |
| remove_file: symlink that needs to be deleted for clearing the old state |
| """ |
| dest_dir = os.path.dirname(dest_file) |
| if os.path.lexists(dest_file): os.unlink(dest_file) |
| if not os.path.exists(dest_dir): os.makedirs(dest_dir) |
| |
| rel_src_file = os.path.relpath(src_file, dest_dir) |
| logging.debug('Linking %s to %s', rel_src_file, dest_file) |
| os.symlink(rel_src_file, dest_file) |
| |
| if remove_file and os.path.lexists(remove_file): |
| logging.debug('REMOVE: Removing %s', remove_file) |
| os.unlink(remove_file) |
| |
| |
| class VersionInfo(object): |
| """Class to encapsulate the Chrome OS version info scheme. |
| |
| You can instantiate this class in two ways. |
| 1)using a version file, specifically chromeos_version.sh, |
| which contains the version information. |
| 2) passing in a string with the 3 version components () |
| Args: |
| version_string: Optional 3 component version string to parse. Contains: |
| build_number: release build number. |
| branch_build_number: current build number on a branch. |
| patch_number: patch number. |
| chrome_branch: If version_string specified, specify chrome_branch i.e. 13. |
| incr_type: How we should increment this version - build|branch|patch |
| version_file: version file location. |
| """ |
| # Pattern for matching build name format. Includes chrome branch hack. |
| VER_PATTERN = '(\d+).(\d+).(\d+)(?:-R(\d+))*' |
| |
| def __init__(self, version_string=None, chrome_branch=None, |
| incr_type='build', version_file=None): |
| # TODO(sosa): Remove default. |
| self.chrome_branch = '14' |
| if version_file: |
| self.version_file = version_file |
| logging.debug('Using VERSION _FILE = %s', version_file) |
| self._LoadFromFile() |
| else: |
| match = re.search(self.VER_PATTERN, version_string) |
| self.build_number = match.group(1) |
| self.branch_build_number = match.group(2) |
| self.patch_number = match.group(3) |
| self.chrome_branch = chrome_branch |
| self.version_file = None |
| |
| self.incr_type = incr_type |
| |
| def _LoadFromFile(self): |
| """Read the version file and set the version components""" |
| with open(self.version_file, 'r') as version_fh: |
| for line in version_fh: |
| if not line.strip(): |
| continue |
| |
| match = self.FindValue('CHROME_BRANCH', line) |
| if match: |
| self.chrome_branch = match |
| logging.debug('Set the Chrome branch number to:%s', |
| self.chrome_branch) |
| continue |
| |
| match = self.FindValue('CHROMEOS_BUILD', line) |
| if match: |
| self.build_number = match |
| logging.debug('Set the build version to:%s', self.build_number) |
| continue |
| |
| match = self.FindValue('CHROMEOS_BRANCH', line) |
| if match: |
| self.branch_build_number = match |
| logging.debug('Set the branch version to:%s', |
| self.branch_build_number) |
| continue |
| |
| match = self.FindValue('CHROMEOS_PATCH', line) |
| if match: |
| self.patch_number = match |
| logging.debug('Set the patch version to:%s', self.patch_number) |
| continue |
| |
| logging.debug(self.VersionString()) |
| |
| def FindValue(self, key, line): |
| """Given the key find the value from the line, if it finds key = value |
| Args: |
| key: key to look for |
| line: string to search |
| returns: |
| None: on a non match |
| value: for a matching key |
| """ |
| regex = '.*(%s)\s*=\s*(\d+)$' % key |
| |
| match = re.match(regex, line) |
| if match: |
| return match.group(2) |
| return None |
| |
| def IncrementVersion(self, message, dry_run): |
| """Updates the version file by incrementing the patch component. |
| Args: |
| message: Commit message to use when incrementing the version. |
| dry_run: Git dry_run. |
| """ |
| def IncrementOldValue(line, key, new_value): |
| """Change key to new_value if found on line. Returns True if changed.""" |
| old_value = self.FindValue(key, line) |
| if old_value: |
| temp_fh.write(line.replace(old_value, new_value, 1)) |
| return True |
| else: |
| return False |
| |
| if not self.version_file: |
| raise VersionUpdateException('Cannot call IncrementVersion without ' |
| 'an associated version_file') |
| if not self.incr_type: |
| raise VersionUpdateException('Need to specify the part of the version to' |
| ' increment') |
| |
| if self.incr_type == 'build': |
| self.build_number = str(int(self.build_number) + 1) |
| self.branch_build_number = '0' |
| self.patch_number = '0' |
| |
| if self.incr_type == 'branch': |
| self.branch_build_number = str(int(self.branch_build_number) + 1) |
| self.patch_number = '0' |
| |
| if self.incr_type == 'patch': |
| self.patch_number = str(int(self.patch_number) + 1) |
| |
| temp_file = tempfile.mkstemp(suffix='mvp', prefix='tmp', dir=None, |
| text=True)[1] |
| with open(self.version_file, 'r') as source_version_fh: |
| with open(temp_file, 'w') as temp_fh: |
| for line in source_version_fh: |
| if IncrementOldValue(line, 'CHROMEOS_BUILD', self.build_number): |
| pass |
| elif IncrementOldValue(line, 'CHROMEOS_BRANCH', |
| self.branch_build_number): |
| pass |
| elif IncrementOldValue(line, 'CHROMEOS_PATCH', self.patch_number): |
| pass |
| else: |
| temp_fh.write(line) |
| |
| temp_fh.close() |
| |
| source_version_fh.close() |
| |
| repo_dir = os.path.dirname(self.version_file) |
| |
| PrepForChanges(repo_dir, dry_run) |
| |
| shutil.copyfile(temp_file, self.version_file) |
| os.unlink(temp_file) |
| |
| _PushGitChanges(repo_dir, message, dry_run=dry_run) |
| |
| return self.VersionString() |
| |
| def VersionString(self): |
| """returns the version string""" |
| return '%s.%s.%s' % (self.build_number, self.branch_build_number, |
| self.patch_number) |
| |
| @classmethod |
| def VersionCompare(cls, version_string): |
| """Useful method to return a comparable version of a LKGM string.""" |
| info = cls(version_string) |
| return map(int, [info.build_number, info.branch_build_number, |
| info.patch_number]) |
| |
| def DirPrefix(self): |
| """Returns the sub directory suffix in manifest-versions""" |
| return self.chrome_branch |
| |
| def BuildPrefix(self): |
| """Returns the build prefix to match the buildspecs in manifest-versions""" |
| if self.incr_type == 'patch': |
| return '%s.%s' % (self.build_number, self.branch_build_number) |
| |
| if self.incr_type == 'branch': |
| return self.build_number |
| |
| # Default to build incr_type. |
| return '' |
| |
| |
| class BuildSpecsManager(object): |
| """A Class to manage buildspecs and their states.""" |
| _TMP_MANIFEST_DIR = '/tmp/manifests' |
| # Various status builds can be in. |
| STATUS_FAILED = 'fail' |
| STATUS_PASSED = 'pass' |
| STATUS_INFLIGHT = 'inflight' |
| STATUS_COMPLETED = [STATUS_PASSED, STATUS_FAILED] |
| |
| # Max timeout before assuming other builders have failed. |
| LONG_MAX_TIMEOUT_SECONDS = 1200 |
| |
| @classmethod |
| def GetManifestDir(cls): |
| """Get the directory where specs are checked out to.""" |
| return cls._TMP_MANIFEST_DIR |
| |
| def __init__(self, source_dir, checkout_repo, manifest_repo, branch, |
| build_name, incr_type, dry_run=True): |
| """Initializes a build specs manager. |
| Args: |
| source_dir: Directory to which we checkout out source code. |
| checkout_repo: Checkout repository for cros. |
| manifest_repo: Manifest repository for manifest versions / buildspecs. |
| branch: The branch. |
| build_name: Identifier for the build. Must match cbuildbot_config. |
| incr_type: part of the version to increment. 'patch or branch' |
| dry_run: Whether we actually commit changes we make or not. |
| """ |
| self.cros_source = repository.RepoRepository( |
| checkout_repo, source_dir, branch=branch) |
| self.manifest_repo = manifest_repo |
| self.branch = branch |
| self.build_name = build_name |
| self.incr_type = incr_type |
| self.dry_run = dry_run |
| |
| # Directories and specifications are set once we load the specs. |
| self.all_specs_dir = None |
| self.pass_dir = None |
| self.fail_dir = None |
| self.inflight_dir = None |
| |
| # A list of versions this builder has successfully built. |
| self.passed = None |
| |
| # Path to specs for builder. Requires passing %(builder)s. |
| self.specs_for_builder = None |
| |
| # Specs. |
| self.all = None |
| self.latest = None |
| self.latest_unprocessed = None |
| self.compare_versions_fn = VersionInfo.VersionCompare |
| |
| self.current_version = None |
| |
| def _GetMatchingSpecs(self, version_info, directory): |
| """Returns the sorted list of buildspecs that match '*.xml in a directory.' |
| Args: |
| version_info: Info class for version information of cros. |
| directory: Directory of the buildspecs. |
| """ |
| matched_manifests = [] |
| if os.path.exists(directory): |
| all_manifests = os.listdir(directory) |
| match_string = version_info.BuildPrefix() + '*.xml' |
| matched_manifests = fnmatch.filter(all_manifests, match_string) |
| matched_manifests = [os.path.splitext(m)[0] for m in matched_manifests] |
| |
| return sorted(matched_manifests, key=self.compare_versions_fn) |
| |
| def _GetSpecAge(self, version): |
| cmd = ['git', 'log', '-1', '--format=%ct', '%s.xml' % version] |
| result = cros_lib.RunCommand(cmd, cwd=self.all_specs_dir, |
| redirect_stdout=True) |
| return time.time() - int(result.output.strip()) |
| |
| def _LoadSpecs(self, version_info, relative_working_dir=''): |
| """Loads the specifications from the working directory. |
| Args: |
| version_info: Info class for version information of cros. |
| relative_working_dir: Optional working directory within buildspecs repo. |
| """ |
| working_dir = os.path.join(self._TMP_MANIFEST_DIR, relative_working_dir) |
| dir_pfx = version_info.DirPrefix() |
| self.specs_for_builder = os.path.join(working_dir, 'build-name', |
| '%(builder)s') |
| specs_for_build = self.specs_for_builder % {'builder': self.build_name} |
| self.all_specs_dir = os.path.join(working_dir, 'buildspecs', dir_pfx) |
| self.pass_dir = os.path.join(specs_for_build, |
| BuildSpecsManager.STATUS_PASSED, dir_pfx) |
| self.fail_dir = os.path.join(specs_for_build, |
| BuildSpecsManager.STATUS_FAILED, dir_pfx) |
| self.inflight_dir = os.path.join(specs_for_build, |
| BuildSpecsManager.STATUS_INFLIGHT, dir_pfx) |
| |
| # Conservatively grab the latest manifest versions repository. |
| # Note: This is key to some of the Git push logic for non-repos for |
| # local developers. If this is changed, please revisit PushChanges and |
| # PrepForChanges. |
| _RemoveDirs(self._TMP_MANIFEST_DIR) |
| repository.CloneGitRepo(self._TMP_MANIFEST_DIR, self.manifest_repo) |
| |
| # Build lists of specs. |
| self.all = self._GetMatchingSpecs(version_info, self.all_specs_dir) |
| |
| # Build list of unprocessed specs. |
| self.passed = self._GetMatchingSpecs(version_info, self.pass_dir) |
| failed = self._GetMatchingSpecs(version_info, self.fail_dir) |
| inflight = self._GetMatchingSpecs(version_info, self.inflight_dir) |
| processed = set(self.passed + failed + inflight) |
| |
| if self.all: |
| self.latest = self.all[-1] |
| |
| # Check if we have a fresh spec file. |
| if (self.latest not in processed and |
| self._GetSpecAge(self.latest) < self.LONG_MAX_TIMEOUT_SECONDS): |
| self.latest_unprocessed = self.latest |
| |
| def _GetCurrentVersionInfo(self): |
| """Returns the current version info from the version file. |
| Args: |
| """ |
| self.cros_source.Sync(repository.RepoRepository.DEFAULT_MANIFEST) |
| version_file_path = self.cros_source.GetRelativePath(constants.VERSION_FILE) |
| return VersionInfo(version_file=version_file_path, |
| incr_type=self.incr_type) |
| |
| def _CreateNewBuildSpec(self, version_info): |
| """Generates a new buildspec for the builders to consume. |
| |
| Checks to see, if there are new changes that need to be built from the |
| last time another buildspec was created. Updates the version number in |
| version number file. If there are no new changes returns None. Otherwise |
| returns the version string for the new spec. |
| |
| Args: |
| version_info: Info class for version information of cros. |
| Returns: |
| next build number: on new changes or |
| None: on no new changes |
| """ |
| if self.latest: |
| latest_spec_file = '%s.xml' % os.path.join(self.all_specs_dir, |
| self.latest) |
| if not self.cros_source.IsManifestDifferent(latest_spec_file): |
| return None |
| |
| version = version_info.VersionString() |
| if version in self.all: |
| message = ('Automatic: %s - Updating to a new version number from %s' % ( |
| self.build_name, version)) |
| version = version_info.IncrementVersion(message, dry_run=self.dry_run) |
| logging.debug('Incremented version number to %s', version) |
| self.cros_source.Sync(repository.RepoRepository.DEFAULT_MANIFEST) |
| |
| spec_file = '%s.xml' % os.path.join(self.all_specs_dir, version) |
| if not os.path.exists(os.path.dirname(spec_file)): |
| os.makedirs(os.path.dirname(spec_file)) |
| |
| self.cros_source.ExportManifest(spec_file) |
| logging.debug('Created New Build Spec %s', version) |
| return version |
| |
| def DidLastBuildSucceed(self): |
| """Returns True if this is our first build or the last build succeeded.""" |
| return not self.latest or self.latest in self.passed |
| |
| def GetBuildStatus(self, builder, version, version_info): |
| """Given a builder, version, verison_info returns the build status.""" |
| xml_name = self.current_version + '.xml' |
| dir_pfx = version_info.DirPrefix() |
| specs_for_build = self.specs_for_builder % {'builder': builder} |
| pass_file = os.path.join(specs_for_build, self.STATUS_PASSED, dir_pfx, |
| xml_name) |
| fail_file = os.path.join(specs_for_build, self.STATUS_FAILED, dir_pfx, |
| xml_name) |
| inflight_file = os.path.join(specs_for_build, self.STATUS_INFLIGHT, dir_pfx, |
| xml_name) |
| |
| if os.path.lexists(pass_file): |
| return BuildSpecsManager.STATUS_PASSED |
| elif os.path.lexists(fail_file): |
| return BuildSpecsManager.STATUS_FAILED |
| elif os.path.lexists(inflight_file): |
| return BuildSpecsManager.STATUS_INFLIGHT |
| else: |
| return None |
| |
| def GetLocalManifest(self, version): |
| """Return path to local copy of manifest given by version.""" |
| if version: |
| return os.path.join(self.all_specs_dir, version + '.xml') |
| |
| return None |
| |
| def GetNextBuildSpec(self, latest=False, force_version=None, |
| retries=5): |
| """Gets the version number of the next build spec to build. |
| Args: |
| latest: Whether we need to handout the latest build. Default: False |
| force_version: Forces us to use this version. |
| retries: Number of retries for updating the status |
| Returns: |
| Local path to manifest to build or None in case of no need to build. |
| Raises: |
| GenerateBuildSpecException in case of failure to generate a buildspec |
| """ |
| last_error = None |
| for index in range(0, retries + 1): |
| try: |
| version_info = self._GetCurrentVersionInfo() |
| logging.debug('Using version %s' % version_info.VersionString()) |
| self._LoadSpecs(version_info) |
| if force_version: |
| # We don't need to re-set inflight. |
| return self.GetLocalManifest(force_version) |
| |
| self._PrepSpecChanges() |
| if not self.latest_unprocessed: |
| self.current_version = self._CreateNewBuildSpec(version_info) |
| elif latest: |
| self.current_version = self.latest_unprocessed |
| else: |
| self.current_version = self.unprocessed[0] |
| |
| if self.current_version: |
| logging.debug('Using build spec: %s', self.current_version) |
| commit_message = 'Automatic: Start %s %s' % (self.build_name, |
| self.current_version) |
| self._SetInFlight() |
| self._PushSpecChanges(commit_message) |
| return self.GetLocalManifest(self.current_version) |
| else: |
| return None |
| |
| except (GitCommandException, cros_lib.RunCommandError) as e: |
| last_error = 'Failed to generate buildspec. error: %s' % e |
| logging.error(last_error) |
| logging.error('Retrying to generate buildspec: Retry %d/%d' % |
| (index + 1, retries)) |
| else: |
| raise GenerateBuildSpecException(last_error) |
| |
| def _SetInFlight(self): |
| """Marks the buildspec as inflight by creating a symlink in inflight dir.""" |
| dest_file = '%s.xml' % os.path.join(self.inflight_dir, self.current_version) |
| src_file = '%s.xml' % os.path.join(self.all_specs_dir, self.current_version) |
| logging.debug('Setting build in flight %s: %s', src_file, dest_file) |
| CreateSymlink(src_file, dest_file) |
| |
| def _SetFailed(self): |
| """Marks the buildspec as failed by creating a symlink in fail dir.""" |
| dest_file = '%s.xml' % os.path.join(self.fail_dir, self.current_version) |
| src_file = '%s.xml' % os.path.join(self.all_specs_dir, self.current_version) |
| remove_file = '%s.xml' % os.path.join(self.inflight_dir, |
| self.current_version) |
| logging.debug('Setting build to failed %s: %s', src_file, dest_file) |
| CreateSymlink(src_file, dest_file, remove_file) |
| |
| def _SetPassed(self): |
| """Marks the buildspec as passed by creating a symlink in passed dir.""" |
| dest_file = '%s.xml' % os.path.join(self.pass_dir, self.current_version) |
| src_file = '%s.xml' % os.path.join(self.all_specs_dir, self.current_version) |
| remove_file = '%s.xml' % os.path.join(self.inflight_dir, |
| self.current_version) |
| logging.debug('Setting build to passed %s: %s', src_file, dest_file) |
| CreateSymlink(src_file, dest_file, remove_file) |
| |
| def _PrepSpecChanges(self): |
| PrepForChanges(self._TMP_MANIFEST_DIR, self.dry_run) |
| |
| def _PushSpecChanges(self, commit_message): |
| _PushGitChanges(self._TMP_MANIFEST_DIR, commit_message, |
| dry_run=self.dry_run) |
| |
| def UpdateStatus(self, success, retries=5): |
| """Updates the status of the build for the current build spec. |
| Args: |
| success: True for success, False for failure |
| retries: Number of retries for updating the status |
| """ |
| last_error = None |
| for index in range(0, retries + 1): |
| try: |
| self._PrepSpecChanges() |
| status = self.STATUS_PASSED if success else self.STATUS_FAILED |
| commit_message = ('Automatic checkin: status=%s build_version %s for ' |
| '%s' % (status, |
| self.current_version, |
| self.build_name)) |
| if success: |
| self._SetPassed() |
| else: |
| self._SetFailed() |
| |
| self._PushSpecChanges(commit_message) |
| except (GitCommandException, cros_lib.RunCommandError) as e: |
| last_error = ('Failed to update the status for %s with the ' |
| 'following error %s' % (self.build_name, |
| e.message)) |
| logging.error(last_error) |
| logging.error('Retrying to generate buildspec: Retry %d/%d' % |
| (index + 1, retries)) |
| else: |
| return |
| else: |
| raise StatusUpdateException(last_error) |
| |
| |
| def SetLogFileHandler(logfile): |
| """This sets the logging handler to a file. |
| |
| Defines a Handler which writes INFO messages or higher to the sys.stderr |
| Add the log message handler to the logger |
| |
| Args: |
| logfile: name of the logfile to open |
| """ |
| logfile_handler = logging.handlers.RotatingFileHandler(logfile, backupCount=5) |
| logfile_handler.setLevel(logging.DEBUG) |
| logfile_handler.setFormatter(logging.Formatter(logging_format)) |
| logging.getLogger().addHandler(logfile_handler) |