From nobody Mon Feb 9 20:32:02 2026 Received: from relay8-d.mail.gandi.net (relay8-d.mail.gandi.net [217.70.183.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 695D734402D; Thu, 22 Jan 2026 14:07:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.183.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769090842; cv=none; b=iY0p2+X3Unws3p07TI6cjoPunm/mXaJWkIwIEg0Iae8N2F9bACCu5abseO1P47WeE1Jf9bjWTnjn/RIC2exIVQ4zX5fmlGSJNswvXNPJT90kk3GB7aa7BDqmCdpFUuqEtL0giKbqMrlm2odkRZClrDQbXbfK8+UCp4d20IyELyY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769090842; c=relaxed/simple; bh=7vHTCrEOiEJkfsBFKnpnS6aRcKX8QOUns3U4parp2ro=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=s7osuggkPIIVtxUXBrIG2O6UdpbZzP0sM/RHEQnRkLwJyuIiG/an2nZXLr9Gp4A2BXrKc0vzn/0/l9U74syzb39NNkbpuOd8dURZ0LwehxdM4h1tW0HlRc+XxrDLJ+hLqvKJ6VkcpQtsek3xND9Ua2mtt4ieGCvNFkQ5pcaMl68= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gtucker.io; spf=pass smtp.mailfrom=gtucker.io; dkim=pass (2048-bit key) header.d=gtucker.io header.i=@gtucker.io header.b=OEHVZmZ3; arc=none smtp.client-ip=217.70.183.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gtucker.io Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gtucker.io Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gtucker.io header.i=@gtucker.io header.b="OEHVZmZ3" Received: by mail.gandi.net (Postfix) with ESMTPSA id A199643B48; Thu, 22 Jan 2026 14:07:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gtucker.io; s=gm1; t=1769090832; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ELUAB8rPpZBfx/pf2+f40Rmk9ME9ideSlq/XiQKdxsg=; b=OEHVZmZ3Wa2VEfOmAQ4IbraGv3rGSI1L9cychL3LrYkOjEsoPFiofWpCGIDrFeNdL7/gok PS8uBdt1S6IIlGxBcZnMD3pIyEET35nlHRXoExiHaHAdQRsB9U6Zj6Lw4OLhNmLDH2wCeo 3SQy9SOUft8Ze5ZZ001EwciZAFA6jUPwC2/HlR4wLI0tOt6QqutHLAScm5va4DVIQN61aL WLnPN/2ARmoECox9ErlqvCYLK4KHtwuVffQN/JlZ7U0nMQkHbyuE0LAgsK0VoFokMWCAgK zscWWP0o7/+h+8Jp36yN/vGBzaOWwXxf6PaVGbMCG5Edt7zxvU5sPeIv1gTLPg== From: Guillaume Tucker To: Nathan Chancellor , Nicolas Schier , Miguel Ojeda , David Gow , =?UTF-8?q?Onur=20=C3=96zkan?= Cc: Guillaume Tucker , Arnd Bergmann , linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kbuild@vger.kernel.org, automated-testing@lists.yoctoproject.org, workflows@vger.kernel.org, llvm@lists.linux.dev Subject: [PATCH v4 1/2] scripts: add tool to run containerized builds Date: Thu, 22 Jan 2026 15:06:59 +0100 Message-ID: <9b8da20157e409e8fa3134d2101678779e157256.1769090419.git.gtucker@gtucker.io> X-Mailer: git-send-email 2.47.3 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-GND-Sasl: gtucker@gtucker.io X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddugeeifeeiucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuifetpfffkfdpucggtfgfnhhsuhgsshgtrhhisggvnecuuegrihhlohhuthemuceftddunecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefhvfevufffkffojghfgggtgfesthekredtredtjeenucfhrhhomhepifhuihhllhgruhhmvgcuvfhutghkvghruceoghhtuhgtkhgvrhesghhtuhgtkhgvrhdrihhoqeenucggtffrrghtthgvrhhnpeeiudettdfhkeehveehfeegueelgeelteeuteettddtgeeiudeiudejfedvleegteenucffohhmrghinhepkhgvrhhnvghlrdhorhhgnecukfhppedvtddtudemkeeiudemgegrgedtmeekiedvtdemvdeigegsmedurgefheemuggsheegmedufeegvgenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepihhnvghtpedvtddtudemkeeiudemgegrgedtmeekiedvtdemvdeigegsmedurgefheemuggsheegmedufeegvgdphhgvlhhopehrihhnghhordhlrghnpdhmrghilhhfrhhomhepghhtuhgtkhgvrhesghhtuhgtkhgvrhdrihhopdhqihgupeetudelleeigeefueegkedpmhhouggvpehsmhhtphhouhhtpdhnsggprhgtphhtthhopedufedprhgtphhtthhopehnrghthhgrnheskhgvrhhnvghlrdhorhhgpdhrtghpthhtohepnhhstgeskhgvrhhnvghlrdhorhhgpdhrtghpthhtohepohhjvggurgeskhgvr hhnvghlrdhorhhgpdhrtghpthhtohepuggrvhhiughgohifsehgohhoghhlvgdrtghomhdprhgtphhtthhopeifohhrkhesohhnuhhrohiikhgrnhdruggvvhdprhgtphhtthhopehgthhutghkvghrsehgthhutghkvghrrdhioh X-GND-State: clean X-GND-Score: -100 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. Cc: Nathan Chancellor Cc: Nicolas Schier Cc: Miguel Ojeda Cc: David Gow Cc: "Onur =C3=96zkan" Link: https://lore.kernel.org/all/affb7aff-dc9b-4263-bbd4-a7965c19ac4e@gtuc= ker.io/ Signed-off-by: Guillaume Tucker --- MAINTAINERS | 6 ++ scripts/container | 199 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100755 scripts/container diff --git a/MAINTAINERS b/MAINTAINERS index da9dbc1a4019..affd55ff05e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6384,6 +6384,11 @@ S: Supported F: drivers/video/console/ F: include/linux/console* =20 +CONTAINER BUILD SCRIPT +M: Guillaume Tucker +S: Maintained +F: scripts/container + CONTEXT TRACKING M: Frederic Weisbecker M: "Paul E. McKenney" @@ -13676,6 +13681,7 @@ F: scripts/Makefile* F: scripts/bash-completion/ F: scripts/basic/ F: scripts/clang-tools/ +F: scripts/container F: scripts/dummy-tools/ F: scripts/include/ F: scripts/mk* diff --git a/scripts/container b/scripts/container new file mode 100755 index 000000000000..09663eccb8d3 --- /dev/null +++ b/scripts/container @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Guillaume Tucker + +"""Containerized builds""" + +import abc +import argparse +import logging +import os +import pathlib +import shutil +import subprocess +import sys +import uuid + + +class ContainerRuntime(abc.ABC): + """Base class for a container runtime implementation""" + + name =3D None # Property defined in each implementation class + + def __init__(self, args, logger): + self._uid =3D args.uid or os.getuid() + self._gid =3D args.gid or args.uid or os.getgid() + self._env_file =3D args.env_file + self._shell =3D args.shell + self._logger =3D logger + + @classmethod + def is_present(cls): + """Determine whether the runtime is present on the system""" + return shutil.which(cls.name) is not None + + @abc.abstractmethod + def _do_run(self, image, cmd, container_name): + """Runtime-specific handler to run a command in a container""" + + @abc.abstractmethod + def _do_abort(self, container_name): + """Runtime-specific handler to abort a running container""" + + def run(self, image, cmd): + """Run a command in a runtime container""" + container_name =3D str(uuid.uuid4()) + self._logger.debug("container: %s", container_name) + try: + return self._do_run(image, cmd, container_name) + except KeyboardInterrupt: + self._logger.error("user aborted") + self._do_abort(container_name) + return 1 + + +class CommonRuntime(ContainerRuntime): + """Common logic for Docker and Podman""" + + def _do_run(self, image, cmd, container_name): + cmdline =3D [self.name, 'run'] + cmdline +=3D self._get_opts(container_name) + cmdline.append(image) + cmdline +=3D cmd + self._logger.debug('command: %s', ' '.join(cmdline)) + return subprocess.call(cmdline) + + def _get_opts(self, container_name): + opts =3D [ + '--name', container_name, + '--rm', + '--volume', f'{pathlib.Path.cwd()}:/src', + '--workdir', '/src', + ] + if self._env_file: + opts +=3D ['--env-file', self._env_file] + if self._shell: + opts +=3D ['--interactive', '--tty'] + return opts + + def _do_abort(self, container_name): + subprocess.call([self.name, 'kill', container_name]) + + +class DockerRuntime(CommonRuntime): + """Run a command in a Docker container""" + + name =3D 'docker' + + def _get_opts(self, container_name): + return super()._get_opts(container_name) + [ + '--user', f'{self._uid}:{self._gid}' + ] + + +class PodmanRuntime(CommonRuntime): + """Run a command in a Podman container""" + + name =3D 'podman' + + def _get_opts(self, container_name): + return super()._get_opts(container_name) + [ + '--userns', f'keep-id:uid=3D{self._uid},gid=3D{self._gid}', + ] + + +class Runtimes: + """List of all supported runtimes""" + + runtimes =3D [PodmanRuntime, DockerRuntime] + + @classmethod + def get_names(cls): + """Get a list of all the runtime names""" + return list(runtime.name for runtime in cls.runtimes) + + @classmethod + def get(cls, name): + """Get a single runtime class matching the given name""" + for runtime in cls.runtimes: + if runtime.name =3D=3D name: + if not runtime.is_present(): + raise ValueError(f"runtime not found: {name}") + return runtime + raise ValueError(f"unknown runtime: {runtime}") + + @classmethod + def find(cls): + """Find the first runtime present on the system""" + for runtime in cls.runtimes: + if runtime.is_present(): + return runtime + raise ValueError("no runtime found") + + +def _get_logger(verbose): + """Set up a logger with the appropriate level""" + logger =3D logging.getLogger('container') + handler =3D logging.StreamHandler() + handler.setFormatter(logging.Formatter( + fmt=3D'[container {levelname}] {message}', style=3D'{' + )) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG if verbose is True else logging.INFO) + return logger + + +def main(args): + """Main entry point for the container tool""" + logger =3D _get_logger(args.verbose) + try: + cls =3D Runtimes.get(args.runtime) if args.runtime else Runtimes.f= ind() + except ValueError as ex: + logger.error(ex) + return 1 + logger.debug("runtime: %s", cls.name) + logger.debug("image: %s", args.image) + return cls(args, logger).run(args.image, args.cmd) + + +if __name__ =3D=3D '__main__': + parser =3D argparse.ArgumentParser( + 'container', + description=3D"See the documentation for more details: " + "https://docs.kernel.org/dev-tools/container.html" + ) + parser.add_argument( + '-e', '--env-file', + help=3D"Path to an environment file to load in the container." + ) + parser.add_argument( + '-g', '--gid', + help=3D"Group ID to use inside the container." + ) + parser.add_argument( + '-i', '--image', required=3DTrue, + help=3D"Container image name." + ) + parser.add_argument( + '-r', '--runtime', choices=3DRuntimes.get_names(), + help=3D"Container runtime name. If not specified, the first one f= ound " + "on the system will be used i.e. Podman if present, otherwise Dock= er." + ) + parser.add_argument( + '-s', '--shell', action=3D'store_true', + help=3D"Run the container in an interactive shell." + ) + parser.add_argument( + '-u', '--uid', + help=3D"User ID to use inside the container. If the -g option is = not " + "specified, the user ID will also be set as the group ID." + ) + parser.add_argument( + '-v', '--verbose', action=3D'store_true', + help=3D"Enable verbose output." + ) + parser.add_argument( + 'cmd', nargs=3D'+', + help=3D"Command to run in the container" + ) + sys.exit(main(parser.parse_args(sys.argv[1:]))) --=20 2.47.3