diff options
Diffstat (limited to 'floss/build/build-in-podman.py')
-rwxr-xr-x | floss/build/build-in-podman.py | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/floss/build/build-in-podman.py b/floss/build/build-in-podman.py new file mode 100755 index 0000000000..ba4591482e --- /dev/null +++ b/floss/build/build-in-podman.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +import argparse +import os +import subprocess +import sys +import time + +SRC_MOUNT = "/root/src" +STAGING_MOUNT = "/root/.floss" + + +class FlossPodmanRunner: + """Runs Floss build inside podman container.""" + + # Commands to run for build + BUILD_COMMANDS = [ + # First run bootstrap to get latest code + create symlinks + [f'{SRC_MOUNT}/build.py', '--run-bootstrap'], + + # Clean up any previous artifacts inside the volume + [f'{SRC_MOUNT}/build.py', '--target', 'clean'], + + # Run normal code builder + [f'{SRC_MOUNT}/build.py', '--target', 'all'], + + # Run tests + [f'{SRC_MOUNT}/build.py', '--target', 'test'], + ] + + def __init__(self, workdir, rootdir, image_tag, volume_name, container_name, staging_dir): + """ Constructor. + + Args: + workdir: Current working directory (should be the script path). + rootdir: Root directory for Bluetooth. + image_tag: Tag for podman image used for building. + volume_name: Volume name used for storing artifacts. + container_name: Name for running container instance. + staging_dir: Directory to mount for artifacts instead of using volume. + """ + self.workdir = workdir + self.rootdir = rootdir + self.image_tag = image_tag + self.env = os.environ.copy() + + # Name of running container + self.container_name = container_name + + # Name of volume to write output. + self.volume_name = volume_name + # Staging dir where we send output instead of the volume. + self.staging_dir = staging_dir + + def run_command(self, target, args, cwd=None, env=None, ignore_rc=False): + """ Run command and stream the output. + """ + # Set some defaults + if not cwd: + cwd = self.workdir + if not env: + env = self.env + + 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="") + if not line: + rc = process.poll() + if rc is not None: + break + + time.sleep(0.1) + + if rc != 0 and not ignore_rc: + raise Exception("{} failed. Return code is {}".format(target, rc)) + + def _create_volume_if_needed(self): + # Check if the volume exists. Otherwise create it. + try: + subprocess.check_output(['podman', 'volume', 'inspect', self.volume_name]) + except: + self.run_command('podman volume create', ['podman', 'volume', 'create', self.volume_name]) + + def start_container(self): + """Starts the podman container with correct mounts.""" + # Stop any previously started container. + self.stop_container(ignore_error=True) + + # Create volume and create mount string + if self.staging_dir: + mount_output_volume = 'type=bind,src={},dst={}'.format(self.staging_dir, STAGING_MOUNT) + else: + # If not using staging dir, use the volume instead + self._create_volume_if_needed() + mount_output_volume = 'type=volume,src={},dst={}'.format(self.volume_name, STAGING_MOUNT) + + # Mount the source directory + mount_src_dir = 'type=bind,src={},dst={}'.format(self.rootdir, SRC_MOUNT) + + # Run the podman image. It will run `tail` indefinitely so the container + # doesn't close and we can run `podman exec` on it. + self.run_command('podman run', [ + 'podman', 'run', '--name', self.container_name, '--mount', mount_output_volume, '--mount', mount_src_dir, + '-d', self.image_tag, 'tail', '-f', '/dev/null' + ]) + + def stop_container(self, ignore_error=False): + """Stops the podman container for build.""" + self.run_command('podman stop', ['podman', 'stop', '-t', '1', self.container_name], ignore_rc=ignore_error) + self.run_command('podman rm', ['podman', 'rm', self.container_name], ignore_rc=ignore_error) + + def do_build(self): + """Runs the basic build commands.""" + # Start container before building + self.start_container() + + try: + # Run all commands + for i, cmd in enumerate(self.BUILD_COMMANDS): + self.run_command('podman exec #{}'.format(i), ['podman', 'exec', '-it', self.container_name] + cmd) + finally: + # Always stop container before exiting + self.stop_container() + + def print_do_build(self): + """Prints the commands for building.""" + podman_exec = ['podman', 'exec', '-it', self.container_name] + print('Normally, build would run the following commands: \n') + for cmd in self.BUILD_COMMANDS: + print(' '.join(podman_exec + cmd)) + + def check_podman_runnable(self): + try: + subprocess.check_output(['podman', 'ps'], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: + if 'denied' in err.output.decode('utf-8'): + print('Run script as sudo') + else: + print('Unexpected error: {}'.format(err.output.decode('utf-8'))) + + return False + + # No exception means podman is ok + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser('Builder Floss inside podman image.') + parser.add_argument( + '--only-start', + action='store_true', + default=False, + help='Only start the container. Prints the commands it would have ran.') + parser.add_argument('--only-stop', action='store_true', default=False, help='Only stop the container and exit.') + parser.add_argument('--image-tag', default='floss:latest', help='Podman image to use to build.') + parser.add_argument( + '--volume-tag', + default='floss-out', + help='Name of volume to use. This is where build artifacts will be stored by default.') + parser.add_argument( + '--staging-dir', + default=None, + help='Staging directory to use instead of volume. Build artifacts will be written here.') + parser.add_argument('--container-name', default='floss-podman-runner', help='What to name the started container') + args = parser.parse_args() + + # cwd should be set to same directory as this script (that's where Podmanfile + # is kept). + workdir = os.path.dirname(os.path.abspath(sys.argv[0])) + rootdir = os.path.abspath(os.path.join(workdir, '../..')) + + # Determine staging directory absolute path + staging = os.path.abspath(args.staging_dir) if args.staging_dir else None + + fdr = FlossPodmanRunner(workdir, rootdir, args.image_tag, args.volume_tag, args.container_name, staging) + + # Make sure podman is runnable before continuing + if fdr.check_podman_runnable(): + # Handle some flags + if args.only_start: + fdr.start_container() + fdr.print_do_build() + elif args.only_stop: + fdr.stop_container() + else: + fdr.do_build() |