blob: b93935a7811caab257d3dfb136e9b3acd77d9e97 [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
33import shutil
34import six
35import subprocess
36import sys
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -080037import tarfile
Chris Mantone7ad6332021-09-30 22:55:39 -070038import time
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000039
40# Use flags required by common-mk (find -type f | grep -nE 'use[.]' {})
41COMMON_MK_USES = [
42 'asan',
43 'coverage',
44 'cros_host',
45 'fuzzer',
46 'fuzzer',
47 'msan',
48 'profiling',
49 'tcmalloc',
50 'test',
51 'ubsan',
52]
53
54# Default use flags.
55USE_DEFAULTS = {
56 'android': False,
57 'bt_nonstandard_codecs': False,
58 'test': False,
59}
60
61VALID_TARGETS = [
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000062 'all', # All targets except test and clean
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -080063 'clean', # Clean up output directory
64 'docs', # Build Rust docs
65 'main', # Build the main C++ codebase
66 'prepare', # Prepare the output directory (gn gen + rust setup)
67 'rust', # Build only the rust components + copy artifacts to output dir
68 'test', # Run the unit tests
69 'tools', # Build the host tools (i.e. packetgen)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +000070]
71
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000072# TODO(b/190750167) - Host tests are disabled until we are full bazel build
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000073HOST_TESTS = [
Abhishek Pandit-Subedi70e43042021-06-10 21:16:52 +000074 # 'bluetooth_test_common',
75 # 'bluetoothtbd_test',
76 # 'net_test_avrcp',
77 # 'net_test_btcore',
78 # 'net_test_types',
79 # 'net_test_btm_iso',
80 # 'net_test_btpackets',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +000081]
82
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -070083BOOTSTRAP_GIT_REPOS = {
84 'platform2': 'https://chromium.googlesource.com/chromiumos/platform2',
85 'rust_crates': 'https://chromium.googlesource.com/chromiumos/third_party/rust_crates',
86 'proto_logging': 'https://android.googlesource.com/platform/frameworks/proto_logging'
87}
88
89# List of packages required for linux build
90REQUIRED_APT_PACKAGES = [
91 'bison',
92 'build-essential',
93 'curl',
94 'debmake',
95 'flatbuffers-compiler',
96 'flex',
97 'g++-multilib',
98 'gcc-multilib',
99 'generate-ninja',
100 'gnupg',
101 'gperf',
Martin Brabhamba22adf2022-02-04 19:51:21 +0000102 'libc++abi-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700103 'libc++-dev',
104 'libdbus-1-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000105 'libdouble-conversion-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700106 'libevent-dev',
107 'libevent-dev',
108 'libflatbuffers-dev',
109 'libflatbuffers1',
110 'libgl1-mesa-dev',
111 'libglib2.0-dev',
Martin Brabham996f1502022-02-14 17:39:23 +0000112 'libgtest-dev',
113 'libgmock-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700114 'liblz4-tool',
115 'libncurses5',
116 'libnss3-dev',
117 'libprotobuf-dev',
118 'libre2-9',
Martin Brabham996f1502022-02-14 17:39:23 +0000119 'libre2-dev',
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700120 'libssl-dev',
121 'libtinyxml2-dev',
122 'libx11-dev',
123 'libxml2-utils',
124 'ninja-build',
125 'openssl',
126 'protobuf-compiler',
127 'unzip',
128 'x11proto-core-dev',
129 'xsltproc',
130 'zip',
131 'zlib1g-dev',
132]
133
134# List of cargo packages required for linux build
135REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd']
136
137APT_PKG_LIST = ['apt', '-qq', 'list']
138CARGO_PKG_LIST = ['cargo', 'install', '--list']
139
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000140
141class UseFlags():
142
143 def __init__(self, use_flags):
144 """ Construct the use flags.
145
146 Args:
147 use_flags: List of use flags parsed from the command.
148 """
149 self.flags = {}
150
151 # Import use flags required by common-mk
152 for use in COMMON_MK_USES:
153 self.set_flag(use, False)
154
155 # Set our defaults
156 for use, value in USE_DEFAULTS.items():
157 self.set_flag(use, value)
158
159 # Set use flags - value is set to True unless the use starts with -
160 # All given use flags always override the defaults
161 for use in use_flags:
162 value = not use.startswith('-')
163 self.set_flag(use, value)
164
165 def set_flag(self, key, value=True):
166 setattr(self, key, value)
167 self.flags[key] = value
168
169
170class HostBuild():
171
172 def __init__(self, args):
173 """ Construct the builder.
174
175 Args:
176 args: Parsed arguments from ArgumentParser
177 """
178 self.args = args
179
180 # Set jobs to number of cpus unless explicitly set
181 self.jobs = self.args.jobs
182 if not self.jobs:
183 self.jobs = multiprocessing.cpu_count()
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000184 print("Number of jobs = {}".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000185
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700186 # Normalize bootstrap dir and make sure it exists
187 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
188 os.makedirs(self.bootstrap_dir, exist_ok=True)
189
190 # Output and platform directories are based on bootstrap
191 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
192 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000193 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000194 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800195 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000196
197 # If default target isn't set, build everything
198 self.target = 'all'
199 if hasattr(self.args, 'target') and self.args.target:
200 self.target = self.args.target
201
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000202 target_use = self.args.use if self.args.use else []
203
204 # Unless set, always build test code
205 if not self.args.notest:
206 target_use.append('test')
207
208 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000209
210 # Validate platform directory
211 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
212 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
213
214 # Make sure output directory exists (or create it)
215 os.makedirs(self.output_dir, exist_ok=True)
216
217 # Set some default attributes
218 self.libbase_ver = None
219
220 self.configure_environ()
221
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000222 def _generate_rustflags(self):
223 """ Rustflags to include for the build.
224 """
225 rust_flags = [
226 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700227 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000228 '-C',
229 'link-arg=-Wl,--allow-multiple-definition',
Sonny Sasaka87bacb62022-04-29 10:34:29 -0700230 # exclude uninteresting warnings
231 '-A improper_ctypes_definitions -A improper_ctypes -A unknown_lints',
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000232 ]
233
234 return ' '.join(rust_flags)
235
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000236 def configure_environ(self):
237 """ Configure environment variables for GN and Cargo.
238 """
239 self.env = os.environ.copy()
240
241 # Make sure cargo home dir exists and has a bin directory
242 cargo_home = os.path.join(self.output_dir, 'cargo_home')
243 os.makedirs(cargo_home, exist_ok=True)
244 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
245
246 # Configure Rust env variables
247 self.env['CARGO_TARGET_DIR'] = self.output_dir
248 self.env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000249 self.env['RUSTFLAGS'] = self._generate_rustflags()
Abhishek Pandit-Subedi1927afa2021-04-28 21:16:18 -0700250 self.env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000251
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000252 def run_command(self, target, args, cwd=None, env=None):
253 """ Run command and stream the output.
254 """
255 # Set some defaults
256 if not cwd:
257 cwd = self.platform_dir
258 if not env:
259 env = self.env
260
261 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
262 with open(log_file, 'wb') as lf:
263 rc = 0
264 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
265 while True:
266 line = process.stdout.readline()
267 print(line.decode('utf-8'), end="")
268 lf.write(line)
269 if not line:
270 rc = process.poll()
271 if rc is not None:
272 break
273
274 time.sleep(0.1)
275
276 if rc != 0:
277 raise Exception("Return code is {}".format(rc))
278
279 def _get_basever(self):
280 if self.libbase_ver:
281 return self.libbase_ver
282
283 self.libbase_ver = os.environ.get('BASE_VER', '')
284 if not self.libbase_ver:
285 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
286 try:
287 with open(base_file, 'r') as f:
288 self.libbase_ver = f.read().strip('\n')
289 except:
290 self.libbase_ver = 'NOT-INSTALLED'
291
292 return self.libbase_ver
293
294 def _gn_default_output(self):
295 return os.path.join(self.output_dir, 'out/Default')
296
297 def _gn_configure(self):
298 """ Configure all required parameters for platform2.
299
300 Mostly copied from //common-mk/platform2.py
301 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700302 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000303
304 def to_gn_string(s):
305 return '"%s"' % s.replace('"', '\\"')
306
307 def to_gn_list(strs):
308 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
309
310 def to_gn_args_args(gn_args):
311 for k, v in gn_args.items():
312 if isinstance(v, bool):
313 v = str(v).lower()
314 elif isinstance(v, list):
315 v = to_gn_list(v)
316 elif isinstance(v, six.string_types):
317 v = to_gn_string(v)
318 else:
319 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
320 yield '%s=%s' % (k.replace('-', '_'), v)
321
322 gn_args = {
323 'platform_subdir': 'bt',
324 'cc': 'clang' if clang else 'gcc',
325 'cxx': 'clang++' if clang else 'g++',
326 'ar': 'llvm-ar' if clang else 'ar',
327 'pkg-config': 'pkg-config',
328 'clang_cc': clang,
329 'clang_cxx': clang,
330 'OS': 'linux',
331 'sysroot': self.sysroot,
332 'libdir': os.path.join(self.sysroot, self.libdir),
333 'build_root': self.output_dir,
334 'platform2_root': self.platform_dir,
335 'libbase_ver': self._get_basever(),
336 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
337 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800338 'external_cxxflags': ["-DNDEBUG"],
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000339 'enable_werror': False,
340 }
341
342 if clang:
343 # Make sure to mark the clang use flag as true
344 self.use.set_flag('clang', True)
345 gn_args['external_cxxflags'] += ['-I/usr/include/']
346
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000347 gn_args_args = list(to_gn_args_args(gn_args))
348 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
349 gn_args_args += ['use={%s}' % (' '.join(use_args))]
350
351 gn_args = [
352 'gn',
353 'gen',
354 ]
355
356 if self.args.verbose:
357 gn_args.append('-v')
358
359 gn_args += [
360 '--root=%s' % self.platform_dir,
361 '--args=%s' % ' '.join(gn_args_args),
362 self._gn_default_output(),
363 ]
364
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700365 if 'PKG_CONFIG_PATH' in self.env:
366 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000367
368 self.run_command('configure', gn_args)
369
370 def _gn_build(self, target):
371 """ Generate the ninja command for the target and run it.
372 """
373 args = ['%s:%s' % ('bt', target)]
374 ninja_args = ['ninja', '-C', self._gn_default_output()]
375 if self.jobs:
376 ninja_args += ['-j', str(self.jobs)]
377 ninja_args += args
378
379 if self.args.verbose:
380 ninja_args.append('-v')
381
382 self.run_command('build', ninja_args)
383
384 def _rust_configure(self):
385 """ Generate config file at cargo_home so we use vendored crates.
386 """
387 template = """
388 [source.systembt]
389 directory = "{}/external/rust/vendor"
390
391 [source.crates-io]
392 replace-with = "systembt"
393 local-registry = "/nonexistent"
394 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700395
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700396 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700397 contents = template.format(self.platform_dir)
398 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
399 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000400
401 def _rust_build(self):
402 """ Run `cargo build` from platform2/bt directory.
403 """
404 self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
405
406 def _target_prepare(self):
407 """ Target to prepare the output directory for building.
408
409 This runs gn gen to generate all rquired files and set up the Rust
410 config properly. This will be run
411 """
412 self._gn_configure()
413 self._rust_configure()
414
415 def _target_tools(self):
416 """ Build the tools target in an already prepared environment.
417 """
418 self._gn_build('tools')
419
420 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
421 shutil.copy(
422 os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
423
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800424 def _target_docs(self):
425 """Build the Rust docs."""
426 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
427
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000428 def _target_rust(self):
429 """ Build rust artifacts in an already prepared environment.
430 """
431 self._rust_build()
432
433 def _target_main(self):
434 """ Build the main GN artifacts in an already prepared environment.
435 """
436 self._gn_build('all')
437
438 def _target_test(self):
439 """ Runs the host tests.
440 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000441 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800442 rust_test_cmd = ['cargo', 'test']
443 if self.args.test_name:
444 rust_test_cmd = rust_test_cmd + [self.args.test_name]
445
446 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 +0000447
448 # Host tests second based on host test list
449 for t in HOST_TESTS:
450 self.run_command(
451 'test', [os.path.join(self.output_dir, 'out/Default', t)],
452 cwd=os.path.join(self.output_dir),
453 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000454
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800455 def _target_install(self):
456 """ Installs files required to run Floss to install directory.
457 """
458 # First make the install directory
459 prefix = self.install_dir
460 os.makedirs(prefix, exist_ok=True)
461
462 # Next save the cwd and change to install directory
463 last_cwd = os.getcwd()
464 os.chdir(prefix)
465
466 bindir = os.path.join(self.output_dir, 'debug')
467 srcdir = os.path.dirname(__file__)
468
469 install_map = [
470 {
471 'src': os.path.join(bindir, 'btadapterd'),
472 'dst': 'usr/libexec/bluetooth/btadapterd',
473 'strip': True
474 },
475 {
476 'src': os.path.join(bindir, 'btmanagerd'),
477 'dst': 'usr/libexec/bluetooth/btmanagerd',
478 'strip': True
479 },
480 {
481 'src': os.path.join(bindir, 'btclient'),
482 'dst': 'usr/local/bin/btclient',
483 'strip': True
484 },
485 ]
486
487 for v in install_map:
488 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
489 dst = os.path.join(prefix, partial_dst)
490
491 # Create dst directory first and copy file there
492 os.makedirs(os.path.dirname(dst), exist_ok=True)
493 print('Installing {}'.format(dst))
494 shutil.copy(src, dst)
495
496 # Binary should be marked for strip and no-strip option shouldn't be
497 # set. No-strip is useful while debugging.
498 if strip and not self.args.no_strip:
499 self.run_command('install', ['llvm-strip', dst])
500
501 # Put all files into a tar.gz for easier installation
502 tar_location = os.path.join(prefix, 'floss.tar.gz')
503 with tarfile.open(tar_location, 'w:gz') as tar:
504 for v in install_map:
505 tar.add(v['dst'])
506
507 print('Tarball created at {}'.format(tar_location))
508
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000509 def _target_clean(self):
510 """ Delete the output directory entirely.
511 """
512 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800513
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700514 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800515 try:
516 os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock'))
517 except FileNotFoundError:
518 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000519
520 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800521 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000522 """
523 self._target_prepare()
524 self._target_tools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000525 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700526 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000527
528 def build(self):
529 """ Builds according to self.target
530 """
531 print('Building target ', self.target)
532
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800533 # Validate that the target is valid
534 if self.target not in VALID_TARGETS:
535 print('Target {} is not valid. Must be in {}', self.target, VALID_TARGETS)
536 return
537
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000538 if self.target == 'prepare':
539 self._target_prepare()
540 elif self.target == 'tools':
541 self._target_tools()
542 elif self.target == 'rust':
543 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800544 elif self.target == 'docs':
545 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000546 elif self.target == 'main':
547 self._target_main()
548 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000549 self._target_test()
550 elif self.target == 'clean':
551 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800552 elif self.target == 'install':
553 self._target_install()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000554 elif self.target == 'all':
555 self._target_all()
556
557
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700558class Bootstrap():
559
560 def __init__(self, base_dir, bt_dir):
561 """ Construct bootstrapper.
562
563 Args:
564 base_dir: Where to stage everything.
565 bt_dir: Where bluetooth source is kept (will be symlinked)
566 """
567 self.base_dir = os.path.abspath(base_dir)
568 self.bt_dir = os.path.abspath(bt_dir)
569
570 # Create base directory if it doesn't already exist
571 os.makedirs(self.base_dir, exist_ok=True)
572
573 if not os.path.isdir(self.bt_dir):
574 raise Exception('{} is not a valid directory'.format(self.bt_dir))
575
576 self.git_dir = os.path.join(self.base_dir, 'repos')
577 self.staging_dir = os.path.join(self.base_dir, 'staging')
578 self.output_dir = os.path.join(self.base_dir, 'output')
579 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
580
581 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
582
583 def _update_platform2(self):
584 """Updates repositories used for build."""
585 for repo in BOOTSTRAP_GIT_REPOS.keys():
586 cwd = os.path.join(self.git_dir, repo)
587 subprocess.check_call(['git', 'pull'], cwd=cwd)
588
589 def _setup_platform2(self):
590 """ Set up platform2.
591
592 This will check out all the git repos and symlink everything correctly.
593 """
594
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800595 # Create all directories we will need to use
596 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
597 os.makedirs(dirpath, exist_ok=True)
598
599 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700600 if os.path.isfile(self.dir_setup_complete):
601 print('{} already set-up. Updating instead.'.format(self.base_dir))
602 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800603 else:
604 # Check out all repos in git directory
605 for repo in BOOTSTRAP_GIT_REPOS.values():
606 subprocess.check_call(['git', 'clone', repo], cwd=self.git_dir)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700607
608 # Symlink things
609 symlinks = [
610 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
611 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
612 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
613 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
614 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
615 ]
616
617 # Create symlinks
618 for pairs in symlinks:
619 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000620 try:
621 os.unlink(dst)
622 except Exception as e:
623 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700624 os.symlink(src, dst)
625
626 # Write to setup complete file so we don't repeat this step
627 with open(self.dir_setup_complete, 'w') as f:
628 f.write('Setup complete.')
629
630 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
631 """ Pretty print an install command.
632
633 Args:
634 install_cmd: Prefixed install command.
635 packages: Enumerate packages and append them to install command.
636 line_limit: Number of characters per line.
637
638 Return:
639 Array of lines to join and print.
640 """
641 install = [install_cmd]
642 line = ' '
643 # Remainder needed = space + len(pkg) + space + \
644 # Assuming 80 character lines, that's 80 - 3 = 77
645 line_limit = line_limit - 3
646 for pkg in packages:
647 if len(line) + len(pkg) < line_limit:
648 line = '{}{} '.format(line, pkg)
649 else:
650 install.append(line)
651 line = ' {} '.format(pkg)
652
653 if len(line) > 0:
654 install.append(line)
655
656 return install
657
658 def _check_package_installed(self, package, cmd, predicate):
659 """Check that the given package is installed.
660
661 Args:
662 package: Check that this package is installed.
663 cmd: Command prefix to check if installed (package appended to end)
664 predicate: Function/lambda to check if package is installed based
665 on output. Takes string output and returns boolean.
666
667 Return:
668 True if package is installed.
669 """
670 try:
671 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
672 is_installed = predicate(output.decode('utf-8'))
673 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
674
675 return is_installed
676 except Exception as e:
677 print(e)
678 return False
679
680 def _get_command_output(self, cmd):
681 """Runs the command and gets the output.
682
683 Args:
684 cmd: Command to run.
685
686 Return:
687 Tuple (Success, Output). Success represents if the command ran ok.
688 """
689 try:
690 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
691 return (True, output.decode('utf-8').split('\n'))
692 except Exception as e:
693 print(e)
694 return (False, "")
695
696 def _print_missing_packages(self):
697 """Print any missing packages found via apt.
698
699 This will find any missing packages necessary for build using apt and
700 print it out as an apt-get install printf.
701 """
702 print('Checking for any missing packages...')
703
704 (success, output) = self._get_command_output(APT_PKG_LIST)
705 if not success:
706 raise Exception("Could not query apt for packages.")
707
708 packages_installed = {}
709 for line in output:
710 if 'installed' in line:
711 split = line.split('/', 2)
712 packages_installed[split[0]] = True
713
714 need_packages = []
715 for pkg in REQUIRED_APT_PACKAGES:
716 if pkg not in packages_installed:
717 need_packages.append(pkg)
718
719 # No packages need to be installed
720 if len(need_packages) == 0:
721 print('+ All required packages are installed')
722 return
723
724 install = self._pretty_print_install('sudo apt-get install', need_packages)
725
726 # Print all lines so they can be run in cmdline
727 print('Missing system packages. Run the following command: ')
728 print(' \\\n'.join(install))
729
730 def _print_missing_rust_packages(self):
731 """Print any missing packages found via cargo.
732
733 This will find any missing packages necessary for build using cargo and
734 print it out as a cargo-install printf.
735 """
736 print('Checking for any missing cargo packages...')
737
738 (success, output) = self._get_command_output(CARGO_PKG_LIST)
739 if not success:
740 raise Exception("Could not query cargo for packages.")
741
742 packages_installed = {}
743 for line in output:
744 # Cargo installed packages have this format
745 # :
746 #
747 # We only care about the crates themselves
748 if ':' not in line:
749 continue
750
751 split = line.split(' ', 2)
752 packages_installed[split[0]] = True
753
754 need_packages = []
755 for pkg in REQUIRED_CARGO_PACKAGES:
756 if pkg not in packages_installed:
757 need_packages.append(pkg)
758
759 # No packages to be installed
760 if len(need_packages) == 0:
761 print('+ All required cargo packages are installed')
762 return
763
764 install = self._pretty_print_install('cargo install', need_packages)
765 print('Missing cargo packages. Run the following command: ')
766 print(' \\\n'.join(install))
767
768 def bootstrap(self):
769 """ Bootstrap the Linux build."""
770 self._setup_platform2()
771 self._print_missing_packages()
772 self._print_missing_rust_packages()
773
774
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000775if __name__ == '__main__':
776 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700777 parser.add_argument(
778 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
779 parser.add_argument(
780 '--run-bootstrap',
781 help='Run bootstrap code to verify build env is ok to build.',
782 default=False,
783 action='store_true')
784 parser.add_argument('--no-clang', help='Use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800785 parser.add_argument(
786 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000787 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800788 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
789 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000790 parser.add_argument('--target', help='Run specific build target')
791 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700792 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000793 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700794 parser.add_argument(
795 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000796 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000797 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700798
799 # Make sure we get absolute path + expanded path for bootstrap directory
800 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
801
802 if args.run_bootstrap:
803 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
804 bootstrap.bootstrap()
805 else:
806 build = HostBuild(args)
807 build.build()