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)