Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(104)

Unified Diff: scripts/slave/recipe_modules/recipe_autoroller/api.py

Issue 2240303002: Reland of moving old recipe code (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: scripts/slave/recipe_modules/recipe_autoroller/api.py
diff --git a/scripts/slave/recipe_modules/recipe_autoroller/api.py b/scripts/slave/recipe_modules/recipe_autoroller/api.py
deleted file mode 100644
index 2c00720c5df1d9504dabcdc08c22f1fc377368ba..0000000000000000000000000000000000000000
--- a/scripts/slave/recipe_modules/recipe_autoroller/api.py
+++ /dev/null
@@ -1,288 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import hashlib
-import json
-import re
-
-from recipe_engine import recipe_api
-
-
-def get_reviewers(commit_infos):
- """Get a set of authors and reviewers from 'recipes.py autoroll' commit infos.
- """
- reviewers = set()
- for project, commits in commit_infos.iteritems():
- for commit in commits:
- reviewers.add(commit['author'])
- for field in ('R', 'TBR'):
- for m in re.findall(
- '^%s=(.*)' % field, commit['message'], re.MULTILINE):
- for s in m.split(','):
- # TODO(martiniss): infer domain for email address somehow?
- parts = s.split('@')
- if len(parts) != 2:
- continue
- # This mirrors a check in depot_tools/third_party/upload.py .
- if '.' not in parts[1]:
- continue
- reviewers.add(s.strip())
- return reviewers
-
-
-def get_bugs(commit_infos):
- """Return a set of bug IDs from 'recipes.py autoroll' commit infos.
- """
- bugs = set()
- for project, commits in commit_infos.iteritems():
- for commit in commits:
- for m in re.findall('^BUG=(.*)', commit['message'], re.MULTILINE):
- for s in m.split(','):
- if s:
- bugs.add(s.strip())
- return bugs
-
-
-def get_blame(commit_infos):
- blame = []
- for project, commits in commit_infos.iteritems():
- blame.append('%s:' % project)
- for commit in commits:
- message = commit['message'].splitlines()
- # TODO(phajdan.jr): truncate long messages.
- message = message[0] if message else 'n/a'
- blame.append(' https://crrev.com/%s %s (%s)' % (
- commit['revision'], message, commit['author']))
- return blame
-
-
-COMMIT_MESSAGE_HEADER = (
-"""
-This is an automated CL created by the recipe roller. This CL rolls recipe
-changes from upstream projects (e.g. depot_tools) into downstream projects
-(e.g. tools/build).
-""")
-
-
-NON_TRIVIAL_MESSAGE = (
-"""
-
-Please review the expectation changes, and LGTM as normal. The recipe roller
-will *NOT* CQ the change itself, so you must commit the change manually.
-"""
-)
-
-COMMIT_MESSAGE_FOOTER = (
-"""
-
-More info is at https://goo.gl/zkKdpD. Use https://goo.gl/noib3a to file a bug
-(or complain)
-
-""")
-
-
-TRIVIAL_ROLL_TBR_EMAILS = (
- 'martiniss@chromium.org',
- 'phajdan.jr@chromium.org',
-)
-
-
-# These are different results of a roll attempt:
-# - success means we have a working non-empty roll
-# - empty means the repo is using latest revision of its dependencies
-# - failure means there are roll candidates but none of them are suitable
-# for an automated roll
-ROLL_SUCCESS, ROLL_EMPTY, ROLL_FAILURE = range(3)
-
-
-def get_commit_message(roll_result):
- """Construct a roll commit message from 'recipes.py autoroll' result.
- """
- message = 'Roll recipe dependencies (%s).\n' % (
- 'trivial' if roll_result['trivial'] else 'nontrivial')
- message += COMMIT_MESSAGE_HEADER
- if not roll_result['trivial']:
- message += NON_TRIVIAL_MESSAGE
- message += COMMIT_MESSAGE_FOOTER
-
- commit_infos = roll_result['picked_roll_details']['commit_infos']
-
- message += '%s\n' % '\n'.join(get_blame(commit_infos))
- message += '\n'
- message += 'R=%s\n' % ','.join(get_reviewers(commit_infos))
- message += 'BUG=%s\n' % ','.join(get_bugs(commit_infos))
- return message
-
-
-class RecipeAutorollerApi(recipe_api.RecipeApi):
- def prepare_checkout(self): #pragma: no cover
- """Creates a default checkout for the recipe autoroller."""
- # Removed, but keep it here so roll succeeds
- # TODO(martiniss): Delete once safe
- pass
-
-
- def roll_projects(self, projects):
- """Attempts to roll each project from the provided list.
-
- If rolling any of the projects leads to failures, other
- projects are not affected.
- """
- project_data = self.m.luci_config.get_projects()
-
- self.m.cipd.install_client()
- with self.m.tempfile.temp_dir('recipes') as recipes_dir:
- self.m.cipd.ensure(recipes_dir, {
- 'infra/recipes-py': 'latest',
- })
-
- results = []
- with recipe_api.defer_results():
- for project in projects:
- with self.m.step.nest(str(project)):
- results.append(self._roll_project(
- project_data[project], recipes_dir))
-
- # We need to unwrap |DeferredResult|s.
- results = [r.get_result() for r in results]
-
- # Failures to roll are OK as long as at least one of the repos is moving
- # forward. For example, with repos with following dependencies:
- #
- # A <- B
- # A, B <- C
- #
- # New commit in A repo will need to get rolled into B first. However,
- # it'd also appear as a candidate for C roll, leading to a failure there.
- if ROLL_FAILURE in results and ROLL_SUCCESS not in results:
- self.m.python.failing_step(
- 'roll result',
- 'manual intervention needed: automated roll attempt failed')
-
- def _roll_project(self, project_data, recipes_dir):
- with self.m.tempfile.temp_dir('roll_%s' % project_data['id']) as workdir:
- self.m.git.checkout(
- project_data['repo_url'], dir_path=workdir, submodules=False)
-
- # Introduce ourselves to git - also needed for git cl upload to work.
- self.m.git(
- 'config', 'user.email', 'recipe-roller@chromium.org', cwd=workdir)
- self.m.git('config', 'user.name', 'recipe-roller', cwd=workdir)
-
- # git cl upload cannot work with detached HEAD, it requires a branch.
- self.m.git('checkout', '-t', '-b', 'roll', 'origin/master', cwd=workdir)
-
- recipes_cfg_path = workdir.join('infra', 'config', 'recipes.cfg')
-
- # Use the recipes bootstrap to checkout coverage.
- roll_step = self.m.step(
- 'roll',
- [recipes_dir.join('recipes.py'), '--use-bootstrap', '--package',
- recipes_cfg_path, 'autoroll', '--output-json', self.m.json.output()])
- roll_result = roll_step.json.output
-
- if roll_result['success']:
- self._process_successful_roll(roll_step, roll_result, workdir)
- return ROLL_SUCCESS
- else:
- if (not roll_result['roll_details'] and
- not roll_result['rejected_candidates_details']):
- roll_step.presentation.step_text += ' (already at latest revisions)'
- return ROLL_EMPTY
- else:
- return ROLL_FAILURE
-
- def _process_successful_roll(self, roll_step, roll_result, workdir):
- roll_step.presentation.logs['blame'] = get_blame(
- roll_result['picked_roll_details']['commit_infos'])
-
- if roll_result['trivial']:
- roll_step.presentation.step_text += ' (trivial)'
- else:
- roll_step.presentation.status = self.m.step.FAILURE
-
- # We use recipes.cfg hashes to uniquely identify changes (which might be
- # rebased).
- cfg_contents = roll_result['picked_roll_details']['spec']
- cfg_digest = hashlib.md5(cfg_contents).hexdigest()
-
- # We use diff hashes to uniquely identify patchsets within a change.
- self.m.git('commit', '-a', '-m', 'roll recipes.cfg', cwd=workdir)
- diff_result = self.m.git(
- 'show', '--format=%b',
- stdout=self.m.raw_io.output(),
- cwd=workdir,
- step_test_data=lambda: self.m.raw_io.test_api.stream_output(
- '-some line\n+some other line\n'))
- diff = diff_result.stdout
- diff_result.presentation.logs['output'] = diff.splitlines()
- diff_digest = hashlib.md5(diff).hexdigest()
-
- # Check if we have uploaded this before.
- need_to_upload = False
- rebase = False
- cat_result = self.m.gsutil.cat(
- 'gs://recipe-roller-cl-uploads/%s' % cfg_digest,
- stdout=self.m.raw_io.output(),
- stderr=self.m.raw_io.output(),
- ok_ret=(0,1))
-
- if cat_result.retcode:
- cat_result.presentation.logs['stderr'] = [
- self.m.step.active_result.stderr]
- assert re.search('No URLs matched', cat_result.stderr), (
- 'gsutil failed in an unexpected way; see stderr log')
- # We have never uploaded this change before.
- need_to_upload = True
-
- if not need_to_upload:
- # We have uploaded before, now let's check the diff hash to see if we
- # have uploaded this patchset before.
- change_data = json.loads(cat_result.stdout)
- if change_data['diff_digest'] != diff_digest:
- self.m.git('cl', 'issue', change_data['issue'], cwd=workdir)
- need_to_upload = True
- rebase = True
- cat_result.presentation.links['Issue %s' % change_data['issue']] = (
- change_data['issue_url'])
-
- if need_to_upload:
- commit_message = (
- 'Rebase' if rebase else get_commit_message(roll_result))
- if roll_result['trivial']:
- # Land immediately.
- upload_args = ['--use-commit-queue']
- if not rebase:
- commit_message += '\nTBR=%s\n' % ','.join(TRIVIAL_ROLL_TBR_EMAILS)
- else:
- upload_args = ['--send-mail', '--cq-dry-run']
- upload_args.extend(['--bypass-hooks', '-f', '-m', commit_message])
- upload_args.extend([
- '--auth-refresh-token-json=/creds/refresh_tokens/recipe-roller'])
- self.m.git('cl', 'upload', *upload_args, name='git cl upload', cwd=workdir)
- issue_result = self.m.git(
- 'cl', 'issue',
- name='git cl issue', stdout=self.m.raw_io.output(),
- cwd=workdir,
- step_test_data=lambda: self.m.raw_io.test_api.stream_output(
- 'Issue number: '
- '123456789 (https://codereview.chromium.org/123456789)'))
-
- m = re.match('Issue number: (\d+) \((\S*)\)', issue_result.stdout.strip())
- if not m:
- self.m.python.failing_step(
- 'git cl upload failed', 'git cl issue output "%s" is not valid' %
- issue_result.stdout.strip())
-
- change_data = {
- 'issue': m.group(1),
- 'issue_url': m.group(2),
- 'diff_digest': diff_digest,
- }
- issue_result.presentation.links['Issue %s' % change_data['issue']] = (
- change_data['issue_url'])
- self.m.gsutil.upload(
- self.m.json.input(change_data),
- 'recipe-roller-cl-uploads',
- cfg_digest)
« no previous file with comments | « scripts/slave/recipe_modules/recipe_autoroller/__init__.py ('k') | scripts/slave/recipe_modules/recipe_autoroller/test_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698