815 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			815 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| #  Copyright 2021 Google, Inc.
 | |
| #
 | |
| #  Licensed under the Apache License, Version 2.0 (the "License");
 | |
| #  you may not use this file except in compliance with the License.
 | |
| #  You may obtain a copy of the License at:
 | |
| #
 | |
| #  http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| #  Unless required by applicable law or agreed to in writing, software
 | |
| #  distributed under the License is distributed on an "AS IS" BASIS,
 | |
| #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| #  See the License for the specific language governing permissions and
 | |
| #  limitations under the License.
 | |
| """ Build BT targets on the host system.
 | |
| 
 | |
| For building, you will first have to stage a platform directory that has the
 | |
| following structure:
 | |
| |-common-mk
 | |
| |-bt
 | |
| |-external
 | |
| |-|-rust
 | |
| |-|-|-vendor
 | |
| 
 | |
| The simplest way to do this is to check out platform2 to another directory (that
 | |
| is not a subdir of this bt directory), symlink bt there and symlink the rust
 | |
| vendor repository as well.
 | |
| """
 | |
| import argparse
 | |
| import multiprocessing
 | |
| import os
 | |
| import shutil
 | |
| import six
 | |
| import subprocess
 | |
| import sys
 | |
| import tarfile
 | |
| import time
 | |
| 
 | |
| # Use flags required by common-mk (find -type f | grep -nE 'use[.]' {})
 | |
| COMMON_MK_USES = [
 | |
|     'asan',
 | |
|     'coverage',
 | |
|     'cros_host',
 | |
|     'fuzzer',
 | |
|     'fuzzer',
 | |
|     'msan',
 | |
|     'profiling',
 | |
|     'tcmalloc',
 | |
|     'test',
 | |
|     'ubsan',
 | |
| ]
 | |
| 
 | |
| # Default use flags.
 | |
| USE_DEFAULTS = {
 | |
|     'android': False,
 | |
|     'bt_nonstandard_codecs': False,
 | |
|     'test': False,
 | |
| }
 | |
| 
 | |
| VALID_TARGETS = [
 | |
|     'all',  # All targets except test and clean
 | |
|     'clean',  # Clean up output directory
 | |
|     'docs',  # Build Rust docs
 | |
|     'main',  # Build the main C++ codebase
 | |
|     'prepare',  # Prepare the output directory (gn gen + rust setup)
 | |
|     'rootcanal',  # Build Rust targets for RootCanal
 | |
|     'rust',  # Build only the rust components + copy artifacts to output dir
 | |
|     'test',  # Run the unit tests
 | |
|     'tools',  # Build the host tools (i.e. packetgen)
 | |
| ]
 | |
| 
 | |
| # TODO(b/190750167) - Host tests are disabled until we are full bazel build
 | |
| HOST_TESTS = [
 | |
|     # 'bluetooth_test_common',
 | |
|     # 'bluetoothtbd_test',
 | |
|     # 'net_test_avrcp',
 | |
|     # 'net_test_btcore',
 | |
|     # 'net_test_types',
 | |
|     # 'net_test_btm_iso',
 | |
|     # 'net_test_btpackets',
 | |
| ]
 | |
| 
 | |
| BOOTSTRAP_GIT_REPOS = {
 | |
|     'platform2': 'https://chromium.googlesource.com/chromiumos/platform2',
 | |
|     'rust_crates': 'https://chromium.googlesource.com/chromiumos/third_party/rust_crates',
 | |
|     'proto_logging': 'https://android.googlesource.com/platform/frameworks/proto_logging'
 | |
| }
 | |
| 
 | |
| # List of packages required for linux build
 | |
