• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2020 The ChromiumOS Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Wrapper around bb invocation of the cl_factory recipe."""
6
7import argparse
8import json
9import re
10import subprocess
11import sys
12
13from shlex import quote
14
15
16def Main(args):  # pylint: disable=invalid-name
17  """Wrapper around `bb add` invocation to invoke cl_factory.
18
19  Friendlier wrapper around the `bb add` command to aid invoking the cl_factory
20  recipe. A typical invocation would look like the following:
21
22  cl_factory \
23    --cl https://chrome-internal-review.googlesource.com/c/chromeos/program/galaxy/+/3095418 \
24    --regex src/program src/project \
25    --reviewers reviewer1@chromium.org reviewer2@google.com \
26    --ccs cc1@google.com \
27    --hashtag regen-audio-configs \
28    --replace_strings *.star foo bar \
29    --message "Hello world
30
31BUG=chromium:1092954
32TEST=CQ"
33
34  Note that by default the command is only printed out but not ran. To run the
35  command and cause the invocation of the cl_factory recipe additionally pass:
36
37  --nodryrun
38
39  Also note that you likely to want to provide a "hashtag". This will allow
40  gerrit cli commands to accurately target the generated CLs for batch review,
41  verify, and commit. These commands are output in the "summarize results" step
42  of the ClFactory recipe.
43
44  For more information on arguments, such as the variables available for
45  interpolation in the CL message, see:
46
47  http://cs/chromeos_public/infra/recipes/recipes/cl_factory.proto
48
49  Args:
50    args: Namespace object holding parsed args.
51  """
52  cmd = ['bb', 'add']
53
54  for cl in args.cl or []:
55    cmd.extend(['-cl', cl])
56
57  cmd.extend(csv_param('repo_regexes', args.regex))
58  cmd.extend(csv_param('reviewers', args.reviewers))
59  cmd.extend(csv_param('ccs', args.ccs))
60  cmd.extend(csv_param('hashtags', args.hashtag))
61
62  if args.set_source_depends:
63    cmd.extend(['-p', 'set_source_depends=true'])
64
65  replace_strings = list(map(lambda rs: replace_strings_dict(rs),
66                             args.replace_strings or []))
67  if replace_strings:
68    cmd.extend(
69        ['-p', '\'replace_strings={}\''.format(json.dumps(replace_strings))])
70
71  if args.manifest_branch:
72    cmd.extend(['-p', '\'manifest_branch={}\''.format(args.manifest_branch)])
73
74  cmd.extend(['-p', quote('message_template={}'.format(args.message))])
75  cmd.append('chromeos/infra/ClFactory')
76
77  cmd = ' '.join(cmd)
78
79  print('Command:')
80  print(cmd)
81  print('\n')
82
83  if args.dryrun:
84    print('Dry run only. Command not executed. Pass:')
85    print('  --nodryrun')
86    print('to invoke the recipe.')
87  else:
88    print('Executing command.')
89    subprocess.run(cmd, shell=True, check=True)
90
91
92def csv_param(name, values):
93  if not values:
94    return []
95  values = ', '.join(map(lambda v: '"{}"'.format(v), values))
96  return ['-p', '\'{}=[{}]\''.format(name, values)]
97
98
99def replace_strings_dict(rs):
100  return {
101      'file_glob': rs[0],
102      'before': rs[1],
103      'after': rs[2],
104  }
105
106
107def validate_regex(value):
108  try:
109    re.compile(value)
110    return value
111  except:
112    raise argparse.ArgumentTypeError('Invalid regex "{}"'.format(value))
113
114
115def main():
116  """Main program which parses args and runs Main."""
117  parser = argparse.ArgumentParser(description=__doc__)
118  parser.add_argument(
119      '--cl',
120      nargs='+',
121      type=str,
122      required=False,
123      help='CL URL(s) input to patch into the projects.')
124  parser.add_argument(
125      '--regex',
126      nargs='+',
127      type=validate_regex,
128      required=True,
129      help='Operate on projects matching the regex or '
130      'wildcard. Usage as in `repo forall`.')
131  parser.add_argument(
132      '--reviewers',
133      nargs='+',
134      type=str,
135      required=True,
136      help='Reviewers to add to the generated CLs.')
137  parser.add_argument(
138      '--ccs',
139      nargs='+',
140      type=str,
141      required=False,
142      help='CCs to add to the generated CLs.')
143  parser.add_argument(
144      '--hashtag',
145      nargs='+',
146      type=str,
147      default=[],
148      help='Hashtags to add to the generated CLs.')
149  parser.add_argument(
150      '--replace_strings',
151      action='append',
152      nargs=3,
153      metavar=('glob', 'before', 'after'),
154      help='String replacements to perform on the generated CLs.')
155  parser.add_argument(
156      '--message',
157      type=str,
158      required=True,
159      help='Message template for the generated CLs.')
160  parser.add_argument(
161      '--set_source_depends',
162      type=bool,
163      required=False,
164      default=False,
165      help='Whether to set Cq-Depends on the input CLs to the list '
166      'of generated CLs.')
167  parser.add_argument(
168      '--nodryrun',
169      dest='dryrun',
170      default=True,
171      action='store_false')
172  parser.add_argument(
173      '--manifest_branch',
174      type=str,
175      required=False,
176      help='Manifest branch to checkout and upload changes for. '
177      'If not specified, ToT is used.')
178  args = parser.parse_args()
179  Main(args)
180
181
182if __name__ == '__main__':
183  sys.exit(main())
184