Переглянути джерело

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.
Marc Chevrier 11 місяців тому
батько
коміт
521a6d409c

+ 2 - 0
Help/manual/cmake-variables.7.rst

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

+ 2 - 0
Help/prop_tgt/STATIC_LIBRARY_OPTIONS.rst

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

+ 23 - 0
Help/prop_tgt/STATIC_LIBRARY_OPTIONS_ARCHIVER.txt

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

+ 6 - 0
Help/release/dev/STATIC_LIBRARY_OPTIONS-ARCHIVER-prefix.rst

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

+ 17 - 0
Help/variable/CMAKE_LANG_ARCHIVER_WRAPPER_FLAG.rst

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

+ 11 - 0
Help/variable/CMAKE_LANG_ARCHIVER_WRAPPER_FLAG_SEP.rst

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

+ 8 - 0
Source/cmGeneratorTarget.h

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

+ 49 - 29
Source/cmGeneratorTarget_Options.cxx

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

+ 3 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion-ARCHIVER-check.cmake

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

+ 4 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion-ARCHIVER_NESTED-check.cmake

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

+ 4 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion-ARCHIVER_NESTED_SHELL-check.cmake

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

+ 3 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion-ARCHIVER_SHELL-check.cmake

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

+ 15 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion-validation.cmake

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

+ 66 - 0
Tests/RunCMake/ARCHIVER-prefix/ARCHIVER_expansion.cmake

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

+ 5 - 0
Tests/RunCMake/ARCHIVER-prefix/CMakeLists.txt

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

+ 30 - 0
Tests/RunCMake/ARCHIVER-prefix/RunCMakeTest.cmake

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

+ 1 - 0
Tests/RunCMake/ARCHIVER-prefix/bad_SHELL_usage-result.txt

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

+ 4 - 0
Tests/RunCMake/ARCHIVER-prefix/bad_SHELL_usage-stderr.txt

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

+ 5 - 0
Tests/RunCMake/ARCHIVER-prefix/bad_SHELL_usage.cmake

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

+ 13 - 0
Tests/RunCMake/ARCHIVER-prefix/dump.c

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

+ 7 - 0
Tests/RunCMake/ARCHIVER-prefix/lib.c

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

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

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