blob: ace84f9305eb44292a493b7476840ae64090301c [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
609 def __init__(self, base_dir, bt_dir):
610 """ Construct bootstrapper.
611
612 Args:
613 base_dir: Where to stage everything.
614 bt_dir: Where bluetooth source is kept (will be symlinked)
615 """
616 self.base_dir = os.path.abspath(base_dir)
617 self.bt_dir = os.path.abspath(bt_dir)
618
619 # Create base directory if it doesn't already exist
620 os.makedirs(self.base_dir, exist_ok=True)
621
622 if not os.path.isdir(self.bt_dir):
623 raise Exception('{} is not a valid directory'.format(self.bt_dir))
624
625 self.git_dir = os.path.join(self.base_dir, 'repos')
626 self.staging_dir = os.path.join(self.base_dir, 'staging')
627 self.output_dir = os.path.join(self.base_dir, 'output')
628 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
629
630 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
631
632 def _update_platform2(self):
633 """Updates repositories used for build."""
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800634 for project in BOOTSTRAP_GIT_REPOS.keys():
635 cwd = os.path.join(self.git_dir, project)
636 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
637
638 # Update to required commit when necessary or pull the latest code.
639 if commit:
640 head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).strip()
641 if head != commit:
642 subprocess.check_call(['git', 'fetch'], cwd=cwd)
643 subprocess.check_call(['git', 'checkout', commit], cwd=cwd)
644 else:
645 subprocess.check_call(['git', 'pull'], cwd=cwd)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700646
647 def _setup_platform2(self):
648 """ Set up platform2.
649
650 This will check out all the git repos and symlink everything correctly.
651 """
652
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800653 # Create all directories we will need to use
654 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
655 os.makedirs(dirpath, exist_ok=True)
656
657 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700658 if os.path.isfile(self.dir_setup_complete):
659 print('{} already set-up. Updating instead.'.format(self.base_dir))
660 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800661 else:
662 # Check out all repos in git directory
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800663 for project in BOOTSTRAP_GIT_REPOS.keys():
664 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
665 subprocess.check_call(['git', 'clone', repo, project], cwd=self.git_dir)
666 # Pin to commit.
667 if commit:
668 subprocess.check_call(['git', 'checkout', commit], cwd=os.path.join(self.git_dir, project))
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700669
670 # Symlink things
671 symlinks = [
672 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
Sonny Sasakaae9f6522022-03-28 10:31:34 -0700673 (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 -0700674 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
675 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
676 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
677 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
678 ]
679
680 # Create symlinks
681 for pairs in symlinks:
682 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000683 try:
684 os.unlink(dst)
685 except Exception as e:
686 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700687 os.symlink(src, dst)
688
689 # Write to setup complete file so we don't repeat this step
690 with open(self.dir_setup_complete, 'w') as f:
691 f.write('Setup complete.')
692
693 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
694 """ Pretty print an install command.
695
696 Args:
697 install_cmd: Prefixed install command.
698 packages: Enumerate packages and append them to install command.
699 line_limit: Number of characters per line.
700
701 Return:
702 Array of lines to join and print.
703 """
704 install = [install_cmd]
705 line = ' '
706 # Remainder needed = space + len(pkg) + space + \
707 # Assuming 80 character lines, that's 80 - 3 = 77
708 line_limit = line_limit - 3
709 for pkg in packages:
710 if len(line) + len(pkg) < line_limit:
711 line = '{}{} '.format(line, pkg)
712 else:
713 install.append(line)
714 line = ' {} '.format(pkg)
715
716 if len(line) > 0:
717 install.append(line)
718
719 return install
720
721 def _check_package_installed(self, package, cmd, predicate):
722 """Check that the given package is installed.
723
724 Args:
725 package: Check that this package is installed.
726 cmd: Command prefix to check if installed (package appended to end)
727 predicate: Function/lambda to check if package is installed based
728 on output. Takes string output and returns boolean.
729
730 Return:
731 True if package is installed.
732 """
733 try:
734 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
735 is_installed = predicate(output.decode('utf-8'))
736 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
737
738 return is_installed
739 except Exception as e:
740 print(e)
741 return False
742
743 def _get_command_output(self, cmd):
744 """Runs the command and gets the output.
745
746 Args:
747 cmd: Command to run.
748
749 Return:
750 Tuple (Success, Output). Success represents if the command ran ok.
751 """
752 try:
753 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
754 return (True, output.decode('utf-8').split('\n'))
755 except Exception as e:
756 print(e)
757 return (False, "")
758
759 def _print_missing_packages(self):
760 """Print any missing packages found via apt.
761
762 This will find any missing packages necessary for build using apt and
763 print it out as an apt-get install printf.
764 """
765 print('Checking for any missing packages...')
766
767 (success, output) = self._get_command_output(APT_PKG_LIST)
768 if not success:
769 raise Exception("Could not query apt for packages.")
770
771 packages_installed = {}
772 for line in output:
773 if 'installed' in line:
774 split = line.split('/', 2)
775 packages_installed[split[0]] = True
776
777 need_packages = []
778 for pkg in REQUIRED_APT_PACKAGES:
779 if pkg not in packages_installed:
780 need_packages.append(pkg)
781
782 # No packages need to be installed
783 if len(need_packages) == 0:
784 print('+ All required packages are installed')
785 return
786
787 install = self._pretty_print_install('sudo apt-get install', need_packages)
788
789 # Print all lines so they can be run in cmdline
790 print('Missing system packages. Run the following command: ')
791 print(' \\\n'.join(install))
792
793 def _print_missing_rust_packages(self):
794 """Print any missing packages found via cargo.
795
796 This will find any missing packages necessary for build using cargo and
797 print it out as a cargo-install printf.
798 """
799 print('Checking for any missing cargo packages...')
800
801 (success, output) = self._get_command_output(CARGO_PKG_LIST)
802 if not success:
803 raise Exception("Could not query cargo for packages.")
804
805 packages_installed = {}
806 for line in output:
807 # Cargo installed packages have this format
808 # <crate name> <version>:
809 # <binary name>
810 # We only care about the crates themselves
811 if ':' not in line:
812 continue
813
814 split = line.split(' ', 2)
815 packages_installed[split[0]] = True
816
817 need_packages = []
818 for pkg in REQUIRED_CARGO_PACKAGES:
819 if pkg not in packages_installed:
820 need_packages.append(pkg)
821
822 # No packages to be installed
823 if len(need_packages) == 0:
824 print('+ All required cargo packages are installed')
825 return
826
827 install = self._pretty_print_install('cargo install', need_packages)
828 print('Missing cargo packages. Run the following command: ')
829 print(' \\\n'.join(install))
830
831 def bootstrap(self):
832 """ Bootstrap the Linux build."""
833 self._setup_platform2()
834 self._print_missing_packages()
835 self._print_missing_rust_packages()
836
837
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000838if __name__ == '__main__':
839 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700840 parser.add_argument(
841 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
842 parser.add_argument(
843 '--run-bootstrap',
844 help='Run bootstrap code to verify build env is ok to build.',
845 default=False,
846 action='store_true')
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700847 parser.add_argument(
848 '--print-env', help='Print environment variables used for build.', default=False, action='store_true')
Andre Braga17ff7bc2022-06-24 22:43:18 +0000849 parser.add_argument('--no-clang', help='Don\'t use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800850 parser.add_argument(
851 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000852 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800853 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
854 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000855 parser.add_argument('--target', help='Run specific build target')
856 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700857 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000858 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700859 parser.add_argument(
860 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000861 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700862 parser.add_argument('--rust-debug', help='Build Rust code as debug.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000863 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700864
865 # Make sure we get absolute path + expanded path for bootstrap directory
866 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
867
Andre Bragaaa11e7d2022-08-10 21:46:44 +0000868 # Possible values for machine() come from 'uname -m'
869 # Since this script only runs on Linux, x86_64 machines must have this value
870 if platform.machine() != 'x86_64':
871 raise Exception("Only x86_64 machines are currently supported by this build script.")
872
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700873 if args.run_bootstrap:
874 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
875 bootstrap.bootstrap()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700876 elif args.print_env:
877 build = HostBuild(args)
878 build.print_env()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700879 else:
880 build = HostBuild(args)
881 build.build()