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