From 548ac51d8ea319c65abefa0a771636893c45014c Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Thu, 19 Jul 2018 18:14:40 -0700 Subject: CPack/Deb: Support SOURCE_DATE_EPOCH when packaging files --- Help/cpack_gen/deb.rst | 8 +++ Help/release/dev/cpack-deb-source-date-epoch.rst | 6 +++ Source/cmArchiveWrite.cxx | 28 ++++++++++- Tests/RunCMake/CPack/CPackTestHelpers.cmake | 6 ++- Tests/RunCMake/CPack/DEB/Helpers.cmake | 2 +- Tests/RunCMake/CPack/RunCMakeTest.cmake | 3 ++ .../CPack/tests/TIMESTAMPS/ExpectedFiles.cmake | 2 + .../CPack/tests/TIMESTAMPS/VerifyResult.cmake | 58 ++++++++++++++++++++++ Tests/RunCMake/CPack/tests/TIMESTAMPS/test.cmake | 3 ++ 9 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 Help/release/dev/cpack-deb-source-date-epoch.rst create mode 100644 Tests/RunCMake/CPack/tests/TIMESTAMPS/ExpectedFiles.cmake create mode 100644 Tests/RunCMake/CPack/tests/TIMESTAMPS/VerifyResult.cmake create mode 100644 Tests/RunCMake/CPack/tests/TIMESTAMPS/test.cmake diff --git a/Help/cpack_gen/deb.rst b/Help/cpack_gen/deb.rst index c5923be..37d750d 100644 --- a/Help/cpack_gen/deb.rst +++ b/Help/cpack_gen/deb.rst @@ -527,3 +527,11 @@ alternate data stream (ADT) is used. When a filesystem without ADT support is used only owner read/write permissions can be preserved. + +Reproducible packages +^^^^^^^^^^^^^^^^^^^^^ + +The environment variable ``SOURCE_DATE_EPOCH`` may be set to a UNIX +timestamp, defined as the number of seconds, excluding leap seconds, +since 01 Jan 1970 00:00:00 UTC. If set, the CPack Deb generator will +use its value for timestamps in the package. diff --git a/Help/release/dev/cpack-deb-source-date-epoch.rst b/Help/release/dev/cpack-deb-source-date-epoch.rst new file mode 100644 index 0000000..b276569 --- /dev/null +++ b/Help/release/dev/cpack-deb-source-date-epoch.rst @@ -0,0 +1,6 @@ +cpack-deb-source-date-epoch +--------------------------- + +* The :cpack_gen:`CPack Deb Generator` learned to honor the ``SOURCE_DATE_EPOCH`` + environment variable when packaging files. This is useful for generating + reproducible packages. diff --git a/Source/cmArchiveWrite.cxx b/Source/cmArchiveWrite.cxx index 1dd7734..6031781 100644 --- a/Source/cmArchiveWrite.cxx +++ b/Source/cmArchiveWrite.cxx @@ -10,6 +10,7 @@ #include "cmsys/Encoding.hxx" #include "cmsys/FStream.hxx" #include +#include #include #include @@ -94,13 +95,25 @@ cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c, return; } break; - case CompressGZip: + case CompressGZip: { if (archive_write_add_filter_gzip(this->Archive) != ARCHIVE_OK) { this->Error = "archive_write_add_filter_gzip: "; this->Error += cm_archive_error_string(this->Archive); return; } - break; + std::string source_date_epoch; + cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); + if (!source_date_epoch.empty()) { + // We're not able to specify an arbitrary timestamp for gzip. + // The next best thing is to omit the timestamp entirely. + if (archive_write_set_filter_option(this->Archive, "gzip", "timestamp", + nullptr) != ARCHIVE_OK) { + this->Error = "archive_write_set_filter_option: "; + this->Error += cm_archive_error_string(this->Archive); + return; + } + } + } break; case CompressBZip2: if (archive_write_add_filter_bzip2(this->Archive) != ARCHIVE_OK) { this->Error = "archive_write_add_filter_bzip2: "; @@ -243,6 +256,17 @@ bool cmArchiveWrite::AddFile(const char* file, size_t skip, const char* prefix) return false; } archive_entry_set_mtime(e, t, 0); + } else { + std::string source_date_epoch; + cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); + if (!source_date_epoch.empty()) { + std::istringstream iss(source_date_epoch); + time_t epochTime; + iss >> epochTime; + if (iss.eof() && !iss.fail()) { + archive_entry_set_mtime(e, epochTime, 0); + } + } } // manages the uid/guid of the entry (if any) diff --git a/Tests/RunCMake/CPack/CPackTestHelpers.cmake b/Tests/RunCMake/CPack/CPackTestHelpers.cmake index 8c9f4b4..d00ef3b 100644 --- a/Tests/RunCMake/CPack/CPackTestHelpers.cmake +++ b/Tests/RunCMake/CPack/CPackTestHelpers.cmake @@ -78,8 +78,12 @@ function(run_cpack_test_common_ TEST_NAME types build SUBTEST_SUFFIX source PACK endif() # execute cpack + set(SETENV) + if(ENVIRONMENT) + set(SETENV ${CMAKE_COMMAND} -E env "${ENVIRONMENT}") + endif() execute_process( - COMMAND ${cpack_command_} + COMMAND ${SETENV} ${cpack_command_} WORKING_DIRECTORY "${RunCMake_TEST_BINARY_DIR}" RESULT_VARIABLE "result_" OUTPUT_FILE "${RunCMake_TEST_BINARY_DIR}/test_output.txt" diff --git a/Tests/RunCMake/CPack/DEB/Helpers.cmake b/Tests/RunCMake/CPack/DEB/Helpers.cmake index f7c5c84..e01f81d 100644 --- a/Tests/RunCMake/CPack/DEB/Helpers.cmake +++ b/Tests/RunCMake/CPack/DEB/Helpers.cmake @@ -1,7 +1,7 @@ set(ALL_FILES_GLOB "*.deb") function(getPackageContent FILE RESULT_VAR) - execute_process(COMMAND ${DPKG_EXECUTABLE} -c "${FILE}" + execute_process(COMMAND ${CMAKE_COMMAND} -E env TZ=Etc/UTC ${DPKG_EXECUTABLE} -c "${FILE}" OUTPUT_VARIABLE package_content_ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/Tests/RunCMake/CPack/RunCMakeTest.cmake b/Tests/RunCMake/CPack/RunCMakeTest.cmake index 33ffa14..b273c1e 100644 --- a/Tests/RunCMake/CPack/RunCMakeTest.cmake +++ b/Tests/RunCMake/CPack/RunCMakeTest.cmake @@ -28,6 +28,9 @@ run_cpack_test(EXTRA_SLASH_IN_PATH "RPM" true "COMPONENT") run_cpack_source_test(SOURCE_PACKAGE "RPM") run_cpack_test(SUGGESTS "RPM" false "MONOLITHIC") run_cpack_test(SYMLINKS "RPM;TGZ" false "MONOLITHIC;COMPONENT") +set(ENVIRONMENT "SOURCE_DATE_EPOCH=123456789") +run_cpack_test(TIMESTAMPS "DEB;TGZ" false "COMPONENT") +unset(ENVIRONMENT) run_cpack_test(USER_FILELIST "RPM" false "MONOLITHIC") run_cpack_test(MD5SUMS "DEB" false "MONOLITHIC;COMPONENT") run_cpack_test(CPACK_INSTALL_SCRIPT "ZIP" false "MONOLITHIC") diff --git a/Tests/RunCMake/CPack/tests/TIMESTAMPS/ExpectedFiles.cmake b/Tests/RunCMake/CPack/tests/TIMESTAMPS/ExpectedFiles.cmake new file mode 100644 index 0000000..d1a3a5f --- /dev/null +++ b/Tests/RunCMake/CPack/tests/TIMESTAMPS/ExpectedFiles.cmake @@ -0,0 +1,2 @@ +set(EXPECTED_FILES_COUNT "1") +set(EXPECTED_FILE_CONTENT_1_LIST "/foo;/foo/CMakeLists.txt") diff --git a/Tests/RunCMake/CPack/tests/TIMESTAMPS/VerifyResult.cmake b/Tests/RunCMake/CPack/tests/TIMESTAMPS/VerifyResult.cmake new file mode 100644 index 0000000..e7e2645 --- /dev/null +++ b/Tests/RunCMake/CPack/tests/TIMESTAMPS/VerifyResult.cmake @@ -0,0 +1,58 @@ +macro(getFileMetadata_ FILE RESULT_VAR) + if(GENERATOR_TYPE STREQUAL "TGZ") + # getPackageContent defined for archives omit the metadata (non-verbose) + execute_process(COMMAND ${CMAKE_COMMAND} -E env TZ=Etc/UTC ${CMAKE_COMMAND} -E tar -xtvf ${FILE} + OUTPUT_VARIABLE ${RESULT_VAR} + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + getPackageContent("${FILE}" ${RESULT_VAR}) + endif() +endmacro() + +function(checkContentTimestamp FILE REGEX) + getFileMetadata_("${FILE}" METADATA_) + + if(NOT METADATA_ MATCHES "${REGEX}") + string(REPLACE "\n" "\n " metadata_indented "${METADATA_}") + message(FATAL_ERROR + "Wrong timestamps in file:\n" + " ${FILE}\n" + "Expected timestamps to match:\n" + " ${REGEX}\n" + "Actual timestamps:\n" + " ${metadata_indented}") + endif() +endfunction() + +function(checkTimestamp FILE_NAME) + file(READ ${FILE_NAME} ACTUAL_TIMESTAMP OFFSET 4 LIMIT 4 HEX) + + if(NOT ACTUAL_TIMESTAMP STREQUAL "00000000") + message(FATAL_ERROR "${FILE_NAME} contains a timestamp [0x${ACTUAL_TIMESTAMP}]") + endif() +endfunction() + +# Expected timestamp is UNIX time 123456789 +if(GENERATOR_TYPE STREQUAL "TGZ") + set(EXPECTED_TIMESTAMP "29 Nov +1973") + set(EXPECTED_FILES foo/ foo/CMakeLists.txt) +else() + set(EXPECTED_TIMESTAMP "1973-11-29 21:33") + set(EXPECTED_FILES ./usr/ ./usr/foo/ ./usr/foo/CMakeLists.txt) +endif() + +set(EXPECTED_METADATA) +foreach(FILE ${EXPECTED_FILES}) + list(APPEND EXPECTED_METADATA ".* ${EXPECTED_TIMESTAMP} ${FILE}") +endforeach() +list(JOIN EXPECTED_METADATA ".*" EXPECTED_REGEX) +checkContentTimestamp("${FOUND_FILE_1}" "${EXPECTED_REGEX}") + +if(GENERATOR_TYPE STREQUAL "TGZ") + checkTimestamp("${FOUND_FILE_1}") +else() + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${FOUND_FILE_1}") + checkTimestamp("data.tar.gz") + checkTimestamp("control.tar.gz") +endif() diff --git a/Tests/RunCMake/CPack/tests/TIMESTAMPS/test.cmake b/Tests/RunCMake/CPack/tests/TIMESTAMPS/test.cmake new file mode 100644 index 0000000..a193852 --- /dev/null +++ b/Tests/RunCMake/CPack/tests/TIMESTAMPS/test.cmake @@ -0,0 +1,3 @@ +install(FILES CMakeLists.txt DESTINATION foo COMPONENT test) + +set(CPACK_COMPONENTS_ALL test) -- cgit v0.12