Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

scripts: add tool to run containerized builds

Add a 'scripts/container' tool written in Python to run any command in
the source tree from within a container. This can typically be used
to call 'make' with a compiler toolchain image to run reproducible
builds but any arbitrary command can be run too. Only Docker and
Podman are supported in this initial version.

Add a new entry to MAINTAINERS accordingly.

Link: https://lore.kernel.org/all/affb7aff-dc9b-4263-bbd4-a7965c19ac4e@gtucker.io/
Signed-off-by: Guillaume Tucker <gtucker@gtucker.io>
Tested-by: Nicolas Schier <nsc@kernel.org>
Acked-by: Nicolas Schier <nsc@kernel.org>
Link: https://patch.msgid.link/9b8da20157e409e8fa3134d2101678779e157256.1769090419.git.gtucker@gtucker.io
Signed-off-by: Nathan Chancellor <nathan@kernel.org>

authored by

Guillaume Tucker and committed by
Nathan Chancellor
8f989b3b 502678b8

+205
+6
MAINTAINERS
··· 6380 6380 F: drivers/video/console/ 6381 6381 F: include/linux/console* 6382 6382 6383 + CONTAINER BUILD SCRIPT 6384 + M: Guillaume Tucker <gtucker@gtucker.io> 6385 + S: Maintained 6386 + F: scripts/container 6387 + 6383 6388 CONTEXT TRACKING 6384 6389 M: Frederic Weisbecker <frederic@kernel.org> 6385 6390 M: "Paul E. McKenney" <paulmck@kernel.org> ··· 13673 13668 F: scripts/bash-completion/ 13674 13669 F: scripts/basic/ 13675 13670 F: scripts/clang-tools/ 13671 + F: scripts/container 13676 13672 F: scripts/dummy-tools/ 13677 13673 F: scripts/include/ 13678 13674 F: scripts/mk*
+199
scripts/container
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0-only 3 + # Copyright (C) 2025 Guillaume Tucker 4 + 5 + """Containerized builds""" 6 + 7 + import abc 8 + import argparse 9 + import logging 10 + import os 11 + import pathlib 12 + import shutil 13 + import subprocess 14 + import sys 15 + import uuid 16 + 17 + 18 + class ContainerRuntime(abc.ABC): 19 + """Base class for a container runtime implementation""" 20 + 21 + name = None # Property defined in each implementation class 22 + 23 + def __init__(self, args, logger): 24 + self._uid = args.uid or os.getuid() 25 + self._gid = args.gid or args.uid or os.getgid() 26 + self._env_file = args.env_file 27 + self._shell = args.shell 28 + self._logger = logger 29 + 30 + @classmethod 31 + def is_present(cls): 32 + """Determine whether the runtime is present on the system""" 33 + return shutil.which(cls.name) is not None 34 + 35 + @abc.abstractmethod 36 + def _do_run(self, image, cmd, container_name): 37 + """Runtime-specific handler to run a command in a container""" 38 + 39 + @abc.abstractmethod 40 + def _do_abort(self, container_name): 41 + """Runtime-specific handler to abort a running container""" 42 + 43 + def run(self, image, cmd): 44 + """Run a command in a runtime container""" 45 + container_name = str(uuid.uuid4()) 46 + self._logger.debug("container: %s", container_name) 47 + try: 48 + return self._do_run(image, cmd, container_name) 49 + except KeyboardInterrupt: 50 + self._logger.error("user aborted") 51 + self._do_abort(container_name) 52 + return 1 53 + 54 + 55 + class CommonRuntime(ContainerRuntime): 56 + """Common logic for Docker and Podman""" 57 + 58 + def _do_run(self, image, cmd, container_name): 59 + cmdline = [self.name, 'run'] 60 + cmdline += self._get_opts(container_name) 61 + cmdline.append(image) 62 + cmdline += cmd 63 + self._logger.debug('command: %s', ' '.join(cmdline)) 64 + return subprocess.call(cmdline) 65 + 66 + def _get_opts(self, container_name): 67 + opts = [ 68 + '--name', container_name, 69 + '--rm', 70 + '--volume', f'{pathlib.Path.cwd()}:/src', 71 + '--workdir', '/src', 72 + ] 73 + if self._env_file: 74 + opts += ['--env-file', self._env_file] 75 + if self._shell: 76 + opts += ['--interactive', '--tty'] 77 + return opts 78 + 79 + def _do_abort(self, container_name): 80 + subprocess.call([self.name, 'kill', container_name]) 81 + 82 + 83 + class DockerRuntime(CommonRuntime): 84 + """Run a command in a Docker container""" 85 + 86 + name = 'docker' 87 + 88 + def _get_opts(self, container_name): 89 + return super()._get_opts(container_name) + [ 90 + '--user', f'{self._uid}:{self._gid}' 91 + ] 92 + 93 + 94 + class PodmanRuntime(CommonRuntime): 95 + """Run a command in a Podman container""" 96 + 97 + name = 'podman' 98 + 99 + def _get_opts(self, container_name): 100 + return super()._get_opts(container_name) + [ 101 + '--userns', f'keep-id:uid={self._uid},gid={self._gid}', 102 + ] 103 + 104 + 105 + class Runtimes: 106 + """List of all supported runtimes""" 107 + 108 + runtimes = [PodmanRuntime, DockerRuntime] 109 + 110 + @classmethod 111 + def get_names(cls): 112 + """Get a list of all the runtime names""" 113 + return list(runtime.name for runtime in cls.runtimes) 114 + 115 + @classmethod 116 + def get(cls, name): 117 + """Get a single runtime class matching the given name""" 118 + for runtime in cls.runtimes: 119 + if runtime.name == name: 120 + if not runtime.is_present(): 121 + raise ValueError(f"runtime not found: {name}") 122 + return runtime 123 + raise ValueError(f"unknown runtime: {name}") 124 + 125 + @classmethod 126 + def find(cls): 127 + """Find the first runtime present on the system""" 128 + for runtime in cls.runtimes: 129 + if runtime.is_present(): 130 + return runtime 131 + raise ValueError("no runtime found") 132 + 133 + 134 + def _get_logger(verbose): 135 + """Set up a logger with the appropriate level""" 136 + logger = logging.getLogger('container') 137 + handler = logging.StreamHandler() 138 + handler.setFormatter(logging.Formatter( 139 + fmt='[container {levelname}] {message}', style='{' 140 + )) 141 + logger.addHandler(handler) 142 + logger.setLevel(logging.DEBUG if verbose is True else logging.INFO) 143 + return logger 144 + 145 + 146 + def main(args): 147 + """Main entry point for the container tool""" 148 + logger = _get_logger(args.verbose) 149 + try: 150 + cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find() 151 + except ValueError as ex: 152 + logger.error(ex) 153 + return 1 154 + logger.debug("runtime: %s", cls.name) 155 + logger.debug("image: %s", args.image) 156 + return cls(args, logger).run(args.image, args.cmd) 157 + 158 + 159 + if __name__ == '__main__': 160 + parser = argparse.ArgumentParser( 161 + 'container', 162 + description="See the documentation for more details: " 163 + "https://docs.kernel.org/dev-tools/container.html" 164 + ) 165 + parser.add_argument( 166 + '-e', '--env-file', 167 + help="Path to an environment file to load in the container." 168 + ) 169 + parser.add_argument( 170 + '-g', '--gid', 171 + help="Group ID to use inside the container." 172 + ) 173 + parser.add_argument( 174 + '-i', '--image', required=True, 175 + help="Container image name." 176 + ) 177 + parser.add_argument( 178 + '-r', '--runtime', choices=Runtimes.get_names(), 179 + help="Container runtime name. If not specified, the first one found " 180 + "on the system will be used i.e. Podman if present, otherwise Docker." 181 + ) 182 + parser.add_argument( 183 + '-s', '--shell', action='store_true', 184 + help="Run the container in an interactive shell." 185 + ) 186 + parser.add_argument( 187 + '-u', '--uid', 188 + help="User ID to use inside the container. If the -g option is not " 189 + "specified, the user ID will also be set as the group ID." 190 + ) 191 + parser.add_argument( 192 + '-v', '--verbose', action='store_true', 193 + help="Enable verbose output." 194 + ) 195 + parser.add_argument( 196 + 'cmd', nargs='+', 197 + help="Command to run in the container" 198 + ) 199 + sys.exit(main(parser.parse_args(sys.argv[1:])))