Reproducible builds - #300
Conversation
We set -ffile-prefix-map=<build dir>/= to hide the build path. The trailing slash misses the compilation directory, which is the build dir itself, so DW_AT_comp_dir keeps the full path. Two builds in different build dirs are then not bit by bit identical. Drop the slash and map the build dir to /tuxmake. Set the same in KAFLAGS for the .S files, and keep both out of the reproducer. Signed-off-by: Anders Roxell <anders.roxell@linaro.org>
modules_install creates lib/modules/<ver>/build and source. They point at the local build and source dirs. The build dir is new for every build, so the tarball is never the same twice, even when the modules are identical. The links are broken outside the build machine anyway. Leave them out. Signed-off-by: Anders Roxell <anders.roxell@linaro.org>
We map the build dir, but not the source tree. Kbuild passes the source files with an absolute path, so DW_AT_name keeps it. Two builds of the same tree in different dirs are then not bit by bit identical. Map the source tree too, with a trailing slash, so the file names come out relative to the tree, like an in tree build. It comes first: the last map that matches wins, and --build-dir can put the build dir inside the source tree. Rust needs its own, rustc does not take the gcc spelling. It also needs the scope, the kernel sets --remap-path-scope=macro for out of tree builds and rustc takes that once only. That needs a kernel patch, sent separately. Signed-off-by: Anders Roxell <anders.roxell@linaro.org>
In a git worktree .git is a file that points at a dir outside the tree. We only mount the source tree, so git does not work in the container. setlocalversion finds nothing, and the kernel version loses the git part. A worktree build gets 7.2.0-rc5 where the main tree gets 7.2.0-rc5-00001-gd000866da13d, and that string ends up in the kernel. Mount the git dir read only, setlocalversion does not write. The gitdir is not always absolute. worktree.useRelativePaths and submodules write it relative to the tree, so resolve it there. Against the current dir it gives a path that does not exist, and docker mounts that as an empty dir without saying anything. Signed-off-by: Anders Roxell <anders.roxell@linaro.org>
aa5c160 to
38c8862
Compare
| commondir = gitdir / "commondir" | ||
| if commondir.exists(): | ||
| gitdir = gitdir / commondir.read_text().strip() | ||
| return gitdir.resolve() |
There was a problem hiding this comment.
This should return None unless the dir exists, (or I guess guard the call site). Otherwise it will try and mount unconditionally/and doesnt check for existence.
| dotgit = directory / ".git" | ||
| if not dotgit.is_file(): | ||
| return None | ||
| gitdir = directory / Path(dotgit.read_text().partition("gitdir:")[2].strip()) |
There was a problem hiding this comment.
This should also return None when the seperator not found. I.e. if .git is a file without a gitdir:.
| && {make} modules_install | ||
| && {tar_caf} {build_dir}/modules.tar{z_ext} -C {build_dir}/modinstall lib | ||
| && {tar_caf} {build_dir}/modules.tar{z_ext} | ||
| --exclude=lib/modules/*/build --exclude=lib/modules/*/source |
There was a problem hiding this comment.
--anchored --no-wildcards-match-slash should be added
There was a problem hiding this comment.
To explain: GNU tar defaults exclusions to --no-anchored --wildcards --wildcards-match-slash, so lib/modules/*/build also matches any build directory nested deeper and drops the modules inside it. No in-tree kernel hits this today, it is really about out of tree module trees and future-proofing.
| env["KRUSTFLAGS"] = ( | ||
| f"--remap-path-prefix={self.source_tree}/= " | ||
| f"--remap-path-prefix={self.build_dir}=/tuxmake " | ||
| "--remap-path-scope=all" |
There was a problem hiding this comment.
This breaks rust builds for me. Since kernel commit dda135077ecc (v7.0-rc4) the Makefile already feature-tests and sets --remap-path-scope=macro for out of tree builds, then appends KRUSTFLAGS, so rustc sees the option twice and errors with Option 'remap-path-scope' given more than once. The option also only stabilised in rustc 1.95, so anything older rejects it outright. all is the default anyway, I compared binaries built with and without it and they are byte identical, so I think this line can just go.
| && {make} modules_install | ||
| && {tar_caf} {build_dir}/modules.tar{z_ext} -C {build_dir}/modinstall lib | ||
| && {tar_caf} {build_dir}/modules.tar{z_ext} | ||
| --exclude=lib/modules/*/build --exclude=lib/modules/*/source |
There was a problem hiding this comment.
To explain: GNU tar defaults exclusions to --no-anchored --wildcards --wildcards-match-slash, so lib/modules/*/build also matches any build directory nested deeper and drops the modules inside it. No in-tree kernel hits this today, it is really about out of tree module trees and future-proofing.
| f"--remap-path-prefix={self.build_dir}=/tuxmake " | ||
| "--remap-path-scope=all" | ||
| ) | ||
| env.update(self.__environment_input__) |
There was a problem hiding this comment.
This says the mapping happens unconditionally, but env.update(self.environment_input) a bit further down in build.py lets a user-supplied KCFLAGS replace the whole prefix map rather than extend it. So --environment=KCFLAGS=-Werror drops both -ffile-prefix-map entries and the absolute paths go back into the debug info, and the reproducer carries that KCFLAGS to the next machine where it clobbers them again. Not a line this PR touches, but the PR is what makes it matter. Either append rather than overwrite, or caveat this paragraph.
Two builds of the same tree did not give the same artifacts. The build
dir and the source tree ended up in the debug info, the modules tarball
had a symlink to the build dir, and a build from a git worktree lost the
git part of the kernel version.
Two arm64 defconfig builds now give the same seven artifacts, vmlinux
byte for byte.
The compat vDSO needs a kernel patch too. Will send separately.