| REQUIRED_APT_PACKAGES = [
 | |
|     'bison',
 | |
|     'build-essential',
 | |
|     'curl',
 | |
|     'debmake',
 | |
|     'flatbuffers-compiler',
 | |
|     'flex',
 | |
|     'g++-multilib',
 | |
|     'gcc-multilib',
 | |
|     'generate-ninja',
 | |
|     'gnupg',
 | |
|     'gperf',
 | |
|     'libc++abi-dev',
 | |
|     'libc++-dev',
 | |
|     'libdbus-1-dev',
 | |
|     'libdouble-conversion-dev',
 | |
|     'libevent-dev',
 | |
|     'libevent-dev',
 | |
|     'libflatbuffers-dev',
 | |
|     'libflatbuffers1',
 | |
|     'libgl1-mesa-dev',
 | |
|     'libglib2.0-dev',
 | |
|     'libgtest-dev',
 | |
|     'libgmock-dev',
 | |
|     'liblz4-tool',
 | |
|     'libncurses5',
 | |
|     'libnss3-dev',
 | |
|     'libprotobuf-dev',
 | |
|     'libre2-9',
 | |
|     'libre2-dev',
 | |
|     'libssl-dev',
 | |
|     'libtinyxml2-dev',
 | |
|     'libx11-dev',
 | |
|     'libxml2-utils',
 | |
|     'ninja-build',
 | |
|     'openssl',
 | |
|     'protobuf-compiler',
 | |
|     'unzip',
 | |
|     'x11proto-core-dev',
 | |
|     'xsltproc',
 | |
|     'zip',
 | |
|     'zlib1g-dev',
 | |
| ]
 | |
| 
 | |
| # List of cargo packages required for linux build
 | |
| REQUIRED_CARGO_PACKAGES = ['cxxbridge-cmd']
 | |
| 
 | |
| APT_PKG_LIST = ['apt', '-qq', 'list']
 | |
| CARGO_PKG_LIST = ['cargo', 'install', '--list']
 | |
| 
 | |
| 
 | |
| class UseFlags():
 | |
| 
 | |
|     def __init__(self, use_flags):
 | |
|         """ Construct the use flags.
 | |
| 
 | |
|         Args:
 | |
|             use_flags: List of use flags parsed from the command.
 | |
|         """
 | |
|         self.flags = {}
 | |
| 
 | |
|         # Import use flags required by common-mk
 | |
|         for use in COMMON_MK_USES:
 | |
|             self.set_flag(use, False)
 | |
| 
 | |
|         # Set our defaults
 | |
|         for use, value in USE_DEFAULTS.items():
 | |
|             self.set_flag(use, value)
 | |
| 
 | |
|         # Set use flags - value is set to True unless the use starts with -
 | |
|         # All given use flags always override the defaults
 | |
|         for use in use_flags:
 | |
|             value = not use.startswith('-')
 | |
|             self.set_flag(use, value)
 | |
| 
 | |
|     def set_flag(self, key, value=True):
 | |
|         setattr(self, key, value)
 | |
|         self.flags[key] = value
 | |
| 
 | |
| 
 | |
| class HostBuild():
 | |
| 
 | |
|     def __init__(self, args):
 | |
|         """ Construct the builder.
 | |
| 
 | |
|         Args:
 | |
|             args: Parsed arguments from ArgumentParser
 | |
|         """
 | |
|         self.args = args
 | |
| 
 | |
|         # Set jobs to number of cpus unless explicitly set
 | |
|         self.jobs = self.args.jobs
 | |
|         if not self.jobs:
 | |
|             self.jobs = multiprocessing.cpu_count()
 | |
|             print("Number of jobs = {}".format(self.jobs))
 | |
| 
 | |
|         # Normalize bootstrap dir and make sure it exists
 | |
|         self.bootstrap_dir = os.path.abspath(self.args.bootstrap_dir)
 | |
|         os.makedirs(self.bootstrap_dir, exist_ok=True)
 | |
| 
 | |
|         # Output and platform directories are based on bootstrap
 | |
|         self.output_dir = os.path.join(self.bootstrap_dir, 'output')
 | |
|         self.platform_dir = os.path.join(self.bootstrap_dir, 'staging')
 | |
|         self.sysroot = self.args.sysroot
 | |
|         self.libdir = self.args.libdir
 | |
|         self.install_dir = os.path.join(self.output_dir, 'install')
 | |
| 
 | |
|         # If default target isn't set, build everything
 | |
|         self.target = 'all'
 | |
|         if hasattr(self.args, 'target') and self.args.target:
 | |
|             self.target = self.args.target
 | |
| 
 | |
|         target_use = self.args.use if self.args.use else []
 | |
| 
 | |
|         # Unless set, always build test code
 | |
|         if not self.args.notest:
 | |
|             target_use.append('test')
 | |
| 
 | |
|         self.use = UseFlags(target_use)
 | |
| 
 | |
|         # Validate platform directory
 | |
|         assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
 | |
|         assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
 | |
| 
 | |
|         # Make sure output directory exists (or create it)
 | |
