Flutter Engine
The Flutter Engine
build_fuchsia_artifacts.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# Copyright 2013 The Flutter Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7""" Builds all Fuchsia artifacts vended by Flutter.
8"""
9
10import argparse
11import errno
12import os
13import platform
14import re
15import shutil
16import subprocess
17import sys
18import tempfile
19
20from gather_flutter_runner_artifacts import CreateMetaPackage, CopyPath
21from gen_package import CreateFarPackage
22
23_script_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..'))
24_src_root_dir = os.path.join(_script_dir, '..', '..', '..')
25_out_dir = os.path.join(_src_root_dir, 'out', 'ci')
26_bucket_directory = os.path.join(_out_dir, 'fuchsia_bucket')
27
28
29def IsLinux():
30 return platform.system() == 'Linux'
31
32
33def IsMac():
34 return platform.system() == 'Darwin'
35
36
38 # host_os references the gn host_os
39 # https://gn.googlesource.com/gn/+/main/docs/reference.md#var_host_os
40 host_os = ''
41 if IsLinux():
42 host_os = 'linux'
43 elif IsMac():
44 host_os = 'mac'
45 else:
46 host_os = 'windows'
47
48 return os.path.join(_src_root_dir, 'fuchsia', 'sdk', host_os)
49
50
52 host_arch = platform.machine()
53 # platform.machine() returns AMD64 on 64-bit Windows.
54 if host_arch in ['x86_64', 'AMD64']:
55 return 'x64'
56 elif host_arch == 'aarch64':
57 return 'arm64'
58 raise Exception('Unsupported host architecture: %s' % host_arch)
59
60
62 return os.path.join(GetFuchsiaSDKPath(), 'tools', GetHostArchFromPlatform(), 'pm')
63
64
65def RunExecutable(command):
66 subprocess.check_call(command, cwd=_src_root_dir)
67
68
69def RunGN(variant_dir, flags):
70 print('Running gn for variant "%s" with flags: %s' % (variant_dir, ','.join(flags)))
72 os.path.join('flutter', 'tools', 'gn'),
73 ] + flags)
74
75 assert os.path.exists(os.path.join(_out_dir, variant_dir))
76
77
78def BuildNinjaTargets(variant_dir, targets):
79 assert os.path.exists(os.path.join(_out_dir, variant_dir))
80
81 print('Running autoninja for targets: %s' % targets)
82 RunExecutable(['autoninja', '-C', os.path.join(_out_dir, variant_dir)] + targets)
83
84
86 if not os.path.exists(path):
87 return
88
89 if os.path.isfile(path) or os.path.islink(path):
90 os.unlink(path)
91 else:
92 shutil.rmtree(path)
93
94
95def CopyFiles(source, destination):
96 try:
97 shutil.copytree(source, destination)
98 except OSError as error:
99 if error.errno == errno.ENOTDIR:
100 shutil.copy(source, destination)
101 else:
102 raise
103
104
105def FindFile(name, path):
106 for root, dirs, files in os.walk(path):
107 if name in files:
108 return os.path.join(root, name)
109
110
111def FindFileAndCopyTo(file_name, source, dest_parent, dst_name=None):
112 found = FindFile(file_name, source)
113 if not dst_name:
114 dst_name = file_name
115 if found:
116 dst_path = os.path.join(dest_parent, dst_name)
117 CopyPath(found, dst_path)
118
119
120def CopyGenSnapshotIfExists(source, destination):
121 source_root = os.path.join(_out_dir, source)
122 destination_base = os.path.join(destination, 'dart_binaries')
123 FindFileAndCopyTo('gen_snapshot', source_root, destination_base)
124 FindFileAndCopyTo('gen_snapshot_product', source_root, destination_base)
126 'kernel_compiler.dart.snapshot', source_root, destination_base, 'kernel_compiler.snapshot'
127 )
129 'frontend_server.dart.snapshot', source_root, destination_base,
130 'flutter_frontend_server.snapshot'
131 )
133 'list_libraries.dart.snapshot', source_root, destination_base, 'list_libraries.snapshot'
134 )
135
136
137def CopyFlutterTesterBinIfExists(source, destination):
138 source_root = os.path.join(_out_dir, source)
139 destination_base = os.path.join(destination, 'flutter_binaries')
140 FindFileAndCopyTo('flutter_tester', source_root, destination_base)
141
142
143def CopyZirconFFILibIfExists(source, destination):
144 source_root = os.path.join(_out_dir, source)
145 destination_base = os.path.join(destination, 'flutter_binaries')
146 FindFileAndCopyTo('libzircon_ffi.so', source_root, destination_base)
147
148
149def CopyToBucketWithMode(source, destination, aot, product, runner_type, api_level):
150 mode = 'aot' if aot else 'jit'
151 product_suff = '_product' if product else ''
152 runner_name = '%s_%s%s_runner' % (runner_type, mode, product_suff)
153 far_dir_name = '%s_far' % runner_name
154 source_root = os.path.join(_out_dir, source)
155 far_base = os.path.join(source_root, far_dir_name)
156 CreateMetaPackage(far_base, runner_name)
157 pm_bin = GetPMBinPath()
158 key_path = os.path.join(_script_dir, 'development.key')
159
160 destination = os.path.join(_bucket_directory, destination, mode)
161 CreateFarPackage(pm_bin, far_base, key_path, destination, api_level)
162 patched_sdk_dirname = '%s_runner_patched_sdk' % runner_type
163 patched_sdk_dir = os.path.join(source_root, patched_sdk_dirname)
164 dest_sdk_path = os.path.join(destination, patched_sdk_dirname)
165 if not os.path.exists(dest_sdk_path):
166 CopyPath(patched_sdk_dir, dest_sdk_path)
167 CopyGenSnapshotIfExists(source_root, destination)
168 CopyFlutterTesterBinIfExists(source_root, destination)
169 CopyZirconFFILibIfExists(source_root, destination)
170
171
172def CopyToBucket(src, dst, product=False):
173 api_level = ReadTargetAPILevel()
174 CopyToBucketWithMode(src, dst, False, product, 'flutter', api_level)
175 CopyToBucketWithMode(src, dst, True, product, 'flutter', api_level)
176 CopyToBucketWithMode(src, dst, False, product, 'dart', api_level)
177 CopyToBucketWithMode(src, dst, True, product, 'dart', api_level)
178
179
181 filename = os.path.join(os.path.dirname(__file__), '../../build/config/fuchsia/gn_configs.gni')
182 with open(filename) as f:
183 for line in f:
184 if line.startswith('fuchsia_target_api_level'):
185 return line.split('=')[-1].strip()
186 assert False, 'No fuchsia_target_api_level found in //flutter/build/config/fuchsia/gn_configs.gni'
187
188
189def CopyVulkanDepsToBucket(src, dst, arch):
190 sdk_path = GetFuchsiaSDKPath()
191 deps_bucket_path = os.path.join(_bucket_directory, dst)
192 if not os.path.exists(deps_bucket_path):
193 FindFileAndCopyTo('VkLayer_khronos_validation.json', '%s/pkg' % (sdk_path), deps_bucket_path)
195 'VkLayer_khronos_validation.so', '%s/arch/%s' % (sdk_path, arch), deps_bucket_path
196 )
197
198
200 source_root = os.path.join(_out_dir, src)
201 deps_bucket_path = os.path.join(_bucket_directory, dst)
202 FindFileAndCopyTo('icudtl.dat', source_root, deps_bucket_path)
203
204
205def CopyBuildToBucket(runtime_mode, arch, optimized, product):
206 unopt = "_unopt" if not optimized else ""
207
208 out_dir = 'fuchsia_%s%s_%s/' % (runtime_mode, unopt, arch)
209 bucket_dir = 'flutter/%s/%s%s/' % (arch, runtime_mode, unopt)
210 deps_dir = 'flutter/%s/deps/' % (arch)
211
212 CopyToBucket(out_dir, bucket_dir, product)
213 CopyVulkanDepsToBucket(out_dir, deps_dir, arch)
214 CopyIcuDepsToBucket(out_dir, deps_dir)
215
216 # Copy the CIPD YAML template from the source directory to be next to the bucket
217 # we are about to package.
218 cipd_yaml = os.path.join(_script_dir, 'fuchsia.cipd.yaml')
219 CopyFiles(cipd_yaml, os.path.join(_bucket_directory, 'fuchsia.cipd.yaml'))
220
221 # Copy the license files from the source directory to be next to the bucket we
222 # are about to package.
223 bucket_root = os.path.join(_bucket_directory, 'flutter')
224 licenses_root = os.path.join(_src_root_dir, 'flutter/ci/licenses_golden')
225 license_files = ['licenses_flutter', 'licenses_fuchsia', 'licenses_skia']
226 for license in license_files:
227 src_path = os.path.join(licenses_root, license)
228 dst_path = os.path.join(bucket_root, license)
229 CopyPath(src_path, dst_path)
230
231
232def CheckCIPDPackageExists(package_name, tag):
233 '''Check to see if the current package/tag combo has been published'''
234 command = [
235 'cipd',
236 'search',
237 package_name,
238 '-tag',
239 tag,
240 ]
241 stdout = subprocess.check_output(command)
242 # TODO ricardoamador: remove this check when python 2 is deprecated.
243 stdout = stdout if isinstance(stdout, str) else stdout.decode('UTF-8')
244 match = re.search(r'No matching instances\.', stdout)
245 if match:
246 return False
247 else:
248 return True
249
250
252 # Retry up to three times. We've seen CIPD fail on verification in some
253 # instances. Normally verification takes slightly more than 1 minute when
254 # it succeeds.
255 num_tries = 3
256 for tries in range(num_tries):
257 try:
258 subprocess.check_call(command, cwd=_bucket_directory)
259 break
260 except subprocess.CalledProcessError:
261 print('Failed %s times' % tries + 1)
262 if tries == num_tries - 1:
263 raise
264
265
266def ProcessCIPDPackage(upload, engine_version):
267 if not upload or not IsLinux():
269 'cipd', 'pkg-build', '-pkg-def', 'fuchsia.cipd.yaml', '-out',
270 os.path.join(_bucket_directory, 'fuchsia.cipd')
271 ])
272 return
273
274 # Everything after this point will only run iff `upload==true` and
275 # `IsLinux() == true`
276 assert (upload)
277 assert (IsLinux())
278 if engine_version is None:
279 print('--upload requires --engine-version to be specified.')
280 return
281
282 tag = 'git_revision:%s' % engine_version
283 already_exists = CheckCIPDPackageExists('flutter/fuchsia', tag)
284 if already_exists:
285 print('CIPD package flutter/fuchsia tag %s already exists!' % tag)
286 return
287
289 'cipd',
290 'create',
291 '-pkg-def',
292 'fuchsia.cipd.yaml',
293 '-ref',
294 'latest',
295 '-tag',
296 tag,
297 ])
298
299
301 runtime_mode, arch, optimized, enable_lto, enable_legacy, asan, dart_version_git_info,
302 prebuilt_dart_sdk, build_targets
303):
304 unopt = "_unopt" if not optimized else ""
305 out_dir = 'fuchsia_%s%s_%s' % (runtime_mode, unopt, arch)
306 flags = [
307 '--fuchsia',
308 '--fuchsia-cpu',
309 arch,
310 '--runtime-mode',
311 runtime_mode,
312 ]
313
314 if not optimized:
315 flags.append('--unoptimized')
316 if not enable_lto:
317 flags.append('--no-lto')
318 if not enable_legacy:
319 flags.append('--no-fuchsia-legacy')
320 if asan:
321 flags.append('--asan')
322 if not dart_version_git_info:
323 flags.append('--no-dart-version-git-info')
324 if not prebuilt_dart_sdk:
325 flags.append('--no-prebuilt-dart-sdk')
326
327 RunGN(out_dir, flags)
328 BuildNinjaTargets(out_dir, build_targets)
329
330 return
331
332
333def main():
334 parser = argparse.ArgumentParser()
335
336 parser.add_argument(
337 '--cipd-dry-run',
338 default=False,
339 action='store_true',
340 help='If set, creates the CIPD package but does not upload it.'
341 )
342
343 parser.add_argument(
344 '--upload',
345 default=False,
346 action='store_true',
347 help='If set, uploads the CIPD package and tags it as the latest.'
348 )
349
350 parser.add_argument('--engine-version', required=False, help='Specifies the flutter engine SHA.')
351
352 parser.add_argument(
353 '--unoptimized',
354 action='store_true',
355 default=False,
356 help='If set, disables compiler optimization for the build.'
357 )
358
359 parser.add_argument(
360 '--runtime-mode', type=str, choices=['debug', 'profile', 'release', 'all'], default='all'
361 )
362
363 parser.add_argument('--archs', type=str, choices=['x64', 'arm64', 'all'], default='all')
364
365 parser.add_argument(
366 '--asan',
367 action='store_true',
368 default=False,
369 help='If set, enables address sanitization (including leak sanitization) for the build.'
370 )
371
372 parser.add_argument(
373 '--no-lto', action='store_true', default=False, help='If set, disables LTO for the build.'
374 )
375
376 parser.add_argument(
377 '--no-legacy',
378 action='store_true',
379 default=False,
380 help='If set, disables legacy code for the build.'
381 )
382
383 parser.add_argument(
384 '--skip-build',
385 action='store_true',
386 default=False,
387 help='If set, skips building and just creates packages.'
388 )
389
390 parser.add_argument(
391 '--targets',
392 default='',
393 help=('Comma-separated list; adds additional targets to build for '
394 'Fuchsia.')
395 )
396
397 parser.add_argument(
398 '--no-dart-version-git-info',
399 action='store_true',
400 default=False,
401 help='If set, turns off the Dart SDK git hash check.'
402 )
403
404 parser.add_argument(
405 '--no-prebuilt-dart-sdk',
406 action='store_true',
407 default=False,
408 help='If set, builds the Dart SDK locally instead of using the prebuilt Dart SDK.'
409 )
410
411 parser.add_argument(
412 '--copy-unoptimized-debug-artifacts',
413 action='store_true',
414 default=False,
415 help='If set, unoptimized debug artifacts will be copied into CIPD along '
416 'with optimized builds. This is a hack to allow infra to make '
417 'and copy two debug builds, one with ASAN and one without.'
418 )
419
420 # TODO(http://fxb/110639): Deprecate this in favor of multiple runtime parameters
421 parser.add_argument(
422 '--skip-remove-buckets',
423 action='store_true',
424 default=False,
425 help='This allows for multiple runtimes to exist in the default bucket directory. If '
426 'set, will skip over the removal of existing artifacts in the bucket directory '
427 '(which is the default behavior).'
428 )
429
430 args = parser.parse_args()
431 build_mode = args.runtime_mode
432 if (not args.skip_remove_buckets):
433 RemoveDirectoryIfExists(_bucket_directory)
434
435 archs = ['x64', 'arm64'] if args.archs == 'all' else [args.archs]
436 runtime_modes = ['debug', 'profile', 'release']
437 product_modes = [False, False, True]
438
439 optimized = not args.unoptimized
440 enable_lto = not args.no_lto
441 enable_legacy = not args.no_legacy
442
443 # Build buckets
444 for arch in archs:
445 for i in range(len(runtime_modes)):
446 runtime_mode = runtime_modes[i]
447 product = product_modes[i]
448 if build_mode == 'all' or runtime_mode == build_mode:
449 if not args.skip_build:
451 runtime_mode, arch, optimized, enable_lto, enable_legacy, args.asan,
452 not args.no_dart_version_git_info, not args.no_prebuilt_dart_sdk,
453 args.targets.split(",") if args.targets else ['flutter']
454 )
455 CopyBuildToBucket(runtime_mode, arch, optimized, product)
456
457 # This is a hack. The recipe for building and uploading Fuchsia to CIPD
458 # builds both a debug build (debug without ASAN) and unoptimized debug
459 # build (debug with ASAN). To copy both builds into CIPD, the recipe
460 # runs build_fuchsia_artifacts.py in optimized mode and tells
461 # build_fuchsia_artifacts.py to also copy_unoptimized_debug_artifacts.
462 #
463 # TODO(akbiggs): Consolidate Fuchsia's building and copying logic to
464 # avoid ugly hacks like this.
465 if args.copy_unoptimized_debug_artifacts and runtime_mode == 'debug' and optimized:
466 CopyBuildToBucket(runtime_mode, arch, not optimized, product)
467
468 # Set revision to HEAD if empty and remove upload. This is to support
469 # presubmit workflows.
470 should_upload = args.upload
471 engine_version = args.engine_version
472 if not engine_version:
473 engine_version = 'HEAD'
474 should_upload = False
475
476 # Create and optionally upload CIPD package
477 if args.cipd_dry_run or args.upload:
478 ProcessCIPDPackage(should_upload, engine_version)
479
480 return 0
481
482
483if __name__ == '__main__':
484 sys.exit(main())
SkPath CopyPath(const SkPath &a)
def CopyVulkanDepsToBucket(src, dst, arch)
def BuildNinjaTargets(variant_dir, targets)
def CheckCIPDPackageExists(package_name, tag)
def FindFileAndCopyTo(file_name, source, dest_parent, dst_name=None)
def CopyToBucketWithMode(source, destination, aot, product, runner_type, api_level)
def CopyToBucket(src, dst, product=False)
def ProcessCIPDPackage(upload, engine_version)
def CopyBuildToBucket(runtime_mode, arch, optimized, product)
def CopyZirconFFILibIfExists(source, destination)
def CopyFlutterTesterBinIfExists(source, destination)
def CopyFiles(source, destination)
def RunGN(variant_dir, flags)
def BuildTarget(runtime_mode, arch, optimized, enable_lto, enable_legacy, asan, dart_version_git_info, prebuilt_dart_sdk, build_targets)
def CopyGenSnapshotIfExists(source, destination)
def CreateFarPackage(pm_bin, package_dir, signing_key, dst_dir, api_level)
Definition: gen_package.py:40
Definition: main.py:1
def print(*args, **kwargs)
Definition: run_tests.py:49
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741