diff options
author | 2021-11-08 15:39:30 -0800 | |
---|---|---|
committer | 2021-11-08 15:47:32 -0800 | |
commit | ddd429fec4f5794d3f17037b2124e23f8820a364 (patch) | |
tree | 60956ace3ede54df6596c4d296a9704e15ab2e13 /build.py | |
parent | a31dda4e17f61aae0df48acf4ac4df0c916c5093 (diff) |
Floss: Fix build post repo migration
Bug: 205591223
Test: Run `./build.py`
Tag: #floss
Change-Id: Ibc75b1e171013a24826c8b5f5cbc7b8fbf605547
Diffstat (limited to 'build.py')
-rwxr-xr-x | build.py | 720 |
1 files changed, 0 insertions, 720 deletions
diff --git a/build.py b/build.py deleted file mode 100755 index 3fcb2fb14a..0000000000 --- a/build.py +++ /dev/null @@ -1,720 +0,0 @@ -#!/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 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 = [ - 'prepare', # Prepare the output directory (gn gen + rust setup) - 'tools', # Build the host tools (i.e. packetgen) - 'rust', # Build only the rust components + copy artifacts to output dir - 'main', # Build the main C++ codebase - 'test', # Run the unit tests - 'clean', # Clean up output directory - 'all', # All targets except test and clean -] - -# 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++-dev', - 'libdbus-1-dev', - 'libevent-dev', - 'libevent-dev', - 'libflatbuffers-dev', - 'libflatbuffers1', - 'libgl1-mesa-dev', - 'libglib2.0-dev', - 'liblz4-tool', - 'libncurses5', - 'libnss3-dev', - 'libprotobuf-dev', - 'libre2-9', - '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 - - # 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': [], - '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_rust(self): - """ Build rust artifacts in an already prepared environment. - """ - self._rust_build() - rust_dir = os.path.join(self._gn_default_output(), 'rust') - if os.path.exists(rust_dir): - shutil.rmtree(rust_dir) - shutil.copytree(os.path.join(self.output_dir, 'debug'), rust_dir) - - 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 - self.run_command('test', ['cargo', 'test'], cwd=os.path.join(self.platform_dir, 'bt'), 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_clean(self): - """ Delete the output directory entirely. - """ - shutil.rmtree(self.output_dir) - # Remove Cargo.lock that may have become generated - os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock')) - - def _target_all(self): - """ Build all common targets (skipping 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) - - if self.target == 'prepare': - self._target_prepare() - elif self.target == 'tools': - self._target_tools() - elif self.target == 'rust': - self._target_rust() - elif self.target == 'main': - self._target_main() - elif self.target == 'test': - self._target_test() - elif self.target == 'clean': - self._target_clean() - 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. - """ - - # If already set up, exit early - if os.path.isfile(self.dir_setup_complete): - print('{} already set-up. Updating instead.'.format(self.base_dir)) - self._update_platform2() - return - - # 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) - - # 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 - 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('--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('--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() |