# SPDX-License-Identifier: GPL-2.0-or-later # Copyright 2018 sysmocom - s.f.m.c. GmbH import atexit import collections import sys import os import shutil import subprocess import tempfile def next_buildable(depends, done): """ Find the next program that can be built, because it has all dependencies satisfied. Initially this would be libosmocore, as it has no dependencies, then the only library that depends on libosmocore and so on. :param depends: return value of dependencies.generate() :param done: ordered dict of programs that would already have been built at this point. Example: {"lib-a": "0.11.0", "lib-b": "0.5.0"} """ # Iterate over dependencies for program, data in depends.items(): # Skip what's already done if program in done: continue # Check for missing dependencies depends_done = True for depend in data["depends"]: if depend not in done: depends_done = False break # All dependencies satisfied: we have a winner! if depends_done: return program, data["version"] # Impossible to build the dependency tree print_dict(done) print("ERROR: can't figure out how to build the rest!") sys.exit(1) def generate(depends): """ Generate an ordered dictionary with the right build order. :param depends: return value of dependencies.generate() :returns: an ordered dict like the following: {"libosmocore": "0.11.0", "libosmo-abis": "0.5.0", "osmo-bts": "master"} """ # Iterate over dependencies ret = collections.OrderedDict() count = len(depends.keys()) while len(ret) != count: # Continue with the one without unsatisfied dependencies program, version = next_buildable(depends, ret) ret[program] = version return ret def print_dict(stack): """ Print the whole build stack. :param stack: return value from generate() above """ print("Build order:") for program, version in stack.items(): print(" * " + program + ":" + version) def set_environment(jobs, prefix): """ Configure the environment variables before running configure, make etc. :param jobs: parallel build jobs (for make) :param prefix: installation folder """ # Add prefix to PKG_CONFIG_PATH and LD_LIBRARY_PATH extend = {"PKG_CONFIG_PATH": prefix + "/lib/pkgconfig", "LD_LIBRARY_PATH": prefix + "/lib"} for env_var, folder in extend.items(): old = os.environ[env_var] if env_var in os.environ else "" os.environ[env_var] = old + ":" + folder # Set JOBS for make os.environ["JOBS"] = str(jobs) def build(workdir, jobs, stack): """ Build one program with all its dependencies. :param workdir: path to where all data (git, build, install) is stored :param jobs: parallel build jobs (for make) :param stack: the build stack as returned by generate() above The dependencies.clone() function has already downloaded missing sources and checked out the right version tags. So in this function we can directly enter the source folder and run the build commands. Notes about the usage of 'make clean' and 'make distclean': * Without 'make clean' we might have files in the build directory with a different prefix hardcoded (e.g. from a previous run of osmo-depcheck): * 'make distclean' gets used to remove everything that mentioned the prefix set by osmo-depcheck. That way the user won't have it set anymore in case they decide to compile the code again manually from the source folder. """ # Prepare the install folder and environment prefix = workdir + "/install" unitdir = prefix + "/lib/systemd/system/" set_environment(jobs, prefix) # Iterate over stack for program, version in stack.items(): print("Building " + program + ":" + version) # Create and enter the build folder builddir = workdir + "/build/" + program os.mkdir(builddir) os.chdir(builddir) # Run the build commands gitdir = workdir + "/git/" + program commands = [["autoreconf", "-fi", gitdir], [gitdir + "/configure", "--prefix", prefix, "--with-systemdsystemunitdir=" + unitdir], ["make", "clean"], ["make"], ["make", "install"], ["make", "distclean"]] for command in commands: print("+ " + " ".join(command)) subprocess.run(command, check=True)