blob: 828ba99d7d1a02bceaf9dd96e9f81e4ad249935f [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',
105 'libevent-dev',
106 'libevent-dev',
107 'libflatbuffers-dev',
108 'libflatbuffers1',
109 'libgl1-mesa-dev',
110 'libglib2.0-dev',
111 'liblz4-tool',
112 'libncurses5',
113 'libnss3-dev',
114 'libprotobuf-dev',
115 'libre2-9',
116 'libssl-dev',
117 'libtinyxml2-dev',
118 'libx11-dev',
119 'libxml2-utils',
120 'ninja-build',
121 'openssl',
122 'protobuf-compiler',
123 'unzip',
124 'x11proto-core-dev',
125 'xsltproc',
126 'zip',
127 'zlib1g-dev',
128]
129
130# List of cargo packages required for linux build
131REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd']
132
133APT_PKG_LIST = ['apt', '-qq', 'list']
134CARGO_PKG_LIST = ['cargo', 'install', '--list']
135
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000136
137class UseFlags():
138
139 def __init__(self, use_flags):
140 """ Construct the use flags.
141
142 Args:
143 use_flags: List of use flags parsed from the command.
144 """
145 self.flags = {}
146
147 # Import use flags required by common-mk
148 for use in COMMON_MK_USES:
149 self.set_flag(use, False)
150
151 # Set our defaults
152 for use, value in USE_DEFAULTS.items():
153 self.set_flag(use, value)
154
155 # Set use flags - value is set to True unless the use starts with -
156 # All given use flags always override the defaults
157 for use in use_flags:
158 value = not use.startswith('-')
159 self.set_flag(use, value)
160
161 def set_flag(self, key, value=True):
162 setattr(self, key, value)
163 self.flags[key] = value
164
165
166class HostBuild():
167
168 def __init__(self, args):
169 """ Construct the builder.
170
171 Args:
172 args: Parsed arguments from ArgumentParser
173 """
174 self.args = args
175
176 # Set jobs to number of cpus unless explicitly set
177 self.jobs = self.args.jobs
178 if not self.jobs:
179 self.jobs = multiprocessing.cpu_count()
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000180 print("Number of jobs = {}".format(self.jobs))
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000181
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700182 # Normalize bootstrap dir and make sure it exists
183 self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
184 os.makedirs(self.bootstrap_dir, exist_ok=True)
185
186 # Output and platform directories are based on bootstrap
187 self.output_dir = os.path.join(self.bootstrap_dir, 'output')
188 self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000189 self.sysroot = self.args.sysroot
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000190 self.libdir = self.args.libdir
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800191 self.install_dir = os.path.join(self.output_dir, 'install')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000192
193 # If default target isn't set, build everything
194 self.target = 'all'
195 if hasattr(self.args, 'target') and self.args.target:
196 self.target = self.args.target
197
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000198 target_use = self.args.use if self.args.use else []
199
200 # Unless set, always build test code
201 if not self.args.notest:
202 target_use.append('test')
203
204 self.use = UseFlags(target_use)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000205
206 # Validate platform directory
207 assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
208 assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
209
210 # Make sure output directory exists (or create it)
211 os.makedirs(self.output_dir, exist_ok=True)
212
213 # Set some default attributes
214 self.libbase_ver = None
215
216 self.configure_environ()
217
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000218 def _generate_rustflags(self):
219 """ Rustflags to include for the build.
220 """
221 rust_flags = [
222 '-L',
Martin Brabham1c24fda2021-09-16 11:19:46 -0700223 '{}/out/Default'.format(self.output_dir),
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000224 '-C',
225 'link-arg=-Wl,--allow-multiple-definition',
226 ]
227
228 return ' '.join(rust_flags)
229
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000230 def configure_environ(self):
231 """ Configure environment variables for GN and Cargo.
232 """
233 self.env = os.environ.copy()
234
235 # Make sure cargo home dir exists and has a bin directory
236 cargo_home = os.path.join(self.output_dir, 'cargo_home')
237 os.makedirs(cargo_home, exist_ok=True)
238 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
239
240 # Configure Rust env variables
241 self.env['CARGO_TARGET_DIR'] = self.output_dir
242 self.env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000243 self.env['RUSTFLAGS'] = self._generate_rustflags()
Abhishek Pandit-Subedi1927afa2021-04-28 21:16:18 -0700244 self.env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000245
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000246 def run_command(self, target, args, cwd=None, env=None):
247 """ Run command and stream the output.
248 """
249 # Set some defaults
250 if not cwd:
251 cwd = self.platform_dir
252 if not env:
253 env = self.env
254
255 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
256 with open(log_file, 'wb') as lf:
257 rc = 0
258 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
259 while True:
260 line = process.stdout.readline()
261 print(line.decode('utf-8'), end="")
262 lf.write(line)
263 if not line:
264 rc = process.poll()
265 if rc is not None:
266 break
267
268 time.sleep(0.1)
269
270 if rc != 0:
271 raise Exception("Return code is {}".format(rc))
272
273 def _get_basever(self):
274 if self.libbase_ver:
275 return self.libbase_ver
276
277 self.libbase_ver = os.environ.get('BASE_VER', '')
278 if not self.libbase_ver:
279 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
280 try:
281 with open(base_file, 'r') as f:
282 self.libbase_ver = f.read().strip('\n')
283 except:
284 self.libbase_ver = 'NOT-INSTALLED'
285
286 return self.libbase_ver
287
288 def _gn_default_output(self):
289 return os.path.join(self.output_dir, 'out/Default')
290
291 def _gn_configure(self):
292 """ Configure all required parameters for platform2.
293
294 Mostly copied from //common-mk/platform2.py
295 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700296 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000297
298 def to_gn_string(s):
299 return '"%s"' % s.replace('"', '\\"')
300
301 def to_gn_list(strs):
302 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
303
304 def to_gn_args_args(gn_args):
305 for k, v in gn_args.items():
306 if isinstance(v, bool):
307 v = str(v).lower()
308 elif isinstance(v, list):
309 v = to_gn_list(v)
310 elif isinstance(v, six.string_types):
311 v = to_gn_string(v)
312 else:
313 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
314 yield '%s=%s' % (k.replace('-', '_'), v)
315
316 gn_args = {
317 'platform_subdir': 'bt',
318 'cc': 'clang' if clang else 'gcc',
319 'cxx': 'clang++' if clang else 'g++',
320 'ar': 'llvm-ar' if clang else 'ar',
321 'pkg-config': 'pkg-config',
322 'clang_cc': clang,
323 'clang_cxx': clang,
324 'OS': 'linux',
325 'sysroot': self.sysroot,
326 'libdir': os.path.join(self.sysroot, self.libdir),
327 'build_root': self.output_dir,
328 'platform2_root': self.platform_dir,
329 'libbase_ver': self._get_basever(),
330 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
331 'external_cflags': [],
332 'external_cxxflags': [],
333 'enable_werror': False,
334 }
335
336 if clang:
337 # Make sure to mark the clang use flag as true
338 self.use.set_flag('clang', True)
339 gn_args['external_cxxflags'] += ['-I/usr/include/']
340
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000341 gn_args_args = list(to_gn_args_args(gn_args))
342 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
343 gn_args_args += ['use={%s}' % (' '.join(use_args))]
344
345 gn_args = [
346 'gn',
347 'gen',
348 ]
349
350 if self.args.verbose:
351 gn_args.append('-v')
352
353 gn_args += [
354 '--root=%s' % self.platform_dir,
355 '--args=%s' % ' '.join(gn_args_args),
356 self._gn_default_output(),
357 ]
358
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700359 if 'PKG_CONFIG_PATH' in self.env:
360 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000361
362 self.run_command('configure', gn_args)
363
364 def _gn_build(self, target):
365 """ Generate the ninja command for the target and run it.
366 """
367 args = ['%s:%s' % ('bt', target)]
368 ninja_args = ['ninja', '-C', self._gn_default_output()]
369 if self.jobs:
370 ninja_args += ['-j', str(self.jobs)]
371 ninja_args += args
372
373 if self.args.verbose:
374 ninja_args.append('-v')
375
376 self.run_command('build', ninja_args)
377
378 def _rust_configure(self):
379 """ Generate config file at cargo_home so we use vendored crates.
380 """
381 template = """
382 [source.systembt]
383 directory = "{}/external/rust/vendor"
384
385 [source.crates-io]
386 replace-with = "systembt"
387 local-registry = "/nonexistent"
388 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700389
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700390 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700391 contents = template.format(self.platform_dir)
392 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
393 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000394
395 def _rust_build(self):
396 """ Run `cargo build` from platform2/bt directory.
397 """
398 self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
399
400 def _target_prepare(self):
401 """ Target to prepare the output directory for building.
402
403 This runs gn gen to generate all rquired files and set up the Rust
404 config properly. This will be run
405 """
406 self._gn_configure()
407 self._rust_configure()
408
409 def _target_tools(self):
410 """ Build the tools target in an already prepared environment.
411 """
412 self._gn_build('tools')
413
414 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
415 shutil.copy(
416 os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
417
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800418 def _target_docs(self):
419 """Build the Rust docs."""
420 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
421
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000422 def _target_rust(self):
423 """ Build rust artifacts in an already prepared environment.
424 """
425 self._rust_build()
426
427 def _target_main(self):
428 """ Build the main GN artifacts in an already prepared environment.
429 """
430 self._gn_build('all')
431
432 def _target_test(self):
433 """ Runs the host tests.
434 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000435 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800436 rust_test_cmd = ['cargo', 'test']
437 if self.args.test_name:
438 rust_test_cmd = rust_test_cmd + [self.args.test_name]
439
440 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 +0000441
442 # Host tests second based on host test list
443 for t in HOST_TESTS:
444 self.run_command(
445 'test', [os.path.join(self.output_dir, 'out/Default', t)],
446 cwd=os.path.join(self.output_dir),
447 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000448
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800449 def _target_install(self):
450 """ Installs files required to run Floss to install directory.
451 """
452 # First make the install directory
453 prefix = self.install_dir
454 os.makedirs(prefix, exist_ok=True)
455
456 # Next save the cwd and change to install directory
457 last_cwd = os.getcwd()
458 os.chdir(prefix)
459
460 bindir = os.path.join(self.output_dir, 'debug')
461 srcdir = os.path.dirname(__file__)
462
463 install_map = [
464 {
465 'src': os.path.join(bindir, 'btadapterd'),
466 'dst': 'usr/libexec/bluetooth/btadapterd',
467 'strip': True
468 },
469 {
470 'src': os.path.join(bindir, 'btmanagerd'),
471 'dst': 'usr/libexec/bluetooth/btmanagerd',
472 'strip': True
473 },
474 {
475 'src': os.path.join(bindir, 'btclient'),
476 'dst': 'usr/local/bin/btclient',
477 'strip': True
478 },
479 ]
480
481 for v in install_map:
482 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
483 dst = os.path.join(prefix, partial_dst)
484
485 # Create dst directory first and copy file there
486 os.makedirs(os.path.dirname(dst), exist_ok=True)
487 print('Installing {}'.format(dst))
488 shutil.copy(src, dst)
489
490 # Binary should be marked for strip and no-strip option shouldn't be
491 # set. No-strip is useful while debugging.
492 if strip and not self.args.no_strip:
493 self.run_command('install', ['llvm-strip', dst])
494
495 # Put all files into a tar.gz for easier installation
496 tar_location = os.path.join(prefix, 'floss.tar.gz')
497 with tarfile.open(tar_location, 'w:gz') as tar:
498 for v in install_map:
499 tar.add(v['dst'])
500
501 print('Tarball created at {}'.format(tar_location))
502
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000503 def _target_clean(self):
504 """ Delete the output directory entirely.
505 """
506 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800507
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700508 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800509 try:
510 os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock'))
511 except FileNotFoundError:
512 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000513
514 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800515 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000516 """
517 self._target_prepare()
518 self._target_tools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000519 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700520 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000521
522 def build(self):
523 """ Builds according to self.target
524 """
525 print('Building target ', self.target)
526
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800527 # Validate that the target is valid
528 if self.target not in VALID_TARGETS:
529 print('Target {} is not valid. Must be in {}', self.target, VALID_TARGETS)
530 return
531
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000532 if self.target == 'prepare':
533 self._target_prepare()
534 elif self.target == 'tools':
535 self._target_tools()
536 elif self.target == 'rust':
537 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800538 elif self.target == 'docs':
539 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000540 elif self.target == 'main':
541 self._target_main()
542 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000543 self._target_test()
544 elif self.target == 'clean':
545 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800546 elif self.target == 'install':
547 self._target_install()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000548 elif self.target == 'all':
549 self._target_all()
550
551
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700552class Bootstrap():
553
554 def __init__(self, base_dir, bt_dir):
555 """ Construct bootstrapper.
556
557 Args:
558 base_dir: Where to stage everything.
559 bt_dir: Where bluetooth source is kept (will be symlinked)
560 """
561 self.base_dir = os.path.abspath(base_dir)
562 self.bt_dir = os.path.abspath(bt_dir)
563
564 # Create base directory if it doesn't already exist
565 os.makedirs(self.base_dir, exist_ok=True)
566
567 if not os.path.isdir(self.bt_dir):
568 raise Exception('{} is not a valid directory'.format(self.bt_dir))
569
570 self.git_dir = os.path.join(self.base_dir, 'repos')
571 self.staging_dir = os.path.join(self.base_dir, 'staging')
572 self.output_dir = os.path.join(self.base_dir, 'output')
573 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
574
575 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
576
577 def _update_platform2(self):
578 """Updates repositories used for build."""
579 for repo in BOOTSTRAP_GIT_REPOS.keys():
580 cwd = os.path.join(self.git_dir, repo)
581 subprocess.check_call(['git', 'pull'], cwd=cwd)
582
583 def _setup_platform2(self):
584 """ Set up platform2.
585
586 This will check out all the git repos and symlink everything correctly.
587 """
588
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800589 # Create all directories we will need to use
590 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
591 os.makedirs(dirpath, exist_ok=True)
592
593 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700594 if os.path.isfile(self.dir_setup_complete):
595 print('{} already set-up. Updating instead.'.format(self.base_dir))
596 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800597 else:
598 # Check out all repos in git directory
599 for repo in BOOTSTRAP_GIT_REPOS.values():
600 subprocess.check_call(['git', 'clone', repo], cwd=self.git_dir)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700601
602 # Symlink things
603 symlinks = [
604 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
605 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
606 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
607 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
608 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
609 ]
610
611 # Create symlinks
612 for pairs in symlinks:
613 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000614 try:
615 os.unlink(dst)
616 except Exception as e:
617 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700618 os.symlink(src, dst)
619
620 # Write to setup complete file so we don't repeat this step
621 with open(self.dir_setup_complete, 'w') as f:
622 f.write('Setup complete.')
623
624 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
625 """ Pretty print an install command.
626
627 Args:
628 install_cmd: Prefixed install command.
629 packages: Enumerate packages and append them to install command.
630 line_limit: Number of characters per line.
631
632 Return:
633 Array of lines to join and print.
634 """
635 install = [install_cmd]
636 line = ' '
637 # Remainder needed = space + len(pkg) + space + \
638 # Assuming 80 character lines, that's 80 - 3 = 77
639 line_limit = line_limit - 3
640 for pkg in packages:
641 if len(line) + len(pkg) < line_limit:
642 line = '{}{} '.format(line, pkg)
643 else:
644 install.append(line)
645 line = ' {} '.format(pkg)
646
647 if len(line) > 0:
648 install.append(line)
649
650 return install
651
652 def _check_package_installed(self, package, cmd, predicate):
653 """Check that the given package is installed.
654
655 Args:
656 package: Check that this package is installed.
657 cmd: Command prefix to check if installed (package appended to end)
658 predicate: Function/lambda to check if package is installed based
659 on output. Takes string output and returns boolean.
660
661 Return:
662 True if package is installed.
663 """
664 try:
665 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
666 is_installed = predicate(output.decode('utf-8'))
667 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
668
669 return is_installed
670 except Exception as e:
671 print(e)
672 return False
673
674 def _get_command_output(self, cmd):
675 """Runs the command and gets the output.
676
677 Args:
678 cmd: Command to run.
679
680 Return:
681 Tuple (Success, Output). Success represents if the command ran ok.
682 """
683 try:
684 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
685 return (True, output.decode('utf-8').split('\n'))
686 except Exception as e:
687 print(e)
688 return (False, "")
689
690 def _print_missing_packages(self):
691 """Print any missing packages found via apt.
692
693 This will find any missing packages necessary for build using apt and
694 print it out as an apt-get install printf.
695 """
696 print('Checking for any missing packages...')
697
698 (success, output) = self._get_command_output(APT_PKG_LIST)
699 if not success:
700 raise Exception("Could not query apt for packages.")
701
702 packages_installed = {}
703 for line in output:
704 if 'installed' in line:
705 split = line.split('/', 2)
706 packages_installed[split[0]] = True
707
708 need_packages = []
709 for pkg in REQUIRED_APT_PACKAGES:
710 if pkg not in packages_installed:
711 need_packages.append(pkg)
712
713 # No packages need to be installed
714 if len(need_packages) == 0:
715 print('+ All required packages are installed')
716 return
717
718 install = self._pretty_print_install('sudo apt-get install', need_packages)
719
720 # Print all lines so they can be run in cmdline
721 print('Missing system packages. Run the following command: ')
722 print(' \\\n'.join(install))
723
724 def _print_missing_rust_packages(self):
725 """Print any missing packages found via cargo.
726
727 This will find any missing packages necessary for build using cargo and
728 print it out as a cargo-install printf.
729 """
730 print('Checking for any missing cargo packages...')
731
732 (success, output) = self._get_command_output(CARGO_PKG_LIST)
733 if not success:
734 raise Exception("Could not query cargo for packages.")
735
736 packages_installed = {}
737 for line in output:
738 # Cargo installed packages have this format
739 # <crate name> <version>:
740 # <binary name>
741 # We only care about the crates themselves
742 if ':' not in line:
743 continue
744
745 split = line.split(' ', 2)
746 packages_installed[split[0]] = True
747
748 need_packages = []
749 for pkg in REQUIRED_CARGO_PACKAGES:
750 if pkg not in packages_installed:
751 need_packages.append(pkg)
752
753 # No packages to be installed
754 if len(need_packages) == 0:
755 print('+ All required cargo packages are installed')
756 return
757
758 install = self._pretty_print_install('cargo install', need_packages)
759 print('Missing cargo packages. Run the following command: ')
760 print(' \\\n'.join(install))
761
762 def bootstrap(self):
763 """ Bootstrap the Linux build."""
764 self._setup_platform2()
765 self._print_missing_packages()
766 self._print_missing_rust_packages()
767
768
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000769if __name__ == '__main__':
770 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700771 parser.add_argument(
772 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
773 parser.add_argument(
774 '--run-bootstrap',
775 help='Run bootstrap code to verify build env is ok to build.',
776 default=False,
777 action='store_true')
778 parser.add_argument('--no-clang', help='Use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800779 parser.add_argument(
780 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000781 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800782 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
783 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000784 parser.add_argument('--target', help='Run specific build target')
785 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700786 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000787 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700788 parser.add_argument(
789 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000790 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000791 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700792
793 # Make sure we get absolute path + expanded path for bootstrap directory
794 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
795
796 if args.run_bootstrap:
797 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
798 bootstrap.bootstrap()
799 else:
800 build = HostBuild(args)
801 build.build()