Просмотр исходного кода

file(ARCHIVE_CREATE): Add option to control compression level

Fixes: #21125
Asit Dhal 5 лет назад
Родитель
Сommit
195d14e781
23 измененных файлов с 284 добавлено и 16 удалено
  1. 5 0
      Help/command/file.rst
  2. 5 0
      Help/release/dev/file-ARCHIVE-compression-level.rst
  3. 36 1
      Source/cmArchiveWrite.cxx
  4. 1 1
      Source/cmArchiveWrite.h
  5. 41 11
      Source/cmFileCommand.cxx
  6. 3 2
      Source/cmSystemTools.cxx
  7. 2 1
      Source/cmSystemTools.h
  8. 8 0
      Tests/RunCMake/File_Archive/RunCMakeTest.cmake
  9. 1 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-1-result.txt
  10. 5 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-1-stderr.txt
  11. 8 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-1.cmake
  12. 1 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-2-result.txt
  13. 5 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-2-stderr.txt
  14. 8 0
      Tests/RunCMake/File_Archive/argument-validation-compression-level-2.cmake
  15. 85 0
      Tests/RunCMake/File_Archive/compression-level.cmake
  16. 10 0
      Tests/RunCMake/File_Archive/gnutar-gz-compression-level.cmake
  17. 10 0
      Tests/RunCMake/File_Archive/pax-xz-compression-level.cmake
  18. 10 0
      Tests/RunCMake/File_Archive/pax-zstd-compression-level.cmake
  19. 10 0
      Tests/RunCMake/File_Archive/paxr-bz2-compression-level.cmake
  20. 17 0
      Tests/RunCMake/File_Archive/roundtrip.cmake
  21. 1 0
      Tests/RunCMake/File_Archive/unsupported-compression-level-result.txt
  22. 5 0
      Tests/RunCMake/File_Archive/unsupported-compression-level-stderr.txt
  23. 7 0
      Tests/RunCMake/File_Archive/unsupported-compression-level.cmake

+ 5 - 0
Help/command/file.rst

@@ -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 - 0
Help/release/dev/file-ARCHIVE-compression-level.rst

@@ -0,0 +1,5 @@
+file-ARCHIVE-compression-level
+------------------------------
+
+* The :command:`file(ARCHIVE_CREATE)` command gained a ``COMPRESSION_LEVEL``
+  option to specify the compression level.

+ 36 - 1
Source/cmArchiveWrite.cxx

@@ -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: ",

+ 1 - 1
Source/cmArchiveWrite.h

@@ -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();
 

+ 41 - 11
Source/cmFileCommand.cxx

@@ -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;

+ 3 - 2
Source/cmSystemTools.cxx

@@ -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);

+ 2 - 1
Source/cmSystemTools.h

@@ -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

+ 8 - 0
Tests/RunCMake/File_Archive/RunCMakeTest.cmake

@@ -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)

+ 1 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-1-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-1-stderr.txt

@@ -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\)

+ 8 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-1.cmake

@@ -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")

+ 1 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-2-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-2-stderr.txt

@@ -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\)

+ 8 - 0
Tests/RunCMake/File_Archive/argument-validation-compression-level-2.cmake

@@ -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 - 0
Tests/RunCMake/File_Archive/compression-level.cmake

@@ -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()

+ 10 - 0
Tests/RunCMake/File_Archive/gnutar-gz-compression-level.cmake

@@ -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 - 0
Tests/RunCMake/File_Archive/pax-xz-compression-level.cmake

@@ -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 - 0
Tests/RunCMake/File_Archive/pax-zstd-compression-level.cmake

@@ -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 - 0
Tests/RunCMake/File_Archive/paxr-bz2-compression-level.cmake

@@ -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")

+ 17 - 0
Tests/RunCMake/File_Archive/roundtrip.cmake

@@ -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()

+ 1 - 0
Tests/RunCMake/File_Archive/unsupported-compression-level-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Archive/unsupported-compression-level-stderr.txt

@@ -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\)

+ 7 - 0
Tests/RunCMake/File_Archive/unsupported-compression-level.cmake

@@ -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")