From 2edb0b71edd36031f2fcc0b65633c1c16f8e9268 Mon Sep 17 00:00:00 2001 From: Daniel Eiband Date: Wed, 11 Sep 2019 12:19:43 +0200 Subject: cmMakefile: Add lookup from source name to targets via byproducts Given an output source name it is now possible to query which target has a byproduct of this name or has a PRE_BUILD, PRE_LINK, or POST_BUILD build event with a byproduct of this name. In a call to GetSourceFileWithOutput a matching byproduct can now optionally be returned as fallback if there is no matching output of a custom command. Default behavior is not changed by this commit. --- Source/cmMakefile.cxx | 160 ++++++++++++++++++++++++++++++++++++++++++-------- Source/cmMakefile.h | 70 ++++++++++++++++++---- 2 files changed, 194 insertions(+), 36 deletions(-) diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 3a80b02..9e64f97 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -908,6 +908,37 @@ void cmMakefile::AddCustomCommandToTarget( t.AddPostBuildCommand(cc); break; } + this->UpdateOutputToSourceMap(byproducts, &t); +} + +void cmMakefile::UpdateOutputToSourceMap( + std::vector const& byproducts, cmTarget* target) +{ + for (std::string const& o : byproducts) { + this->UpdateOutputToSourceMap(o, target); + } +} + +void cmMakefile::UpdateOutputToSourceMap(std::string const& byproduct, + cmTarget* target) +{ + SourceEntry entry; + entry.Sources.Target = target; + + auto pr = this->OutputToSource.emplace(byproduct, entry); + if (!pr.second) { + SourceEntry& current = pr.first->second; + // Has the target already been set? + if (!current.Sources.Target) { + current.Sources.Target = target; + } else { + // Multiple custom commands/targets produce the same output (source file + // or target). See also comment in other UpdateOutputToSourceMap + // overload. + // + // TODO: Warn the user about this case. + } + } } cmSourceFile* cmMakefile::AddCustomCommandToOutput( @@ -1003,35 +1034,47 @@ cmSourceFile* cmMakefile::AddCustomCommandToOutput( cc->SetDepfile(depfile); cc->SetJobPool(job_pool); file->SetCustomCommand(cc); - this->UpdateOutputToSourceMap(outputs, file); + this->UpdateOutputToSourceMap(outputs, file, false); + this->UpdateOutputToSourceMap(byproducts, file, true); } return file; } void cmMakefile::UpdateOutputToSourceMap( - std::vector const& outputs, cmSourceFile* source) + std::vector const& outputs, cmSourceFile* source, + bool byproduct) { for (std::string const& o : outputs) { - this->UpdateOutputToSourceMap(o, source); + this->UpdateOutputToSourceMap(o, source, byproduct); } } void cmMakefile::UpdateOutputToSourceMap(std::string const& output, - cmSourceFile* source) -{ - auto i = this->OutputToSource.find(output); - if (i != this->OutputToSource.end()) { - // Multiple custom commands produce the same output but may - // be attached to a different source file (MAIN_DEPENDENCY). - // LinearGetSourceFileWithOutput would return the first one, - // so keep the mapping for the first one. - // - // TODO: Warn the user about this case. However, the VS 8 generator - // triggers it for separate generate.stamp rules in ZERO_CHECK and - // individual targets. - return; + cmSourceFile* source, bool byproduct) +{ + SourceEntry entry; + entry.Sources.Source = source; + entry.Sources.SourceIsByproduct = byproduct; + + auto pr = this->OutputToSource.emplace(output, entry); + if (!pr.second) { + SourceEntry& current = pr.first->second; + // Outputs take precedence over byproducts + if (!current.Sources.Source || + (current.Sources.SourceIsByproduct && !byproduct)) { + current.Sources.Source = source; + current.Sources.SourceIsByproduct = false; + } else { + // Multiple custom commands produce the same output but may + // be attached to a different source file (MAIN_DEPENDENCY). + // LinearGetSourceFileWithOutput would return the first one, + // so keep the mapping for the first one. + // + // TODO: Warn the user about this case. However, the VS 8 generator + // triggers it for separate generate.stamp rules in ZERO_CHECK and + // individual targets. + } } - this->OutputToSource[output] = source; } cmSourceFile* cmMakefile::AddCustomCommandToOutput( @@ -1194,6 +1237,8 @@ cmTarget* cmMakefile::AddUtilityCommand( } else { cmSystemTools::Error("Could not get source file entry for " + force); } + + this->UpdateOutputToSourceMap(byproducts, target); } return target; } @@ -2007,6 +2052,7 @@ cmTarget* cmMakefile::AddNewTarget(cmStateEnums::TargetType type, this->Targets .emplace(name, cmTarget(name, type, cmTarget::VisibilityNormal, this)) .first; + this->OrderedTargets.push_back(&it->second); this->GetGlobalGenerator()->IndexTarget(&it->second); this->GetStateSnapshot().GetDirectory().AddNormalTargetName(name); return &it->second; @@ -2026,11 +2072,45 @@ bool AnyOutputMatches(const std::string& name, } return false; } + +bool AnyTargetCommandOutputMatches( + const std::string& name, const std::vector& commands) +{ + for (cmCustomCommand const& command : commands) { + if (AnyOutputMatches(name, command.GetByproducts())) { + return true; + } + } + return false; +} +} + +cmTarget* cmMakefile::LinearGetTargetWithOutput(const std::string& name) const +{ + // We go through the ordered vector of targets to get reproducible results + // should multiple names match. + for (cmTarget* t : this->OrderedTargets) { + // Does the output of any command match the source file name? + if (AnyTargetCommandOutputMatches(name, t->GetPreBuildCommands())) { + return t; + } + if (AnyTargetCommandOutputMatches(name, t->GetPreLinkCommands())) { + return t; + } + if (AnyTargetCommandOutputMatches(name, t->GetPostBuildCommands())) { + return t; + } + } + return nullptr; } cmSourceFile* cmMakefile::LinearGetSourceFileWithOutput( - const std::string& name) const + const std::string& name, cmSourceOutputKind kind, bool& byproduct) const { + // Outputs take precedence over byproducts. + byproduct = false; + cmSourceFile* fallback = nullptr; + // Look through all the source files that have custom commands and see if the // custom command has the passed source file as an output. for (cmSourceFile* src : this->SourceFiles) { @@ -2041,25 +2121,57 @@ cmSourceFile* cmMakefile::LinearGetSourceFileWithOutput( // Return the first matching output. return src; } + if (kind == cmSourceOutputKind::OutputOrByproduct) { + if (AnyOutputMatches(name, src->GetCustomCommand()->GetByproducts())) { + // Do not return the source yet as there might be a matching output. + fallback = src; + } + } } } - // otherwise return NULL - return nullptr; + // Did we find a byproduct? + byproduct = fallback != nullptr; + return fallback; } -cmSourceFile* cmMakefile::GetSourceFileWithOutput( +cmSourcesWithOutput cmMakefile::GetSourcesWithOutput( const std::string& name) const { + // Linear search? Also see GetSourceFileWithOutput for detail. + if (!cmSystemTools::FileIsFullPath(name)) { + cmSourcesWithOutput sources; + sources.Target = this->LinearGetTargetWithOutput(name); + sources.Source = this->LinearGetSourceFileWithOutput( + name, cmSourceOutputKind::OutputOrByproduct, sources.SourceIsByproduct); + return sources; + } + // Otherwise we use an efficient lookup map. + auto o = this->OutputToSource.find(name); + if (o != this->OutputToSource.end()) { + return o->second.Sources; + } + return {}; +} + +cmSourceFile* cmMakefile::GetSourceFileWithOutput( + const std::string& name, cmSourceOutputKind kind) const +{ // If the queried path is not absolute we use the backward compatible // linear-time search for an output with a matching suffix. if (!cmSystemTools::FileIsFullPath(name)) { - return this->LinearGetSourceFileWithOutput(name); + bool byproduct = false; + return this->LinearGetSourceFileWithOutput(name, kind, byproduct); } // Otherwise we use an efficient lookup map. auto o = this->OutputToSource.find(name); - if (o != this->OutputToSource.end()) { - return (*o).second; + if (o != this->OutputToSource.end() && + (!o->second.Sources.SourceIsByproduct || + kind == cmSourceOutputKind::OutputOrByproduct)) { + // Source file could also be null pointer for example if we found the + // byproduct of a utility target or a PRE_BUILD, PRE_LINK, or POST_BUILD + // command of a target. + return o->second.Sources.Source; } return nullptr; } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 52464d6..dc9318b 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -51,6 +51,24 @@ class cmTestGenerator; class cmVariableWatch; class cmake; +/** Flag if byproducts shall also be considered. */ +enum class cmSourceOutputKind +{ + OutputOnly, + OutputOrByproduct +}; + +/** Target and source file which have a specific output. */ +struct cmSourcesWithOutput +{ + /** Target with byproduct. */ + cmTarget* Target = nullptr; + + /** Source file with output or byproduct. */ + cmSourceFile* Source = nullptr; + bool SourceIsByproduct = false; +}; + /** A type-safe wrapper for a string representing a directory id. */ class cmDirectoryId { @@ -687,10 +705,19 @@ public: } /** - * Is there a source file that has the provided source file as an output? - * if so then return it + * Return the target if the provided source name is a byproduct of a utility + * target or a PRE_BUILD, PRE_LINK, or POST_BUILD command. + * Return the source file which has the provided source name as output. + */ + cmSourcesWithOutput GetSourcesWithOutput(const std::string& name) const; + + /** + * Is there a source file that has the provided source name as an output? + * If so then return it. */ - cmSourceFile* GetSourceFileWithOutput(const std::string& outName) const; + cmSourceFile* GetSourceFileWithOutput( + const std::string& name, + cmSourceOutputKind kind = cmSourceOutputKind::OutputOnly) const; //! Add a new cmTest to the list of tests for this makefile. cmTest* CreateTest(const std::string& testName); @@ -914,6 +941,9 @@ protected: mutable cmTargetMap Targets; std::map AliasTargets; + using TargetsVec = std::vector; + TargetsVec OrderedTargets; + using SourceFileVec = std::vector; SourceFileVec SourceFiles; @@ -1036,21 +1066,37 @@ private: cmSourceFileLocationKind kind = cmSourceFileLocationKind::Ambiguous); /** - * Old version of GetSourceFileWithOutput(const std::string&) kept for - * backward-compatibility. It implements a linear search and support - * relative file paths. It is used as a fall back by - * GetSourceFileWithOutput(const std::string&). + * See LinearGetSourceFileWithOutput for background information + */ + cmTarget* LinearGetTargetWithOutput(const std::string& name) const; + + /** + * Generalized old version of GetSourceFileWithOutput kept for + * backward-compatibility. It implements a linear search and supports + * relative file paths. It is used as a fall back by GetSourceFileWithOutput + * and GetSourcesWithOutput. */ - cmSourceFile* LinearGetSourceFileWithOutput(const std::string& cname) const; + cmSourceFile* LinearGetSourceFileWithOutput(const std::string& name, + cmSourceOutputKind kind, + bool& byproduct) const; + + struct SourceEntry + { + cmSourcesWithOutput Sources; + }; // A map for fast output to input look up. - using OutputToSourceMap = std::unordered_map; + using OutputToSourceMap = std::unordered_map; OutputToSourceMap OutputToSource; + void UpdateOutputToSourceMap(std::vector const& byproducts, + cmTarget* target); + void UpdateOutputToSourceMap(std::string const& byproduct, cmTarget* target); + void UpdateOutputToSourceMap(std::vector const& outputs, - cmSourceFile* source); - void UpdateOutputToSourceMap(std::string const& output, - cmSourceFile* source); + cmSourceFile* source, bool byproduct); + void UpdateOutputToSourceMap(std::string const& output, cmSourceFile* source, + bool byproduct); bool AddRequiredTargetCFeature(cmTarget* target, const std::string& feature, std::string* error = nullptr) const; -- cgit v0.12