Reproducible Builds - avoid embedding build directory in RPATH
CMake by default sets the RPATH property on executables that link to shared libraries in the same project. This makes it possible to run executables (like tests) from the build directory without having to set LD_LIBRARY_PATH
.
Unfortunately this default behavior hurts reproducible builds in the sense that the build directory also becomes part of the build environment and thus the linked binaries are different. Even if the rpath is stripped as part of the install target, the damage is already done since the .note.gnu.build-id
section was computed over the original file that contained the rpath. (Related post about this issue: https://lists.reproducible-builds.org/pipermail/rb-general/2018-October/001174.html)
As workaround, packagers can set CMAKE_SKIP_RPATH=ON
to disable rpaths completely, but of course that will break in some cases (which then require LD_LIBRARY_PATH
to be set before running tests).
Possible solutions:
- Relink the executables on installation instead of merely changing the embedded rpath.
- Make rpath relative to objects in the build directory using
$ORIGIN
. I would be in favor of this, would you be willing to accept patches for this feature?
To reproduce this issue, see the files below and use these commands (note difference in path length as well):
mkdir build-first && (cd build-first && cmake -GNinja .. && DESTDIR=$PWD/fs ninja install)
mkdir build-second && (cd build-second && cmake -GNinja .. && DESTDIR=$PWD/fs ninja install)
mkdir norp-first && (cd norp-first && cmake -GNinja .. -DCMAKE_SKIP_RPATH=ON && DESTDIR=$PWD/fs ninja install)
mkdir norp-second && (cd norp-second && cmake -GNinja .. -DCMAKE_SKIP_RPATH=ON && DESTDIR=$PWD/fs ninja install)
find * -name main -exec sha256sum {} \;
As you can see, the files with RPATHs embedded are all different even after stripping:
c21d8f3cc410be95758e306eac22bf34e65c0731cbc6dc311395554d41b405ab build-first/fs/usr/local/bin/main
2720b92ff8147297e5f46b5f9048696f2a10816573f337db5b85be63fb13da15 build-first/main
2c03da564482d42acb47994a4464877f8570a662c33d738d72cf3d8248c0e761 build-second/fs/usr/local/bin/main
eb19657ffcf59d2a1e94d3c15d62d8bd147953a666ecba68b5880b4018a4dc74 build-second/main
9d7784b23366b3374272981d52dce4065bc4edbaa07dda51d644d52f02861bf7 norp-first/fs/usr/local/bin/main
9d7784b23366b3374272981d52dce4065bc4edbaa07dda51d644d52f02861bf7 norp-first/main
9d7784b23366b3374272981d52dce4065bc4edbaa07dda51d644d52f02861bf7 norp-second/fs/usr/local/bin/main
9d7784b23366b3374272981d52dce4065bc4edbaa07dda51d644d52f02861bf7 norp-second/main
diff -u <(objdump -xags build-first/main) <(objdump -xags build-second/main)
--- /dev/fd/63 2018-10-02 16:16:40.129184290 +0200
+++ /dev/fd/62 2018-10-02 16:16:40.129184290 +0200
@@ -1,6 +1,6 @@
-build-first/main: file format elf64-x86-64
-build-first/main
+build-second/main: file format elf64-x86-64
+build-second/main
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000001040
@@ -32,7 +32,7 @@
Dynamic Section:
NEEDED libutils.so
NEEDED libc.so.6
- RPATH /tmp/cm/rep/build-first:
+ RPATH /tmp/cm/rep/build-second:
INIT 0x0000000000001000
FINI 0x00000000000011c8
INIT_ARRAY 0x0000000000003dc0
@@ -42,7 +42,7 @@
GNU_HASH 0x0000000000000308
STRTAB 0x00000000000003f0
SYMTAB 0x0000000000000330
- STRSZ 0x00000000000000ab
+ STRSZ 0x00000000000000ac
SYMENT 0x0000000000000018
DEBUG 0x0000000000000000
PLTGOT 0x0000000000004000
@@ -74,7 +74,7 @@
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 000000c0 0000000000000330 0000000000000330 00000330 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
- 5 .dynstr 000000ab 00000000000003f0 00000000000003f0 000003f0 2**0
+ 5 .dynstr 000000ac 00000000000003f0 00000000000003f0 000003f0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 00000010 000000000000049c 000000000000049c 0000049c 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
@@ -192,8 +192,8 @@
02d4 00000000 03000000 02000000 00000000 ................
Contents of section .note.gnu.build-id:
02e4 04000000 14000000 03000000 474e5500 ............GNU.
- 02f4 72e392e0 84573517 b9d1f3ff 717a1639 r....W5.....qz.9
- 0304 5c49fbe0 \I..
+ 02f4 56ccc14f be0fa1d5 88616df4 a750af47 V..O.....am..P.G
+ 0304 da39beda .9..
Contents of section .gnu.hash:
0308 02000000 07000000 01000000 06000000 ................
0318 80100000 00000000 07000000 00000000 ................
@@ -222,7 +222,7 @@
0460 697a6500 5f5f6c69 62635f73 74617274 ize.__libc_start
0470 5f6d6169 6e00474c 4942435f 322e322e _main.GLIBC_2.2.
0480 35002f74 6d702f63 6d2f7265 702f6275 5./tmp/cm/rep/bu
- 0490 696c642d 66697273 743a00 ild-first:.
+ 0490 696c642d 7365636f 6e643a00 ild-second:.
Contents of section .gnu.version:
049c 00000000 02000200 00000000 02000000 ................
Contents of section .gnu.version_r:
@@ -321,7 +321,7 @@
3e68 f5feff6f 00000000 08030000 00000000 ...o............
3e78 05000000 00000000 f0030000 00000000 ................
3e88 06000000 00000000 30030000 00000000 ........0.......
- 3e98 0a000000 00000000 ab000000 00000000 ................
+ 3e98 0a000000 00000000 ac000000 00000000 ................
3ea8 0b000000 00000000 18000000 00000000 ................
3eb8 15000000 00000000 00000000 00000000 ................
3ec8 03000000 00000000 00400000 00000000 .........@......
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(TestC LANGUAGES C)
add_library(utils SHARED utils.c)
add_executable(main main.c)
target_link_libraries(main utils)
include(GNUInstallDirs)
install(TARGETS main RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS utils LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
main.c
#include <stdio.h>
extern const char msg[];
int main() {
puts(msg);
}
utils.c
const char msg[] = "hello";
FWIW, a similar Meson configuration uses $ORIGIN
by default for rpath:
meson.build
project('TestC', 'c')
lib_utils = shared_library('utils', 'utils.c')
executable('main', 'main.c', link_with : lib_utils)
Build with:
$ CFLAGS=-fdebug-prefix-map=$PWD/$i=. CC=gcc meson setup m1 && ninja -C m1
$ CFLAGS=-fdebug-prefix-map=$PWD/$i=. CC=gcc meson setup m-two && ninja -C m-two
...
[2/5] gcc -Imain@exe -I. -I.. -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -g -fdebug-prefix-map=/tmp/cm/rep/m-two=. -MD -MQ 'main@exe/main.c.o' -MF 'main@exe/main.c.o.d' -o 'main@exe/main.c.o' -c ../main.c
...
[5/5] gcc -o main 'main@exe/main.c.o' -Wl,--no-undefined -Wl,--as-needed -fdebug-prefix-map=/tmp/cm/rep/m-two=. -Wl,--start-group libutils.so -Wl,--end-group '-Wl,-rpath,$ORIGIN/' -Wl,-rpath-link,/tmp/cm/rep/m-two/
and observe that the main
executables are equal.