xref: /openbmc/openbmc-build-scripts/scripts/build-unit-test-docker (revision ed8aecaf3db8379b0d966effc3da52c8429344b6)
102871c91SPatrick Williams#!/usr/bin/env python3
202871c91SPatrick Williams#
302871c91SPatrick Williams# Build the required docker image to run package unit tests
402871c91SPatrick Williams#
502871c91SPatrick Williams# Script Variables:
602871c91SPatrick Williams#   DOCKER_IMG_NAME:  <optional, the name of the docker image to generate>
702871c91SPatrick Williams#                     default is openbmc/ubuntu-unit-test
802871c91SPatrick Williams#   DISTRO:           <optional, the distro to build a docker image against>
950837436SPatrick Williams#   FORCE_DOCKER_BUILD: <optional, a non-zero value with force all Docker
1050837436SPatrick Williams#                     images to be rebuilt rather than reusing caches.>
1150837436SPatrick Williams#   BUILD_URL:        <optional, used to detect running under CI context
1250837436SPatrick Williams#                     (ex. Jenkins)>
1302871c91SPatrick Williams#   BRANCH:           <optional, branch to build from each of the openbmc/
1402871c91SPatrick Williams#                     repositories>
1502871c91SPatrick Williams#                     default is master, which will be used if input branch not
1602871c91SPatrick Williams#                     provided or not found
1702871c91SPatrick Williams#   UBUNTU_MIRROR:    <optional, the URL of a mirror of Ubuntu to override the
1802871c91SPatrick Williams#                     default ones in /etc/apt/sources.list>
1902871c91SPatrick Williams#                     default is empty, and no mirror is used.
20fe2768c7SAndrew Geissler#   DOCKER_REG:       <optional, the URL of a docker registry to utilize
2123ec3323SAndrew Geissler#                     instead of our default (public.ecr.aws/ubuntu)
2223ec3323SAndrew Geissler#                     (ex. docker.io)
2302871c91SPatrick Williams#   http_proxy        The HTTP address of the proxy server to connect to.
2402871c91SPatrick Williams#                     Default: "", proxy is not setup if this is not set
2502871c91SPatrick Williams
26276bd0e2SPatrick Williamsimport json
2702871c91SPatrick Williamsimport os
28f3d27e64SAndrew Geisslerimport re
2902871c91SPatrick Williamsimport sys
30b16f3e20SPatrick Williamsimport threading
31276bd0e2SPatrick Williamsimport urllib.request
32a18d9c57SPatrick Williamsfrom datetime import date
33a18d9c57SPatrick Williamsfrom hashlib import sha256
34e08ffba8SPatrick Williams
35e08ffba8SPatrick Williams# typing.Dict is used for type-hints.
36e08ffba8SPatrick Williamsfrom typing import Any, Callable, Dict, Iterable, Optional  # noqa: F401
3702871c91SPatrick Williams
388f7146faSAndrew Geisslerfrom sh import git, nproc  # type: ignore
398f7146faSAndrew Geissler
408f7146faSAndrew Geisslertry:
418f7146faSAndrew Geissler    # System may have docker or it may have podman, try docker first
428f7146faSAndrew Geissler    from sh import docker
438f7146faSAndrew Geissler
448f7146faSAndrew Geissler    container = docker
458f7146faSAndrew Geisslerexcept ImportError:
468f7146faSAndrew Geissler    try:
478f7146faSAndrew Geissler        from sh import podman
488f7146faSAndrew Geissler
498f7146faSAndrew Geissler        container = podman
508f7146faSAndrew Geissler    except Exception:
518f7146faSAndrew Geissler        print("No docker or podman found on system")
528f7146faSAndrew Geissler        exit(1)
5341d86218SPatrick Williams
54ee3c9eebSPatrick Williamstry:
55ee3c9eebSPatrick Williams    # Python before 3.8 doesn't have TypedDict, so reroute to standard 'dict'.
56ee3c9eebSPatrick Williams    from typing import TypedDict
5741d86218SPatrick Williamsexcept Exception:
58ee3c9eebSPatrick Williams
59ee3c9eebSPatrick Williams    class TypedDict(dict):  # type: ignore
60ee3c9eebSPatrick Williams        # We need to do this to eat the 'total' argument.
6141d86218SPatrick Williams        def __init_subclass__(cls, **kwargs: Any) -> None:
62ee3c9eebSPatrick Williams            super().__init_subclass__()
63ee3c9eebSPatrick Williams
64ee3c9eebSPatrick Williams
65ee3c9eebSPatrick Williams# Declare some variables used in package definitions.
66aae36d18SPatrick Williamsprefix = "/usr/local"
6702871c91SPatrick Williamsproc_count = nproc().strip()
6802871c91SPatrick Williams
69ee3c9eebSPatrick Williams
70ee3c9eebSPatrick Williamsclass PackageDef(TypedDict, total=False):
71ee3c9eebSPatrick Williams    """Package Definition for packages dictionary."""
72ee3c9eebSPatrick Williams
73ee3c9eebSPatrick Williams    # rev [optional]: Revision of package to use.
74ee3c9eebSPatrick Williams    rev: str
75ee3c9eebSPatrick Williams    # url [optional]: lambda function to create URL: (package, rev) -> url.
76ee3c9eebSPatrick Williams    url: Callable[[str, str], str]
77ee3c9eebSPatrick Williams    # depends [optional]: List of package dependencies.
78ee3c9eebSPatrick Williams    depends: Iterable[str]
79ee3c9eebSPatrick Williams    # build_type [required]: Build type used for package.
80ee3c9eebSPatrick Williams    #   Currently supported: autoconf, cmake, custom, make, meson
81ee3c9eebSPatrick Williams    build_type: str
82ee3c9eebSPatrick Williams    # build_steps [optional]: Steps to run for 'custom' build_type.
83ee3c9eebSPatrick Williams    build_steps: Iterable[str]
84ee3c9eebSPatrick Williams    # config_flags [optional]: List of options to pass configuration tool.
85ee3c9eebSPatrick Williams    config_flags: Iterable[str]
86ee3c9eebSPatrick Williams    # config_env [optional]: List of environment variables to set for config.
87ee3c9eebSPatrick Williams    config_env: Iterable[str]
88ee3c9eebSPatrick Williams    # custom_post_dl [optional]: List of steps to run after download, but
89ee3c9eebSPatrick Williams    #   before config / build / install.
90ee3c9eebSPatrick Williams    custom_post_dl: Iterable[str]
916bce2ca1SPatrick Williams    # custom_post_install [optional]: List of steps to run after install.
926bce2ca1SPatrick Williams    custom_post_install: Iterable[str]
93ee3c9eebSPatrick Williams
94ee3c9eebSPatrick Williams    # __tag [private]: Generated Docker tag name for package stage.
95ee3c9eebSPatrick Williams    __tag: str
96ee3c9eebSPatrick Williams    # __package [private]: Package object associated with this package.
97ee3c9eebSPatrick Williams    __package: Any  # Type is Package, but not defined yet.
98ee3c9eebSPatrick Williams
9902871c91SPatrick Williams
1007204324cSPatrick Williams# Packages to include in image.
1017204324cSPatrick Williamspackages = {
102ee3c9eebSPatrick Williams    "boost": PackageDef(
1039698215eSJayanth Othayoth        rev="1.86.0",
104ee3c9eebSPatrick Williams        url=(
1059698215eSJayanth Othayoth            lambda pkg, rev: f"https://github.com/boostorg/{pkg}/releases/download/{pkg}-{rev}/{pkg}-{rev}-cmake.tar.gz"
1062abc4a48SPatrick Williams        ),
107ee3c9eebSPatrick Williams        build_type="custom",
108ee3c9eebSPatrick Williams        build_steps=[
109e08ffba8SPatrick Williams            (
110e08ffba8SPatrick Williams                "./bootstrap.sh"
1119698215eSJayanth Othayoth                f" --prefix={prefix} --with-libraries=atomic,context,coroutine,filesystem,process,url"
112e08ffba8SPatrick Williams            ),
113aae36d18SPatrick Williams            "./b2",
11404770ccdSMichal Orzel            f"./b2 install --prefix={prefix} valgrind=on",
115aae36d18SPatrick Williams        ],
116ee3c9eebSPatrick Williams    ),
117ee3c9eebSPatrick Williams    "USCiLab/cereal": PackageDef(
118c1977839SPatrick Williams        rev="v1.3.2",
119ee3c9eebSPatrick Williams        build_type="custom",
120ee3c9eebSPatrick Williams        build_steps=[f"cp -a include/cereal/ {prefix}/include/"],
121ee3c9eebSPatrick Williams    ),
122c7198558SEd Tanous    "danmar/cppcheck": PackageDef(
12351021786SPatrick Williams        rev="2.12.1",
124c7198558SEd Tanous        build_type="cmake",
125c7198558SEd Tanous    ),
126ee3c9eebSPatrick Williams    "CLIUtils/CLI11": PackageDef(
127fc39733aSPatrick Williams        rev="v2.3.2",
128ee3c9eebSPatrick Williams        build_type="cmake",
129ee3c9eebSPatrick Williams        config_flags=[
130aae36d18SPatrick Williams            "-DBUILD_TESTING=OFF",
131aae36d18SPatrick Williams            "-DCLI11_BUILD_DOCS=OFF",
132aae36d18SPatrick Williams            "-DCLI11_BUILD_EXAMPLES=OFF",
133aae36d18SPatrick Williams        ],
134ee3c9eebSPatrick Williams    ),
135ee3c9eebSPatrick Williams    "fmtlib/fmt": PackageDef(
136c061e07bSPatrick Williams        rev="10.1.1",
137ee3c9eebSPatrick Williams        build_type="cmake",
138ee3c9eebSPatrick Williams        config_flags=[
139aae36d18SPatrick Williams            "-DFMT_DOC=OFF",
140aae36d18SPatrick Williams            "-DFMT_TEST=OFF",
141aae36d18SPatrick Williams        ],
142ee3c9eebSPatrick Williams    ),
143ee3c9eebSPatrick Williams    "Naios/function2": PackageDef(
144cb09974cSPatrick Williams        rev="4.2.4",
145ee3c9eebSPatrick Williams        build_type="custom",
146ee3c9eebSPatrick Williams        build_steps=[
147aae36d18SPatrick Williams            f"mkdir {prefix}/include/function2",
148aae36d18SPatrick Williams            f"cp include/function2/function2.hpp {prefix}/include/function2/",
149aae36d18SPatrick Williams        ],
150ee3c9eebSPatrick Williams    ),
151ee3c9eebSPatrick Williams    "google/googletest": PackageDef(
152d11e9c75SPatrick Williams        rev="v1.15.2",
153ee3c9eebSPatrick Williams        build_type="cmake",
1544dd32c02SWilliam A. Kennington III        config_env=["CXXFLAGS=-std=c++20"],
155ee3c9eebSPatrick Williams        config_flags=["-DTHREADS_PREFER_PTHREAD_FLAG=ON"],
156ee3c9eebSPatrick Williams    ),
157178b4b29SEd Tanous    "nghttp2/nghttp2": PackageDef(
158abb106a9SEd Tanous        rev="v1.61.0",
159178b4b29SEd Tanous        build_type="cmake",
160178b4b29SEd Tanous        config_env=["CXXFLAGS=-std=c++20"],
161178b4b29SEd Tanous        config_flags=[
162178b4b29SEd Tanous            "-DENABLE_LIB_ONLY=ON",
163178b4b29SEd Tanous            "-DENABLE_STATIC_LIB=ON",
164178b4b29SEd Tanous        ],
165178b4b29SEd Tanous    ),
166ee3c9eebSPatrick Williams    "nlohmann/json": PackageDef(
167c1977839SPatrick Williams        rev="v3.11.2",
1686bce2ca1SPatrick Williams        build_type="cmake",
1696bce2ca1SPatrick Williams        config_flags=["-DJSON_BuildTests=OFF"],
1706bce2ca1SPatrick Williams        custom_post_install=[
171e08ffba8SPatrick Williams            (
172e08ffba8SPatrick Williams                f"ln -s {prefix}/include/nlohmann/json.hpp"
173e08ffba8SPatrick Williams                f" {prefix}/include/json.hpp"
174e08ffba8SPatrick Williams            ),
175aae36d18SPatrick Williams        ],
176ee3c9eebSPatrick Williams    ),
177058e3a34SPrzemyslaw Czarnowski    "json-c/json-c": PackageDef(
178eee65beeSPatrick Williams        rev="json-c-0.17-20230812",
179058e3a34SPrzemyslaw Czarnowski        build_type="cmake",
180058e3a34SPrzemyslaw Czarnowski    ),
181ee3c9eebSPatrick Williams    "LibVNC/libvncserver": PackageDef(
182c042132cSPatrick Williams        rev="LibVNCServer-0.9.14",
183ee3c9eebSPatrick Williams        build_type="cmake",
184ee3c9eebSPatrick Williams    ),
185ee3c9eebSPatrick Williams    "leethomason/tinyxml2": PackageDef(
186c1977839SPatrick Williams        rev="9.0.0",
187ee3c9eebSPatrick Williams        build_type="cmake",
188ee3c9eebSPatrick Williams    ),
189ee3c9eebSPatrick Williams    "tristanpenman/valijson": PackageDef(
1905a2c113cSPatrick Williams        rev="v1.0.1",
191ee3c9eebSPatrick Williams        build_type="cmake",
192ee3c9eebSPatrick Williams        config_flags=[
1930eedeedaSPatrick Williams            "-Dvalijson_BUILD_TESTS=0",
1940eedeedaSPatrick Williams            "-Dvalijson_INSTALL_HEADERS=1",
195aae36d18SPatrick Williams        ],
196ee3c9eebSPatrick Williams    ),
197ee3c9eebSPatrick Williams    "open-power/pdbg": PackageDef(build_type="autoconf"),
198ee3c9eebSPatrick Williams    "openbmc/gpioplus": PackageDef(
199ee3c9eebSPatrick Williams        build_type="meson",
200ee3c9eebSPatrick Williams        config_flags=[
201aae36d18SPatrick Williams            "-Dexamples=false",
202aae36d18SPatrick Williams            "-Dtests=disabled",
203aae36d18SPatrick Williams        ],
204ee3c9eebSPatrick Williams    ),
205ee3c9eebSPatrick Williams    "openbmc/phosphor-dbus-interfaces": PackageDef(
206ee3c9eebSPatrick Williams        depends=["openbmc/sdbusplus"],
207ee3c9eebSPatrick Williams        build_type="meson",
2084fe87776SWilliam A. Kennington III        config_flags=["-Dgenerate_md=false"],
209ee3c9eebSPatrick Williams    ),
210ee3c9eebSPatrick Williams    "openbmc/phosphor-logging": PackageDef(
211ee3c9eebSPatrick Williams        depends=[
21283394610SPatrick Williams            "USCiLab/cereal",
21383394610SPatrick Williams            "openbmc/phosphor-dbus-interfaces",
21483394610SPatrick Williams            "openbmc/sdbusplus",
21583394610SPatrick Williams            "openbmc/sdeventplus",
216aae36d18SPatrick Williams        ],
217f79ce4c4SPatrick Williams        build_type="meson",
218ee3c9eebSPatrick Williams        config_flags=[
2196c98f280SWilliam A. Kennington III            "-Dlibonly=true",
2206c98f280SWilliam A. Kennington III            "-Dtests=disabled",
2215eabdae9SPatrick Williams            f"-Dyamldir={prefix}/share/phosphor-dbus-yaml/yaml",
222aae36d18SPatrick Williams        ],
223ee3c9eebSPatrick Williams    ),
224ee3c9eebSPatrick Williams    "openbmc/phosphor-objmgr": PackageDef(
225ee3c9eebSPatrick Williams        depends=[
22611e5762cSBrad Bishop            "CLIUtils/CLI11",
22770af95caSPatrick Williams            "boost",
22883394610SPatrick Williams            "leethomason/tinyxml2",
22970af95caSPatrick Williams            "openbmc/phosphor-dbus-interfaces",
23083394610SPatrick Williams            "openbmc/phosphor-logging",
23183394610SPatrick Williams            "openbmc/sdbusplus",
232aae36d18SPatrick Williams        ],
2331197e359SBrad Bishop        build_type="meson",
2341197e359SBrad Bishop        config_flags=[
2351197e359SBrad Bishop            "-Dtests=disabled",
2361197e359SBrad Bishop        ],
237ee3c9eebSPatrick Williams    ),
238c02ff271SJason M. Bills    "openbmc/libpeci": PackageDef(
239c02ff271SJason M. Bills        build_type="meson",
240c02ff271SJason M. Bills        config_flags=[
241c02ff271SJason M. Bills            "-Draw-peci=disabled",
242c02ff271SJason M. Bills        ],
243c02ff271SJason M. Bills    ),
2441c19e453SManojkiran Eda    "openbmc/libpldm": PackageDef(
245ee3c9eebSPatrick Williams        build_type="meson",
24629163971SAndrew Jeffery        config_flags=[
24729163971SAndrew Jeffery            "-Dabi=deprecated,stable",
24829163971SAndrew Jeffery            "-Dtests=false",
24929163971SAndrew Jeffery            "-Dabi-compliance-check=false",
25029163971SAndrew Jeffery        ],
251ee3c9eebSPatrick Williams    ),
252ee3c9eebSPatrick Williams    "openbmc/sdbusplus": PackageDef(
25354d01da4SPatrick Williams        depends=[
25454d01da4SPatrick Williams            "nlohmann/json",
25554d01da4SPatrick Williams        ],
256ee3c9eebSPatrick Williams        build_type="meson",
257ee3c9eebSPatrick Williams        custom_post_dl=[
258aae36d18SPatrick Williams            "cd tools",
259aae36d18SPatrick Williams            f"./setup.py install --root=/ --prefix={prefix}",
260aae36d18SPatrick Williams            "cd ..",
261aae36d18SPatrick Williams        ],
262ee3c9eebSPatrick Williams        config_flags=[
263aae36d18SPatrick Williams            "-Dexamples=disabled",
264aae36d18SPatrick Williams            "-Dtests=disabled",
265aae36d18SPatrick Williams        ],
266b16f3e20SPatrick Williams    ),
267ee3c9eebSPatrick Williams    "openbmc/sdeventplus": PackageDef(
26870af95caSPatrick Williams        depends=[
26970af95caSPatrick Williams            "openbmc/stdplus",
27070af95caSPatrick Williams        ],
271ee3c9eebSPatrick Williams        build_type="meson",
272ee3c9eebSPatrick Williams        config_flags=[
273ee3c9eebSPatrick Williams            "-Dexamples=false",
274ee3c9eebSPatrick Williams            "-Dtests=disabled",
275ee3c9eebSPatrick Williams        ],
276ee3c9eebSPatrick Williams    ),
277ee3c9eebSPatrick Williams    "openbmc/stdplus": PackageDef(
27870af95caSPatrick Williams        depends=[
27970af95caSPatrick Williams            "fmtlib/fmt",
280ca1bf0c0SWilliam A. Kennington III            "google/googletest",
281ca1bf0c0SWilliam A. Kennington III            "Naios/function2",
28270af95caSPatrick Williams        ],
283ee3c9eebSPatrick Williams        build_type="meson",
284ee3c9eebSPatrick Williams        config_flags=[
285ee3c9eebSPatrick Williams            "-Dexamples=false",
286ee3c9eebSPatrick Williams            "-Dtests=disabled",
287ca1bf0c0SWilliam A. Kennington III            "-Dgtest=enabled",
288ee3c9eebSPatrick Williams        ],
289ee3c9eebSPatrick Williams    ),
290ee3c9eebSPatrick Williams}  # type: Dict[str, PackageDef]
29102871c91SPatrick Williams
29202871c91SPatrick Williams# Define common flags used for builds
29302871c91SPatrick Williamsconfigure_flags = " ".join(
29402871c91SPatrick Williams    [
29502871c91SPatrick Williams        f"--prefix={prefix}",
29602871c91SPatrick Williams    ]
29702871c91SPatrick Williams)
29802871c91SPatrick Williamscmake_flags = " ".join(
29902871c91SPatrick Williams    [
30002871c91SPatrick Williams        "-DBUILD_SHARED_LIBS=ON",
3010f2086b3SPatrick Williams        "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
30202871c91SPatrick Williams        f"-DCMAKE_INSTALL_PREFIX:PATH={prefix}",
3030f2086b3SPatrick Williams        "-GNinja",
3040f2086b3SPatrick Williams        "-DCMAKE_MAKE_PROGRAM=ninja",
30502871c91SPatrick Williams    ]
30602871c91SPatrick Williams)
30702871c91SPatrick Williamsmeson_flags = " ".join(
30802871c91SPatrick Williams    [
30902871c91SPatrick Williams        "--wrap-mode=nodownload",
31002871c91SPatrick Williams        f"-Dprefix={prefix}",
31102871c91SPatrick Williams    ]
31202871c91SPatrick Williams)
31302871c91SPatrick Williams
314ee3c9eebSPatrick Williams
315ee3c9eebSPatrick Williamsclass Package(threading.Thread):
316ee3c9eebSPatrick Williams    """Class used to build the Docker stages for each package.
317ee3c9eebSPatrick Williams
318ee3c9eebSPatrick Williams    Generally, this class should not be instantiated directly but through
319ee3c9eebSPatrick Williams    Package.generate_all().
320ee3c9eebSPatrick Williams    """
321ee3c9eebSPatrick Williams
322ee3c9eebSPatrick Williams    # Copy the packages dictionary.
323ee3c9eebSPatrick Williams    packages = packages.copy()
324ee3c9eebSPatrick Williams
325ee3c9eebSPatrick Williams    # Lock used for thread-safety.
326ee3c9eebSPatrick Williams    lock = threading.Lock()
327ee3c9eebSPatrick Williams
328ee3c9eebSPatrick Williams    def __init__(self, pkg: str):
329ee3c9eebSPatrick Williams        """pkg - The name of this package (ex. foo/bar )"""
330ee3c9eebSPatrick Williams        super(Package, self).__init__()
331ee3c9eebSPatrick Williams
332ee3c9eebSPatrick Williams        self.package = pkg
333ee3c9eebSPatrick Williams        self.exception = None  # type: Optional[Exception]
334ee3c9eebSPatrick Williams
335ee3c9eebSPatrick Williams        # Reference to this package's
336ee3c9eebSPatrick Williams        self.pkg_def = Package.packages[pkg]
337ee3c9eebSPatrick Williams        self.pkg_def["__package"] = self
338ee3c9eebSPatrick Williams
339ee3c9eebSPatrick Williams    def run(self) -> None:
340ee3c9eebSPatrick Williams        """Thread 'run' function.  Builds the Docker stage."""
341ee3c9eebSPatrick Williams
342ee3c9eebSPatrick Williams        # In case this package has no rev, fetch it from Github.
343ee3c9eebSPatrick Williams        self._update_rev()
344ee3c9eebSPatrick Williams
345ee3c9eebSPatrick Williams        # Find all the Package objects that this package depends on.
346ee3c9eebSPatrick Williams        #   This section is locked because we are looking into another
347ee3c9eebSPatrick Williams        #   package's PackageDef dict, which could be being modified.
348ee3c9eebSPatrick Williams        Package.lock.acquire()
349ee3c9eebSPatrick Williams        deps: Iterable[Package] = [
350ee3c9eebSPatrick Williams            Package.packages[deppkg]["__package"]
351ee3c9eebSPatrick Williams            for deppkg in self.pkg_def.get("depends", [])
352ee3c9eebSPatrick Williams        ]
353ee3c9eebSPatrick Williams        Package.lock.release()
354ee3c9eebSPatrick Williams
355ee3c9eebSPatrick Williams        # Wait until all the depends finish building.  We need them complete
356ee3c9eebSPatrick Williams        # for the "COPY" commands.
357ee3c9eebSPatrick Williams        for deppkg in deps:
358ee3c9eebSPatrick Williams            deppkg.join()
359ee3c9eebSPatrick Williams
360ee3c9eebSPatrick Williams        # Generate this package's Dockerfile.
361ee3c9eebSPatrick Williams        dockerfile = f"""
362ee3c9eebSPatrick WilliamsFROM {docker_base_img_name}
363ee3c9eebSPatrick Williams{self._df_copycmds()}
364ee3c9eebSPatrick Williams{self._df_build()}
365ee3c9eebSPatrick Williams"""
366ee3c9eebSPatrick Williams
367ee3c9eebSPatrick Williams        # Generate the resulting tag name and save it to the PackageDef.
368ee3c9eebSPatrick Williams        #   This section is locked because we are modifying the PackageDef,
369ee3c9eebSPatrick Williams        #   which can be accessed by other threads.
370ee3c9eebSPatrick Williams        Package.lock.acquire()
371ee3c9eebSPatrick Williams        tag = Docker.tagname(self._stagename(), dockerfile)
372ee3c9eebSPatrick Williams        self.pkg_def["__tag"] = tag
373ee3c9eebSPatrick Williams        Package.lock.release()
374ee3c9eebSPatrick Williams
375ee3c9eebSPatrick Williams        # Do the build / save any exceptions.
376ee3c9eebSPatrick Williams        try:
377ee3c9eebSPatrick Williams            Docker.build(self.package, tag, dockerfile)
378ee3c9eebSPatrick Williams        except Exception as e:
379ee3c9eebSPatrick Williams            self.exception = e
380ee3c9eebSPatrick Williams
381ee3c9eebSPatrick Williams    @classmethod
382ee3c9eebSPatrick Williams    def generate_all(cls) -> None:
383ee3c9eebSPatrick Williams        """Ensure a Docker stage is created for all defined packages.
384ee3c9eebSPatrick Williams
385ee3c9eebSPatrick Williams        These are done in parallel but with appropriate blocking per
386ee3c9eebSPatrick Williams        package 'depends' specifications.
387ee3c9eebSPatrick Williams        """
388ee3c9eebSPatrick Williams
389ee3c9eebSPatrick Williams        # Create a Package for each defined package.
390ee3c9eebSPatrick Williams        pkg_threads = [Package(p) for p in cls.packages.keys()]
391ee3c9eebSPatrick Williams
392ee3c9eebSPatrick Williams        # Start building them all.
3936dbd7807SPatrick Williams        #   This section is locked because threads depend on each other,
3946dbd7807SPatrick Williams        #   based on the packages, and they cannot 'join' on a thread
3956dbd7807SPatrick Williams        #   which is not yet started.  Adding a lock here allows all the
3966dbd7807SPatrick Williams        #   threads to start before they 'join' their dependencies.
3976dbd7807SPatrick Williams        Package.lock.acquire()
398ee3c9eebSPatrick Williams        for t in pkg_threads:
399ee3c9eebSPatrick Williams            t.start()
4006dbd7807SPatrick Williams        Package.lock.release()
401ee3c9eebSPatrick Williams
402ee3c9eebSPatrick Williams        # Wait for completion.
403ee3c9eebSPatrick Williams        for t in pkg_threads:
404ee3c9eebSPatrick Williams            t.join()
405ee3c9eebSPatrick Williams            # Check if the thread saved off its own exception.
406ee3c9eebSPatrick Williams            if t.exception:
407ee3c9eebSPatrick Williams                print(f"Package {t.package} failed!", file=sys.stderr)
408ee3c9eebSPatrick Williams                raise t.exception
409ee3c9eebSPatrick Williams
410ee3c9eebSPatrick Williams    @staticmethod
411ee3c9eebSPatrick Williams    def df_all_copycmds() -> str:
412ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to copy all packages
413ee3c9eebSPatrick Williams        into the final image.
414ee3c9eebSPatrick Williams        """
415ee3c9eebSPatrick Williams        return Package.df_copycmds_set(Package.packages.keys())
416ee3c9eebSPatrick Williams
417ee3c9eebSPatrick Williams    @classmethod
418ee3c9eebSPatrick Williams    def depcache(cls) -> str:
419ee3c9eebSPatrick Williams        """Create the contents of the '/tmp/depcache'.
420ee3c9eebSPatrick Williams        This file is a comma-separated list of "<pkg>:<rev>".
421ee3c9eebSPatrick Williams        """
422ee3c9eebSPatrick Williams
423ee3c9eebSPatrick Williams        # This needs to be sorted for consistency.
424ee3c9eebSPatrick Williams        depcache = ""
425ee3c9eebSPatrick Williams        for pkg in sorted(cls.packages.keys()):
426ee3c9eebSPatrick Williams            depcache += "%s:%s," % (pkg, cls.packages[pkg]["rev"])
427ee3c9eebSPatrick Williams        return depcache
428ee3c9eebSPatrick Williams
429276bd0e2SPatrick Williams    def _check_gerrit_topic(self) -> bool:
430276bd0e2SPatrick Williams        if not gerrit_topic:
431276bd0e2SPatrick Williams            return False
432276bd0e2SPatrick Williams        if not self.package.startswith("openbmc/"):
433276bd0e2SPatrick Williams            return False
434276bd0e2SPatrick Williams        if gerrit_project == self.package and gerrit_rev:
435276bd0e2SPatrick Williams            return False
436276bd0e2SPatrick Williams
437276bd0e2SPatrick Williams        try:
438276bd0e2SPatrick Williams            commits = json.loads(
439276bd0e2SPatrick Williams                urllib.request.urlopen(
440276bd0e2SPatrick Williams                    f"https://gerrit.openbmc.org/changes/?q=status:open+project:{self.package}+topic:{gerrit_topic}"
441276bd0e2SPatrick Williams                )
442276bd0e2SPatrick Williams                .read()
443276bd0e2SPatrick Williams                .splitlines()[-1]
444276bd0e2SPatrick Williams            )
445276bd0e2SPatrick Williams
446276bd0e2SPatrick Williams            if len(commits) == 0:
447276bd0e2SPatrick Williams                return False
448276bd0e2SPatrick Williams            if len(commits) > 1:
449276bd0e2SPatrick Williams                print(
450276bd0e2SPatrick Williams                    f"{self.package} has more than 1 commit under {gerrit_topic}; using lastest upstream: {len(commits)}",
451276bd0e2SPatrick Williams                    file=sys.stderr,
452276bd0e2SPatrick Williams                )
453276bd0e2SPatrick Williams                return False
454276bd0e2SPatrick Williams
455276bd0e2SPatrick Williams            change_id = commits[0]["id"]
456276bd0e2SPatrick Williams
457276bd0e2SPatrick Williams            commit = json.loads(
458276bd0e2SPatrick Williams                urllib.request.urlopen(
459276bd0e2SPatrick Williams                    f"https://gerrit.openbmc.org/changes/{change_id}/revisions/current/commit"
460276bd0e2SPatrick Williams                )
461276bd0e2SPatrick Williams                .read()
462276bd0e2SPatrick Williams                .splitlines()[-1]
463276bd0e2SPatrick Williams            )["commit"]
464276bd0e2SPatrick Williams
465276bd0e2SPatrick Williams            print(
466276bd0e2SPatrick Williams                f"Using {commit} from {gerrit_topic} for {self.package}",
467276bd0e2SPatrick Williams                file=sys.stderr,
468276bd0e2SPatrick Williams            )
469276bd0e2SPatrick Williams            self.pkg_def["rev"] = commit
470276bd0e2SPatrick Williams            return True
471276bd0e2SPatrick Williams
472276bd0e2SPatrick Williams        except urllib.error.HTTPError as e:
473276bd0e2SPatrick Williams            print(
474276bd0e2SPatrick Williams                f"Error loading topic {gerrit_topic} for {self.package}: ",
475276bd0e2SPatrick Williams                e,
476276bd0e2SPatrick Williams                file=sys.stderr,
477276bd0e2SPatrick Williams            )
478276bd0e2SPatrick Williams            return False
479276bd0e2SPatrick Williams
480ee3c9eebSPatrick Williams    def _update_rev(self) -> None:
481ee3c9eebSPatrick Williams        """Look up the HEAD for missing a static rev."""
482ee3c9eebSPatrick Williams
483ee3c9eebSPatrick Williams        if "rev" in self.pkg_def:
484ee3c9eebSPatrick Williams            return
485ee3c9eebSPatrick Williams
486276bd0e2SPatrick Williams        if self._check_gerrit_topic():
487276bd0e2SPatrick Williams            return
488276bd0e2SPatrick Williams
48965b21fb9SPatrick Williams        # Check if Jenkins/Gerrit gave us a revision and use it.
49065b21fb9SPatrick Williams        if gerrit_project == self.package and gerrit_rev:
49165b21fb9SPatrick Williams            print(
49265b21fb9SPatrick Williams                f"Found Gerrit revision for {self.package}: {gerrit_rev}",
49365b21fb9SPatrick Williams                file=sys.stderr,
49465b21fb9SPatrick Williams            )
49565b21fb9SPatrick Williams            self.pkg_def["rev"] = gerrit_rev
49665b21fb9SPatrick Williams            return
49765b21fb9SPatrick Williams
498ee3c9eebSPatrick Williams        # Ask Github for all the branches.
49905fb2a0aSPatrick Williams        lookup = git(
50005fb2a0aSPatrick Williams            "ls-remote", "--heads", f"https://github.com/{self.package}"
50105fb2a0aSPatrick Williams        )
502ee3c9eebSPatrick Williams
503ee3c9eebSPatrick Williams        # Find the branch matching {branch} (or fallback to master).
504ee3c9eebSPatrick Williams        #   This section is locked because we are modifying the PackageDef.
505ee3c9eebSPatrick Williams        Package.lock.acquire()
506ee3c9eebSPatrick Williams        for line in lookup.split("\n"):
507f3d27e64SAndrew Geissler            if re.fullmatch(f".*{branch}$", line.strip()):
508ee3c9eebSPatrick Williams                self.pkg_def["rev"] = line.split()[0]
509f3d27e64SAndrew Geissler                break
510c7d73646SPatrick Williams            elif (
511c7d73646SPatrick Williams                "refs/heads/master" in line or "refs/heads/main" in line
512c7d73646SPatrick Williams            ) and "rev" not in self.pkg_def:
513ee3c9eebSPatrick Williams                self.pkg_def["rev"] = line.split()[0]
514ee3c9eebSPatrick Williams        Package.lock.release()
515ee3c9eebSPatrick Williams
516ee3c9eebSPatrick Williams    def _stagename(self) -> str:
517ee3c9eebSPatrick Williams        """Create a name for the Docker stage associated with this pkg."""
518ee3c9eebSPatrick Williams        return self.package.replace("/", "-").lower()
519ee3c9eebSPatrick Williams
520ee3c9eebSPatrick Williams    def _url(self) -> str:
521ee3c9eebSPatrick Williams        """Get the URL for this package."""
522ee3c9eebSPatrick Williams        rev = self.pkg_def["rev"]
523ee3c9eebSPatrick Williams
524ee3c9eebSPatrick Williams        # If the lambda exists, call it.
525ee3c9eebSPatrick Williams        if "url" in self.pkg_def:
526ee3c9eebSPatrick Williams            return self.pkg_def["url"](self.package, rev)
527ee3c9eebSPatrick Williams
528ee3c9eebSPatrick Williams        # Default to the github archive URL.
529ee3c9eebSPatrick Williams        return f"https://github.com/{self.package}/archive/{rev}.tar.gz"
530ee3c9eebSPatrick Williams
531ee3c9eebSPatrick Williams    def _cmd_download(self) -> str:
532ee3c9eebSPatrick Williams        """Formulate the command necessary to download and unpack to source."""
533ee3c9eebSPatrick Williams
534ee3c9eebSPatrick Williams        url = self._url()
535ee3c9eebSPatrick Williams        if ".tar." not in url:
536ee3c9eebSPatrick Williams            raise NotImplementedError(
537ee3c9eebSPatrick Williams                f"Unhandled download type for {self.package}: {url}"
538ee3c9eebSPatrick Williams            )
539ee3c9eebSPatrick Williams
540ee3c9eebSPatrick Williams        cmd = f"curl -L {url} | tar -x"
541ee3c9eebSPatrick Williams
542ee3c9eebSPatrick Williams        if url.endswith(".bz2"):
543ee3c9eebSPatrick Williams            cmd += "j"
544ee3c9eebSPatrick Williams        elif url.endswith(".gz"):
545ee3c9eebSPatrick Williams            cmd += "z"
546ee3c9eebSPatrick Williams        else:
547ee3c9eebSPatrick Williams            raise NotImplementedError(
548ee3c9eebSPatrick Williams                f"Unknown tar flags needed for {self.package}: {url}"
549ee3c9eebSPatrick Williams            )
550ee3c9eebSPatrick Williams
551ee3c9eebSPatrick Williams        return cmd
552ee3c9eebSPatrick Williams
553ee3c9eebSPatrick Williams    def _cmd_cd_srcdir(self) -> str:
554ee3c9eebSPatrick Williams        """Formulate the command necessary to 'cd' into the source dir."""
555ee3c9eebSPatrick Williams        return f"cd {self.package.split('/')[-1]}*"
556ee3c9eebSPatrick Williams
557ee3c9eebSPatrick Williams    def _df_copycmds(self) -> str:
558ee3c9eebSPatrick Williams        """Formulate the dockerfile snippet necessary to COPY all depends."""
559ee3c9eebSPatrick Williams
560ee3c9eebSPatrick Williams        if "depends" not in self.pkg_def:
561ee3c9eebSPatrick Williams            return ""
562ee3c9eebSPatrick Williams        return Package.df_copycmds_set(self.pkg_def["depends"])
563ee3c9eebSPatrick Williams
564ee3c9eebSPatrick Williams    @staticmethod
565ee3c9eebSPatrick Williams    def df_copycmds_set(pkgs: Iterable[str]) -> str:
566ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to COPY a set of
567ee3c9eebSPatrick Williams        packages into a Docker stage.
568ee3c9eebSPatrick Williams        """
569ee3c9eebSPatrick Williams
570ee3c9eebSPatrick Williams        copy_cmds = ""
571ee3c9eebSPatrick Williams
572ee3c9eebSPatrick Williams        # Sort the packages for consistency.
573ee3c9eebSPatrick Williams        for p in sorted(pkgs):
574ee3c9eebSPatrick Williams            tag = Package.packages[p]["__tag"]
575ee3c9eebSPatrick Williams            copy_cmds += f"COPY --from={tag} {prefix} {prefix}\n"
576ee3c9eebSPatrick Williams            # Workaround for upstream docker bug and multiple COPY cmds
577ee3c9eebSPatrick Williams            # https://github.com/moby/moby/issues/37965
578ee3c9eebSPatrick Williams            copy_cmds += "RUN true\n"
579ee3c9eebSPatrick Williams
580ee3c9eebSPatrick Williams        return copy_cmds
581ee3c9eebSPatrick Williams
582ee3c9eebSPatrick Williams    def _df_build(self) -> str:
583ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to download, build, and
584ee3c9eebSPatrick Williams        install a package into a Docker stage.
585ee3c9eebSPatrick Williams        """
586ee3c9eebSPatrick Williams
587ee3c9eebSPatrick Williams        # Download and extract source.
588ee3c9eebSPatrick Williams        result = f"RUN {self._cmd_download()} && {self._cmd_cd_srcdir()} && "
589ee3c9eebSPatrick Williams
590ee3c9eebSPatrick Williams        # Handle 'custom_post_dl' commands.
591ee3c9eebSPatrick Williams        custom_post_dl = self.pkg_def.get("custom_post_dl")
592ee3c9eebSPatrick Williams        if custom_post_dl:
593ee3c9eebSPatrick Williams            result += " && ".join(custom_post_dl) + " && "
594ee3c9eebSPatrick Williams
595ee3c9eebSPatrick Williams        # Build and install package based on 'build_type'.
596ee3c9eebSPatrick Williams        build_type = self.pkg_def["build_type"]
597ee3c9eebSPatrick Williams        if build_type == "autoconf":
598ee3c9eebSPatrick Williams            result += self._cmd_build_autoconf()
599ee3c9eebSPatrick Williams        elif build_type == "cmake":
600ee3c9eebSPatrick Williams            result += self._cmd_build_cmake()
601ee3c9eebSPatrick Williams        elif build_type == "custom":
602ee3c9eebSPatrick Williams            result += self._cmd_build_custom()
603ee3c9eebSPatrick Williams        elif build_type == "make":
604ee3c9eebSPatrick Williams            result += self._cmd_build_make()
605ee3c9eebSPatrick Williams        elif build_type == "meson":
606ee3c9eebSPatrick Williams            result += self._cmd_build_meson()
607ee3c9eebSPatrick Williams        else:
608ee3c9eebSPatrick Williams            raise NotImplementedError(
609ee3c9eebSPatrick Williams                f"Unhandled build type for {self.package}: {build_type}"
610ee3c9eebSPatrick Williams            )
611ee3c9eebSPatrick Williams
6126bce2ca1SPatrick Williams        # Handle 'custom_post_install' commands.
6136bce2ca1SPatrick Williams        custom_post_install = self.pkg_def.get("custom_post_install")
6146bce2ca1SPatrick Williams        if custom_post_install:
6156bce2ca1SPatrick Williams            result += " && " + " && ".join(custom_post_install)
6166bce2ca1SPatrick Williams
617ee3c9eebSPatrick Williams        return result
618ee3c9eebSPatrick Williams
619ee3c9eebSPatrick Williams    def _cmd_build_autoconf(self) -> str:
620ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
621ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
622ee3c9eebSPatrick Williams        result = "./bootstrap.sh && "
623ee3c9eebSPatrick Williams        result += f"{env} ./configure {configure_flags} {options} && "
624ee3c9eebSPatrick Williams        result += f"make -j{proc_count} && make install"
625ee3c9eebSPatrick Williams        return result
626ee3c9eebSPatrick Williams
627ee3c9eebSPatrick Williams    def _cmd_build_cmake(self) -> str:
628ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
629ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
630ee3c9eebSPatrick Williams        result = "mkdir builddir && cd builddir && "
631ee3c9eebSPatrick Williams        result += f"{env} cmake {cmake_flags} {options} .. && "
632ee3c9eebSPatrick Williams        result += "cmake --build . --target all && "
633ee3c9eebSPatrick Williams        result += "cmake --build . --target install && "
634ee3c9eebSPatrick Williams        result += "cd .."
635ee3c9eebSPatrick Williams        return result
636ee3c9eebSPatrick Williams
637ee3c9eebSPatrick Williams    def _cmd_build_custom(self) -> str:
638ee3c9eebSPatrick Williams        return " && ".join(self.pkg_def.get("build_steps", []))
639ee3c9eebSPatrick Williams
640ee3c9eebSPatrick Williams    def _cmd_build_make(self) -> str:
641ee3c9eebSPatrick Williams        return f"make -j{proc_count} && make install"
642ee3c9eebSPatrick Williams
643ee3c9eebSPatrick Williams    def _cmd_build_meson(self) -> str:
644ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
645ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
646e2da11adSAndrew Jeffery        result = f"{env} meson setup builddir {meson_flags} {options} && "
647ee3c9eebSPatrick Williams        result += "ninja -C builddir && ninja -C builddir install"
648ee3c9eebSPatrick Williams        return result
649ee3c9eebSPatrick Williams
650ee3c9eebSPatrick Williams
651ee3c9eebSPatrick Williamsclass Docker:
652ee3c9eebSPatrick Williams    """Class to assist with Docker interactions.  All methods are static."""
653ee3c9eebSPatrick Williams
654ee3c9eebSPatrick Williams    @staticmethod
655ee3c9eebSPatrick Williams    def timestamp() -> str:
656ee3c9eebSPatrick Williams        """Generate a timestamp for today using the ISO week."""
657ee3c9eebSPatrick Williams        today = date.today().isocalendar()
658ee3c9eebSPatrick Williams        return f"{today[0]}-W{today[1]:02}"
659ee3c9eebSPatrick Williams
660ee3c9eebSPatrick Williams    @staticmethod
66141d86218SPatrick Williams    def tagname(pkgname: Optional[str], dockerfile: str) -> str:
662ee3c9eebSPatrick Williams        """Generate a tag name for a package using a hash of the Dockerfile."""
663ee3c9eebSPatrick Williams        result = docker_image_name
664ee3c9eebSPatrick Williams        if pkgname:
665ee3c9eebSPatrick Williams            result += "-" + pkgname
666ee3c9eebSPatrick Williams
667ee3c9eebSPatrick Williams        result += ":" + Docker.timestamp()
668ee3c9eebSPatrick Williams        result += "-" + sha256(dockerfile.encode()).hexdigest()[0:16]
669ee3c9eebSPatrick Williams
670ee3c9eebSPatrick Williams        return result
671ee3c9eebSPatrick Williams
672ee3c9eebSPatrick Williams    @staticmethod
673ee3c9eebSPatrick Williams    def build(pkg: str, tag: str, dockerfile: str) -> None:
67422e6110bSAndrew Geissler        """Build a docker image using the Dockerfile and tagging it with 'tag'."""
675ee3c9eebSPatrick Williams
676ee3c9eebSPatrick Williams        # If we're not forcing builds, check if it already exists and skip.
677ee3c9eebSPatrick Williams        if not force_build:
6788f7146faSAndrew Geissler            if container.image.ls(
6798f7146faSAndrew Geissler                tag, "--format", '"{{.Repository}}:{{.Tag}}"'
6808f7146faSAndrew Geissler            ):
68105fb2a0aSPatrick Williams                print(
68205fb2a0aSPatrick Williams                    f"Image {tag} already exists.  Skipping.", file=sys.stderr
68305fb2a0aSPatrick Williams                )
684ee3c9eebSPatrick Williams                return
685ee3c9eebSPatrick Williams
686ee3c9eebSPatrick Williams        # Build it.
687ee3c9eebSPatrick Williams        #   Capture the output of the 'docker build' command and send it to
688ee3c9eebSPatrick Williams        #   stderr (prefixed with the package name).  This allows us to see
689a6ebc6e2SManojkiran Eda        #   progress but not pollute stdout.  Later on we output the final
690ee3c9eebSPatrick Williams        #   docker tag to stdout and we want to keep that pristine.
691ee3c9eebSPatrick Williams        #
692ee3c9eebSPatrick Williams        #   Other unusual flags:
693ee3c9eebSPatrick Williams        #       --no-cache: Bypass the Docker cache if 'force_build'.
694ee3c9eebSPatrick Williams        #       --force-rm: Clean up Docker processes if they fail.
6958f7146faSAndrew Geissler        container.build(
696ee3c9eebSPatrick Williams            proxy_args,
697ee3c9eebSPatrick Williams            "--network=host",
698ee3c9eebSPatrick Williams            "--force-rm",
699ee3c9eebSPatrick Williams            "--no-cache=true" if force_build else "--no-cache=false",
700ee3c9eebSPatrick Williams            "-t",
701ee3c9eebSPatrick Williams            tag,
702ee3c9eebSPatrick Williams            "-",
703ee3c9eebSPatrick Williams            _in=dockerfile,
704ee3c9eebSPatrick Williams            _out=(
705ee3c9eebSPatrick Williams                lambda line: print(
706ee3c9eebSPatrick Williams                    pkg + ":", line, end="", file=sys.stderr, flush=True
707ee3c9eebSPatrick Williams                )
708ee3c9eebSPatrick Williams            ),
70988dd7929SJonathan Doman            _err_to_out=True,
710ee3c9eebSPatrick Williams        )
711ee3c9eebSPatrick Williams
712ee3c9eebSPatrick Williams
713ee3c9eebSPatrick Williams# Read a bunch of environment variables.
71405fb2a0aSPatrick Williamsdocker_image_name = os.environ.get(
71505fb2a0aSPatrick Williams    "DOCKER_IMAGE_NAME", "openbmc/ubuntu-unit-test"
71605fb2a0aSPatrick Williams)
717ee3c9eebSPatrick Williamsforce_build = os.environ.get("FORCE_DOCKER_BUILD")
718ee3c9eebSPatrick Williamsis_automated_ci_build = os.environ.get("BUILD_URL", False)
719917b1774SPatrick Williamsdistro = os.environ.get("DISTRO", "ubuntu:oracular")
720ee3c9eebSPatrick Williamsbranch = os.environ.get("BRANCH", "master")
721ee3c9eebSPatrick Williamsubuntu_mirror = os.environ.get("UBUNTU_MIRROR")
72223ec3323SAndrew Geisslerdocker_reg = os.environ.get("DOCKER_REG", "public.ecr.aws/ubuntu")
723ee3c9eebSPatrick Williamshttp_proxy = os.environ.get("http_proxy")
724ee3c9eebSPatrick Williams
72565b21fb9SPatrick Williamsgerrit_project = os.environ.get("GERRIT_PROJECT")
72665b21fb9SPatrick Williamsgerrit_rev = os.environ.get("GERRIT_PATCHSET_REVISION")
727276bd0e2SPatrick Williamsgerrit_topic = os.environ.get("GERRIT_TOPIC")
72865b21fb9SPatrick Williams
729d0dabc3eSAndrew Geissler# Ensure appropriate docker build output to see progress and identify
730d0dabc3eSAndrew Geissler# any issues
731d0dabc3eSAndrew Geissleros.environ["BUILDKIT_PROGRESS"] = "plain"
732d0dabc3eSAndrew Geissler
733ee3c9eebSPatrick Williams# Set up some common variables.
734ee3c9eebSPatrick Williamsusername = os.environ.get("USER", "root")
735ee3c9eebSPatrick Williamshomedir = os.environ.get("HOME", "/root")
736ee3c9eebSPatrick Williamsgid = os.getgid()
737ee3c9eebSPatrick Williamsuid = os.getuid()
738ee3c9eebSPatrick Williams
7396825a018SJosh Lehan# Use well-known constants if user is root
7406825a018SJosh Lehanif username == "root":
7416825a018SJosh Lehan    homedir = "/root"
7426825a018SJosh Lehan    gid = 0
7436825a018SJosh Lehan    uid = 0
7446825a018SJosh Lehan
74502871c91SPatrick Williams# Special flags if setting up a deb mirror.
74602871c91SPatrick Williamsmirror = ""
74702871c91SPatrick Williamsif "ubuntu" in distro and ubuntu_mirror:
74802871c91SPatrick Williams    mirror = f"""
749e08ffba8SPatrick WilliamsRUN echo "deb {ubuntu_mirror} \
750e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME) \
751e08ffba8SPatrick Williams        main restricted universe multiverse" > /etc/apt/sources.list && \\
752e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
753e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-updates \
754e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
755e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
756e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-security \
757e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
758e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
759e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-proposed \
760e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
761e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
762e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-backports \
763e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list
76402871c91SPatrick Williams"""
76502871c91SPatrick Williams
76602871c91SPatrick Williams# Special flags for proxying.
76702871c91SPatrick Williamsproxy_cmd = ""
76834ec77e8SAdrian Ambrożewiczproxy_keyserver = ""
76902871c91SPatrick Williamsproxy_args = []
77002871c91SPatrick Williamsif http_proxy:
77102871c91SPatrick Williams    proxy_cmd = f"""
77202871c91SPatrick WilliamsRUN echo "[http]" >> {homedir}/.gitconfig && \
77302871c91SPatrick Williams    echo "proxy = {http_proxy}" >> {homedir}/.gitconfig
77402871c91SPatrick Williams"""
77534ec77e8SAdrian Ambrożewicz    proxy_keyserver = f"--keyserver-options http-proxy={http_proxy}"
77634ec77e8SAdrian Ambrożewicz
77702871c91SPatrick Williams    proxy_args.extend(
77802871c91SPatrick Williams        [
77902871c91SPatrick Williams            "--build-arg",
78002871c91SPatrick Williams            f"http_proxy={http_proxy}",
78102871c91SPatrick Williams            "--build-arg",
782d461cd6aSLei YU            f"https_proxy={http_proxy}",
78302871c91SPatrick Williams        ]
78402871c91SPatrick Williams    )
78502871c91SPatrick Williams
786ee3c9eebSPatrick Williams# Create base Dockerfile.
787a18d9c57SPatrick Williamsdockerfile_base = f"""
788fe2768c7SAndrew GeisslerFROM {docker_reg}/{distro}
78902871c91SPatrick Williams
79002871c91SPatrick Williams{mirror}
79102871c91SPatrick Williams
79202871c91SPatrick WilliamsENV DEBIAN_FRONTEND noninteractive
79302871c91SPatrick Williams
7948949d3c3SPatrick WilliamsENV PYTHONPATH "/usr/local/lib/python3.10/site-packages/"
79502871c91SPatrick Williams
796bb16ac14SPatrick Williams# Sometimes the ubuntu key expires and we need a way to force an execution
797bb16ac14SPatrick Williams# of the apt-get commands for the dbgsym-keyring.  When this happens we see
798bb16ac14SPatrick Williams# an error like: "Release: The following signatures were invalid:"
799bb16ac14SPatrick Williams# Insert a bogus echo that we can change here when we get this error to force
800bb16ac14SPatrick Williams# the update.
801bb16ac14SPatrick WilliamsRUN echo "ubuntu keyserver rev as of 2021-04-21"
802bb16ac14SPatrick Williams
80302871c91SPatrick Williams# We need the keys to be imported for dbgsym repos
80402871c91SPatrick Williams# New releases have a package, older ones fall back to manual fetching
80502871c91SPatrick Williams# https://wiki.ubuntu.com/Debug%20Symbol%20Packages
806575b5e4cSJagpal Singh Gill# Known issue with gpg to get keys via proxy -
807575b5e4cSJagpal Singh Gill# https://bugs.launchpad.net/ubuntu/+source/gnupg2/+bug/1788190, hence using
808575b5e4cSJagpal Singh Gill# curl to get keys.
80950837436SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy && \
810938d303fSJian Zhang    ( apt-get install -yy gpgv ubuntu-dbgsym-keyring || \
811575b5e4cSJagpal Singh Gill        ( apt-get install -yy dirmngr curl && \
812575b5e4cSJagpal Singh Gill          curl -sSL \
813575b5e4cSJagpal Singh Gill          'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF2EDC64DC5AEE1F6B9C621F0C8CAB6595FDFF622' \
814575b5e4cSJagpal Singh Gill          | apt-key add - ))
81502871c91SPatrick Williams
81602871c91SPatrick Williams# Parse the current repo list into a debug repo list
817e08ffba8SPatrick WilliamsRUN sed -n '/^deb /s,^deb [^ ]* ,deb http://ddebs.ubuntu.com ,p' \
818e08ffba8SPatrick Williams        /etc/apt/sources.list >/etc/apt/sources.list.d/debug.list
81902871c91SPatrick Williams
82002871c91SPatrick Williams# Remove non-existent debug repos
82141d86218SPatrick WilliamsRUN sed -i '/-\\(backports\\|security\\) /d' /etc/apt/sources.list.d/debug.list
82202871c91SPatrick Williams
82302871c91SPatrick WilliamsRUN cat /etc/apt/sources.list.d/debug.list
82402871c91SPatrick Williams
82502871c91SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy && apt-get install -yy \
82658f1915eSAndrew Jeffery    abi-compliance-checker \
8278b112068SAndrew Jeffery    abi-dumper \
82802871c91SPatrick Williams    autoconf \
82902871c91SPatrick Williams    autoconf-archive \
830af49ed51SAndrew Geissler    bison \
831af49ed51SAndrew Geissler    cmake \
832af49ed51SAndrew Geissler    curl \
833af49ed51SAndrew Geissler    dbus \
834af49ed51SAndrew Geissler    device-tree-compiler \
835af49ed51SAndrew Geissler    flex \
836dbce976dSAndrew Jeffery    g++-14 \
837dbce976dSAndrew Jeffery    gcc-14 \
838af49ed51SAndrew Geissler    git \
839b4eec87bSPatrick Williams    glib-2.0 \
8406968e83eSPatrick Williams    gnupg \
84102871c91SPatrick Williams    iproute2 \
842af49ed51SAndrew Geissler    iputils-ping \
843524a331cSManojkiran Eda    libaudit-dev \
844af49ed51SAndrew Geissler    libc6-dbg \
845af49ed51SAndrew Geissler    libc6-dev \
846c7bc4d1dSPatrick Williams    libcjson-dev \
847af49ed51SAndrew Geissler    libconfig++-dev \
848af49ed51SAndrew Geissler    libcryptsetup-dev \
849af49ed51SAndrew Geissler    libdbus-1-dev \
850af49ed51SAndrew Geissler    libevdev-dev \
851af49ed51SAndrew Geissler    libgpiod-dev \
852af49ed51SAndrew Geissler    libi2c-dev \
853af49ed51SAndrew Geissler    libjpeg-dev \
854af49ed51SAndrew Geissler    libjson-perl \
855af49ed51SAndrew Geissler    libldap2-dev \
856af49ed51SAndrew Geissler    libmimetic-dev \
85702871c91SPatrick Williams    libnl-3-dev \
85802871c91SPatrick Williams    libnl-genl-3-dev \
85902871c91SPatrick Williams    libpam0g-dev \
86002871c91SPatrick Williams    libpciaccess-dev \
861af49ed51SAndrew Geissler    libperlio-gzip-perl \
862af49ed51SAndrew Geissler    libpng-dev \
863af49ed51SAndrew Geissler    libprotobuf-dev \
864af49ed51SAndrew Geissler    libsnmp-dev \
865af49ed51SAndrew Geissler    libssl-dev \
866af49ed51SAndrew Geissler    libsystemd-dev \
867af49ed51SAndrew Geissler    libtool \
868af49ed51SAndrew Geissler    liburing-dev \
86902871c91SPatrick Williams    libxml2-utils \
8700eedeedaSPatrick Williams    libxml-simple-perl \
8716968e83eSPatrick Williams    lsb-release \
872af49ed51SAndrew Geissler    ninja-build \
873af49ed51SAndrew Geissler    npm \
874af49ed51SAndrew Geissler    pkg-config \
875af49ed51SAndrew Geissler    protobuf-compiler \
876af49ed51SAndrew Geissler    python3 \
877af49ed51SAndrew Geissler    python3-dev\
878af49ed51SAndrew Geissler    python3-git \
879af49ed51SAndrew Geissler    python3-mako \
880af49ed51SAndrew Geissler    python3-pip \
88125ba1e2fSWilliam A. Kennington III    python3-protobuf \
882af49ed51SAndrew Geissler    python3-setuptools \
883af49ed51SAndrew Geissler    python3-socks \
884af49ed51SAndrew Geissler    python3-yaml \
8859adf68d6SJohn Wedig    rsync \
886af49ed51SAndrew Geissler    shellcheck \
8878dd1bfe6SEwelina Walkusz    socat \
8886968e83eSPatrick Williams    software-properties-common \
889af49ed51SAndrew Geissler    sudo \
890af49ed51SAndrew Geissler    systemd \
891917b1774SPatrick Williams    systemd-dev \
892af49ed51SAndrew Geissler    valgrind \
893b565f825SAndrew Geissler    vim \
894af49ed51SAndrew Geissler    wget \
895af49ed51SAndrew Geissler    xxd
89602871c91SPatrick Williams
897dbce976dSAndrew JefferyRUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14 \
898dbce976dSAndrew Jeffery  --slave /usr/bin/g++ g++ /usr/bin/g++-14 \
899dbce976dSAndrew Jeffery  --slave /usr/bin/gcov gcov /usr/bin/gcov-14 \
900dbce976dSAndrew Jeffery  --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-14 \
901dbce976dSAndrew Jeffery  --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-14
902961f148bSPatrick WilliamsRUN update-alternatives --remove cpp /usr/bin/cpp && \
903dbce976dSAndrew Jeffery    update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-14 14
90402871c91SPatrick Williams
9056968e83eSPatrick Williams# Set up LLVM apt repository.
906*ed8aecafSPatrick WilliamsRUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" 19
9076968e83eSPatrick Williams
9086968e83eSPatrick Williams# Install extra clang tools
909*ed8aecafSPatrick WilliamsRUN apt-get install -y \
910*ed8aecafSPatrick Williams        clang-19 \
911*ed8aecafSPatrick Williams        clang-format-19 \
912*ed8aecafSPatrick Williams        clang-tidy-19
9136968e83eSPatrick Williams
914*ed8aecafSPatrick WilliamsRUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 1000 \
915*ed8aecafSPatrick Williams  --slave /usr/bin/clang++ clang++ /usr/bin/clang++-19 \
916*ed8aecafSPatrick Williams  --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-19 \
917*ed8aecafSPatrick Williams  --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-19 \
918e08ffba8SPatrick Williams  --slave /usr/bin/run-clang-tidy run-clang-tidy.py \
919*ed8aecafSPatrick Williams        /usr/bin/run-clang-tidy-19 \
920*ed8aecafSPatrick Williams  --slave /usr/bin/scan-build scan-build /usr/bin/scan-build-19
92102871c91SPatrick Williams
92250837436SPatrick Williams"""
92350837436SPatrick Williams
92450837436SPatrick Williamsif is_automated_ci_build:
92550837436SPatrick Williams    dockerfile_base += f"""
926a6ebc6e2SManojkiran Eda# Run an arbitrary command to pollute the docker cache regularly force us
92750837436SPatrick Williams# to re-run `apt-get update` daily.
928ee3c9eebSPatrick WilliamsRUN echo {Docker.timestamp()}
92950837436SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy
93050837436SPatrick Williams
93150837436SPatrick Williams"""
93250837436SPatrick Williams
93341d86218SPatrick Williamsdockerfile_base += """
9345e4d8402SPatrick WilliamsRUN pip3 install --break-system-packages \
935818023dfSPatrick Williams        beautysh \
936818023dfSPatrick Williams        black \
937818023dfSPatrick Williams        codespell \
938818023dfSPatrick Williams        flake8 \
9392d8c551fSEwelina Walkusz        gcovr \
940818023dfSPatrick Williams        gitlint \
941818023dfSPatrick Williams        inflection \
942f7381ad6SArya K Padman        isoduration \
943818023dfSPatrick Williams        isort \
944818023dfSPatrick Williams        jsonschema \
94516baaf73SPatrick Williams        meson==1.3.0 \
946818023dfSPatrick Williams        requests
947b08ddf77SPatrick Williams
948b08ddf77SPatrick WilliamsRUN npm install -g \
949d0757deaSXinnan Xie        eslint@v8.56.0 eslint-plugin-json@v3.1.0 \
9507d41f6d2SPatrick Williams        markdownlint-cli@latest \
951b08ddf77SPatrick Williams        prettier@latest
952fb9948a3SEd Tanous"""
953fb9948a3SEd Tanous
954ee3c9eebSPatrick Williams# Build the base and stage docker images.
955ee3c9eebSPatrick Williamsdocker_base_img_name = Docker.tagname("base", dockerfile_base)
956ee3c9eebSPatrick WilliamsDocker.build("base", docker_base_img_name, dockerfile_base)
957ee3c9eebSPatrick WilliamsPackage.generate_all()
95802871c91SPatrick Williams
959ee3c9eebSPatrick Williams# Create the final Dockerfile.
960a18d9c57SPatrick Williamsdockerfile = f"""
96102871c91SPatrick Williams# Build the final output image
962a18d9c57SPatrick WilliamsFROM {docker_base_img_name}
963ee3c9eebSPatrick Williams{Package.df_all_copycmds()}
96402871c91SPatrick Williams
96502871c91SPatrick Williams# Some of our infrastructure still relies on the presence of this file
96602871c91SPatrick Williams# even though it is no longer needed to rebuild the docker environment
96702871c91SPatrick Williams# NOTE: The file is sorted to ensure the ordering is stable.
968ee3c9eebSPatrick WilliamsRUN echo '{Package.depcache()}' > /tmp/depcache
96902871c91SPatrick Williams
97067cc0616SPatrick Williams# Ensure the group, user, and home directory are created (or rename them if
97167cc0616SPatrick Williams# they already exist).
97267cc0616SPatrick WilliamsRUN if grep -q ":{gid}:" /etc/group ; then \
97367cc0616SPatrick Williams        groupmod -n {username} $(awk -F : '{{ if ($3 == {gid}) {{ print $1 }} }}' /etc/group) ; \
97467cc0616SPatrick Williams    else \
97567cc0616SPatrick Williams        groupadd -f -g {gid} {username} ; \
97667cc0616SPatrick Williams    fi
97702871c91SPatrick WilliamsRUN mkdir -p "{os.path.dirname(homedir)}"
97867cc0616SPatrick WilliamsRUN if grep -q ":{uid}:" /etc/passwd ; then \
97973b3ee91SPatrick Williams        usermod -l {username} -d {homedir} -m $(awk -F : '{{ if ($3 == {uid}) {{ print $1 }} }}' /etc/passwd) ; \
98067cc0616SPatrick Williams    else \
98167cc0616SPatrick Williams        useradd -d {homedir} -m -u {uid} -g {gid} {username} ; \
98267cc0616SPatrick Williams    fi
98302871c91SPatrick WilliamsRUN sed -i '1iDefaults umask=000' /etc/sudoers
98402871c91SPatrick WilliamsRUN echo "{username} ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
98502871c91SPatrick Williams
986305a9a5dSAndrew Geissler# Ensure user has ability to write to /usr/local for different tool
987305a9a5dSAndrew Geissler# and data installs
9887bb00b13SAndrew GeisslerRUN chown -R {username}:{username} /usr/local/share
989305a9a5dSAndrew Geissler
990ab4fee83SJonathan Doman# Update library cache
991ab4fee83SJonathan DomanRUN ldconfig
992ab4fee83SJonathan Doman
99302871c91SPatrick Williams{proxy_cmd}
99402871c91SPatrick Williams
99502871c91SPatrick WilliamsRUN /bin/bash
99602871c91SPatrick Williams"""
99702871c91SPatrick Williams
998a18d9c57SPatrick Williams# Do the final docker build
999ee3c9eebSPatrick Williamsdocker_final_img_name = Docker.tagname(None, dockerfile)
1000ee3c9eebSPatrick WilliamsDocker.build("final", docker_final_img_name, dockerfile)
1001ee3c9eebSPatrick Williams
100200536fbeSPatrick Williams# Print the tag of the final image.
100300536fbeSPatrick Williamsprint(docker_final_img_name)
1004