diff options
Diffstat (limited to 'bootstrap.py')
-rwxr-xr-x | bootstrap.py | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/bootstrap.py b/bootstrap.py new file mode 100755 index 0000000000..c7c04bfd75 --- /dev/null +++ b/bootstrap.py @@ -0,0 +1,243 @@ +#!/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 packages/modules/Bluetooth/system', required=True) + + args = parser.parse_args() + bootstrap = Bootstrap(args.base_dir, args.bt_dir) + bootstrap.bootstrap() |