blob: 2bffdc31822f420c8a5c375b8b0c9a5d00f12265 [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',
230 ]
231
232 return ' '.join(rust_flags)
233
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000234 def configure_environ(self):
235 """ Configure environment variables for GN and Cargo.
236 """
237 self.env = os.environ.copy()
238
239 # Make sure cargo home dir exists and has a bin directory
240 cargo_home = os.path.join(self.output_dir, 'cargo_home')
241 os.makedirs(cargo_home, exist_ok=True)
242 os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
243
244 # Configure Rust env variables
245 self.env['CARGO_TARGET_DIR'] = self.output_dir
246 self.env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000247 self.env['RUSTFLAGS'] = self._generate_rustflags()
Abhishek Pandit-Subedi1927afa2021-04-28 21:16:18 -0700248 self.env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000249
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000250 def run_command(self, target, args, cwd=None, env=None):
251 """ Run command and stream the output.
252 """
253 # Set some defaults
254 if not cwd:
255 cwd = self.platform_dir
256 if not env:
257 env = self.env
258
259 log_file = os.path.join(self.output_dir, '{}.log'.format(target))
260 with open(log_file, 'wb') as lf:
261 rc = 0
262 process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
263 while True:
264 line = process.stdout.readline()
265 print(line.decode('utf-8'), end="")
266 lf.write(line)
267 if not line:
268 rc = process.poll()
269 if rc is not None:
270 break
271
272 time.sleep(0.1)
273
274 if rc != 0:
275 raise Exception("Return code is {}".format(rc))
276
277 def _get_basever(self):
278 if self.libbase_ver:
279 return self.libbase_ver
280
281 self.libbase_ver = os.environ.get('BASE_VER', '')
282 if not self.libbase_ver:
283 base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
284 try:
285 with open(base_file, 'r') as f:
286 self.libbase_ver = f.read().strip('\n')
287 except:
288 self.libbase_ver = 'NOT-INSTALLED'
289
290 return self.libbase_ver
291
292 def _gn_default_output(self):
293 return os.path.join(self.output_dir, 'out/Default')
294
295 def _gn_configure(self):
296 """ Configure all required parameters for platform2.
297
298 Mostly copied from //common-mk/platform2.py
299 """
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700300 clang = not self.args.no_clang
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000301
302 def to_gn_string(s):
303 return '"%s"' % s.replace('"', '\\"')
304
305 def to_gn_list(strs):
306 return '[%s]' % ','.join([to_gn_string(s) for s in strs])
307
308 def to_gn_args_args(gn_args):
309 for k, v in gn_args.items():
310 if isinstance(v, bool):
311 v = str(v).lower()
312 elif isinstance(v, list):
313 v = to_gn_list(v)
314 elif isinstance(v, six.string_types):
315 v = to_gn_string(v)
316 else:
317 raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
318 yield '%s=%s' % (k.replace('-', '_'), v)
319
320 gn_args = {
321 'platform_subdir': 'bt',
322 'cc': 'clang' if clang else 'gcc',
323 'cxx': 'clang++' if clang else 'g++',
324 'ar': 'llvm-ar' if clang else 'ar',
325 'pkg-config': 'pkg-config',
326 'clang_cc': clang,
327 'clang_cxx': clang,
328 'OS': 'linux',
329 'sysroot': self.sysroot,
330 'libdir': os.path.join(self.sysroot, self.libdir),
331 'build_root': self.output_dir,
332 'platform2_root': self.platform_dir,
333 'libbase_ver': self._get_basever(),
334 'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
335 'external_cflags': [],
Abhishek Pandit-Subedi852dc3a2022-02-14 15:12:36 -0800336 'external_cxxflags': ["-DNDEBUG"],
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000337 'enable_werror': False,
338 }
339
340 if clang:
341 # Make sure to mark the clang use flag as true
342 self.use.set_flag('clang', True)
343 gn_args['external_cxxflags'] += ['-I/usr/include/']
344
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000345 gn_args_args = list(to_gn_args_args(gn_args))
346 use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
347 gn_args_args += ['use={%s}' % (' '.join(use_args))]
348
349 gn_args = [
350 'gn',
351 'gen',
352 ]
353
354 if self.args.verbose:
355 gn_args.append('-v')
356
357 gn_args += [
358 '--root=%s' % self.platform_dir,
359 '--args=%s' % ' '.join(gn_args_args),
360 self._gn_default_output(),
361 ]
362
Sonny Sasaka706ec3b2021-03-25 05:39:20 -0700363 if 'PKG_CONFIG_PATH' in self.env:
364 print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000365
366 self.run_command('configure', gn_args)
367
368 def _gn_build(self, target):
369 """ Generate the ninja command for the target and run it.
370 """
371 args = ['%s:%s' % ('bt', target)]
372 ninja_args = ['ninja', '-C', self._gn_default_output()]
373 if self.jobs:
374 ninja_args += ['-j', str(self.jobs)]
375 ninja_args += args
376
377 if self.args.verbose:
378 ninja_args.append('-v')
379
380 self.run_command('build', ninja_args)
381
382 def _rust_configure(self):
383 """ Generate config file at cargo_home so we use vendored crates.
384 """
385 template = """
386 [source.systembt]
387 directory = "{}/external/rust/vendor"
388
389 [source.crates-io]
390 replace-with = "systembt"
391 local-registry = "/nonexistent"
392 """
Sonny Sasakac1335a22021-03-25 07:10:47 -0700393
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700394 if not self.args.no_vendored_rust:
Sonny Sasakac1335a22021-03-25 07:10:47 -0700395 contents = template.format(self.platform_dir)
396 with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
397 f.write(contents)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000398
399 def _rust_build(self):
400 """ Run `cargo build` from platform2/bt directory.
401 """
402 self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
403
404 def _target_prepare(self):
405 """ Target to prepare the output directory for building.
406
407 This runs gn gen to generate all rquired files and set up the Rust
408 config properly. This will be run
409 """
410 self._gn_configure()
411 self._rust_configure()
412
413 def _target_tools(self):
414 """ Build the tools target in an already prepared environment.
415 """
416 self._gn_build('tools')
417
418 # Also copy bluetooth_packetgen to CARGO_HOME so it's available
419 shutil.copy(
420 os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
421
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800422 def _target_docs(self):
423 """Build the Rust docs."""
424 self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
425
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000426 def _target_rust(self):
427 """ Build rust artifacts in an already prepared environment.
428 """
429 self._rust_build()
430
431 def _target_main(self):
432 """ Build the main GN artifacts in an already prepared environment.
433 """
434 self._gn_build('all')
435
436 def _target_test(self):
437 """ Runs the host tests.
438 """
Abhishek Pandit-Subedid801b122021-04-10 00:41:07 +0000439 # Rust tests first
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800440 rust_test_cmd = ['cargo', 'test']
441 if self.args.test_name:
442 rust_test_cmd = rust_test_cmd + [self.args.test_name]
443
444 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 +0000445
446 # Host tests second based on host test list
447 for t in HOST_TESTS:
448 self.run_command(
449 'test', [os.path.join(self.output_dir, 'out/Default', t)],
450 cwd=os.path.join(self.output_dir),
451 env=self.env)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000452
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800453 def _target_install(self):
454 """ Installs files required to run Floss to install directory.
455 """
456 # First make the install directory
457 prefix = self.install_dir
458 os.makedirs(prefix, exist_ok=True)
459
460 # Next save the cwd and change to install directory
461 last_cwd = os.getcwd()
462 os.chdir(prefix)
463
464 bindir = os.path.join(self.output_dir, 'debug')
465 srcdir = os.path.dirname(__file__)
466
467 install_map = [
468 {
469 'src': os.path.join(bindir, 'btadapterd'),
470 'dst': 'usr/libexec/bluetooth/btadapterd',
471 'strip': True
472 },
473 {
474 'src': os.path.join(bindir, 'btmanagerd'),
475 'dst': 'usr/libexec/bluetooth/btmanagerd',
476 'strip': True
477 },
478 {
479 'src': os.path.join(bindir, 'btclient'),
480 'dst': 'usr/local/bin/btclient',
481 'strip': True
482 },
483 ]
484
485 for v in install_map:
486 src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
487 dst = os.path.join(prefix, partial_dst)
488
489 # Create dst directory first and copy file there
490 os.makedirs(os.path.dirname(dst), exist_ok=True)
491 print('Installing {}'.format(dst))
492 shutil.copy(src, dst)
493
494 # Binary should be marked for strip and no-strip option shouldn't be
495 # set. No-strip is useful while debugging.
496 if strip and not self.args.no_strip:
497 self.run_command('install', ['llvm-strip', dst])
498
499 # Put all files into a tar.gz for easier installation
500 tar_location = os.path.join(prefix, 'floss.tar.gz')
501 with tarfile.open(tar_location, 'w:gz') as tar:
502 for v in install_map:
503 tar.add(v['dst'])
504
505 print('Tarball created at {}'.format(tar_location))
506
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000507 def _target_clean(self):
508 """ Delete the output directory entirely.
509 """
510 shutil.rmtree(self.output_dir)
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800511
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700512 # Remove Cargo.lock that may have become generated
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800513 try:
514 os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock'))
515 except FileNotFoundError:
516 pass
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000517
518 def _target_all(self):
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800519 """ Build all common targets (skipping doc, test, and clean).
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000520 """
521 self._target_prepare()
522 self._target_tools()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000523 self._target_main()
Abhishek Pandit-Subedia7b57b72021-04-01 15:33:05 -0700524 self._target_rust()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000525
526 def build(self):
527 """ Builds according to self.target
528 """
529 print('Building target ', self.target)
530
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800531 # Validate that the target is valid
532 if self.target not in VALID_TARGETS:
533 print('Target {} is not valid. Must be in {}', self.target, VALID_TARGETS)
534 return
535
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000536 if self.target == 'prepare':
537 self._target_prepare()
538 elif self.target == 'tools':
539 self._target_tools()
540 elif self.target == 'rust':
541 self._target_rust()
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800542 elif self.target == 'docs':
543 self._target_docs()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000544 elif self.target == 'main':
545 self._target_main()
546 elif self.target == 'test':
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000547 self._target_test()
548 elif self.target == 'clean':
549 self._target_clean()
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800550 elif self.target == 'install':
551 self._target_install()
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000552 elif self.target == 'all':
553 self._target_all()
554
555
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700556class Bootstrap():
557
558 def __init__(self, base_dir, bt_dir):
559 """ Construct bootstrapper.
560
561 Args:
562 base_dir: Where to stage everything.
563 bt_dir: Where bluetooth source is kept (will be symlinked)
564 """
565 self.base_dir = os.path.abspath(base_dir)
566 self.bt_dir = os.path.abspath(bt_dir)
567
568 # Create base directory if it doesn't already exist
569 os.makedirs(self.base_dir, exist_ok=True)
570
571 if not os.path.isdir(self.bt_dir):
572 raise Exception('{} is not a valid directory'.format(self.bt_dir))
573
574 self.git_dir = os.path.join(self.base_dir, 'repos')
575 self.staging_dir = os.path.join(self.base_dir, 'staging')
576 self.output_dir = os.path.join(self.base_dir, 'output')
577 self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
578
579 self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
580
581 def _update_platform2(self):
582 """Updates repositories used for build."""
583 for repo in BOOTSTRAP_GIT_REPOS.keys():
584 cwd = os.path.join(self.git_dir, repo)
585 subprocess.check_call(['git', 'pull'], cwd=cwd)
586
587 def _setup_platform2(self):
588 """ Set up platform2.
589
590 This will check out all the git repos and symlink everything correctly.
591 """
592
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800593 # Create all directories we will need to use
594 for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
595 os.makedirs(dirpath, exist_ok=True)
596
597 # If already set up, only update platform2
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700598 if os.path.isfile(self.dir_setup_complete):
599 print('{} already set-up. Updating instead.'.format(self.base_dir))
600 self._update_platform2()
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800601 else:
602 # Check out all repos in git directory
603 for repo in BOOTSTRAP_GIT_REPOS.values():
604 subprocess.check_call(['git', 'clone', repo], cwd=self.git_dir)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700605
606 # Symlink things
607 symlinks = [
608 (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
609 (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
610 (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
611 (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
612 (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
613 ]
614
615 # Create symlinks
616 for pairs in symlinks:
617 (src, dst) = pairs
Martin Brabham247d80b2022-02-04 19:42:49 +0000618 try:
619 os.unlink(dst)
620 except Exception as e:
621 print(e)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700622 os.symlink(src, dst)
623
624 # Write to setup complete file so we don't repeat this step
625 with open(self.dir_setup_complete, 'w') as f:
626 f.write('Setup complete.')
627
628 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
629 """ Pretty print an install command.
630
631 Args:
632 install_cmd: Prefixed install command.
633 packages: Enumerate packages and append them to install command.
634 line_limit: Number of characters per line.
635
636 Return:
637 Array of lines to join and print.
638 """
639 install = [install_cmd]
640 line = ' '
641 # Remainder needed = space + len(pkg) + space + \
642 # Assuming 80 character lines, that's 80 - 3 = 77
643 line_limit = line_limit - 3
644 for pkg in packages:
645 if len(line) + len(pkg) < line_limit:
646 line = '{}{} '.format(line, pkg)
647 else:
648 install.append(line)
649 line = ' {} '.format(pkg)
650
651 if len(line) > 0:
652 install.append(line)
653
654 return install
655
656 def _check_package_installed(self, package, cmd, predicate):
657 """Check that the given package is installed.
658
659 Args:
660 package: Check that this package is installed.
661 cmd: Command prefix to check if installed (package appended to end)
662 predicate: Function/lambda to check if package is installed based
663 on output. Takes string output and returns boolean.
664
665 Return:
666 True if package is installed.
667 """
668 try:
669 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
670 is_installed = predicate(output.decode('utf-8'))
671 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
672
673 return is_installed
674 except Exception as e:
675 print(e)
676 return False
677
678 def _get_command_output(self, cmd):
679 """Runs the command and gets the output.
680
681 Args:
682 cmd: Command to run.
683
684 Return:
685 Tuple (Success, Output). Success represents if the command ran ok.
686 """
687 try:
688 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
689 return (True, output.decode('utf-8').split('\n'))
690 except Exception as e:
691 print(e)
692 return (False, "")
693
694 def _print_missing_packages(self):
695 """Print any missing packages found via apt.
696
697 This will find any missing packages necessary for build using apt and
698 print it out as an apt-get install printf.
699 """
700 print('Checking for any missing packages...')
701
702 (success, output) = self._get_command_output(APT_PKG_LIST)
703 if not success:
704 raise Exception("Could not query apt for packages.")
705
706 packages_installed = {}
707 for line in output:
708 if 'installed' in line:
709 split = line.split('/', 2)
710 packages_installed[split[0]] = True
711
712 need_packages = []
713 for pkg in REQUIRED_APT_PACKAGES:
714 if pkg not in packages_installed:
715 need_packages.append(pkg)
716
717 # No packages need to be installed
718 if len(need_packages) == 0:
719 print('+ All required packages are installed')
720 return
721
722 install = self._pretty_print_install('sudo apt-get install', need_packages)
723
724 # Print all lines so they can be run in cmdline
725 print('Missing system packages. Run the following command: ')
726 print(' \\\n'.join(install))
727
728 def _print_missing_rust_packages(self):
729 """Print any missing packages found via cargo.
730
731 This will find any missing packages necessary for build using cargo and
732 print it out as a cargo-install printf.
733 """
734 print('Checking for any missing cargo packages...')
735
736 (success, output) = self._get_command_output(CARGO_PKG_LIST)
737 if not success:
738 raise Exception("Could not query cargo for packages.")
739
740 packages_installed = {}
741 for line in output:
742 # Cargo installed packages have this format
743 # <crate name> <version>:
744 # <binary name>
745 # We only care about the crates themselves
746 if ':' not in line:
747 continue
748
749 split = line.split(' ', 2)
750 packages_installed[split[0]] = True
751
752 need_packages = []
753 for pkg in REQUIRED_CARGO_PACKAGES:
754 if pkg not in packages_installed:
755 need_packages.append(pkg)
756
757 # No packages to be installed
758 if len(need_packages) == 0:
759 print('+ All required cargo packages are installed')
760 return
761
762 install = self._pretty_print_install('cargo install', need_packages)
763 print('Missing cargo packages. Run the following command: ')
764 print(' \\\n'.join(install))
765
766 def bootstrap(self):
767 """ Bootstrap the Linux build."""
768 self._setup_platform2()
769 self._print_missing_packages()
770 self._print_missing_rust_packages()
771
772
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000773if __name__ == '__main__':
774 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700775 parser.add_argument(
776 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
777 parser.add_argument(
778 '--run-bootstrap',
779 help='Run bootstrap code to verify build env is ok to build.',
780 default=False,
781 action='store_true')
782 parser.add_argument('--no-clang', help='Use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800783 parser.add_argument(
784 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000785 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800786 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
787 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000788 parser.add_argument('--target', help='Run specific build target')
789 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700790 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000791 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700792 parser.add_argument(
793 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000794 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000795 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700796
797 # Make sure we get absolute path + expanded path for bootstrap directory
798 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
799
800 if args.run_bootstrap:
801 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
802 bootstrap.bootstrap()
803 else:
804 build = HostBuild(args)
805 build.build()