1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-24 03:02:46 +08:00

Archive library: Add support for ARCHIVER: prefix

Add the support of the ARCHIVER: prefix to offer a portable way to pass
options to the archiver when the compiler is used as driver.
This commit is contained in:
Marc Chevrier
2024-11-18 15:13:17 +01:00
parent 08c0fcb4d0
commit 521a6d409c
22 changed files with 279 additions and 29 deletions

View File

@@ -613,6 +613,8 @@ Variables for Languages
/variable/CMAKE_LANG_ARCHIVE_APPEND
/variable/CMAKE_LANG_ARCHIVE_CREATE
/variable/CMAKE_LANG_ARCHIVE_FINISH
/variable/CMAKE_LANG_ARCHIVER_WRAPPER_FLAG
/variable/CMAKE_LANG_ARCHIVER_WRAPPER_FLAG_SEP
/variable/CMAKE_LANG_BYTE_ORDER
/variable/CMAKE_LANG_COMPILE_OBJECT
/variable/CMAKE_LANG_COMPILER

View File

@@ -22,3 +22,5 @@ for more on defining buildsystem properties.
property.
.. include:: ../command/OPTIONS_SHELL.txt
.. include:: ../prop_tgt/STATIC_LIBRARY_OPTIONS_ARCHIVER.txt

View File

@@ -0,0 +1,23 @@
Handling Archiver Driver Differences
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.32
To pass options to the archiver tool, each compiler driver has its own syntax.
The ``ARCHIVER:`` prefix and ``,`` separator can be used to specify, in a portable
way, options to pass to the archiver tool. ``ARCHIVER:`` is replaced by the
appropriate driver option and ``,`` by the appropriate driver separator.
The driver prefix and driver separator are given by the values of the
:variable:`CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG` and
:variable:`CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG_SEP` variables.
The ``ARCHIVER:`` prefix can be specified as part of a ``SHELL:`` prefix
expression.
The ``ARCHIVER:`` prefix supports, as an alternative syntax, specification of
arguments using the ``SHELL:`` prefix and space as separator.
.. note::
Specifying the ``SHELL:`` prefix anywhere other than at the beginning of the
``ARCHIVER:`` prefix is not supported.

View File

@@ -0,0 +1,6 @@
STATIC_LIBRARY_OPTIONS-ARCHIVER-prefix
--------------------------------------
* The :prop_tgt:`STATIC_LIBRARY_OPTIONS` target property gains the support of
the ``ARCHIVER:`` prefix to pass options to the archiver through the compiler
driver in a portable way.

View File

@@ -0,0 +1,17 @@
CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG
----------------------------------
.. versionadded:: 3.32
Defines the syntax of compiler driver option to pass options to the archiver
tool. It will be used to translate the ``ARCHIVER:`` prefix in the static
library options (see :prop_tgt:`STATIC_LIBRARY_OPTIONS`).
This variable holds a :ref:`semicolon-separated list <CMake Language Lists>` of
tokens. If a space (i.e. " ") is specified as last token, flag and
``ARCHIVER:`` arguments will be specified as separate arguments to the compiler
driver. The :variable:`CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG_SEP` variable can be
specified to manage concatenation of arguments.
See :variable:`CMAKE_<LANG>_LINKER_WRAPPER_FLAG` variable for examples of
definitions because ``CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG`` use the same syntax.

View File

@@ -0,0 +1,11 @@
CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG_SEP
--------------------------------------
.. versionadded:: 3.32
This variable is used with :variable:`CMAKE_<LANG>_ARCHIVER_WRAPPER_FLAG`
variable to format ``ARCHIVER:`` prefix in the static library options
(see :prop_tgt:`STATIC_LIBRARY_OPTIONS`).
When specified, arguments of the ``ARCHIVER:`` prefix will be concatenated
using this value as separator.

View File

