blob: c62b800604ea9c6c47586ace02fd8d1224e0c420 [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
Abhishek Pandit-Subedib987eb62021-11-17 17:27:31 -0800613 os.unlink(dst)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700614 os.symlink(src, dst)
615
616 # Write to setup complete file so we don't repeat this step
617 with open(self.dir_setup_complete, 'w') as f:
618 f.write('Setup complete.')
619
620 def _pretty_print_install(self, install_cmd, packages, line_limit=80):
621 """ Pretty print an install command.
622
623 Args:
624 install_cmd: Prefixed install command.
625 packages: Enumerate packages and append them to install command.
626 line_limit: Number of characters per line.
627
628 Return:
629 Array of lines to join and print.
630 """
631 install = [install_cmd]
632 line = ' '
633 # Remainder needed = space + len(pkg) + space + \
634 # Assuming 80 character lines, that's 80 - 3 = 77
635 line_limit = line_limit - 3
636 for pkg in packages:
637 if len(line) + len(pkg) < line_limit:
638 line = '{}{} '.format(line, pkg)
639 else:
640 install.append(line)
641 line = ' {} '.format(pkg)
642
643 if len(line) > 0:
644 install.append(line)
645
646 return install
647
648 def _check_package_installed(self, package, cmd, predicate):
649 """Check that the given package is installed.
650
651 Args:
652 package: Check that this package is installed.
653 cmd: Command prefix to check if installed (package appended to end)
654 predicate: Function/lambda to check if package is installed based
655 on output. Takes string output and returns boolean.
656
657 Return:
658 True if package is installed.
659 """
660 try:
661 output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
662 is_installed = predicate(output.decode('utf-8'))
663 print(' {} is {}'.format(package, 'installed' if is_installed else 'missing'))
664
665 return is_installed
666 except Exception as e:
667 print(e)
668 return False
669
670 def _get_command_output(self, cmd):
671 """Runs the command and gets the output.
672
673 Args:
674 cmd: Command to run.
675
676 Return:
677 Tuple (Success, Output). Success represents if the command ran ok.
678 """
679 try:
680 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
681 return (True, output.decode('utf-8').split('\n'))
682 except Exception as e:
683 print(e)
684 return (False, "")
685
686 def _print_missing_packages(self):
687 """Print any missing packages found via apt.
688
689 This will find any missing packages necessary for build using apt and
690 print it out as an apt-get install printf.
691 """
692 print('Checking for any missing packages...')
693
694 (success, output) = self._get_command_output(APT_PKG_LIST)
695 if not success:
696 raise Exception("Could not query apt for packages.")
697
698 packages_installed = {}
699 for line in output:
700 if 'installed' in line:
701 split = line.split('/', 2)
702 packages_installed[split[0]] = True
703
704 need_packages = []
705 for pkg in REQUIRED_APT_PACKAGES:
706 if pkg not in packages_installed:
707 need_packages.append(pkg)
708
709 # No packages need to be installed
710 if len(need_packages) == 0:
711 print('+ All required packages are installed')
712 return
713
714 install = self._pretty_print_install('sudo apt-get install', need_packages)
715
716 # Print all lines so they can be run in cmdline
717 print('Missing system packages. Run the following command: ')
718 print(' \\\n'.join(install))
719
720 def _print_missing_rust_packages(self):
721 """Print any missing packages found via cargo.
722
723 This will find any missing packages necessary for build using cargo and
724 print it out as a cargo-install printf.
725 """
726 print('Checking for any missing cargo packages...')
727
728 (success, output) = self._get_command_output(CARGO_PKG_LIST)
729 if not success:
730 raise Exception("Could not query cargo for packages.")
731
732 packages_installed = {}
733 for line in output:
734 # Cargo installed packages have this format
735 # <crate name> <version>:
736 # <binary name>
737 # We only care about the crates themselves
738 if ':' not in line:
739 continue
740
741 split = line.split(' ', 2)
742 packages_installed[split[0]] = True
743
744 need_packages = []
745 for pkg in REQUIRED_CARGO_PACKAGES:
746 if pkg not in packages_installed:
747 need_packages.append(pkg)
748
749 # No packages to be installed
750 if len(need_packages) == 0:
751 print('+ All required cargo packages are installed')
752 return
753
754 install = self._pretty_print_install('cargo install', need_packages)
755 print('Missing cargo packages. Run the following command: ')
756 print(' \\\n'.join(install))
757
758 def bootstrap(self):
759 """ Bootstrap the Linux build."""
760 self._setup_platform2()
761 self._print_missing_packages()
762 self._print_missing_rust_packages()
763
764
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000765if __name__ == '__main__':
766 parser = argparse.ArgumentParser(description='Simple build for host.')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700767 parser.add_argument(
768 '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
769 parser.add_argument(
770 '--run-bootstrap',
771 help='Run bootstrap code to verify build env is ok to build.',
772 default=False,
773 action='store_true')
774 parser.add_argument('--no-clang', help='Use clang compiler.', default=False, action='store_true')
Abhishek Pandit-Subedia1a94d12021-11-18 13:14:04 -0800775 parser.add_argument(
776 '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000777 parser.add_argument('--use', help='Set a specific use flag.')
Abhishek Pandit-Subedi94764142022-02-07 17:18:00 -0800778 parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
779 parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000780 parser.add_argument('--target', help='Run specific build target')
781 parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700782 parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000783 parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700784 parser.add_argument(
785 '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000786 parser.add_argument('--verbose', help='Verbose logs for build.')
Abhishek Pandit-Subedif52cf662021-03-02 22:33:25 +0000787 args = parser.parse_args()
Abhishek Pandit-Subedi0db77012021-09-20 17:54:26 -0700788
789 # Make sure we get absolute path + expanded path for bootstrap directory
790 args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
791
792 if args.run_bootstrap:
793 bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
794 bootstrap.bootstrap()
795 else:
796 build = HostBuild(args)
797 build.build()