diff --git a/Help/release/dev/cpack.hash_computing.rst b/Help/release/dev/cpack.hash_computing.rst new file mode 100644 index 0000000000..9780bb286f --- /dev/null +++ b/Help/release/dev/cpack.hash_computing.rst @@ -0,0 +1,5 @@ +cpack.hash_computing +-------------------- + +* CPack gained a new :variable:`CPACK_PACKAGE_CHECKSUM` variable to + enable generation of a checksum file for each package file. diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake index 675b38ba65..70ae7f72ac 100644 --- a/Modules/CPack.cmake +++ b/Modules/CPack.cmake @@ -116,6 +116,15 @@ # A branding image that will be displayed inside the installer (used by GUI # installers). # +# .. variable:: CPACK_PACKAGE_CHECKSUM +# +# An algorithm that will be used to generate additional file with checksum +# of the package. Output file name will be:: +# +# ${CPACK_PACKAGE_FILE_NAME}.${CPACK_PACKAGE_CHECKSUM} +# +# Current supported alogorithms: MD5|SHA1|SHA224|SHA256|SHA384|SHA512. +# # .. variable:: CPACK_PROJECT_CONFIG_FILE # # CPack-time project CPack configuration file. This file included at cpack diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx index d6b58f2c3f..e6aba899e0 100644 --- a/Source/CPack/cmCPackGenerator.cxx +++ b/Source/CPack/cmCPackGenerator.cxx @@ -14,6 +14,7 @@ #include "cmCPackComponentGroup.h" #include "cmCPackLog.h" +#include "cmCryptoHash.h" #include "cmGeneratedFileStream.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" @@ -163,6 +164,14 @@ int cmCPackGenerator::PrepareNames() << std::endl); return 0; } + const char* algoSignature = this->GetOption("CPACK_PACKAGE_CHECKSUM"); + if (algoSignature) { + if (cmCryptoHash::New(algoSignature).get() == CM_NULLPTR) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot recognize algorithm: " + << algoSignature << std::endl); + return 0; + } + } this->SetOptionIfNotSet("CPACK_REMOVE_TOPLEVEL_DIRECTORY", "1"); @@ -980,6 +989,10 @@ int cmCPackGenerator::DoPackage() return 0; } + /* Prepare checksum algorithm*/ + const char* algo = this->GetOption("CPACK_PACKAGE_CHECKSUM"); + CM_AUTO_PTR crypto = cmCryptoHash::New(algo ? algo : ""); + /* * Copy the generated packages to final destination * - there may be several of them @@ -992,8 +1005,9 @@ int cmCPackGenerator::DoPackage() /* now copy package one by one */ for (it = packageFileNames.begin(); it != packageFileNames.end(); ++it) { std::string tmpPF(this->GetOption("CPACK_OUTPUT_FILE_PREFIX")); + std::string filename(cmSystemTools::GetFilenameName(*it)); tempPackageFileName = it->c_str(); - tmpPF += "/" + cmSystemTools::GetFilenameName(*it); + tmpPF += "/" + filename; const char* packageFileName = tmpPF.c_str(); cmCPackLogger(cmCPackLog::LOG_DEBUG, "Copy final package(s): " << (tempPackageFileName ? tempPackageFileName : "(NULL)") @@ -1009,6 +1023,23 @@ int cmCPackGenerator::DoPackage() } cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- package: " << packageFileName << " generated." << std::endl); + + /* Generate checksum file */ + if (crypto.get() != CM_NULLPTR) { + std::string hashFile(this->GetOption("CPACK_OUTPUT_FILE_PREFIX")); + hashFile += + "/" + filename.substr(0, filename.rfind(this->GetOutputExtension())); + hashFile += "." + cmSystemTools::LowerCase(algo); + cmsys::ofstream outF(hashFile.c_str()); + if (!outF) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot create checksum file: " + << hashFile << std::endl); + return 0; + } + outF << crypto->HashFile(packageFileName) << " " << filename << "\n"; + cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- checksum file: " + << hashFile << " generated." << std::endl); + } } return 1; diff --git a/Tests/RunCMake/CPack/CMakeLists.txt b/Tests/RunCMake/CPack/CMakeLists.txt index 46f13676be..e42e9710e7 100644 --- a/Tests/RunCMake/CPack/CMakeLists.txt +++ b/Tests/RunCMake/CPack/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(${RunCMake_TEST} CXX) -include(${RunCMake_TEST}.cmake) +include(${RunCMake_TEST_FILE_PREFIX}.cmake) # include test generator specifics -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${GENERATOR_TYPE}/${RunCMake_TEST}-specifics.cmake") - include("${GENERATOR_TYPE}/${RunCMake_TEST}-specifics.cmake") +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${GENERATOR_TYPE}/${RunCMake_TEST_FILE_PREFIX}-specifics.cmake") + include("${GENERATOR_TYPE}/${RunCMake_TEST_FILE_PREFIX}-specifics.cmake") endif() set(CPACK_GENERATOR "${GENERATOR_TYPE}") diff --git a/Tests/RunCMake/CPack/CPackTestHelpers.cmake b/Tests/RunCMake/CPack/CPackTestHelpers.cmake index 7bf42f9526..5a35acdc39 100644 --- a/Tests/RunCMake/CPack/CPackTestHelpers.cmake +++ b/Tests/RunCMake/CPack/CPackTestHelpers.cmake @@ -1,9 +1,15 @@ cmake_policy(SET CMP0057 NEW) -function(run_cpack_test TEST_NAME types build) +function(run_cpack_test_common_ TEST_NAME types build SUBTEST_SUFFIX) if(TEST_TYPE IN_LIST types) set(RunCMake_TEST_NO_CLEAN TRUE) set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${TEST_NAME}-build") + set(full_test_name_ "${TEST_NAME}") + + if(SUBTEST_SUFFIX) + set(RunCMake_TEST_BINARY_DIR "${RunCMake_TEST_BINARY_DIR}-${SUBTEST_SUFFIX}-subtest") + set(full_test_name_ "${full_test_name_}-${SUBTEST_SUFFIX}-subtest") + endif() # TODO this should be executed only once per ctest run (not per generator) file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") @@ -24,12 +30,14 @@ function(run_cpack_test TEST_NAME types build) endif() # execute cmake - set(RunCMake_TEST_OPTIONS "-DGENERATOR_TYPE=${TEST_TYPE}") - run_cmake(${TEST_NAME}) + set(RunCMake_TEST_OPTIONS "-DGENERATOR_TYPE=${TEST_TYPE}" + "-DRunCMake_TEST_FILE_PREFIX=${TEST_NAME}" + "-DRunCMake_SUBTEST_SUFFIX=${SUBTEST_SUFFIX}") + run_cmake(${full_test_name_}) # execute optional build step if(build) - run_cmake_command(${TEST_NAME}-Build "${CMAKE_COMMAND}" --build "${RunCMake_TEST_BINARY_DIR}") + run_cmake_command(${full_test_name_}-Build "${CMAKE_COMMAND}" --build "${RunCMake_TEST_BINARY_DIR}") endif() # execute cpack @@ -40,11 +48,21 @@ function(run_cpack_test TEST_NAME types build) ERROR_FILE "${RunCMake_TEST_BINARY_DIR}/test_error.txt" ) + foreach(o out err) + if(SUBTEST_SUFFIX AND EXISTS ${RunCMake_SOURCE_DIR}/${TEST_TYPE}/${TEST_NAME}-${SUBTEST_SUFFIX}-std${o}.txt) + set(RunCMake-std${o}-file "${TEST_TYPE}/${TEST_NAME}-${SUBTEST_SUFFIX}-std${o}.txt") + elseif(EXISTS ${RunCMake_SOURCE_DIR}/${TEST_TYPE}/${TEST_NAME}-std${o}.txt) + set(RunCMake-std${o}-file "${TEST_TYPE}/${TEST_NAME}-std${o}.txt") + endif() + endforeach() + # verify result run_cmake_command( - ${TEST_TYPE}/${TEST_NAME} + ${TEST_TYPE}/${full_test_name_} "${CMAKE_COMMAND}" - -DRunCMake_TEST=${TEST_NAME} + -DRunCMake_TEST=${full_test_name_} + -DRunCMake_TEST_FILE_PREFIX=${TEST_NAME} + -DRunCMake_SUBTEST_SUFFIX=${SUBTEST_SUFFIX} -DGENERATOR_TYPE=${TEST_TYPE} "-Dsrc_dir=${RunCMake_SOURCE_DIR}" "-Dbin_dir=${RunCMake_TEST_BINARY_DIR}" @@ -53,3 +71,13 @@ function(run_cpack_test TEST_NAME types build) ) endif() endfunction() + +function(run_cpack_test TEST_NAME types build) + run_cpack_test_common_("${TEST_NAME}" "${types}" "${build}" "") +endfunction() + +function(run_cpack_test_subtests TEST_NAME SUBTEST_SUFFIXES types build) + foreach(suffix_ IN LISTS SUBTEST_SUFFIXES) + run_cpack_test_common_("${TEST_NAME}" "${types}" "${build}" "${suffix_}") + endforeach() +endfunction() diff --git a/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake b/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake new file mode 100644 index 0000000000..5ca288ca48 --- /dev/null +++ b/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake @@ -0,0 +1,4 @@ +install(FILES CMakeLists.txt DESTINATION foo) + +set(CPACK_PACKAGE_NAME "package_checksum") +set(CPACK_PACKAGE_CHECKSUM ${RunCMake_SUBTEST_SUFFIX}) diff --git a/Tests/RunCMake/CPack/RunCMakeTest.cmake b/Tests/RunCMake/CPack/RunCMakeTest.cmake index a3029cfd8a..abad58b32a 100644 --- a/Tests/RunCMake/CPack/RunCMakeTest.cmake +++ b/Tests/RunCMake/CPack/RunCMakeTest.cmake @@ -18,3 +18,4 @@ run_cpack_test(DEB_GENERATE_SHLIBS "DEB" true) run_cpack_test(DEB_GENERATE_SHLIBS_LDCONFIG "DEB" true) run_cpack_test(DEBUGINFO "RPM" true) run_cpack_test(LONG_FILENAMES "DEB" false) +run_cpack_test_subtests(PACKAGE_CHECKSUM "invalid;MD5;SHA1;SHA224;SHA256;SHA384;SHA512" "TGZ" false) diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake new file mode 100644 index 0000000000..205dcd8d8b --- /dev/null +++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake @@ -0,0 +1,9 @@ +set(whitespaces_ "[\t\n\r ]*") + +set(EXPECTED_FILES_COUNT "0") + +if (NOT ${RunCMake_SUBTEST_SUFFIX} MATCHES "invalid") + set(EXPECTED_FILES_COUNT "1") + set(EXPECTED_FILE_1 "package_checksum*.tar.gz") + set(EXPECTED_FILE_CONTENT_1 "^[^\n]*package_checksum*-[^\n]*/foo/\n[^\n]*package_checksum-[^\n]*/foo/CMakeLists.txt$") +endif() diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake new file mode 100644 index 0000000000..e9e65d6dce --- /dev/null +++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake @@ -0,0 +1,14 @@ +if(NOT ${RunCMake_SUBTEST_SUFFIX} MATCHES "invalid") + string(TOLOWER ${RunCMake_SUBTEST_SUFFIX} EXTENSION) + file(GLOB PACKAGE RELATIVE ${bin_dir} "*.tar.gz") + file(GLOB CSUMFILE RELATIVE ${bin_dir} "*.${EXTENSION}") + file(STRINGS ${CSUMFILE} CHSUM_VALUE) + file(${RunCMake_SUBTEST_SUFFIX} ${PACKAGE} expected_value ) + set(expected_value "${expected_value} ${PACKAGE}") + + if(NOT expected_value STREQUAL CHSUM_VALUE) + message(FATAL_ERROR "Generated checksum is not valid! Expected [${expected_value}] Got [${CHSUM_VALUE}]") + endif() +else() + message(${error}) +endif() diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt new file mode 100644 index 0000000000..abf6d8c886 --- /dev/null +++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt @@ -0,0 +1,2 @@ +^CPack Error: Cannot recognize algorithm: invalid +CPack Error: Error when generating package: package_checksum$ diff --git a/Tests/RunCMake/CPack/VerifyResult.cmake b/Tests/RunCMake/CPack/VerifyResult.cmake index 074890f934..238cbfd30f 100644 --- a/Tests/RunCMake/CPack/VerifyResult.cmake +++ b/Tests/RunCMake/CPack/VerifyResult.cmake @@ -11,7 +11,7 @@ set(output_error_message "\nCPack output: '${output}'\nCPack error: '${error}';\nconfig file: '${config_file_content}'") # check that expected generated files exist and contain expected content -include("${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST}-ExpectedFiles.cmake") +include("${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST_FILE_PREFIX}-ExpectedFiles.cmake") if(NOT EXPECTED_FILES_COUNT EQUAL 0) foreach(file_no_ RANGE 1 ${EXPECTED_FILES_COUNT}) @@ -82,8 +82,8 @@ else() endif() # handle additional result verifications -if(EXISTS "${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST}-VerifyResult.cmake") - include("${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST}-VerifyResult.cmake") +if(EXISTS "${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST_FILE_PREFIX}-VerifyResult.cmake") + include("${src_dir}/${GENERATOR_TYPE}/${RunCMake_TEST_FILE_PREFIX}-VerifyResult.cmake") else() # by default only print out output and error so that they can be compared by # regex