diff options
author | Marc Chevrier <marc.chevrier@gmail.com> | 2020-10-18 14:11:27 (GMT) |
---|---|---|
committer | Marc Chevrier <marc.chevrier@gmail.com> | 2020-11-29 14:25:42 (GMT) |
commit | 2c71d051facad13b0a42a57066be2489d5fff6ea (patch) | |
tree | d07f850f8abc74d9df34ad885681c08d36c14472 /Source | |
parent | afd0f6785dc1220a07743d31699fcd9097cca46a (diff) | |
download | CMake-2c71d051facad13b0a42a57066be2489d5fff6ea.zip CMake-2c71d051facad13b0a42a57066be2489d5fff6ea.tar.gz CMake-2c71d051facad13b0a42a57066be2489d5fff6ea.tar.bz2 |
Makefiles Generators: use compiler for dependencies generation
Each source compilation generates a dependencies file. These dependencies
files are consolidated in one file per target. This consolidation is done
as part of command 'cmake -E cmake_depends` launched before evaluation of
makefile dependency graph.
The consolidation uses the same approach as `CMake` dependencies management.
Fixes: #21321
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmDependsCompiler.cxx | 267 | ||||
-rw-r--r-- | Source/cmDependsCompiler.h | 60 | ||||
-rw-r--r-- | Source/cmGlobalUnixMakefileGenerator3.h | 10 | ||||
-rw-r--r-- | Source/cmLocalUnixMakefileGenerator3.cxx | 529 | ||||
-rw-r--r-- | Source/cmMakefileTargetGenerator.cxx | 155 | ||||
-rw-r--r-- | Source/cmcmd.cxx | 183 |
7 files changed, 964 insertions, 242 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index d1616ad..8d022eb 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -224,6 +224,8 @@ set(SRCS cmDependsJava.h cmDependsJavaParserHelper.cxx cmDependsJavaParserHelper.h + cmDependsCompiler.cxx + cmDependsCompiler.h cmDocumentation.cxx cmDocumentationFormatter.cxx cmDocumentationSection.cxx diff --git a/Source/cmDependsCompiler.cxx b/Source/cmDependsCompiler.cxx new file mode 100644 index 0000000..eb0f1d5 --- /dev/null +++ b/Source/cmDependsCompiler.cxx @@ -0,0 +1,267 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDependsCompiler.h" + +#include <algorithm> +#include <map> +#include <string> +#include <unordered_set> +#include <utility> + +#include <cm/string_view> +#include <cm/vector> +#include <cmext/string_view> + +#include "cmsys/FStream.hxx" + +#include "cmFileTime.h" +#include "cmGlobalUnixMakefileGenerator3.h" +#include "cmLocalUnixMakefileGenerator3.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" + +namespace { +std::string& ReplaceAll(std::string& data, const std::string& toSearch, + const std::string& replaceStr) +{ + // Get the first occurrence + auto pos = data.find(toSearch); + // Repeat until the end is reached + while (pos != std::string::npos) { + // Replace this occurrence of Sub String + data.replace(pos, toSearch.size(), replaceStr); + // Get the next occurrence from the current position + pos = data.find(toSearch, pos + replaceStr.size()); + } + + return data; +} + +std::string& NormalizePath(std::string& item) +{ + ReplaceAll(item, "$$", "$"); + ReplaceAll(item, "\\ ", " "); + ReplaceAll(item, "\\#", "#"); + ReplaceAll(item, "\\", "/"); + + return item; +} + +void ParseLine(const std::string& line, std::vector<std::string>& depends) +{ + auto start = line.find_first_not_of(' '); + if (start == std::string::npos || line[start] == '#') { + return; + } + + auto index = start; + while ((index = line.find(' ', index)) != std::string::npos) { + if (line[index - 1] == '\\') { + index += 1; + continue; + } + + auto item = line.substr(start, index - start); + if (item.back() != ':') { + // check that ':' is not present after some spaces + auto index2 = line.find_first_not_of(' ', index + 1); + if (index2 == std::string::npos || line[index2] != ':') { + // this is a dependency, add it + depends.emplace_back(std::move(NormalizePath(item))); + } else { + index = index2; + } + } + + start = line.find_first_not_of(' ', index + 1); + index = start; + } + if (start != std::string::npos) { + auto item = line.substr(start); + if (line.back() != ':') { + // this is a dependency, add it + depends.emplace_back(std::move(NormalizePath(item))); + } + } +} +} + +bool cmDependsCompiler::CheckDependencies( + const std::string& internalDepFile, const std::vector<std::string>& depFiles, + cmDepends::DependencyMap& dependencies, + const std::function<bool(const std::string&)>& isValidPath) +{ + bool status = true; + bool forceReadDeps = true; + + cmFileTime internalDepFileTime; + // read cached dependencies stored in internal file + if (cmSystemTools::FileExists(internalDepFile)) { + internalDepFileTime.Load(internalDepFile); + forceReadDeps = false; + + // read current dependencies + cmsys::ifstream fin(internalDepFile.c_str()); + if (fin) { + std::string line; + std::string depender; + std::vector<std::string>* currentDependencies = nullptr; + while (std::getline(fin, line)) { + if (line.empty() || line.front() == '#') { + continue; + } + // Drop carriage return character at the end + if (line.back() == '\r') { + line.pop_back(); + if (line.empty()) { + continue; + } + } + // Check if this a depender line + if (line.front() != ' ') { + depender = std::move(line); + currentDependencies = &dependencies[depender]; + continue; + } + // This is a dependee line + if (currentDependencies != nullptr) { + currentDependencies->emplace_back(line.substr(1)); + } + } + fin.close(); + } + } + + // Now, update dependencies map with all new compiler generated + // dependencies files + cmFileTime depFileTime; + for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { + const auto& source = *dep++; + const auto& target = *dep++; + const auto& format = *dep++; + const auto& depFile = *dep; + + if (!cmSystemTools::FileExists(depFile)) { + continue; + } + + if (!forceReadDeps) { + depFileTime.Load(depFile); + } + if (forceReadDeps || depFileTime.Newer(internalDepFileTime)) { + status = false; + if (this->Verbose) { + cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile, + "\" is newer than depends file \"", + internalDepFile, "\".\n")); + } + cmsys::ifstream fin(depFile.c_str()); + if (!fin) { + continue; + } + + std::vector<std::string> depends; + std::string line; + if (format == "msvc"_s) { + if (!isValidPath) { + // insert source as first dependency + depends.push_back(source); + } + while (cmSystemTools::GetLineFromStream(fin, line)) { + depends.emplace_back(std::move(line)); + } + } else { + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (line.empty()) { + continue; + } + if (line.back() == '\\') { + line.pop_back(); + } + ParseLine(line, depends); + } + + // depending of the effective format of the dependencies file generated + // by the compiler, the target can be wrongly identified as a + // dependency so remove it from the list + if (depends.front() == target) { + depends.erase(depends.begin()); + } + + if (isValidPath) { + // remove first dependency because it must not be filtered out + depends.erase(depends.begin()); + } + } + + if (isValidPath) { + cm::erase_if(depends, isValidPath); + // insert source as first dependency + depends.insert(depends.begin(), source); + } + + dependencies[target] = std::move(depends); + } + } + + return status; +} + +void cmDependsCompiler::WriteDependencies( + const cmDepends::DependencyMap& dependencies, std::ostream& makeDepends, + std::ostream& internalDepends) +{ + // dependencies file consumed by make tool + const auto& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>( + this->LocalGenerator->GetGlobalGenerator()) + ->LineContinueDirective; + const auto& binDir = this->LocalGenerator->GetBinaryDirectory(); + cmDepends::DependencyMap makeDependencies(dependencies); + std::unordered_set<cm::string_view> phonyTargets; + + // external dependencies file + for (auto& node : makeDependencies) { + auto& deps = node.second; + std::transform( + deps.cbegin(), deps.cend(), deps.begin(), + [this, &binDir](const std::string& dep) { + return LocalGenerator->ConvertToMakefilePath( + this->LocalGenerator->MaybeConvertToRelativePath(binDir, dep)); + }); + + makeDepends << this->LocalGenerator->ConvertToMakefilePath(node.first) + << ": " << deps.front(); + // first dependency is the source, remove it because should not be declared + // as phony target + deps.erase(deps.begin()); + for (const auto& dep : deps) { + makeDepends << ' ' << lineContinue << " " << dep; + phonyTargets.emplace(dep.data(), dep.length()); + } + makeDepends << std::endl << std::endl; + } + + // add phony targets + for (const auto& target : phonyTargets) { + makeDepends << std::endl << target << ':' << std::endl; + } + + // internal dependencies file + for (const auto& node : dependencies) { + internalDepends << node.first << std::endl; + for (const auto& dep : node.second) { + internalDepends << ' ' << dep << std::endl; + } + internalDepends << std::endl; + } +} + +void cmDependsCompiler::ClearDependencies( + const std::vector<std::string>& depFiles) +{ + for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) { + dep += 3; + cmSystemTools::RemoveFile(*dep); + } +} diff --git a/Source/cmDependsCompiler.h b/Source/cmDependsCompiler.h new file mode 100644 index 0000000..838156d --- /dev/null +++ b/Source/cmDependsCompiler.h @@ -0,0 +1,60 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <functional> +#include <iosfwd> +#include <string> +#include <vector> + +#include "cmDepends.h" + +class cmLocalUnixMakefileGenerator3; + +/** \class cmDepends + * \brief Dependencies files manager. + * + * This class is responsible for maintaining a compiler_depends.make file in + * the build tree corresponding to an object file. + */ +class cmDependsCompiler +{ +public: + cmDependsCompiler() = default; + ~cmDependsCompiler() = default; + + /** should this be verbose in its output */ + void SetVerbose(bool verb) { this->Verbose = verb; } + + /** Set the local generator for the directory in which we are + scanning dependencies. This is not a full local generator; it + has been setup to do relative path conversions for the current + directory. */ + void SetLocalGenerator(cmLocalUnixMakefileGenerator3* lg) + { + this->LocalGenerator = lg; + } + + /** Read dependencies for the target file. Return true if + dependencies didn't changed and false if not. + Up-to-date Dependencies will be stored in deps. */ + bool CheckDependencies( + const std::string& internalDepFile, + const std::vector<std::string>& depFiles, + cmDepends::DependencyMap& dependencies, + const std::function<bool(const std::string&)>& isValidPath); + + /** Write dependencies for the target file. */ + void WriteDependencies(const cmDepends::DependencyMap& dependencies, + std::ostream& makeDepends, + std::ostream& internalDepends); + + /** Clear dependencies for the target so they will be regenerated. */ + void ClearDependencies(const std::vector<std::string>& depFiles); + +private: + bool Verbose = false; + cmLocalUnixMakefileGenerator3* LocalGenerator = nullptr; +}; diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h index 5d12831..6459771 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.h +++ b/Source/cmGlobalUnixMakefileGenerator3.h @@ -127,6 +127,12 @@ public: void WriteConvenienceRules(std::ostream& ruleFileStream, std::set<std::string>& emitted); + // Make tool supports dependency files generated by compiler + bool SupportsCompilerDependencies() + { + return this->ToolSupportsCompilerDependencies; + } + /** Get the command to use for a target that has no rule. This is used for multiple output dependencies and for cmake_force. */ std::string GetEmptyRuleHackCommand() { return this->EmptyRuleHackCommand; } @@ -219,6 +225,10 @@ protected: bool CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const override { return true; } + // Specify if the make tool is able to consume dependency files + // generated by the compiler + bool ToolSupportsCompilerDependencies = true; + // Some make programs (Borland) do not keep a rule if there are no // dependencies or commands. This is a problem for creating rules // that might not do anything but might have other dependencies diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx index 3654e8f..08cefb7 100644 --- a/Source/cmLocalUnixMakefileGenerator3.cxx +++ b/Source/cmLocalUnixMakefileGenerator3.cxx @@ -5,6 +5,7 @@ #include <algorithm> #include <cassert> #include <cstdio> +#include <functional> #include <sstream> #include <utility> @@ -19,6 +20,7 @@ #include "cmCMakePath.h" #include "cmCustomCommand.h" // IWYU pragma: keep #include "cmCustomCommandGenerator.h" +#include "cmDependsCompiler.h" #include "cmFileTimeCache.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" @@ -52,8 +54,9 @@ # include "cmDependsJava.h" #endif +namespace { // Helper function used below. -static std::string cmSplitExtension(std::string const& in, std::string& base) +std::string cmSplitExtension(std::string const& in, std::string& base) { std::string ext; std::string::size_type dot_pos = in.rfind('.'); @@ -67,6 +70,43 @@ static std::string cmSplitExtension(std::string const& in, std::string& base) return ext; } +// Helper predicate for removing absolute paths that don't point to the +// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY +// is set ON, to only consider in-project dependencies during the build. +class NotInProjectDir +{ +public: + // Constructor with the source and binary directory's path + NotInProjectDir(cm::string_view sourceDir, cm::string_view binaryDir) + : SourceDir(sourceDir) + , BinaryDir(binaryDir) + { + } + + // Operator evaluating the predicate + bool operator()(const std::string& p) const + { + auto path = cmCMakePath(p).Normal(); + + // Keep all relative paths: + if (path.IsRelative()) { + return false; + } + + // If it's an absolute path, check if it starts with the source + // directory: + return !(cmCMakePath(SourceDir).IsPrefix(path) || + cmCMakePath(BinaryDir).IsPrefix(path)); + } + +private: + // The path to the source directory + cm::string_view SourceDir; + // The path to the binary directory + cm::string_view BinaryDir; +}; +} + cmLocalUnixMakefileGenerator3::cmLocalUnixMakefileGenerator3( cmGlobalGenerator* gg, cmMakefile* mf) : cmLocalCommonGenerator(gg, mf, mf->GetCurrentBinaryDirectory()) @@ -554,8 +594,10 @@ void cmLocalUnixMakefileGenerator3::WriteMakeRule( } } - // Write the list of commands. - os << cmWrap("\t", commands, "", "\n") << "\n"; + if (!commands.empty()) { + // Write the list of commands. + os << cmWrap("\t", commands, "", "\n") << "\n"; + } if (symbolic && !this->IsWatcomWMake()) { os << ".PHONY : " << tgt << "\n"; } @@ -1300,91 +1342,153 @@ bool cmLocalUnixMakefileGenerator3::UpdateDependencies( cmSystemTools::Error("Target DependInfo.cmake file not found"); } + bool status = true; + // Check if any multiple output pairs have a missing file. this->CheckMultipleOutputs(verbose); std::string const targetDir = cmSystemTools::GetFilenamePath(tgtInfo); - std::string const internalDependFile = targetDir + "/depend.internal"; - std::string const dependFile = targetDir + "/depend.make"; - - // If the target DependInfo.cmake file has changed since the last - // time dependencies were scanned then force rescanning. This may - // happen when a new source file is added and CMake regenerates the - // project but no other sources were touched. - bool needRescanDependInfo = false; - cmFileTimeCache* ftc = - this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache(); - { - int result; - if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) { - if (verbose) { - cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo, - "\" is newer than depender \"", - internalDependFile, "\".\n")); + if (!this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) { + // dependencies are managed by CMake itself + + std::string const internalDependFile = targetDir + "/depend.internal"; + std::string const dependFile = targetDir + "/depend.make"; + + // If the target DependInfo.cmake file has changed since the last + // time dependencies were scanned then force rescanning. This may + // happen when a new source file is added and CMake regenerates the + // project but no other sources were touched. + bool needRescanDependInfo = false; + cmFileTimeCache* ftc = + this->GlobalGenerator->GetCMakeInstance()->GetFileTimeCache(); + { + int result; + if (!ftc->Compare(internalDependFile, tgtInfo, &result) || result < 0) { + if (verbose) { + cmSystemTools::Stdout(cmStrCat("Dependee \"", tgtInfo, + "\" is newer than depender \"", + internalDependFile, "\".\n")); + } + needRescanDependInfo = true; } - needRescanDependInfo = true; } - } - // If the directory information is newer than depend.internal, include dirs - // may have changed. In this case discard all old dependencies. - bool needRescanDirInfo = false; - { - std::string dirInfoFile = - cmStrCat(this->GetCurrentBinaryDirectory(), - "/CMakeFiles/CMakeDirectoryInformation.cmake"); - int result; - if (!ftc->Compare(internalDependFile, dirInfoFile, &result) || - result < 0) { - if (verbose) { - cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile, - "\" is newer than depender \"", - internalDependFile, "\".\n")); + // If the directory information is newer than depend.internal, include + // dirs may have changed. In this case discard all old dependencies. + bool needRescanDirInfo = false; + { + std::string dirInfoFile = + cmStrCat(this->GetCurrentBinaryDirectory(), + "/CMakeFiles/CMakeDirectoryInformation.cmake"); + int result; + if (!ftc->Compare(internalDependFile, dirInfoFile, &result) || + result < 0) { + if (verbose) { + cmSystemTools::Stdout(cmStrCat("Dependee \"", dirInfoFile, + "\" is newer than depender \"", + internalDependFile, "\".\n")); + } + needRescanDirInfo = true; } - needRescanDirInfo = true; + } + + // Check the implicit dependencies to see if they are up to date. + // The build.make file may have explicit dependencies for the object + // files but these will not affect the scanning process so they need + // not be considered. + cmDepends::DependencyMap validDependencies; + bool needRescanDependencies = false; + if (!needRescanDirInfo) { + cmDependsC checker; + checker.SetVerbose(verbose); + checker.SetFileTimeCache(ftc); + // cmDependsC::Check() fills the vector validDependencies() with the + // dependencies for those files where they are still valid, i.e. + // neither the files themselves nor any files they depend on have + // changed. We don't do that if the CMakeDirectoryInformation.cmake + // file has changed, because then potentially all dependencies have + // changed. This information is given later on to cmDependsC, which + // then only rescans the files where it did not get valid dependencies + // via this dependency vector. This means that in the normal case, when + // only few or one file have been edited, then also only this one file + // is actually scanned again, instead of all files for this target. + needRescanDependencies = + !checker.Check(dependFile, internalDependFile, validDependencies); + } + + if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) { + // The dependencies must be regenerated. + std::string targetName = cmSystemTools::GetFilenameName(targetDir); + targetName = targetName.substr(0, targetName.length() - 4); + std::string message = + cmStrCat("Scanning dependencies of target ", targetName); + cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | + cmsysTerminal_Color_ForegroundBold, + message.c_str(), true, color); + + status = this->ScanDependencies(targetDir, dependFile, + internalDependFile, validDependencies); } } - // Check the implicit dependencies to see if they are up to date. - // The build.make file may have explicit dependencies for the object - // files but these will not affect the scanning process so they need - // not be considered. - cmDepends::DependencyMap validDependencies; - bool needRescanDependencies = false; - if (!needRescanDirInfo) { - cmDependsC checker; - checker.SetVerbose(verbose); - checker.SetFileTimeCache(ftc); - // cmDependsC::Check() fills the vector validDependencies() with the - // dependencies for those files where they are still valid, i.e. neither - // the files themselves nor any files they depend on have changed. - // We don't do that if the CMakeDirectoryInformation.cmake file has - // changed, because then potentially all dependencies have changed. - // This information is given later on to cmDependsC, which then only - // rescans the files where it did not get valid dependencies via this - // dependency vector. This means that in the normal case, when only - // few or one file have been edited, then also only this one file is - // actually scanned again, instead of all files for this target. - needRescanDependencies = - !checker.Check(dependFile, internalDependFile, validDependencies); - } - - if (needRescanDependInfo || needRescanDirInfo || needRescanDependencies) { - // The dependencies must be regenerated. - std::string targetName = cmSystemTools::GetFilenameName(targetDir); - targetName = targetName.substr(0, targetName.length() - 4); - std::string message = - cmStrCat("Scanning dependencies of target ", targetName); - cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | - cmsysTerminal_Color_ForegroundBold, - message.c_str(), true, color); - - return this->ScanDependencies(targetDir, dependFile, internalDependFile, - validDependencies); + auto depends = + this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES"); + if (!depends.empty()) { + // dependencies are managed by compiler + auto depFiles = cmExpandedList(depends); + std::string const internalDepFile = + targetDir + "/compiler_depend.internal"; + std::string const depFile = targetDir + "/compiler_depend.make"; + cmDepends::DependencyMap dependencies; + cmDependsCompiler depsManager; + bool projectOnly = cmIsOn( + this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_IN_PROJECT_ONLY")); + + depsManager.SetVerbose(verbose); + depsManager.SetLocalGenerator(this); + + if (!depsManager.CheckDependencies( + internalDepFile, depFiles, dependencies, + projectOnly ? NotInProjectDir(this->GetSourceDirectory(), + this->GetBinaryDirectory()) + : std::function<bool(const std::string&)>())) { + // regenerate dependencies files + std::string targetName = + cmCMakePath(targetDir).GetFileName().RemoveExtension().GenericString(); + auto message = cmStrCat( + "Consolidate compiler generated dependencies of target ", targetName); + cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundMagenta | + cmsysTerminal_Color_ForegroundBold, + message.c_str(), true, color); + + // Open the make depends file. This should be copy-if-different + // because the make tool may try to reload it needlessly otherwise. + cmGeneratedFileStream ruleFileStream( + depFile, false, this->GlobalGenerator->GetMakefileEncoding()); + ruleFileStream.SetCopyIfDifferent(true); + if (!ruleFileStream) { + return false; + } + + // Open the cmake dependency tracking file. This should not be + // copy-if-different because dependencies are re-scanned when it is + // older than the DependInfo.cmake. + cmGeneratedFileStream internalRuleFileStream( + internalDepFile, false, this->GlobalGenerator->GetMakefileEncoding()); + if (!internalRuleFileStream) { + return false; + } + + this->WriteDisclaimer(ruleFileStream); + this->WriteDisclaimer(internalRuleFileStream); + + depsManager.WriteDependencies(dependencies, ruleFileStream, + internalRuleFileStream); + } } // The dependencies are already up-to-date. - return true; + return status; } bool cmLocalUnixMakefileGenerator3::ScanDependencies( @@ -1723,166 +1827,193 @@ void cmLocalUnixMakefileGenerator3::ClearDependencies(cmMakefile* mf, cmDepends clearer; clearer.SetVerbose(verbose); for (std::string const& file : files) { - std::string dir = cmSystemTools::GetFilenamePath(file); - - // Clear the implicit dependency makefile. - std::string dependFile = dir + "/depend.make"; - clearer.Clear(dependFile); + auto snapshot = mf->GetState()->CreateBaseSnapshot(); + cmMakefile lmf(mf->GetGlobalGenerator(), snapshot); + lmf.ReadListFile(file); - // Remove the internal dependency check file to force - // regeneration. - std::string internalDependFile = dir + "/depend.internal"; - cmSystemTools::RemoveFile(internalDependFile); - } -} + if (!lmf.GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES").empty()) { + std::string dir = cmSystemTools::GetFilenamePath(file); -namespace { -// Helper predicate for removing absolute paths that don't point to the -// source or binary directory. It is used when CMAKE_DEPENDS_IN_PROJECT_ONLY -// is set ON, to only consider in-project dependencies during the build. -class NotInProjectDir -{ -public: - // Constructor with the source and binary directory's path - NotInProjectDir(cm::string_view sourceDir, cm::string_view binaryDir) - : SourceDir(sourceDir) - , BinaryDir(binaryDir) - { - } + // Clear the implicit dependency makefile. + std::string dependFile = dir + "/depend.make"; + clearer.Clear(dependFile); - // Operator evaluating the predicate - bool operator()(const std::string& p) const - { - auto path = cmCMakePath(p).Normal(); + // Remove the internal dependency check file to force + // regeneration. + std::string internalDependFile = dir + "/depend.internal"; + cmSystemTools::RemoveFile(internalDependFile); + } - // Keep all relative paths: - if (path.IsRelative()) { - return false; + auto depsFiles = lmf.GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES"); + if (!depsFiles.empty()) { + auto dir = cmCMakePath(file).GetParentPath(); + // Clear the implicit dependency makefile. + auto depFile = cmCMakePath(dir).Append("compiler_depend.make"); + clearer.Clear(depFile.GenericString()); + + // Remove the internal dependency check file + auto internalDepFile = + cmCMakePath(dir).Append("compiler_depend.internal"); + cmSystemTools::RemoveFile(internalDepFile.GenericString()); + + // Touch timestamp file to force dependencies regeneration + auto DepTimestamp = cmCMakePath(dir).Append("compiler_depend.ts"); + cmSystemTools::Touch(DepTimestamp.GenericString(), true); + + // clear the dependencies files generated by the compiler + std::vector<std::string> dependencies = cmExpandedList(depsFiles); + cmDependsCompiler depsManager; + depsManager.SetVerbose(verbose); + depsManager.ClearDependencies(dependencies); } - // If it's an absolute path, check if it starts with the source - // directory: - return !(cmCMakePath(SourceDir).IsPrefix(path) || - cmCMakePath(BinaryDir).IsPrefix(path)); } - -private: - // The path to the source directory - cm::string_view SourceDir; - // The path to the binary directory - cm::string_view BinaryDir; -}; } void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo( std::ostream& cmakefileStream, cmGeneratorTarget* target) { - ImplicitDependLanguageMap const& implicitLangs = - this->GetImplicitDepends(target); + // To enable dependencies filtering + cmakefileStream << "\n" + << "# Consider dependencies only in project.\n" + << "set(CMAKE_DEPENDS_IN_PROJECT_ONLY " + << (cmIsOn(this->Makefile->GetSafeDefinition( + "CMAKE_DEPENDS_IN_PROJECT_ONLY")) + ? "ON" + : "OFF") + << ")\n\n"; + + auto const& implicitLangs = + this->GetImplicitDepends(target, cmDependencyScannerKind::CMake); // list the languages - cmakefileStream - << "# The set of languages for which implicit dependencies are needed:\n"; + cmakefileStream << "# The set of languages for which implicit " + "dependencies are needed:\n"; cmakefileStream << "set(CMAKE_DEPENDS_LANGUAGES\n"; for (auto const& implicitLang : implicitLangs) { cmakefileStream << " \"" << implicitLang.first << "\"\n"; } cmakefileStream << " )\n"; - // now list the files for each language - cmakefileStream - << "# The set of files for implicit dependencies of each language:\n"; - for (auto const& implicitLang : implicitLangs) { - cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << implicitLang.first - << "\n"; - ImplicitDependFileMap const& implicitPairs = implicitLang.second; - - // for each file pair - for (auto const& implicitPair : implicitPairs) { - for (auto const& di : implicitPair.second) { - cmakefileStream << " \"" << di << "\" "; - cmakefileStream << "\"" << implicitPair.first << "\"\n"; + if (!implicitLangs.empty()) { + // now list the files for each language + cmakefileStream + << "# The set of files for implicit dependencies of each language:\n"; + for (auto const& implicitLang : implicitLangs) { + const auto& lang = implicitLang.first; + + cmakefileStream << "set(CMAKE_DEPENDS_CHECK_" << lang << "\n"; + auto const& implicitPairs = implicitLang.second; + + // for each file pair + for (auto const& implicitPair : implicitPairs) { + for (auto const& di : implicitPair.second) { + cmakefileStream << " \"" << di << "\" "; + cmakefileStream << "\"" << implicitPair.first << "\"\n"; + } } - } - cmakefileStream << " )\n"; - - // Tell the dependency scanner what compiler is used. - std::string cidVar = - cmStrCat("CMAKE_", implicitLang.first, "_COMPILER_ID"); - cmProp cid = this->Makefile->GetDefinition(cidVar); - if (cmNonempty(cid)) { - cmakefileStream << "set(CMAKE_" << implicitLang.first - << "_COMPILER_ID \"" << *cid << "\")\n"; - } + cmakefileStream << " )\n"; - if (implicitLang.first == "Fortran") { - std::string smodSep = - this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP"); - std::string smodExt = - this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT"); - cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep - << "\")\n"; - cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt - << "\")\n"; - } + // Tell the dependency scanner what compiler is used. + std::string cidVar = cmStrCat("CMAKE_", lang, "_COMPILER_ID"); + cmProp cid = this->Makefile->GetDefinition(cidVar); + if (cmNonempty(cid)) { + cmakefileStream << "set(CMAKE_" << lang << "_COMPILER_ID \"" << *cid + << "\")\n"; + } - // Build a list of preprocessor definitions for the target. - std::set<std::string> defines; - this->GetTargetDefines(target, this->GetConfigName(), implicitLang.first, - defines); - if (!defines.empty()) { - /* clang-format off */ + if (lang == "Fortran") { + std::string smodSep = + this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP"); + std::string smodExt = + this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT"); + cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_SEP \"" << smodSep + << "\")\n"; + cmakefileStream << "set(CMAKE_Fortran_SUBMODULE_EXT \"" << smodExt + << "\")\n"; + } + + // Build a list of preprocessor definitions for the target. + std::set<std::string> defines; + this->GetTargetDefines(target, this->GetConfigName(), lang, defines); + if (!defines.empty()) { + /* clang-format off */ cmakefileStream << "\n" << "# Preprocessor definitions for this target.\n" - << "set(CMAKE_TARGET_DEFINITIONS_" << implicitLang.first << "\n"; - /* clang-format on */ - for (std::string const& define : defines) { - cmakefileStream << " " << cmOutputConverter::EscapeForCMake(define) - << "\n"; + << "set(CMAKE_TARGET_DEFINITIONS_" << lang << "\n"; + /* clang-format on */ + for (std::string const& define : defines) { + cmakefileStream << " " << cmOutputConverter::EscapeForCMake(define) + << "\n"; + } + cmakefileStream << " )\n"; + } + + // Target-specific include directories: + cmakefileStream << "\n" + << "# The include file search paths:\n"; + cmakefileStream << "set(CMAKE_" << lang << "_TARGET_INCLUDE_PATH\n"; + std::vector<std::string> includes; + + this->GetIncludeDirectories(includes, target, lang, + this->GetConfigName()); + std::string const& binaryDir = this->GetState()->GetBinaryDirectory(); + if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) { + std::string const& sourceDir = this->GetState()->GetSourceDirectory(); + cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir)); + } + for (std::string const& include : includes) { + cmakefileStream << " \"" + << this->MaybeConvertToRelativePath(binaryDir, include) + << "\"\n"; } cmakefileStream << " )\n"; } - // Target-specific include directories: - cmakefileStream << "\n" - << "# The include file search paths:\n"; - cmakefileStream << "set(CMAKE_" << implicitLang.first - << "_TARGET_INCLUDE_PATH\n"; - std::vector<std::string> includes; - - this->GetIncludeDirectories(includes, target, implicitLang.first, - this->GetConfigName()); - std::string const& binaryDir = this->GetState()->GetBinaryDirectory(); - if (this->Makefile->IsOn("CMAKE_DEPENDS_IN_PROJECT_ONLY")) { - std::string const& sourceDir = this->GetState()->GetSourceDirectory(); - cm::erase_if(includes, ::NotInProjectDir(sourceDir, binaryDir)); + // Store include transform rule properties. Write the directory + // rules first because they may be overridden by later target rules. + std::vector<std::string> transformRules; + if (cmProp xform = + this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { + cmExpandList(*xform, transformRules); } - for (std::string const& include : includes) { - cmakefileStream << " \"" - << this->MaybeConvertToRelativePath(binaryDir, include) - << "\"\n"; + if (cmProp xform = + target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { + cmExpandList(*xform, transformRules); + } + if (!transformRules.empty()) { + cmakefileStream << "\nset(CMAKE_INCLUDE_TRANSFORMS\n"; + for (std::string const& tr : transformRules) { + cmakefileStream << " " << cmOutputConverter::EscapeForCMake(tr) + << "\n"; + } + cmakefileStream << " )\n"; } - cmakefileStream << " )\n"; } - // Store include transform rule properties. Write the directory - // rules first because they may be overridden by later target rules. - std::vector<std::string> transformRules; - if (cmProp xform = - this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { - cmExpandList(*xform, transformRules); - } - if (cmProp xform = - target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) { - cmExpandList(*xform, transformRules); - } - if (!transformRules.empty()) { - cmakefileStream << "set(CMAKE_INCLUDE_TRANSFORMS\n"; - for (std::string const& tr : transformRules) { - cmakefileStream << " " << cmOutputConverter::EscapeForCMake(tr) << "\n"; + auto const& compilerLangs = + this->GetImplicitDepends(target, cmDependencyScannerKind::Compiler); + + // list the dependency files managed by the compiler + cmakefileStream << "\n# The set of dependency files which are needed:\n"; + cmakefileStream << "set(CMAKE_DEPENDS_DEPENDENCY_FILES\n"; + for (auto const& compilerLang : compilerLangs) { + auto depFormat = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", compilerLang.first, "_DEPFILE_FORMAT")); + auto const& compilerPairs = compilerLang.second; + for (auto const& compilerPair : compilerPairs) { + for (auto const& src : compilerPair.second) { + cmakefileStream << " \"" << src << "\" \"" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), compilerPair.first) + << "\" \"" << depFormat << "\" \"" + << this->MaybeConvertToRelativePath( + this->GetBinaryDirectory(), compilerPair.first) + << ".d\"\n"; + } } - cmakefileStream << " )\n"; } + cmakefileStream << " )\n"; } void cmLocalUnixMakefileGenerator3::WriteDisclaimer(std::ostream& os) diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 6bc915d..155a097 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -12,7 +12,9 @@ #include <utility> #include <cm/memory> +#include <cm/string_view> #include <cmext/algorithm> +#include <cmext/string_view> #include "cmComputeLinkInformation.h" #include "cmCustomCommand.h" @@ -326,7 +328,45 @@ void cmMakefileTargetGenerator::WriteCommonCodeRules() << cmSystemTools::ConvertToOutputPath( this->LocalGenerator->MaybeConvertToRelativePath( this->LocalGenerator->GetBinaryDirectory(), dependFileNameFull)) - << "\n\n"; + << "\n"; + + std::string depsUseCompiler = "CMAKE_DEPENDS_USE_COMPILER"; + if (!this->Makefile->IsDefinitionSet(depsUseCompiler) || + this->Makefile->IsOn(depsUseCompiler)) { + std::string compilerDependFile = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make"); + *this->BuildFileStream + << "# Include any dependencies generated by the " + "compiler for this target.\n" + << this->GlobalGenerator->IncludeDirective << " " << root + << cmSystemTools::ConvertToOutputPath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), compilerDependFile)) + << "\n\n"; + + if (!cmSystemTools::FileExists(compilerDependFile)) { + // Write an empty dependency file. + cmGeneratedFileStream depFileStream( + compilerDependFile, false, + this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# Empty compiler generated dependencies file for " + << this->GeneratorTarget->GetName() << ".\n" + << "# This may be replaced when dependencies are built.\n"; + } + + std::string compilerDependTimestamp = + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"); + if (!cmSystemTools::FileExists(compilerDependTimestamp)) { + // Write a dependency timestamp file. + cmGeneratedFileStream depFileStream( + compilerDependTimestamp, false, + this->GlobalGenerator->GetMakefileEncoding()); + depFileStream << "# CMAKE generated file: DO NOT EDIT!\n" + << "# Timestamp file for compiler generated dependencies " + "management for " + << this->GeneratorTarget->GetName() << ".\n"; + } + } if (!this->NoRuleMessages) { // Include the progress variables for the target. @@ -473,6 +513,14 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( return; } + // Use compiler to generate dependencies, if supported. + bool compilerGenerateDeps = + this->GlobalGenerator->SupportsCompilerDependencies() && + cmIsOn(this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "_DEPENDS_USE_COMPILER"))); + auto scanner = compilerGenerateDeps ? cmDependencyScannerKind::Compiler + : cmDependencyScannerKind::CMake; + // Get the full path name of the object file. std::string const& objectName = this->GeneratorTarget->GetObjectName(&source); @@ -512,7 +560,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( std::string srcFullPath = cmSystemTools::CollapseFullPath(source.GetFullPath()); this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang, - objFullPath, srcFullPath); + objFullPath, srcFullPath, scanner); this->LocalGenerator->AppendRuleDepend(depends, this->FlagFileNameFull.c_str()); @@ -554,8 +602,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( depends.push_back( this->GeneratorTarget->GetPchFile(config, lang, arch)); } - this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang, - objFullPath, pchHeader); + this->LocalGenerator->AddImplicitDepends( + this->GeneratorTarget, lang, objFullPath, pchHeader, scanner); } } @@ -689,7 +737,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( source.GetFullPath(), cmOutputConverter::SHELL); // Construct the build message. - std::vector<std::string> no_commands; + std::vector<std::string> no_depends; std::vector<std::string> commands; // add in a progress call if needed @@ -783,6 +831,26 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( "$(" + lang + "_INCLUDES)"); vars.Includes = includesString.c_str(); + std::string dependencyTarget; + std::string shellDependencyFile; + std::string dependencyTimestamp; + if (compilerGenerateDeps) { + dependencyTarget = this->LocalGenerator->EscapeForShell( + this->LocalGenerator->ConvertToMakefilePath( + this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), relativeObj))); + vars.DependencyTarget = dependencyTarget.c_str(); + + auto depFile = cmStrCat(obj, ".d"); + shellDependencyFile = this->LocalGenerator->ConvertToOutputFormat( + depFile, cmOutputConverter::SHELL); + vars.DependencyFile = shellDependencyFile.c_str(); + + dependencyTimestamp = this->LocalGenerator->MaybeConvertToRelativePath( + this->LocalGenerator->GetBinaryDirectory(), + cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts")); + } + // At the moment, it is assumed that C, C++, Fortran, and CUDA have both // assembly and preprocessor capabilities. The same is true for the // ability to export compile commands @@ -954,6 +1022,53 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( } } + std::string flagsWithDeps(flags); + + if (compilerGenerateDeps) { + // Injects dependency computation + auto depFlags = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_DEPFILE_FLAGS_", lang)); + + if (!depFlags.empty()) { + // Add dependency flags + rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator, + depFlags, vars); + flagsWithDeps.append(1, ' '); + flagsWithDeps.append(depFlags); + } + vars.Flags = flagsWithDeps.c_str(); + + const auto& extraCommands = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS")); + if (!extraCommands.empty()) { + auto commandList = cmExpandedList(extraCommands); + compileCommands.insert(compileCommands.end(), commandList.cbegin(), + commandList.cend()); + } + + const auto& depFormat = this->Makefile->GetRequiredDefinition( + cmStrCat("CMAKE_", lang, "_DEPFILE_FORMAT")); + + if (depFormat == "msvc"_s) { + // compiler must be launched through a wrapper to pick-up dependencies + std::string depFilter = + "$(CMAKE_COMMAND) -E cmake_cl_compile_depends "; + depFilter += cmStrCat("--dep-file=", shellDependencyFile); + depFilter += + cmStrCat(" --working-dir=", + this->LocalGenerator->ConvertToOutputFormat( + this->LocalGenerator->GetCurrentBinaryDirectory(), + cmOutputConverter::SHELL)); + const auto& prefix = this->Makefile->GetSafeDefinition( + cmStrCat("CMAKE_", lang, "_CL_SHOWINCLUDES_PREFIX")); + depFilter += cmStrCat(" --filter-prefix=", + this->LocalGenerator->ConvertToOutputFormat( + prefix, cmOutputConverter::SHELL)); + depFilter += " -- "; + compileCommands.front().insert(0, depFilter); + } + } + // Expand placeholders in the commands. for (std::string& compileCommand : compileCommands) { compileCommand = cmStrCat(launcher, compileCommand); @@ -979,8 +1094,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( cmExpandList(evaluated_outputs, outputs); } } - if (!ispcHeaderRelative - .empty()) { // can't move ispcHeader as vars is using it + if (!ispcHeaderRelative.empty()) { + // can't move ispcHeader as vars is using it outputs.emplace_back(ispcHeaderRelative); } @@ -988,10 +1103,19 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( this->CleanFiles.insert(outputs.begin() + 1, outputs.end()); } + if (compilerGenerateDeps) { + depends.push_back(dependencyTimestamp); + } + // Write the rule. this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends, commands); + if (compilerGenerateDeps) { + // set back flags without dependency generation + vars.Flags = flags.c_str(); + } + bool do_preprocess_rules = lang_has_preprocessor && this->LocalGenerator->GetCreatePreprocessedSourceRules(); bool do_assembly_rules = @@ -1388,10 +1512,10 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule( std::string registerFileCmd; - // The generated register file contains macros that when expanded register - // the device routines. Because the routines are the same for all - // architectures the register file will be the same too. Thus generate it - // only on the first invocation to reduce overhead. + // The generated register file contains macros that when expanded + // register the device routines. Because the routines are the same for + // all architectures the register file will be the same too. Thus + // generate it only on the first invocation to reduce overhead. if (fatbinaryDepends.size() == 1) { std::string registerFileRel = this->LocalGenerator->MaybeConvertToRelativePath( @@ -1426,7 +1550,8 @@ void cmMakefileTargetGenerator::WriteDeviceLinkRule( fatbinaryOutputRel, fatbinaryDepends, { fatbinaryCommand }, false); - // Compile the stub that registers the kernels and contains the fatbinaries. + // Compile the stub that registers the kernels and contains the + // fatbinaries. cmRulePlaceholderExpander::RuleVariables vars; vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str(); vars.CMTargetType = @@ -1839,9 +1964,9 @@ bool cmMakefileTargetGenerator::CheckUseResponseFileForObjects( if (size_t const limit = cmSystemTools::CalculateCommandLineLengthLimit()) { // Compute the total length of our list of object files with room // for argument separation and quoting. This does not convert paths - // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so the - // actual list will likely be much shorter than this. However, in the - // worst case all objects will remain as absolute paths. + // relative to CMAKE_CURRENT_BINARY_DIR like the final list will be, so + // the actual list will likely be much shorter than this. However, in + // the worst case all objects will remain as absolute paths. size_t length = 0; for (std::string const& obj : this->Objects) { length += obj.size() + 3; diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 81374a1..b8464f2 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -33,6 +33,13 @@ # include "bindexplib.h" #endif +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) +# include <algorithm> + +# include "cmCMakePath.h" +# include "cmProcessTools.h" +#endif + #if !defined(CMAKE_BOOTSTRAP) && defined(_WIN32) && !defined(__CYGWIN__) # include "cmVisualStudioWCEPlatformParser.h" #endif @@ -66,6 +73,7 @@ int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg, int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg, std::vector<std::string>::const_iterator argEnd); +namespace { void CMakeCommandUsage(const char* program) { std::ostringstream errorStream; @@ -144,8 +152,7 @@ void CMakeCommandUsage(const char* program) cmSystemTools::Error(errorStream.str()); } -static bool cmTarFilesFrom(std::string const& file, - std::vector<std::string>& files) +bool cmTarFilesFrom(std::string const& file, std::vector<std::string>& files) { if (cmSystemTools::FileIsDirectory(file)) { std::ostringstream e; @@ -180,7 +187,7 @@ static bool cmTarFilesFrom(std::string const& file, return true; } -static void cmCatFile(const std::string& fileToAppend) +void cmCatFile(const std::string& fileToAppend) { #ifdef _WIN32 _setmode(fileno(stdout), _O_BINARY); @@ -190,7 +197,7 @@ static void cmCatFile(const std::string& fileToAppend) std::cout << source.rdbuf(); } -static bool cmRemoveDirectory(const std::string& dir, bool recursive = true) +bool cmRemoveDirectory(const std::string& dir, bool recursive = true) { if (cmSystemTools::FileIsSymlink(dir)) { if (!cmSystemTools::RemoveFile(dir)) { @@ -208,9 +215,123 @@ static bool cmRemoveDirectory(const std::string& dir, bool recursive = true) return true; } -static int HandleIWYU(const std::string& runCmd, - const std::string& /* sourceFile */, - const std::vector<std::string>& orig_cmd) +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) +class CLIncludeParser : public cmProcessTools::LineParser +{ +public: + CLIncludeParser(cm::string_view includePrefix, cmsys::ofstream& depFile, + std::ostream& output) + : IncludePrefix(includePrefix) + , DepFile(depFile) + , Output(output) + { + } + +private: + bool ProcessLine() override + { + if (cmHasPrefix(this->Line, this->IncludePrefix)) { + this->DepFile << cmCMakePath( + cmTrimWhitespace(this->Line.c_str() + + this->IncludePrefix.size())) + .GenericString() + << std::endl; + } else { + this->Output << this->Line << std::endl << std::flush; + } + + return true; + } + + cm::string_view IncludePrefix; + cmsys::ofstream& DepFile; + std::ostream& Output; +}; + +class CLOutputLogger : public cmProcessTools::OutputLogger +{ +public: + CLOutputLogger(std::ostream& log) + : cmProcessTools::OutputLogger(log) + { + } + + bool ProcessLine() override + { + *this->Log << std::flush; + return true; + } +}; + +int CLCompileAndDependencies(const std::vector<std::string>& args) +{ + std::string depFile; + std::string currentBinaryDir; + std::string filterPrefix; + std::vector<std::string> command; + for (auto it = args.cbegin() + 2; it != args.cend(); it++) { + if (cmHasLiteralPrefix(*it, "--dep-file=")) { + depFile = it->substr(11); + } else if (cmHasLiteralPrefix(*it, "--working-dir=")) { + currentBinaryDir = it->substr(14); + } else if (cmHasLiteralPrefix(*it, "--filter-prefix=")) { + filterPrefix = it->substr(16); + } else if (*it == "--") { + command.insert(command.begin(), ++it, args.cend()); + break; + } else { + return 1; + } + } + + std::unique_ptr<cmsysProcess, void (*)(cmsysProcess*)> cp( + cmsysProcess_New(), cmsysProcess_Delete); + std::vector<const char*> argv(command.size() + 1); + std::transform(command.begin(), command.end(), argv.begin(), + [](std::string const& s) { return s.c_str(); }); + argv.back() = nullptr; + cmsysProcess_SetCommand(cp.get(), argv.data()); + cmsysProcess_SetWorkingDirectory(cp.get(), currentBinaryDir.c_str()); + + cmsys::ofstream fout(depFile.c_str()); + if (!fout) { + return 3; + } + + CLIncludeParser includeParser(filterPrefix, fout, std::cout); + CLOutputLogger errLogger(std::cerr); + + // Start the process. + cmProcessTools::RunProcess(cp.get(), &includeParser, &errLogger); + + int status = 0; + // handle status of process + switch (cmsysProcess_GetState(cp.get())) { + case cmsysProcess_State_Exited: + status = cmsysProcess_GetExitValue(cp.get()); + break; + case cmsysProcess_State_Exception: + status = 1; + break; + case cmsysProcess_State_Error: + status = 2; + break; + default: + break; + } + + if (status != 0) { + // remove the dependencies file because potentially invalid + fout.close(); + cmSystemTools::RemoveFile(depFile); + } + + return status; +} +#endif + +int HandleIWYU(const std::string& runCmd, const std::string& /* sourceFile */, + const std::vector<std::string>& orig_cmd) { // Construct the iwyu command line by taking what was given // and adding all the arguments we give to the compiler. @@ -235,8 +356,8 @@ static int HandleIWYU(const std::string& runCmd, return 0; } -static int HandleTidy(const std::string& runCmd, const std::string& sourceFile, - const std::vector<std::string>& orig_cmd) +int HandleTidy(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>& orig_cmd) { // Construct the clang-tidy command line by taking what was given // and adding our compiler command line. The clang-tidy tool will @@ -265,9 +386,8 @@ static int HandleTidy(const std::string& runCmd, const std::string& sourceFile, return ret; } -static int HandleLWYU(const std::string& runCmd, - const std::string& /* sourceFile */, - const std::vector<std::string>&) +int HandleLWYU(const std::string& runCmd, const std::string& /* sourceFile */, + const std::vector<std::string>&) { // Construct the ldd -r -u (link what you use lwyu) command line // ldd -u -r lwuy target @@ -298,9 +418,8 @@ static int HandleLWYU(const std::string& runCmd, return 0; } -static int HandleCppLint(const std::string& runCmd, - const std::string& sourceFile, - const std::vector<std::string>&) +int HandleCppLint(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>&) { // Construct the cpplint command line. std::vector<std::string> cpplint_cmd = cmExpandedList(runCmd, true); @@ -326,9 +445,8 @@ static int HandleCppLint(const std::string& runCmd, return 0; } -static int HandleCppCheck(const std::string& runCmd, - const std::string& sourceFile, - const std::vector<std::string>& orig_cmd) +int HandleCppCheck(const std::string& runCmd, const std::string& sourceFile, + const std::vector<std::string>& orig_cmd) { // Construct the cpplint command line. std::vector<std::string> cppcheck_cmd = cmExpandedList(runCmd, true); @@ -391,7 +509,7 @@ struct CoCompiler bool NoOriginalCommand; }; -static const std::array<CoCompiler, 5> CoCompilers = { +const std::array<CoCompiler, 5> CoCompilers = { { // Table of options and handlers. { "--cppcheck=", HandleCppCheck, false }, { "--cpplint=", HandleCppLint, false }, @@ -405,6 +523,7 @@ struct CoCompileJob std::string Command; CoCompileHandler Handler; }; +} // called when args[0] == "__run_co_compile" int cmcmd::HandleCoCompileCommands(std::vector<std::string> const& args) @@ -586,7 +705,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } else if (args[2] == "--ignore-eol") { filesDiffer = cmsys::SystemTools::TextFilesDiffer(args[3], args[4]); } else { - ::CMakeCommandUsage(args[0].c_str()); + CMakeCommandUsage(args[0].c_str()); return 2; } @@ -621,8 +740,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } } if (outValid) { - // The def file already exists and all input files are older than the - // existing def file. + // The def file already exists and all input files are older than + // the existing def file. return 0; } } @@ -1162,6 +1281,13 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, return 1; } +#if !defined(CMAKE_BOOTSTRAP) || defined(CMAKE_BOOTSTRAP_MAKEFILES) + // Internal CMake compiler dependencies filtering + if (args[1] == "cmake_cl_compile_depends") { + return CLCompileAndDependencies(args); + } +#endif + // Internal CMake link script support. if (args[1] == "cmake_link_script" && args.size() >= 3) { return cmcmd::ExecuteLinkScript(args); @@ -1412,7 +1538,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args, } } - ::CMakeCommandUsage(args[0].c_str()); + CMakeCommandUsage(args[0].c_str()); return 1; } @@ -1779,8 +1905,8 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args) skipNextArg = false; continue; } - // We use ++ as seperator between the preprocessing step definition and the - // rc compilation step becase we need to prepend a -- to seperate the + // We use ++ as seperator between the preprocessing step definition and + // the rc compilation step becase we need to prepend a -- to seperate the // source file properly from other options when using clang-cl for // preprocessing. if (arg == "++") { @@ -1830,7 +1956,8 @@ int cmcmd::RunLLVMRC(std::vector<std::string> const& args) return 1; } // Since we might have skipped the last argument to llvm-rc - // we need to make sure the llvm-rc source file is present in the commandline + // we need to make sure the llvm-rc source file is present in the + // commandline if (resource_compile.back() != intermediate_file) { resource_compile.push_back(intermediate_file); } @@ -2123,8 +2250,8 @@ int cmVSLink::LinkIncremental() // http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx // 1. Compiler compiles the application and generates the *.obj files. - // 2. An empty manifest file is generated if this is a clean build and if - // not the previous one is reused. + // 2. An empty manifest file is generated if this is a clean build and + // if not the previous one is reused. // 3. The resource compiler (rc.exe) compiles the *.manifest file to a // *.res file. // 4. Linker generates the binary (EXE or DLL) with the /incremental |