Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 1 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | # Copyright 2018 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| 3 | |
| 4 | import atexit |
| 5 | import collections |
| 6 | import sys |
| 7 | import os |
| 8 | import shutil |
| 9 | import subprocess |
| 10 | import tempfile |
| 11 | |
| 12 | |
| 13 | def next_buildable(depends, done): |
| 14 | """ Find the next program that can be built, because it has all |
| 15 | dependencies satisfied. Initially this would be libosmocore, as it has |
| 16 | no dependencies, then the only library that depends on libosmocore and |
| 17 | so on. |
| 18 | |
| 19 | :param depends: return value of dependencies.generate() |
| 20 | :param done: ordered dict of programs that would already have been |
| 21 | built at this point. |
| 22 | Example: {"lib-a": "0.11.0", "lib-b": "0.5.0"} |
| 23 | """ |
| 24 | # Iterate over dependencies |
| 25 | for program, data in depends.items(): |
| 26 | # Skip what's already done |
| 27 | if program in done: |
| 28 | continue |
| 29 | |
| 30 | # Check for missing dependencies |
| 31 | depends_done = True |
| 32 | for depend in data["depends"]: |
| 33 | if depend not in done: |
| 34 | depends_done = False |
| 35 | break |
| 36 | |
| 37 | # All dependencies satisfied: we have a winner! |
| 38 | if depends_done: |
| 39 | return program, data["version"] |
| 40 | |
| 41 | # Impossible to build the dependency tree |
| 42 | print_dict(done) |
| 43 | print("ERROR: can't figure out how to build the rest!") |
| 44 | sys.exit(1) |
| 45 | |
| 46 | |
| 47 | def generate(depends): |
| 48 | """ Generate an ordered dictionary with the right build order. |
| 49 | |
| 50 | :param depends: return value of dependencies.generate() |
| 51 | :returns: an ordered dict like the following: |
| 52 | {"libosmocore": "0.11.0", |
| 53 | "libosmo-abis": "0.5.0", |
| 54 | "osmo-bts": "master"} """ |
| 55 | # Iterate over dependencies |
| 56 | ret = collections.OrderedDict() |
| 57 | count = len(depends.keys()) |
| 58 | while len(ret) != count: |
| 59 | # Continue with the one without unsatisfied dependencies |
| 60 | program, version = next_buildable(depends, ret) |
| 61 | ret[program] = version |
| 62 | return ret |
| 63 | |
| 64 | |
| 65 | def print_dict(stack): |
| 66 | """ Print the whole build stack. |
| 67 | :param stack: return value from generate() above """ |
| 68 | print("Build order:") |
| 69 | for program, version in stack.items(): |
| 70 | print(" * " + program + ":" + version) |
| 71 | |
| 72 | |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 73 | def set_environment(jobs, prefix): |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 74 | """ Configure the environment variables before running configure, make etc. |
| 75 | |
| 76 | :param jobs: parallel build jobs (for make) |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 77 | :param prefix: installation folder |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 78 | """ |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 79 | # Add prefix to PKG_CONFIG_PATH and LD_LIBRARY_PATH |
| 80 | extend = {"PKG_CONFIG_PATH": prefix + "/lib/pkgconfig", |
| 81 | "LD_LIBRARY_PATH": prefix + "/lib"} |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 82 | for env_var, folder in extend.items(): |
| 83 | old = os.environ[env_var] if env_var in os.environ else "" |
| 84 | os.environ[env_var] = old + ":" + folder |
| 85 | |
| 86 | # Set JOBS for make |
| 87 | os.environ["JOBS"] = str(jobs) |
| 88 | |
| 89 | |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 90 | def build(workdir, jobs, stack): |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 91 | """ Build one program with all its dependencies. |
| 92 | |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 93 | :param workdir: path to where all data (git, build, install) is stored |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 94 | :param jobs: parallel build jobs (for make) |
| 95 | :param stack: the build stack as returned by generate() above |
| 96 | |
| 97 | The dependencies.clone() function has already downloaded missing |
| 98 | sources and checked out the right version tags. So in this function we |
| 99 | can directly enter the source folder and run the build commands. |
| 100 | |
| 101 | Notes about the usage of 'make clean' and 'make distclean': |
| 102 | * Without 'make clean' we might have files in the build directory with |
| 103 | a different prefix hardcoded (e.g. from a previous run of |
| 104 | osmo-depcheck): |
| 105 | <https://lists.gnu.org/archive/html/libtool/2006-12/msg00011.html> |
| 106 | * 'make distclean' gets used to remove everything that mentioned the |
| 107 | prefix set by osmo-depcheck. That way the user won't have it set |
| 108 | anymore in case they decide to compile the code again manually from |
| 109 | the source folder. """ |
| 110 | # Prepare the install folder and environment |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 111 | prefix = workdir + "/install" |
| 112 | unitdir = prefix + "/lib/systemd/system/" |
| 113 | set_environment(jobs, prefix) |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 114 | |
| 115 | # Iterate over stack |
| 116 | for program, version in stack.items(): |
| 117 | print("Building " + program + ":" + version) |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 118 | |
| 119 | # Create and enter the build folder |
| 120 | builddir = workdir + "/build/" + program |
| 121 | os.mkdir(builddir) |
| 122 | os.chdir(builddir) |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 123 | |
| 124 | # Run the build commands |
Oliver Smith | a99a4ef | 2018-09-21 10:29:51 +0200 | [diff] [blame] | 125 | gitdir = workdir + "/git/" + program |
| 126 | commands = [["autoreconf", "-fi", gitdir], |
| 127 | [gitdir + "/configure", "--prefix", prefix, |
Oliver Smith | 85c2eff | 2018-09-13 15:39:44 +0200 | [diff] [blame] | 128 | "--with-systemdsystemunitdir=" + unitdir], |
| 129 | ["make", "clean"], |
| 130 | ["make"], |
| 131 | ["make", "install"], |
| 132 | ["make", "distclean"]] |
| 133 | for command in commands: |
| 134 | print("+ " + " ".join(command)) |
| 135 | subprocess.run(command, check=True) |