From f77d0c0760b4cfd3290ca44fc5395d8d1980364a Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Fri, 5 Jun 2026 17:44:10 +0900 Subject: [PATCH] Build: replace fixed SOURCE_DATE_EPOCH fallback Keep caller-provided SOURCE_DATE_EPOCH authoritative and derive the automatic default through a shared helper used by the Makefile, direct CMake/CPack packaging, and the deb packaging wrapper. When repository metadata is available, use the HEAD commit timestamp without relying on git -C. Resolve the source root before probing Git so symlinked source paths still use the checkout HEAD. For source tarballs without .git, derive the fallback timestamp from the release date encoded in Common/Tcdefs.h instead of the stale 2020-01-01 constant. Add TC_RELEASE_DATE_DAY and validate it together with TC_RELEASE_DATE_YEAR, TC_RELEASE_DATE_MONTH, and TC_STR_RELEASE_DATE. Abort when no valid timestamp can be derived. For direct CMake invocation, initialize SOURCEPATH when the wrapper has not provided it, use the shared helper for derivation, validate the result, and export it for package targets. Also persist the configured epoch through CPACK_PROJECT_CONFIG_FILE so later standalone cpack --config runs export the same value before invoking package generators. Document that automatic git-checkout builds and release-tarball builds intentionally use different epochs; release reproducers should build from the tarball or set SOURCE_DATE_EPOCH explicitly. --- README.md | 8 ++ src/Build/CMakeLists.txt | 56 ++++++-- .../Tools/cpack_source_date_epoch.cmake.in | 3 + src/Build/Tools/source_date_epoch.sh | 121 ++++++++++++++++++ src/Build/build_cmake_deb.sh | 31 +++-- src/Common/Tcdefs.h | 1 + src/Makefile | 8 +- 7 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 src/Build/Tools/cpack_source_date_epoch.cmake.in create mode 100755 src/Build/Tools/source_date_epoch.sh diff --git a/README.md b/README.md index c7452f2c..be87b3f5 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,14 @@ it is also available [online](https://veracrypt.jp/en/CompilingGuidelineLinux.ht 4. If successful, the VeraCrypt executable should be located in the directory 'Main'. +Reproducible build note: when `SOURCE_DATE_EPOCH` is not set, a build from a +git checkout uses the HEAD commit timestamp, while a build from a release +tarball uses the release date in `src/Common/Tcdefs.h` at 00:00 UTC. To +reproduce official release artifacts from a git checkout, set +`SOURCE_DATE_EPOCH` explicitly or build from the release tarball. Vendored +VeraCrypt sources tracked in another git checkout are treated the same way and +use that checkout's HEAD timestamp. + By default, a universal executable supporting both graphical and text user interface (through the switch --text) is built. On Linux, a console-only executable, which requires no GUI library, can be diff --git a/src/Build/CMakeLists.txt b/src/Build/CMakeLists.txt index ec287c8c..11d14112 100644 --- a/src/Build/CMakeLists.txt +++ b/src/Build/CMakeLists.txt @@ -27,23 +27,48 @@ else() endif() project(${PROJECT_NAME}) -# SOURCE_DATE_EPOCH for the cpack-driven DEB pipeline. -# Precedence: -DSOURCE_DATE_EPOCH=N, env, git HEAD, fixed fallback. -# Re-exported to ENV so dpkg-deb/tar inherit it. +# Source directory which contains the Makefile. Build scripts set this, but keep +# direct cmake invocations working as well. +if(NOT DEFINED ENV{SOURCEPATH}) + get_filename_component(_vc_sourcepath "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) + set(ENV{SOURCEPATH} "${_vc_sourcepath}") +endif() +if("$ENV{SOURCEPATH}" STREQUAL "") + get_filename_component(_vc_sourcepath "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) + set(ENV{SOURCEPATH} "${_vc_sourcepath}") +endif() + +# SOURCE_DATE_EPOCH for the cpack-driven packaging pipeline. +# Precedence: -DSOURCE_DATE_EPOCH=N, env, git HEAD, Common/Tcdefs.h release date. +# Re-exported to this process for package targets and written into +# CPACK_PROJECT_CONFIG_FILE so later standalone "cpack --config" runs export +# the same value before invoking package generators. if(NOT DEFINED SOURCE_DATE_EPOCH) if(DEFINED ENV{SOURCE_DATE_EPOCH}) set(SOURCE_DATE_EPOCH "$ENV{SOURCE_DATE_EPOCH}") + endif() +endif() +set(_derive_source_date_epoch FALSE) +if(NOT DEFINED SOURCE_DATE_EPOCH) + set(_derive_source_date_epoch TRUE) +elseif("${SOURCE_DATE_EPOCH}" STREQUAL "") + set(_derive_source_date_epoch TRUE) +endif() +if(_derive_source_date_epoch) + execute_process( + COMMAND sh "$ENV{SOURCEPATH}/Build/Tools/source_date_epoch.sh" "$ENV{SOURCEPATH}" + OUTPUT_VARIABLE _source_date_epoch + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE _source_date_epoch_error + RESULT_VARIABLE _source_date_epoch_rc) + if(_source_date_epoch_rc EQUAL 0 AND NOT "${_source_date_epoch}" STREQUAL "") + set(SOURCE_DATE_EPOCH "${_source_date_epoch}") else() - execute_process( - COMMAND git -C "$ENV{SOURCEPATH}" log -1 --pretty=%ct - OUTPUT_VARIABLE _git_ct - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _git_rc) - if(_git_rc EQUAL 0 AND _git_ct) - set(SOURCE_DATE_EPOCH "${_git_ct}") + string(STRIP "${_source_date_epoch_error}" _source_date_epoch_error) + if(_source_date_epoch_error) + MESSAGE(FATAL_ERROR "SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date: ${_source_date_epoch_error}") else() - set(SOURCE_DATE_EPOCH "1577836800") + MESSAGE(FATAL_ERROR "SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date") endif() endif() endif() @@ -52,6 +77,13 @@ if(NOT SOURCE_DATE_EPOCH MATCHES "^[0-9]+$") endif() message(STATUS "SOURCE_DATE_EPOCH = ${SOURCE_DATE_EPOCH}") set(ENV{SOURCE_DATE_EPOCH} "${SOURCE_DATE_EPOCH}") +# Standalone "cpack --config CPackConfig.cmake" runs in a new process, so +# persist the configure-time epoch into CPack's package-time config. +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/Tools/cpack_source_date_epoch.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cpack_source_date_epoch.cmake" + @ONLY) +set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/cpack_source_date_epoch.cmake") # Avoid nondeterministic ordering from cpack 3.18+ parallel compression. set(CPACK_THREADS 1) diff --git a/src/Build/Tools/cpack_source_date_epoch.cmake.in b/src/Build/Tools/cpack_source_date_epoch.cmake.in new file mode 100644 index 00000000..861dc97d --- /dev/null +++ b/src/Build/Tools/cpack_source_date_epoch.cmake.in @@ -0,0 +1,3 @@ +# Preserve configure-time SOURCE_DATE_EPOCH for standalone cpack --config runs. +set(SOURCE_DATE_EPOCH "@SOURCE_DATE_EPOCH@") +set(ENV{SOURCE_DATE_EPOCH} "${SOURCE_DATE_EPOCH}") diff --git a/src/Build/Tools/source_date_epoch.sh b/src/Build/Tools/source_date_epoch.sh new file mode 100755 index 00000000..f9d14956 --- /dev/null +++ b/src/Build/Tools/source_date_epoch.sh @@ -0,0 +1,121 @@ +#!/bin/sh +# +# Derive SOURCE_DATE_EPOCH for VeraCrypt build and packaging paths. +# Precedence inside this helper is git HEAD, then the release date encoded in +# Common/Tcdefs.h. Callers remain responsible for honoring an explicit +# SOURCE_DATE_EPOCH before invoking this helper. The source root is resolved +# before probing Git so symlinked build paths still use the checkout HEAD, +# while release tarballs unpacked below unrelated repositories ignore the +# parent repository and fall back to Common/Tcdefs.h. + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +SOURCE_ROOT_INPUT=${1%/} +if [ -z "$SOURCE_ROOT_INPUT" ]; then + SOURCE_ROOT_INPUT=/ +fi +SOURCE_ROOT=$(cd "$SOURCE_ROOT_INPUT" 2>/dev/null && pwd -P) || { + echo "Error: $1 is not a readable directory" >&2 + exit 1 +} +TCDEFS_H="$SOURCE_ROOT/Common/Tcdefs.h" + +GIT_WORKTREE= +GIT_SOURCE_PREFIX= +SOURCE_ROOT_BASENAME=${SOURCE_ROOT##*/} + +if [ -e "$SOURCE_ROOT/.git" ]; then + GIT_WORKTREE="$SOURCE_ROOT" +elif [ -e "$SOURCE_ROOT/../.git" ]; then + GIT_WORKTREE=$(cd "$SOURCE_ROOT/.." 2>/dev/null && pwd -P) + GIT_SOURCE_PREFIX="$SOURCE_ROOT_BASENAME/" +fi + +GIT_EPOCH= +if [ -n "$GIT_WORKTREE" ] && + (cd "$GIT_WORKTREE" && + git rev-parse --is-inside-work-tree >/dev/null 2>&1 && + git ls-files --error-unmatch \ + "${GIT_SOURCE_PREFIX}Common/Tcdefs.h" \ + "${GIT_SOURCE_PREFIX}Build/Tools/source_date_epoch.sh" >/dev/null 2>&1); then + GIT_EPOCH=$(cd "$GIT_WORKTREE" && git log -1 --pretty=%ct 2>/dev/null) +fi +case "$GIT_EPOCH" in + ''|*[!0-9]*) + ;; + *) + printf '%s\n' "$GIT_EPOCH" + exit 0 + ;; +esac + +if [ ! -r "$TCDEFS_H" ]; then + echo "Error: $TCDEFS_H is not readable" >&2 + exit 1 +fi + +RELEASE_EPOCH=$(awk ' + function leap(y) { return ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) } + function month_number(name) { + return (name == "January" ? 1 : name == "February" ? 2 : name == "March" ? 3 : + name == "April" ? 4 : name == "May" ? 5 : name == "June" ? 6 : + name == "July" ? 7 : name == "August" ? 8 : name == "September" ? 9 : + name == "October" ? 10 : name == "November" ? 11 : name == "December" ? 12 : 0); + } + function mdays(m, y) { return (m == 2 ? 28 + leap(y) : (m == 4 || m == 6 || m == 9 || m == 11 ? 30 : 31)) } + function epoch(y, m, d, days, i) { + days = 0; + for (i = 1970; i < y; i++) days += 365 + leap(i); + for (i = 1; i < m; i++) days += mdays(i, y); + days += d - 1; + return days * 86400; + } + function is_number(value) { return (value ~ /^[0-9]+$/) } + + /^[[:space:]]*#define[[:space:]]+TC_STR_RELEASE_DATE[[:space:]]+L"/ { + date = $0; + sub(/^.*L"/, "", date); + sub(/".*$/, "", date); + split(date, parts, /[ ,]+/); + if (month_number(parts[1]) == 0 || !is_number(parts[2]) || !is_number(parts[3])) exit 1; + date_month = month_number(parts[1]); + date_day = parts[2] + 0; + date_year = parts[3] + 0; + seen_date = 1; + } + /^[[:space:]]*#define[[:space:]]+TC_RELEASE_DATE_YEAR[[:space:]]+/ { + if (!is_number($3)) exit 1; + year = $3 + 0; + seen_year = 1; + } + /^[[:space:]]*#define[[:space:]]+TC_RELEASE_DATE_MONTH[[:space:]]+/ { + if (!is_number($3)) exit 1; + month = $3 + 0; + seen_month = 1; + } + /^[[:space:]]*#define[[:space:]]+TC_RELEASE_DATE_DAY[[:space:]]+/ { + if (!is_number($3)) exit 1; + day = $3 + 0; + seen_day = 1; + } + END { + if (!seen_date || !seen_year || !seen_month || !seen_day) exit 1; + if (year < 1970 || month < 1 || month > 12 || day < 1 || day > mdays(month, year)) exit 1; + if (date_month != month || date_day != day || date_year != year) exit 1; + printf "%d", epoch(year, month, day); + }' "$TCDEFS_H") || { + echo "Error: unable to derive SOURCE_DATE_EPOCH from $TCDEFS_H" >&2 + exit 1 +} + +case "$RELEASE_EPOCH" in + ''|*[!0-9]*) + echo "Error: unable to derive SOURCE_DATE_EPOCH from $TCDEFS_H" >&2 + exit 1 + ;; +esac + +printf '%s\n' "$RELEASE_EPOCH" diff --git a/src/Build/build_cmake_deb.sh b/src/Build/build_cmake_deb.sh index d85cdded..451fb321 100755 --- a/src/Build/build_cmake_deb.sh +++ b/src/Build/build_cmake_deb.sh @@ -15,20 +15,6 @@ set -e # not reproducible. Pin it for the whole packaging run. umask 022 -# Compute and export SOURCE_DATE_EPOCH so cmake/cpack inherit it (they get -# an empty env from this shell otherwise). Precedence: caller, git HEAD, -# fallback constant matching src/Makefile and CMakeLists.txt. -if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then - SOURCE_DATE_EPOCH=$(git -C "$(dirname "$0")/../.." log -1 --pretty=%ct 2>/dev/null || echo 1577836800) -fi -case "$SOURCE_DATE_EPOCH" in - ''|*[!0-9]*) - echo "Error: SOURCE_DATE_EPOCH must be a non-negative Unix timestamp" >&2 - exit 1 - ;; -esac -export SOURCE_DATE_EPOCH - # Absolute path to this script export SCRIPT=$(readlink -f "$0") # Absolute path this script is in @@ -38,6 +24,23 @@ export SOURCEPATH=$(readlink -f "$SCRIPTPATH/..") # Directory where the VeraCrypt has been checked out export PARENTDIR=$(readlink -f "$SCRIPTPATH/../../..") +# Compute and export SOURCE_DATE_EPOCH so cmake/cpack inherit it (they get +# an empty env from this shell otherwise). Precedence: caller, git HEAD, +# Common/Tcdefs.h release date. +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + SOURCE_DATE_EPOCH=$(sh "$SOURCEPATH/Build/Tools/source_date_epoch.sh" "$SOURCEPATH") || { + echo "Error: SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date" >&2 + exit 1 + } +fi +case "$SOURCE_DATE_EPOCH" in + ''|*[!0-9]*) + echo "Error: SOURCE_DATE_EPOCH must be a non-negative Unix timestamp" >&2 + exit 1 + ;; +esac +export SOURCE_DATE_EPOCH + # Check the condition of wxBuildConsole and wxWidgets-3.2.5 in the original PARENTDIR if [ -d "$PARENTDIR/wxBuildConsole" ]; then echo "Using existing PARENTDIR: $PARENTDIR, wxBuildConsole is present." diff --git a/src/Common/Tcdefs.h b/src/Common/Tcdefs.h index 931025bd..c7c28262 100644 --- a/src/Common/Tcdefs.h +++ b/src/Common/Tcdefs.h @@ -76,6 +76,7 @@ extern unsigned short _rotl16(unsigned short value, unsigned char shift); #define TC_STR_RELEASE_DATE L"June 4, 2026" #define TC_RELEASE_DATE_YEAR 2026 #define TC_RELEASE_DATE_MONTH 6 +#define TC_RELEASE_DATE_DAY 4 #define BYTES_PER_KB 1024LL #define BYTES_PER_MB 1048576LL diff --git a/src/Makefile b/src/Makefile index 102a3adc..8a5f5ffd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -601,10 +601,10 @@ LFLAGS := $(LFLAGS) $(TC_EXTRA_LFLAGS) # is honoured by GCC/Clang for any residual __DATE__/__TIME__ expansion, by # ar/ranlib in deterministic mode, and by tar, gzip and makeself for archive # member timestamps. If the caller does not set it, derive a stable value -# from the HEAD commit; fall back to a fixed constant for tarball builds with -# no git tree so that unattended builds are still deterministic. +# from the HEAD commit; fall back to the release date embedded in +# Common/Tcdefs.h for source-tarball builds without a git checkout. ifndef SOURCE_DATE_EPOCH -export SOURCE_DATE_EPOCH := $(shell git -C $(BASE_DIR) log -1 --pretty=%ct 2>/dev/null || echo 1577836800) +export SOURCE_DATE_EPOCH := $(shell sh "$(BASE_DIR)/Build/Tools/source_date_epoch.sh" "$(BASE_DIR)") export VC_SOURCE_DATE_EPOCH_AUTO := 1 endif override export SOURCE_DATE_EPOCH := $(value SOURCE_DATE_EPOCH) @@ -620,7 +620,7 @@ SOURCE_DATE_EPOCH_REMAINDER := $(subst 7,,$(SOURCE_DATE_EPOCH_REMAINDER)) SOURCE_DATE_EPOCH_REMAINDER := $(subst 8,,$(SOURCE_DATE_EPOCH_REMAINDER)) SOURCE_DATE_EPOCH_REMAINDER := $(subst 9,,$(SOURCE_DATE_EPOCH_REMAINDER)) ifeq "$(SOURCE_DATE_EPOCH)" "" -$(error SOURCE_DATE_EPOCH must be a non-negative Unix timestamp) +$(error SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date) endif ifneq "$(SOURCE_DATE_EPOCH_REMAINDER)" "" $(error SOURCE_DATE_EPOCH must contain decimal digits only)