From 3676fb49634efd01755aa5c12d58e2f2bf20d09f Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Thu, 13 Feb 2014 20:52:21 +0100 Subject: [PATCH] cmTarget: Allow transitive evaluation of SOURCES property. Extend the cmGeneratorExpressionDAGChecker with an interface returning the name of the top target. Use that to determine when there is a DAG violation, as required by the RunCMake.Languages tests. --- Help/manual/cmake-properties.7.rst | 1 + Help/prop_tgt/INTERFACE_SOURCES.rst | 15 ++ Help/release/dev/target-INTERFACE_SOURCES.rst | 5 + Source/cmGeneratorExpressionDAGChecker.cxx | 12 ++ Source/cmGeneratorExpressionDAGChecker.h | 5 +- Source/cmGeneratorExpressionEvaluator.cxx | 7 +- Source/cmTarget.cxx | 191 ++++++++++++++---- Source/cmTarget.h | 12 +- Tests/CMakeLists.txt | 1 + Tests/ConfigSources/CMakeLists.txt | 8 + Tests/ConfigSources/iface_debug.h | 4 + Tests/ConfigSources/iface_debug_src.cpp | 7 + Tests/ConfigSources/iface_src.cpp | 5 + Tests/ConfigSources/main.cpp | 4 +- Tests/SourcesProperty/CMakeLists.txt | 10 + Tests/SourcesProperty/iface.cpp | 5 + Tests/SourcesProperty/iface.h | 2 + Tests/SourcesProperty/main.cpp | 7 + 18 files changed, 258 insertions(+), 43 deletions(-) create mode 100644 Help/prop_tgt/INTERFACE_SOURCES.rst create mode 100644 Help/release/dev/target-INTERFACE_SOURCES.rst create mode 100644 Tests/ConfigSources/iface_debug.h create mode 100644 Tests/ConfigSources/iface_debug_src.cpp create mode 100644 Tests/ConfigSources/iface_src.cpp create mode 100644 Tests/SourcesProperty/CMakeLists.txt create mode 100644 Tests/SourcesProperty/iface.cpp create mode 100644 Tests/SourcesProperty/iface.h create mode 100644 Tests/SourcesProperty/main.cpp diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 6ea5839c8..fd16eb95f 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -152,6 +152,7 @@ Properties on Targets /prop_tgt/INTERFACE_INCLUDE_DIRECTORIES /prop_tgt/INTERFACE_LINK_LIBRARIES /prop_tgt/INTERFACE_POSITION_INDEPENDENT_CODE + /prop_tgt/INTERFACE_SOURCES /prop_tgt/INTERFACE_SYSTEM_INCLUDE_DIRECTORIES /prop_tgt/INTERPROCEDURAL_OPTIMIZATION_CONFIG /prop_tgt/INTERPROCEDURAL_OPTIMIZATION diff --git a/Help/prop_tgt/INTERFACE_SOURCES.rst b/Help/prop_tgt/INTERFACE_SOURCES.rst new file mode 100644 index 000000000..fb28231a3 --- /dev/null +++ b/Help/prop_tgt/INTERFACE_SOURCES.rst @@ -0,0 +1,15 @@ +INTERFACE_SOURCES +----------------- + +List of interface sources to pass to the compiler. + +Targets may populate this property to publish the sources +for consuming targets to compile. Consuming +targets can add entries to their own :prop_tgt:`SOURCES` property +such as ``$`` to use the +sources specified in the interface of ``foo``. + +Contents of ``INTERFACE_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. diff --git a/Help/release/dev/target-INTERFACE_SOURCES.rst b/Help/release/dev/target-INTERFACE_SOURCES.rst new file mode 100644 index 000000000..4e3494321 --- /dev/null +++ b/Help/release/dev/target-INTERFACE_SOURCES.rst @@ -0,0 +1,5 @@ +target-INTERFACE_SOURCES +------------------------ + +* A new :prop_tgt:`INTERFACE_SOURCES` target property was introduced. This is + consumed by dependent targets, which compile and link the listed sources. diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx index 07efba9bc..7f8e6943a 100644 --- a/Source/cmGeneratorExpressionDAGChecker.cxx +++ b/Source/cmGeneratorExpressionDAGChecker.cxx @@ -179,6 +179,18 @@ bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries(const char *tgt) || strcmp(prop, "INTERFACE_LINK_LIBRARIES") == 0; } +std::string cmGeneratorExpressionDAGChecker::TopTarget() const +{ + const cmGeneratorExpressionDAGChecker *top = this; + const cmGeneratorExpressionDAGChecker *parent = this->Parent; + while (parent) + { + top = parent; + parent = parent->Parent; + } + return top->Target; +} + enum TransitiveProperty { #define DEFINE_ENUM_ENTRY(NAME) NAME, CM_FOR_EACH_TRANSITIVE_PROPERTY_NAME(DEFINE_ENUM_ENTRY) diff --git a/Source/cmGeneratorExpressionDAGChecker.h b/Source/cmGeneratorExpressionDAGChecker.h index 6cbbd2a07..b3147f7e1 100644 --- a/Source/cmGeneratorExpressionDAGChecker.h +++ b/Source/cmGeneratorExpressionDAGChecker.h @@ -25,7 +25,8 @@ SELECT(F, EvaluatingSystemIncludeDirectories, SYSTEM_INCLUDE_DIRECTORIES) \ SELECT(F, EvaluatingCompileDefinitions, COMPILE_DEFINITIONS) \ SELECT(F, EvaluatingCompileOptions, COMPILE_OPTIONS) \ - SELECT(F, EvaluatingAutoUicOptions, AUTOUIC_OPTIONS) + SELECT(F, EvaluatingAutoUicOptions, AUTOUIC_OPTIONS) \ + SELECT(F, EvaluatingSources, SOURCES) #define CM_FOR_EACH_TRANSITIVE_PROPERTY(F) \ CM_FOR_EACH_TRANSITIVE_PROPERTY_IMPL(F, CM_SELECT_BOTH) @@ -70,6 +71,8 @@ struct cmGeneratorExpressionDAGChecker void SetTransitivePropertiesOnly() { this->TransitivePropertiesOnly = true; } + std::string TopTarget() const; + private: Result CheckGraph() const; diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index a3926750f..59e3aec6c 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -985,7 +985,8 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode if (propertyName == "LINKER_LANGUAGE") { if (target->LinkLanguagePropagatesToDependents() && - dagCheckerParent && dagCheckerParent->EvaluatingLinkLibraries()) + dagCheckerParent && (dagCheckerParent->EvaluatingLinkLibraries() + || dagCheckerParent->EvaluatingSources())) { reportError(context, content->GetOriginalExpression(), "LINKER_LANGUAGE target property can not be used while evaluating " @@ -1569,7 +1570,9 @@ struct TargetFilesystemArtifact : public cmGeneratorExpressionNode "Target \"" + name + "\" is not an executable or library."); return std::string(); } - if (dagChecker && dagChecker->EvaluatingLinkLibraries(name.c_str())) + if (dagChecker && (dagChecker->EvaluatingLinkLibraries(name.c_str()) + || (dagChecker->EvaluatingSources() + && name == dagChecker->TopTarget()))) { ::reportError(context, content->GetOriginalExpression(), "Expressions which require the linker language may not " diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index fbd731586..0b5734de5 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -159,10 +159,13 @@ public: CachedLinkInterfaceCompileOptionsEntries; mutable std::map > CachedLinkInterfaceCompileDefinitionsEntries; + mutable std::map > + CachedLinkInterfaceSourcesEntries; mutable std::map CacheLinkInterfaceIncludeDirectoriesDone; mutable std::map CacheLinkInterfaceCompileDefinitionsDone; mutable std::map CacheLinkInterfaceCompileOptionsDone; + mutable std::map CacheLinkInterfaceSourcesDone; }; //---------------------------------------------------------------------------- @@ -198,6 +201,7 @@ cmTargetInternals::~cmTargetInternals() deleteAndClear(this->CachedLinkInterfaceIncludeDirectoriesEntries); deleteAndClear(this->CachedLinkInterfaceCompileOptionsEntries); deleteAndClear(this->CachedLinkInterfaceCompileDefinitionsEntries); + deleteAndClear(this->CachedLinkInterfaceSourcesEntries); } //---------------------------------------------------------------------------- @@ -542,41 +546,155 @@ bool cmTarget::IsBundleOnApple() const this->IsCFBundleOnApple(); } +//---------------------------------------------------------------------------- +static void processSources(cmTarget const* tgt, + const std::vector &entries, + std::vector &srcs, + std::set &uniqueSrcs, + cmGeneratorExpressionDAGChecker *dagChecker, + cmTarget const* head, + std::string const& config) +{ + cmMakefile *mf = tgt->GetMakefile(); + + for (std::vector::const_iterator + it = entries.begin(), end = entries.end(); it != end; ++it) + { + bool cacheSources = false; + std::vector entrySources = (*it)->CachedEntries; + if(entrySources.empty()) + { + cmSystemTools::ExpandListArgument((*it)->ge->Evaluate(mf, + config, + false, + head ? head : tgt, + tgt, + dagChecker), + entrySources); + if (mf->IsGeneratingBuildSystem() + && !(*it)->ge->GetHadContextSensitiveCondition()) + { + cacheSources = true; + } + + for(std::vector::iterator i = entrySources.begin(); + i != entrySources.end(); ++i) + { + std::string& src = *i; + + cmSourceFile* sf = mf->GetOrCreateSource(src); + std::string e; + src = sf->GetFullPath(&e); + if(src.empty()) + { + if(!e.empty()) + { + cmake* cm = mf->GetCMakeInstance(); + cm->IssueMessage(cmake::FATAL_ERROR, e, + tgt->GetBacktrace()); + } + return; + } + } + if (cacheSources) + { + (*it)->CachedEntries = entrySources; + } + } + for(std::vector::iterator + li = entrySources.begin(); li != entrySources.end(); ++li) + { + std::string src = *li; + + if(uniqueSrcs.insert(src).second) + { + srcs.push_back(src); + } + } + } +} + //---------------------------------------------------------------------------- void cmTarget::GetSourceFiles(std::vector &files, - const std::string& config) const + const std::string& config, + cmTarget const* head) const { assert(this->GetType() != INTERFACE_LIBRARY); - for(std::vector::const_iterator - si = this->Internal->SourceEntries.begin(); - si != this->Internal->SourceEntries.end(); ++si) - { - std::vector srcs; - cmSystemTools::ExpandListArgument((*si)->ge->Evaluate(this->Makefile, - config, - false, - this), - srcs); - for(std::vector::const_iterator i = srcs.begin(); - i != srcs.end(); ++i) + + cmListFileBacktrace lfbt; + + cmGeneratorExpressionDAGChecker dagChecker(lfbt, + this->GetName(), + "SOURCES", 0, 0); + + std::set uniqueSrcs; + processSources(this, + this->Internal->SourceEntries, + files, + uniqueSrcs, + &dagChecker, + head, + config); + + if (!this->Internal->CacheLinkInterfaceSourcesDone[config]) + { + for (std::vector::const_iterator + it = this->Internal->LinkImplementationPropertyEntries.begin(), + end = this->Internal->LinkImplementationPropertyEntries.end(); + it != end; ++it) { - std::string src = *i; - cmSourceFile* sf = this->Makefile->GetOrCreateSource(src); - std::string e; - src = sf->GetFullPath(&e); - if(src.empty()) + if (!cmGeneratorExpression::IsValidTargetName(it->Value) + && cmGeneratorExpression::Find(it->Value) == std::string::npos) { - if(!e.empty()) - { - cmake* cm = this->Makefile->GetCMakeInstance(); - cm->IssueMessage(cmake::FATAL_ERROR, e, - this->GetBacktrace()); - } - return; + continue; + } + { + cmGeneratorExpression ge(lfbt); + cmsys::auto_ptr cge = + ge.Parse(it->Value); + std::string targetResult = cge->Evaluate(this->Makefile, config, + false, this, 0, &dagChecker); + if (!this->Makefile->FindTargetToUse(targetResult)) + { + continue; } - files.push_back(src); } + std::string sourceGenex = "$Value + ",INTERFACE_SOURCES>"; + if (cmGeneratorExpression::Find(it->Value) != std::string::npos) + { + // Because it->Value is a generator expression, ensure that it + // evaluates to the non-empty string before being used in the + // TARGET_PROPERTY expression. + sourceGenex = "$<$Value + ">:" + sourceGenex + ">"; + } + cmGeneratorExpression ge(it->Backtrace); + cmsys::auto_ptr cge = ge.Parse( + sourceGenex); + + this->Internal + ->CachedLinkInterfaceSourcesEntries[config].push_back( + new cmTargetInternals::TargetPropertyEntry(cge, + it->Value)); + } + } + + processSources(this, + this->Internal->CachedLinkInterfaceSourcesEntries[config], + files, + uniqueSrcs, + &dagChecker, + head, + config); + + if (!this->Makefile->IsGeneratingBuildSystem()) + { + deleteAndClear(this->Internal->CachedLinkInterfaceSourcesEntries); + } + else + { + this->Internal->CacheLinkInterfaceSourcesDone[config] = true; } } @@ -639,10 +757,11 @@ cmTarget::GetConfigCommonSourceFiles(std::vector& files) const //---------------------------------------------------------------------------- void cmTarget::GetSourceFiles(std::vector &files, - const std::string& config) const + const std::string& config, + cmTarget const* head) const { std::vector srcs; - this->GetSourceFiles(srcs, config); + this->GetSourceFiles(srcs, config, head); std::set emitted; @@ -5053,10 +5172,11 @@ bool cmTarget::IsLinkInterfaceDependentNumberMaxProperty(const std::string &p, //---------------------------------------------------------------------------- void cmTarget::GetLanguages(std::set& languages, - const std::string& config) const + const std::string& config, + cmTarget const* head) const { std::vector sourceFiles; - this->GetSourceFiles(sourceFiles, config); + this->GetSourceFiles(sourceFiles, config, head); for(std::vector::const_iterator i = sourceFiles.begin(); i != sourceFiles.end(); ++i) { @@ -5111,7 +5231,7 @@ void cmTarget::GetLanguages(std::set& languages, for(std::vector::const_iterator i = objectLibraries.begin(); i != objectLibraries.end(); ++i) { - (*i)->GetLanguages(languages, config); + (*i)->GetLanguages(languages, config, head); } } @@ -6050,7 +6170,7 @@ cmTarget::GetLinkImplementation(const std::string& config, // Compute the link implementation for this configuration. LinkImplementation impl; this->ComputeLinkImplementation(config, impl, head); - this->ComputeLinkImplementationLanguages(config, impl); + this->ComputeLinkImplementationLanguages(config, impl, head); // Store the information for this configuration. cmTargetInternals::LinkImplMapType::value_type entry(key, impl); @@ -6058,7 +6178,7 @@ cmTarget::GetLinkImplementation(const std::string& config, } else if (i->second.Languages.empty()) { - this->ComputeLinkImplementationLanguages(config, i->second); + this->ComputeLinkImplementationLanguages(config, i->second, head); } return &i->second; @@ -6172,12 +6292,13 @@ void cmTarget::ComputeLinkImplementation(const std::string& config, //---------------------------------------------------------------------------- void cmTarget::ComputeLinkImplementationLanguages(const std::string& config, - LinkImplementation& impl) const + LinkImplementation& impl, + cmTarget const* head) const { // This target needs runtime libraries for its source languages. std::set languages; // Get languages used in our source files. - this->GetLanguages(languages, config); + this->GetLanguages(languages, config, head); // Copy the set of langauges to the link implementation. for(std::set::iterator li = languages.begin(); li != languages.end(); ++li) diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 042b441d7..15c49ea02 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -136,9 +136,11 @@ public: * Get the list of the source files used by this target */ void GetSourceFiles(std::vector &files, - const std::string& config) const; + const std::string& config, + cmTarget const* head = 0) const; void GetSourceFiles(std::vector &files, - const std::string& config) const; + const std::string& config, + cmTarget const* head = 0) const; bool GetConfigCommonSourceFiles(std::vector& files) const; /** @@ -469,7 +471,8 @@ public: // information to forward these property changes to the targets // until we have per-target object file properties. void GetLanguages(std::set& languages, - const std::string& config) const; + std::string const& config, + cmTarget const* head = 0) const; /** Return whether this target is an executable with symbol exports enabled. */ @@ -743,7 +746,8 @@ private: LinkImplementation& impl, cmTarget const* head) const; void ComputeLinkImplementationLanguages(const std::string& config, - LinkImplementation& impl) const; + LinkImplementation& impl, + cmTarget const* head) const; void ComputeLinkClosure(const std::string& config, LinkClosure& lc, cmTarget const* head) const; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index e77133a55..1c474ab13 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -282,6 +282,7 @@ if(BUILD_TESTING) set(ConfigSources_BUILD_OPTIONS -DCMAKE_BUILD_TYPE=Debug) ADD_TEST_MACRO(ConfigSources ConfigSources) endif() + ADD_TEST_MACRO(SourcesProperty SourcesProperty) set_tests_properties(EmptyLibrary PROPERTIES PASS_REGULAR_EXPRESSION "CMake Error: CMake can not determine linker language for target: test") ADD_TEST_MACRO(CrossCompile CrossCompile) diff --git a/Tests/ConfigSources/CMakeLists.txt b/Tests/ConfigSources/CMakeLists.txt index 68a4233db..c272257ca 100644 --- a/Tests/ConfigSources/CMakeLists.txt +++ b/Tests/ConfigSources/CMakeLists.txt @@ -3,7 +3,15 @@ cmake_minimum_required(VERSION 3.0) project(ConfigSources) +add_library(iface INTERFACE) +set_property(TARGET iface PROPERTY INTERFACE_SOURCES + iface_src.cpp + $<$:iface_debug_src.cpp> + $<$:does_not_exist.cpp> +) + add_executable(ConfigSources $<$:main.cpp> $<$:does_not_exist.cpp> ) +target_link_libraries(ConfigSources iface) diff --git a/Tests/ConfigSources/iface_debug.h b/Tests/ConfigSources/iface_debug.h new file mode 100644 index 000000000..a23d7374b --- /dev/null +++ b/Tests/ConfigSources/iface_debug.h @@ -0,0 +1,4 @@ + +int iface_src(); + +int iface_debug(); diff --git a/Tests/ConfigSources/iface_debug_src.cpp b/Tests/ConfigSources/iface_debug_src.cpp new file mode 100644 index 000000000..63b22fcd1 --- /dev/null +++ b/Tests/ConfigSources/iface_debug_src.cpp @@ -0,0 +1,7 @@ + +#include "iface_debug.h" + +int iface_debug() +{ + return 0; +} diff --git a/Tests/ConfigSources/iface_src.cpp b/Tests/ConfigSources/iface_src.cpp new file mode 100644 index 000000000..c3a0c8f1e --- /dev/null +++ b/Tests/ConfigSources/iface_src.cpp @@ -0,0 +1,5 @@ + +int iface_src() +{ + return 0; +} diff --git a/Tests/ConfigSources/main.cpp b/Tests/ConfigSources/main.cpp index 1c19e8dc3..71af72f70 100644 --- a/Tests/ConfigSources/main.cpp +++ b/Tests/ConfigSources/main.cpp @@ -1,5 +1,7 @@ +#include "iface_debug.h" + int main(int argc, char** argv) { - return 0; + return iface_src() + iface_debug(); } diff --git a/Tests/SourcesProperty/CMakeLists.txt b/Tests/SourcesProperty/CMakeLists.txt new file mode 100644 index 000000000..0b3097e49 --- /dev/null +++ b/Tests/SourcesProperty/CMakeLists.txt @@ -0,0 +1,10 @@ + +cmake_minimum_required(VERSION 3.0) + +project(SourcesProperty) + +add_library(iface INTERFACE) +set_property(TARGET iface PROPERTY INTERFACE_SOURCES iface.cpp) + +add_executable(SourcesProperty main.cpp) +target_link_libraries(SourcesProperty iface) diff --git a/Tests/SourcesProperty/iface.cpp b/Tests/SourcesProperty/iface.cpp new file mode 100644 index 000000000..e38ac3778 --- /dev/null +++ b/Tests/SourcesProperty/iface.cpp @@ -0,0 +1,5 @@ + +int iface() +{ + return 0; +} diff --git a/Tests/SourcesProperty/iface.h b/Tests/SourcesProperty/iface.h new file mode 100644 index 000000000..2cac248ef --- /dev/null +++ b/Tests/SourcesProperty/iface.h @@ -0,0 +1,2 @@ + +int iface(); diff --git a/Tests/SourcesProperty/main.cpp b/Tests/SourcesProperty/main.cpp new file mode 100644 index 000000000..ae4f30567 --- /dev/null +++ b/Tests/SourcesProperty/main.cpp @@ -0,0 +1,7 @@ + +#include "iface.h" + +int main(int argc, char** argv) +{ + return iface(); +}