From 321c6476407ad2324daf7fd741b49bf07f220cc3 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Thu, 2 Jan 2025 21:54:23 -0800 Subject: [PATCH] Include source file without an extension after the same name with an extension CMP0115 requires that source files listed in CMake must include their file extension, but there are cases when projects have different source files with the same name, but one with an extension and one without. In the current state, CMake will ignore the file without the extension an always map it to the file with an extension. ```cmake add_library(foo bar.c bar) ``` In the above example, the target `foo` will only recognize and depend on `bar.c` and miss the file `bar` unless `bar` comes before `bar.c` in the source list. This issue also affects how custom commands emit files. This change adds a new policy to recognize files with and without a file extension as different files, both when building targets, and when they are being created. Fixes: #26058 --- Help/manual/cmake-policies.7.rst | 1 + Help/policy/CMP0187.rst | 33 ++++++++++++++++++ Source/cmPolicies.h | 6 +++- Source/cmSourceFileLocation.cxx | 17 ++++++++-- .../CMP0187-NEW-CMP0115-OLD-build-result.txt | 1 + .../CMP0187-NEW-CMP0115-OLD-stderr.txt | 3 ++ .../CMP0187/CMP0187-NEW-CMP0115-OLD.cmake | 4 +++ .../CMP0187/CMP0187-NEW-build-result.txt | 1 + Tests/RunCMake/CMP0187/CMP0187-NEW.cmake | 3 ++ .../CMP0187/CMP0187-OLD-build-result.txt | 1 + Tests/RunCMake/CMP0187/CMP0187-OLD.cmake | 3 ++ Tests/RunCMake/CMP0187/CMP0187.cmake | 4 +++ Tests/RunCMake/CMP0187/CMakeLists.txt | 4 +++ Tests/RunCMake/CMP0187/RunCMakeTest.cmake | 34 +++++++++++++++++++ Tests/RunCMake/CMakeLists.txt | 1 + 15 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 Help/policy/CMP0187.rst create mode 100644 Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-build-result.txt create mode 100644 Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-stderr.txt create mode 100644 Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD.cmake create mode 100644 Tests/RunCMake/CMP0187/CMP0187-NEW-build-result.txt create mode 100644 Tests/RunCMake/CMP0187/CMP0187-NEW.cmake create mode 100644 Tests/RunCMake/CMP0187/CMP0187-OLD-build-result.txt create mode 100644 Tests/RunCMake/CMP0187/CMP0187-OLD.cmake create mode 100644 Tests/RunCMake/CMP0187/CMP0187.cmake create mode 100644 Tests/RunCMake/CMP0187/CMakeLists.txt create mode 100644 Tests/RunCMake/CMP0187/RunCMakeTest.cmake diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index 9f3de09654..7c41dedb6e 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1 .. toctree:: :maxdepth: 1 + CMP0187: Include source file without an extension after the same name with an extension. CMP0186: Regular expressions match ^ at most once in repeated searches. Policies Introduced by CMake 4.0 diff --git a/Help/policy/CMP0187.rst b/Help/policy/CMP0187.rst new file mode 100644 index 0000000000..d833d84399 --- /dev/null +++ b/Help/policy/CMP0187.rst @@ -0,0 +1,33 @@ +CMP0187 +------- + +.. versionadded:: 4.1 + +Include source file without an extension after the same name with an extension. + +In CMake 4.0 and below, if two source files have the same filename and only one +file has a file extension and the file with the extension is listed first, the +file without the extension is omitted from the target. + +For example, the following library target only include ``hello.c`` in the +target, but omits the file ``hello``. + +.. code-block:: cmake + + add_library(library hello.c hello) + +If the file without the extension is listed before the file with the extension, +both files are included in the target. + +Starting in CMake 4.1, CMake includes both files in the library target. + +This policy has no effect if :policy:`CMP0115` uses the ``OLD`` behavior. + +The ``OLD`` behavior for this policy is to omit the file without the extension. +The ``NEW`` behavior for this policy is to include it. + +.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1 +.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn +.. include:: STANDARD_ADVICE.txt + +.. include:: DEPRECATED.txt diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 5f5f383a7d..785acc02a0 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -558,7 +558,11 @@ class cmMakefile; WARN) \ SELECT(POLICY, CMP0186, \ "Regular expressions match ^ at most once in repeated searches.", 4, \ - 1, 0, WARN) + 1, 0, WARN) \ + SELECT(POLICY, CMP0187, \ + "Include source file without an extension after the same name with " \ + "an extension.", \ + 4, 1, 0, WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ diff --git a/Source/cmSourceFileLocation.cxx b/Source/cmSourceFileLocation.cxx index b7b9770902..c9ba19edd8 100644 --- a/Source/cmSourceFileLocation.cxx +++ b/Source/cmSourceFileLocation.cxx @@ -9,11 +9,19 @@ #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmPolicies.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmake.h" -cmSourceFileLocation::cmSourceFileLocation() = default; +// if CMP0187 and CMP0115 are NEW, then we assume that source files that do not +// include a file extension are not ambiguous but intentionally do not have an +// extension. +bool NoAmbiguousExtensions(cmMakefile const& makefile) +{ + return makefile.GetPolicyStatus(cmPolicies::CMP0115) == cmPolicies::NEW && + makefile.GetPolicyStatus(cmPolicies::CMP0187) == cmPolicies::NEW; +} cmSourceFileLocation::cmSourceFileLocation(cmSourceFileLocation const& loc) : Makefile(loc.Makefile) @@ -30,7 +38,12 @@ cmSourceFileLocation::cmSourceFileLocation(cmMakefile const* mf, : Makefile(mf) { this->AmbiguousDirectory = !cmSystemTools::FileIsFullPath(name); - this->AmbiguousExtension = true; + // If ambiguous extensions are allowed then the extension is assumed to be + // ambiguous unless the name has an extension, in which case + // `UpdateExtension` will update this. If ambiguous extensions are not + // allowed, then set this to false as the file extension must be provided or + // the file doesn't have an extension. + this->AmbiguousExtension = !NoAmbiguousExtensions(*mf); this->Directory = cmSystemTools::GetFilenamePath(name); if (cmSystemTools::FileIsFullPath(this->Directory)) { this->Directory = cmSystemTools::CollapseFullPath(this->Directory); diff --git a/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-build-result.txt b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-build-result.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-build-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-stderr.txt b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-stderr.txt new file mode 100644 index 0000000000..ac1885ac8a --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD-stderr.txt @@ -0,0 +1,3 @@ +^CMake Deprecation Warning at CMP0187-NEW-CMP0115-OLD\.cmake:[0-9]+ \(cmake_policy\): + The OLD behavior for policy CMP0115 will be removed from a future version + of CMake\. diff --git a/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD.cmake b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD.cmake new file mode 100644 index 0000000000..ee45dfbd5e --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-NEW-CMP0115-OLD.cmake @@ -0,0 +1,4 @@ +cmake_policy(SET CMP0115 OLD) +cmake_policy(SET CMP0187 NEW) + +include(CMP0187.cmake) diff --git a/Tests/RunCMake/CMP0187/CMP0187-NEW-build-result.txt b/Tests/RunCMake/CMP0187/CMP0187-NEW-build-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-NEW-build-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CMP0187/CMP0187-NEW.cmake b/Tests/RunCMake/CMP0187/CMP0187-NEW.cmake new file mode 100644 index 0000000000..a140325ee3 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-NEW.cmake @@ -0,0 +1,3 @@ +cmake_policy(SET CMP0187 NEW) + +include(CMP0187.cmake) diff --git a/Tests/RunCMake/CMP0187/CMP0187-OLD-build-result.txt b/Tests/RunCMake/CMP0187/CMP0187-OLD-build-result.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-OLD-build-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/CMP0187/CMP0187-OLD.cmake b/Tests/RunCMake/CMP0187/CMP0187-OLD.cmake new file mode 100644 index 0000000000..d48b4bb140 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187-OLD.cmake @@ -0,0 +1,3 @@ +cmake_policy(SET CMP0187 OLD) + +include(CMP0187.cmake) diff --git a/Tests/RunCMake/CMP0187/CMP0187.cmake b/Tests/RunCMake/CMP0187/CMP0187.cmake new file mode 100644 index 0000000000..029d0c9546 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMP0187.cmake @@ -0,0 +1,4 @@ +add_custom_command(OUTPUT z.h COMMAND ${CMAKE_COMMAND} -E true) +add_custom_command(OUTPUT z COMMAND ${CMAKE_COMMAND} -E false) + +add_library(lib INTERFACE z z.h) diff --git a/Tests/RunCMake/CMP0187/CMakeLists.txt b/Tests/RunCMake/CMP0187/CMakeLists.txt new file mode 100644 index 0000000000..7e8fde0b90 --- /dev/null +++ b/Tests/RunCMake/CMP0187/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 4.0) + +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/CMP0187/RunCMakeTest.cmake b/Tests/RunCMake/CMP0187/RunCMakeTest.cmake new file mode 100644 index 0000000000..f28cc704ac --- /dev/null +++ b/Tests/RunCMake/CMP0187/RunCMakeTest.cmake @@ -0,0 +1,34 @@ +include(RunCMake) + +block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0187-NEW-build) + run_cmake_with_options(CMP0187-NEW "-DCMAKE_POLICY_DEFAULT_CMP0187=NEW") + + if(RunCMake_GENERATOR MATCHES "Ninja.*") + set(RunCMake_TEST_NO_CLEAN 1) + # -n: dry-run to avoid actually compiling, -v: verbose to capture executed command + run_cmake_command(CMP0187-NEW-build ${CMAKE_COMMAND} --build .) + endif() +endblock() + +block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0187-OLD-build) + run_cmake_with_options(CMP0187-OLD "-DCMAKE_POLICY_DEFAULT_CMP0187=OLD") + + if(RunCMake_GENERATOR MATCHES "Ninja.*") + set(RunCMake_TEST_NO_CLEAN 1) + # -n: dry-run to avoid actually compiling, -v: verbose to capture executed command + run_cmake_command(CMP0187-OLD-build ${CMAKE_COMMAND} --build .) + endif() +endblock() + +block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0187-NEW-CMP0115-OLD-build) + run_cmake(CMP0187-NEW-CMP0115-OLD) + + if(RunCMake_GENERATOR MATCHES "Ninja.*") + set(RunCMake_TEST_NO_CLEAN 1) + # -n: dry-run to avoid actually compiling, -v: verbose to capture executed command + run_cmake_command(CMP0187-NEW-CMP0115-OLD-build ${CMAKE_COMMAND} --build .) + endif() +endblock() diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index e664ca333a..34aedf02b2 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -179,6 +179,7 @@ add_RunCMake_test(CMP0169) add_RunCMake_test(CMP0170) add_RunCMake_test(CMP0171) add_RunCMake_test(CMP0173) +add_RunCMake_test(CMP0187) # The test for Policy 65 requires the use of the # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode