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