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