mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
file(ARCHIVE_CREATE): Add PATTERNS_EXCLUDE option
Exclude files and directories matching the given patterns while creating an archive, using libarchive's matcher for parity with file(ARCHIVE_EXTRACT). Fixes: #27877.
This commit is contained in:
@@ -937,6 +937,7 @@ Archiving
|
||||
[MTIME <mtime>]
|
||||
[THREADS <number>]
|
||||
[WORKING_DIRECTORY <dir>]
|
||||
[PATTERNS_EXCLUDE <pattern>...]
|
||||
[VERBOSE])
|
||||
:target: ARCHIVE_CREATE
|
||||
:break: verbatim
|
||||
@@ -1077,6 +1078,17 @@ Archiving
|
||||
this directory. If this option is not provided, the current working
|
||||
directory will be used by default.
|
||||
|
||||
``PATTERNS_EXCLUDE <pattern>...``
|
||||
.. versionadded:: 4.5
|
||||
|
||||
Do not add files or directories that match one of the given patterns.
|
||||
Wildcards are supported. When a directory matches a pattern, it is
|
||||
excluded together with everything beneath it.
|
||||
|
||||
Exclusion patterns are not anchored to the start of an entry's path: a
|
||||
pattern matches if it matches any portion of the path. This is consistent
|
||||
with the ``--exclude`` option of command-line ``tar``.
|
||||
|
||||
``VERBOSE``
|
||||
Enable verbose output from the archive operation.
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
file-ARCHIVE_CREATE-patterns-exclude
|
||||
------------------------------------
|
||||
|
||||
* The :command:`file(ARCHIVE_CREATE)` command gained a ``PATTERNS_EXCLUDE``
|
||||
option to omit files and directories matching the given patterns from the
|
||||
created archive.
|
||||
@@ -406,8 +406,44 @@ bool cmArchiveWrite::Open()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmArchiveWrite::SetExcludePatterns(
|
||||
std::vector<std::string> const& patterns)
|
||||
{
|
||||
if (patterns.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!this->MatchObject) {
|
||||
this->MatchObject = archive_match_new();
|
||||
if (!this->MatchObject) {
|
||||
this->Error = "archive_match_new: out of memory";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-use-anyofallof)
|
||||
for (std::string const& pattern : patterns) {
|
||||
// Reject empty patterns ourselves. libarchive would reject them too, but
|
||||
// only after allocating an error message on the match object that
|
||||
// archive_match_free() does not release.
|
||||
if (pattern.empty()) {
|
||||
this->Error = "exclusion pattern must not be empty";
|
||||
return false;
|
||||
}
|
||||
if (archive_match_exclude_pattern(this->MatchObject, pattern.c_str()) !=
|
||||
ARCHIVE_OK) {
|
||||
this->Error =
|
||||
cmStrCat("Failed to add to exclusion list: ", pattern, " (",
|
||||
cm_archive_error_string(this->MatchObject), ')');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
cmArchiveWrite::~cmArchiveWrite()
|
||||
{
|
||||
if (this->MatchObject) {
|
||||
archive_match_free(this->MatchObject);
|
||||
}
|
||||
archive_read_free(this->Disk);
|
||||
archive_write_free(this->Archive);
|
||||
}
|
||||
@@ -425,6 +461,23 @@ bool cmArchiveWrite::Add(std::string path, size_t skip, char const* prefix,
|
||||
bool cmArchiveWrite::AddPath(std::string const& path, size_t skip,
|
||||
char const* prefix, bool recursive)
|
||||
{
|
||||
// Skip paths whose archive entry name matches an exclusion pattern. For a
|
||||
// directory this also prevents descending into it, pruning the subtree.
|
||||
if (this->MatchObject && skip < path.length()) {
|
||||
cm::string_view out = cm::string_view(path).substr(skip);
|
||||
std::string dest = cmStrCat(prefix ? prefix : "", out);
|
||||
Entry e;
|
||||
cm_archive_entry_copy_pathname(e, dest.c_str());
|
||||
int matched = archive_match_path_excluded(this->MatchObject, e);
|
||||
if (matched < 0) {
|
||||
this->Error = cmStrCat("archive_match_path_excluded: ",
|
||||
cm_archive_error_string(this->MatchObject));
|
||||
return false;
|
||||
}
|
||||
if (matched > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (path != "." || (this->Format != "zip" && this->Format != "7zip")) {
|
||||
if (!this->AddFile(path, skip, prefix)) {
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(CMAKE_BOOTSTRAP)
|
||||
# error "cmArchiveWrite not allowed during bootstrap build!"
|
||||
@@ -143,6 +144,11 @@ public:
|
||||
this->Gname = "";
|
||||
}
|
||||
|
||||
//! Sets exclusion patterns. Any path whose archive entry name matches one
|
||||
//! of the patterns is skipped, and excluded directories are not descended
|
||||
//! into. Returns false and sets the error if a pattern cannot be added.
|
||||
bool SetExcludePatterns(std::vector<std::string> const& patterns);
|
||||
|
||||
private:
|
||||
bool Okay() const { return this->Error.empty(); }
|
||||
bool AddPath(std::string const& path, size_t skip, char const* prefix,
|
||||
@@ -158,6 +164,7 @@ private:
|
||||
std::ostream& Stream;
|
||||
struct archive* Archive;
|
||||
struct archive* Disk;
|
||||
struct archive* MatchObject = nullptr;
|
||||
bool Verbose = false;
|
||||
std::string Format;
|
||||
std::string Error;
|
||||
|
||||
+3
-2
@@ -1338,8 +1338,9 @@ std::string cmCTest::Base64GzipEncodeFile(std::string const& file)
|
||||
std::vector<std::string> files;
|
||||
files.push_back(file);
|
||||
|
||||
if (!cmSystemTools::CreateTar(
|
||||
tarFile, files, {}, cmSystemTools::TarCompressGZip, "UTF-8", false)) {
|
||||
if (!cmSystemTools::CreateTar(tarFile, files, {}, {},
|
||||
cmSystemTools::TarCompressGZip, "UTF-8",
|
||||
false)) {
|
||||
cmCTestLog(this, ERROR_MESSAGE,
|
||||
"Error creating tar while "
|
||||
"encoding file: "
|
||||
|
||||
@@ -3720,6 +3720,7 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
bool Verbose = false;
|
||||
// "PATHS" requires at least one value, but use a custom check below.
|
||||
ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths;
|
||||
ArgumentParser::MaybeEmpty<std::vector<std::string>> PatternsExclude;
|
||||
};
|
||||
|
||||
static auto const parser =
|
||||
@@ -3733,7 +3734,8 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
.Bind("THREADS"_s, &Arguments::Threads)
|
||||
.Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
|
||||
.Bind("VERBOSE"_s, &Arguments::Verbose)
|
||||
.Bind("PATHS"_s, &Arguments::Paths);
|
||||
.Bind("PATHS"_s, &Arguments::Paths)
|
||||
.Bind("PATTERNS_EXCLUDE"_s, &Arguments::PatternsExclude);
|
||||
|
||||
std::vector<std::string> unrecognizedArguments;
|
||||
auto parsedArgs =
|
||||
@@ -3866,9 +3868,10 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
|
||||
}
|
||||
|
||||
if (!cmSystemTools::CreateTar(
|
||||
parsedArgs.Output, parsedArgs.Paths, parsedArgs.WorkingDirectory,
|
||||
compress, parsedArgs.Encoding, parsedArgs.Verbose, parsedArgs.MTime,
|
||||
parsedArgs.Format, compressionLevel, threads)) {
|
||||
parsedArgs.Output, parsedArgs.Paths, parsedArgs.PatternsExclude,
|
||||
parsedArgs.WorkingDirectory, compress, parsedArgs.Encoding,
|
||||
parsedArgs.Verbose, parsedArgs.MTime, parsedArgs.Format,
|
||||
compressionLevel, threads)) {
|
||||
status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return false;
|
||||
|
||||
@@ -2238,6 +2238,7 @@ bool cmSystemTools::IsPathToMacOSSharedLibrary(std::string const& path)
|
||||
|
||||
bool cmSystemTools::CreateTar(
|
||||
std::string const& arFileName, std::vector<std::string> const& files,
|
||||
std::vector<std::string> const& excludeFiles,
|
||||
std::string const& workingDirectory, cmTarCompression compressType,
|
||||
std::string const& encoding, bool verbose, std::string const& mtime,
|
||||
std::string const& format, int compressionLevel, int numThreads)
|
||||
@@ -2300,6 +2301,10 @@ bool cmSystemTools::CreateTar(
|
||||
}
|
||||
a.SetMTime(mtime);
|
||||
a.SetVerbose(verbose);
|
||||
if (!a.SetExcludePatterns(excludeFiles)) {
|
||||
cmSystemTools::Error(a.GetError());
|
||||
return false;
|
||||
}
|
||||
bool tarCreatedSuccessfully = true;
|
||||
for (auto path : files) {
|
||||
if (cmSystemTools::FileIsFullPath(path)) {
|
||||
@@ -2315,6 +2320,7 @@ bool cmSystemTools::CreateTar(
|
||||
#else
|
||||
(void)arFileName;
|
||||
(void)files;
|
||||
(void)excludeFiles;
|
||||
(void)encoding;
|
||||
(void)verbose;
|
||||
return false;
|
||||
|
||||
@@ -527,6 +527,7 @@ public:
|
||||
std::string const& encoding, bool verbose);
|
||||
static bool CreateTar(std::string const& arFileName,
|
||||
std::vector<std::string> const& files,
|
||||
std::vector<std::string> const& excludeFiles,
|
||||
std::string const& workingDirectory,
|
||||
cmTarCompression compressType,
|
||||
std::string const& encoding, bool verbose,
|
||||
|
||||
+3
-3
@@ -2129,9 +2129,9 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
|
||||
if (files.empty()) {
|
||||
std::cerr << "tar: No files or directories specified\n";
|
||||
}
|
||||
if (!cmSystemTools::CreateTar(outFile, files, {}, compress, encoding,
|
||||
verbose, mtime, format, compressionLevel,
|
||||
numThreads)) {
|
||||
if (!cmSystemTools::CreateTar(outFile, files, {}, {}, compress,
|
||||
encoding, verbose, mtime, format,
|
||||
compressionLevel, numThreads)) {
|
||||
cmSystemTools::Error(cmStrCat("Problem creating tar:\n ", outFile));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,10 @@ run_cmake(zip-filtered)
|
||||
run_cmake(zip-filtered-exclude)
|
||||
run_cmake(zip-filtered-exclude-precedence)
|
||||
|
||||
# Excluding selected files or directories from creation
|
||||
run_cmake(create-filtered-exclude)
|
||||
run_cmake(create-empty-pattern-exclude)
|
||||
|
||||
run_cmake(create-missing-args)
|
||||
run_cmake(extract-missing-args)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,3 @@
|
||||
^CMake Error: exclusion pattern must not be empty
|
||||
CMake Error at create-empty-pattern-exclude\.cmake:[0-9]+ \(file\):
|
||||
file failed to compress:
|
||||
@@ -0,0 +1,8 @@
|
||||
set(COMPRESS_DIR ${CMAKE_CURRENT_BINARY_DIR}/compress_dir)
|
||||
file(WRITE ${COMPRESS_DIR}/f1.txt "f1")
|
||||
|
||||
file(ARCHIVE_CREATE
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/out.tar
|
||||
FORMAT gnutar
|
||||
PATHS ${COMPRESS_DIR}
|
||||
PATTERNS_EXCLUDE "*.o" "") # empty pattern is rejected
|
||||
@@ -0,0 +1,30 @@
|
||||
set(OUTPUT_NAME "test.zip")
|
||||
|
||||
set(ARCHIVE_FORMAT zip)
|
||||
|
||||
# Exclude entries matching PATTERNS_EXCLUDE while *creating* the archive.
|
||||
set(COMPRESSION_OPTIONS
|
||||
PATTERNS_EXCLUDE
|
||||
"compress_dir/d 2/*" # exclude everything under a directory
|
||||
"d-4" # exclude by unanchored name (dir + contents)
|
||||
"no_such_entry" # matches nothing: must not be an error
|
||||
)
|
||||
|
||||
# Everything that was not excluded must still be packed and extracted.
|
||||
set(CUSTOM_CHECK_FILES
|
||||
"f1.txt"
|
||||
"d1/f1.txt"
|
||||
"d + 3/f1.txt"
|
||||
"d_4/f1.txt" # underscore, not the excluded "d-4"
|
||||
"My Special Directory/f1.txt"
|
||||
)
|
||||
|
||||
# The excluded entries must not be present in the created archive.
|
||||
set(NOT_EXISTING_FILES_CHECK
|
||||
"d 2/f1.txt"
|
||||
"d-4/f1.txt"
|
||||
)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
|
||||
|
||||
check_magic("504b0304" LIMIT 4 HEX)
|
||||
@@ -65,6 +65,7 @@ file(ARCHIVE_CREATE
|
||||
COMPRESSION "${COMPRESSION_TYPE}"
|
||||
WORKING_DIRECTORY "${WORKING_DIRECTORY}"
|
||||
${ENCODING_OPTIONS}
|
||||
${COMPRESSION_OPTIONS}
|
||||
VERBOSE
|
||||
PATHS ${FULL_COMPRESS_DIR})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user