mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-07 14:20:06 -06:00
file(ARCHIVE_CREATE): Add option to control compression level
Fixes: #21125
This commit is contained in:
@@ -987,6 +987,7 @@ Archiving
|
||||
PATHS <paths>...
|
||||
[FORMAT <format>]
|
||||
[COMPRESSION <compression>]
|
||||
[COMPRESSION_LEVEL <compression level>]
|
||||
[MTIME <mtime>]
|
||||
[VERBOSE])
|
||||
|
||||
@@ -1004,6 +1005,10 @@ compression. The other formats use no compression by default, but can be
|
||||
directed to do so with the ``COMPRESSION`` option. Valid values for
|
||||
``<compression>`` are ``None``, ``BZip2``, ``GZip``, ``XZ``, and ``Zstd``.
|
||||
|
||||
Compression level can be specied by using ``COMPRESSION_LEVEL`` option.
|
||||
Compression level should be between 0-9. 0 is the default compression.
|
||||
``COMPRESSION`` option must be specified for ``COMPRESSION_LEVEL``.
|
||||
|
||||
.. note::
|
||||
With ``FORMAT`` set to ``raw`` only one file will be compressed with the
|
||||
compression type specified by ``COMPRESSION``.
|
||||
|
||||
5
Help/release/dev/file-ARCHIVE-compression-level.rst
Normal file
5
Help/release/dev/file-ARCHIVE-compression-level.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
file-ARCHIVE-compression-level
|
||||
------------------------------
|
||||
|
||||
* The :command:`file(ARCHIVE_CREATE)` command gained a ``COMPRESSION_LEVEL``
|
||||
option to specify the compression level.
|
||||
@@ -81,7 +81,7 @@ struct cmArchiveWrite::Callback
|
||||
};
|
||||
|
||||
cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c,
|
||||
std::string const& format)
|
||||
std::string const& format, int compressionLevel)
|
||||
: Stream(os)
|
||||
, Archive(archive_write_new())
|
||||
, Disk(archive_read_disk_new())
|
||||
@@ -151,6 +151,41 @@ cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c,
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (compressionLevel != 0) {
|
||||
std::string compressionLevelStr = std::to_string(compressionLevel);
|
||||
std::string archiveFilterName;
|
||||
switch (c) {
|
||||
case CompressNone:
|
||||
case CompressCompress:
|
||||
break;
|
||||
case CompressGZip:
|
||||
archiveFilterName = "gzip";
|
||||
break;
|
||||
case CompressBZip2:
|
||||
archiveFilterName = "bzip2";
|
||||
break;
|
||||
case CompressLZMA:
|
||||
archiveFilterName = "lzma";
|
||||
break;
|
||||
case CompressXZ:
|
||||
archiveFilterName = "xz";
|
||||
break;
|
||||
case CompressZstd:
|
||||
archiveFilterName = "zstd";
|
||||
break;
|
||||
}
|
||||
if (!archiveFilterName.empty()) {
|
||||
if (archive_write_set_filter_option(
|
||||
this->Archive, archiveFilterName.c_str(), "compression-level",
|
||||
compressionLevelStr.c_str()) != ARCHIVE_OK) {
|
||||
this->Error = cmStrCat("archive_write_set_filter_option: ",
|
||||
cm_archive_error_string(this->Archive));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) || defined(__CYGWIN__)
|
||||
if (archive_read_disk_set_standard_lookup(this->Disk) != ARCHIVE_OK) {
|
||||
this->Error = cmStrCat("archive_read_disk_set_standard_lookup: ",
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
|
||||
/** Construct with output stream to which to write archive. */
|
||||
cmArchiveWrite(std::ostream& os, Compress c = CompressNone,
|
||||
std::string const& format = "paxr");
|
||||
std::string const& format = "paxr", int compressionLevel = 0);
|
||||
|
||||
~cmArchiveWrite();
|
||||
|
||||
|
||||
@@ -3023,18 +3023,21 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
std::string Output;
|
||||
std::string Format;
|
||||
std::string Compression;
|
||||
std::string CompressionLevel;
|
||||
std::string MTime;
|
||||
bool Verbose = false;
|
||||
std::vector<std::string> Paths;
|
||||
};
|
||||
|
||||
static auto const parser = cmArgumentParser<Arguments>{}
|
||||
.Bind("OUTPUT"_s, &Arguments::Output)
|
||||
.Bind("FORMAT"_s, &Arguments::Format)
|
||||
.Bind("COMPRESSION"_s, &Arguments::Compression)
|
||||
.Bind("MTIME"_s, &Arguments::MTime)
|
||||
.Bind("VERBOSE"_s, &Arguments::Verbose)
|
||||
.Bind("PATHS"_s, &Arguments::Paths);
|
||||
static auto const parser =
|
||||
cmArgumentParser<Arguments>{}
|
||||
.Bind("OUTPUT"_s, &Arguments::Output)
|
||||
.Bind("FORMAT"_s, &Arguments::Format)
|
||||
.Bind("COMPRESSION"_s, &Arguments::Compression)
|
||||
.Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
|
||||
.Bind("MTIME"_s, &Arguments::MTime)
|
||||
.Bind("VERBOSE"_s, &Arguments::Verbose)
|
||||
.Bind("PATHS"_s, &Arguments::Paths);
|
||||
|
||||
std::vector<std::string> unrecognizedArguments;
|
||||
std::vector<std::string> keywordsMissingValues;
|
||||
@@ -3048,9 +3051,9 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<std::string> LIST_ARGS = { "OUTPUT", "FORMAT",
|
||||
"COMPRESSION", "MTIME",
|
||||
"PATHS" };
|
||||
const std::vector<std::string> LIST_ARGS = {
|
||||
"OUTPUT", "FORMAT", "COMPRESSION", "COMPRESSION_LEVEL", "MTIME", "PATHS"
|
||||
};
|
||||
auto kwbegin = keywordsMissingValues.cbegin();
|
||||
auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
|
||||
if (kwend != kwbegin) {
|
||||
@@ -3099,6 +3102,33 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
return false;
|
||||
}
|
||||
|
||||
int compressionLevel = 0;
|
||||
if (!parsedArgs.CompressionLevel.empty()) {
|
||||
if (parsedArgs.CompressionLevel.size() != 1 &&
|
||||
!std::isdigit(parsedArgs.CompressionLevel[0])) {
|
||||
status.SetError(cmStrCat("compression level ",
|
||||
parsedArgs.CompressionLevel,
|
||||
" should be in range 0 to 9"));
|
||||
cmSystemTools::SetFatalErrorOccured();
|
||||
return false;
|
||||
}
|
||||
compressionLevel = std::stoi(parsedArgs.CompressionLevel);
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
status.SetError(cmStrCat("compression level ",
|
||||
parsedArgs.CompressionLevel,
|
||||
" should be in range 0 to 9"));
|
||||
cmSystemTools::SetFatalErrorOccured();
|
||||
return false;
|
||||
}
|
||||
if (compress == cmSystemTools::TarCompressNone) {
|
||||
status.SetError(cmStrCat("compression level is not supported for "
|
||||
"compression \"None\"",
|
||||
parsedArgs.Compression));
|
||||
cmSystemTools::SetFatalErrorOccured();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedArgs.Paths.empty()) {
|
||||
status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
|
||||
cmSystemTools::SetFatalErrorOccured();
|
||||
@@ -3107,7 +3137,7 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
|
||||
if (!cmSystemTools::CreateTar(parsedArgs.Output, parsedArgs.Paths, compress,
|
||||
parsedArgs.Verbose, parsedArgs.MTime,
|
||||
parsedArgs.Format)) {
|
||||
parsedArgs.Format, compressionLevel)) {
|
||||
status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
|
||||
cmSystemTools::SetFatalErrorOccured();
|
||||
return false;
|
||||
|
||||
@@ -1442,7 +1442,7 @@ bool cmSystemTools::CreateTar(const std::string& outFileName,
|
||||
const std::vector<std::string>& files,
|
||||
cmTarCompression compressType, bool verbose,
|
||||
std::string const& mtime,
|
||||
std::string const& format)
|
||||
std::string const& format, int compressionLevel)
|
||||
{
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
|
||||
@@ -1472,7 +1472,8 @@ bool cmSystemTools::CreateTar(const std::string& outFileName,
|
||||
break;
|
||||
}
|
||||
|
||||
cmArchiveWrite a(fout, compress, format.empty() ? "paxr" : format);
|
||||
cmArchiveWrite a(fout, compress, format.empty() ? "paxr" : format,
|
||||
compressionLevel);
|
||||
|
||||
a.Open();
|
||||
a.SetMTime(mtime);
|
||||
|
||||
@@ -362,7 +362,8 @@ public:
|
||||
const std::vector<std::string>& files,
|
||||
cmTarCompression compressType, bool verbose,
|
||||
std::string const& mtime = std::string(),
|
||||
std::string const& format = std::string());
|
||||
std::string const& format = std::string(),
|
||||
int compressionLevel = 0);
|
||||
static bool ExtractTar(const std::string& inFileName,
|
||||
const std::vector<std::string>& files, bool verbose);
|
||||
// This should be called first thing in main
|
||||
|
||||
@@ -16,3 +16,11 @@ run_cmake(zip-filtered)
|
||||
run_cmake(unsupported-format)
|
||||
run_cmake(zip-with-bad-compression)
|
||||
run_cmake(7zip-with-bad-compression)
|
||||
|
||||
run_cmake(unsupported-compression-level)
|
||||
run_cmake(argument-validation-compression-level-1)
|
||||
run_cmake(argument-validation-compression-level-2)
|
||||
run_cmake(gnutar-gz-compression-level)
|
||||
run_cmake(pax-xz-compression-level)
|
||||
run_cmake(pax-zstd-compression-level)
|
||||
run_cmake(paxr-bz2-compression-level)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
CMake Error at compression-level.cmake:39 \(file\):
|
||||
file compression level 100 should be in range 0 to 9
|
||||
Call Stack \(most recent call first\):
|
||||
argument-validation-compression-level-1.cmake:8 \(check_compression_level\)
|
||||
CMakeLists.txt:3 \(include\)
|
||||
@@ -0,0 +1,8 @@
|
||||
set(OUTPUT_NAME "test.tar.gz")
|
||||
|
||||
set(ARCHIVE_FORMAT gnutar)
|
||||
set(COMPRESSION_TYPE GZip)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("100")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
CMake Error at compression-level.cmake:39 \(file\):
|
||||
file compression level high should be in range 0 to 9
|
||||
Call Stack \(most recent call first\):
|
||||
argument-validation-compression-level-2.cmake:8 \(check_compression_level\)
|
||||
CMakeLists.txt:3 \(include\)
|
||||
@@ -0,0 +1,8 @@
|
||||
set(OUTPUT_NAME "test.tar.gz")
|
||||
|
||||
set(ARCHIVE_FORMAT gnutar)
|
||||
set(COMPRESSION_TYPE GZip)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("high")
|
||||
85
Tests/RunCMake/File_Archive/compression-level.cmake
Normal file
85
Tests/RunCMake/File_Archive/compression-level.cmake
Normal file
@@ -0,0 +1,85 @@
|
||||
foreach(parameter OUTPUT_NAME ARCHIVE_FORMAT)
|
||||
if(NOT DEFINED ${parameter})
|
||||
message(FATAL_ERROR "missing required parameter ${parameter}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(COMPRESS_DIR compress_dir)
|
||||
set(FULL_COMPRESS_DIR ${CMAKE_CURRENT_BINARY_DIR}/${COMPRESS_DIR})
|
||||
|
||||
set(DECOMPRESS_DIR decompress_dir)
|
||||
set(FULL_DECOMPRESS_DIR ${CMAKE_CURRENT_BINARY_DIR}/${DECOMPRESS_DIR})
|
||||
|
||||
set(FULL_OUTPUT_NAME ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME})
|
||||
|
||||
set(CHECK_FILES
|
||||
"f1.txt"
|
||||
"d1/f1.txt"
|
||||
"d 2/f1.txt"
|
||||
"d + 3/f1.txt"
|
||||
"d_4/f1.txt"
|
||||
"d-4/f1.txt"
|
||||
"My Special Directory/f1.txt"
|
||||
)
|
||||
|
||||
function(check_compression_level COMPRESSION_LEVEL)
|
||||
foreach(file ${CHECK_FILES})
|
||||
configure_file(${CMAKE_CURRENT_LIST_FILE} ${FULL_COMPRESS_DIR}/${file} COPYONLY)
|
||||
endforeach()
|
||||
|
||||
if(UNIX)
|
||||
execute_process(COMMAND ln -sf f1.txt ${FULL_COMPRESS_DIR}/d1/f2.txt)
|
||||
list(APPEND CHECK_FILES "d1/f2.txt")
|
||||
endif()
|
||||
|
||||
file(REMOVE ${FULL_OUTPUT_NAME})
|
||||
file(REMOVE_RECURSE ${FULL_DECOMPRESS_DIR})
|
||||
file(MAKE_DIRECTORY ${FULL_DECOMPRESS_DIR})
|
||||
|
||||
file(ARCHIVE_CREATE
|
||||
OUTPUT ${FULL_OUTPUT_NAME}
|
||||
FORMAT "${ARCHIVE_FORMAT}"
|
||||
COMPRESSION "${COMPRESSION_TYPE}"
|
||||
COMPRESSION_LEVEL ${COMPRESSION_LEVEL}
|
||||
VERBOSE
|
||||
PATHS ${COMPRESS_DIR})
|
||||
|
||||
file(ARCHIVE_EXTRACT
|
||||
INPUT ${FULL_OUTPUT_NAME}
|
||||
${DECOMPRESSION_OPTIONS}
|
||||
DESTINATION ${FULL_DECOMPRESS_DIR}
|
||||
VERBOSE)
|
||||
|
||||
if(CUSTOM_CHECK_FILES)
|
||||
set(CHECK_FILES ${CUSTOM_CHECK_FILES})
|
||||
endif()
|
||||
|
||||
foreach(file ${CHECK_FILES})
|
||||
set(input ${FULL_COMPRESS_DIR}/${file})
|
||||
set(output ${FULL_DECOMPRESS_DIR}/${COMPRESS_DIR}/${file})
|
||||
|
||||
if(NOT EXISTS ${input})
|
||||
message(SEND_ERROR "Cannot find input file ${output}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS ${output})
|
||||
message(SEND_ERROR "Cannot find output file ${output}")
|
||||
endif()
|
||||
|
||||
file(MD5 ${input} input_md5)
|
||||
file(MD5 ${output} output_md5)
|
||||
|
||||
if(NOT input_md5 STREQUAL output_md5)
|
||||
message(SEND_ERROR "Files \"${input}\" and \"${output}\" are different")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(file ${NOT_EXISTING_FILES_CHECK})
|
||||
set(output ${FULL_DECOMPRESS_DIR}/${COMPRESS_DIR}/${file})
|
||||
|
||||
if(EXISTS ${output})
|
||||
message(SEND_ERROR "File ${output} exists but it shouldn't")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
endfunction()
|
||||
@@ -0,0 +1,10 @@
|
||||
set(OUTPUT_NAME "test.tar.gz")
|
||||
|
||||
set(ARCHIVE_FORMAT gnutar)
|
||||
set(COMPRESSION_TYPE GZip)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("1")
|
||||
check_compression_level("5")
|
||||
check_compression_level("9")
|
||||
10
Tests/RunCMake/File_Archive/pax-xz-compression-level.cmake
Normal file
10
Tests/RunCMake/File_Archive/pax-xz-compression-level.cmake
Normal file
@@ -0,0 +1,10 @@
|
||||
set(OUTPUT_NAME "test.tar.xz")
|
||||
|
||||
set(ARCHIVE_FORMAT pax)
|
||||
set(COMPRESSION_TYPE XZ)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("1")
|
||||
check_compression_level("5")
|
||||
check_compression_level("9")
|
||||
10
Tests/RunCMake/File_Archive/pax-zstd-compression-level.cmake
Normal file
10
Tests/RunCMake/File_Archive/pax-zstd-compression-level.cmake
Normal file
@@ -0,0 +1,10 @@
|
||||
set(OUTPUT_NAME "test.tar.zstd")
|
||||
|
||||
set(ARCHIVE_FORMAT pax)
|
||||
set(COMPRESSION_TYPE Zstd)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("1")
|
||||
check_compression_level("5")
|
||||
check_compression_level("9")
|
||||
10
Tests/RunCMake/File_Archive/paxr-bz2-compression-level.cmake
Normal file
10
Tests/RunCMake/File_Archive/paxr-bz2-compression-level.cmake
Normal file
@@ -0,0 +1,10 @@
|
||||
set(OUTPUT_NAME "test.tar.bz2")
|
||||
|
||||
set(ARCHIVE_FORMAT paxr)
|
||||
set(COMPRESSION_TYPE BZip2)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("1")
|
||||
check_compression_level("5")
|
||||
check_compression_level("9")
|
||||
@@ -90,3 +90,20 @@ function(check_magic EXPECTED)
|
||||
"Actual [${ACTUAL}] does not match expected [${EXPECTED}]")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
function(check_compression_level COMPRESSION_LEVEL)
|
||||
file(ARCHIVE_CREATE
|
||||
OUTPUT "${FULL_OUTPUT_NAME}_compression_level"
|
||||
FORMAT "${ARCHIVE_FORMAT}"
|
||||
COMPRESSION_LEVEL ${COMPRESSION_LEVEL}
|
||||
COMPRESSION "${COMPRESSION_TYPE}"
|
||||
VERBOSE
|
||||
PATHS ${COMPRESS_DIR})
|
||||
|
||||
file(ARCHIVE_EXTRACT
|
||||
INPUT "${FULL_OUTPUT_NAME}_compression_level"
|
||||
${DECOMPRESSION_OPTIONS}
|
||||
DESTINATION ${FULL_DECOMPRESS_DIR}
|
||||
VERBOSE)
|
||||
endfunction()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
CMake Error at compression-level.cmake:39 \(file\):
|
||||
file compression level is not supported for compression "None"
|
||||
Call Stack \(most recent call first\):
|
||||
unsupported-compression-level.cmake:7 \(check_compression_level\)
|
||||
CMakeLists.txt:3 \(include\)
|
||||
@@ -0,0 +1,7 @@
|
||||
set(OUTPUT_NAME "test.7z")
|
||||
|
||||
set(ARCHIVE_FORMAT 7zip)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/compression-level.cmake)
|
||||
|
||||
check_compression_level("1")
|
||||
Reference in New Issue
Block a user