summaryrefslogtreecommitdiff
path: root/build.py
diff options
context:
space:
mode:
author Martin Brabham <optedoblivion@google.com> 2021-11-08 15:39:30 -0800
committer Martin Brabham <optedoblivion@google.com> 2021-11-08 15:47:32 -0800
commitddd429fec4f5794d3f17037b2124e23f8820a364 (patch)
tree60956ace3ede54df6596c4d296a9704e15ab2e13 /build.py
parenta31dda4e17f61aae0df48acf4ac4df0c916c5093 (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-xbuild.py720
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()