blob: 1f2a92535101fa751d1320c37afbaf706bc9b202 [file] [log] [blame]
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +00001#!/usr/bin/env python3
2
3# Copyright 2021 Google, Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at:
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16""" Build BT targets on the host system.
17
18For building, you will first have to stage a platform directory that has the
19following structure:
20|-common-mk
21|-bt
22|-external
23|-|-rust
24|-|-|-vendor
25
26The simplest way to do this is to check out platform2 to another directory (that
27is not a subdir of this bt directory), symlink bt there and symlink the rust
28vendor repository as well.
29"""
30import argparse
31import multiprocessing
32import os
Andre Bragaaa11e7d2022-08-10 21:46:44 +000033import platform
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000034import shutil
35import six
36import subprocess
37import sys
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -080038import tarfile
Chris Mantone7ad6332021-09-30 22:55:39 -070039import time
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000040
41# Use flags required by common-mk (find -type f | grep -nE 'use[.]' {})
42COMMON_MK_USES = [
43 'asan',
44 'coverage',
45 'cros_host',
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080046 'cros_debug',
JohnLai762f8202023-09-18 18:53:16 +080047 'floss_rootcanal',
48 'function_elimination_experiment',
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000049 'fuzzer',
50 'fuzzer',
JohnLai762f8202023-09-18 18:53:16 +080051 'lto_experiment',
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000052 'msan',
53 'profiling',
JohnLai762f8202023-09-18 18:53:16 +080054 'proto_force_optimize_speed',
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000055 'tcmalloc',
56 'test',
57 'ubsan',
58]
59
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080060# Use a specific commit version for common-mk to avoid build surprises.
JohnLai762f8202023-09-18 18:53:16 +080061COMMON_MK_COMMIT = "d014d561eaf5ece08166edd98b10c145ef81312d"
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080062
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000063# Default use flags.
64USE_DEFAULTS = {
65 'android': False,
66 'bt_nonstandard_codecs': False,
67 'test': False,
68}
69
70VALID_TARGETS = [
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000071 'all', # All targets except test and clean
Abhishek Pandit-Subedia04c74c2024-08-01 09:24:34 -070072 'bloat', # Check bloat of crates
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080073 'clean', # Clean up output directory
74 'docs', # Build Rust docs
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080075 'hosttools', # Build the host tools (i.e. packetgen)
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080076 'main', # Build the main C++ codebase
77 'prepare', # Prepare the output directory (gn gen + rust setup)
78 'rust', # Build only the rust components + copy artifacts to output dir
79 'test', # Run the unit tests
Hsin-chen Chuangbcd7b632024-06-07 01:07:08 +080080 'clippy', # Run cargo clippy
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080081 'utils', # Build Floss utils
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000082]
83
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000084# TODO(b/190750167) - Host tests are disabled until we are full bazel build
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000085HOST_TESTS = [
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000086 # 'bluetooth_test_common',
87 # 'bluetoothtbd_test',
88 # 'net_test_avrcp',
89 # 'net_test_btcore',
90 # 'net_test_types',
91 # 'net_test_btm_iso',
92 # 'net_test_btpackets',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000093]
94
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080095# Map of git repos to bootstrap and what commit to check them out at. None
96# values will just checkout to HEAD.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070097BOOTSTRAP_GIT_REPOS = {
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080098 'platform2': ('https://chromium.googlesource.com/chromiumos/platform2', COMMON_MK_COMMIT),
99 'rust_crates': ('https://chromium.googlesource.com/chromiumos/third_party/rust_crates', None),
100 'proto_logging': ('https://android.googlesource.com/platform/frameworks/proto_logging', None),
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700101}
102
103# List of packages required for linux build
104REQUIRED_APT_PACKAGES = [
105 'bison',
106 'build-essential',
107 'curl',
108 'debmake',
109 'flatbuffers-compiler',
110 'flex',
111 'g++-multilib',
112 'gcc-multilib',
113 'generate-ninja',
114 'gnupg',
115 'gperf',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000116 'libabsl-dev',
Martin Brabhamba22adf2022-02-04 19:51:21 +0000117 'libc++abi-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700118 'libc++-dev',
119 'libdbus-1-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000120 'libdouble-conversion-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700121 'libevent-dev',
122 'libevent-dev',
123 'libflatbuffers-dev',
David Duartef11fe882023-02-22 21:27:34 +0000124 'libfmt-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700125 'libgl1-mesa-dev',
126 'libglib2.0-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000127 'libgtest-dev',
128 'libgmock-dev',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000129 'liblc3-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700130 'liblz4-tool',
131 'libncurses5',
132 'libnss3-dev',
Henri Chatainge08cbed2023-10-20 09:06:51 -0700133 'libfmt-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700134 'libprotobuf-dev',
135 'libre2-9',
Martin Brabham996f1502022-02-14 17:39:23 +0000136 'libre2-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700137 'libssl-dev',
138 'libtinyxml2-dev',
139 'libx11-dev',
140 'libxml2-utils',
141 'ninja-build',
142 'openssl',
143 'protobuf-compiler',
144 'unzip',
145 'x11proto-core-dev',
146 'xsltproc',
147 'zip',
148 'zlib1g-dev',
149]
150
151# List of cargo packages required for linux build
Abhishek Pandit-Subedia04c74c2024-08-01 09:24:34 -0700152REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd', 'pdl-compiler', 'grpcio-compiler', 'cargo-bloat']
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700153
154APT_PKG_LIST = ['apt', '-qq', 'list']
155CARGO_PKG_LIST = ['cargo', 'install', '--list']
156
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000157
158class UseFlags():
159
160 def __init__(self, use_flags):
161 """ Construct the use flags.
162
163 Args:
164 use_flags: List of use flags parsed from the command.
165 """
166 self.flags = {}
167
168 # Import use flags required by common-mk
169 for use in COMMON_MK_USES:
170 self.set_flag(use, False)
171
172 # Set our defaults
173 for use, value in USE_DEFAULTS.items():
174 self.set_flag(use, value)
175
176 # Set use flags - value is set to True unless the use starts with -
177 # All given use flags always override the defaults
178 for use in use_flags:
179 value = not use.startswith('-')
180 self.set_flag(use, value)
181
182 def set_flag(self, key, value=True):
183 setattr(self, key, value)
184 self.flags[key] = value
185
186
187class HostBuild():
188
189 def __init__(self, args):
190 """ Construct the builder.
191
192 Args:
193 args: Parsed arguments from ArgumentParser
194 """
195 self.args = args
196
197 # Set jobs to number of cpus unless explicitly set
198 self.jobs = self.args.jobs
199 if not self.jobs:
200 self.jobs = multiprocessing.cpu_count()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700201 sys.stderr.write("Number of jobs = {}\n".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000202
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700203 # Normalize bootstrap dir and make sure it exists
204 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
205 os.makedirs(self.bootstrap_dir, exist_ok=True)
206
207 # Output and platform directories are based on bootstrap
208 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
209 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Michael Sun4940f2b2022-09-15 16:08:24 -0700210 self.bt_dir = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000211 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000212 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800213 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000214
Michael Sun4940f2b2022-09-15 16:08:24 -0700215 assert os.path.samefile(self.bt_dir,
216 os.path.dirname(__file__)), "Please rerun bootstrap for the current project!"
217
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000218 # If default target isn't set, build everything
219 self.target = 'all'
220 if hasattr(self.args, 'target') and self.args.target:
221 self.target = self.args.target
222
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000223 target_use = self.args.use if self.args.use else []
224
225 # Unless set, always build test code
226 if not self.args.notest:
227 target_use.append('test')
228
229 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000230
231 # Validate platform directory
232 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
233 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
234
235 # Make sure output directory exists (or create it)
236 os.makedirs(self.output_dir, exist_ok=True)
237
238 # Set some default attributes
239 self.libbase_ver = None
240
241 self.configure_environ()
242
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000243 def _generate_rustflags(self):
244 """ Rustflags to include for the build.
245 """
246 rust_flags = [
247 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700248 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000249 '-C',
250 'link-arg=-Wl,--allow-multiple-definition',
Sonny Sasaka87bacb62022-04-29 10:34:29 -0700251 # exclude uninteresting warnings
252 '-A improper_ctypes_definitions -A improper_ctypes -A unknown_lints',
Abhishek Pandit-Subedia04c74c2024-08-01 09:24:34 -0700253 '-Cstrip=debuginfo',
254 '-Copt-level=z',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000255 ]
256
257 return ' '.join(rust_flags)
258
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000259 def configure_environ(self):
260 """ Configure environment variables for GN and Cargo.
261 """
262 self.env = os.environ.copy()
263
264 # Make sure cargo home dir exists and has a bin directory
265 cargo_home = os.path.join(self.output_dir, 'cargo_home')
266 os.makedirs(cargo_home, exist_ok=True)
267 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
268
269 # Configure Rust env variables
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700270 self.custom_env = {}
271 self.custom_env['CARGO_TARGET_DIR'] = self.output_dir
272 self.custom_env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
273 self.custom_env['RUSTFLAGS'] = self._generate_rustflags()
274 self.custom_env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
275 self.custom_env['CROS_SYSTEM_API_ROOT'] = os.path.join(self.platform_dir, 'system_api')
276 self.custom_env['CXX_OUTDIR'] = self._gn_default_output()
Ludovic Barman6cd82362023-11-08 09:12:07 +0000277
278 # On ChromeOS, this is /usr/bin/grpc_rust_plugin
279 # In the container, this is /root/.cargo/bin/grpc_rust_plugin
280 self.custom_env['GRPC_RUST_PLUGIN_PATH'] = shutil.which('grpc_rust_plugin')
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700281 self.env.update(self.custom_env)
282
283 def print_env(self):
284 """ Print the custom environment variables that are used in build.
285
286 Useful so that external tools can mimic the environment to be the same
287 as build.py, e.g. rust-analyzer.
288 """
289 for k, v in self.custom_env.items():
290 print("export {}='{}'".format(k, v))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000291
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000292 def run_command(self, target, args, cwd=None, env=None):
293 """ Run command and stream the output.
294 """
295 # Set some defaults
296 if not cwd:
297 cwd = self.platform_dir
298 if not env:
299 env = self.env
300
Abhishek Pandit-Subedi2b095072024-08-01 09:34:03 -0700301 for k, v in env.items():
302 if env[k] is None:
303 env[k] = ""
304
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000305 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
306 with open(log_file, 'wb') as lf:
307 rc = 0
308 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
309 while True:
310 line = process.stdout.readline()
311 print(line.decode('utf-8'), end="")
312 lf.write(line)
313 if not line:
314 rc = process.poll()
315 if rc is not None:
316 break
317
318 time.sleep(0.1)
319
320 if rc != 0:
321 raise Exception("Return code is {}".format(rc))
322
323 def _get_basever(self):
324 if self.libbase_ver:
325 return self.libbase_ver
326
327 self.libbase_ver = os.environ.get('BASE_VER', '')
328 if not self.libbase_ver:
329 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
330 try:
331 with open(base_file, 'r') as f:
332 self.libbase_ver = f.read().strip('\n')
333 except:
334 self.libbase_ver = 'NOT-INSTALLED'
335
336 return self.libbase_ver
337
338 def _gn_default_output(self):
339 return os.path.join(self.output_dir, 'out/Default')
340
341 def _gn_configure(self):
342 """ Configure all required parameters for platform2.
343
344 Mostly copied from //common-mk/platform2.py
345 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700346 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000347
348 def to_gn_string(s):
349 return '"%s"' % s.replace('"', '\\"')
350
351 def to_gn_list(strs):
352 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
353
354 def to_gn_args_args(gn_args):
355 for k, v in gn_args.items():
356 if isinstance(v, bool):
357 v = str(v).lower()
358 elif isinstance(v, list):
359 v = to_gn_list(v)
360 elif isinstance(v, six.string_types):
361 v = to_gn_string(v)
362 else:
363 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
364 yield '%s=%s' % (k.replace('-', '_'), v)
365
366 gn_args = {
367 'platform_subdir': 'bt',
368 'cc': 'clang' if clang else 'gcc',
369 'cxx': 'clang++' if clang else 'g++',
370 'ar': 'llvm-ar' if clang else 'ar',
371 'pkg-config': 'pkg-config',
372 'clang_cc': clang,
373 'clang_cxx': clang,
374 'OS': 'linux',
375 'sysroot': self.sysroot,
376 'libdir': os.path.join(self.sysroot, self.libdir),
377 'build_root': self.output_dir,
378 'platform2_root': self.platform_dir,
379 'libbase_ver': self._get_basever(),
380 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
381 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800382 'external_cxxflags': ["-DNDEBUG"],
Hsin-chen Chuange31b4162024-08-23 11:45:28 +0800383 'enable_werror': True,
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000384 }
385
386 if clang:
387 # Make sure to mark the clang use flag as true
388 self.use.set_flag('clang', True)
389 gn_args['external_cxxflags'] += ['-I/usr/include/']
390
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000391 gn_args_args = list(to_gn_args_args(gn_args))
392 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
393 gn_args_args += ['use={%s}' % (' '.join(use_args))]
394
395 gn_args = [
396 'gn',
397 'gen',
398 ]
399
400 if self.args.verbose:
401 gn_args.append('-v')
402
403 gn_args += [
404 '--root=%s' % self.platform_dir,
405 '--args=%s' % ' '.join(gn_args_args),
406 self._gn_default_output(),
407 ]
408
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700409 if 'PKG_CONFIG_PATH' in self.env:
410 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000411
412 self.run_command('configure', gn_args)
413
414 def _gn_build(self, target):
415 """ Generate the ninja command for the target and run it.
416 """
417 args = ['%s:%s' % ('bt', target)]
418 ninja_args = ['ninja', '-C', self._gn_default_output()]
419 if self.jobs:
420 ninja_args += ['-j', str(self.jobs)]
421 ninja_args += args
422
423 if self.args.verbose:
424 ninja_args.append('-v')
425
426 self.run_command('build', ninja_args)
427
428 def _rust_configure(self):
429 """ Generate config file at cargo_home so we use vendored crates.
430 """
431 template = """
432 [source.systembt]
433 directory = "{}/external/rust/vendor"
434
435 [source.crates-io]
436 replace-with = "systembt"
437 local-registry = "/nonexistent"
438 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700439
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700440 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700441 contents = template.format(self.platform_dir)
442 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
443 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000444
445 def _rust_build(self):
446 """ Run `cargo build` from platform2/bt directory.
447 """
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700448 cmd = ['cargo', 'build']
449 if not self.args.rust_debug:
450 cmd.append('--release')
451
452 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000453
454 def _target_prepare(self):
455 """ Target to prepare the output directory for building.
456
457 This runs gn gen to generate all rquired files and set up the Rust
458 config properly. This will be run
459 """
460 self._gn_configure()
461 self._rust_configure()
462
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800463 def _target_hosttools(self):
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000464 """ Build the tools target in an already prepared environment.
465 """
466 self._gn_build('tools')
467
468 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800469 shutil.copy(os.path.join(self._gn_default_output(), 'bluetooth_packetgen'),
470 os.path.join(self.env['CARGO_HOME'], 'bin'))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000471
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800472 def _target_docs(self):
473 """Build the Rust docs."""
474 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
475
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000476 def _target_rust(self):
477 """ Build rust artifacts in an already prepared environment.
478 """
479 self._rust_build()
480
481 def _target_main(self):
482 """ Build the main GN artifacts in an already prepared environment.
483 """
484 self._gn_build('all')
485
486 def _target_test(self):
487 """ Runs the host tests.
488 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000489 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800490 rust_test_cmd = ['cargo', 'test']
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700491 if not self.args.rust_debug:
492 rust_test_cmd.append('--release')
493
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800494 if self.args.test_name:
Abhishek Pandit-Subedib04e6a92022-09-08 19:19:40 -0700495 rust_test_cmd = rust_test_cmd + [self.args.test_name, "--", "--test-threads=1", "--nocapture"]
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800496
497 self.run_command('test', rust_test_cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000498
499 # Host tests second based on host test list
500 for t in HOST_TESTS:
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800501 self.run_command('test', [os.path.join(self.output_dir, 'out/Default', t)],
502 cwd=os.path.join(self.output_dir),
503 env=self.env)
504
Hsin-chen Chuangbcd7b632024-06-07 01:07:08 +0800505 def _target_clippy(self):
506 """ Runs cargo clippy, a collection of lints to catch common mistakes.
507 """
508 cmd = ['cargo', 'clippy']
509 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
510
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800511 def _target_utils(self):
512 """ Builds the utility applications.
513 """
514 rust_targets = ['hcidoc']
515
516 # Build targets
517 for target in rust_targets:
518 self.run_command('utils', ['cargo', 'build', '-p', target],
519 cwd=os.path.join(self.platform_dir, 'bt'),
520 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000521
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800522 def _target_install(self):
523 """ Installs files required to run Floss to install directory.
524 """
525 # First make the install directory
526 prefix = self.install_dir
527 os.makedirs(prefix, exist_ok=True)
528
529 # Next save the cwd and change to install directory
530 last_cwd = os.getcwd()
531 os.chdir(prefix)
532
533 bindir = os.path.join(self.output_dir, 'debug')
534 srcdir = os.path.dirname(__file__)
535
536 install_map = [
537 {
538 'src': os.path.join(bindir, 'btadapterd'),
539 'dst': 'usr/libexec/bluetooth/btadapterd',
540 'strip': True
541 },
542 {
543 'src': os.path.join(bindir, 'btmanagerd'),
544 'dst': 'usr/libexec/bluetooth/btmanagerd',
545 'strip': True
546 },
547 {
548 'src': os.path.join(bindir, 'btclient'),
549 'dst': 'usr/local/bin/btclient',
550 'strip': True
551 },
552 ]
553
554 for v in install_map:
555 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
556 dst = os.path.join(prefix, partial_dst)
557
558 # Create dst directory first and copy file there
559 os.makedirs(os.path.dirname(dst), exist_ok=True)
560 print('Installing {}'.format(dst))
561 shutil.copy(src, dst)
562
563 # Binary should be marked for strip and no-strip option shouldn't be
564 # set. No-strip is useful while debugging.
565 if strip and not self.args.no_strip:
566 self.run_command('install', ['llvm-strip', dst])
567
568 # Put all files into a tar.gz for easier installation
569 tar_location = os.path.join(prefix, 'floss.tar.gz')
570 with tarfile.open(tar_location, 'w:gz') as tar:
571 for v in install_map:
572 tar.add(v['dst'])
573
574 print('Tarball created at {}'.format(tar_location))
575
Abhishek Pandit-Subedia04c74c2024-08-01 09:24:34 -0700576 def _target_bloat(self):
577 """Run cargo bloat on workspace.
578 """
579 crate_paths = [
580 os.path.join(self.platform_dir, 'bt', 'system', 'gd', 'rust', 'linux', 'mgmt'),
581 os.path.join(self.platform_dir, 'bt', 'system', 'gd', 'rust', 'linux', 'service'),
582 os.path.join(self.platform_dir, 'bt', 'system', 'gd', 'rust', 'linux', 'client')
583 ]
584 for crate in crate_paths:
585 self.run_command('bloat', ['cargo', 'bloat', '--release', '--crates', '--wide'], cwd=crate, env=self.env)
586
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000587 def _target_clean(self):
588 """ Delete the output directory entirely.
589 """
590 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800591
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700592 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700593 cargo_lock_files = [
594 os.path.join(self.platform_dir, 'bt', 'Cargo.lock'),
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700595 ]
596 for lock_file in cargo_lock_files:
597 try:
598 os.remove(lock_file)
599 print('Removed {}'.format(lock_file))
600 except FileNotFoundError:
601 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000602
603 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800604 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000605 """
606 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800607 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000608 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700609 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000610
611 def build(self):
612 """ Builds according to self.target
613 """
614 print('Building target ', self.target)
615
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800616 # Validate that the target is valid
617 if self.target not in VALID_TARGETS:
howardchung63187b62022-08-16 17:06:17 +0800618 print('Target {} is not valid. Must be in {}'.format(self.target, VALID_TARGETS))
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800619 return
620
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000621 if self.target == 'prepare':
622 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800623 elif self.target == 'hosttools':
624 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000625 elif self.target == 'rust':
626 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800627 elif self.target == 'docs':
628 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000629 elif self.target == 'main':
630 self._target_main()
631 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000632 self._target_test()
Hsin-chen Chuangbcd7b632024-06-07 01:07:08 +0800633 elif self.target == 'clippy':
634 self._target_clippy()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000635 elif self.target == 'clean':
636 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800637 elif self.target == 'install':
638 self._target_install()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800639 elif self.target == 'utils':
640 self._target_utils()
Abhishek Pandit-Subedia04c74c2024-08-01 09:24:34 -0700641 elif self.target == 'bloat':
642 self._target_bloat()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000643 elif self.target == 'all':
644 self._target_all()
645
646
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700647# Default to 10 min timeouts on all git operations.
648GIT_TIMEOUT_SEC = 600
649
650
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700651class Bootstrap():
652
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700653 def __init__(self, base_dir, bt_dir, partial_staging, clone_timeout):
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700654 """ Construct bootstrapper.
655
656 Args:
657 base_dir: Where to stage everything.
658 bt_dir: Where bluetooth source is kept (will be symlinked)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800659 partial_staging: Whether to do a partial clone for staging.
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700660 clone_timeout: Timeout for clone operations.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700661 """
662 self.base_dir = os.path.abspath(base_dir)
663 self.bt_dir = os.path.abspath(bt_dir)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800664 self.partial_staging = partial_staging
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700665 self.clone_timeout = clone_timeout
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700666
667 # Create base directory if it doesn't already exist
668 os.makedirs(self.base_dir, exist_ok=True)
669
670 if not os.path.isdir(self.bt_dir):
671 raise Exception('{} is not a valid directory'.format(self.bt_dir))
672
673 self.git_dir = os.path.join(self.base_dir, 'repos')
674 self.staging_dir = os.path.join(self.base_dir, 'staging')
675 self.output_dir = os.path.join(self.base_dir, 'output')
676 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
677
678 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
679
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700680 def _run_with_timeout(self, cmd, cwd, timeout=None):
681 """Runs a command using subprocess.check_output. """
682 print('Running command: {} [at cwd={}]'.format(' '.join(cmd), cwd))
683 with subprocess.Popen(cmd, cwd=cwd) as proc:
684 try:
685 outs, errs = proc.communicate(timeout=timeout)
686 except subprocess.TimeoutExpired:
687 proc.kill()
688 outs, errs = proc.communicate()
689 print('Timeout on {}'.format(' '.join(cmd)), file=sys.stderr)
690 raise
691
692 if proc.returncode != 0:
693 raise Exception('Cmd {} had return code {}'.format(' '.join(cmd), proc.returncode))
694
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700695 def _update_platform2(self):
696 """Updates repositories used for build."""
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800697 for project in BOOTSTRAP_GIT_REPOS.keys():
698 cwd = os.path.join(self.git_dir, project)
699 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
700
701 # Update to required commit when necessary or pull the latest code.
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700702 if commit is not None:
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800703 head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).strip()
704 if head != commit:
705 subprocess.check_call(['git', 'fetch'], cwd=cwd)
706 subprocess.check_call(['git', 'checkout', commit], cwd=cwd)
707 else:
708 subprocess.check_call(['git', 'pull'], cwd=cwd)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700709
710 def _setup_platform2(self):
711 """ Set up platform2.
712
713 This will check out all the git repos and symlink everything correctly.
714 """
715
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800716 # Create all directories we will need to use
717 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
718 os.makedirs(dirpath, exist_ok=True)
719
720 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700721 if os.path.isfile(self.dir_setup_complete):
722 print('{} already set-up. Updating instead.'.format(self.base_dir))
723 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800724 else:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800725 clone_options = []
726 # When doing a partial staging, we use a treeless clone which allows
727 # us to access all commits but downloads things on demand. This
728 # helps speed up the initial git clone during builds but isn't good
729 # for long-term development.
730 if self.partial_staging:
731 clone_options = ['--filter=tree:0']
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800732 # Check out all repos in git directory
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800733 for project in BOOTSTRAP_GIT_REPOS.keys():
734 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700735
736 # Try repo clone several times.
737 # Currently, we set timeout on this operation after
738 # |self.clone_timeout|. If it fails, try to recover.
739 tries = 2
740 for x in range(tries):
741 try:
742 self._run_with_timeout(['git', 'clone', repo, project] + clone_options,
743 cwd=self.git_dir,
744 timeout=self.clone_timeout)
745 except subprocess.TimeoutExpired:
746 shutil.rmtree(os.path.join(self.git_dir, project))
747 if x == tries - 1:
748 raise
749 # All other exceptions should raise
750 except:
751 raise
752 # No exceptions/problems should not retry.
753 else:
754 break
755
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800756 # Pin to commit.
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700757 if commit is not None:
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800758 subprocess.check_call(['git', 'checkout', commit], cwd=os.path.join(self.git_dir, project))
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700759
760 # Symlink things
761 symlinks = [
762 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
Sonny Sasakaae9f6522022-03-28 10:31:34 -0700763 (os.path.join(self.git_dir, 'platform2', 'system_api'), os.path.join(self.staging_dir, 'system_api')),
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700764 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
765 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
766 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
767 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
768 ]
769
770 # Create symlinks
771 for pairs in symlinks:
772 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000773 try:
774 os.unlink(dst)
775 except Exception as e:
776 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700777 os.symlink(src, dst)
778
779 # Write to setup complete file so we don't repeat this step
780 with open(self.dir_setup_complete, 'w') as f:
781 f.write('Setup complete.')
782
783 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
784 """ Pretty print an install command.
785
786 Args:
787 install_cmd: Prefixed install command.
788 packages: Enumerate packages and append them to install command.
789 line_limit: Number of characters per line.
790
791 Return:
792 Array of lines to join and print.
793 """
794 install = [install_cmd]
795 line = ' '
796 # Remainder needed = space + len(pkg) + space + \
797 # Assuming 80 character lines, that's 80 - 3 = 77
798 line_limit = line_limit - 3
799 for pkg in packages:
800 if len(line) + len(pkg) < line_limit:
801 line = '{}{} '.format(line, pkg)
802 else:
803 install.append(line)
804 line = ' {} '.format(pkg)
805
806 if len(line) > 0:
807 install.append(line)
808
809 return install
810
811 def _check_package_installed(self, package, cmd, predicate):
812 """Check that the given package is installed.
813
814 Args:
815 package: Check that this package is installed.
816 cmd: Command prefix to check if installed (package appended to end)
817 predicate: Function/lambda to check if package is installed based
818 on output. Takes string output and returns boolean.
819
820 Return:
821 True if package is installed.
822 """
823 try:
824 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
825 is_installed = predicate(output.decode('utf-8'))
826 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
827
828 return is_installed
829 except Exception as e:
830 print(e)
831 return False
832
833 def _get_command_output(self, cmd):
834 """Runs the command and gets the output.
835
836 Args:
837 cmd: Command to run.
838
839 Return:
840 Tuple (Success, Output). Success represents if the command ran ok.
841 """
842 try:
843 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
844 return (True, output.decode('utf-8').split('\n'))
845 except Exception as e:
846 print(e)
847 return (False, "")
848
849 def _print_missing_packages(self):
850 """Print any missing packages found via apt.
851
852 This will find any missing packages necessary for build using apt and
853 print it out as an apt-get install printf.
854 """
855 print('Checking for any missing packages...')
856
857 (success, output) = self._get_command_output(APT_PKG_LIST)
858 if not success:
859 raise Exception("Could not query apt for packages.")
860
861 packages_installed = {}
862 for line in output:
863 if 'installed' in line:
864 split = line.split('/', 2)
865 packages_installed[split[0]] = True
866
867 need_packages = []
868 for pkg in REQUIRED_APT_PACKAGES:
869 if pkg not in packages_installed:
870 need_packages.append(pkg)
871
872 # No packages need to be installed
873 if len(need_packages) == 0:
874 print('+ All required packages are installed')
875 return
876
877 install = self._pretty_print_install('sudo apt-get install', need_packages)
878
879 # Print all lines so they can be run in cmdline
880 print('Missing system packages. Run the following command: ')
881 print(' \\\n'.join(install))
882
883 def _print_missing_rust_packages(self):
884 """Print any missing packages found via cargo.
885
886 This will find any missing packages necessary for build using cargo and
887 print it out as a cargo-install printf.
888 """
889 print('Checking for any missing cargo packages...')
890
891 (success, output) = self._get_command_output(CARGO_PKG_LIST)
892 if not success:
893 raise Exception("Could not query cargo for packages.")
894
895 packages_installed = {}
896 for line in output:
897 # Cargo installed packages have this format
898 # <crate name> <version>:
899 # <binary name>
900 # We only care about the crates themselves
901 if ':' not in line:
902 continue
903
904 split = line.split(' ', 2)
905 packages_installed[split[0]] = True
906
907 need_packages = []
908 for pkg in REQUIRED_CARGO_PACKAGES:
909 if pkg not in packages_installed:
910 need_packages.append(pkg)
911
912 # No packages to be installed
913 if len(need_packages) == 0:
914 print('+ All required cargo packages are installed')
915 return
916
917 install = self._pretty_print_install('cargo install', need_packages)
918 print('Missing cargo packages. Run the following command: ')
919 print(' \\\n'.join(install))
920
921 def bootstrap(self):
922 """ Bootstrap the Linux build."""
923 self._setup_platform2()
924 self._print_missing_packages()
925 self._print_missing_rust_packages()
926
927
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000928if __name__ == '__main__':
929 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800930 parser.add_argument('--bootstrap-dir',
931 help='Directory to run bootstrap on (or was previously run on).',
932 default="~/.floss")
933 parser.add_argument('--run-bootstrap',
934 help='Run bootstrap code to verify build env is ok to build.',
935 default=False,
936 action='store_true')
937 parser.add_argument('--print-env',
938 help='Print environment variables used for build.',
939 default=False,
940 action='store_true')
Andre Braga17ff7bc2022-06-24 22:43:18 +0000941 parser.add_argument('--no-clang', help='Don\'t use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800942 parser.add_argument('--no-strip',
943 help='Skip stripping binaries during install.',
944 default=False,
945 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000946 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800947 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
948 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000949 parser.add_argument('--target', help='Run specific build target')
950 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700951 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000952 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800953 parser.add_argument('--no-vendored-rust',
954 help='Do not use vendored rust crates',
955 default=False,
956 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000957 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700958 parser.add_argument('--rust-debug', help='Build Rust code as debug.', default=False, action='store_true')
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800959 parser.add_argument(
960 '--partial-staging',
961 help='Bootstrap git repositories with partial clones. Use to speed up initial git clone for automated builds.',
962 default=False,
963 action='store_true')
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700964 parser.add_argument('--clone-timeout',
965 help='Timeout for repository cloning during bootstrap.',
966 default=GIT_TIMEOUT_SEC,
967 type=int)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000968 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700969
970 # Make sure we get absolute path + expanded path for bootstrap directory
971 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
972
Andre Bragaaa11e7d2022-08-10 21:46:44 +0000973 # Possible values for machine() come from 'uname -m'
974 # Since this script only runs on Linux, x86_64 machines must have this value
975 if platform.machine() != 'x86_64':
976 raise Exception("Only x86_64 machines are currently supported by this build script.")
977
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700978 if args.run_bootstrap:
Abhishek Pandit-Subedic1342e82024-04-22 17:27:57 -0700979 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__), args.partial_staging, args.clone_timeout)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700980 bootstrap.bootstrap()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700981 elif args.print_env:
982 build = HostBuild(args)
983 build.print_env()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700984 else:
985 build = HostBuild(args)
986 build.build()