blob: 7fd925e8d15c703576c92a43498e1e40b6d5a3d9 [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',
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000047 'fuzzer',
48 'fuzzer',
49 'msan',
50 'profiling',
51 'tcmalloc',
52 'test',
53 'ubsan',
54]
55
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080056# Use a specific commit version for common-mk to avoid build surprises.
57COMMON_MK_COMMIT = "136c3e114b65f2c6c5f026376c2e75c73c2478a3"
58
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000059# Default use flags.
60USE_DEFAULTS = {
61 'android': False,
62 'bt_nonstandard_codecs': False,
63 'test': False,
64}
65
66VALID_TARGETS = [
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000067 'all', # All targets except test and clean
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080068 'clean', # Clean up output directory
69 'docs', # Build Rust docs
70 'main', # Build the main C++ codebase
71 'prepare', # Prepare the output directory (gn gen + rust setup)
Ivan Podogovd7b6b6d2022-08-10 11:07:12 +010072 'rootcanal', # Build Rust targets for RootCanal
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080073 'rust', # Build only the rust components + copy artifacts to output dir
74 'test', # Run the unit tests
75 'tools', # Build the host tools (i.e. packetgen)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000076]
77
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000078# TODO(b/190750167) - Host tests are disabled until we are full bazel build
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000079HOST_TESTS = [
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000080 # 'bluetooth_test_common',
81 # 'bluetoothtbd_test',
82 # 'net_test_avrcp',
83 # 'net_test_btcore',
84 # 'net_test_types',
85 # 'net_test_btm_iso',
86 # 'net_test_btpackets',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000087]
88
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080089# Map of git repos to bootstrap and what commit to check them out at. None
90# values will just checkout to HEAD.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070091BOOTSTRAP_GIT_REPOS = {
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -080092 'platform2': ('https://chromium.googlesource.com/chromiumos/platform2', COMMON_MK_COMMIT),
93 'rust_crates': ('https://chromium.googlesource.com/chromiumos/third_party/rust_crates', None),
94 'proto_logging': ('https://android.googlesource.com/platform/frameworks/proto_logging', None),
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070095}
96
97# List of packages required for linux build
98REQUIRED_APT_PACKAGES = [
99 'bison',
100 'build-essential',
101 'curl',
102 'debmake',
103 'flatbuffers-compiler',
104 'flex',
105 'g++-multilib',
106 'gcc-multilib',
107 'generate-ninja',
108 'gnupg',
109 'gperf',
Martin Brabhamba22adf2022-02-04 19:51:21 +0000110 'libc++abi-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700111 'libc++-dev',
112 'libdbus-1-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000113 'libdouble-conversion-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700114 'libevent-dev',
115 'libevent-dev',
116 'libflatbuffers-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700117 'libgl1-mesa-dev',
118 'libglib2.0-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000119 'libgtest-dev',
120 'libgmock-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700121 'liblz4-tool',
122 'libncurses5',
123 'libnss3-dev',
124 'libprotobuf-dev',
125 'libre2-9',
Martin Brabham996f1502022-02-14 17:39:23 +0000126 'libre2-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700127 'libssl-dev',
128 'libtinyxml2-dev',
129 'libx11-dev',
130 'libxml2-utils',
131 'ninja-build',
132 'openssl',
133 'protobuf-compiler',
134 'unzip',
135 'x11proto-core-dev',
136 'xsltproc',
137 'zip',
138 'zlib1g-dev',
139]
140
141# List of cargo packages required for linux build
142REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd']
143
144APT_PKG_LIST = ['apt', '-qq', 'list']
145CARGO_PKG_LIST = ['cargo', 'install', '--list']
146
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000147
148class UseFlags():
149
150 def __init__(self, use_flags):
151 """ Construct the use flags.
152
153 Args:
154 use_flags: List of use flags parsed from the command.
155 """
156 self.flags = {}
157
158 # Import use flags required by common-mk
159 for use in COMMON_MK_USES:
160 self.set_flag(use, False)
161
162 # Set our defaults
163 for use, value in USE_DEFAULTS.items():
164 self.set_flag(use, value)
165
166 # Set use flags - value is set to True unless the use starts with -
167 # All given use flags always override the defaults
168 for use in use_flags:
169 value = not use.startswith('-')
170 self.set_flag(use, value)
171
172 def set_flag(self, key, value=True):
173 setattr(self, key, value)
174 self.flags[key] = value
175
176
177class HostBuild():
178
179 def __init__(self, args):
180 """ Construct the builder.
181
182 Args:
183 args: Parsed arguments from ArgumentParser
184 """
185 self.args = args
186
187 # Set jobs to number of cpus unless explicitly set
188 self.jobs = self.args.jobs
189 if not self.jobs:
190 self.jobs = multiprocessing.cpu_count()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700191 sys.stderr.write("Number of jobs = {}\n".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000192
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700193 # Normalize bootstrap dir and make sure it exists
194 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
195 os.makedirs(self.bootstrap_dir, exist_ok=True)
196
197 # Output and platform directories are based on bootstrap
198 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
199 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Michael Sun4940f2b2022-09-15 16:08:24 -0700200 self.bt_dir = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000201 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000202 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800203 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000204
Michael Sun4940f2b2022-09-15 16:08:24 -0700205 assert os.path.samefile(self.bt_dir,
206 os.path.dirname(__file__)), "Please rerun bootstrap for the current project!"
207
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000208 # If default target isn't set, build everything
209 self.target = 'all'
210 if hasattr(self.args, 'target') and self.args.target:
211 self.target = self.args.target
212
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000213 target_use = self.args.use if self.args.use else []
214
215 # Unless set, always build test code
216 if not self.args.notest:
217 target_use.append('test')
218
219 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000220
221 # Validate platform directory
222 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
223 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
224
225 # Make sure output directory exists (or create it)
226 os.makedirs(self.output_dir, exist_ok=True)
227
228 # Set some default attributes
229 self.libbase_ver = None
230
231 self.configure_environ()
232
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000233 def _generate_rustflags(self):
234 """ Rustflags to include for the build.
235 """
236 rust_flags = [
237 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700238 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000239 '-C',
240 'link-arg=-Wl,--allow-multiple-definition',
Sonny Sasaka87bacb62022-04-29 10:34:29 -0700241 # exclude uninteresting warnings
242 '-A improper_ctypes_definitions -A improper_ctypes -A unknown_lints',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000243 ]
244
245 return ' '.join(rust_flags)
246
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000247 def configure_environ(self):
248 """ Configure environment variables for GN and Cargo.
249 """
250 self.env = os.environ.copy()
251
252 # Make sure cargo home dir exists and has a bin directory
253 cargo_home = os.path.join(self.output_dir, 'cargo_home')
254 os.makedirs(cargo_home, exist_ok=True)
255 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
256
257 # Configure Rust env variables
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700258 self.custom_env = {}
259 self.custom_env['CARGO_TARGET_DIR'] = self.output_dir
260 self.custom_env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
261 self.custom_env['RUSTFLAGS'] = self._generate_rustflags()
262 self.custom_env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
263 self.custom_env['CROS_SYSTEM_API_ROOT'] = os.path.join(self.platform_dir, 'system_api')
264 self.custom_env['CXX_OUTDIR'] = self._gn_default_output()
265 self.env.update(self.custom_env)
266
267 def print_env(self):
268 """ Print the custom environment variables that are used in build.
269
270 Useful so that external tools can mimic the environment to be the same
271 as build.py, e.g. rust-analyzer.
272 """
273 for k, v in self.custom_env.items():
274 print("export {}='{}'".format(k, v))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000275
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000276 def run_command(self, target, args, cwd=None, env=None):
277 """ Run command and stream the output.
278 """
279 # Set some defaults
280 if not cwd:
281 cwd = self.platform_dir
282 if not env:
283 env = self.env
284
285 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
286 with open(log_file, 'wb') as lf:
287 rc = 0
288 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
289 while True:
290 line = process.stdout.readline()
291 print(line.decode('utf-8'), end="")
292 lf.write(line)
293 if not line:
294 rc = process.poll()
295 if rc is not None:
296 break
297
298 time.sleep(0.1)
299
300 if rc != 0:
301 raise Exception("Return code is {}".format(rc))
302
303 def _get_basever(self):
304 if self.libbase_ver:
305 return self.libbase_ver
306
307 self.libbase_ver = os.environ.get('BASE_VER', '')
308 if not self.libbase_ver:
309 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
310 try:
311 with open(base_file, 'r') as f:
312 self.libbase_ver = f.read().strip('\n')
313 except:
314 self.libbase_ver = 'NOT-INSTALLED'
315
316 return self.libbase_ver
317
318 def _gn_default_output(self):
319 return os.path.join(self.output_dir, 'out/Default')
320
321 def _gn_configure(self):
322 """ Configure all required parameters for platform2.
323
324 Mostly copied from //common-mk/platform2.py
325 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700326 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000327
328 def to_gn_string(s):
329 return '"%s"' % s.replace('"', '\\"')
330
331 def to_gn_list(strs):
332 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
333
334 def to_gn_args_args(gn_args):
335 for k, v in gn_args.items():
336 if isinstance(v, bool):
337 v = str(v).lower()
338 elif isinstance(v, list):
339 v = to_gn_list(v)
340 elif isinstance(v, six.string_types):
341 v = to_gn_string(v)
342 else:
343 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
344 yield '%s=%s' % (k.replace('-', '_'), v)
345
346 gn_args = {
347 'platform_subdir': 'bt',
348 'cc': 'clang' if clang else 'gcc',
349 'cxx': 'clang++' if clang else 'g++',
350 'ar': 'llvm-ar' if clang else 'ar',
351 'pkg-config': 'pkg-config',
352 'clang_cc': clang,
353 'clang_cxx': clang,
354 'OS': 'linux',
355 'sysroot': self.sysroot,
356 'libdir': os.path.join(self.sysroot, self.libdir),
357 'build_root': self.output_dir,
358 'platform2_root': self.platform_dir,
359 'libbase_ver': self._get_basever(),
360 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
361 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800362 'external_cxxflags': ["-DNDEBUG"],
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000363 'enable_werror': False,
364 }
365
366 if clang:
367 # Make sure to mark the clang use flag as true
368 self.use.set_flag('clang', True)
369 gn_args['external_cxxflags'] += ['-I/usr/include/']
370
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000371 gn_args_args = list(to_gn_args_args(gn_args))
372 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
373 gn_args_args += ['use={%s}' % (' '.join(use_args))]
374
375 gn_args = [
376 'gn',
377 'gen',
378 ]
379
380 if self.args.verbose:
381 gn_args.append('-v')
382
383 gn_args += [
384 '--root=%s' % self.platform_dir,
385 '--args=%s' % ' '.join(gn_args_args),
386 self._gn_default_output(),
387 ]
388
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700389 if 'PKG_CONFIG_PATH' in self.env:
390 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000391
392 self.run_command('configure', gn_args)
393
394 def _gn_build(self, target):
395 """ Generate the ninja command for the target and run it.
396 """
397 args = ['%s:%s' % ('bt', target)]
398 ninja_args = ['ninja', '-C', self._gn_default_output()]
399 if self.jobs:
400 ninja_args += ['-j', str(self.jobs)]
401 ninja_args += args
402
403 if self.args.verbose:
404 ninja_args.append('-v')
405
406 self.run_command('build', ninja_args)
407
408 def _rust_configure(self):
409 """ Generate config file at cargo_home so we use vendored crates.
410 """
411 template = """
412 [source.systembt]
413 directory = "{}/external/rust/vendor"
414
415 [source.crates-io]
416 replace-with = "systembt"
417 local-registry = "/nonexistent"
418 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700419
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700420 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700421 contents = template.format(self.platform_dir)
422 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
423 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000424
425 def _rust_build(self):
426 """ Run `cargo build` from platform2/bt directory.
427 """
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700428 cmd = ['cargo', 'build']
429 if not self.args.rust_debug:
430 cmd.append('--release')
431
432 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000433
434 def _target_prepare(self):
435 """ Target to prepare the output directory for building.
436
437 This runs gn gen to generate all rquired files and set up the Rust
438 config properly. This will be run
439 """
440 self._gn_configure()
441 self._rust_configure()
442
443 def _target_tools(self):
444 """ Build the tools target in an already prepared environment.
445 """
446 self._gn_build('tools')
447
448 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
449 shutil.copy(
450 os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
451
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800452 def _target_docs(self):
453 """Build the Rust docs."""
454 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
455
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000456 def _target_rust(self):
457 """ Build rust artifacts in an already prepared environment.
458 """
459 self._rust_build()
460
Ivan Podogovd7b6b6d2022-08-10 11:07:12 +0100461 def _target_rootcanal(self):
462 """ Build rust artifacts for RootCanal in an already prepared environment.
463 """
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700464 cmd = ['cargo', 'build']
465 if not self.args.rust_debug:
466 cmd.append('--release')
467
468 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt/tools/rootcanal'), env=self.env)
Ivan Podogovd7b6b6d2022-08-10 11:07:12 +0100469
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000470 def _target_main(self):
471 """ Build the main GN artifacts in an already prepared environment.
472 """
473 self._gn_build('all')
474
475 def _target_test(self):
476 """ Runs the host tests.
477 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000478 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800479 rust_test_cmd = ['cargo', 'test']
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700480 if not self.args.rust_debug:
481 rust_test_cmd.append('--release')
482
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800483 if self.args.test_name:
Abhishek Pandit-Subedib04e6a92022-09-08 19:19:40 -0700484 rust_test_cmd = rust_test_cmd + [self.args.test_name, "--", "--test-threads=1", "--nocapture"]
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800485
486 self.run_command('test', rust_test_cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Ivan Podogovd7b6b6d2022-08-10 11:07:12 +0100487 self.run_command('test', rust_test_cmd, cwd=os.path.join(self.platform_dir, 'bt/tools/rootcanal'), env=self.env)
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000488
489 # Host tests second based on host test list
490 for t in HOST_TESTS:
491 self.run_command(
492 'test', [os.path.join(self.output_dir, 'out/Default', t)],
493 cwd=os.path.join(self.output_dir),
494 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000495
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800496 def _target_install(self):
497 """ Installs files required to run Floss to install directory.
498 """
499 # First make the install directory
500 prefix = self.install_dir
501 os.makedirs(prefix, exist_ok=True)
502
503 # Next save the cwd and change to install directory
504 last_cwd = os.getcwd()
505 os.chdir(prefix)
506
507 bindir = os.path.join(self.output_dir, 'debug')
508 srcdir = os.path.dirname(__file__)
509
510 install_map = [
511 {
512 'src': os.path.join(bindir, 'btadapterd'),
513 'dst': 'usr/libexec/bluetooth/btadapterd',
514 'strip': True
515 },
516 {
517 'src': os.path.join(bindir, 'btmanagerd'),
518 'dst': 'usr/libexec/bluetooth/btmanagerd',
519 'strip': True
520 },
521 {
522 'src': os.path.join(bindir, 'btclient'),
523 'dst': 'usr/local/bin/btclient',
524 'strip': True
525 },
526 ]
527
528 for v in install_map:
529 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
530 dst = os.path.join(prefix, partial_dst)
531
532 # Create dst directory first and copy file there
533 os.makedirs(os.path.dirname(dst), exist_ok=True)
534 print('Installing {}'.format(dst))
535 shutil.copy(src, dst)
536
537 # Binary should be marked for strip and no-strip option shouldn't be
538 # set. No-strip is useful while debugging.
539 if strip and not self.args.no_strip:
540 self.run_command('install', ['llvm-strip', dst])
541
542 # Put all files into a tar.gz for easier installation
543 tar_location = os.path.join(prefix, 'floss.tar.gz')
544 with tarfile.open(tar_location, 'w:gz') as tar:
545 for v in install_map:
546 tar.add(v['dst'])
547
548 print('Tarball created at {}'.format(tar_location))
549
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000550 def _target_clean(self):
551 """ Delete the output directory entirely.
552 """
553 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800554
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700555 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700556 cargo_lock_files = [
557 os.path.join(self.platform_dir, 'bt', 'Cargo.lock'),
558 os.path.join(self.platform_dir, 'bt', 'tools', 'rootcanal', 'Cargo.lock'),
559 ]
560 for lock_file in cargo_lock_files:
561 try:
562 os.remove(lock_file)
563 print('Removed {}'.format(lock_file))
564 except FileNotFoundError:
565 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000566
567 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800568 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000569 """
570 self._target_prepare()
571 self._target_tools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000572 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700573 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000574
575 def build(self):
576 """ Builds according to self.target
577 """
578 print('Building target ', self.target)
579
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800580 # Validate that the target is valid
581 if self.target not in VALID_TARGETS:
howardchung63187b62022-08-16 17:06:17 +0800582 print('Target {} is not valid. Must be in {}'.format(self.target, VALID_TARGETS))
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800583 return
584
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000585 if self.target == 'prepare':
586 self._target_prepare()
587 elif self.target == 'tools':
588 self._target_tools()
Ivan Podogovd7b6b6d2022-08-10 11:07:12 +0100589 elif self.target == 'rootcanal':
590 self._target_rootcanal()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000591 elif self.target == 'rust':
592 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800593 elif self.target == 'docs':
594 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000595 elif self.target == 'main':
596 self._target_main()
597 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000598 self._target_test()
599 elif self.target == 'clean':
600 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800601 elif self.target == 'install':
602 self._target_install()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000603 elif self.target == 'all':
604 self._target_all()
605
606
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700607class Bootstrap():
608
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800609 def __init__(self, base_dir, bt_dir, partial_staging):
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700610 """ Construct bootstrapper.
611
612 Args:
613 base_dir: Where to stage everything.
614 bt_dir: Where bluetooth source is kept (will be symlinked)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800615 partial_staging: Whether to do a partial clone for staging.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700616 """
617 self.base_dir = os.path.abspath(base_dir)
618 self.bt_dir = os.path.abspath(bt_dir)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800619 self.partial_staging = partial_staging
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700620
621 # Create base directory if it doesn't already exist
622 os.makedirs(self.base_dir, exist_ok=True)
623
624 if not os.path.isdir(self.bt_dir):
625 raise Exception('{} is not a valid directory'.format(self.bt_dir))
626
627 self.git_dir = os.path.join(self.base_dir, 'repos')
628 self.staging_dir = os.path.join(self.base_dir, 'staging')
629 self.output_dir = os.path.join(self.base_dir, 'output')
630 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
631
632 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
633
634 def _update_platform2(self):
635 """Updates repositories used for build."""
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800636 for project in BOOTSTRAP_GIT_REPOS.keys():
637 cwd = os.path.join(self.git_dir, project)
638 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
639
640 # Update to required commit when necessary or pull the latest code.
641 if commit:
642 head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).strip()
643 if head != commit:
644 subprocess.check_call(['git', 'fetch'], cwd=cwd)
645 subprocess.check_call(['git', 'checkout', commit], cwd=cwd)
646 else:
647 subprocess.check_call(['git', 'pull'], cwd=cwd)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700648
649 def _setup_platform2(self):
650 """ Set up platform2.
651
652 This will check out all the git repos and symlink everything correctly.
653 """
654
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800655 # Create all directories we will need to use
656 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
657 os.makedirs(dirpath, exist_ok=True)
658
659 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700660 if os.path.isfile(self.dir_setup_complete):
661 print('{} already set-up. Updating instead.'.format(self.base_dir))
662 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800663 else:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800664 clone_options = []
665 # When doing a partial staging, we use a treeless clone which allows
666 # us to access all commits but downloads things on demand. This
667 # helps speed up the initial git clone during builds but isn't good
668 # for long-term development.
669 if self.partial_staging:
670 clone_options = ['--filter=tree:0']
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800671 # Check out all repos in git directory
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800672 for project in BOOTSTRAP_GIT_REPOS.keys():
673 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800674 subprocess.check_call(['git', 'clone', repo, project] + clone_options, cwd=self.git_dir)
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800675 # Pin to commit.
676 if commit:
677 subprocess.check_call(['git', 'checkout', commit], cwd=os.path.join(self.git_dir, project))
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700678
679 # Symlink things
680 symlinks = [
681 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
Sonny Sasakaae9f6522022-03-28 10:31:34 -0700682 (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 -0700683 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
684 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
685 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
686 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
687 ]
688
689 # Create symlinks
690 for pairs in symlinks:
691 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000692 try:
693 os.unlink(dst)
694 except Exception as e:
695 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700696 os.symlink(src, dst)
697
698 # Write to setup complete file so we don't repeat this step
699 with open(self.dir_setup_complete, 'w') as f:
700 f.write('Setup complete.')
701
702 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
703 """ Pretty print an install command.
704
705 Args:
706 install_cmd: Prefixed install command.
707 packages: Enumerate packages and append them to install command.
708 line_limit: Number of characters per line.
709
710 Return:
711 Array of lines to join and print.
712 """
713 install = [install_cmd]
714 line = ' '
715 # Remainder needed = space + len(pkg) + space + \
716 # Assuming 80 character lines, that's 80 - 3 = 77
717 line_limit = line_limit - 3
718 for pkg in packages:
719 if len(line) + len(pkg) < line_limit:
720 line = '{}{} '.format(line, pkg)
721 else:
722 install.append(line)
723 line = ' {} '.format(pkg)
724
725 if len(line) > 0:
726 install.append(line)
727
728 return install
729
730 def _check_package_installed(self, package, cmd, predicate):
731 """Check that the given package is installed.
732
733 Args:
734 package: Check that this package is installed.
735 cmd: Command prefix to check if installed (package appended to end)
736 predicate: Function/lambda to check if package is installed based
737 on output. Takes string output and returns boolean.
738
739 Return:
740 True if package is installed.
741 """
742 try:
743 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
744 is_installed = predicate(output.decode('utf-8'))
745 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
746
747 return is_installed
748 except Exception as e:
749 print(e)
750 return False
751
752 def _get_command_output(self, cmd):
753 """Runs the command and gets the output.
754
755 Args:
756 cmd: Command to run.
757
758 Return:
759 Tuple (Success, Output). Success represents if the command ran ok.
760 """
761 try:
762 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
763 return (True, output.decode('utf-8').split('\n'))
764 except Exception as e:
765 print(e)
766 return (False, "")
767
768 def _print_missing_packages(self):
769 """Print any missing packages found via apt.
770
771 This will find any missing packages necessary for build using apt and
772 print it out as an apt-get install printf.
773 """
774 print('Checking for any missing packages...')
775
776 (success, output) = self._get_command_output(APT_PKG_LIST)
777 if not success:
778 raise Exception("Could not query apt for packages.")
779
780 packages_installed = {}
781 for line in output:
782 if 'installed' in line:
783 split = line.split('/', 2)
784 packages_installed[split[0]] = True
785
786 need_packages = []
787 for pkg in REQUIRED_APT_PACKAGES:
788 if pkg not in packages_installed:
789 need_packages.append(pkg)
790
791 # No packages need to be installed
792 if len(need_packages) == 0:
793 print('+ All required packages are installed')
794 return
795
796 install = self._pretty_print_install('sudo apt-get install', need_packages)
797
798 # Print all lines so they can be run in cmdline
799 print('Missing system packages. Run the following command: ')
800 print(' \\\n'.join(install))
801
802 def _print_missing_rust_packages(self):
803 """Print any missing packages found via cargo.
804
805 This will find any missing packages necessary for build using cargo and
806 print it out as a cargo-install printf.
807 """
808 print('Checking for any missing cargo packages...')
809
810 (success, output) = self._get_command_output(CARGO_PKG_LIST)
811 if not success:
812 raise Exception("Could not query cargo for packages.")
813
814 packages_installed = {}
815 for line in output:
816 # Cargo installed packages have this format
817 # :
818 #
819 # We only care about the crates themselves
820 if ':' not in line:
821 continue
822
823 split = line.split(' ', 2)
824 packages_installed[split[0]] = True
825
826 need_packages = []
827 for pkg in REQUIRED_CARGO_PACKAGES:
828 if pkg not in packages_installed:
829 need_packages.append(pkg)
830
831 # No packages to be installed
832 if len(need_packages) == 0:
833 print('+ All required cargo packages are installed')
834 return
835
836 install = self._pretty_print_install('cargo install', need_packages)
837 print('Missing cargo packages. Run the following command: ')
838 print(' \\\n'.join(install))
839
840 def bootstrap(self):
841 """ Bootstrap the Linux build."""
842 self._setup_platform2()
843 self._print_missing_packages()
844 self._print_missing_rust_packages()
845
846
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000847if __name__ == '__main__':
848 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700849 parser.add_argument(
850 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
851 parser.add_argument(
852 '--run-bootstrap',
853 help='Run bootstrap code to verify build env is ok to build.',
854 default=False,
855 action='store_true')
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700856 parser.add_argument(
857 '--print-env', help='Print environment variables used for build.', default=False, action='store_true')
Andre Braga17ff7bc2022-06-24 22:43:18 +0000858 parser.add_argument('--no-clang', help='Don\'t use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800859 parser.add_argument(
860 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000861 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800862 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
863 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000864 parser.add_argument('--target', help='Run specific build target')
865 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700866 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000867 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700868 parser.add_argument(
869 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000870 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700871 parser.add_argument('--rust-debug', help='Build Rust code as debug.', default=False, action='store_true')
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800872 parser.add_argument(
873 '--partial-staging',
874 help='Bootstrap git repositories with partial clones. Use to speed up initial git clone for automated builds.',
875 default=False,
876 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000877 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700878
879 # Make sure we get absolute path + expanded path for bootstrap directory
880 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
881
Andre Bragaaa11e7d2022-08-10 21:46:44 +0000882 # Possible values for machine() come from 'uname -m'
883 # Since this script only runs on Linux, x86_64 machines must have this value
884 if platform.machine() != 'x86_64':
885 raise Exception("Only x86_64 machines are currently supported by this build script.")
886
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700887 if args.run_bootstrap:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800888 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__), args.partial_staging)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700889 bootstrap.bootstrap()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700890 elif args.print_env:
891 build = HostBuild(args)
892 build.print_env()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700893 else:
894 build = HostBuild(args)
895 build.build()