|         os.makedirs(self.output_dir, exist_ok=True)
 | |
| 
 | |
|         # Set some default attributes
 | |
|         self.libbase_ver = None
 | |
| 
 | |
|         self.configure_environ()
 | |
| 
 | |
|     def _generate_rustflags(self):
 | |
|         """ Rustflags to include for the build.
 | |
|       """
 | |
|         rust_flags = [
 | |
|             '-L',
 | |
|             '{}/out/Default'.format(self.output_dir),
 | |
|             '-C',
 | |
|             'link-arg=-Wl,--allow-multiple-definition',
 | |
|         ]
 | |
| 
 | |
|         return ' '.join(rust_flags)
 | |
| 
 | |
|     def configure_environ(self):
 | |
|         """ Configure environment variables for GN and Cargo.
 | |
|         """
 | |
|         self.env = os.environ.copy()
 | |
| 
 | |
|         # Make sure cargo home dir exists and has a bin directory
 | |
|         cargo_home = os.path.join(self.output_dir, 'cargo_home')
 | |
|         os.makedirs(cargo_home, exist_ok=True)
 | |
|         os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
 | |
| 
 | |
|         # Configure Rust env variables
 | |
|         self.env['CARGO_TARGET_DIR'] = self.output_dir
 | |
|         self.env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
 | |
|         self.env['RUSTFLAGS'] = self._generate_rustflags()
 | |
|         self.env['CXX_ROOT_PATH'] = os.path.join(self.platform_dir, 'bt')
 | |
| 
 | |
|     def run_command(self, target, args, cwd=None, env=None):
 | |
|         """ Run command and stream the output.
 | |
|         """
 | |
|         # Set some defaults
 | |
|         if not cwd:
 | |
|             cwd = self.platform_dir
 | |
|         if not env:
 | |
|             env = self.env
 | |
| 
 | |
|         log_file = os.path.join(self.output_dir, '{}.log'.format(target))
 | |
|         with open(log_file, 'wb') as lf:
 | |
|             rc = 0
 | |
|             process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
 | |
|             while True:
 | |
|                 line = process.stdout.readline()
 | |
|                 print(line.decode('utf-8'), end="")
 | |
|                 lf.write(line)
 | |
|                 if not line:
 | |
|                     rc = process.poll()
 | |
|                     if rc is not None:
 | |
|                         break
 | |
| 
 | |
|                     time.sleep(0.1)
 | |
| 
 | |
|             if rc != 0:
 | |
|                 raise Exception("Return code is {}".format(rc))
 | |
| 
 | |
|     def _get_basever(self):
 | |
|         if self.libbase_ver:
 | |
|             return self.libbase_ver
 | |
| 
 | |
|         self.libbase_ver = os.environ.get('BASE_VER', '')
 | |
|         if not self.libbase_ver:
 | |
|             base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
 | |
|             try:
 | |
|                 with open(base_file, 'r') as f:
 | |
|                     self.libbase_ver = f.read().strip('\n')
 | |
|             except:
 | |
|                 self.libbase_ver = 'NOT-INSTALLED'
 | |
| 
 | |
|         return self.libbase_ver
 | |
| 
 | |
|     def _gn_default_output(self):
 | |
|         return os.path.join(self.output_dir, 'out/Default')
 | |
| 
 | |
|     def _gn_configure(self):
 | |
|         """ Configure all required parameters for platform2.
 | |
| 
 | |
|         Mostly copied from //common-mk/platform2.py
 | |
|         """
 | |
|         clang = not self.args.no_clang
 | |
| 
 | |
|         def to_gn_string(s):
 | |
|             return '"%s"' % s.replace('"', '\\"')
 | |
| 
 | |
|         def to_gn_list(strs):
 | |
|             return '[%s]' % ','.join([to_gn_string(s) for s in strs])
 | |
| 
 | |
|         def to_gn_args_args(gn_args):
 | |
|             for k, v in gn_args.items():
 | |
|                 if isinstance(v, bool):
 | |
|                     v = str(v).lower()
 | |
|                 elif isinstance(v, list):
 | |
|                     v = to_gn_list(v)
 | |
|                 elif isinstance(v, six.string_types):
 | |
|                     v = to_gn_string(v)
 | |
|                 else:
 | |
|                     raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
 | |
|                 yield '%s=%s' % (k.replace('-', '_'), v)
 | |
| 
 | |
