blob: d7c1f65f406b83812e23c22ebbb8eef6232b65b5 [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
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080070 'hosttools', # Build the host tools (i.e. packetgen)
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080071 'main', # Build the main C++ codebase
72 'prepare', # Prepare the output directory (gn gen + rust setup)
73 'rust', # Build only the rust components + copy artifacts to output dir
74 'test', # Run the unit tests
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -080075 'utils', # Build Floss utils
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',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000110 'libabsl-dev',
Martin Brabhamba22adf2022-02-04 19:51:21 +0000111 'libc++abi-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700112 'libc++-dev',
113 'libdbus-1-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000114 'libdouble-conversion-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700115 'libevent-dev',
116 'libevent-dev',
117 'libflatbuffers-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700118 'libgl1-mesa-dev',
119 'libglib2.0-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000120 'libgtest-dev',
121 'libgmock-dev',
Yun-Hao Chung69db9ce2023-09-08 11:50:50 +0000122 'liblc3-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700123 'liblz4-tool',
124 'libncurses5',
125 'libnss3-dev',
126 'libprotobuf-dev',
127 'libre2-9',
Martin Brabham996f1502022-02-14 17:39:23 +0000128 'libre2-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700129 'libssl-dev',
130 'libtinyxml2-dev',
131 'libx11-dev',
132 'libxml2-utils',
133 'ninja-build',
134 'openssl',
135 'protobuf-compiler',
136 'unzip',
137 'x11proto-core-dev',
138 'xsltproc',
139 'zip',
140 'zlib1g-dev',
141]
142
143# List of cargo packages required for linux build
Abhishek Pandit-Subedif6064252023-06-02 08:31:17 +0000144REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd', 'pdl-compiler']
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700145
146APT_PKG_LIST = ['apt', '-qq', 'list']
147CARGO_PKG_LIST = ['cargo', 'install', '--list']
148
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000149
150class UseFlags():
151
152 def __init__(self, use_flags):
153 """ Construct the use flags.
154
155 Args:
156 use_flags: List of use flags parsed from the command.
157 """
158 self.flags = {}
159
160 # Import use flags required by common-mk
161 for use in COMMON_MK_USES:
162 self.set_flag(use, False)
163
164 # Set our defaults
165 for use, value in USE_DEFAULTS.items():
166 self.set_flag(use, value)
167
168 # Set use flags - value is set to True unless the use starts with -
169 # All given use flags always override the defaults
170 for use in use_flags:
171 value = not use.startswith('-')
172 self.set_flag(use, value)
173
174 def set_flag(self, key, value=True):
175 setattr(self, key, value)
176 self.flags[key] = value
177
178
179class HostBuild():
180
181 def __init__(self, args):
182 """ Construct the builder.
183
184 Args:
185 args: Parsed arguments from ArgumentParser
186 """
187 self.args = args
188
189 # Set jobs to number of cpus unless explicitly set
190 self.jobs = self.args.jobs
191 if not self.jobs:
192 self.jobs = multiprocessing.cpu_count()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700193 sys.stderr.write("Number of jobs = {}\n".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000194
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700195 # Normalize bootstrap dir and make sure it exists
196 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
197 os.makedirs(self.bootstrap_dir, exist_ok=True)
198
199 # Output and platform directories are based on bootstrap
200 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
201 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Michael Sun4940f2b2022-09-15 16:08:24 -0700202 self.bt_dir = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000203 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000204 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800205 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000206
Michael Sun4940f2b2022-09-15 16:08:24 -0700207 assert os.path.samefile(self.bt_dir,
208 os.path.dirname(__file__)), "Please rerun bootstrap for the current project!"
209
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000210 # If default target isn't set, build everything
211 self.target = 'all'
212 if hasattr(self.args, 'target') and self.args.target:
213 self.target = self.args.target
214
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000215 target_use = self.args.use if self.args.use else []
216
217 # Unless set, always build test code
218 if not self.args.notest:
219 target_use.append('test')
220
221 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000222
223 # Validate platform directory
224 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
225 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
226
227 # Make sure output directory exists (or create it)
228 os.makedirs(self.output_dir, exist_ok=True)
229
230 # Set some default attributes
231 self.libbase_ver = None
232
233 self.configure_environ()
234
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000235 def _generate_rustflags(self):
236 """ Rustflags to include for the build.
237 """
238 rust_flags = [
239 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700240 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000241 '-C',
242 'link-arg=-Wl,--allow-multiple-definition',
Sonny Sasaka87bacb62022-04-29 10:34:29 -0700243 # exclude uninteresting warnings
244 '-A improper_ctypes_definitions -A improper_ctypes -A unknown_lints',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000245 ]
246
247 return ' '.join(rust_flags)
248
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000249 def configure_environ(self):
250 """ Configure environment variables for GN and Cargo.
251 """
252 self.env = os.environ.copy()
253
254 # Make sure cargo home dir exists and has a bin directory
255 cargo_home = os.path.join(self.output_dir, 'cargo_home')
256 os.makedirs(cargo_home, exist_ok=True)
257 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
258
259 # Configure Rust env variables
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700260 self.custom_env = {}
261 self.custom_env['CARGO_TARGET_DIR'] = self.output_dir
262 self.custom_env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
263 self.custom_env['RUSTFLAGS'] = self._generate_rustflags()
264 self.custom_env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
265 self.custom_env['CROS_SYSTEM_API_ROOT'] = os.path.join(self.platform_dir, 'system_api')
266 self.custom_env['CXX_OUTDIR'] = self._gn_default_output()
267 self.env.update(self.custom_env)
268
269 def print_env(self):
270 """ Print the custom environment variables that are used in build.
271
272 Useful so that external tools can mimic the environment to be the same
273 as build.py, e.g. rust-analyzer.
274 """
275 for k, v in self.custom_env.items():
276 print("export {}='{}'".format(k, v))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000277
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000278 def run_command(self, target, args, cwd=None, env=None):
279 """ Run command and stream the output.
280 """
281 # Set some defaults
282 if not cwd:
283 cwd = self.platform_dir
284 if not env:
285 env = self.env
286
287 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
288 with open(log_file, 'wb') as lf:
289 rc = 0
290 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
291 while True:
292 line = process.stdout.readline()
293 print(line.decode('utf-8'), end="")
294 lf.write(line)
295 if not line:
296 rc = process.poll()
297 if rc is not None:
298 break
299
300 time.sleep(0.1)
301
302 if rc != 0:
303 raise Exception("Return code is {}".format(rc))
304
305 def _get_basever(self):
306 if self.libbase_ver:
307 return self.libbase_ver
308
309 self.libbase_ver = os.environ.get('BASE_VER', '')
310 if not self.libbase_ver:
311 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
312 try:
313 with open(base_file, 'r') as f:
314 self.libbase_ver = f.read().strip('\n')
315 except:
316 self.libbase_ver = 'NOT-INSTALLED'
317
318 return self.libbase_ver
319
320 def _gn_default_output(self):
321 return os.path.join(self.output_dir, 'out/Default')
322
323 def _gn_configure(self):
324 """ Configure all required parameters for platform2.
325
326 Mostly copied from //common-mk/platform2.py
327 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700328 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000329
330 def to_gn_string(s):
331 return '"%s"' % s.replace('"', '\\"')
332
333 def to_gn_list(strs):
334 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
335
336 def to_gn_args_args(gn_args):
337 for k, v in gn_args.items():
338 if isinstance(v, bool):
339 v = str(v).lower()
340 elif isinstance(v, list):
341 v = to_gn_list(v)
342 elif isinstance(v, six.string_types):
343 v = to_gn_string(v)
344 else:
345 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
346 yield '%s=%s' % (k.replace('-', '_'), v)
347
348 gn_args = {
349 'platform_subdir': 'bt',
350 'cc': 'clang' if clang else 'gcc',
351 'cxx': 'clang++' if clang else 'g++',
352 'ar': 'llvm-ar' if clang else 'ar',
353 'pkg-config': 'pkg-config',
354 'clang_cc': clang,
355 'clang_cxx': clang,
356 'OS': 'linux',
357 'sysroot': self.sysroot,
358 'libdir': os.path.join(self.sysroot, self.libdir),
359 'build_root': self.output_dir,
360 'platform2_root': self.platform_dir,
361 'libbase_ver': self._get_basever(),
362 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
363 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800364 'external_cxxflags': ["-DNDEBUG"],
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000365 'enable_werror': False,
366 }
367
368 if clang:
369 # Make sure to mark the clang use flag as true
370 self.use.set_flag('clang', True)
371 gn_args['external_cxxflags'] += ['-I/usr/include/']
372
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000373 gn_args_args = list(to_gn_args_args(gn_args))
374 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
375 gn_args_args += ['use={%s}' % (' '.join(use_args))]
376
377 gn_args = [
378 'gn',
379 'gen',
380 ]
381
382 if self.args.verbose:
383 gn_args.append('-v')
384
385 gn_args += [
386 '--root=%s' % self.platform_dir,
387 '--args=%s' % ' '.join(gn_args_args),
388 self._gn_default_output(),
389 ]
390
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700391 if 'PKG_CONFIG_PATH' in self.env:
392 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000393
394 self.run_command('configure', gn_args)
395
396 def _gn_build(self, target):
397 """ Generate the ninja command for the target and run it.
398 """
399 args = ['%s:%s' % ('bt', target)]
400 ninja_args = ['ninja', '-C', self._gn_default_output()]
401 if self.jobs:
402 ninja_args += ['-j', str(self.jobs)]
403 ninja_args += args
404
405 if self.args.verbose:
406 ninja_args.append('-v')
407
408 self.run_command('build', ninja_args)
409
410 def _rust_configure(self):
411 """ Generate config file at cargo_home so we use vendored crates.
412 """
413 template = """
414 [source.systembt]
415 directory = "{}/external/rust/vendor"
416
417 [source.crates-io]
418 replace-with = "systembt"
419 local-registry = "/nonexistent"
420 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700421
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700422 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700423 contents = template.format(self.platform_dir)
424 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
425 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000426
427 def _rust_build(self):
428 """ Run `cargo build` from platform2/bt directory.
429 """
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700430 cmd = ['cargo', 'build']
431 if not self.args.rust_debug:
432 cmd.append('--release')
433
434 self.run_command('rust', cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000435
436 def _target_prepare(self):
437 """ Target to prepare the output directory for building.
438
439 This runs gn gen to generate all rquired files and set up the Rust
440 config properly. This will be run
441 """
442 self._gn_configure()
443 self._rust_configure()
444
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800445 def _target_hosttools(self):
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000446 """ Build the tools target in an already prepared environment.
447 """
448 self._gn_build('tools')
449
450 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800451 shutil.copy(os.path.join(self._gn_default_output(), 'bluetooth_packetgen'),
452 os.path.join(self.env['CARGO_HOME'], 'bin'))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000453
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800454 def _target_docs(self):
455 """Build the Rust docs."""
456 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
457
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000458 def _target_rust(self):
459 """ Build rust artifacts in an already prepared environment.
460 """
461 self._rust_build()
462
463 def _target_main(self):
464 """ Build the main GN artifacts in an already prepared environment.
465 """
466 self._gn_build('all')
467
468 def _target_test(self):
469 """ Runs the host tests.
470 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000471 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800472 rust_test_cmd = ['cargo', 'test']
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700473 if not self.args.rust_debug:
474 rust_test_cmd.append('--release')
475
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800476 if self.args.test_name:
Abhishek Pandit-Subedib04e6a92022-09-08 19:19:40 -0700477 rust_test_cmd = rust_test_cmd + [self.args.test_name, "--", "--test-threads=1", "--nocapture"]
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800478
479 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 +0000480
481 # Host tests second based on host test list
482 for t in HOST_TESTS:
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800483 self.run_command('test', [os.path.join(self.output_dir, 'out/Default', t)],
484 cwd=os.path.join(self.output_dir),
485 env=self.env)
486
487 def _target_utils(self):
488 """ Builds the utility applications.
489 """
490 rust_targets = ['hcidoc']
491
492 # Build targets
493 for target in rust_targets:
494 self.run_command('utils', ['cargo', 'build', '-p', target],
495 cwd=os.path.join(self.platform_dir, 'bt'),
496 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000497
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800498 def _target_install(self):
499 """ Installs files required to run Floss to install directory.
500 """
501 # First make the install directory
502 prefix = self.install_dir
503 os.makedirs(prefix, exist_ok=True)
504
505 # Next save the cwd and change to install directory
506 last_cwd = os.getcwd()
507 os.chdir(prefix)
508
509 bindir = os.path.join(self.output_dir, 'debug')
510 srcdir = os.path.dirname(__file__)
511
512 install_map = [
513 {
514 'src': os.path.join(bindir, 'btadapterd'),
515 'dst': 'usr/libexec/bluetooth/btadapterd',
516 'strip': True
517 },
518 {
519 'src': os.path.join(bindir, 'btmanagerd'),
520 'dst': 'usr/libexec/bluetooth/btmanagerd',
521 'strip': True
522 },
523 {
524 'src': os.path.join(bindir, 'btclient'),
525 'dst': 'usr/local/bin/btclient',
526 'strip': True
527 },
528 ]
529
530 for v in install_map:
531 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
532 dst = os.path.join(prefix, partial_dst)
533
534 # Create dst directory first and copy file there
535 os.makedirs(os.path.dirname(dst), exist_ok=True)
536 print('Installing {}'.format(dst))
537 shutil.copy(src, dst)
538
539 # Binary should be marked for strip and no-strip option shouldn't be
540 # set. No-strip is useful while debugging.
541 if strip and not self.args.no_strip:
542 self.run_command('install', ['llvm-strip', dst])
543
544 # Put all files into a tar.gz for easier installation
545 tar_location = os.path.join(prefix, 'floss.tar.gz')
546 with tarfile.open(tar_location, 'w:gz') as tar:
547 for v in install_map:
548 tar.add(v['dst'])
549
550 print('Tarball created at {}'.format(tar_location))
551
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000552 def _target_clean(self):
553 """ Delete the output directory entirely.
554 """
555 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800556
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700557 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700558 cargo_lock_files = [
559 os.path.join(self.platform_dir, 'bt', 'Cargo.lock'),
Abhishek Pandit-Subedi16617282022-09-28 10:11:10 -0700560 ]
561 for lock_file in cargo_lock_files:
562 try:
563 os.remove(lock_file)
564 print('Removed {}'.format(lock_file))
565 except FileNotFoundError:
566 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000567
568 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800569 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000570 """
571 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800572 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000573 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700574 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000575
576 def build(self):
577 """ Builds according to self.target
578 """
579 print('Building target ', self.target)
580
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800581 # Validate that the target is valid
582 if self.target not in VALID_TARGETS:
howardchung63187b62022-08-16 17:06:17 +0800583 print('Target {} is not valid. Must be in {}'.format(self.target, VALID_TARGETS))
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800584 return
585
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000586 if self.target == 'prepare':
587 self._target_prepare()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800588 elif self.target == 'hosttools':
589 self._target_hosttools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000590 elif self.target == 'rust':
591 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800592 elif self.target == 'docs':
593 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000594 elif self.target == 'main':
595 self._target_main()
596 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000597 self._target_test()
598 elif self.target == 'clean':
599 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800600 elif self.target == 'install':
601 self._target_install()
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800602 elif self.target == 'utils':
603 self._target_utils()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000604 elif self.target == 'all':
605 self._target_all()
606
607
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700608class Bootstrap():
609
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800610 def __init__(self, base_dir, bt_dir, partial_staging):
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700611 """ Construct bootstrapper.
612
613 Args:
614 base_dir: Where to stage everything.
615 bt_dir: Where bluetooth source is kept (will be symlinked)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800616 partial_staging: Whether to do a partial clone for staging.
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700617 """
618 self.base_dir = os.path.abspath(base_dir)
619 self.bt_dir = os.path.abspath(bt_dir)
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800620 self.partial_staging = partial_staging
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700621
622 # Create base directory if it doesn't already exist
623 os.makedirs(self.base_dir, exist_ok=True)
624
625 if not os.path.isdir(self.bt_dir):
626 raise Exception('{} is not a valid directory'.format(self.bt_dir))
627
628 self.git_dir = os.path.join(self.base_dir, 'repos')
629 self.staging_dir = os.path.join(self.base_dir, 'staging')
630 self.output_dir = os.path.join(self.base_dir, 'output')
631 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
632
633 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
634
635 def _update_platform2(self):
636 """Updates repositories used for build."""
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800637 for project in BOOTSTRAP_GIT_REPOS.keys():
638 cwd = os.path.join(self.git_dir, project)
639 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
640
641 # Update to required commit when necessary or pull the latest code.
642 if commit:
643 head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).strip()
644 if head != commit:
645 subprocess.check_call(['git', 'fetch'], cwd=cwd)
646 subprocess.check_call(['git', 'checkout', commit], cwd=cwd)
647 else:
648 subprocess.check_call(['git', 'pull'], cwd=cwd)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700649
650 def _setup_platform2(self):
651 """ Set up platform2.
652
653 This will check out all the git repos and symlink everything correctly.
654 """
655
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800656 # Create all directories we will need to use
657 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
658 os.makedirs(dirpath, exist_ok=True)
659
660 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700661 if os.path.isfile(self.dir_setup_complete):
662 print('{} already set-up. Updating instead.'.format(self.base_dir))
663 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800664 else:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800665 clone_options = []
666 # When doing a partial staging, we use a treeless clone which allows
667 # us to access all commits but downloads things on demand. This
668 # helps speed up the initial git clone during builds but isn't good
669 # for long-term development.
670 if self.partial_staging:
671 clone_options = ['--filter=tree:0']
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800672 # Check out all repos in git directory
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800673 for project in BOOTSTRAP_GIT_REPOS.keys():
674 (repo, commit) = BOOTSTRAP_GIT_REPOS[project]
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800675 subprocess.check_call(['git', 'clone', repo, project] + clone_options, cwd=self.git_dir)
Abhishek Pandit-Subedi461eeab2023-01-09 10:20:17 -0800676 # Pin to commit.
677 if commit:
678 subprocess.check_call(['git', 'checkout', commit], cwd=os.path.join(self.git_dir, project))
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700679
680 # Symlink things
681 symlinks = [
682 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
Sonny Sasakaae9f6522022-03-28 10:31:34 -0700683 (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 -0700684 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
685 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
686 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
687 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
688 ]
689
690 # Create symlinks
691 for pairs in symlinks:
692 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000693 try:
694 os.unlink(dst)
695 except Exception as e:
696 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700697 os.symlink(src, dst)
698
699 # Write to setup complete file so we don't repeat this step
700 with open(self.dir_setup_complete, 'w') as f:
701 f.write('Setup complete.')
702
703 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
704 """ Pretty print an install command.
705
706 Args:
707 install_cmd: Prefixed install command.
708 packages: Enumerate packages and append them to install command.
709 line_limit: Number of characters per line.
710
711 Return:
712 Array of lines to join and print.
713 """
714 install = [install_cmd]
715 line = ' '
716 # Remainder needed = space + len(pkg) + space + \
717 # Assuming 80 character lines, that's 80 - 3 = 77
718 line_limit = line_limit - 3
719 for pkg in packages:
720 if len(line) + len(pkg) < line_limit:
721 line = '{}{} '.format(line, pkg)
722 else:
723 install.append(line)
724 line = ' {} '.format(pkg)
725
726 if len(line) > 0:
727 install.append(line)
728
729 return install
730
731 def _check_package_installed(self, package, cmd, predicate):
732 """Check that the given package is installed.
733
734 Args:
735 package: Check that this package is installed.
736 cmd: Command prefix to check if installed (package appended to end)
737 predicate: Function/lambda to check if package is installed based
738 on output. Takes string output and returns boolean.
739
740 Return:
741 True if package is installed.
742 """
743 try:
744 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
745 is_installed = predicate(output.decode('utf-8'))
746 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
747
748 return is_installed
749 except Exception as e:
750 print(e)
751 return False
752
753 def _get_command_output(self, cmd):
754 """Runs the command and gets the output.
755
756 Args:
757 cmd: Command to run.
758
759 Return:
760 Tuple (Success, Output). Success represents if the command ran ok.
761 """
762 try:
763 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
764 return (True, output.decode('utf-8').split('\n'))
765 except Exception as e:
766 print(e)
767 return (False, "")
768
769 def _print_missing_packages(self):
770 """Print any missing packages found via apt.
771
772 This will find any missing packages necessary for build using apt and
773 print it out as an apt-get install printf.
774 """
775 print('Checking for any missing packages...')
776
777 (success, output) = self._get_command_output(APT_PKG_LIST)
778 if not success:
779 raise Exception("Could not query apt for packages.")
780
781 packages_installed = {}
782 for line in output:
783 if 'installed' in line:
784 split = line.split('/', 2)
785 packages_installed[split[0]] = True
786
787 need_packages = []
788 for pkg in REQUIRED_APT_PACKAGES:
789 if pkg not in packages_installed:
790 need_packages.append(pkg)
791
792 # No packages need to be installed
793 if len(need_packages) == 0:
794 print('+ All required packages are installed')
795 return
796
797 install = self._pretty_print_install('sudo apt-get install', need_packages)
798
799 # Print all lines so they can be run in cmdline
800 print('Missing system packages. Run the following command: ')
801 print(' \\\n'.join(install))
802
803 def _print_missing_rust_packages(self):
804 """Print any missing packages found via cargo.
805
806 This will find any missing packages necessary for build using cargo and
807 print it out as a cargo-install printf.
808 """
809 print('Checking for any missing cargo packages...')
810
811 (success, output) = self._get_command_output(CARGO_PKG_LIST)
812 if not success:
813 raise Exception("Could not query cargo for packages.")
814
815 packages_installed = {}
816 for line in output:
817 # Cargo installed packages have this format
818 # <crate name> <version>:
819 # <binary name>
820 # We only care about the crates themselves
821 if ':' not in line:
822 continue
823
824 split = line.split(' ', 2)
825 packages_installed[split[0]] = True
826
827 need_packages = []
828 for pkg in REQUIRED_CARGO_PACKAGES:
829 if pkg not in packages_installed:
830 need_packages.append(pkg)
831
832 # No packages to be installed
833 if len(need_packages) == 0:
834 print('+ All required cargo packages are installed')
835 return
836
837 install = self._pretty_print_install('cargo install', need_packages)
838 print('Missing cargo packages. Run the following command: ')
839 print(' \\\n'.join(install))
840
841 def bootstrap(self):
842 """ Bootstrap the Linux build."""
843 self._setup_platform2()
844 self._print_missing_packages()
845 self._print_missing_rust_packages()
846
847
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000848if __name__ == '__main__':
849 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800850 parser.add_argument('--bootstrap-dir',
851 help='Directory to run bootstrap on (or was previously run on).',
852 default="~/.floss")
853 parser.add_argument('--run-bootstrap',
854 help='Run bootstrap code to verify build env is ok to build.',
855 default=False,
856 action='store_true')
857 parser.add_argument('--print-env',
858 help='Print environment variables used for build.',
859 default=False,
860 action='store_true')
Andre Braga17ff7bc2022-06-24 22:43:18 +0000861 parser.add_argument('--no-clang', help='Don\'t use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800862 parser.add_argument('--no-strip',
863 help='Skip stripping binaries during install.',
864 default=False,
865 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000866 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800867 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
868 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000869 parser.add_argument('--target', help='Run specific build target')
870 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700871 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000872 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi2a9a9162022-12-16 16:09:01 -0800873 parser.add_argument('--no-vendored-rust',
874 help='Do not use vendored rust crates',
875 default=False,
876 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000877 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedi30e57302022-10-17 13:41:09 -0700878 parser.add_argument('--rust-debug', help='Build Rust code as debug.', default=False, action='store_true')
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800879 parser.add_argument(
880 '--partial-staging',
881 help='Bootstrap git repositories with partial clones. Use to speed up initial git clone for automated builds.',
882 default=False,
883 action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000884 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700885
886 # Make sure we get absolute path + expanded path for bootstrap directory
887 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
888
Andre Bragaaa11e7d2022-08-10 21:46:44 +0000889 # Possible values for machine() come from 'uname -m'
890 # Since this script only runs on Linux, x86_64 machines must have this value
891 if platform.machine() != 'x86_64':
892 raise Exception("Only x86_64 machines are currently supported by this build script.")
893
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700894 if args.run_bootstrap:
Abhishek Pandit-Subedi4a56c042023-01-23 13:52:52 -0800895 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__), args.partial_staging)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700896 bootstrap.bootstrap()
Sonny Sasaka5b7e6f22022-09-06 11:31:53 -0700897 elif args.print_env:
898 build = HostBuild(args)
899 build.print_env()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700900 else:
901 build = HostBuild(args)
902 build.build()