target_sources: Interpret relative paths as relative to the calling directory

Previously the command considered non-absolute source file paths relative to
the associated target on the LHS. This causes problems in incremental builds
where files are added from subdirectories and forces users to workaround by
manually converting to absolute paths. Change this to enable more intuitive
usage by projects.

Fixes #17981
This commit is contained in:
Patrick Stotko 2018-06-02 17:20:51 +02:00
parent f31d4ac7d6
commit 316815e1f4
46 changed files with 447 additions and 4 deletions

View File

@ -9,7 +9,9 @@ Add sources to a target.
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
Specify sources to use when compiling a given target. The
Specify sources to use when compiling a given target. Relative
source file paths are interpreted as being relative to the current
source directory (i.e. :variable:`CMAKE_CURRENT_SOURCE_DIR`). The
named ``<target>`` must have been created by a command such as
:command:`add_executable` or :command:`add_library` and must not be an
:ref:`ALIAS target <Alias Targets>`.
@ -27,3 +29,6 @@ Arguments to ``target_sources`` may use "generator expressions"
with the syntax ``$<...>``. See the :manual:`cmake-generator-expressions(7)`
manual for available expressions. See the :manual:`cmake-buildsystem(7)`
manual for more on defining buildsystem properties.
See also the :policy:`CMP0076` policy for older behavior related to the
handling of relative source file paths.

View File

