aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/obs/lib/__init__.py
blob: 479c1c1ca3b3123704be193a53d7476854ea7422 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
import importlib
import os
import shutil
import subprocess
import sys
import tempfile
import inspect
import lib.config

cmds_verbose = False


def add_shared_arguments(parser):
    """ Arguments shared between build_srcpkg.py and update_obs_project.py. """
    parser.add_argument("-f", "--feed",
			help="package feed (default: nightly). The feed"
			" determines the git revision to be built:"
			" 'nightly' and 'master' build 'origin/master',"
			" 'latest' builds the last signed tag,"
			" other feeds build their respective branch.",
                        metavar="FEED", default="nightly",
                        choices=lib.config.feeds)
    parser.add_argument("-b", "--git-branch", help="instead of using a branch"
                              " based on the feed, checkout this git branch",
                        metavar="BRANCH", default=None)
    parser.add_argument("-d", "--docker",
                        help="run in docker to avoid installing required pkgs",
                        action="store_true")
    parser.add_argument("-s", "--git-skip-fetch",
                        help="do not fetch already cloned git repositories",
                        action="store_false", dest="git_fetch")
    parser.add_argument("-m", "--meta", action="store_true",
                        help="build a meta package (e.g. osmocom-nightly)")
    parser.add_argument("-i", "--ignore-req", action="store_true",
                        help="skip required programs check")
    parser.add_argument("-c", "--conflict-version", nargs="?",
                        help="Of the generated source packages, all Osmocom"
                             " packages (not e.g. open5gs, see lib/config.py"
                             " for full list) depend on a meta-package such as"
                             " osmocom-nightly, osmocom-latest, osmocom-2021q1"
                             " etc. These meta-packages conflict with each"
                             " other to ensure that one does not mix e.g."
                             " latest and nightly packages by accident."
                             " With this -c argument, it is possible to let"
                             " these packages depend on a meta-package of a"
                             " specific version. This is used for nightly and"
                             " 20YYqX packages to ensure they are not mixed"
                             " from different build dates (ABI compatibility"
                             " is only on latest).")
    parser.add_argument("-v", "--verbose", action="store_true",
                        help="always print shell commands and their output,"
                             " instead of only printing them on error")


def set_cmds_verbose(new_val):
    global cmds_verbose
    cmds_verbose = new_val


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.find_loader(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")
        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

    print(f"ERROR: unknown package: {package}")
    print("See packages_osmocom and packages_other in obs/lib/config.py")
    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)}"