file: Add ARCHIVE_* options to specify pathname encoding

Issue: #26903
This commit is contained in:
AJIOB authored and Brad King committed 2026-03-24 14:07:21 -04:00
1 parent 25af9288f9
commit 97455c3d7e
35 files changed
+369 -6

No files matched your search

+70
View File
@@ -915,6 +915,7 @@ Archiving
[FORMAT <format>]
[COMPRESSION <compression>
[COMPRESSION_LEVEL <compression-level>]]
[ENCODING <encoding>]
[MTIME <mtime>]
[THREADS <number>]
[WORKING_DIRECTORY <dir>]
@@ -1006,6 +1007,39 @@ Archiving
formats too. The ``Zstd`` algorithm compression level can be set
between 0-19, except for ``zip`` format.
``ENCODING <encoding>``
.. versionadded:: 4.4
Specify the pathname character encoding used in the archive.
The ``<encoding>`` may be one of:
``UTF-8``
Archive pathnames are encoded as UTF-8.
In CMake 4.4 and below, this is the default.
See policy :policy:`CMP0213` for CMake 4.3 and below compatibility
details.
``OEM``
On Windows platforms, pathnames are encoded as using the original
equipment manufacturer (OEM) code page. On non-Windows platforms,
pathnames are encoded according to the current locale.
In CMake 4.3 and below, the ``OEM`` encoding (current locale)
was always used.
``UTF-16LE``, ``UTF-16BE``
Archive pathnames are encoded as UTF-16 little-endian or big-endian.
``...``
Any encoding name supported by ``iconv`` on the current platform.
On Windows, code page names may be specified.
.. note::
``7zip`` archives always encode paths as ``UTF-16LE``,
so this option is silently ignored for that format.
``MTIME <mtime>``
Specify the modification time recorded in tarball entries.
@@ -1032,6 +1066,7 @@ Archiving
file(ARCHIVE_EXTRACT
INPUT <archive>
[DESTINATION <dir>]
[ENCODING <encoding>]
[PATTERNS <pattern>...]
[LIST_ONLY]
[VERBOSE]
@@ -1050,6 +1085,41 @@ Archiving
If ``DESTINATION`` is not given, the current binary directory will
be used.
``ENCODING <encoding>``
.. versionadded:: 4.4
Specify the pathname character encoding used in the archive.
The ``<encoding>`` may be one of:
``UTF-8``
Archive pathnames are encoded as UTF-8.
In CMake 4.3 and below, this is the default
if the :policy:`CMP0213` is ``NEW``.
``OEM``
On Windows platforms, pathnames are encoded as using the original
equipment manufacturer (OEM) code page. On non-Windows platforms,
pathnames are encoded according to the current locale.
In CMake 4.3 and below, the ``OEM`` encoding (current locale)
was always used.
In CMake 4.4 and above, the ``OEM`` encoding (current locale)
is only used if the :policy:`CMP0213` is not ``NEW``.
``UTF-16LE``, ``UTF-16BE``
Archive pathnames are encoded as UTF-16 little-endian or big-endian.
``...``
Any encoding name supported by ``iconv`` on the current platform.
On Windows, code page names may be specified.
.. note::
``7zip`` archives always encode paths as ``UTF-16LE``,
so this option is silently ignored for that format.
``PATTERNS <pattern>...``
Extract/list only files and directories that match one of the given
patterns. Wildcards are supported. If the ``PATTERNS`` option is
+1
View File
@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.4
.. toctree::
:maxdepth: 1
CMP0213: file(ARCHIVE_{CREATE,EXTRACT}) encode archive paths as UTF-8 by default. </policy/CMP0213>
CMP0212: add_custom_command DEPENDS does not strip .exe suffixes. </policy/CMP0212>
CMP0211: A file may belong to at most one file set in a target. </policy/CMP0211>
+22
View File
@@ -0,0 +1,22 @@
CMP0213
-------
.. versionadded:: 4.4
:command:`file(ARCHIVE_CREATE)` and :command:`file(ARCHIVE_EXTRACT)`
encode archive paths as UTF-8 by default.
CMake 4.3 and below always used the ``OEM`` (current locale) encoding for archive
paths. CMake 4.4 offer the ``ENCODING`` option, but prefer to use ``UTF-8``
by default. This policy provides compatibility for projects that have not
been updated to specify the encoding or accept the new default.
The ``OLD`` behavior for this policy is to use the current locale as the
default encoding, which is also known as an ``OEM`` encoding.
The ``NEW`` behavior uses ``UTF-8`` as the default encoding.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4
.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst
@@ -0,0 +1,7 @@
file-archive-encoding
---------------------
* The :command:`file(ARCHIVE_CREATE)` and :command:`file(ARCHIVE_EXTRACT)`
commands accepts an ``ENCODING`` option to specify the encoding of the
archive pathnames. The default is now ``UTF-8``.
See policy :policy:`CMP0213`.
+26 -4
View File
@@ -3678,6 +3678,7 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
std::string Format;
std::string Compression;
std::string CompressionLevel;
std::string Encoding;
// "MTIME" should require one value, but it has long been accidentally
// accepted without one and treated as if an empty value were given.
// Fixing this would require a policy.
@@ -3695,6 +3696,7 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
.Bind("FORMAT"_s, &Arguments::Format)
.Bind("COMPRESSION"_s, &Arguments::Compression)
.Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
.Bind("ENCODING"_s, &Arguments::Encoding)
.Bind("MTIME"_s, &Arguments::MTime)
.Bind("THREADS"_s, &Arguments::Threads)
.Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
@@ -3822,9 +3824,18 @@ bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
return false;
}
if (parsedArgs.Encoding.empty()) {
if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) ==
cmPolicies::NEW) {
parsedArgs.Encoding = "UTF-8";
} else {
parsedArgs.Encoding = "OEM";
}
}
if (!cmSystemTools::CreateTar(
parsedArgs.Output, parsedArgs.Paths, parsedArgs.WorkingDirectory,
compress, "OEM", parsedArgs.Verbose, parsedArgs.MTime,
compress, parsedArgs.Encoding, parsedArgs.Verbose, parsedArgs.MTime,
parsedArgs.Format, compressionLevel, threads)) {
status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
cmSystemTools::SetFatalErrorOccurred();
@@ -3840,6 +3851,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
struct Arguments : public ArgumentParser::ParseResult
{
std::string Input;
std::string Encoding;
bool Verbose = false;
bool ListOnly = false;
std::string Destination;
@@ -3849,6 +3861,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
static auto const parser = cmArgumentParser<Arguments>{}
.Bind("INPUT"_s, &Arguments::Input)
.Bind("ENCODING"_s, &Arguments::Encoding)
.Bind("VERBOSE"_s, &Arguments::Verbose)
.Bind("LIST_ONLY"_s, &Arguments::ListOnly)
.Bind("DESTINATION"_s, &Arguments::Destination)
@@ -3872,9 +3885,18 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
std::string inFile = parsedArgs.Input;
if (parsedArgs.Encoding.empty()) {
if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) ==
cmPolicies::NEW) {
parsedArgs.Encoding = "UTF-8";
} else {
parsedArgs.Encoding = "OEM";
}
}
if (parsedArgs.ListOnly) {
if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns, "OEM",
parsedArgs.Verbose)) {
if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
parsedArgs.Encoding, parsedArgs.Verbose)) {
status.SetError(cmStrCat("failed to list: ", inFile));
cmSystemTools::SetFatalErrorOccurred();
return false;
@@ -3911,7 +3933,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
inFile, parsedArgs.Patterns,
parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
: cmSystemTools::cmTarExtractTimestamps::Yes,
"OEM", parsedArgs.Verbose)) {
parsedArgs.Encoding, parsedArgs.Verbose)) {
status.SetError(cmStrCat("failed to extract:\n ", inFile));
cmSystemTools::SetFatalErrorOccurred();
return false;
+5 -1
View File
@@ -635,7 +635,11 @@ class cmMakefile;
WARN) \
SELECT(POLICY, CMP0212, \
"add_custom_command DEPENDS does not strip .exe suffixes.", 4, 4, 0, \
WARN)
WARN) \
SELECT(POLICY, CMP0213, \
"file(ARCHIVE_{CREATE,EXTRACT}) encode archive paths as UTF-8 by " \
"default.", \
4, 4, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
+5 -1
View File
@@ -1016,7 +1016,11 @@ endif()
add_RunCMake_test(LinkLibrariesProcessing)
add_RunCMake_test(LinkLibrariesStrategy)
add_RunCMake_test(LinkFlags -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
add_RunCMake_test(File_Archive -DPython_EXECUTABLE=${Python_EXECUTABLE})
if(DEFINED CMake_TEST_LOCALE_CHARSET)
list(APPEND File_Archive_ARGS -DCMake_TEST_LOCALE_CHARSET=${CMake_TEST_LOCALE_CHARSET})
endif()
list(APPEND File_Archive_ARGS -DPython_EXECUTABLE=${Python_EXECUTABLE})
add_RunCMake_test(File_Archive)
add_RunCMake_test(File_Configure)
add_RunCMake_test(File_Generate)
add_RunCMake_test(ExportWithoutLanguage)
@@ -0,0 +1,10 @@
set(OUTPUT_NAME "test.zip")
cmake_policy(SET CMP0213 NEW)
set(ARCHIVE_FORMAT zip)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error: archive_write_header: Can't translate [Pp]athname( '[^']*\.txt')? to [^
]*
CMake Error at roundtrip\.cmake:[0-9]+ \(file\):
file failed to compress:
@@ -0,0 +1,11 @@
set(OUTPUT_NAME "bad.zip")
cmake_policy(SET CMP0213 OLD)
set(ARCHIVE_FORMAT zip)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1,10 @@
set(OUTPUT_NAME "test.zip")
cmake_policy(SET CMP0213 OLD)
set(ARCHIVE_FORMAT zip)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error: archive_write_header: Can't translate [Pp]athname( '[^']*\.txt')? to [^
]*
CMake Error at roundtrip\.cmake:[0-9]+ \(file\):
file failed to compress:
@@ -0,0 +1,11 @@
set(OUTPUT_NAME "bad.zip")
# No need to set CMP0213 - WARN is default
set(ARCHIVE_FORMAT zip)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1,10 @@
set(OUTPUT_NAME "test.zip")
# No need to set CMP0213 - WARN is default
set(ARCHIVE_FORMAT zip)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -30,6 +30,41 @@ run_cmake(threads-gz)
run_cmake(threads-xz)
run_cmake(threads-zstd)
# Encoding tests. These rely on UTF-8 encoding of our test sources.
if(NOT DEFINED CMake_TEST_LOCALE_CHARSET)
cmake_host_system_information(RESULT CMake_TEST_LOCALE_CHARSET QUERY LOCALE_CHARSET)
message(STATUS "Detected LOCALE_CHARSET '${CMake_TEST_LOCALE_CHARSET}'")
endif()
if(CMake_TEST_LOCALE_CHARSET STREQUAL "UTF-8")
if(CMAKE_HOST_WIN32)
# The OEM encoding cannot represent our UTF-8 test paths.
run_cmake(encoding-oem-gnutar-fails)
run_cmake(encoding-oem-zip-fails)
run_cmake(CMP0213-OLD-fails)
run_cmake(CMP0213-WARN-fails)
else()
run_cmake(encoding-oem-gnutar)
run_cmake(encoding-oem-zip)
run_cmake(CMP0213-OLD)
run_cmake(CMP0213-WARN)
endif()
run_cmake(CMP0213-NEW)
run_cmake(encoding-oem-7zip)
run_cmake(encoding-oem-pax)
run_cmake(encoding-oem-paxr)
run_cmake(encoding-utf-8-7zip)
run_cmake(encoding-utf-8-gnutar)
run_cmake(encoding-utf-8-pax)
run_cmake(encoding-utf-8-paxr)
run_cmake(encoding-utf-8-zip)
else()
message(STATUS "encoding-* - SKIPPED due to non-UTF-8 locale")
endif()
# Extracting only selected files or directories
run_cmake(zip-filtered)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.7z")
set(ARCHIVE_FORMAT 7zip)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("377abcaf271c" LIMIT 6 HEX)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error: archive_write_header: Can't translate [Pp]athname( '[^']*\.txt')? to [^
]*
CMake Error at roundtrip\.cmake:[0-9]+ \(file\):
file failed to compress:
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "bad.tar")
set(ARCHIVE_FORMAT gnutar)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172202000" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT gnutar)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172202000" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT pax)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172003030" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT paxr)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172003030" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error: archive_write_header: Can't translate [Pp]athname( '[^']*\.txt')? to [^
]*
CMake Error at roundtrip\.cmake:[0-9]+ \(file\):
file failed to compress:
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "bad.zip")
set(ARCHIVE_FORMAT zip)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.zip")
set(ARCHIVE_FORMAT zip)
set(ENCODING OEM)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.7z")
set(ARCHIVE_FORMAT 7zip)
set(ENCODING UTF-8)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("377abcaf271c" LIMIT 6 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT gnutar)
set(ENCODING UTF-8)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172202000" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT pax)
set(ENCODING UTF-8)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172003030" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.tar")
set(ARCHIVE_FORMAT paxr)
set(ENCODING UTF-8)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("7573746172003030" OFFSET 257 LIMIT 8 HEX)
@@ -0,0 +1,9 @@
set(OUTPUT_NAME "test.zip")
set(ARCHIVE_FORMAT zip)
set(ENCODING UTF-8)
include(${CMAKE_CURRENT_LIST_DIR}/utf-8-paths.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake)
check_magic("504b0304" LIMIT 4 HEX)
@@ -20,6 +20,7 @@ set(CHECK_FILES
"d_4/f1.txt"
"d-4/f1.txt"
"My Special Directory/f1.txt"
${COMPRESSION_ADDITIONAL_FILE_NAMES}
)
foreach(file ${CHECK_FILES})
@@ -35,16 +36,23 @@ file(REMOVE ${FULL_OUTPUT_NAME})
file(REMOVE_RECURSE ${FULL_DECOMPRESS_DIR})
file(MAKE_DIRECTORY ${FULL_DECOMPRESS_DIR})
set(ENCODING_OPTIONS "")
if (ENCODING)
set(ENCODING_OPTIONS ENCODING "${ENCODING}")
endif()
file(ARCHIVE_CREATE
OUTPUT ${FULL_OUTPUT_NAME}
FORMAT "${ARCHIVE_FORMAT}"
COMPRESSION "${COMPRESSION_TYPE}"
WORKING_DIRECTORY "${WORKING_DIRECTORY}"
${ENCODING_OPTIONS}
VERBOSE
PATHS ${FULL_COMPRESS_DIR})
file(ARCHIVE_EXTRACT
INPUT ${FULL_OUTPUT_NAME}
${ENCODING_OPTIONS}
${DECOMPRESSION_OPTIONS}
DESTINATION ${FULL_DECOMPRESS_DIR}
VERBOSE)
@@ -0,0 +1,10 @@
list(APPEND COMPRESSION_ADDITIONAL_FILE_NAMES
"arabic-نَسِيج.txt"
"armenian-Բարեւ ձեզ.txt"
"chinese-质地.txt"
"cyrillic-привет.txt"
"greek-Γεια.txt"
"hungarian-hűtő.txt"
"japanese-テクスチャ.txt"
"korean-조직.txt"
)