@@ -669,6 +669,10 @@ public:
std::vector<BT<std::string>> GetStaticLibraryLinkOptions(
std::string const& config, std::string const& language) const;
std::vector<BT<std::string>>& ResolveArchiverWrapper(
std::vector<BT<std::string>>& result, const std::string& language,
bool joinItems = false) const;
void GetLinkDirectories(std::vector<std::string>& result,
const std::string& config,
const std::string& language) const;
@@ -1354,6 +1358,10 @@ private:
LookupLinkItemScope* scope,
LookupSelf lookupSelf) const;
std::vector<BT<std::string>>& ResolvePrefixWrapper(
std::vector<BT<std::string>>& result, cm::string_view prefix,
const std::string& language, bool joinItems) const;
std::vector<BT<std::string>> GetSourceFilePaths(
std::string const& config) const;
std::vector<BT<cmSourceFile*>> GetSourceFilesWithoutObjectLibraries(

View File

@@ -95,7 +95,7 @@ void processOptions(cmGeneratorTarget const* tgt,
enum class NestedLinkerFlags
{
PreserveAsSpelled,
Normalize,
Normalize
};
std::vector<BT<std::string>> wrapOptions(
@@ -525,32 +525,31 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetLinkOptions(
return result;
}
std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper(
std::vector<BT<std::string>>& result, const std::string& language,
bool joinItems) const
std::vector<BT<std::string>>& cmGeneratorTarget::ResolvePrefixWrapper(
std::vector<BT<std::string>>& result, cm::string_view prefix,
const std::string& language, bool joinItems) const
{
// replace "LINKER:" prefixed elements by actual linker wrapper
// replace "LINKER:" or "ARCHIVER:" prefixed elements by actual linker or
// archiver wrapper
const std::string wrapper(this->Makefile->GetSafeDefinition(
"CMAKE_" + language +
(this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG"
: "_LINKER_WRAPPER_FLAG")));
cmStrCat("CMAKE_", language, (this->IsDeviceLink() ? "_DEVICE_" : "_"),
prefix, "_WRAPPER_FLAG")));
cmList wrapperFlag{ wrapper };
const std::string wrapperSep(this->Makefile->GetSafeDefinition(
"CMAKE_" + language +
(this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG_SEP"
: "_LINKER_WRAPPER_FLAG_SEP")));
cmStrCat("CMAKE_", language, (this->IsDeviceLink() ? "_DEVICE_" : "_"),
prefix, "_WRAPPER_FLAG_SEP")));
bool concatFlagAndArgs = true;
if (!wrapperFlag.empty() && wrapperFlag.back() == " ") {
concatFlagAndArgs = false;
wrapperFlag.pop_back();
}
const std::string LINKER{ "LINKER:" };
const std::string PREFIX{ cmStrCat(prefix, ':') };
const std::string SHELL{ "SHELL:" };
const std::string LINKER_SHELL = LINKER + SHELL;
const std::string PREFIX_SHELL = cmStrCat(PREFIX, SHELL);
for (auto entry = result.begin(); entry != result.end(); ++entry) {
if (entry->Value.compare(0, LINKER.length(), LINKER) != 0) {
if (entry->Value.compare(0, PREFIX.length(), PREFIX) != 0) {
continue;
}
@@ -558,27 +557,28 @@ std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper(
cmListFileBacktrace bt = std::move(entry->Backtrace);
entry = result.erase(entry);
std::vector<std::string> linkerOptions;
if (value.compare(0, LINKER_SHELL.length(), LINKER_SHELL) == 0) {
std::vector<std::string> options;
if (value.compare(0, PREFIX_SHELL.length(), PREFIX_SHELL) == 0) {
cmSystemTools::ParseUnixCommandLine(
value.c_str() + LINKER_SHELL.length(), linkerOptions);
value.c_str() + PREFIX_SHELL.length(), options);
} else {
linkerOptions =
cmTokenize(value.substr(LINKER.length()), ',', cmTokenizerMode::New);
options =
cmTokenize(value.substr(PREFIX.length()), ',', cmTokenizerMode::New);
}
if (linkerOptions.empty()) {
if (options.empty()) {
continue;
}
// for now, raise an error if prefix SHELL: is part of arguments
if (std::find_if(linkerOptions.begin(), linkerOptions.end(),
if (std::find_if(options.begin(), options.end(),
[&SHELL](const std::string& item) -> bool {
return item.find(SHELL) != std::string::npos;
}) != linkerOptions.end()) {
}) != options.end()) {
this->LocalGenerator->GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
"'SHELL:' prefix is not supported as part of 'LINKER:' arguments.",
cmStrCat("'SHELL:' prefix is not supported as part of '", prefix,
":' arguments."),
this->GetBacktrace());
return result;
}
@@ -586,21 +586,30 @@ std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper(
// Very old versions of the C++ standard library return void for insert, so
// can't use it to get the new iterator
const auto index = entry - result.begin();
std::vector<BT<std::string>> options =
wrapOptions(linkerOptions, bt, wrapperFlag, wrapperSep,
concatFlagAndArgs, NestedLinkerFlags::PreserveAsSpelled);
std::vector<BT<std::string>> processedOptions =
wrapOptions(options, bt, wrapperFlag, wrapperSep, concatFlagAndArgs,
NestedLinkerFlags::PreserveAsSpelled);
if (joinItems) {
result.insert(
entry, cmJoin(cmMakeRange(options.begin(), options.end()), " "_s));
entry,
cmJoin(cmMakeRange(processedOptions.begin(), processedOptions.end()),
" "_s));
entry = std::next(result.begin(), index);
} else {
result.insert(entry, options.begin(), options.end());
entry = std::next(result.begin(), index + options.size() - 1);
result.insert(entry, processedOptions.begin(), processedOptions.end());
entry = std::next(result.begin(), index + processedOptions.size() - 1);
}
}
return result;
}
std::vector<BT<std::string>>& cmGeneratorTarget::ResolveLinkerWrapper(
std::vector<BT<std::string>>& result, const std::string& language,
bool joinItems) const
{
return this->ResolvePrefixWrapper(result, "LINKER"_s, language, joinItems);
}
void cmGeneratorTarget::GetStaticLibraryLinkOptions(
std::vector<std::string>& result, const std::string& config,
const std::string& language) const
@@ -633,9 +642,20 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetStaticLibraryLinkOptions(
processOptions(this, entries, result, uniqueOptions, false,
"static library link options", OptionsParse::Shell);
// Last step: replace "ARCHIVER:" prefixed elements by
// actual archiver wrapper
this->ResolveArchiverWrapper(result, language);
return result;
}
std::vector<BT<std::string>>& cmGeneratorTarget::ResolveArchiverWrapper(
std::vector<BT<std::string>>& result, const std::string& language,
bool joinItems) const
{
return this->ResolvePrefixWrapper(result, "ARCHIVER"_s, language, joinItems);
}
void cmGeneratorTarget::GetLinkDepends(std::vector<std::string>& result,
const std::string& config,
const std::string& language) const

View File

@@ -0,0 +1,3 @@
set(reference_file "ARCHIVER.txt")
include ("${CMAKE_CURRENT_LIST_DIR}/ARCHIVER_expansion-validation.cmake")

View File

@@ -0,0 +1,4 @@
set(reference_file "ARCHIVER_NESTED.txt")
set(archiver_prefix_expected YES)
include ("${CMAKE_CURRENT_LIST_DIR}/ARCHIVER_expansion-validation.cmake")

View File

@@ -0,0 +1,4 @@
set(reference_file "ARCHIVER_NESTED_SHELL.txt")
set(archiver_prefix_expected YES)
include ("${CMAKE_CURRENT_LIST_DIR}/ARCHIVER_expansion-validation.cmake")

View File

@@ -0,0 +1,3 @@
set(reference_file "ARCHIVER.txt")
include ("${CMAKE_CURRENT_LIST_DIR}/ARCHIVER_expansion-validation.cmake")

View File

@@ -0,0 +1,15 @@
if (actual_stdout MATCHES "ARCHIVER:" AND NOT archiver_prefix_expected)
set (RunCMake_TEST_FAILED "ARCHIVER: prefix was not expanded.")
return()
endif()
if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${reference_file}")
set (RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/${reference_file}: Reference file not found.")
return()
endif()
file(READ "${RunCMake_TEST_BINARY_DIR}/${reference_file}" archiver_flag)
if (NOT actual_stdout MATCHES "${archiver_flag}")
set (RunCMake_TEST_FAILED "ARCHIVER: was not expanded correctly.")
endif()

View File

@@ -0,0 +1,66 @@
enable_language(C)
set(cfg_dir)
get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(_isMultiConfig)
set(cfg_dir /Debug)
endif()
set(DUMP_EXE "${CMAKE_CURRENT_BINARY_DIR}${cfg_dir}/dump${CMAKE_EXECUTABLE_SUFFIX}")
add_executable(dump dump.c)
# Overwrite archive rule to enable command line dump
set(CMAKE_C_CREATE_STATIC_LIBRARY "\"${DUMP_EXE}\" create_archive <LINK_FLAGS>")
function(add_test_library target_name options)
add_library(${target_name} STATIC lib.c)
set_property(TARGET ${target_name} PROPERTY STATIC_LIBRARY_OPTIONS "${options}")
add_dependencies(${target_name} dump)
endfunction()
# Use ARCHIVER alone
add_test_library(archiver "ARCHIVER:-foo,bar")
# Use ARCHIVER with SHELL
add_test_library(archiver_shell "ARCHIVER:SHELL:-foo bar")
# Nested ARCHIVER: prefixes should be preserved as written, only the outermost ARCHIVER: prefix removed
add_test_library(archiver_nested "ARCHIVER:ARCHIVER:-foo,bar")
# Same with ARCHIVER:SHELL:
add_test_library(archiver_nested_shell "ARCHIVER:SHELL:ARCHIVER:-foo bar")
# generate reference for ARCHIVER flag
if (CMAKE_C_ARCHIVER_WRAPPER_FLAG)
set(archiver_flag ${CMAKE_C_ARCHIVER_WRAPPER_FLAG})
list(GET archiver_flag -1 archiver_space)
if (archiver_space STREQUAL " ")
list(REMOVE_AT archiver_flag -1)
else()
set(archiver_space)
endif()
list (JOIN archiver_flag " " archiver_flag)
if (CMAKE_C_ARCHIVER_WRAPPER_FLAG_SEP)
set(archiver_sep "${CMAKE_C_ARCHIVER_WRAPPER_FLAG_SEP}")
set(archiver_flag_nested "${archiver_flag}${archiver_space}ARCHIVER:-foo${archiver_sep}bar")
set(archiver_flag_nested_shell "${archiver_flag}${archiver_space}ARCHIVER:-foo${archiver_sep}bar")
string (APPEND archiver_flag "${archiver_space}" "-foo${archiver_sep}bar")
else()
set(archiver_prefix "${archiver_flag}${archiver_space}")
set(archiver_flag_nested "${archiver_prefix}ARCHIVER:-foo ${archiver_prefix}bar")
set(archiver_flag_nested_shell "${archiver_prefix}ARCHIVER:-foo ${archiver_prefix}bar")
set (archiver_flag "${archiver_prefix}-foo ${archiver_prefix}bar")
endif()
else()
set(archiver_flag_nested "ARCHIVER:-foo bar")
set(archiver_flag_nested_shell "ARCHIVER:-foo bar")
set(archiver_flag "-foo bar")
endif()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ARCHIVER.txt" "${archiver_flag}")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ARCHIVER_NESTED.txt" "${archiver_flag_nested}")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ARCHIVER_NESTED_SHELL.txt" "${archiver_flag_nested_shell}")

View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.31...3.32)
project(${RunCMake_TEST} LANGUAGES NONE)
include(${RunCMake_TEST}.cmake)