@ -51,6 +51,14 @@ The :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` variable may also be used
to determine whether to report an error on use of deprecated macros or
functions.
Policies Introduced by CMake 3.13
=================================
.. toctree::
:maxdepth: 1
CMP0076: target_sources() command converts relative paths to absolute. </policy/CMP0076>
Policies Introduced by CMake 3.12
=================================

26
Help/policy/CMP0076.rst Normal file
View File

@ -0,0 +1,26 @@
CMP0076
-------
The :command:`target_sources` command converts relative paths to absolute.
In CMake 3.13 and above, the :command:`target_sources` command now converts
relative source file paths to absolute paths in the following cases:
* Source files are added to the target's :prop_tgt:`INTERFACE_SOURCES`
property.
* The target's :prop_tgt:`SOURCE_DIR` property differs from
:variable:`CMAKE_CURRENT_SOURCE_DIR`.
A path that begins with a generator expression is always left unmodified.
This policy provides compatibility with projects that have not been updated
to expect this behavior. The ``OLD`` behavior for this policy is to leave
all relative source file paths unmodified. The ``NEW`` behavior of this
policy is to convert relative paths to absolute according to above rules.
This policy was introduced in CMake version 3.13. CMake version
|release| warns when the policy is not set and uses ``OLD`` behavior.
Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
explicitly.
.. include:: DEPRECATED.txt

View File

@ -0,0 +1,8 @@
subdirectory-sources
--------------------
* The :command:`target_sources` command now interprets relative source file
paths as relative to the current source directory. This simplifies
incrementally building up a target's sources from subdirectories. The
:policy:`CMP0076` policy was added to provide backward compatibility with
the old behavior where required.

View File

@ -223,7 +223,10 @@ class cmMakefile;
12, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0075, \
"Include file check macros honor CMAKE_REQUIRED_LIBRARIES.", 3, 12, \
0, cmPolicies::WARN)
0, cmPolicies::WARN) \
SELECT(POLICY, CMP0076, \
"target_sources() command converts relative paths to absolute.", 3, \
13, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
@ -248,7 +251,8 @@ class cmMakefile;
F(CMP0065) \
F(CMP0068) \
F(CMP0069) \
F(CMP0073)
F(CMP0073) \
F(CMP0076)
/** \class cmPolicies
* \brief Handles changes in CMake behavior and policies

View File

@ -2,10 +2,14 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmTargetSourcesCommand.h"
#include <cstring>
#include <sstream>
#include "cmAlgorithms.h"
#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmake.h"
@ -17,6 +21,14 @@ bool cmTargetSourcesCommand::InitialPass(std::vector<std::string> const& args,
return this->HandleArguments(args, "SOURCES");
}
void cmTargetSourcesCommand::HandleInterfaceContent(
cmTarget* tgt, const std::vector<std::string>& content, bool prepend,
bool system)
{
cmTargetPropCommandBase::HandleInterfaceContent(
tgt, ConvertToAbsoluteContent(tgt, content, true), prepend, system);
}
void cmTargetSourcesCommand::HandleMissingTarget(const std::string& name)
{
std::ostringstream e;
@ -35,6 +47,79 @@ std::string cmTargetSourcesCommand::Join(
bool cmTargetSourcesCommand::HandleDirectContent(
cmTarget* tgt, const std::vector<std::string>& content, bool, bool)
{
tgt->AppendProperty("SOURCES", this->Join(content).c_str());
tgt->AppendProperty(
"SOURCES",
this->Join(ConvertToAbsoluteContent(tgt, content, false)).c_str());
return true; // Successfully handled.
}
std::vector<std::string> cmTargetSourcesCommand::ConvertToAbsoluteContent(
cmTarget* tgt, const std::vector<std::string>& content,
bool isInterfaceContent)
{
// Skip conversion in case old behavior has been explictly requested
if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0076) ==
cmPolicies::OLD) {
return content;
}
bool changedPath = false;
std::vector<std::string> absoluteContent;
absoluteContent.reserve(content.size());
for (std::string const& src : content) {
std::string absoluteSrc;
if (cmSystemTools::FileIsFullPath(src) ||
cmGeneratorExpression::Find(src) == 0 ||
(!isInterfaceContent &&
strcmp(this->Makefile->GetCurrentSourceDirectory(),
tgt->GetMakefile()->GetCurrentSourceDirectory()) == 0)) {
absoluteSrc = src;
} else {
changedPath = true;
absoluteSrc = this->Makefile->GetCurrentSourceDirectory();
absoluteSrc += "/";
absoluteSrc += src;
}
absoluteContent.push_back(absoluteSrc);
}
if (!changedPath) {
return content;
}
bool issueMessage = true;
bool useAbsoluteContent = false;
std::ostringstream e;
switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0076)) {
case cmPolicies::WARN:
e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0076) << "\n";
break;
case cmPolicies::OLD:
issueMessage = false;
break;
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::REQUIRED_IF_USED:
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0076));
break;
case cmPolicies::NEW: {
issueMessage = false;
useAbsoluteContent = true;
break;
}
}
if (issueMessage) {
if (isInterfaceContent) {
e << "An interface source of target \"" << tgt->GetName()
<< "\" has a relative path.";
} else {
e << "A private source from a directory other than that of target \""
<< tgt->GetName() << "\" has a relative path.";
}
this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, e.str());
}
return useAbsoluteContent ? absoluteContent : content;
}

View File

@ -29,6 +29,11 @@ public:
bool InitialPass(std::vector<std::string> const& args,
cmExecutionStatus& status) override;
protected:
void HandleInterfaceContent(cmTarget* tgt,
const std::vector<std::string>& content,
bool prepend, bool system) override;
private:
void HandleMissingTarget(const std::string& name) override;
@ -37,6 +42,10 @@ private:
bool prepend, bool system) override;
std::string Join(const std::vector<std::string>& content) override;
std::vector<std::string> ConvertToAbsoluteContent(
cmTarget* tgt, const std::vector<std::string>& content,
bool isInterfaceContent);
};
#endif

View File

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.12)
cmake_policy(SET CMP0076 NEW)
project(target_sources)
add_library(target_sources_lib)
target_compile_definitions(target_sources_lib PRIVATE "-DIS_LIB")
add_subdirectory(subdir)
set(subdir_fullpath "${CMAKE_CURRENT_LIST_DIR}/subdir")
get_property(target_sources_lib_property TARGET target_sources_lib PROPERTY SOURCES)
if (NOT "$<1:${subdir_fullpath}/subdir_empty_1.cpp>" IN_LIST target_sources_lib_property)
message(SEND_ERROR "target_sources_lib: Generator expression to absolute sub directory file not found")
endif()
if (NOT "$<1:${subdir_fullpath}/../empty_1.cpp>" IN_LIST target_sources_lib_property)
message(SEND_ERROR "target_sources_lib: Generator expression to absolute main directory file not found")
endif()
if (NOT "${subdir_fullpath}/subdir_empty_2.cpp" IN_LIST target_sources_lib_property)
message(SEND_ERROR "target_sources_lib: Relative sub directory file not converted to absolute")
endif()
if (NOT "$<1:empty_2.cpp>" IN_LIST target_sources_lib_property)
message(SEND_ERROR "target_sources_lib: Generator expression to relative main directory file not found")
endif()
if (NOT "${subdir_fullpath}/../empty_3.cpp" IN_LIST target_sources_lib_property)
message(SEND_ERROR "target_sources_lib: Relative main directory file not converted to absolute")
endif()
add_executable(target_sources main.cpp)
target_link_libraries(target_sources target_sources_lib)
get_property(target_sources_property TARGET target_sources PROPERTY SOURCES)
if (NOT "main.cpp" IN_LIST target_sources_property)
message(SEND_ERROR "target_sources: Relative main directory file converted to absolute")
endif()

View File

@ -0,0 +1,21 @@
#ifdef IS_LIB
# ifdef _WIN32
__declspec(dllexport)
# endif
int internal_empty_1()
{
return 0;
}
#else
# ifdef _WIN32
__declspec(dllexport)
# endif
int empty_1()
{
return 0;
}
#endif

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty_2()
{
return 0;
}

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty_3()
{
return 0;
}

View File

@ -0,0 +1,16 @@
#include <iostream>
int empty_1();
int subdir_empty_1();
int subdir_empty_2();
int main()
{
int e1 = empty_1();
int se1 = subdir_empty_1();
int se2 = subdir_empty_2();
std::cout << e1 << " " << se1 << " " << se2 << std::endl;
return 0;
}

View File

@ -0,0 +1,6 @@
target_sources(target_sources_lib PUBLIC $<1:${CMAKE_CURRENT_LIST_DIR}/subdir_empty_1.cpp>
$<1:${CMAKE_CURRENT_LIST_DIR}/../empty_1.cpp>
subdir_empty_2.cpp
PRIVATE $<1:empty_2.cpp>
../empty_3.cpp)

View File

@ -0,0 +1,21 @@
#ifdef IS_LIB
# ifdef _WIN32
__declspec(dllexport)
# endif
int internal_subdir_empty_1()
{
return 0;
}
#else
# ifdef _WIN32
__declspec(dllexport)
# endif
int subdir_empty_1()
{
return 0;
}
#endif

View File

@ -0,0 +1,21 @@
#ifdef IS_LIB
# ifdef _WIN32
__declspec(dllexport)
# endif
int internal_subdir_empty_2()
{
return 0;
}
#else
# ifdef _WIN32
__declspec(dllexport)
# endif
int subdir_empty_2()
{
return 0;
}
#endif

View File

@ -2839,6 +2839,7 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
ADD_TEST_MACRO(CMakeCommands.target_include_directories target_include_directories)
ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions)
ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options)
ADD_TEST_MACRO(CMakeCommands.target_sources target_sources)
# The cmake server-mode test requires python for a simple client.
find_package(PythonInterp QUIET)

View File

@ -24,6 +24,7 @@
\* CMP0068
\* CMP0069
\* CMP0073
\* CMP0076
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,10 @@
cmake_policy(SET CMP0076 OLD)
add_library(iface INTERFACE)
target_sources(iface INTERFACE empty_1.cpp)
get_property(iface_sources TARGET iface PROPERTY INTERFACE_SOURCES)
message(STATUS "iface: ${iface_sources}")
add_executable(main main.cpp)
target_link_libraries(main iface)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,21 @@
CMake Warning \(dev\) at CMP0076-WARN/CMakeLists.txt:2 \(target_sources\):
Policy CMP0076 is not set: target_sources\(\) command converts relative paths
to absolute. Run "cmake --help-policy CMP0076" for policy details. Use
the cmake_policy command to set the policy and suppress this warning.
An interface source of target "publiclib" has a relative path.
This warning is for project developers. Use -Wno-dev to suppress it.
CMake Warning \(dev\) at CMP0076-WARN/CMakeLists.txt:2 \(target_sources\):
Policy CMP0076 is not set: target_sources\(\) command converts relative paths
to absolute. Run "cmake --help-policy CMP0076" for policy details. Use
the cmake_policy command to set the policy and suppress this warning.
A private source from a directory other than that of target "publiclib" has
a relative path.
This warning is for project developers. Use -Wno-dev to suppress it.
CMake Error in CMakeLists.txt:
Target "publiclib" contains relative path in its INTERFACE_SOURCES:
"CMP0076-WARN/subdir_empty_1.cpp"

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.12)
add_library(publiclib)
add_subdirectory(CMP0076-WARN)
add_executable(main main.cpp)
target_link_libraries(main publiclib)

View File

@ -0,0 +1,3 @@
target_sources(publiclib INTERFACE CMP0076-WARN/subdir_empty_1.cpp
PRIVATE empty_1.cpp)

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1 @@
-- iface: .*Tests/RunCMake/TargetSources/empty_1.cpp

View File

@ -1,6 +1,10 @@
cmake_policy(SET CMP0076 NEW)
add_library(iface INTERFACE)
target_sources(iface INTERFACE empty_1.cpp)
get_property(iface_sources TARGET iface PROPERTY INTERFACE_SOURCES)
message(STATUS "iface: ${iface_sources}")
add_executable(main main.cpp)
target_link_libraries(main iface)

View File

@ -0,0 +1 @@
-- genexlib: \$<1:.*Tests/RunCMake/TargetSources/RelativePathInSubdirGenEx/subdir_empty_1.cpp>;\$<1:.*Tests/RunCMake/TargetSources/RelativePathInSubdirGenEx/../empty_1.cpp>;\$<1:empty_2.cpp>

View File

@ -0,0 +1,10 @@
cmake_policy(SET CMP0076 NEW)
add_library(genexlib)
add_subdirectory(RelativePathInSubdirGenEx)
get_property(genexlib_sources TARGET genexlib PROPERTY SOURCES)
message(STATUS "genexlib: ${genexlib_sources}")
add_executable(genexmain main.cpp)
target_link_libraries(genexmain genexlib)

View File

@ -0,0 +1,4 @@
target_sources(genexlib PUBLIC $<1:${CMAKE_CURRENT_LIST_DIR}/subdir_empty_1.cpp>
$<1:${CMAKE_CURRENT_LIST_DIR}/../empty_1.cpp>
PRIVATE $<1:empty_2.cpp>)

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1 @@
-- privatelib: .*Tests/RunCMake/TargetSources/RelativePathInSubdirInclude/subdir_empty_1.cpp;empty_1.cpp

View File

@ -0,0 +1,8 @@
cmake_policy(SET CMP0076 NEW)
add_library(privatelib)
include("RelativePathInSubdirInclude/CMakeLists.txt")
get_property(privatelib_sources TARGET privatelib PROPERTY SOURCES)
message(STATUS "privatelib: ${privatelib_sources}")

View File

@ -0,0 +1,3 @@
target_sources(privatelib PRIVATE "${CMAKE_CURRENT_LIST_DIR}/subdir_empty_1.cpp"
empty_1.cpp)

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1 @@
-- iface: .*Tests/RunCMake/TargetSources/RelativePathInSubdirInterface/subdir_empty_1.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirInterface/subdir_empty_2.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirInterface/../empty_1.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirInterface/../empty_2.cpp

View File

@ -0,0 +1,11 @@
cmake_policy(SET CMP0076 NEW)
add_library(iface INTERFACE)
add_subdirectory(RelativePathInSubdirInterface)
get_property(iface_sources TARGET iface PROPERTY INTERFACE_SOURCES)
message(STATUS "iface: ${iface_sources}")
add_executable(main main.cpp)
target_link_libraries(main iface)

View File

@ -0,0 +1,5 @@
target_sources(iface INTERFACE subdir_empty_1.cpp
"${CMAKE_CURRENT_LIST_DIR}/subdir_empty_2.cpp"
../empty_1.cpp
"${CMAKE_CURRENT_LIST_DIR}/../empty_2.cpp")

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1 @@
-- privatelib: .*Tests/RunCMake/TargetSources/RelativePathInSubdirPrivate/subdir_empty_1.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirPrivate/subdir_empty_2.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirPrivate/../empty_1.cpp;.*Tests/RunCMake/TargetSources/RelativePathInSubdirPrivate/../empty_2.cpp

View File

@ -0,0 +1,8 @@
cmake_policy(SET CMP0076 NEW)
add_library(privatelib)
add_subdirectory(RelativePathInSubdirPrivate)
get_property(privatelib_sources TARGET privatelib PROPERTY SOURCES)
message(STATUS "privatelib: ${privatelib_sources}")

View File

@ -0,0 +1,5 @@
target_sources(privatelib PRIVATE subdir_empty_1.cpp
"${CMAKE_CURRENT_LIST_DIR}/subdir_empty_2.cpp"
../empty_1.cpp
"${CMAKE_CURRENT_LIST_DIR}/../empty_2.cpp")

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -0,0 +1,7 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
int empty()
{
return 0;
}

View File

@ -6,5 +6,11 @@ endif()
run_cmake(OriginDebug)
run_cmake(CMP0026-LOCATION)
run_cmake(CMP0076-OLD)
run_cmake(CMP0076-WARN)
run_cmake(RelativePathInInterface)
run_cmake(RelativePathInSubdirGenEx)
run_cmake(RelativePathInSubdirInterface)
run_cmake(RelativePathInSubdirPrivate)
run_cmake(RelativePathInSubdirInclude)
run_cmake(ExportBuild)