From 9409f4be3e84d9206c752dab3da57b98d1a94517 Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 18:01:05 +0800 Subject: [PATCH 1/6] feat: linux ktree build recipe prepare --- recipes/linux-kbuild-tree/conanfile.py | 249 +++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 recipes/linux-kbuild-tree/conanfile.py diff --git a/recipes/linux-kbuild-tree/conanfile.py b/recipes/linux-kbuild-tree/conanfile.py new file mode 100644 index 0000000..2c6962d --- /dev/null +++ b/recipes/linux-kbuild-tree/conanfile.py @@ -0,0 +1,249 @@ +import os +import re +import shlex +import subprocess + +from conan import ConanFile +from conan.errors import ConanInvalidConfiguration +from conan.tools.build import cross_building +from conan.tools.files import copy, save + + +class LinuxKbuildTreeConan(ConanFile): + name = "linux-kbuild-tree" + package_type = "application" + + settings = "os", "arch", "compiler", "build_type" + + options = { + "url": ["ANY"], + "defconfig": ["ANY"], + "make_modules": [True, False], + } + + default_options = { + "url": "https://github.com/torvalds/linux.git", + "defconfig": "", + "make_modules": False, + } + + exports_sources = "configs/*" + + def package_id(self): + arch = self.conf.get("user.kernel:arch", default=None) + if arch: + self.info.conf.define("user.kernel:arch", str(arch)) + + cross_compile = self.conf.get("user.kernel:cross_compile", default=None) + if cross_compile is None: + cross_compile = self._cross_compile_from_compiler_executables() + if cross_compile is not None: + self.info.conf.define("user.kernel:cross_compile", str(cross_compile)) + + def validate(self): + if self.settings.os != "Linux": + raise ConanInvalidConfiguration("linux-kbuild-tree requires a Linux target") + + if not self.version: + raise ConanInvalidConfiguration( + "linux-kbuild-tree requires a package version, e.g. " + "conan create recipes/linux-kbuild-tree --version=6.5.9" + ) + + self._kernel_arch + + if cross_building(self) and self._cross_compile is None: + raise ConanInvalidConfiguration( + "Cross-building a kernel tree requires user.kernel:cross_compile " + "or tools.build:compiler_executables with a GCC-like C compiler" + ) + + def source(self): + self.run( + "git clone --depth=1 " + f"--branch {shlex.quote(self._source_ref)} " + f"{shlex.quote(str(self.options.url))} linux" + ) + + def build(self): + linux = os.path.join(self.source_folder, "linux") + arch = self._kernel_arch + cross_compile = self._cross_compile or "" + jobs = self.conf.get("tools.build:jobs", default=None) + make_jobs = f" -j{jobs}" if jobs else "" + + make_base = ( + f"make -C {shlex.quote(linux)}{make_jobs} " + f"ARCH={shlex.quote(str(arch))} " + f"CROSS_COMPILE={shlex.quote(str(cross_compile))}" + ) + + self._install_config(linux, make_base) + + self.run(f"{make_base} olddefconfig prepare modules_prepare") + + if self.options.make_modules: + # Required when CONFIG_MODVERSIONS=y and the target kernel exports + # symbols that are not available from a vendor-provided Module.symvers. + self.run(f"{make_base} modules") + + kernelrelease = self._capture(f"{make_base} kernelrelease") + compiler = self._capture(f"{self._cross_gcc} --version").splitlines()[0] + git_commit = self._capture(f"git -C {shlex.quote(linux)} rev-parse HEAD") + + save( + self, + os.path.join(self.build_folder, "kbuild-tree-info.txt"), + "\n".join( + [ + f"url={self.options.url}", + f"ref={self.version}", + f"git_commit={git_commit}", + f"kernelrelease={kernelrelease}", + f"arch={arch}", + f"cross_compile={cross_compile}", + f"compiler={compiler}", + f"make_modules={self.options.make_modules}", + "", + ] + ), + ) + + def package(self): + linux = os.path.join(self.source_folder, "linux") + + # External module builds need source files, generated headers, scripts, + # .config, and often Module.symvers. Packaging the whole prepared tree is + # intentionally conservative and can be optimized after the flow is stable. + copy(self, "*", src=linux, dst=self.package_folder, excludes=(".git", ".git/*")) + copy(self, ".config", src=linux, dst=self.package_folder, keep_path=False) + copy( + self, + "kbuild-tree-info.txt", + src=self.build_folder, + dst=self.package_folder, + keep_path=False, + ) + + def package_info(self): + self.conf_info.define("user.kernel:kernel_dir", self.package_folder) + self.conf_info.define("user.kernel:arch", self._kernel_arch) + self.conf_info.define("user.kernel:cross_compile", self._cross_compile or "") + + @property + def _kernel_arch(self): + arch = self.conf.get("user.kernel:arch", default=None) + if arch: + return str(arch) + + arch = str(self.settings.arch) + arch_map = { + "x86": "x86", + "x86_64": "x86", + "armv5el": "arm", + "armv5hf": "arm", + "armv6": "arm", + "armv7": "arm", + "armv7hf": "arm", + "armv8": "arm64", + "armv8_32": "arm", + "armv8.3": "arm64", + "armv8.5": "arm64", + "armv8.6": "arm64", + "armv8.7": "arm64", + "armv8.8": "arm64", + "armv8.9": "arm64", + "ppc32": "powerpc", + "ppc64": "powerpc", + "ppc64le": "powerpc", + "riscv32": "riscv", + "riscv64": "riscv", + "s390": "s390", + "s390x": "s390", + "sparc": "sparc", + "sparcv9": "sparc", + "mips": "mips", + "mips64": "mips", + } + if arch not in arch_map: + raise ConanInvalidConfiguration( + f"Cannot derive Linux ARCH from settings.arch={arch!r}; " + "set user.kernel:arch explicitly" + ) + return arch_map[arch] + + @property + def _cross_compile(self): + cross_compile = self.conf.get("user.kernel:cross_compile", default=None) + if cross_compile is not None: + return str(cross_compile) + + if not cross_building(self): + return "" + + c_compiler = self._compiler_executable("c") + if not c_compiler: + return None + + return self._cross_compile_from_executable(c_compiler) + + def _cross_compile_from_compiler_executables(self): + c_compiler = self._compiler_executable("c") + if not c_compiler: + return None + return self._cross_compile_from_executable(c_compiler) + + def _cross_compile_from_executable(self, c_compiler): + executable = os.path.basename(c_compiler) + match = re.match(r"^(.+?)(?:gcc|cc)(?:-\d+(?:\.\d+)*)?$", executable) + if not match: + return None + return match.group(1) + + @property + def _cross_gcc(self): + cross_compile = self._cross_compile or "" + return shlex.quote(f"{cross_compile}gcc") + + def _compiler_executable(self, language): + executables = self.conf.get("tools.build:compiler_executables", default={}) + if not executables: + return None + if hasattr(executables, "get"): + return executables.get(language) + return None + + def _install_config(self, linux, make_base): + defconfig = str(self.options.defconfig) + if not defconfig: + if not os.path.exists(os.path.join(linux, ".config")): + raise ConanInvalidConfiguration( + "Missing kernel config: set linux-kbuild-tree/*:defconfig " + "or provide a source tree with .config" + ) + return + + exported_config = os.path.join(self.source_folder, "configs", defconfig) + if os.path.isfile(exported_config): + copy( + self, + defconfig, + src=os.path.dirname(exported_config), + dst=linux, + keep_path=False, + ) + os.replace(os.path.join(linux, defconfig), os.path.join(linux, ".config")) + return + + self.run(f"{make_base} {shlex.quote(defconfig)}") + + def _capture(self, command): + output = subprocess.check_output( + command, + shell=True, + stderr=subprocess.STDOUT, + text=True, + ).strip() + if not output: + raise ConanInvalidConfiguration(f"Command produced no output: {command}") + return output From e231e9b9675aff4cc201d03cdc7479e0d2dc1639 Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 18:58:53 +0800 Subject: [PATCH 2/6] feat: basic linux kbuild tree recipe implement --- recipes/linux-kbuild-tree/.gitignore | 3 +++ recipes/linux-kbuild-tree/conanfile.py | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 recipes/linux-kbuild-tree/.gitignore diff --git a/recipes/linux-kbuild-tree/.gitignore b/recipes/linux-kbuild-tree/.gitignore new file mode 100644 index 0000000..fc6de81 --- /dev/null +++ b/recipes/linux-kbuild-tree/.gitignore @@ -0,0 +1,3 @@ +/build/ +/linux/ +*.txt diff --git a/recipes/linux-kbuild-tree/conanfile.py b/recipes/linux-kbuild-tree/conanfile.py index 2c6962d..d630276 100644 --- a/recipes/linux-kbuild-tree/conanfile.py +++ b/recipes/linux-kbuild-tree/conanfile.py @@ -29,6 +29,10 @@ class LinuxKbuildTreeConan(ConanFile): exports_sources = "configs/*" + def layout(self): + self.folders.build = "build" + self.folders.generators = os.path.join(self.folders.build, "generators") + def package_id(self): arch = self.conf.get("user.kernel:arch", default=None) if arch: @@ -61,8 +65,8 @@ class LinuxKbuildTreeConan(ConanFile): def source(self): self.run( "git clone --depth=1 " - f"--branch {shlex.quote(self._source_ref)} " - f"{shlex.quote(str(self.options.url))} linux" + f"--branch {shlex.quote(self._linux_ref_from_version())} " + "https://github.com/torvalds/linux.git linux" ) def build(self): @@ -73,7 +77,7 @@ class LinuxKbuildTreeConan(ConanFile): make_jobs = f" -j{jobs}" if jobs else "" make_base = ( - f"make -C {shlex.quote(linux)}{make_jobs} " + f"make --no-print-directory -C {shlex.quote(linux)}{make_jobs} " f"ARCH={shlex.quote(str(arch))} " f"CROSS_COMPILE={shlex.quote(str(cross_compile))}" ) @@ -97,7 +101,7 @@ class LinuxKbuildTreeConan(ConanFile): "\n".join( [ f"url={self.options.url}", - f"ref={self.version}", + f"ref={self._linux_ref_from_version()}", f"git_commit={git_commit}", f"kernelrelease={kernelrelease}", f"arch={arch}", @@ -130,6 +134,14 @@ class LinuxKbuildTreeConan(ConanFile): self.conf_info.define("user.kernel:arch", self._kernel_arch) self.conf_info.define("user.kernel:cross_compile", self._cross_compile or "") + def _linux_ref_from_version(self): + # Upstream Linux tags base releases as v6.8, while the kernel release + # and Conan package version are commonly represented as 6.8.0. + version_parts = str(self.version).split(".") + if len(version_parts) == 3 and version_parts[2] == "0": + return f"v{version_parts[0]}.{version_parts[1]}" + return f"v{self.version}" + @property def _kernel_arch(self): arch = self.conf.get("user.kernel:arch", default=None) From 8d6a663379451ade0af5e89167ce7a2f73345f01 Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 19:40:52 +0800 Subject: [PATCH 3/6] fix: remove all `user.kernel:*` config --- recipes/linux-kbuild-tree/conanfile.py | 31 +++++--------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/recipes/linux-kbuild-tree/conanfile.py b/recipes/linux-kbuild-tree/conanfile.py index d630276..902d252 100644 --- a/recipes/linux-kbuild-tree/conanfile.py +++ b/recipes/linux-kbuild-tree/conanfile.py @@ -34,15 +34,9 @@ class LinuxKbuildTreeConan(ConanFile): self.folders.generators = os.path.join(self.folders.build, "generators") def package_id(self): - arch = self.conf.get("user.kernel:arch", default=None) - if arch: - self.info.conf.define("user.kernel:arch", str(arch)) - - cross_compile = self.conf.get("user.kernel:cross_compile", default=None) - if cross_compile is None: - cross_compile = self._cross_compile_from_compiler_executables() - if cross_compile is not None: - self.info.conf.define("user.kernel:cross_compile", str(cross_compile)) + c_compiler = self._compiler_executable("c") + if c_compiler is not None: + self.info.conf.define("tools.build:compiler_executables", {"c": str(c_compiler)}) def validate(self): if self.settings.os != "Linux": @@ -58,8 +52,8 @@ class LinuxKbuildTreeConan(ConanFile): if cross_building(self) and self._cross_compile is None: raise ConanInvalidConfiguration( - "Cross-building a kernel tree requires user.kernel:cross_compile " - "or tools.build:compiler_executables with a GCC-like C compiler" + "Cross-building a kernel tree requires " + "tools.build:compiler_executables with a GCC-like C compiler" ) def source(self): @@ -129,11 +123,6 @@ class LinuxKbuildTreeConan(ConanFile): keep_path=False, ) - def package_info(self): - self.conf_info.define("user.kernel:kernel_dir", self.package_folder) - self.conf_info.define("user.kernel:arch", self._kernel_arch) - self.conf_info.define("user.kernel:cross_compile", self._cross_compile or "") - def _linux_ref_from_version(self): # Upstream Linux tags base releases as v6.8, while the kernel release # and Conan package version are commonly represented as 6.8.0. @@ -144,10 +133,6 @@ class LinuxKbuildTreeConan(ConanFile): @property def _kernel_arch(self): - arch = self.conf.get("user.kernel:arch", default=None) - if arch: - return str(arch) - arch = str(self.settings.arch) arch_map = { "x86": "x86", @@ -180,16 +165,12 @@ class LinuxKbuildTreeConan(ConanFile): if arch not in arch_map: raise ConanInvalidConfiguration( f"Cannot derive Linux ARCH from settings.arch={arch!r}; " - "set user.kernel:arch explicitly" + "add this Conan architecture to the recipe arch map" ) return arch_map[arch] @property def _cross_compile(self): - cross_compile = self.conf.get("user.kernel:cross_compile", default=None) - if cross_compile is not None: - return str(cross_compile) - if not cross_building(self): return "" From 0245c1585588486afc562d531bb7b9fd36a1e0eb Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 21:29:44 +0800 Subject: [PATCH 4/6] feat: linux-kbuild-tree download source from CDN rather than git now fix: linux-kbuild-tree path move in `all` subfolder --- .../linux-kbuild-tree/{ => all}/.gitignore | 0 recipes/linux-kbuild-tree/all/conandata.yml | 21 ++++++++++ .../linux-kbuild-tree/{ => all}/conanfile.py | 42 ++++++++++++++----- 3 files changed, 53 insertions(+), 10 deletions(-) rename recipes/linux-kbuild-tree/{ => all}/.gitignore (100%) create mode 100644 recipes/linux-kbuild-tree/all/conandata.yml rename recipes/linux-kbuild-tree/{ => all}/conanfile.py (86%) diff --git a/recipes/linux-kbuild-tree/.gitignore b/recipes/linux-kbuild-tree/all/.gitignore similarity index 100% rename from recipes/linux-kbuild-tree/.gitignore rename to recipes/linux-kbuild-tree/all/.gitignore diff --git a/recipes/linux-kbuild-tree/all/conandata.yml b/recipes/linux-kbuild-tree/all/conandata.yml new file mode 100644 index 0000000..c09d426 --- /dev/null +++ b/recipes/linux-kbuild-tree/all/conandata.yml @@ -0,0 +1,21 @@ +sources: + "7.0.0": + "6.17.0": + "6.8.0": + "6.5.9": + url: "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.5.9.tar.gz" + sha256: "3c269612220746ba87dc9063bec2afcb2df04b4fdaa9f7e1f67751284966cf37" + "5.19.9": + url: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.9.tar.gz" + sha256: "1f01b0ee846547737793103f23cc46a382e4694e7728e8cb8c48ea51a2bb7a8d" + "5.19.0": + "5.15.0": + "5.15.128": + url: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.128.tar.gz" + sha256: "4ce1331bb3877bc92581ec5faff76d63d19e7b1fa174acdda1f5cba4a8f2abf7" + "5.14.9": + url: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.14.9.tar.gz" + sha256: "b5cd37e6d193c8f6a98e41ce6f9d97260d178e0a3cfc43d7ff684f67c6c25f29" + "5.13.9": + url: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.13.9.tar.gz" + sha256: "cc82e4461d5f759c820b74079164450d1f1ad09def9f3356a58814abd0268787" diff --git a/recipes/linux-kbuild-tree/conanfile.py b/recipes/linux-kbuild-tree/all/conanfile.py similarity index 86% rename from recipes/linux-kbuild-tree/conanfile.py rename to recipes/linux-kbuild-tree/all/conanfile.py index 902d252..7b42aae 100644 --- a/recipes/linux-kbuild-tree/conanfile.py +++ b/recipes/linux-kbuild-tree/all/conanfile.py @@ -6,7 +6,7 @@ import subprocess from conan import ConanFile from conan.errors import ConanInvalidConfiguration from conan.tools.build import cross_building -from conan.tools.files import copy, save +from conan.tools.files import copy, get, save class LinuxKbuildTreeConan(ConanFile): @@ -22,8 +22,8 @@ class LinuxKbuildTreeConan(ConanFile): } default_options = { - "url": "https://github.com/torvalds/linux.git", - "defconfig": "", + "url": "", + "defconfig": "defconfig", "make_modules": False, } @@ -57,10 +57,13 @@ class LinuxKbuildTreeConan(ConanFile): ) def source(self): - self.run( - "git clone --depth=1 " - f"--branch {shlex.quote(self._linux_ref_from_version())} " - "https://github.com/torvalds/linux.git linux" + source_url, sha256 = self._linux_source() + get( + self, + source_url, + sha256=sha256, + destination=os.path.join(self.source_folder, "linux"), + strip_root=True, ) def build(self): @@ -87,16 +90,16 @@ class LinuxKbuildTreeConan(ConanFile): kernelrelease = self._capture(f"{make_base} kernelrelease") compiler = self._capture(f"{self._cross_gcc} --version").splitlines()[0] - git_commit = self._capture(f"git -C {shlex.quote(linux)} rev-parse HEAD") + source_url, sha256 = self._linux_source() save( self, os.path.join(self.build_folder, "kbuild-tree-info.txt"), "\n".join( [ - f"url={self.options.url}", + f"url={source_url}", f"ref={self._linux_ref_from_version()}", - f"git_commit={git_commit}", + f"sha256={sha256 or ''}", f"kernelrelease={kernelrelease}", f"arch={arch}", f"cross_compile={cross_compile}", @@ -131,6 +134,25 @@ class LinuxKbuildTreeConan(ConanFile): return f"v{version_parts[0]}.{version_parts[1]}" return f"v{self.version}" + def _linux_archive_version(self): + version_parts = str(self.version).split(".") + if len(version_parts) == 3 and version_parts[2] == "0": + return f"{version_parts[0]}.{version_parts[1]}" + return str(self.version) + + def _linux_source(self): + source = self.conan_data.get("sources", {}).get(str(self.version)) or {} + source_url = source.get("url") or self._kernel_cdn_url() + return source_url, source.get("sha256") + + def _kernel_cdn_url(self): + archive_version = self._linux_archive_version() + major = archive_version.split(".", 1)[0] + return ( + f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/" + f"linux-{archive_version}.tar.gz" + ) + @property def _kernel_arch(self): arch = str(self.settings.arch) From 18fcb473e8f6089a6cb6e914977387e212508836 Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 21:39:24 +0800 Subject: [PATCH 5/6] ci: install libelf headers for linux kbuild --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 18b5f4a..5116802 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -64,7 +64,7 @@ jobs: - name: Install host build tools uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: build-essential gcc-14 g++-14 cmake ninja-build graphviz crudini + packages: build-essential gcc-14 g++-14 cmake ninja-build graphviz crudini libelf-dev version: 1.0 execute_install_scripts: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ca673f..4b8ccce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Install host build tools uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: build-essential gcc-14 g++-14 cmake ninja-build graphviz crudini + packages: build-essential gcc-14 g++-14 cmake ninja-build graphviz crudini libelf-dev version: 1.0 execute_install_scripts: true From 8e50a5f134d0151809c90aececf08978c5bda3da Mon Sep 17 00:00:00 2001 From: insleker Date: Fri, 22 May 2026 21:46:10 +0800 Subject: [PATCH 6/6] fix: relax old linux objtool warning --- recipes/linux-kbuild-tree/all/conanfile.py | 49 +++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/recipes/linux-kbuild-tree/all/conanfile.py b/recipes/linux-kbuild-tree/all/conanfile.py index 7b42aae..bfde83a 100644 --- a/recipes/linux-kbuild-tree/all/conanfile.py +++ b/recipes/linux-kbuild-tree/all/conanfile.py @@ -76,7 +76,8 @@ class LinuxKbuildTreeConan(ConanFile): make_base = ( f"make --no-print-directory -C {shlex.quote(linux)}{make_jobs} " f"ARCH={shlex.quote(str(arch))} " - f"CROSS_COMPILE={shlex.quote(str(cross_compile))}" + f"CROSS_COMPILE={shlex.quote(str(cross_compile))} " + f"{self._host_warning_flags_args}" ) self._install_config(linux, make_base) @@ -153,6 +154,52 @@ class LinuxKbuildTreeConan(ConanFile): f"linux-{archive_version}.tar.gz" ) + @property + def _host_warning_flags_args(self): + hostcflags = [] + + if self._version_lt("5.16.0") and self._host_compiler_accepts( + "-Wno-error=use-after-free" + ): + # Older objtool builds use -Werror and trip GCC 14's realloc(ptr) + # use-after-free diagnostic in tools/lib/subcmd/subcmd-util.h. + hostcflags.append("-Wno-error=use-after-free") + + if not hostcflags: + return "" + + flags = shlex.quote(" ".join(hostcflags)) + return f"HOSTCFLAGS={flags} EXTRA_CFLAGS={flags}" + + def _host_compiler_accepts(self, flag): + try: + subprocess.run( + [ + "gcc", + flag, + "-x", + "c", + "-c", + "-o", + os.devnull, + "-", + ], + input="int main(void) { return 0; }\n", + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + except (OSError, subprocess.CalledProcessError): + return False + return True + + def _version_lt(self, other): + return self._version_tuple(str(self.version)) < self._version_tuple(other) + + def _version_tuple(self, version): + return tuple(int(part) for part in version.split(".")) + @property def _kernel_arch(self): arch = str(self.settings.arch)