View File

@@ -0,0 +1,30 @@
include(RunCMake)
macro(run_cmake_target test subtest target)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(${test}-${subtest} ${CMAKE_COMMAND} --build . --target ${target} ${VERBOSE})
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
endmacro()
run_cmake(bad_SHELL_usage)
if(RunCMake_GENERATOR MATCHES "Ninja|Makefile|Xcode|Visual Studio")
# Some environments are excluded because they are not able to honor verbose mode
if (RunCMake_GENERATOR MATCHES "Xcode|Visual Studio"
AND NOT CMAKE_C_COMPILER_ID STREQUAL "Intel")
set(RunCMake_TEST_OUTPUT_MERGE TRUE)
set(RunCMake_TEST_EXPECT_RESULT ".*")
set(VERBOSE "--verbose")
endif()
run_cmake(ARCHIVER_expansion)
run_cmake_target(ARCHIVER_expansion ARCHIVER archiver)
run_cmake_target(ARCHIVER_expansion ARCHIVER_SHELL archiver_shell)
run_cmake_target(ARCHIVER_expansion ARCHIVER_NESTED archiver_nested)
run_cmake_target(ARCHIVER_expansion ARCHIVER_NESTED_SHELL archiver_nested_shell)
endif()

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
CMake Error at bad_SHELL_usage.cmake:4 \(add_library\):
'SHELL:' prefix is not supported as part of 'ARCHIVER:' arguments.
Call Stack \(most recent call first\):
CMakeLists.txt:5 \(include\)

View File

@@ -0,0 +1,5 @@
enable_language(C)
add_library(example STATIC lib.c)
set_property(TARGET example PROPERTY STATIC_LIBRARY_OPTIONS "ARCHIVER:-foo,SHELL:-bar")

View File

@@ -0,0 +1,13 @@
#include "stdio.h"
int main(int argc, char* argv[])
{
int i;
for (i = 1; i < argc; i++)
printf("%s ", argv[i]);
printf("\n");
return 0;
}

View File

@@ -0,0 +1,7 @@
#if defined(_WIN32)
__declspec(dllexport)
#endif
int flags_lib(void)
{
return 0;
}

View File

@@ -474,6 +474,7 @@ set_property(TEST RunCMake.LanguageStandards APPEND PROPERTY LABELS "CUDA" "HIP"
add_RunCMake_test(LinkItemValidation)
add_RunCMake_test(LinkStatic)
add_RunCMake_test(ARCHIVER-prefix -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Cray|PGI|NVHPC|XL|XLClang|IBMClang|Fujitsu|FujitsuClang)$")
add_RunCMake_test(MetaCompileFeatures)
endif()