xref: /openbmc/openbmc-build-scripts/scripts/build-unit-test-docker (revision c7e719f9b58e2e27a13586b3ef33b14fa5e2941d)
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(
103e4b761feSPatrick Williams        rev="1.88.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(
136e4b761feSPatrick Williams        rev="11.2.0",
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(
152e4b761feSPatrick Williams        rev="v1.16.0",
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(
158e4b761feSPatrick Williams        rev="v1.65.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(
167e4b761feSPatrick Williams        rev="v3.12.0",
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(
178e4b761feSPatrick Williams        rev="json-c-0.18-20240915",
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(
186e4b761feSPatrick Williams        rev="11.0.0",
187ee3c9eebSPatrick Williams        build_type="cmake",
188ee3c9eebSPatrick Williams    ),
189ee3c9eebSPatrick Williams    "tristanpenman/valijson": PackageDef(
190e4b761feSPatrick Williams        rev="v1.0.5",
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    ),
197*c7e719f9SPatrick Williams    "libgpiod": PackageDef(
198*c7e719f9SPatrick Williams        rev="1.6.5",
199*c7e719f9SPatrick Williams        url=(
200*c7e719f9SPatrick Williams            lambda pkg, rev: f"https://git.kernel.org/pub/scm/libs/{pkg}/{pkg}.git/snapshot/{pkg}-{rev}.tar.gz"
201*c7e719f9SPatrick Williams        ),
202*c7e719f9SPatrick Williams        build_type="autogen",
203*c7e719f9SPatrick Williams        config_flags=["--enable-bindings-cxx"],
204*c7e719f9SPatrick Williams    ),
205ee3c9eebSPatrick Williams    "open-power/pdbg": PackageDef(build_type="autoconf"),
206ee3c9eebSPatrick Williams    "openbmc/gpioplus": PackageDef(
207ee3c9eebSPatrick Williams        build_type="meson",
208ee3c9eebSPatrick Williams        config_flags=[
209aae36d18SPatrick Williams            "-Dexamples=false",
210aae36d18SPatrick Williams            "-Dtests=disabled",
211aae36d18SPatrick Williams        ],
212ee3c9eebSPatrick Williams    ),
213ee3c9eebSPatrick Williams    "openbmc/phosphor-dbus-interfaces": PackageDef(
214ee3c9eebSPatrick Williams        depends=["openbmc/sdbusplus"],
215ee3c9eebSPatrick Williams        build_type="meson",
2164fe87776SWilliam A. Kennington III        config_flags=["-Dgenerate_md=false"],
217ee3c9eebSPatrick Williams    ),
218ee3c9eebSPatrick Williams    "openbmc/phosphor-logging": PackageDef(
219ee3c9eebSPatrick Williams        depends=[
22083394610SPatrick Williams            "USCiLab/cereal",
22183394610SPatrick Williams            "openbmc/phosphor-dbus-interfaces",
22283394610SPatrick Williams            "openbmc/sdbusplus",
22383394610SPatrick Williams            "openbmc/sdeventplus",
224aae36d18SPatrick Williams        ],
225f79ce4c4SPatrick Williams        build_type="meson",
226ee3c9eebSPatrick Williams        config_flags=[
2276c98f280SWilliam A. Kennington III            "-Dlibonly=true",
2286c98f280SWilliam A. Kennington III            "-Dtests=disabled",
229aae36d18SPatrick Williams        ],
230ee3c9eebSPatrick Williams    ),
231ee3c9eebSPatrick Williams    "openbmc/phosphor-objmgr": PackageDef(
232ee3c9eebSPatrick Williams        depends=[
23311e5762cSBrad Bishop            "CLIUtils/CLI11",
23470af95caSPatrick Williams            "boost",
23583394610SPatrick Williams            "leethomason/tinyxml2",
23670af95caSPatrick Williams            "openbmc/phosphor-dbus-interfaces",
23783394610SPatrick Williams            "openbmc/phosphor-logging",
23883394610SPatrick Williams            "openbmc/sdbusplus",
239aae36d18SPatrick Williams        ],
2401197e359SBrad Bishop        build_type="meson",
2411197e359SBrad Bishop        config_flags=[
2421197e359SBrad Bishop            "-Dtests=disabled",
2431197e359SBrad Bishop        ],
244ee3c9eebSPatrick Williams    ),
245c02ff271SJason M. Bills    "openbmc/libpeci": PackageDef(
246c02ff271SJason M. Bills        build_type="meson",
247c02ff271SJason M. Bills        config_flags=[
248c02ff271SJason M. Bills            "-Draw-peci=disabled",
249c02ff271SJason M. Bills        ],
250c02ff271SJason M. Bills    ),
2511c19e453SManojkiran Eda    "openbmc/libpldm": PackageDef(
252ee3c9eebSPatrick Williams        build_type="meson",
25329163971SAndrew Jeffery        config_flags=[
25429163971SAndrew Jeffery            "-Dabi=deprecated,stable",
25529163971SAndrew Jeffery            "-Dtests=false",
25629163971SAndrew Jeffery            "-Dabi-compliance-check=false",
25729163971SAndrew Jeffery        ],
258ee3c9eebSPatrick Williams    ),
259ee3c9eebSPatrick Williams    "openbmc/sdbusplus": PackageDef(
26054d01da4SPatrick Williams        depends=[
26154d01da4SPatrick Williams            "nlohmann/json",
26254d01da4SPatrick Williams        ],
263ee3c9eebSPatrick Williams        build_type="meson",
264ee3c9eebSPatrick Williams        custom_post_dl=[
265aae36d18SPatrick Williams            "cd tools",
266aae36d18SPatrick Williams            f"./setup.py install --root=/ --prefix={prefix}",
267aae36d18SPatrick Williams            "cd ..",
268aae36d18SPatrick Williams        ],
269ee3c9eebSPatrick Williams        config_flags=[
270aae36d18SPatrick Williams            "-Dexamples=disabled",
271aae36d18SPatrick Williams            "-Dtests=disabled",
272aae36d18SPatrick Williams        ],
273b16f3e20SPatrick Williams    ),
274ee3c9eebSPatrick Williams    "openbmc/sdeventplus": PackageDef(
27570af95caSPatrick Williams        depends=[
27670af95caSPatrick Williams            "openbmc/stdplus",
27770af95caSPatrick Williams        ],
278ee3c9eebSPatrick Williams        build_type="meson",
279ee3c9eebSPatrick Williams        config_flags=[
280ee3c9eebSPatrick Williams            "-Dexamples=false",
281ee3c9eebSPatrick Williams            "-Dtests=disabled",
282ee3c9eebSPatrick Williams        ],
283ee3c9eebSPatrick Williams    ),
284ee3c9eebSPatrick Williams    "openbmc/stdplus": PackageDef(
28570af95caSPatrick Williams        depends=[
28670af95caSPatrick Williams            "fmtlib/fmt",
287ca1bf0c0SWilliam A. Kennington III            "google/googletest",
288ca1bf0c0SWilliam A. Kennington III            "Naios/function2",
28970af95caSPatrick Williams        ],
290ee3c9eebSPatrick Williams        build_type="meson",
291ee3c9eebSPatrick Williams        config_flags=[
292ee3c9eebSPatrick Williams            "-Dexamples=false",
293ee3c9eebSPatrick Williams            "-Dtests=disabled",
294ca1bf0c0SWilliam A. Kennington III            "-Dgtest=enabled",
295ee3c9eebSPatrick Williams        ],
296ee3c9eebSPatrick Williams    ),
297ee3c9eebSPatrick Williams}  # type: Dict[str, PackageDef]
29802871c91SPatrick Williams
29902871c91SPatrick Williams# Define common flags used for builds
30002871c91SPatrick Williamsconfigure_flags = " ".join(
30102871c91SPatrick Williams    [
30202871c91SPatrick Williams        f"--prefix={prefix}",
30302871c91SPatrick Williams    ]
30402871c91SPatrick Williams)
30502871c91SPatrick Williamscmake_flags = " ".join(
30602871c91SPatrick Williams    [
30702871c91SPatrick Williams        "-DBUILD_SHARED_LIBS=ON",
3080f2086b3SPatrick Williams        "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
30902871c91SPatrick Williams        f"-DCMAKE_INSTALL_PREFIX:PATH={prefix}",
3100f2086b3SPatrick Williams        "-GNinja",
3110f2086b3SPatrick Williams        "-DCMAKE_MAKE_PROGRAM=ninja",
31202871c91SPatrick Williams    ]
31302871c91SPatrick Williams)
31402871c91SPatrick Williamsmeson_flags = " ".join(
31502871c91SPatrick Williams    [
31602871c91SPatrick Williams        "--wrap-mode=nodownload",
31702871c91SPatrick Williams        f"-Dprefix={prefix}",
31802871c91SPatrick Williams    ]
31902871c91SPatrick Williams)
32002871c91SPatrick Williams
321ee3c9eebSPatrick Williams
322ee3c9eebSPatrick Williamsclass Package(threading.Thread):
323ee3c9eebSPatrick Williams    """Class used to build the Docker stages for each package.
324ee3c9eebSPatrick Williams
325ee3c9eebSPatrick Williams    Generally, this class should not be instantiated directly but through
326ee3c9eebSPatrick Williams    Package.generate_all().
327ee3c9eebSPatrick Williams    """
328ee3c9eebSPatrick Williams
329ee3c9eebSPatrick Williams    # Copy the packages dictionary.
330ee3c9eebSPatrick Williams    packages = packages.copy()
331ee3c9eebSPatrick Williams
332ee3c9eebSPatrick Williams    # Lock used for thread-safety.
333ee3c9eebSPatrick Williams    lock = threading.Lock()
334ee3c9eebSPatrick Williams
335ee3c9eebSPatrick Williams    def __init__(self, pkg: str):
336ee3c9eebSPatrick Williams        """pkg - The name of this package (ex. foo/bar )"""
337ee3c9eebSPatrick Williams        super(Package, self).__init__()
338ee3c9eebSPatrick Williams
339ee3c9eebSPatrick Williams        self.package = pkg
340ee3c9eebSPatrick Williams        self.exception = None  # type: Optional[Exception]
341ee3c9eebSPatrick Williams
342ee3c9eebSPatrick Williams        # Reference to this package's
343ee3c9eebSPatrick Williams        self.pkg_def = Package.packages[pkg]
344ee3c9eebSPatrick Williams        self.pkg_def["__package"] = self
345ee3c9eebSPatrick Williams
346ee3c9eebSPatrick Williams    def run(self) -> None:
347ee3c9eebSPatrick Williams        """Thread 'run' function.  Builds the Docker stage."""
348ee3c9eebSPatrick Williams
349ee3c9eebSPatrick Williams        # In case this package has no rev, fetch it from Github.
350ee3c9eebSPatrick Williams        self._update_rev()
351ee3c9eebSPatrick Williams
352ee3c9eebSPatrick Williams        # Find all the Package objects that this package depends on.
353ee3c9eebSPatrick Williams        #   This section is locked because we are looking into another
354ee3c9eebSPatrick Williams        #   package's PackageDef dict, which could be being modified.
355ee3c9eebSPatrick Williams        Package.lock.acquire()
356ee3c9eebSPatrick Williams        deps: Iterable[Package] = [
357ee3c9eebSPatrick Williams            Package.packages[deppkg]["__package"]
358ee3c9eebSPatrick Williams            for deppkg in self.pkg_def.get("depends", [])
359ee3c9eebSPatrick Williams        ]
360ee3c9eebSPatrick Williams        Package.lock.release()
361ee3c9eebSPatrick Williams
362ee3c9eebSPatrick Williams        # Wait until all the depends finish building.  We need them complete
363ee3c9eebSPatrick Williams        # for the "COPY" commands.
364ee3c9eebSPatrick Williams        for deppkg in deps:
365ee3c9eebSPatrick Williams            deppkg.join()
366ee3c9eebSPatrick Williams
367ee3c9eebSPatrick Williams        # Generate this package's Dockerfile.
368ee3c9eebSPatrick Williams        dockerfile = f"""
369ee3c9eebSPatrick WilliamsFROM {docker_base_img_name}
370ee3c9eebSPatrick Williams{self._df_copycmds()}
371ee3c9eebSPatrick Williams{self._df_build()}
372ee3c9eebSPatrick Williams"""
373ee3c9eebSPatrick Williams
374ee3c9eebSPatrick Williams        # Generate the resulting tag name and save it to the PackageDef.
375ee3c9eebSPatrick Williams        #   This section is locked because we are modifying the PackageDef,
376ee3c9eebSPatrick Williams        #   which can be accessed by other threads.
377ee3c9eebSPatrick Williams        Package.lock.acquire()
378ee3c9eebSPatrick Williams        tag = Docker.tagname(self._stagename(), dockerfile)
379ee3c9eebSPatrick Williams        self.pkg_def["__tag"] = tag
380ee3c9eebSPatrick Williams        Package.lock.release()
381ee3c9eebSPatrick Williams
382ee3c9eebSPatrick Williams        # Do the build / save any exceptions.
383ee3c9eebSPatrick Williams        try:
384ee3c9eebSPatrick Williams            Docker.build(self.package, tag, dockerfile)
385ee3c9eebSPatrick Williams        except Exception as e:
386ee3c9eebSPatrick Williams            self.exception = e
387ee3c9eebSPatrick Williams
388ee3c9eebSPatrick Williams    @classmethod
389ee3c9eebSPatrick Williams    def generate_all(cls) -> None:
390ee3c9eebSPatrick Williams        """Ensure a Docker stage is created for all defined packages.
391ee3c9eebSPatrick Williams
392ee3c9eebSPatrick Williams        These are done in parallel but with appropriate blocking per
393ee3c9eebSPatrick Williams        package 'depends' specifications.
394ee3c9eebSPatrick Williams        """
395ee3c9eebSPatrick Williams
396ee3c9eebSPatrick Williams        # Create a Package for each defined package.
397ee3c9eebSPatrick Williams        pkg_threads = [Package(p) for p in cls.packages.keys()]
398ee3c9eebSPatrick Williams
399ee3c9eebSPatrick Williams        # Start building them all.
4006dbd7807SPatrick Williams        #   This section is locked because threads depend on each other,
4016dbd7807SPatrick Williams        #   based on the packages, and they cannot 'join' on a thread
4026dbd7807SPatrick Williams        #   which is not yet started.  Adding a lock here allows all the
4036dbd7807SPatrick Williams        #   threads to start before they 'join' their dependencies.
4046dbd7807SPatrick Williams        Package.lock.acquire()
405ee3c9eebSPatrick Williams        for t in pkg_threads:
406ee3c9eebSPatrick Williams            t.start()
4076dbd7807SPatrick Williams        Package.lock.release()
408ee3c9eebSPatrick Williams
409ee3c9eebSPatrick Williams        # Wait for completion.
410ee3c9eebSPatrick Williams        for t in pkg_threads:
411ee3c9eebSPatrick Williams            t.join()
412ee3c9eebSPatrick Williams            # Check if the thread saved off its own exception.
413ee3c9eebSPatrick Williams            if t.exception:
414ee3c9eebSPatrick Williams                print(f"Package {t.package} failed!", file=sys.stderr)
415ee3c9eebSPatrick Williams                raise t.exception
416ee3c9eebSPatrick Williams
417ee3c9eebSPatrick Williams    @staticmethod
418ee3c9eebSPatrick Williams    def df_all_copycmds() -> str:
419ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to copy all packages
420ee3c9eebSPatrick Williams        into the final image.
421ee3c9eebSPatrick Williams        """
422ee3c9eebSPatrick Williams        return Package.df_copycmds_set(Package.packages.keys())
423ee3c9eebSPatrick Williams
424ee3c9eebSPatrick Williams    @classmethod
425ee3c9eebSPatrick Williams    def depcache(cls) -> str:
426ee3c9eebSPatrick Williams        """Create the contents of the '/tmp/depcache'.
427ee3c9eebSPatrick Williams        This file is a comma-separated list of "<pkg>:<rev>".
428ee3c9eebSPatrick Williams        """
429ee3c9eebSPatrick Williams
430ee3c9eebSPatrick Williams        # This needs to be sorted for consistency.
431ee3c9eebSPatrick Williams        depcache = ""
432ee3c9eebSPatrick Williams        for pkg in sorted(cls.packages.keys()):
433ee3c9eebSPatrick Williams            depcache += "%s:%s," % (pkg, cls.packages[pkg]["rev"])
434ee3c9eebSPatrick Williams        return depcache
435ee3c9eebSPatrick Williams
436276bd0e2SPatrick Williams    def _check_gerrit_topic(self) -> bool:
437276bd0e2SPatrick Williams        if not gerrit_topic:
438276bd0e2SPatrick Williams            return False
439276bd0e2SPatrick Williams        if not self.package.startswith("openbmc/"):
440276bd0e2SPatrick Williams            return False
441276bd0e2SPatrick Williams        if gerrit_project == self.package and gerrit_rev:
442276bd0e2SPatrick Williams            return False
443276bd0e2SPatrick Williams
444276bd0e2SPatrick Williams        try:
445276bd0e2SPatrick Williams            commits = json.loads(
446276bd0e2SPatrick Williams                urllib.request.urlopen(
447276bd0e2SPatrick Williams                    f"https://gerrit.openbmc.org/changes/?q=status:open+project:{self.package}+topic:{gerrit_topic}"
448276bd0e2SPatrick Williams                )
449276bd0e2SPatrick Williams                .read()
450276bd0e2SPatrick Williams                .splitlines()[-1]
451276bd0e2SPatrick Williams            )
452276bd0e2SPatrick Williams
453276bd0e2SPatrick Williams            if len(commits) == 0:
454276bd0e2SPatrick Williams                return False
455276bd0e2SPatrick Williams            if len(commits) > 1:
456276bd0e2SPatrick Williams                print(
457276bd0e2SPatrick Williams                    f"{self.package} has more than 1 commit under {gerrit_topic}; using lastest upstream: {len(commits)}",
458276bd0e2SPatrick Williams                    file=sys.stderr,
459276bd0e2SPatrick Williams                )
460276bd0e2SPatrick Williams                return False
461276bd0e2SPatrick Williams
462276bd0e2SPatrick Williams            change_id = commits[0]["id"]
463276bd0e2SPatrick Williams
464276bd0e2SPatrick Williams            commit = json.loads(
465276bd0e2SPatrick Williams                urllib.request.urlopen(
466276bd0e2SPatrick Williams                    f"https://gerrit.openbmc.org/changes/{change_id}/revisions/current/commit"
467276bd0e2SPatrick Williams                )
468276bd0e2SPatrick Williams                .read()
469276bd0e2SPatrick Williams                .splitlines()[-1]
470276bd0e2SPatrick Williams            )["commit"]
471276bd0e2SPatrick Williams
472276bd0e2SPatrick Williams            print(
473276bd0e2SPatrick Williams                f"Using {commit} from {gerrit_topic} for {self.package}",
474276bd0e2SPatrick Williams                file=sys.stderr,
475276bd0e2SPatrick Williams            )
476276bd0e2SPatrick Williams            self.pkg_def["rev"] = commit
477276bd0e2SPatrick Williams            return True
478276bd0e2SPatrick Williams
479276bd0e2SPatrick Williams        except urllib.error.HTTPError as e:
480276bd0e2SPatrick Williams            print(
481276bd0e2SPatrick Williams                f"Error loading topic {gerrit_topic} for {self.package}: ",
482276bd0e2SPatrick Williams                e,
483276bd0e2SPatrick Williams                file=sys.stderr,
484276bd0e2SPatrick Williams            )
485276bd0e2SPatrick Williams            return False
486276bd0e2SPatrick Williams
487ee3c9eebSPatrick Williams    def _update_rev(self) -> None:
488ee3c9eebSPatrick Williams        """Look up the HEAD for missing a static rev."""
489ee3c9eebSPatrick Williams
490ee3c9eebSPatrick Williams        if "rev" in self.pkg_def:
491ee3c9eebSPatrick Williams            return
492ee3c9eebSPatrick Williams
493276bd0e2SPatrick Williams        if self._check_gerrit_topic():
494276bd0e2SPatrick Williams            return
495276bd0e2SPatrick Williams
49665b21fb9SPatrick Williams        # Check if Jenkins/Gerrit gave us a revision and use it.
49765b21fb9SPatrick Williams        if gerrit_project == self.package and gerrit_rev:
49865b21fb9SPatrick Williams            print(
49965b21fb9SPatrick Williams                f"Found Gerrit revision for {self.package}: {gerrit_rev}",
50065b21fb9SPatrick Williams                file=sys.stderr,
50165b21fb9SPatrick Williams            )
50265b21fb9SPatrick Williams            self.pkg_def["rev"] = gerrit_rev
50365b21fb9SPatrick Williams            return
50465b21fb9SPatrick Williams
505ee3c9eebSPatrick Williams        # Ask Github for all the branches.
50605fb2a0aSPatrick Williams        lookup = git(
50705fb2a0aSPatrick Williams            "ls-remote", "--heads", f"https://github.com/{self.package}"
50805fb2a0aSPatrick Williams        )
509ee3c9eebSPatrick Williams
510ee3c9eebSPatrick Williams        # Find the branch matching {branch} (or fallback to master).
511ee3c9eebSPatrick Williams        #   This section is locked because we are modifying the PackageDef.
512ee3c9eebSPatrick Williams        Package.lock.acquire()
513ee3c9eebSPatrick Williams        for line in lookup.split("\n"):
514f3d27e64SAndrew Geissler            if re.fullmatch(f".*{branch}$", line.strip()):
515ee3c9eebSPatrick Williams                self.pkg_def["rev"] = line.split()[0]
516f3d27e64SAndrew Geissler                break
517c7d73646SPatrick Williams            elif (
518c7d73646SPatrick Williams                "refs/heads/master" in line or "refs/heads/main" in line
519c7d73646SPatrick Williams            ) and "rev" not in self.pkg_def:
520ee3c9eebSPatrick Williams                self.pkg_def["rev"] = line.split()[0]
521ee3c9eebSPatrick Williams        Package.lock.release()
522ee3c9eebSPatrick Williams
523ee3c9eebSPatrick Williams    def _stagename(self) -> str:
524ee3c9eebSPatrick Williams        """Create a name for the Docker stage associated with this pkg."""
525ee3c9eebSPatrick Williams        return self.package.replace("/", "-").lower()
526ee3c9eebSPatrick Williams
527ee3c9eebSPatrick Williams    def _url(self) -> str:
528ee3c9eebSPatrick Williams        """Get the URL for this package."""
529ee3c9eebSPatrick Williams        rev = self.pkg_def["rev"]
530ee3c9eebSPatrick Williams
531ee3c9eebSPatrick Williams        # If the lambda exists, call it.
532ee3c9eebSPatrick Williams        if "url" in self.pkg_def:
533ee3c9eebSPatrick Williams            return self.pkg_def["url"](self.package, rev)
534ee3c9eebSPatrick Williams
535ee3c9eebSPatrick Williams        # Default to the github archive URL.
536ee3c9eebSPatrick Williams        return f"https://github.com/{self.package}/archive/{rev}.tar.gz"
537ee3c9eebSPatrick Williams
538ee3c9eebSPatrick Williams    def _cmd_download(self) -> str:
539ee3c9eebSPatrick Williams        """Formulate the command necessary to download and unpack to source."""
540ee3c9eebSPatrick Williams
541ee3c9eebSPatrick Williams        url = self._url()
542ee3c9eebSPatrick Williams        if ".tar." not in url:
543ee3c9eebSPatrick Williams            raise NotImplementedError(
544ee3c9eebSPatrick Williams                f"Unhandled download type for {self.package}: {url}"
545ee3c9eebSPatrick Williams            )
546ee3c9eebSPatrick Williams
547ee3c9eebSPatrick Williams        cmd = f"curl -L {url} | tar -x"
548ee3c9eebSPatrick Williams
549ee3c9eebSPatrick Williams        if url.endswith(".bz2"):
550ee3c9eebSPatrick Williams            cmd += "j"
551ee3c9eebSPatrick Williams        elif url.endswith(".gz"):
552ee3c9eebSPatrick Williams            cmd += "z"
553ee3c9eebSPatrick Williams        else:
554ee3c9eebSPatrick Williams            raise NotImplementedError(
555ee3c9eebSPatrick Williams                f"Unknown tar flags needed for {self.package}: {url}"
556ee3c9eebSPatrick Williams            )
557ee3c9eebSPatrick Williams
558ee3c9eebSPatrick Williams        return cmd
559ee3c9eebSPatrick Williams
560ee3c9eebSPatrick Williams    def _cmd_cd_srcdir(self) -> str:
561ee3c9eebSPatrick Williams        """Formulate the command necessary to 'cd' into the source dir."""
562ee3c9eebSPatrick Williams        return f"cd {self.package.split('/')[-1]}*"
563ee3c9eebSPatrick Williams
564ee3c9eebSPatrick Williams    def _df_copycmds(self) -> str:
565ee3c9eebSPatrick Williams        """Formulate the dockerfile snippet necessary to COPY all depends."""
566ee3c9eebSPatrick Williams
567ee3c9eebSPatrick Williams        if "depends" not in self.pkg_def:
568ee3c9eebSPatrick Williams            return ""
569ee3c9eebSPatrick Williams        return Package.df_copycmds_set(self.pkg_def["depends"])
570ee3c9eebSPatrick Williams
571ee3c9eebSPatrick Williams    @staticmethod
572ee3c9eebSPatrick Williams    def df_copycmds_set(pkgs: Iterable[str]) -> str:
573ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to COPY a set of
574ee3c9eebSPatrick Williams        packages into a Docker stage.
575ee3c9eebSPatrick Williams        """
576ee3c9eebSPatrick Williams
577ee3c9eebSPatrick Williams        copy_cmds = ""
578ee3c9eebSPatrick Williams
579ee3c9eebSPatrick Williams        # Sort the packages for consistency.
580ee3c9eebSPatrick Williams        for p in sorted(pkgs):
581ee3c9eebSPatrick Williams            tag = Package.packages[p]["__tag"]
582ee3c9eebSPatrick Williams            copy_cmds += f"COPY --from={tag} {prefix} {prefix}\n"
583ee3c9eebSPatrick Williams            # Workaround for upstream docker bug and multiple COPY cmds
584ee3c9eebSPatrick Williams            # https://github.com/moby/moby/issues/37965
585ee3c9eebSPatrick Williams            copy_cmds += "RUN true\n"
586ee3c9eebSPatrick Williams
587ee3c9eebSPatrick Williams        return copy_cmds
588ee3c9eebSPatrick Williams
589ee3c9eebSPatrick Williams    def _df_build(self) -> str:
590ee3c9eebSPatrick Williams        """Formulate the Dockerfile snippet necessary to download, build, and
591ee3c9eebSPatrick Williams        install a package into a Docker stage.
592ee3c9eebSPatrick Williams        """
593ee3c9eebSPatrick Williams
594ee3c9eebSPatrick Williams        # Download and extract source.
595ee3c9eebSPatrick Williams        result = f"RUN {self._cmd_download()} && {self._cmd_cd_srcdir()} && "
596ee3c9eebSPatrick Williams
597ee3c9eebSPatrick Williams        # Handle 'custom_post_dl' commands.
598ee3c9eebSPatrick Williams        custom_post_dl = self.pkg_def.get("custom_post_dl")
599ee3c9eebSPatrick Williams        if custom_post_dl:
600ee3c9eebSPatrick Williams            result += " && ".join(custom_post_dl) + " && "
601ee3c9eebSPatrick Williams
602ee3c9eebSPatrick Williams        # Build and install package based on 'build_type'.
603ee3c9eebSPatrick Williams        build_type = self.pkg_def["build_type"]
604ee3c9eebSPatrick Williams        if build_type == "autoconf":
605ee3c9eebSPatrick Williams            result += self._cmd_build_autoconf()
606*c7e719f9SPatrick Williams        elif build_type == "autogen":
607*c7e719f9SPatrick Williams            result += self._cmd_build_autogen()
608ee3c9eebSPatrick Williams        elif build_type == "cmake":
609ee3c9eebSPatrick Williams            result += self._cmd_build_cmake()
610ee3c9eebSPatrick Williams        elif build_type == "custom":
611ee3c9eebSPatrick Williams            result += self._cmd_build_custom()
612ee3c9eebSPatrick Williams        elif build_type == "make":
613ee3c9eebSPatrick Williams            result += self._cmd_build_make()
614ee3c9eebSPatrick Williams        elif build_type == "meson":
615ee3c9eebSPatrick Williams            result += self._cmd_build_meson()
616ee3c9eebSPatrick Williams        else:
617ee3c9eebSPatrick Williams            raise NotImplementedError(
618ee3c9eebSPatrick Williams                f"Unhandled build type for {self.package}: {build_type}"
619ee3c9eebSPatrick Williams            )
620ee3c9eebSPatrick Williams
6216bce2ca1SPatrick Williams        # Handle 'custom_post_install' commands.
6226bce2ca1SPatrick Williams        custom_post_install = self.pkg_def.get("custom_post_install")
6236bce2ca1SPatrick Williams        if custom_post_install:
6246bce2ca1SPatrick Williams            result += " && " + " && ".join(custom_post_install)
6256bce2ca1SPatrick Williams
626ee3c9eebSPatrick Williams        return result
627ee3c9eebSPatrick Williams
628ee3c9eebSPatrick Williams    def _cmd_build_autoconf(self) -> str:
629ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
630ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
631ee3c9eebSPatrick Williams        result = "./bootstrap.sh && "
632ee3c9eebSPatrick Williams        result += f"{env} ./configure {configure_flags} {options} && "
633ee3c9eebSPatrick Williams        result += f"make -j{proc_count} && make install"
634ee3c9eebSPatrick Williams        return result
635ee3c9eebSPatrick Williams
636*c7e719f9SPatrick Williams    def _cmd_build_autogen(self) -> str:
637*c7e719f9SPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
638*c7e719f9SPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
639*c7e719f9SPatrick Williams        result = f"{env} ./autogen.sh {configure_flags} {options} && "
640*c7e719f9SPatrick Williams        result += "make && make install"
641*c7e719f9SPatrick Williams        return result
642*c7e719f9SPatrick Williams
643ee3c9eebSPatrick Williams    def _cmd_build_cmake(self) -> str:
644ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
645ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
646ee3c9eebSPatrick Williams        result = "mkdir builddir && cd builddir && "
647ee3c9eebSPatrick Williams        result += f"{env} cmake {cmake_flags} {options} .. && "
648ee3c9eebSPatrick Williams        result += "cmake --build . --target all && "
649ee3c9eebSPatrick Williams        result += "cmake --build . --target install && "
650ee3c9eebSPatrick Williams        result += "cd .."
651ee3c9eebSPatrick Williams        return result
652ee3c9eebSPatrick Williams
653ee3c9eebSPatrick Williams    def _cmd_build_custom(self) -> str:
654ee3c9eebSPatrick Williams        return " && ".join(self.pkg_def.get("build_steps", []))
655ee3c9eebSPatrick Williams
656ee3c9eebSPatrick Williams    def _cmd_build_make(self) -> str:
657ee3c9eebSPatrick Williams        return f"make -j{proc_count} && make install"
658ee3c9eebSPatrick Williams
659ee3c9eebSPatrick Williams    def _cmd_build_meson(self) -> str:
660ee3c9eebSPatrick Williams        options = " ".join(self.pkg_def.get("config_flags", []))
661ee3c9eebSPatrick Williams        env = " ".join(self.pkg_def.get("config_env", []))
662e2da11adSAndrew Jeffery        result = f"{env} meson setup builddir {meson_flags} {options} && "
663ee3c9eebSPatrick Williams        result += "ninja -C builddir && ninja -C builddir install"
664ee3c9eebSPatrick Williams        return result
665ee3c9eebSPatrick Williams
666ee3c9eebSPatrick Williams
667ee3c9eebSPatrick Williamsclass Docker:
668ee3c9eebSPatrick Williams    """Class to assist with Docker interactions.  All methods are static."""
669ee3c9eebSPatrick Williams
670ee3c9eebSPatrick Williams    @staticmethod
671ee3c9eebSPatrick Williams    def timestamp() -> str:
672ee3c9eebSPatrick Williams        """Generate a timestamp for today using the ISO week."""
673ee3c9eebSPatrick Williams        today = date.today().isocalendar()
674ee3c9eebSPatrick Williams        return f"{today[0]}-W{today[1]:02}"
675ee3c9eebSPatrick Williams
676ee3c9eebSPatrick Williams    @staticmethod
67741d86218SPatrick Williams    def tagname(pkgname: Optional[str], dockerfile: str) -> str:
678ee3c9eebSPatrick Williams        """Generate a tag name for a package using a hash of the Dockerfile."""
679ee3c9eebSPatrick Williams        result = docker_image_name
680ee3c9eebSPatrick Williams        if pkgname:
681ee3c9eebSPatrick Williams            result += "-" + pkgname
682ee3c9eebSPatrick Williams
683ee3c9eebSPatrick Williams        result += ":" + Docker.timestamp()
684ee3c9eebSPatrick Williams        result += "-" + sha256(dockerfile.encode()).hexdigest()[0:16]
685ee3c9eebSPatrick Williams
686ee3c9eebSPatrick Williams        return result
687ee3c9eebSPatrick Williams
688ee3c9eebSPatrick Williams    @staticmethod
689ee3c9eebSPatrick Williams    def build(pkg: str, tag: str, dockerfile: str) -> None:
69022e6110bSAndrew Geissler        """Build a docker image using the Dockerfile and tagging it with 'tag'."""
691ee3c9eebSPatrick Williams
692ee3c9eebSPatrick Williams        # If we're not forcing builds, check if it already exists and skip.
693ee3c9eebSPatrick Williams        if not force_build:
6948f7146faSAndrew Geissler            if container.image.ls(
6958f7146faSAndrew Geissler                tag, "--format", '"{{.Repository}}:{{.Tag}}"'
6968f7146faSAndrew Geissler            ):
69705fb2a0aSPatrick Williams                print(
69805fb2a0aSPatrick Williams                    f"Image {tag} already exists.  Skipping.", file=sys.stderr
69905fb2a0aSPatrick Williams                )
700ee3c9eebSPatrick Williams                return
701ee3c9eebSPatrick Williams
702ee3c9eebSPatrick Williams        # Build it.
703ee3c9eebSPatrick Williams        #   Capture the output of the 'docker build' command and send it to
704ee3c9eebSPatrick Williams        #   stderr (prefixed with the package name).  This allows us to see
705a6ebc6e2SManojkiran Eda        #   progress but not pollute stdout.  Later on we output the final
706ee3c9eebSPatrick Williams        #   docker tag to stdout and we want to keep that pristine.
707ee3c9eebSPatrick Williams        #
708ee3c9eebSPatrick Williams        #   Other unusual flags:
709ee3c9eebSPatrick Williams        #       --no-cache: Bypass the Docker cache if 'force_build'.
710ee3c9eebSPatrick Williams        #       --force-rm: Clean up Docker processes if they fail.
7118f7146faSAndrew Geissler        container.build(
712ee3c9eebSPatrick Williams            proxy_args,
713ee3c9eebSPatrick Williams            "--network=host",
714ee3c9eebSPatrick Williams            "--force-rm",
715ee3c9eebSPatrick Williams            "--no-cache=true" if force_build else "--no-cache=false",
716ee3c9eebSPatrick Williams            "-t",
717ee3c9eebSPatrick Williams            tag,
718ee3c9eebSPatrick Williams            "-",
719ee3c9eebSPatrick Williams            _in=dockerfile,
720ee3c9eebSPatrick Williams            _out=(
721ee3c9eebSPatrick Williams                lambda line: print(
722ee3c9eebSPatrick Williams                    pkg + ":", line, end="", file=sys.stderr, flush=True
723ee3c9eebSPatrick Williams                )
724ee3c9eebSPatrick Williams            ),
72588dd7929SJonathan Doman            _err_to_out=True,
726ee3c9eebSPatrick Williams        )
727ee3c9eebSPatrick Williams
728ee3c9eebSPatrick Williams
729ee3c9eebSPatrick Williams# Read a bunch of environment variables.
73005fb2a0aSPatrick Williamsdocker_image_name = os.environ.get(
73105fb2a0aSPatrick Williams    "DOCKER_IMAGE_NAME", "openbmc/ubuntu-unit-test"
73205fb2a0aSPatrick Williams)
733ee3c9eebSPatrick Williamsforce_build = os.environ.get("FORCE_DOCKER_BUILD")
734ee3c9eebSPatrick Williamsis_automated_ci_build = os.environ.get("BUILD_URL", False)
7356b14190fSPatrick Williamsdistro = os.environ.get("DISTRO", "ubuntu:plucky")
736ee3c9eebSPatrick Williamsbranch = os.environ.get("BRANCH", "master")
737ee3c9eebSPatrick Williamsubuntu_mirror = os.environ.get("UBUNTU_MIRROR")
73823ec3323SAndrew Geisslerdocker_reg = os.environ.get("DOCKER_REG", "public.ecr.aws/ubuntu")
739ee3c9eebSPatrick Williamshttp_proxy = os.environ.get("http_proxy")
740ee3c9eebSPatrick Williams
74165b21fb9SPatrick Williamsgerrit_project = os.environ.get("GERRIT_PROJECT")
74265b21fb9SPatrick Williamsgerrit_rev = os.environ.get("GERRIT_PATCHSET_REVISION")
743276bd0e2SPatrick Williamsgerrit_topic = os.environ.get("GERRIT_TOPIC")
74465b21fb9SPatrick Williams
745d0dabc3eSAndrew Geissler# Ensure appropriate docker build output to see progress and identify
746d0dabc3eSAndrew Geissler# any issues
747d0dabc3eSAndrew Geissleros.environ["BUILDKIT_PROGRESS"] = "plain"
748d0dabc3eSAndrew Geissler
749ee3c9eebSPatrick Williams# Set up some common variables.
750ee3c9eebSPatrick Williamsusername = os.environ.get("USER", "root")
751ee3c9eebSPatrick Williamshomedir = os.environ.get("HOME", "/root")
752ee3c9eebSPatrick Williamsgid = os.getgid()
753ee3c9eebSPatrick Williamsuid = os.getuid()
754ee3c9eebSPatrick Williams
7556825a018SJosh Lehan# Use well-known constants if user is root
7566825a018SJosh Lehanif username == "root":
7576825a018SJosh Lehan    homedir = "/root"
7586825a018SJosh Lehan    gid = 0
7596825a018SJosh Lehan    uid = 0
7606825a018SJosh Lehan
76102871c91SPatrick Williams# Special flags if setting up a deb mirror.
76202871c91SPatrick Williamsmirror = ""
76302871c91SPatrick Williamsif "ubuntu" in distro and ubuntu_mirror:
76402871c91SPatrick Williams    mirror = f"""
765e08ffba8SPatrick WilliamsRUN echo "deb {ubuntu_mirror} \
766e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME) \
767e08ffba8SPatrick Williams        main restricted universe multiverse" > /etc/apt/sources.list && \\
768e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
769e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-updates \
770e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
771e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
772e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-security \
773e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
774e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
775e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-proposed \
776e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list && \\
777e08ffba8SPatrick Williams    echo "deb {ubuntu_mirror} \
778e08ffba8SPatrick Williams        $(. /etc/os-release && echo $VERSION_CODENAME)-backports \
779e08ffba8SPatrick Williams            main restricted universe multiverse" >> /etc/apt/sources.list
78002871c91SPatrick Williams"""
78102871c91SPatrick Williams
78202871c91SPatrick Williams# Special flags for proxying.
78302871c91SPatrick Williamsproxy_cmd = ""
78434ec77e8SAdrian Ambrożewiczproxy_keyserver = ""
78502871c91SPatrick Williamsproxy_args = []
78602871c91SPatrick Williamsif http_proxy:
78702871c91SPatrick Williams    proxy_cmd = f"""
78802871c91SPatrick WilliamsRUN echo "[http]" >> {homedir}/.gitconfig && \
78902871c91SPatrick Williams    echo "proxy = {http_proxy}" >> {homedir}/.gitconfig
7903aa71c8cSTan SiewertCOPY <<EOF_WGETRC {homedir}/.wgetrc
791f7e52612SLei YUhttps_proxy = {http_proxy}
792f7e52612SLei YUhttp_proxy = {http_proxy}
793f7e52612SLei YUuse_proxy = on
794f7e52612SLei YUEOF_WGETRC
79502871c91SPatrick Williams"""
79634ec77e8SAdrian Ambrożewicz    proxy_keyserver = f"--keyserver-options http-proxy={http_proxy}"
79734ec77e8SAdrian Ambrożewicz
79802871c91SPatrick Williams    proxy_args.extend(
79902871c91SPatrick Williams        [
80002871c91SPatrick Williams            "--build-arg",
80102871c91SPatrick Williams            f"http_proxy={http_proxy}",
80202871c91SPatrick Williams            "--build-arg",
803d461cd6aSLei YU            f"https_proxy={http_proxy}",
80402871c91SPatrick Williams        ]
80502871c91SPatrick Williams    )
80602871c91SPatrick Williams
807ee3c9eebSPatrick Williams# Create base Dockerfile.
808a18d9c57SPatrick Williamsdockerfile_base = f"""
809fe2768c7SAndrew GeisslerFROM {docker_reg}/{distro}
81002871c91SPatrick Williams
81102871c91SPatrick Williams{mirror}
81202871c91SPatrick Williams
81302871c91SPatrick WilliamsENV DEBIAN_FRONTEND noninteractive
81402871c91SPatrick Williams
8158949d3c3SPatrick WilliamsENV PYTHONPATH "/usr/local/lib/python3.10/site-packages/"
81602871c91SPatrick Williams
817bb16ac14SPatrick Williams# Sometimes the ubuntu key expires and we need a way to force an execution
818bb16ac14SPatrick Williams# of the apt-get commands for the dbgsym-keyring.  When this happens we see
819bb16ac14SPatrick Williams# an error like: "Release: The following signatures were invalid:"
820bb16ac14SPatrick Williams# Insert a bogus echo that we can change here when we get this error to force
821bb16ac14SPatrick Williams# the update.
822a1cbd404SPatrick WilliamsRUN echo "ubuntu keyserver rev as of 2025-06-25"
823bb16ac14SPatrick Williams
82402871c91SPatrick Williams# We need the keys to be imported for dbgsym repos
82502871c91SPatrick Williams# New releases have a package, older ones fall back to manual fetching
82602871c91SPatrick Williams# https://wiki.ubuntu.com/Debug%20Symbol%20Packages
827575b5e4cSJagpal Singh Gill# Known issue with gpg to get keys via proxy -
828575b5e4cSJagpal Singh Gill# https://bugs.launchpad.net/ubuntu/+source/gnupg2/+bug/1788190, hence using
829575b5e4cSJagpal Singh Gill# curl to get keys.
83050837436SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy && \
831938d303fSJian Zhang    ( apt-get install -yy gpgv ubuntu-dbgsym-keyring || \
832575b5e4cSJagpal Singh Gill        ( apt-get install -yy dirmngr curl && \
833575b5e4cSJagpal Singh Gill          curl -sSL \
834575b5e4cSJagpal Singh Gill          'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF2EDC64DC5AEE1F6B9C621F0C8CAB6595FDFF622' \
835575b5e4cSJagpal Singh Gill          | apt-key add - ))
83602871c91SPatrick Williams
83702871c91SPatrick Williams# Parse the current repo list into a debug repo list
838e08ffba8SPatrick WilliamsRUN sed -n '/^deb /s,^deb [^ ]* ,deb http://ddebs.ubuntu.com ,p' \
839e08ffba8SPatrick Williams        /etc/apt/sources.list >/etc/apt/sources.list.d/debug.list
84002871c91SPatrick Williams
84102871c91SPatrick Williams# Remove non-existent debug repos
84241d86218SPatrick WilliamsRUN sed -i '/-\\(backports\\|security\\) /d' /etc/apt/sources.list.d/debug.list
84302871c91SPatrick Williams
84402871c91SPatrick WilliamsRUN cat /etc/apt/sources.list.d/debug.list
84502871c91SPatrick Williams
84602871c91SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy && apt-get install -yy \
84758f1915eSAndrew Jeffery    abi-compliance-checker \
8488b112068SAndrew Jeffery    abi-dumper \
84902871c91SPatrick Williams    autoconf \
85002871c91SPatrick Williams    autoconf-archive \
851af49ed51SAndrew Geissler    bison \
852af49ed51SAndrew Geissler    cmake \
853af49ed51SAndrew Geissler    curl \
854af49ed51SAndrew Geissler    dbus \
855af49ed51SAndrew Geissler    device-tree-compiler \
8561c28d969SAndrew Jeffery    doxygen \
857af49ed51SAndrew Geissler    flex \
858ea1bfb27SPatrick Williams    g++-15 \
859ea1bfb27SPatrick Williams    gcc-15 \
860af49ed51SAndrew Geissler    git \
861b4eec87bSPatrick Williams    glib-2.0 \
8626968e83eSPatrick Williams    gnupg \
86302871c91SPatrick Williams    iproute2 \
864af49ed51SAndrew Geissler    iputils-ping \
865524a331cSManojkiran Eda    libaudit-dev \
866af49ed51SAndrew Geissler    libc6-dbg \
867af49ed51SAndrew Geissler    libc6-dev \
868c7bc4d1dSPatrick Williams    libcjson-dev \
869af49ed51SAndrew Geissler    libconfig++-dev \
870af49ed51SAndrew Geissler    libcryptsetup-dev \
871a7a30551SAnirban Banerjee    libcurl4-openssl-dev \
872af49ed51SAndrew Geissler    libdbus-1-dev \
873af49ed51SAndrew Geissler    libevdev-dev \
874af49ed51SAndrew Geissler    libi2c-dev \
875af49ed51SAndrew Geissler    libjpeg-dev \
876af49ed51SAndrew Geissler    libjson-perl \
877af49ed51SAndrew Geissler    libldap2-dev \
878af49ed51SAndrew Geissler    libmimetic-dev \
8793ee62fb5SEwelina Walkusz    libmpfr-dev \
88002871c91SPatrick Williams    libnl-3-dev \
88102871c91SPatrick Williams    libnl-genl-3-dev \
88202871c91SPatrick Williams    libpam0g-dev \
88302871c91SPatrick Williams    libpciaccess-dev \
884af49ed51SAndrew Geissler    libperlio-gzip-perl \
885af49ed51SAndrew Geissler    libpng-dev \
886af49ed51SAndrew Geissler    libprotobuf-dev \
887af49ed51SAndrew Geissler    libsnmp-dev \
888af49ed51SAndrew Geissler    libssl-dev \
889af49ed51SAndrew Geissler    libsystemd-dev \
890af49ed51SAndrew Geissler    libtool \
891af49ed51SAndrew Geissler    liburing-dev \
89202871c91SPatrick Williams    libxml2-utils \
8930eedeedaSPatrick Williams    libxml-simple-perl \
8946968e83eSPatrick Williams    lsb-release \
895af49ed51SAndrew Geissler    ninja-build \
896af49ed51SAndrew Geissler    npm \
897af49ed51SAndrew Geissler    pkg-config \
898af49ed51SAndrew Geissler    protobuf-compiler \
899af49ed51SAndrew Geissler    python3 \
900af49ed51SAndrew Geissler    python3-dev\
901af49ed51SAndrew Geissler    python3-git \
902af49ed51SAndrew Geissler    python3-mako \
903af49ed51SAndrew Geissler    python3-pip \
90425ba1e2fSWilliam A. Kennington III    python3-protobuf \
905af49ed51SAndrew Geissler    python3-setuptools \
906af49ed51SAndrew Geissler    python3-socks \
907af49ed51SAndrew Geissler    python3-yaml \
9089adf68d6SJohn Wedig    rsync \
909af49ed51SAndrew Geissler    shellcheck \
9108dd1bfe6SEwelina Walkusz    socat \
9116968e83eSPatrick Williams    software-properties-common \
912af49ed51SAndrew Geissler    sudo \
913af49ed51SAndrew Geissler    systemd \
914917b1774SPatrick Williams    systemd-dev \
915af49ed51SAndrew Geissler    valgrind \
916b565f825SAndrew Geissler    vim \
917af49ed51SAndrew Geissler    wget \
918af49ed51SAndrew Geissler    xxd
91902871c91SPatrick Williams
920ea1bfb27SPatrick WilliamsRUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15 \
921ea1bfb27SPatrick Williams  --slave /usr/bin/g++ g++ /usr/bin/g++-15 \
922ea1bfb27SPatrick Williams  --slave /usr/bin/gcov gcov /usr/bin/gcov-15 \
923ea1bfb27SPatrick Williams  --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-15 \
924ea1bfb27SPatrick Williams  --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-15
925961f148bSPatrick WilliamsRUN update-alternatives --remove cpp /usr/bin/cpp && \
926ea1bfb27SPatrick Williams    update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-15 15
92702871c91SPatrick Williams
9286968e83eSPatrick Williams# Set up LLVM apt repository.
929412b2814SPatrick WilliamsRUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -- 20
9306968e83eSPatrick Williams
9316968e83eSPatrick Williams# Install extra clang tools
932ed8aecafSPatrick WilliamsRUN apt-get install -y \
933412b2814SPatrick Williams        clang-20 \
934412b2814SPatrick Williams        clang-format-20 \
935d713349eSEd Tanous        clang-tidy-20 \
936d713349eSEd Tanous        lld-20
9376968e83eSPatrick Williams
938412b2814SPatrick WilliamsRUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-20 1000 \
939412b2814SPatrick Williams  --slave /usr/bin/clang++ clang++ /usr/bin/clang++-20 \
940412b2814SPatrick Williams  --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-20 \
941412b2814SPatrick Williams  --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-20 \
942e08ffba8SPatrick Williams  --slave /usr/bin/run-clang-tidy run-clang-tidy.py \
943412b2814SPatrick Williams        /usr/bin/run-clang-tidy-20 \
944d713349eSEd Tanous  --slave /usr/bin/scan-build scan-build /usr/bin/scan-build-20 \
945d713349eSEd Tanous  --slave /usr/bin/lld lld /usr/bin/lld-20
94602871c91SPatrick Williams
94750837436SPatrick Williams"""
94850837436SPatrick Williams
94950837436SPatrick Williamsif is_automated_ci_build:
95050837436SPatrick Williams    dockerfile_base += f"""
951a6ebc6e2SManojkiran Eda# Run an arbitrary command to pollute the docker cache regularly force us
95250837436SPatrick Williams# to re-run `apt-get update` daily.
953ee3c9eebSPatrick WilliamsRUN echo {Docker.timestamp()}
95450837436SPatrick WilliamsRUN apt-get update && apt-get dist-upgrade -yy
95550837436SPatrick Williams
95650837436SPatrick Williams"""
95750837436SPatrick Williams
95841d86218SPatrick Williamsdockerfile_base += """
9595e4d8402SPatrick WilliamsRUN pip3 install --break-system-packages \
960818023dfSPatrick Williams        beautysh \
961818023dfSPatrick Williams        black \
962818023dfSPatrick Williams        codespell \
963818023dfSPatrick Williams        flake8 \
9642d8c551fSEwelina Walkusz        gcovr \
965818023dfSPatrick Williams        gitlint \
966818023dfSPatrick Williams        inflection \
967f7381ad6SArya K Padman        isoduration \
968818023dfSPatrick Williams        isort \
969818023dfSPatrick Williams        jsonschema \
970bcc78d3bSPatrick Williams        meson==1.8.2 \
9719fdba2d2SPatrick Williams        referencing \
972818023dfSPatrick Williams        requests
973b08ddf77SPatrick Williams
974b08ddf77SPatrick WilliamsRUN npm install -g \
975d0757deaSXinnan Xie        eslint@v8.56.0 eslint-plugin-json@v3.1.0 \
9767d41f6d2SPatrick Williams        markdownlint-cli@latest \
977b08ddf77SPatrick Williams        prettier@latest
978fb9948a3SEd Tanous"""
979fb9948a3SEd Tanous
980ee3c9eebSPatrick Williams# Build the base and stage docker images.
981ee3c9eebSPatrick Williamsdocker_base_img_name = Docker.tagname("base", dockerfile_base)
982ee3c9eebSPatrick WilliamsDocker.build("base", docker_base_img_name, dockerfile_base)
983ee3c9eebSPatrick WilliamsPackage.generate_all()
98402871c91SPatrick Williams
985ee3c9eebSPatrick Williams# Create the final Dockerfile.
986a18d9c57SPatrick Williamsdockerfile = f"""
98702871c91SPatrick Williams# Build the final output image
988a18d9c57SPatrick WilliamsFROM {docker_base_img_name}
989ee3c9eebSPatrick Williams{Package.df_all_copycmds()}
99002871c91SPatrick Williams
99102871c91SPatrick Williams# Some of our infrastructure still relies on the presence of this file
99202871c91SPatrick Williams# even though it is no longer needed to rebuild the docker environment
99302871c91SPatrick Williams# NOTE: The file is sorted to ensure the ordering is stable.
994ee3c9eebSPatrick WilliamsRUN echo '{Package.depcache()}' > /tmp/depcache
99502871c91SPatrick Williams
99667cc0616SPatrick Williams# Ensure the group, user, and home directory are created (or rename them if
99767cc0616SPatrick Williams# they already exist).
99867cc0616SPatrick WilliamsRUN if grep -q ":{gid}:" /etc/group ; then \
99967cc0616SPatrick Williams        groupmod -n {username} $(awk -F : '{{ if ($3 == {gid}) {{ print $1 }} }}' /etc/group) ; \
100067cc0616SPatrick Williams    else \
100167cc0616SPatrick Williams        groupadd -f -g {gid} {username} ; \
100267cc0616SPatrick Williams    fi
100302871c91SPatrick WilliamsRUN mkdir -p "{os.path.dirname(homedir)}"
100467cc0616SPatrick WilliamsRUN if grep -q ":{uid}:" /etc/passwd ; then \
100573b3ee91SPatrick Williams        usermod -l {username} -d {homedir} -m $(awk -F : '{{ if ($3 == {uid}) {{ print $1 }} }}' /etc/passwd) ; \
100667cc0616SPatrick Williams    else \
100767cc0616SPatrick Williams        useradd -d {homedir} -m -u {uid} -g {gid} {username} ; \
100867cc0616SPatrick Williams    fi
100902871c91SPatrick WilliamsRUN sed -i '1iDefaults umask=000' /etc/sudoers
101002871c91SPatrick WilliamsRUN echo "{username} ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
101102871c91SPatrick Williams
1012305a9a5dSAndrew Geissler# Ensure user has ability to write to /usr/local for different tool
1013305a9a5dSAndrew Geissler# and data installs
10147bb00b13SAndrew GeisslerRUN chown -R {username}:{username} /usr/local/share
1015305a9a5dSAndrew Geissler
1016ab4fee83SJonathan Doman# Update library cache
1017ab4fee83SJonathan DomanRUN ldconfig
1018ab4fee83SJonathan Doman
101902871c91SPatrick Williams{proxy_cmd}
102002871c91SPatrick Williams
102102871c91SPatrick WilliamsRUN /bin/bash
102202871c91SPatrick Williams"""
102302871c91SPatrick Williams
1024a18d9c57SPatrick Williams# Do the final docker build
1025ee3c9eebSPatrick Williamsdocker_final_img_name = Docker.tagname(None, dockerfile)
1026ee3c9eebSPatrick WilliamsDocker.build("final", docker_final_img_name, dockerfile)
1027ee3c9eebSPatrick Williams
102800536fbeSPatrick Williams# Print the tag of the final image.
102900536fbeSPatrick Williamsprint(docker_final_img_name)
1030