blob: 5d03f6ef64556f923f2ecc1a14b919cdf5e2d864 [file] [log] [blame]
#!/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.
""" Bootstrap script to help set up Linux build. """
import argparse
import os
import subprocess
PLATFORM2_GIT = 'https://chromium.googlesource.com/chromiumos/platform2'
RUST_CRATES_GIT = 'https://chromium.googlesource.com/chromiumos/third_party/rust_crates'
PROTO_LOGGING_GIT = 'https://android.googlesource.com/platform/frameworks/proto_logging'
# List of packages required for linux build
REQUIRED_APT_PACKAGES = [
'bison',
'build-essential',
'curl',
'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 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)
if not os.path.isdir(self.base_dir):
raise Exception('{} is not a valid directory'.format(self.base_dir))
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 _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('{} is already set-up'.format(self.base_dir))
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 [PLATFORM2_GIT, RUST_CRATES_GIT, PROTO_LOGGING_GIT]:
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 _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...')
need_packages = []
for pkg in REQUIRED_APT_PACKAGES:
if not self._check_package_installed(pkg, APT_PKG_LIST, lambda output: 'installed' in output):
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...')
need_packages = []
for pkg in REQUIRED_CARGO_PACKAGES:
if not self._check_package_installed(pkg, CARGO_PKG_LIST, lambda output: pkg in output):
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='Bootstrap Linux build')
parser.add_argument('--base-dir', help='Where to create build directories.', required=True)
parser.add_argument('--bt-dir', help='Path to system/bt', required=True)
args = parser.parse_args()
bootstrap = Bootstrap(args.base_dir, args.bt_dir)
bootstrap.bootstrap()