|         gn_args = {
 | |
|             'platform_subdir': 'bt',
 | |
|             'cc': 'clang' if clang else 'gcc',
 | |
|             'cxx': 'clang++' if clang else 'g++',
 | |
|             'ar': 'llvm-ar' if clang else 'ar',
 | |
|             'pkg-config': 'pkg-config',
 | |
|             'clang_cc': clang,
 | |
|             'clang_cxx': clang,
 | |
|             'OS': 'linux',
 | |
|             'sysroot': self.sysroot,
 | |
|             'libdir': os.path.join(self.sysroot, self.libdir),
 | |
|             'build_root': self.output_dir,
 | |
|             'platform2_root': self.platform_dir,
 | |
|             'libbase_ver': self._get_basever(),
 | |
|             'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
 | |
|             'external_cflags': [],
 | |
|             'external_cxxflags': ["-DNDEBUG"],
 | |
|             'enable_werror': False,
 | |
|         }
 | |
| 
 | |
|         if clang:
 | |
|             # Make sure to mark the clang use flag as true
 | |
|             self.use.set_flag('clang', True)
 | |
|             gn_args['external_cxxflags'] += ['-I/usr/include/']
 | |
| 
 | |
|         gn_args_args = list(to_gn_args_args(gn_args))
 | |
|         use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
 | |
|         gn_args_args += ['use={%s}' % (' '.join(use_args))]
 | |
| 
 | |
|         gn_args = [
 | |
|             'gn',
 | |
|             'gen',
 | |
|         ]
 | |
| 
 | |
|         if self.args.verbose:
 | |
|             gn_args.append('-v')
 | |
| 
 | |
|         gn_args += [
 | |
|             '--root=%s' % self.platform_dir,
 | |
|             '--args=%s' % ' '.join(gn_args_args),
 | |
|             self._gn_default_output(),
 | |
|         ]
 | |
| 
 | |
|         if 'PKG_CONFIG_PATH' in self.env:
 | |
|             print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
 | |
| 
 | |
|         self.run_command('configure', gn_args)
 | |
| 
 | |
|     def _gn_build(self, target):
 | |
|         """ Generate the ninja command for the target and run it.
 | |
|         """
 | |
|         args = ['%s:%s' % ('bt', target)]
 | |
|         ninja_args = ['ninja', '-C', self._gn_default_output()]
 | |
|         if self.jobs:
 | |
|             ninja_args += ['-j', str(self.jobs)]
 | |
|         ninja_args += args
 | |
| 
 | |
|         if self.args.verbose:
 | |
|             ninja_args.append('-v')
 | |
| 
 | |
|         self.run_command('build', ninja_args)
 | |
| 
 | |
|     def _rust_configure(self):
 | |
|         """ Generate config file at cargo_home so we use vendored crates.
 | |
|         """
 | |
|         template = """
 | |
|         [source.systembt]
 | |
|         directory = "{}/external/rust/vendor"
 | |
| 
 | |
|         [source.crates-io]
 | |
|         replace-with = "systembt"
 | |
|         local-registry = "/nonexistent"
 | |
|         """
 | |
| 
 | |
|         if not self.args.no_vendored_rust:
 | |
|             contents = template.format(self.platform_dir)
 | |
|             with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
 | |
|                 f.write(contents)
 | |
| 
 | |
|     def _rust_build(self):
 | |
|         """ Run `cargo build` from platform2/bt directory.
 | |
|         """
 | |
|         self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
 | |
| 
 | |
|     def _target_prepare(self):
 | |
|         """ Target to prepare the output directory for building.
 | |
| 
 | |
|         This runs gn gen to generate all rquired files and set up the Rust
 | |
|         config properly. This will be run
 | |
|         """
 | |
|         self._gn_configure()
 | |
|         self._rust_configure()
 | |
| 
 | |
|     def _target_tools(self):
 | |
|         """ Build the tools target in an already prepared environment.
 | |
|         """
 | |
|         self._gn_build('tools')
 | |
| 
 | |
|         # Also copy bluetooth_packetgen to CARGO_HOME so it's available
 | |
