From: Guillaume Tucker <gtucker@gtucker.io>
To: "Nathan Chancellor" <nathan@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"David Gow" <davidgow@google.com>,
"Onur Özkan" <work@onurozkan.dev>
Cc: Guillaume Tucker <gtucker@gtucker.io>,
Arnd Bergmann <arnd@arndb.de>,
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 v2 1/2] scripts: add tool to run containerized builds
Date: Thu, 18 Dec 2025 13:49:52 +0100 [thread overview]
Message-ID: <35b951506304b141047812f516fa946a4f1549a1.1766061692.git.gtucker@gtucker.io> (raw)
In-Reply-To: <cover.1766061692.git.gtucker@gtucker.io>
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 for this initial version.
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Miguel Ojeda <ojeda@kernel.org>
Cc: David Gow <davidgow@google.com>
Cc: "Onur Özkan" <work@onurozkan.dev>
Link: https://lore.kernel.org/all/affb7aff-dc9b-4263-bbd4-a7965c19ac4e@gtucker.io/
Signed-off-by: Guillaume Tucker <gtucker@gtucker.io>
---
scripts/container | 194 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 194 insertions(+)
create mode 100755 scripts/container
diff --git a/scripts/container b/scripts/container
new file mode 100755
index 000000000000..2d0143c7d43e
--- /dev/null
+++ b/scripts/container
@@ -0,0 +1,194 @@
+#!/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 shutil
+import subprocess
+import sys
+import uuid
+
+
+class ContainerRuntime(abc.ABC):
+ """Base class for a container runtime implementation"""
+
+ name = None # Property defined in each implementation class
+
+ def __init__(self, args, logger):
+ self._uid = args.uid or os.getuid()
+ self._gid = args.gid or args.uid or os.getgid()
+ self._env_file = args.env_file
+ self._logger = 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 command in running container"""
+
+ def run(self, image, cmd):
+ """Run a command in a runtime container"""
+ container_name = 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 DockerRuntime(ContainerRuntime):
+ """Run a command in a Docker container"""
+
+ name = 'docker'
+
+ def _do_run(self, image, cmd, container_name):
+ cmdline = [
+ 'docker', 'run',
+ '--name', container_name,
+ '--rm',
+ '--tty',
+ '--volume', f'{os.getcwd()}:/src',
+ '--workdir', '/src',
+ '--user', f'{self._uid}:{self._gid}'
+ ]
+ if self._env_file:
+ cmdline += ['--env-file', self._env_file]
+ cmdline.append(image)
+ cmdline += cmd
+ return subprocess.call(cmdline)
+
+ def _do_abort(self, container_name):
+ subprocess.call(['docker', 'kill', container_name])
+
+
+class PodmanRuntime(ContainerRuntime):
+ """Run a command in a Podman container"""
+
+ name = 'podman'
+
+ def _do_run(self, image, cmd, container_name):
+ cmdline = [
+ 'podman', 'run',
+ '--name', container_name,
+ '--rm',
+ '--tty',
+ '--interactive',
+ '--volume', f'{os.getcwd()}:/src',
+ '--workdir', '/src',
+ '--userns', f'keep-id:uid={self._uid},gid={self._gid}',
+ ]
+ if self._env_file:
+ cmdline += ['--env-file', self._env_file]
+ cmdline.append(image)
+ cmdline += cmd
+ return subprocess.call(cmdline)
+
+ def _do_abort(self, container_name):
+ pass # Signals are handled by Podman in interactive mode
+
+
+class Runtimes:
+ """List of all supported runtimes"""
+
+ runtimes = [DockerRuntime, PodmanRuntime]
+
+ @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 == 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 = logging.getLogger('container')
+ handler = logging.StreamHandler()
+ handler.setFormatter(logging.Formatter(
+ fmt='[container {levelname}] {message}', style='{'
+ ))
+ 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 = _get_logger(args.verbose)
+ try:
+ cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find()
+ 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__ == '__main__':
+ parser = argparse.ArgumentParser(
+ 'container',
+ description="Containerized builds. See the dev-tools/container "
+ "kernel documentation section for more details."
+ )
+ parser.add_argument(
+ '-e', '--env-file',
+ help="Path to an environment file to load in the container."
+ )
+ parser.add_argument(
+ '-g', '--gid',
+ help="Group ID to use inside the container."
+ )
+ parser.add_argument(
+ '-i', '--image', required=True,
+ help="Container image name."
+ )
+ parser.add_argument(
+ '-r', '--runtime', choices=Runtimes.get_names(),
+ help="Container runtime name. If not specified, the first one found "
+ "on the system will be used i.e. Docker if present, otherwise Podman."
+ )
+ parser.add_argument(
+ '-u', '--uid',
+ help="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='store_true',
+ help="Enable verbose output."
+ )
+ parser.add_argument(
+ 'cmd', nargs='+',
+ help="Command to run in the container"
+ )
+ sys.exit(main(parser.parse_args(sys.argv[1:])))
--
2.47.3
next prev parent reply other threads:[~2025-12-18 12:50 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-12-18 12:49 [PATCH v2 0/2] scripts: introduce " Guillaume Tucker
2025-12-18 12:49 ` Guillaume Tucker [this message]
2025-12-19 21:27 ` [PATCH v2 1/2] scripts: add tool to run " Nathan Chancellor
2025-12-21 20:09 ` Guillaume Tucker
2025-12-30 20:23 ` Nathan Chancellor
2025-12-21 20:19 ` Guillaume Tucker
2025-12-22 3:30 ` Miguel Ojeda
2025-12-22 9:11 ` Guillaume Tucker
2025-12-22 16:12 ` Konstantin Ryabitsev
2025-12-18 12:49 ` [PATCH v2 2/2] Documentation: dev-tools: add container.rst page Guillaume Tucker
2025-12-21 20:13 ` Guillaume Tucker
2025-12-30 20:16 ` Nathan Chancellor
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=35b951506304b141047812f516fa946a4f1549a1.1766061692.git.gtucker@gtucker.io \
--to=gtucker@gtucker.io \
--cc=arnd@arndb.de \
--cc=automated-testing@lists.yoctoproject.org \
--cc=davidgow@google.com \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=nathan@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=work@onurozkan.dev \
--cc=workflows@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox