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