|         shutil.copy(
 | |
|             os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
 | |
| 
 | |
|     def _target_docs(self):
 | |
|         """Build the Rust docs."""
 | |
|         self.run_command('docs', ['cargo', 'doc'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
 | |
| 
 | |
|     def _target_rust(self):
 | |
|         """ Build rust artifacts in an already prepared environment.
 | |
|         """
 | |
|         self._rust_build()
 | |
| 
 | |
|     def _target_rootcanal(self):
 | |
|         """ Build rust artifacts for RootCanal in an already prepared environment.
 | |
|         """
 | |
|         self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt/tools/rootcanal'), env=self.env)
 | |
| 
 | |
|     def _target_main(self):
 | |
|         """ Build the main GN artifacts in an already prepared environment.
 | |
|         """
 | |
|         self._gn_build('all')
 | |
| 
 | |
|     def _target_test(self):
 | |
|         """ Runs the host tests.
 | |
|         """
 | |
|         # Rust tests first
 | |
|         rust_test_cmd = ['cargo', 'test']
 | |
|         if self.args.test_name:
 | |
|             rust_test_cmd = rust_test_cmd + [self.args.test_name]
 | |
| 
 | |
|         self.run_command('test', rust_test_cmd, cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
 | |
|         self.run_command('test', rust_test_cmd, cwd=os.path.join(self.platform_dir, 'bt/tools/rootcanal'), env=self.env)
 | |
| 
 | |
|         # Host tests second based on host test list
 | |
|         for t in HOST_TESTS:
 | |
|             self.run_command(
 | |
|                 'test', [os.path.join(self.output_dir, 'out/Default', t)],
 | |
|                 cwd=os.path.join(self.output_dir),
 | |
|                 env=self.env)
 | |
| 
 | |
|     def _target_install(self):
 | |
|         """ Installs files required to run Floss to install directory.
 | |
|         """
 | |
|         # First make the install directory
 | |
|         prefix = self.install_dir
 | |
|         os.makedirs(prefix, exist_ok=True)
 | |
| 
 | |
|         # Next save the cwd and change to install directory
 | |
|         last_cwd = os.getcwd()
 | |
|         os.chdir(prefix)
 | |
| 
 | |
|         bindir = os.path.join(self.output_dir, 'debug')
 | |
|         srcdir = os.path.dirname(__file__)
 | |
| 
 | |
|         install_map = [
 | |
|             {
 | |
|                 'src': os.path.join(bindir, 'btadapterd'),
 | |
|                 'dst': 'usr/libexec/bluetooth/btadapterd',
 | |
|                 'strip': True
 | |
|             },
 | |
|             {
 | |
|                 'src': os.path.join(bindir, 'btmanagerd'),
 | |
|                 'dst': 'usr/libexec/bluetooth/btmanagerd',
 | |
|                 'strip': True
 | |
|             },
 | |
|             {
 | |
|                 'src': os.path.join(bindir, 'btclient'),
 | |
|                 'dst': 'usr/local/bin/btclient',
 | |
|                 'strip': True
 | |
|             },
 | |
|         ]
 | |
| 
 | |
|         for v in install_map:
 | |
|             src, partial_dst, strip = (v['src'], v['dst'], v['strip'])
 | |
|             dst = os.path.join(prefix, partial_dst)
 | |
| 
 | |
|             # Create dst directory first and copy file there
 | |
|             os.makedirs(os.path.dirname(dst), exist_ok=True)
 | |
|             print('Installing {}'.format(dst))
 | |
|             shutil.copy(src, dst)
 | |
| 
 | |
|             # Binary should be marked for strip and no-strip option shouldn't be
 | |
|             # set. No-strip is useful while debugging.
 | |
|             if strip and not self.args.no_strip:
 | |
|                 self.run_command('install', ['llvm-strip', dst])
 | |
| 
 | |
|         # Put all files into a tar.gz for easier installation
 | |
|         tar_location = os.path.join(prefix, 'floss.tar.gz')
 | |
|         with tarfile.open(tar_location, 'w:gz') as tar:
 | |
|             for v in install_map:
 | |
|                 tar.add(v['dst'])
 | |
| 
 | |
|         print('Tarball created at {}'.format(tar_location))
 | |
| 
 | |
|     def _target_clean(self):
 | |
|         """ Delete the output directory entirely.
 | |
|         """
 | |
|         shutil.rmtree(self.output_dir)
 | |
| 
 | |
|         # Remove Cargo.lock that may have become generated
 | |
|         try:
 | |
|             os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock'))
 | |
|         except FileNotFoundError:
 | |
|             pass
 | |
| 
 | |
|     def _target_all(self):
 | |
|         """ Build all common targets (skipping doc, test, and clean).
 | |
|         """
 | |
|         self._target_prepare()
 | |
|         self._target_tools()
 | |
|         self._target_main()
 | |
|         self._target_rust()
 | |
| 
 | |
|     def build(self):
 | |
|         """ Builds according to self.target
 | |
|         """
 | |
|         print('Building target ', self.target)
 | |
| 
 | |
|         # Validate that the target is valid
 | |
|         if self.target not in VALID_TARGETS:
 | |
|             print('Target {} is not valid. Must be in {}', self.target, VALID_TARGETS)
 | |
|             return
 | |
| 
 | |
|         if self.target == 'prepare':
 | |
|             self._target_prepare()
 | |
|         elif self.target == 'tools':
 | |
|             self._target_tools()
 | |
|         elif self.target == 'rootcanal':
 | |
|             self._target_rootcanal()
 | |
|         elif self.target == 'rust':
 | |
|             self._target_rust()
 | |
|         elif self.target == 'docs':
 | |
|             self._target_docs()
 | |
|         elif self.target == 'main':
 | |
|             self._target_main()
 | |
|         elif self.target == 'test':
 | |
|             self._target_test()
 | |
|         elif self.target == 'clean':
 | |
|             self._target_clean()
 | |
|         elif self.target == 'install':
 | |
|             self._target_install()
 | |
|         elif self.target == 'all':
 | |
|             self._target_all()
 | |
| 
 | |
| 
 | |
| class Bootstrap():
 | |
| 
 | |
|     def __init__(self, base_dir, bt_dir):
 | |
|         """ Construct bootstrapper.
 | |
| 
 | |
|         Args:
 | |
|             base_dir: Where to stage everything.
 | |
|             bt_dir: Where bluetooth source is kept (will be symlinked)
 | |
|         """
 | |
|         self.base_dir = os.path.abspath(base_dir)
 | |
|         self.bt_dir = os.path.abspath(bt_dir)
 | |
| 
 | |
|         # Create base directory if it doesn't already exist
 | |
|         os.makedirs(self.base_dir, exist_ok=True)
 | |
| 
 | |
|         if not os.path.isdir(self.bt_dir):
 | |
|             raise Exception('{} is not a valid directory'.format(self.bt_dir))
 | |
| 
 | |
|         self.git_dir = os.path.join(self.base_dir, 'repos')
 | |
|         self.staging_dir = os.path.join(self.base_dir, 'staging')
 | |
|         self.output_dir = os.path.join(self.base_dir, 'output')
 | |
|         self.external_dir = os.path.join(self.base_dir, 'staging', 'external')
 | |
| 
 | |
|         self.dir_setup_complete = os.path.join(self.base_dir, '.setup-complete')
 | |
| 
 | |
|     def _update_platform2(self):
 | |
|         """Updates repositories used for build."""
 | |
|         for repo in BOOTSTRAP_GIT_REPOS.keys():
 | |
|             cwd = os.path.join(self.git_dir, repo)
 | |
|             subprocess.check_call(['git', 'pull'], cwd=cwd)
 | |
| 
 | |
|     def _setup_platform2(self):
 | |
|         """ Set up platform2.
 | |
| 
 | |
|         This will check out all the git repos and symlink everything correctly.
 | |
|         """
 | |
| 
 | |
|         # Create all directories we will need to use
 | |
|         for dirpath in [self.git_dir, self.staging_dir, self.output_dir, self.external_dir]:
 | |
|             os.makedirs(dirpath, exist_ok=True)
 | |
| 
 | |
|         # If already set up, only update platform2
 | |
|         if os.path.isfile(self.dir_setup_complete):
 | |
|             print('{} already set-up. Updating instead.'.format(self.base_dir))
 | |
|             self._update_platform2()
 | |
|         else:
 | |
|             # Check out all repos in git directory
 | |
|             for repo in BOOTSTRAP_GIT_REPOS.values():
 | |
|                 subprocess.check_call(['git', 'clone', repo], cwd=self.git_dir)
 | |
| 
 | |
|         # Symlink things
 | |
|         symlinks = [
 | |
|             (os.path.join(self.git_dir, 'platform2', 'common-mk'), os.path.join(self.staging_dir, 'common-mk')),
 | |
|             (os.path.join(self.git_dir, 'platform2', '.gn'), os.path.join(self.staging_dir, '.gn')),
 | |
|             (os.path.join(self.bt_dir), os.path.join(self.staging_dir, 'bt')),
 | |
|             (os.path.join(self.git_dir, 'rust_crates'), os.path.join(self.external_dir, 'rust')),
 | |
|             (os.path.join(self.git_dir, 'proto_logging'), os.path.join(self.external_dir, 'proto_logging')),
 | |
|         ]
 | |
| 
 | |
|         # Create symlinks
 | |
|         for pairs in symlinks:
 | |
|             (src, dst) = pairs
 | |
|             try:
 | |
|                 os.unlink(dst)
 | |
|             except Exception as e:
 | |
|                 print(e)
 | |
|             os.symlink(src, dst)
 | |
| 
 | |
|         # Write to setup complete file so we don't repeat this step
 | |
|         with open(self.dir_setup_complete, 'w') as f:
 | |
|             f.write('Setup complete.')
 | |
| 
 | |
|     def _pretty_print_install(self, install_cmd, packages, line_limit=80):
 | |
|         """ Pretty print an install command.
 | |
| 
 | |
|         Args:
 | |
|             install_cmd: Prefixed install command.
 | |
|             packages: Enumerate packages and append them to install command.
 | |
|             line_limit: Number of characters per line.
 | |
| 
 | |
|         Return:
 | |
|             Array of lines to join and print.
 | |
|         """
 | |
|         install = [install_cmd]
 | |
|         line = '  '
 | |
|         # Remainder needed = space + len(pkg) + space + \
 | |
|         # Assuming 80 character lines, that's 80 - 3 = 77
 | |
|         line_limit = line_limit - 3
 | |
|         for pkg in packages:
 | |
|             if len(line) + len(pkg) < line_limit:
 | |
|                 line = '{}{} '.format(line, pkg)
 | |
|             else:
 | |
|                 install.append(line)
 | |
|                 line = '  {} '.format(pkg)
 | |
| 
 | |
|         if len(line) > 0:
 | |
|             install.append(line)
 | |
| 
 | |
|         return install
 | |
| 
 | |
|     def _check_package_installed(self, package, cmd, predicate):
 | |
|         """Check that the given package is installed.
 | |
| 
 | |
|         Args:
 | |
|             package: Check that this package is installed.
 | |
|             cmd: Command prefix to check if installed (package appended to end)
 | |
|             predicate: Function/lambda to check if package is installed based
 | |
|                        on output. Takes string output and returns boolean.
 | |
| 
 | |
|         Return:
 | |
|             True if package is installed.
 | |
|         """
 | |
|         try:
 | |
|             output = subprocess.check_output(cmd + [package], stderr=subprocess.STDOUT)
 | |
|             is_installed = predicate(output.decode('utf-8'))
 | |
|             print('  {} is {}'.format(package, 'installed' if is_installed else 'missing'))
 | |
| 
 | |
|             return is_installed
 | |
|         except Exception as e:
 | |
|             print(e)
 | |
|             return False
 | |
| 
 | |
|     def _get_command_output(self, cmd):
 | |
|         """Runs the command and gets the output.
 | |
| 
 | |
|         Args:
 | |
|             cmd: Command to run.
 | |
| 
 | |
|         Return:
 | |
|             Tuple (Success, Output). Success represents if the command ran ok.
 | |
|         """
 | |
|         try:
 | |
|             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 | |
|             return (True, output.decode('utf-8').split('\n'))
 | |
|         except Exception as e:
 | |
|             print(e)
 | |
|             return (False, "")
 | |
| 
 | |
|     def _print_missing_packages(self):
 | |
|         """Print any missing packages found via apt.
 | |
| 
 | |
|         This will find any missing packages necessary for build using apt and
 | |
|         print it out as an apt-get install printf.
 | |
|         """
 | |
|         print('Checking for any missing packages...')
 | |
| 
 | |
|         (success, output) = self._get_command_output(APT_PKG_LIST)
 | |
|         if not success:
 | |
|             raise Exception("Could not query apt for packages.")
 | |
| 
 | |
|         packages_installed = {}
 | |
|         for line in output:
 | |
|             if 'installed' in line:
 | |
|                 split = line.split('/', 2)
 | |
|                 packages_installed[split[0]] = True
 | |
| 
 | |
|         need_packages = []
 | |
|         for pkg in REQUIRED_APT_PACKAGES:
 | |
|             if pkg not in packages_installed:
 | |
|                 need_packages.append(pkg)
 | |
| 
 | |
|         # No packages need to be installed
 | |
|         if len(need_packages) == 0:
 | |
|             print('+ All required packages are installed')
 | |
|             return
 | |
| 
 | |
|         install = self._pretty_print_install('sudo apt-get install', need_packages)
 | |
| 
 | |
|         # Print all lines so they can be run in cmdline
 | |
|         print('Missing system packages. Run the following command: ')
 | |
|         print(' \\\n'.join(install))
 | |
| 
 | |
|     def _print_missing_rust_packages(self):
 | |
|         """Print any missing packages found via cargo.
 | |
| 
 | |
|         This will find any missing packages necessary for build using cargo and
 | |
|         print it out as a cargo-install printf.
 | |
|         """
 | |
|         print('Checking for any missing cargo packages...')
 | |
| 
 | |
|         (success, output) = self._get_command_output(CARGO_PKG_LIST)
 | |
|         if not success:
 | |
|             raise Exception("Could not query cargo for packages.")
 | |
| 
 | |
|         packages_installed = {}
 | |
|         for line in output:
 | |
|             # Cargo installed packages have this format
 | |
|             # <crate name> <version>:
 | |
|             #   <binary name>
 | |
|             # We only care about the crates themselves
 | |
|             if ':' not in line:
 | |
|                 continue
 | |
| 
 | |
|             split = line.split(' ', 2)
 | |
|             packages_installed[split[0]] = True
 | |
| 
 | |
|         need_packages = []
 | |
|         for pkg in REQUIRED_CARGO_PACKAGES:
 | |
|             if pkg not in packages_installed:
 | |
|                 need_packages.append(pkg)
 | |
| 
 | |
|         # No packages to be installed
 | |
|         if len(need_packages) == 0:
 | |
|             print('+ All required cargo packages are installed')
 | |
|             return
 | |
| 
 | |
|         install = self._pretty_print_install('cargo install', need_packages)
 | |
|         print('Missing cargo packages. Run the following command: ')
 | |
|         print(' \\\n'.join(install))
 | |
| 
 | |
|     def bootstrap(self):
 | |
|         """ Bootstrap the Linux build."""
 | |
|         self._setup_platform2()
 | |
|         self._print_missing_packages()
 | |
|         self._print_missing_rust_packages()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     parser = argparse.ArgumentParser(description='Simple build for host.')
 | |
|     parser.add_argument(
 | |
|         '--bootstrap-dir', help='Directory to run bootstrap on (or was previously run on).', default="~/.floss")
 | |
|     parser.add_argument(
 | |
|         '--run-bootstrap',
 | |
|         help='Run bootstrap code to verify build env is ok to build.',
 | |
|         default=False,
 | |
|         action='store_true')
 | |
|     parser.add_argument('--no-clang', help='Use clang compiler.', default=False, action='store_true')
 | |
|     parser.add_argument(
 | |
|         '--no-strip', help='Skip stripping binaries during install.', default=False, action='store_true')
 | |
|     parser.add_argument('--use', help='Set a specific use flag.')
 | |
|     parser.add_argument('--notest', help='Don\'t compile test code.', default=False, action='store_true')
 | |
|     parser.add_argument('--test-name', help='Run test with this string in the name.', default=None)
 | |
|     parser.add_argument('--target', help='Run specific build target')
 | |
|     parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
 | |
|     parser.add_argument('--libdir', help='Libdir - default = usr/lib', default='usr/lib')
 | |
|     parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
 | |
|     parser.add_argument(
 | |
|         '--no-vendored-rust', help='Do not use vendored rust crates', default=False, action='store_true')
 | |
|     parser.add_argument('--verbose', help='Verbose logs for build.')
 | |
|     args = parser.parse_args()
 | |
| 
 | |
|     # Make sure we get absolute path + expanded path for bootstrap directory
 | |
|     args.bootstrap_dir = os.path.abspath(os.path.expanduser(args.bootstrap_dir))
 | |
| 
 | |
|     if args.run_bootstrap:
 | |
|         bootstrap = Bootstrap(args.bootstrap_dir, os.path.dirname(__file__))
 | |
|         bootstrap.bootstrap()
 | |
|     else:
 | |
|         build = HostBuild(args)
 | |
|         build.build()
 |