Flutter Engine
The Flutter Engine
build.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
4# for details. All rights reserved. Use of this source code is governed by a
5# BSD-style license that can be found in the LICENSE file.
6
7import argparse
8import io
9import json
10import os
11import subprocess
12import sys
13import time
14import utils
15
16import gn as gn_py
17
18HOST_OS = utils.GuessOS()
19HOST_CPUS = utils.GuessCpus()
20SCRIPT_DIR = os.path.dirname(sys.argv[0])
21DART_ROOT = os.path.realpath(os.path.join(SCRIPT_DIR, '..'))
22AVAILABLE_ARCHS = utils.ARCH_FAMILY.keys()
23
24usage = """\
25usage: %%prog [options] [targets]
26
27This script invokes ninja to build Dart.
28"""
29
31 parser = argparse.ArgumentParser(
32 description='Runs GN (if necessary) followed by ninja',
33 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
34
35 config_group = parser.add_argument_group('Configuration Related Arguments')
36 gn_py.AddCommonConfigurationArgs(config_group)
37
38 gn_group = parser.add_argument_group('GN Related Arguments')
39 gn_py.AddCommonGnOptionArgs(gn_group)
40
41 other_group = parser.add_argument_group('Other Arguments')
42 gn_py.AddOtherArgs(other_group)
43
44 other_group.add_argument("-j",
45 type=int,
46 help='Ninja -j option for RBE builds.',
47 default=200 if sys.platform == 'win32' else 1000)
48 other_group.add_argument("-l",
49 type=int,
50 help='Ninja -l option for RBE builds.',
51 default=64)
52 other_group.add_argument("--no-start-rbe",
53 help="Don't try to start rbe",
54 default=False,
55 action='store_true')
56 other_group.add_argument(
57 "--check-clean",
58 help="Check that a second invocation of Ninja has nothing to do",
59 default=False,
60 action='store_true')
61
62 parser.add_argument('build_targets', nargs='*')
63
64 return parser
65
66
67def NotifyBuildDone(build_config, success, start):
68 if not success:
69 print("BUILD FAILED")
70
71 sys.stdout.flush()
72
73 # Display a notification if build time exceeded DART_BUILD_NOTIFICATION_DELAY.
74 notification_delay = float(
75 os.getenv('DART_BUILD_NOTIFICATION_DELAY', sys.float_info.max))
76 if (time.time() - start) < notification_delay:
77 return
78
79 if success:
80 message = 'Build succeeded.'
81 else:
82 message = 'Build failed.'
83 title = build_config
84
85 command = None
86 if HOST_OS == 'macos':
87 # Use AppleScript to display a UI non-modal notification.
88 script = 'display notification "%s" with title "%s" sound name "Glass"' % (
89 message, title)
90 command = "osascript -e '%s' &" % script
91 elif HOST_OS == 'linux':
92 if success:
93 icon = 'dialog-information'
94 else:
95 icon = 'dialog-error'
96 command = "notify-send -i '%s' '%s' '%s' &" % (icon, message, title)
97 elif HOST_OS == 'win32':
98 if success:
99 icon = 'info'
100 else:
101 icon = 'error'
102 command = (
103 "powershell -command \""
104 "[reflection.assembly]::loadwithpartialname('System.Windows.Forms')"
105 "| Out-Null;"
106 "[reflection.assembly]::loadwithpartialname('System.Drawing')"
107 "| Out-Null;"
108 "$n = new-object system.windows.forms.notifyicon;"
109 "$n.icon = [system.drawing.systemicons]::information;"
110 "$n.visible = $true;"
111 "$n.showballoontip(%d, '%s', '%s', "
112 "[system.windows.forms.tooltipicon]::%s);\"") % (
113 5000, # Notification stays on for this many milliseconds
114 message,
115 title,
116 icon)
117
118 if command:
119 # Ignore return code, if this command fails, it doesn't matter.
120 os.system(command)
121
122
123def UseRBE(out_dir):
124 args_gn = os.path.join(out_dir, 'args.gn')
125 return 'use_rbe = true' in open(args_gn, 'r').read()
126
127
128# Try to start RBE, but don't bail out if we can't. Instead print an error
129# message, and let the build fail with its own error messages as well.
130rbe_started = False
131bootstrap_path = None
132
133
134def StartRBE(out_dir, env):
135 global rbe_started, bootstrap_path
136 if not rbe_started:
137 rbe_dir = 'buildtools/reclient'
138 with open(os.path.join(out_dir, 'args.gn'), 'r') as fp:
139 for line in fp:
140 if 'rbe_dir' in line:
141 words = line.split()
142 rbe_dir = words[2][1:-1] # rbe_dir = "/path/to/rbe"
143 bootstrap_path = os.path.join(rbe_dir, 'bootstrap')
144 bootstrap_command = [bootstrap_path]
145 process = subprocess.Popen(bootstrap_command, env=env)
146 process.wait()
147 if process.returncode != 0:
148 print('Failed to start RBE')
149 return False
150 rbe_started = True
151 return True
152
153
154def StopRBE(env):
155 global rbe_started, bootstrap_path
156 if rbe_started:
157 bootstrap_command = [bootstrap_path, '--shutdown']
158 process = subprocess.Popen(bootstrap_command, env=env)
159 process.wait()
160 rbe_started = False
161
162
163# Returns a tuple (build_config, command to run, whether rbe is used)
164def BuildOneConfig(options, targets, target_os, mode, arch, sanitizer, env):
165 build_config = utils.GetBuildConf(mode, arch, target_os, sanitizer)
166 out_dir = utils.GetBuildRoot(HOST_OS, mode, arch, target_os, sanitizer)
167 using_rbe = False
168 command = ['buildtools/ninja/ninja', '-C', out_dir]
169 if options.verbose:
170 command += ['-v']
171 if UseRBE(out_dir):
172 if options.no_start_rbe or StartRBE(out_dir, env):
173 using_rbe = True
174 command += [('-j%s' % str(options.j))]
175 command += [('-l%s' % str(options.l))]
176 else:
177 exit(1)
178 command += targets
179 return (build_config, command, using_rbe)
180
181
182def RunOneBuildCommand(build_config, args, env):
183 start_time = time.time()
184 print(' '.join(args))
185 process = subprocess.Popen(args, env=env, stdin=None)
186 process.wait()
187 if process.returncode != 0:
188 NotifyBuildDone(build_config, success=False, start=start_time)
189 return 1
190 else:
191 NotifyBuildDone(build_config, success=True, start=start_time)
192
193 return 0
194
195
196def CheckCleanBuild(build_config, args, env):
197 args = args + ['-n', '-d', 'explain']
198 print(' '.join(args))
199 process = subprocess.Popen(args,
200 env=env,
201 stdout=subprocess.PIPE,
202 stderr=subprocess.PIPE,
203 stdin=None)
204 out, err = process.communicate()
205 process.wait()
206 if process.returncode != 0:
207 return 1
208 if 'ninja: no work to do' not in out.decode('utf-8'):
209 print(err.decode('utf-8'))
210 return 1
211
212 return 0
213
214
216 with io.open('tools/bots/test_matrix.json', encoding='utf-8') as fd:
217 config = json.loads(fd.read())
218 env = dict()
219 for k, v in config['sanitizer_options'].items():
220 env[str(k)] = str(v)
221 symbolizer_path = config['sanitizer_symbolizer'].get(HOST_OS, None)
222 if symbolizer_path:
223 symbolizer_path = str(os.path.join(DART_ROOT, symbolizer_path))
224 env['ASAN_SYMBOLIZER_PATH'] = symbolizer_path
225 env['LSAN_SYMBOLIZER_PATH'] = symbolizer_path
226 env['MSAN_SYMBOLIZER_PATH'] = symbolizer_path
227 env['TSAN_SYMBOLIZER_PATH'] = symbolizer_path
228 env['UBSAN_SYMBOLIZER_PATH'] = symbolizer_path
229 return env
230
231
232def Build(configs, env, options):
233 # Build regular configs.
234 rbe_builds = []
235 for (build_config, args, rbe) in configs:
236 if args is None:
237 return 1
238 if rbe:
239 rbe_builds.append([env, args])
240 elif RunOneBuildCommand(build_config, args, env=env) != 0:
241 return 1
242
243 # Run RBE builds in parallel.
244 active_rbe_builds = []
245 for (env, args) in rbe_builds:
246 print(' '.join(args))
247 process = subprocess.Popen(args, env=env)
248 active_rbe_builds.append([args, process])
249 while active_rbe_builds:
250 time.sleep(0.1)
251 for rbe_build in active_rbe_builds:
252 (args, process) = rbe_build
253 if process.poll() is not None:
254 print(' '.join(args) + " done.")
255 active_rbe_builds.remove(rbe_build)
256 if process.returncode != 0:
257 for (_, to_kill) in active_rbe_builds:
258 to_kill.terminate()
259 return 1
260
261 if options.check_clean:
262 for (build_config, args, rbe) in configs:
263 if CheckCleanBuild(build_config, args, env=env) != 0:
264 return 1
265
266 return 0
267
268
269def Main():
270 starttime = time.time()
271 # Parse the options.
272 parser = BuildOptions()
273 options = parser.parse_args()
274
275 targets = options.build_targets
276
277 if not gn_py.ProcessOptions(options):
278 parser.print_help()
279 return 1
280
281 # If binaries are built with sanitizers we should use those flags.
282 # If the binaries are not built with sanitizers the flag should have no
283 # effect.
284 env = dict(os.environ)
286
287 # macOS's python sets CPATH, LIBRARY_PATH, SDKROOT implicitly.
288 #
289 # See:
290 #
291 # * https://openradar.appspot.com/radar?id=5608755232243712
292 # * https://github.com/dart-lang/sdk/issues/52411
293 #
294 # Remove these environment variables to avoid affecting clang's behaviors.
295 if sys.platform == 'darwin':
296 env.pop('CPATH', None)
297 env.pop('LIBRARY_PATH', None)
298 env.pop('SDKROOT', None)
299
300 # Always run GN before building.
301 gn_py.RunGnOnConfiguredConfigurations(options, env)
302
303 # Build all targets for each requested configuration.
304 configs = []
305 for target_os in options.os:
306 for mode in options.mode:
307 for arch in options.arch:
308 for sanitizer in options.sanitizer:
309 configs.append(
310 BuildOneConfig(options, targets, target_os, mode, arch,
311 sanitizer, env))
312
313 exit_code = Build(configs, env, options)
314
315 endtime = time.time()
316
317 StopRBE(env)
318
319 if exit_code == 0:
320 print("The build took %.3f seconds" % (endtime - starttime))
321 return exit_code
322
323
324if __name__ == '__main__':
325 sys.exit(Main())
static bool read(SkStream *stream, void *buffer, size_t amount)
def BuildOneConfig(options, targets, target_os, mode, arch, sanitizer, env)
Definition: build.py:164
def StartRBE(out_dir, env)
Definition: build.py:134
def Main()
Definition: build.py:269
def NotifyBuildDone(build_config, success, start)
Definition: build.py:67
def RunOneBuildCommand(build_config, args, env)
Definition: build.py:182
def Build(configs, env, options)
Definition: build.py:232
def StopRBE(env)
Definition: build.py:154
def CheckCleanBuild(build_config, args, env)
Definition: build.py:196
def BuildOptions()
Definition: build.py:30
def SanitizerEnvironmentVariables()
Definition: build.py:215
def UseRBE(out_dir)
Definition: build.py:123
exit(kErrorExitCode)
const myers::Point & get(const myers::Segment &)
def print(*args, **kwargs)
Definition: run_tests.py:49
def GuessOS()
Definition: utils.py:21
def GetBuildConf(mode, arch)
Definition: utils.py:139
def GuessCpus()
Definition: utils.py:55
def GetBuildRoot(host_os, mode=None, arch=None, sanitizer=None)
Definition: utils.py:143
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741