1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
media / ffmpeg / scripts / robosushi.py [blame]
#!/usr/bin/env python3
#
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Set up everything for the roll.
#
# --prompt: require user input before taking any action. Use in conjunction
# with (before) other options.
# --setup: set up the host to do a roll. Idempotent, but probably doesn't
# need to be run more than once in a while.
# --auto-merge: do the merge. Requires --setup to be run first.
# --test: configure ffmpeg for the host machine, and try running media
# unit tests and the ffmpeg regression tests.
# --build-gn: build ffmpeg configs for all platforms, then generate gn config.
# --patches: generate chromium/patches/README and commit it locally.
import dataclasses
import optparse
import os
import sys
from subprocess import check_output
import typing
import robo_branch
from robo_lib import shell
from robo_lib import errors
from robo_lib import config
import robo_build
import robo_setup
def AreGnConfigsDone(cfg):
# Try to get everything to build if we haven't committed the configs yet.
# Note that the only time we need to do this again is if some change makes
# different files added / deleted to the build, or if ffmpeg configure
# changes. We don't need to do this if you just edit ffmpeg sources;
# those will be built with the tests if they've changed since last time.
#
# So, if you're just editing ffmpeg sources to get tests to pass, then you
# probably don't need to do this step again.
if cfg.force_gn_rebuild():
return False
return robo_branch.IsCommitOnThisBranch(cfg, cfg.gn_commit_title())
def BuildGnConfigsUnconditionally(robo_configuration):
robo_build.BuildAndImportAllFFmpegConfigs(robo_configuration)
# Run sanity checks on the merge before we commit.
robo_branch.CheckMerge(robo_configuration)
# Write the config changes to help the reviewer.
robo_branch.WriteConfigChangesFile(robo_configuration)
# TODO(liberato): Add the 'autodetect' regex too.
# Handle autorenames last, so that we don't stage things and then fail.
# While it's probably okay, it's nicer if we don't.
robo_branch.HandleAutorename(robo_configuration)
robo_branch.AddAndCommit(robo_configuration,
robo_configuration.gn_commit_title())
@dataclasses.dataclass
class Target():
name: str
desc: str
func: typing.Callable[[config.RoboConfiguration], None]
skip: typing.Callable[[config.RoboConfiguration], bool] = None
@staticmethod
def SerialTarget(name:str, desc:str, tasks:['Target']) -> 'Target':
LoadTargets(*tasks)
return Target(name, desc,
lambda cfg: RunAllTargets(cfg, [t.name for t in tasks]))
def can_skip(self, cfg:config.RoboConfiguration):
return self.skip is not None and self.skip(cfg)
def execute(self, cfg:config.RoboConfiguration):
return self.func(cfg)
steps = {}
def LoadTargets(*targets):
global steps
for target in targets:
steps[target.name] = target
def MakeErrorStr(queue:[Target]):
chain = " > ".join([t.name for t in queue])
return f"\033[31m{chain}\033[0m"
class StepError(Exception):
def __init__(self, target_queue:[Target], nested_exception:Exception):
super().__init__(MakeErrorStr(target_queue))
self._steps = target_queue
self._nested = nested_exception
def RaiseFrom(self, target:Target) -> 'StepError':
raise StepError([target] + self._steps, self._nested) from self._nested
def RunAllTargets(cfg:config.RoboConfiguration, tasks:[str]):
global steps
for target_name in tasks:
if target_name not in steps:
raise ValueError(f'Unknown step: {target_name}')
RunTarget(steps[target_name], cfg)
def RunTarget(target:Target, cfg:config.RoboConfiguration):
shell.log(f'loading step: `{target.name}`')
try:
if cfg.skip_allowed() and target.can_skip(cfg):
shell.log(f' skipped step: `{target.name}`')
else:
target.execute(cfg)
except StepError as se:
se.RaiseFrom(target)
except Exception as e:
raise StepError([target], e) from None
LoadTargets(
Target(name="start_fake_deps_roll",
desc="Try a test deps roll against the sushi (not master) branch",
func=robo_branch.TryFakeDepsRoll),
Target(name="download_mac_sdk",
desc="Try to download the mac SDK, if needed.",
func=robo_setup.FetchMacSDK),
Target.SerialTarget(
name="setup",
desc="Convenience roll-up for --setup",
tasks=[Target(name = "install_prereqs",
desc = "Install required software",
func = robo_setup.InstallPrereqs),
Target(name = "ensure_toolchains",
desc = "Download mac / win toolchains",
func = robo_setup.EnsureToolchains),
Target(name="ensure_new_asan_dir",
desc="Create ninja ASAN output directory",
func=robo_setup.EnsureNewASANDirWorks),
Target(name="ensure_x86_dir",
desc="Create ninja x86 output directory",
func=robo_setup.Ensurex86ChromeOutputDir),
Target(name="ensure_nasm",
desc="Compile chromium's nasm if needed",
func=robo_setup.EnsureChromiumNasm),
Target(name="ensure_remote",
desc="Set git remotes if needed",
func=robo_setup.EnsureUpstreamRemote)]),
Target.SerialTarget(
name="auto-merge",
desc="Roll-up for --auto-merge",
tasks=[Target(name="erase_build_output",
desc="Once, at the start of the merge, delete build_ffmpeg"
" output.",
func=robo_build.ObliterateOldBuildOutputIfNeeded),
Target(name="create_sushi_branch",
desc="Create a sushi-MDY branch if we're not on one",
func=robo_branch.CreateAndCheckoutDatedSushiBranch),
Target(name="merge_from_upstream",
desc="Merge upstream/master to our local sushi-MDY branch",
func=robo_branch.MergeUpstreamToSushiBranchIfNeeded),
Target(name="push_merge_to_origin",
desc="Push the merge commit, without review, to"
" origin/sushi-MDY.",
func=robo_branch.PushToOriginWithoutReviewAndTrack),
Target(name="build_gn_configs",
desc="Build gn configs, and commit the results locally.",
skip=AreGnConfigsDone,
func=BuildGnConfigsUnconditionally),
Target(name="update_patches_file",
desc="Rewrite chromium/patches/README and commit locally.",
skip=robo_branch.IsPatchesFileDone,
func=robo_branch.UpdatePatchesFileUnconditionally),
Target(name="update_chromium_readme",
desc="Rewrite README.chromium to reflect upstream SHA-1.",
skip=robo_branch.IsChromiumReadmeDone,
func=robo_branch.UpdateChromiumReadmeWithUpstream),
Target(name="run_tests",
desc="Compile and run ffmpeg_regression_tests and "
"media_unittests",
func=robo_build.RunTests),
Target(name="build_x86",
desc="Compile media_unittests for x86 to ensure building",
func=robo_build.BuildChromex86),
Target(name="upload_for_review",
desc="Upload everything to Gerrit for review, if needed",
skip=robo_branch.IsUploadedForReview,
func=robo_branch.UploadForReview),
Target(name="merge_back_to_origin",
desc="Sushi has landed post review, merge/push to origin",
func=robo_branch.MergeBackToOriginMaster),
Target(name="start_real_deps_roll",
desc="Try a real deps roll against the sushi branch",
func=robo_branch.TryRealDepsRoll),
Target(name="print_happy_message",
desc="Print a happy message when things have completed.",
func=robo_branch.PrintHappyMessage)]),
)
def ListSteps():
for name, step in steps.items():
print(f'{name}: {step.desc}')
def main(argv):
parser = optparse.OptionParser(usage='Usage: %prog [options]')
parser.add_option('--branch',
action='store',
help='Manually set sushi branch name')
parser.add_option('--prompt',
action='store_true',
help='Prompt for each robosushi step')
parser.add_option('--verbose',
action='store_true',
help='Log all shell calls')
parser.add_option(
'--setup',
action='store_true',
help='Run initial setup steps. Do this once before auto-merge.')
parser.add_option('--test',
action='store_true',
help='Build and run all tests')
parser.add_option(
'--build-gn',
action='store_true',
help='Unconditionally build all the configs and import them.')
parser.add_option('--patches',
action='store_true',
help='Update patches file only')
parser.add_option('--auto-merge',
action='store_true',
help='Run auto-merge. (Usually what you want)')
parser.add_option('--list', action='store_true', help='List steps')
parser.add_option('--no-skip',
action='store_true',
help='Don\'t allow any steps to be skipped')
parser.add_option('--force-gn-rebuild',
action='store_true',
help='Force rebuild of GN args.')
parser.add_option('--step', action='append', help='Step to run.')
options, args = parser.parse_args(argv)
if options.list:
ListSteps()
return 0
robo_configuration = config.RoboConfiguration()
robo_configuration.chdir_to_ffmpeg_home()
exec_steps = []
if options.prompt:
robo_configuration.set_prompt_on_call(True)
if options.verbose:
robo_configuration.set_log_shell_calls(True)
if options.no_skip:
robo_configuration.set_skip_allowed(False)
if options.force_gn_rebuild:
robo_configuration.set_force_gn_rebuild()
if options.branch:
robo_configuration.SetBranchName(options.branch)
if options.setup:
exec_steps += ["setup"]
if options.test:
robo_build.BuildAndImportFFmpegConfigForHost(robo_configuration)
robo_build.RunTests(robo_configuration)
if options.build_gn:
# Unconditionally build all the configs and import them.
robo_build.BuildAndImportAllFFmpegConfigs(robo_configuration)
if options.patches:
# To be run after committing a local change to fix the tests.
if not robo_branch.IsWorkingDirectoryClean():
raise errors.UserInstructions(
"Working directory must be clean to generate patches file")
robo_branch.UpdatePatchesFileUnconditionally(robo_configuration)
if options.auto_merge:
exec_steps += ["auto-merge"]
if options.step:
exec_steps += options.step
if len(exec_steps) == 0:
parser.print_help()
return 1
# TODO: make sure that any untracked autorename files are removed, or
# make sure that the autorename git script doesn't try to 'git rm'
# untracked files, else the script fails.
RunAllTargets(robo_configuration, exec_steps)
# TODO: Start a fake deps roll. To do this, we would:
# Create new remote branch from the current remote sushi branch.
# Create and check out a new local branch at the current local branch.
# Make the new local branch track the new remote branch.
# Push to origin/new remote branch.
# Start a fake deps roll CL that runs the *san bots.
# Switch back to original local branch.
# For extra points, include a pointer to the fake deps roll CL in the
# local branch, so that when it's pushed for review, it'll point the
# reviewer at it.
# TODO: git cl upload for review.
if __name__ == "__main__":
main(sys.argv[1:])