#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright 2022 sysmocom - s.f.m.c. GmbH import importlib.util import os import shutil import subprocess import sys import tempfile import inspect import lib.config # Argparse result args = None # Print output of commands as they run, not only on error cmds_verbose = False def add_shared_arguments(parser): """ Arguments shared between build_srcpkg.py and update_obs_project.py. """ group_reqprog = parser.add_argument_group("required program options") group_reqprog.add_argument("-d", "--docker", help="run in docker to avoid" " installing required pkgs", action="store_true") group_reqprog.add_argument("-i", "--ignore-req", action="store_true", help="skip required programs check") group_feed = parser.add_argument_group("feed options", "The feed option implies the source revision (nightly/master: build" " the master branch; latest: build the last tag) and when a package" " is considered outdated (latest/master: when the commit changes on" " the latest tag or on master; nightly: always).") group_feed.add_argument("-f", "--feed", help="package feed (default:" " nightly, can also be master or latest)", metavar="FEED", default="nightly", choices=lib.config.feeds) group_pkg = parser.add_argument_group("package options") group_pkg.add_argument("-a", "--allow-unknown-package", action="store_true", help="don't complain if the name of the package is" " not stored in lib/config.py") group_pkg.add_argument("-e", "--version-append", help="add a string at the end of the version, e.g." " '~osmocom' for the wireshark package") group_pkg.add_argument("-C", "--configure-append", help="add these parameters to the ./configure line" " (e.g. ' --enable-sanitize')") group_pkg.add_argument("-D", "--disable-manuals", action="store_true", help="speed up build by removing the" " osmo-gsm-manuals dependency and not building" " asciidoc and doxygen manuals") group_git = parser.add_argument_group("git options") group_git.add_argument("-b", "--git-branch", help="instead of using a" " branch based on the feed, checkout this git" " branch", metavar="BRANCH", default=None) group_git.add_argument("-s", "--git-skip-fetch", help="do not fetch already cloned git repositories", action="store_false", dest="git_fetch") group_git.add_argument("-S", "--git-skip-checkout", help="do not checkout and reset to a branch/tag", action="store_false", dest="git_checkout") group_meta = parser.add_argument_group("meta package options", "Osmocom packages depend on a meta-package such as osmocom-nightly." " These meta-packages conflict with each other to ensure e.g." " incompatible nightly and latest packages are not mixed by accident." " For nightly we don't have API compatibility and so we let these" " depend on a meta package of a specific build date (-c).") group_meta.add_argument("-m", "--meta", action="store_true", help="build a meta package (e.g. osmocom-nightly)") group_meta.add_argument("-c", "--conflict-version", nargs="?", help="depend on meta-package of specific version") group_meta.add_argument("-p", "--conflict-pkgname", nargs="?", help="name of the meta-package to depend on" " (default: osmocom-$feed)") group_meta.add_argument("-M", "--no-meta", action="store_true", help="Don't depend on the meta package (use when" " building one-off packages for development)") group_devel = parser.add_argument_group("development options") group_devel.add_argument("-v", "--verbose", action="store_true", help="always print shell commands and their output," " instead of only printing them on error") return { "devel": group_devel, "feed": group_feed, "git": group_git, "meta": group_meta, "pkg": group_pkg, "reqprog": group_reqprog, } def set_cmds_verbose(new_val): global cmds_verbose cmds_verbose = new_val def set_args(new_val): global args args = new_val set_cmds_verbose(args.verbose) def check_required_programs(): ok = True for program in lib.config.required_programs: if not shutil.which(program): print(f"ERROR: missing program: {program}") ok = False for module in lib.config.required_python_modules: if not importlib.util.find_spec(module): print(f"ERROR: missing python3 module: {module}") ok = False if not ok: print("Either install them or use the -d argument to run in docker") sys.exit(1) def set_proper_package_name(package): if package in lib.config.projects_osmocom: return package if package in lib.config.projects_other: return package # Add prefix to Osmocom package if missing for package_cfg in lib.config.projects_osmocom: if os.path.basename(package_cfg) == package: return package_cfg if lib.args.allow_unknown_package: return package print(f"ERROR: unknown package: {package}") print("See projects_osmocom and projects_other in obs/lib/config.py") sys.exit(1) def exit_error_cmd(completed, error_msg): """ :param completed: return from run_cmd() below """ global cmds_verbose print() print(f"ERROR: {error_msg}") print() print(f"*** command ***\n{completed.args}\n") print(f"*** returncode ***\n{completed.returncode}\n") if not cmds_verbose: print(f"*** output ***\n{completed.output}") print("*** python trace ***") raise RuntimeError("shell command related error, find details right above" " this python trace") def run_cmd(cmd, check=True, *args, **kwargs): """ Like subprocess.run, but has check=True and text=True by default and allows capturing the output while displaying it at the same time. By default the output is hidden unless there's an error, with -v the output gets written to stdout. :returns: subprocess.CompletedProcess instance, but with combined stdout + stderr written to ret.output :param check: stop with error if exit code is not 0 """ global cmds_verbose caller = inspect.stack()[2][3] if cmds_verbose: print(f"+ {caller}(): {cmd}") with tempfile.TemporaryFile(encoding="utf8", mode="w+") as output_buf: p = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, *args, **kwargs) while True: out = p.stdout.read(1) if out == "" and p.poll() is not None: break if out != "": output_buf.write(out) if cmds_verbose: sys.stdout.write(out) sys.stdout.flush() output_buf.seek(0) setattr(p, "output", output_buf.read()) if p.returncode == 0 or not check: return p exit_error_cmd(p, "command failed unexpectedly") def remove_temp(): run_cmd(["rm", "-rf", lib.config.path_temp]) def remove_cache_extra_files(): """ dpkg-buildpackage outputs all files to the top dir of the package dir, so it will always put them in _cache when building e.g. the debian source package of _cache/libosmocore. Clear all extra files from _cache that don't belog to the git repositories which we actually want to cache. """ run_cmd(["find", lib.config.path_cache, "-maxdepth", "1", "-type", "f", "-delete"]) def get_output_path(project): return f"{lib.config.path_temp}/srcpkgs/{os.path.basename(project)}"