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()
|