Skip to content

Commit da198cb

Browse files
committed
fdroid: make reproducible
1 parent 4a1681a commit da198cb

12 files changed

Lines changed: 245 additions & 98 deletions

ci/Jenkinsfile.fdroid

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env groovy
2-
library 'status-jenkins-lib@v1.9.40'
2+
library 'status-jenkins-lib@fdroid-apk-signing'
33

44
/* Options section can't access functions in objects. */
55
def isPRBuild = utils.isPRBuild()
@@ -93,6 +93,12 @@ pipeline {
9393
}
9494
}
9595

96+
stage('Sign') {
97+
steps { script {
98+
status.signFdroidApk(env.STATUS_FDROID_APK)
99+
} }
100+
}
101+
96102
stage('Upload') {
97103
steps {
98104
script {

fdroid/build-qt.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
11
#!/usr/bin/env bash
22
set -eo pipefail
33

4+
# Reproducibility env
5+
export LC_ALL=C
6+
export LANG=C
7+
export SOURCE_DATE_EPOCH="$(git -C "$BUILD_DIR" log -1 --pretty=%ct 2>/dev/null || echo 0)"
8+
49
QT_VERSION="${QT_VERSION:-6.9.2}"
510

611
QT_MODULES=qtbase,qtdeclarative,qt5compat,qtmultimedia,qtshadertools,qtimageformats,qtwebview,qtscxml,qtsvg,qtconnectivity,qtwebsockets,qtpositioning,qtlottie,qtwebchannel
712
(cd "$QT_SRCDIR" && perl init-repository --module-subset="$QT_MODULES")
813

14+
# Reproducibility:
15+
# add_link_options: strips .note.gnu.build-id from every .so
16+
# add_compile_options: make sure that paths dont leak into final build.
17+
QT5_CMAKELISTS="$QT_SRCDIR/CMakeLists.txt"
18+
if ! grep -q 'build-id=none' "$QT5_CMAKELISTS"; then
19+
sed -i '/^project(Qt$/,/^)$/{/^)$/a\
20+
add_link_options("LINKER:--build-id=none")\
21+
add_compile_options("-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")\
22+
add_compile_options("-ffile-prefix-map=${CMAKE_BINARY_DIR}=.")\
23+
add_compile_options("-ffile-prefix-map=$ENV{HOME}=.")
24+
}' "$QT5_CMAKELISTS"
25+
fi
26+
27+
# Reproducibility: force qmlcachegen --only-bytecode for every Qt-internal QML module.
28+
# Otherwise we end up with +800 bytes of AOT code in one libQt6QuickControls2Fusion.
29+
QT_QML_MACROS="$QT_SRCDIR/qtdeclarative/src/qml/Qt6QmlMacros.cmake"
30+
if [ -f "$QT_QML_MACROS" ] && ! grep -q 'REPRODUCIBILITY_ONLY_BYTECODE' "$QT_QML_MACROS"; then
31+
sed -i '/MATCHES "-NOTFOUND\$"/,/endforeach()/{
32+
/endforeach()/a\
33+
# REPRODUCIBILITY_ONLY_BYTECODE: pin every Qt-internal QML module to\
34+
# qmlcachegen --only-bytecode so AOT C++ codegen does not introduce\
35+
# per-build non-determinism in compiled .so files.\
36+
get_target_property(_qmlcg_args ${target} QT_QMLCACHEGEN_ARGUMENTS)\
37+
if(NOT "--only-bytecode" IN_LIST _qmlcg_args)\
38+
list(APPEND _qmlcg_args "--only-bytecode")\
39+
set_target_properties(${target} PROPERTIES QT_QMLCACHEGEN_ARGUMENTS "${_qmlcg_args}")\
40+
endif()
41+
}' "$QT_QML_MACROS"
42+
fi
43+
44+
# Reproducibility: rewrite absolute build/install paths embedded in compiled objects
45+
QT_REPRO_CFLAGS="-ffile-prefix-map=${QT_SRCDIR}=. -ffile-prefix-map=${BUILD_DIR}=. -ffile-prefix-map=${HOME}=."
46+
947
# Build Qt for host (required as cross-compilation toolchain for Android)
1048
mkdir -p build_qt_host && cd build_qt_host
1149

@@ -18,6 +56,10 @@ mkdir -p build_qt_host && cd build_qt_host
1856
-nomake tests \
1957
-- \
2058
-DCMAKE_BUILD_TYPE=Release \
59+
-DCMAKE_C_FLAGS_INIT="$QT_REPRO_CFLAGS" \
60+
-DCMAKE_CXX_FLAGS_INIT="$QT_REPRO_CFLAGS" \
61+
-DCMAKE_UNITY_BUILD=OFF \
62+
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \
2163
-DCMAKE_MESSAGE_LOG_LEVEL=WARNING \
2264
-Wno-dev
2365

@@ -50,6 +92,10 @@ mkdir -p build_qt_android && cd build_qt_android
5092
-DFFMPEG_DIR="$FFMPEG_DIR" \
5193
-DFEATURE_ffmpeg=ON \
5294
-DQT_DEFAULT_MEDIA_BACKEND=ffmpeg \
95+
-DCMAKE_C_FLAGS_INIT="$QT_REPRO_CFLAGS" \
96+
-DCMAKE_CXX_FLAGS_INIT="$QT_REPRO_CFLAGS" \
97+
-DCMAKE_UNITY_BUILD=OFF \
98+
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \
5399
-DCMAKE_MESSAGE_LOG_LEVEL=WARNING \
54100
-Wno-dev
55101

fdroid/generate-keystore.sh

Lines changed: 0 additions & 36 deletions
This file was deleted.

fdroid/sign-apk.sh

Lines changed: 0 additions & 44 deletions
This file was deleted.

mobile/Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ $(STATUS_GO_LIB):
3333
@echo "Building status-go mobile library"
3434
@mkdir -p $(LIB_PATH)
3535
ifeq ($(OS),android)
36+
# Reproducibility envs for status-go
37+
GOFLAGS="-trimpath -buildvcs=false -toolexec=$(abspath $(SCRIPTS_PATH)/go-toolexec-wrapper.sh)" \
38+
GOMODCACHE="$(BUILD_PATH)/.gomodcache" \
39+
CGO_CFLAGS="-ffile-prefix-map=$(HOME)=. -ffile-prefix-map=$(BUILD_PATH)=. -ffile-prefix-map=$(ROOT_DIR)=. -ffile-prefix-map=$(ANDROID_NDK_ROOT)=." \
40+
CGO_CXXFLAGS="-ffile-prefix-map=$(HOME)=. -ffile-prefix-map=$(BUILD_PATH)=. -ffile-prefix-map=$(ROOT_DIR)=. -ffile-prefix-map=$(ANDROID_NDK_ROOT)=." \
41+
CGO_LDFLAGS="-Wl,--build-id=none" \
3642
CC="$(CC)" $(MAKE) -C ../vendor/status-go statusgo-android-library \
3743
ARCH=$(ARCH) \
3844
ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \
@@ -42,7 +48,17 @@ ifeq ($(OS),android)
4248
USE_SYSTEM_NIM=$(USE_SYSTEM_NIM) \
4349
GO_GENERATE_CMD="go generate" \
4450
SHELL=/bin/sh
51+
@$(SCRIPTS_PATH)/canonicalize-cgo-symbols.sh \
52+
../vendor/status-go/build/bin/libstatus$(LIB_EXT)
53+
@$(ANDROID_NDK_ROOT)/toolchains/llvm/prebuilt/$(HOST_OS)-x86_64/bin/llvm-objcopy \
54+
--remove-section=.note.gnu.build-id \
55+
--remove-section=.note.go.buildid \
56+
../vendor/status-go/build/bin/libstatus$(LIB_EXT)
4557
else ifeq ($(OS),ios)
58+
GOFLAGS="-trimpath -buildvcs=false" \
59+
GOMODCACHE="$(BUILD_PATH)/.gomodcache" \
60+
CGO_CFLAGS="-ffile-prefix-map=$(HOME)=." \
61+
CGO_CXXFLAGS="-ffile-prefix-map=$(HOME)=." \
4662
$(MAKE) -C ../vendor/status-go statusgo-ios-library \
4763
ARCH=$(ARCH) \
4864
IPHONE_SDK="$(IPHONE_SDK)" \

mobile/scripts/buildApp.sh

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ if [[ "${OS}" == "android" ]]; then
4848
export BUILD_VARIANT
4949
export BUNDLE_IDENTIFIER
5050

51-
if [[ "$GRADLE_TARGETS" == *"Fdroid"* ]]; then
52-
# shellcheck source=../../fdroid/generate-keystore.sh
53-
source "$REPO_ROOT/fdroid/generate-keystore.sh" "$REPO_ROOT/status-fdroid.keystore"
54-
fi
55-
51+
# Reproducibility flags for fdroid rebuilds:
52+
# --build-id=none drops the .note.gnu.build-id section
53+
# -ffile-prefix-map remaps absolute source paths so they don't depend on $HOME or $BUILD_DIR.
54+
QMAKE_REPRO_ARGS=(
55+
"QMAKE_LFLAGS+=-Wl,--build-id=none"
56+
"QMAKE_CFLAGS+=-ffile-prefix-map=${BUILD_DIR}=."
57+
"QMAKE_CFLAGS+=-ffile-prefix-map=${REPO_ROOT}=."
58+
"QMAKE_CFLAGS+=-ffile-prefix-map=${HOME}=."
59+
"QMAKE_CXXFLAGS+=-ffile-prefix-map=${BUILD_DIR}=."
60+
"QMAKE_CXXFLAGS+=-ffile-prefix-map=${REPO_ROOT}=."
61+
"QMAKE_CXXFLAGS+=-ffile-prefix-map=${HOME}=."
62+
)
5663
"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" "${QMAKE_CONFIG[@]}" -spec android-clang \
57-
ANDROID_ABIS="${ANDROID_ABI:-arm64-v8a}" VERSION="$VERSION" "${QMAKE_DEFINES[@]}" -after
64+
ANDROID_ABIS="${ANDROID_ABI:-arm64-v8a}" VERSION="$VERSION" "${QMAKE_DEFINES[@]}" \
65+
-after "${QMAKE_REPRO_ARGS[@]}"
5866

5967
make -j"$(nproc)" apk_install_target
6068

@@ -117,18 +125,14 @@ if [[ "${OS}" == "android" ]]; then
117125
echo "APK outputs:"
118126
find build/outputs/apk -name '*.apk' 2>/dev/null || echo "No APKs found"
119127

120-
# If Gradle produced an unsigned APK (e.g. fdroid build where signing configs
121-
# are stripped by fdroid's remove_signing_keys), sign it via the dedicated script.
122-
if [[ ! -f "$APK_OUT" && -f "$APK_OUT_UNSIGNED" && -n "${FDROID_STORE_FILE:-}" ]]; then
123-
echo "Signing unsigned APK..."
124-
"$REPO_ROOT/fdroid/sign-apk.sh"
125-
fi
126-
127128
# Copy whichever artifacts were built
128129
BUILT=""
129130
if [[ -f "$APK_OUT" ]]; then
130131
cp "$APK_OUT" "$BIN_DIR/${OUTPUT_NAME}.apk"
131132
BUILT="$BIN_DIR/${OUTPUT_NAME}.apk"
133+
elif [[ -f "$APK_OUT_UNSIGNED" ]]; then
134+
cp "$APK_OUT_UNSIGNED" "$BIN_DIR/${OUTPUT_NAME}.apk"
135+
BUILT="$BIN_DIR/${OUTPUT_NAME}.apk"
132136
fi
133137
if [[ -f "$AAB_OUT" ]]; then
134138
cp "$AAB_OUT" "$BIN_DIR/${OUTPUT_NAME}.aab"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
SO_PATH="${1:?Usage: $0 <path-to-libstatus.so>}"
6+
NM="${NM:-llvm-nm}"
7+
OBJCOPY="${OBJCOPY:-llvm-objcopy}"
8+
9+
command -v "$NM" >/dev/null || { echo "[canonicalize-cgo] $NM not found on PATH" >&2; exit 1; }
10+
command -v "$OBJCOPY" >/dev/null || { echo "[canonicalize-cgo] $OBJCOPY not found on PATH" >&2; exit 1; }
11+
12+
list_symbols() { "$NM" -D -P "$SO_PATH"; "$NM" -P "$SO_PATH" 2>/dev/null || true; }
13+
14+
mapfile -t HASHES < <(
15+
list_symbols | awk '{print $1}' \
16+
| grep -Eo '^_cgo(exp)?_[0-9a-f]{12,16}_' \
17+
| sed -E 's/^_cgo(exp)?_([0-9a-f]+)_/\2/' \
18+
| sort -u
19+
)
20+
21+
if [[ ${#HASHES[@]} -eq 0 ]]; then
22+
echo "[canonicalize-cgo] no cgo hash symbols found in $SO_PATH" >&2
23+
echo "[canonicalize-cgo] this is unexpected for a cgo build; check that nm reads .dynsym" >&2
24+
exit 1
25+
fi
26+
27+
MAPPING=$(mktemp)
28+
PERL_SCRIPT=""
29+
trap 'rm -f "$MAPPING"' EXIT
30+
31+
# Pick a sha256 tool that's available on both macOS and Debian-derived fdroid
32+
# builders. Both ship at least one of these.
33+
if command -v sha256sum >/dev/null 2>&1; then
34+
SHA256="sha256sum"
35+
else
36+
SHA256="shasum -a 256"
37+
fi
38+
39+
rewrites=0
40+
for HASH in "${HASHES[@]}"; do
41+
LEN=${#HASH}
42+
SHA_LEN=$((LEN - 4))
43+
STABLE=$(
44+
list_symbols | awk '{print $1}' \
45+
| grep -E "_cgo(exp)?_${HASH}_" \
46+
| sed "s/${HASH}/X/g" \
47+
| sort -u \
48+
| $SHA256 | awk '{print $1}'
49+
)
50+
CANON="c0de${STABLE:0:$SHA_LEN}"
51+
52+
if [[ "$HASH" == "$CANON" ]]; then
53+
continue
54+
fi
55+
rewrites=$((rewrites + 1))
56+
57+
list_symbols | awk '{print $1}' \
58+
| grep -E "_cgo(exp)?_${HASH}_" | sort -u \
59+
| while read -r SYM; do
60+
printf '%s %s\n' "$SYM" "${SYM/$HASH/$CANON}"
61+
done \
62+
>> "$MAPPING"
63+
64+
PERL_SCRIPT+="s/_cgo_${HASH}_/_cgo_${CANON}_/g;"
65+
PERL_SCRIPT+="s/_cgoexp_${HASH}_/_cgoexp_${CANON}_/g;"
66+
done
67+
68+
if [[ $rewrites -eq 0 ]]; then
69+
echo "[canonicalize-cgo] all ${#HASHES[@]} hashes already canonical"
70+
exit 0
71+
fi
72+
73+
echo "[canonicalize-cgo] rewriting $rewrites of ${#HASHES[@]} hashes in $SO_PATH"
74+
75+
"$OBJCOPY" --redefine-syms="$MAPPING" "$SO_PATH"
76+
77+
perl -i -0777 -pe "$PERL_SCRIPT" "$SO_PATH"
78+
79+
echo "[canonicalize-cgo] done"

0 commit comments

Comments
 (0)