summaryrefslogtreecommitdiff
path: root/floss/build/build-in-podman.py
diff options
context:
space:
mode:
Diffstat (limited to 'floss/build/build-in-podman.py')
-rwxr-xr-xfloss/build/build-in-podman.py188
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()