#!/usr/bin/env python3 # # Copyright (C) 2021 Red Hat, Inc. # SPDX-License-Identifier: LGPL-2.1-or-later import argparse import os import pathlib import pty import shutil import subprocess import sys class Parser: def __init__(self): # Options that are common to all actions that use containers containerparser = argparse.ArgumentParser(add_help=False) containerparser.add_argument( "target", help="perform action on target OS", ) containerparser.add_argument( "--engine", choices=["auto", "podman", "docker"], default="auto", help="container engine to use", ) containerparser.add_argument( "--login", default=os.getlogin(), # exempt from syntax-check help="login to use inside the container", ) containerparser.add_argument( "--image-prefix", default="registry.gitlab.com/libvirt/libvirt/ci-", help="use container images from non-default location", ) containerparser.add_argument( "--image-tag", default=":latest", help="use container images with non-default tags", ) # Options that are common to all actions that call the # project's build system mesonparser = argparse.ArgumentParser(add_help=False) mesonparser.add_argument( "--meson-args", default="", help="additional arguments passed to meson " "(eg --meson-args='-Dopt1=enabled -Dopt2=disabled')", ) mesonparser.add_argument( "--ninja-args", default="", help="additional arguments passed to ninja", ) # Options that are common to all actions that use lcitool lcitoolparser = argparse.ArgumentParser(add_help=False) lcitoolparser.add_argument( "--lcitool", metavar="PATH", default="lcitool", help="path to lcitool binary", ) # Main parser self.parser = argparse.ArgumentParser() subparsers = self.parser.add_subparsers( dest="action", metavar="ACTION", ) subparsers.required = True # build action buildparser = subparsers.add_parser( "build", help="run a build in a container", parents=[containerparser, mesonparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) buildparser.set_defaults(func=Application.action_build) # test action testparser = subparsers.add_parser( "test", help="run a build in a container (including tests)", parents=[containerparser, mesonparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) testparser.set_defaults(func=Application.action_test) # shell action shellparser = subparsers.add_parser( "shell", help="start a shell in a container", parents=[containerparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) shellparser.set_defaults(func=Application.action_shell) # list-images action listimagesparser = subparsers.add_parser( "list-images", help="list known container images", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) listimagesparser.set_defaults(func=Application.action_list_images) # refresh action refreshparser = subparsers.add_parser( "refresh", help="refresh data generated with lcitool", parents=[lcitoolparser], formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) refreshparser.set_defaults(func=Application.action_refresh) def parse(self): return self.parser.parse_args() class Application: def __init__(self): self.basedir = pathlib.Path(__file__).resolve().parent self.args = Parser().parse() if self.args.action == "refresh": if not shutil.which(self.args.lcitool): sys.exit("error: 'lcitool' not installed") def make_run(self, target): args = [ "-C", self.basedir, target, ] if self.args.action in ["build", "test", "shell"]: args.extend([ f"CI_ENGINE={self.args.engine}", f"CI_USER_LOGIN={self.args.login}", f"CI_IMAGE_PREFIX={self.args.image_prefix}", f"CI_IMAGE_TAG={self.args.image_tag}", ]) if self.args.action in ["build", "test"]: args.extend([ f"CI_MESON_ARGS={self.args.meson_args}", f"CI_NINJA_ARGS={self.args.ninja_args}", ]) if pty.spawn(["make"] + args) != 0: sys.exit("error: 'make' failed") def lcitool_run(self, args): output = subprocess.check_output([self.args.lcitool] + args) return output.decode("utf-8") def lcitool_get_hosts(self): output = self.lcitool_run(["hosts"]) return output.splitlines() def generate_dockerfile(self, host, cross=None): args = ["dockerfile", host, "libvirt"] outdir = self.basedir.joinpath("containers") outfile = f"ci-{host}.Dockerfile" if cross: args.extend(["--cross", cross]) outfile = f"ci-{host}-cross-{cross}.Dockerfile" outpath = outdir.joinpath(outfile) print(outpath) output = self.lcitool_run(args) with open(outpath, "w") as f: f.write(output) def generate_vars(self, host): args = ["variables", host, "libvirt"] outdir = self.basedir.joinpath("cirrus") outfile = f"{host}.vars" outpath = outdir.joinpath(outfile) print(outpath) output = self.lcitool_run(args) with open(outpath, "w") as f: f.write(output) def refresh_containers(self): debian_cross = [ "aarch64", "armv6l", "armv7l", "i686", "mips", "mips64el", "mipsel", "ppc64le", "s390x", ] fedora_cross = [ "mingw32", "mingw64", ] for host in self.lcitool_get_hosts(): if host.startswith("freebsd-") or host.startswith("macos-"): continue self.generate_dockerfile(host) if host == "fedora-rawhide": for cross in fedora_cross: self.generate_dockerfile(host, cross) if host.startswith("debian-"): for cross in debian_cross: if host == "debian-sid" and cross == "mips": continue self.generate_dockerfile(host, cross) def refresh_cirrus(self): for host in self.lcitool_get_hosts(): if not (host.startswith("freebsd-") or host.startswith("macos-")): continue self.generate_vars(host) def action_build(self): self.make_run(f"ci-build@{self.args.target}") def action_test(self): self.make_run(f"ci-test@{self.args.target}") def action_shell(self): self.make_run(f"ci-shell@{self.args.target}") def action_list_images(self): self.make_run(f"ci-list-images") def action_refresh(self): self.refresh_containers() self.refresh_cirrus() def run(self): self.args.func(self) if __name__ == "__main__": Application().run()