| #!/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() |