blob: 388ec46a1356b611b3934c7ead8517d01360f4b4 [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-Subedi94764142022-02-07 17:18:00 -080072 'clean', # Clean up output directory
73 'docs', # Build Rust docs
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080074 'hosttools', # Build the host tools (i.e. packetgen)
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080075 'main', # Build the main C++ codebase
76 'prepare', # Prepare the output directory (gn gen + rust setup)
77 'rust', # Build only the rust components + copy artifacts to output dir
78 'test', # Run the unit tests
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080079 'utils', # Build Floss utils
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000080]
81
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000082# TODO(b/190750167) - Host tests are disabled until we are full bazel build
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000083HOST_TESTS = [
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000084 # 'bluetooth_test_common',
85 # 'bluetoothtbd_test',
86 # 'net_test_avrcp',
87 # 'net_test_btcore',
88 # 'net_test_types',
89 # 'net_test_btm_iso',
90 # 'net_test_btpackets',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000091]
92
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080093# Map of git repos to bootstrap and what commit to check them out at. None
94# values will just checkout to HEAD.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070095BOOTSTRAP_GIT_REPOS = {
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080096 'platform2': ('https://chromium.googlesource.com/chromiumos/platform2', COMMON_MK_COMMIT),
97 'rust_crates': ('https://chromium.googlesource.com/chromiumos/third_party/rust_crates', None),
98 'proto_logging': ('https://android.googlesource.com/platform/frameworks/proto_logging', None),
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070099}
100
101# List of packages required for linux build
102REQUIRED_APT_PACKAGES = [
103 'bison',
104 'build-essential',
105 'curl',
106 'debmake',
107 'flatbuffers-compiler',
108 'flex',
109 'g++-multilib',
110 'gcc-multilib',
111 'generate-ninja',
112 'gnupg',
113 'gperf',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000114 'libabsl-dev',
Martin Brabhamba22adf2022-02-04 19:51:21 +0000115 'libc++abi-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700116 'libc++-dev',
117 'libdbus-1-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000118 'libdouble-conversion-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700119 'libevent-dev',
120 'libevent-dev',
121 'libflatbuffers-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700122 'libgl1-mesa-dev',
123 'libglib2.0-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000124 'libgtest-dev',
125 'libgmock-dev',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000126 'liblc3-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700127 'liblz4-tool',
128 'libncurses5',
129 'libnss3-dev',
130 'libprotobuf-dev',
131 'libre2-9',
Martin Brabham996f1502022-02-14 17:39:23 +0000132 'libre2-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700133 'libssl-dev',
134 'libtinyxml2-dev',
135 'libx11-dev',
136 'libxml2-utils',
137 'ninja-build',
138 'openssl',
139 'protobuf-compiler',
140 'unzip',
141 'x11proto-core-dev',
142 'xsltproc',
143 'zip',
144 'zlib1g-dev',
145]
146
147# List of cargo packages required for linux build
Abhishek Pandit-Subedif6064252023-06-02 08:31:17 +0000148REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd', 'pdl-compiler']
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700149
150APT_PKG_LIST = ['apt', '-qq', 'list']
151CARGO_PKG_LIST = ['cargo', 'install', '--list']
152
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000153
154class UseFlags():
155
156 def __init__(self, use_flags):
157 """ Construct the use flags.
158
159 Args:
160 use_flags: List of use flags parsed from the command.
161 """
162 self.flags = {}
163
164 # Import use flags required by common-mk
165 for use in COMMON_MK_USES:
166 self.set_flag(use, False)
167
168 # Set our defaults
169 for use, value in USE_DEFAULTS.items():
170 self.set_flag(use, value)
171
172 # Set use flags - value is set to True unless the use starts with -
173 # All given use flags always override the defaults
174 for use in use_flags:
175 value = not use.startswith('-')
176 self.set_flag(use, value)
177
178 def set_flag(self, key, value=True):
179 setattr(self, key, value)
180 self.flags[key] = value
181
182
183class HostBuild():
184
185 def __init__(self, args):
186 """ Construct the builder.
187
188 Args:
189 args: Parsed arguments from ArgumentParser
190 """
191 self.args = args
192
193 # Set jobs to number of cpus unless explicitly set
194 self.jobs = self.args.jobs
195 if not self.jobs:
196 self.jobs = multiprocessing.cpu_count()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700197 sys.stderr.write("Number of jobs = {}\n".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000198
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700199 # Normalize bootstrap dir and make sure it exists
200 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
201 os.makedirs(self.bootstrap_dir, exist_ok=True)
202
203 # Output and platform directories are based on bootstrap
204 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
205 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Michael Sun4940f2b2022-09-15 16:08:24 -0700206 self.bt_dir = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000207 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000208 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800209 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000210
Michael Sun4940f2b2022-09-15 16:08:24 -0700211 assert os.path.samefile(self.bt_dir,
212 os.path.dirname(__file__)), "Please rerun bootstrap for the current project!"
213
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000214 # If default target isn't set, build everything
215 self.target = 'all'
216 if hasattr(self.args, 'target') and self.args.target:
217 self.target = self.args.target
218
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000219 target_use = self.args.use if self.args.use else []
220
221 # Unless set, always build test code
222 if not self.args.notest:
223 target_use.append('test')
224
225 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000226
227 # Validate platform directory
228 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
229 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
230
231 # Make sure output directory exists (or create it)
232 os.makedirs(self.output_dir, exist_ok=True)
233
234 # Set some default attributes
235 self.libbase_ver = None
236
237 self.configure_environ()
238
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000239 def _generate_rustflags(self):
240 """ Rustflags to include for the build.
241 """
242 rust_flags = [
243 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700244 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000245 '-C',
246 'link-arg=-Wl,--allow-multiple-definition',
Sonny Sasaka87bacb62022-04-29 10:34:29 -0700247 # exclude uninteresting warnings
248 '-A improper_ctypes_definitions -A improper_ctypes -A unknown_lints',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000249 ]
250
251 return ' '.join(rust_flags)
252
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000253 def configure_environ(self):
254 """ Configure environment variables for GN and Cargo.
255 """
256 self.env = os.environ.copy()
257
258 # Make sure cargo home dir exists and has a bin directory
259 cargo_home = os.path.join(self.output_dir, 'cargo_home')
260 os.makedirs(cargo_home, exist_ok=True)
261 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
262
263 # Configure Rust env variables
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700264 self.custom_env = {}
265 self.custom_env['CARGO_TARGET_DIR'] = self.output_dir
266 self.custom_env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
267 self.custom_env['RUSTFLAGS'] = self._generate_rustflags()
268 self.custom_env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
269 self.custom_env['CROS_SYSTEM_API_ROOT'] = os.path.join(self.platform_dir, 'system_api')
270 self.custom_env['CXX_OUTDIR'] = self._gn_default_output()
271 self.env.update(self.custom_env)
272
273 def print_env(self):
274 """ Print the custom environment variables that are used in build.
275
276 Useful so that external tools can mimic the environment to be the same
277 as build.py, e.g. rust-analyzer.
278 """
279 for k, v in self.custom_env.items():
280 print("export {}='{}'".format(k, v))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000281
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000282 def run_command(self, target, args, cwd=None, env=None):
283 """ Run command and stream the output.
284 """
285 # Set some defaults
286 if not cwd:
287 cwd = self.platform_dir
288 if not env:
289 env = self.env
290
291 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
292 with open(log_file, 'wb') as lf:
293 rc = 0
294 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
295 while True:
296 line = process.stdout.readline()
297 print(line.decode('utf-8'), end="")
298 lf.write(line)
299 if not line:
300 rc = process.poll()
301 if rc is not None:
302 break
303
304 time.sleep(0.1)
305
306 if rc != 0:
307 raise Exception("Return code is {}".format(rc))
308
309 def _get_basever(self):
310 if self.libbase_ver:
311 return self.libbase_ver
312
313 self.libbase_ver = os.environ.get('BASE_VER', '')
314 if not self.libbase_ver:
315 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
316 try:
317 with open(base_file, 'r') as f:
318 self.libbase_ver = f.read().strip('\n')
319 except:
320 self.libbase_ver = 'NOT-INSTALLED'
321
322 return self.libbase_ver
323
324 def _gn_default_output(self):
325 return os.path.join(self.output_dir, 'out/Default')
326
327 def _gn_configure(self):
328 """ Configure all required parameters for platform2.
329
330 Mostly copied from //common-mk/platform2.py
331 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700332 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000333
334 def to_gn_string(s):
335 return '"%s"' % s.replace('"', '\\"')
336
337 def to_gn_list(strs):
338 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
339
340 def to_gn_args_args(gn_args):
341 for k, v in gn_args.items():
342 if isinstance(v, bool):
343 v = str(v).lower()
344 elif isinstance(v, list):
345 v = to_gn_list(v)
346 elif isinstance(v, six.string_types):
347 v = to_gn_string(v)
348 else:
349 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
350 yield '%s=%s' % (k.replace('-', '_'), v)
351
352 gn_args = {
353 'platform_subdir': 'bt',
354 'cc': 'clang' if clang else 'gcc',
355 'cxx': 'clang++' if clang else 'g++',
356 'ar': 'llvm-ar' if clang else 'ar',
357 'pkg-config': 'pkg-config',
358 'clang_cc': clang,
359 'clang_cxx': clang,
360 'OS': 'linux',
361 'sysroot': self.sysroot,
362 'libdir': os.path.join(self.sysroot, self.libdir),
363 'build_root': self.output_dir,
364 'platform2_root': self.platform_dir,
365 'libbase_ver': self._get_basever(),
366 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
367 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800368 'external_cxxflags': ["-DNDEBUG"],
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000369 'enable_werror': False,
370 }
371
372 if clang:
373 # Make sure to mark the clang use flag as true
374 self.use.set_flag('clang', True)
375 gn_args['external_cxxflags'] += ['-I/usr/include/']
376
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000377 gn_args_args = list(to_gn_args_args(gn_args))
378 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
379 gn_args_args += ['use={%s}' % (' '.join(use_args))]
380
381 gn_args = [
382 'gn',
383 'gen',
384 ]
385
386 if self.args.verbose:
387 gn_args.append('-v')
388
389 gn_args += [
390 '--root=%s' % self.platform_dir,
391 '--args=%s' % ' '.join(gn_args_args),
392 self._gn_default_output(),
393 ]
394
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700395 if 'PKG_CONFIG_PATH' in self.env:
396 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000397
398 self.run_command('configure', gn_args)
399
400 def _gn_build(self, target):
401 """ Generate the ninja command for the target and run it.
402 """
403 args = ['%s:%s' % ('bt', target)]
404 ninja_args = ['ninja', '-C', self._gn_default_output()]
405 if self.jobs:
406 ninja_args += ['-j', str(self.jobs)]
407 ninja_args += args
408
409 if self.args.verbose:
410 ninja_args.append('-v')
411
412 self.run_command('build', ninja_args)
413
414 def _rust_configure(self):
415 """ Generate config file at cargo_home so we use vendored crates.
416 """
417 template = """
418 [source.systembt]
419 directory = "{}/external/rust/vendor"
420
421 [source.crates-io]
422 replace-with = "systembt"
423 local-registry = "/nonexistent"
424 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700425
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700426 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700427 contents = template.format(self.platform_dir)
428 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
429 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000430
431 def _rust_build(self):
432 """ Run `cargo build` from platform2/bt directory.
433 """
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700434 cmd = ['cargo', 'build']
435 if not self.args.rust_debug:
436 cmd.append('--release')
437
438 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000439
440 def _target_prepare(self):
441 """ Target to prepare the output directory for building.
442
443 This runs gn gen to generate all rquired files and set up the Rust
444 config properly. This will be run
445 """
446 self._gn_configure()
447 self._rust_configure()
448
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800449 def _target_hosttools(self):
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000450 """ Build the tools target in an already prepared environment.
451 """
452 self._gn_build('tools')
453
454 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800455 shutil.copy(os.path.join(self._gn_default_output(), 'bluetooth_packetgen'),
456 os.path.join(self.env['CARGO_HOME'], 'bin'))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000457
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800458 def _target_docs(self):
459 """Build the Rust docs."""
460 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
461
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000462 def _target_rust(self):
463 """ Build rust artifacts in an already prepared environment.
464 """
465 self._rust_build()
466
467 def _target_main(self):
468 """ Build the main GN artifacts in an already prepared environment.
469 """
470 self._gn_build('all')
471
472 def _target_test(self):
473 """ Runs the host tests.
474 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000475 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800476 rust_test_cmd = ['cargo', 'test']
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700477 if not self.args.rust_debug:
478 rust_test_cmd.append('--release')
479
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800480 if self.args.test_name:
Abhishek Pandit-Subedib04e6a92022-09-08 19:19:40 -0700481 rust_test_cmd = rust_test_cmd + [self.args.test_name, "--", "--test-threads=1", "--nocapture"]
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800482
483 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 +0000484
485 # Host tests second based on host test list
486 for t in HOST_TESTS:
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800487 self.run_command('test', [os.path.join(self.output_dir, 'out/Default', t)],
488 cwd=os.path.join(self.output_dir),
489 env=self.env)
490
491 def _target_utils(self):
492 """ Builds the utility applications.
493 """
494 rust_targets = ['hcidoc']
495
496 # Build targets
497 for target in rust_targets:
498 self.run_command('utils', ['cargo', 'build', '-p', target],
499 cwd=os.path.join(self.platform_dir, 'bt'),
500 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000501
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800502 def _target_install(self):
503 """ Installs files required to run Floss to install directory.
504 """
505 # First make the install directory
506 prefix = self.install_dir
507 os.makedirs(prefix, exist_ok=True)
508
509 # Next save the cwd and change to install directory
510 last_cwd = os.getcwd()
511 os.chdir(prefix)
512
513 bindir = os.path.join(self.output_dir, 'debug')
514 srcdir = os.path.dirname(__file__)
515
516 install_map = [
517 {
518 'src': os.path.join(bindir, 'btadapterd'),
519 'dst': 'usr/libexec/bluetooth/btadapterd',
520 'strip': True
521 },
522 {
523 'src': os.path.join(bindir, 'btmanagerd'),
524 'dst': 'usr/libexec/bluetooth/btmanagerd',
525 'strip': True
526 },
527 {
528 'src': os.path.join(bindir, 'btclient'),
529 'dst': 'usr/local/bin/btclient',
530 'strip': True
531 },
532 ]
533
534 for v in install_map:
535 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
536 dst = os.path.join(prefix, partial_dst)
537
538 # Create dst directory first and copy file there
539 os.makedirs(os.path.dirname(dst), exist_ok=True)
540 print('Installing {}'.format(dst))
541 shutil.copy(src, dst)
542
543 # Binary should be marked for strip and no-strip option shouldn't be
544 # set. No-strip is useful while debugging.
545 if strip and not self.args.no_strip:
546 self.run_command('install', ['llvm-strip', dst])
547
548 # Put all files into a tar.gz for easier installation
549 tar_location = os.path.join(prefix, 'floss.tar.gz')
550 with tarfile.open(tar_location, 'w:gz') as tar:
551 for v in install_map:
552 tar.add(v['dst'])
553
554 print('Tarball created at {}'.format(tar_location))
555
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000556 def _target_clean(self):
557 """ Delete the output directory entirely.
558 """
559 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800560
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700561 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700562 cargo_lock_files = [
563 os.path.join(self.platform_dir, 'bt', 'Cargo.lock'),
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700564 ]
565 for lock_file in cargo_lock_files:
566 try:
567 os.remove(lock_file)
568 print('Removed {}'.format(lock_file))
569 except FileNotFoundError:
570 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000571
572 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800573 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000574 """
575 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800576 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000577 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700578 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000579
580 def build(self):
581 """ Builds according to self.target
582 """
583 print('Building target ', self.target)
584
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800585 # Validate that the target is valid
586 if self.target not in VALID_TARGETS:
howardchung63187b62022-08-16 17:06:17 +0800587 print('Target {} is not valid. Must be in {}'.format(self.target, VALID_TARGETS))
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800588 return
589
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000590 if self.target == 'prepare':
591 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800592 elif self.target == 'hosttools':
593 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000594 elif self.target == 'rust':
595 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800596 elif self.target == 'docs':
597 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000598 elif self.target == 'main':
599 self._target_main()
600 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000601 self._target_test()
602 elif self.target == 'clean':
603 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800604 elif self.target == 'install':
605 self._target_install()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800606 elif self.target == 'utils':
607 self._target_utils()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000608 elif self.target == 'all':
609 self._target_all()
610
611
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700612class Bootstrap():
613
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800614 def __init__(self, base_dir, bt_dir, partial_staging):
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700615 """ Construct bootstrapper.
616
617 Args:
618 base_dir: Where to stage everything.
619 bt_dir: Where bluetooth source is kept (will be symlinked)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800620 partial_staging: Whether to do a partial clone for staging.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700621 """
622 self.base_dir = os.path.abspath(base_dir)
623 self.bt_dir = os.path.abspath(bt_dir)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800624 self.partial_staging = partial_staging
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700625
626 # Create base directory if it doesn't already exist
627 os.makedirs(self.base_dir, exist_ok=True)
628
629 if not os.path.isdir(self.bt_dir):
630 raise Exception('{} is not a valid directory'.format(self.bt_dir))
631
632 self.git_dir = os.path.join(self.base_dir, 'repos')
633 self.staging_dir = os.path.join(self.base_dir, 'staging')
634 self.output_dir = os.path.join(self.base_dir, 'output')
635 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
636
637 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
638
639 def _update_platform2(self):
640 """Updates repositories used for build."""
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800641 for project in BOOTSTRAP_GIT_REPOS.keys():
642 cwd = os.path.join(self.git_dir, project)
643 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
644
645 # Update to required commit when necessary or pull the latest code.
646 if commit:
647 head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).strip()
648 if head != commit:
649 subprocess.check_call(['git', 'fetch'], cwd=cwd)
650 subprocess.check_call(['git', 'checkout', commit], cwd=cwd)
651 else:
652 subprocess.check_call(['git', 'pull'], cwd=cwd)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700653
654 def _setup_platform2(self):
655 """ Set up platform2.
656
657 This will check out all the git repos and symlink everything correctly.
658 """
659
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800660 # Create all directories we will need to use
661 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
662 os.makedirs(dirpath, exist_ok=True)
663
664 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700665 if os.path.isfile(self.dir_setup_complete):
666 print('{} already set-up. Updating instead.'.format(self.base_dir))
667 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800668 else:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800669 clone_options = []
670 # When doing a partial staging, we use a treeless clone which allows
671 # us to access all commits but downloads things on demand. This
672 # helps speed up the initial git clone during builds but isn't good
673 # for long-term development.
674 if self.partial_staging:
675 clone_options = ['--filter=tree:0']
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800676 # Check out all repos in git directory
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800677 for project in BOOTSTRAP_GIT_REPOS.keys():
678 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800679 subprocess.check_call(['git', 'clone', repo, project] + clone_options, cwd=self.git_dir)
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800680 # Pin to commit.
681 if commit:
682 subprocess.check_call(['git', 'checkout', commit], cwd=os.path.join(self.git_dir, project))
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700683
684 # Symlink things
685 symlinks = [
686 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
Sonny Sasakaae9f6522022-03-28 10:31:34 -0700687 (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 -0700688 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
689 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
690 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
691 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
692 ]
693
694 # Create symlinks
695 for pairs in symlinks:
696 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000697 try:
698 os.unlink(dst)
699 except Exception as e:
700 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700701 os.symlink(src, dst)
702
703 # Write to setup complete file so we don't repeat this step
704 with open(self.dir_setup_complete, 'w') as f:
705 f.write('Setup complete.')
706
707 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
708 """ Pretty print an install command.
709
710 Args:
711 install_cmd: Prefixed install command.
712 packages: Enumerate packages and append them to install command.
713 line_limit: Number of characters per line.
714
715 Return:
716 Array of lines to join and print.
717 """
718 install = [install_cmd]
719 line = ' '
720 # Remainder needed = space + len(pkg) + space + \
721 # Assuming 80 character lines, that's 80 - 3 = 77
722 line_limit = line_limit - 3
723 for pkg in packages:
724 if len(line) + len(pkg) < line_limit:
725 line = '{}{} '.format(line, pkg)
726 else:
727 install.append(line)
728 line = ' {} '.format(pkg)
729
730 if len(line) > 0:
731 install.append(line)
732
733 return install
734
735 def _check_package_installed(self, package, cmd, predicate):
736 """Check that the given package is installed.
737
738 Args:
739 package: Check that this package is installed.
740 cmd: Command prefix to check if installed (package appended to end)
741 predicate: Function/lambda to check if package is installed based
742 on output. Takes string output and returns boolean.
743
744 Return:
745 True if package is installed.
746 """
747 try:
748 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
749 is_installed = predicate(output.decode('utf-8'))
750 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
751
752 return is_installed
753 except Exception as e:
754 print(e)
755 return False
756
757 def _get_command_output(self, cmd):
758 """Runs the command and gets the output.
759
760 Args:
761 cmd: Command to run.
762
763 Return:
764 Tuple (Success, Output). Success represents if the command ran ok.
765 """
766 try:
767 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
768 return (True, output.decode('utf-8').split('\n'))
769 except Exception as e:
770 print(e)
771 return (False, "")
772
773 def _print_missing_packages(self):
774 """Print any missing packages found via apt.
775
776 This will find any missing packages necessary for build using apt and
777 print it out as an apt-get install printf.
778 """
779 print('Checking for any missing packages...')
780
781 (success, output) = self._get_command_output(APT_PKG_LIST)
782 if not success:
783 raise Exception("Could not query apt for packages.")
784
785 packages_installed = {}
786 for line in output:
787 if 'installed' in line:
788 split = line.split('/', 2)
789 packages_installed[split[0]] = True
790
791 need_packages = []
792 for pkg in REQUIRED_APT_PACKAGES:
793 if pkg not in packages_installed:
794 need_packages.append(pkg)
795
796 # No packages need to be installed
797 if len(need_packages) == 0:
798 print('+ All required packages are installed')
799 return
800
801 install = self._pretty_print_install('sudo apt-get install', need_packages)
802
803 # Print all lines so they can be run in cmdline
804 print('Missing system packages. Run the following command: ')
805 print(' \\\n'.join(install))
806
807 def _print_missing_rust_packages(self):
808 """Print any missing packages found via cargo.
809
810 This will find any missing packages necessary for build using cargo and
811 print it out as a cargo-install printf.
812 """
813 print('Checking for any missing cargo packages...')
814
815 (success, output) = self._get_command_output(CARGO_PKG_LIST)
816 if not success:
817 raise Exception("Could not query cargo for packages.")
818
819 packages_installed = {}
820 for line in output:
821 # Cargo installed packages have this format
822 # <crate name> <version>:
823 # <binary name>
824 # We only care about the crates themselves
825 if ':' not in line:
826 continue
827
828 split = line.split(' ', 2)
829 packages_installed[split[0]] = True
830
831 need_packages = []
832 for pkg in REQUIRED_CARGO_PACKAGES:
833 if pkg not in packages_installed:
834 need_packages.append(pkg)
835
836 # No packages to be installed
837 if len(need_packages) == 0:
838 print('+ All required cargo packages are installed')
839 return
840
841 install = self._pretty_print_install('cargo install', need_packages)
842 print('Missing cargo packages. Run the following command: ')
843 print(' \\\n'.join(install))
844
845 def bootstrap(self):
846 """ Bootstrap the Linux build."""
847 self._setup_platform2()
848 self._print_missing_packages()
849 self._print_missing_rust_packages()
850
851
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000852if __name__ == '__main__':
853 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800854 parser.add_argument('--bootstrap-dir',
855 help='Directory to run bootstrap on (or was previously run on).',
856 default="~/.floss")
857 parser.add_argument('--run-bootstrap',
858 help='Run bootstrap code to verify build env is ok to build.',
859 default=False,
860 action='store_true')
861 parser.add_argument('--print-env',
862 help='Print environment variables used for build.',
863 default=False,
864 action='store_true')
Andre Braga17ff7bc2022-06-24 22:43:18 +0000865 parser.add_argument('--no-clang', help='Don\'t use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800866 parser.add_argument('--no-strip',
867 help='Skip stripping binaries during install.',
868 default=False,
869 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000870 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800871 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
872 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000873 parser.add_argument('--target', help='Run specific build target')
874 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700875 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000876 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800877 parser.add_argument('--no-vendored-rust',
878 help='Do not use vendored rust crates',
879 default=False,
880 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000881 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700882 parser.add_argument('--rust-debug', help='Build Rust code as debug.', default=False, action='store_true')
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800883 parser.add_argument(
884 '--partial-staging',
885 help='Bootstrap git repositories with partial clones. Use to speed up initial git clone for automated builds.',
886 default=False,
887 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000888 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700889
890 # Make sure we get absolute path + expanded path for bootstrap directory
891 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
892
Andre Bragaaa11e7d2022-08-10 21:46:44 +0000893 # Possible values for machine() come from 'uname -m'
894 # Since this script only runs on Linux, x86_64 machines must have this value
895 if platform.machine() != 'x86_64':
896 raise Exception("Only x86_64 machines are currently supported by this build script.")
897
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700898 if args.run_bootstrap:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800899 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__), args.partial_staging)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700900 bootstrap.bootstrap()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700901 elif args.print_env:
902 build = HostBuild(args)
903 build.print_env()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700904 else:
905 build = HostBuild(args)
906 build.build()