Makefiles: Add support of DEPFILE for add_custom_command

Issue: #20286
Fixes: #21415
This commit is contained in:
Marc Chevrier 2020-12-04 18:35:04 +01:00
parent a526f71266
commit cfd8a5ac1f
20 changed files with 310 additions and 80 deletions

View File

@ -201,6 +201,10 @@ The options are:
Note that the ``IMPLICIT_DEPENDS`` option is currently supported
only for Makefile generators and will be ignored by other generators.
.. note::
This option cannot be specified at the same time as ``DEPFILE`` option.
``JOB_POOL``
.. versionadded:: 3.15
@ -263,15 +267,26 @@ The options are:
``DEPFILE``
.. versionadded:: 3.7
Specify a ``.d`` depfile for the :generator:`Ninja` generator.
Specify a ``.d`` depfile for the :generator:`Ninja` generator and
:ref:`Makefile Generators`.
A ``.d`` file holds dependencies usually emitted by the custom
command itself.
Using ``DEPFILE`` with other generators than Ninja is an error.
Using ``DEPFILE`` with other generators than :generator:`Ninja` or
:ref:`Makefile Generators` is an error.
.. versionadded:: 3.20
Added the support of :ref:`Makefile Generators`.
If the ``DEPFILE`` argument is relative, it should be relative to
:variable:`CMAKE_CURRENT_BINARY_DIR`, and any relative paths inside the
``DEPFILE`` should also be relative to :variable:`CMAKE_CURRENT_BINARY_DIR`
(see policy :policy:`CMP0116`.)
(see policy :policy:`CMP0116`. This policy is always ``NEW`` for
:ref:`Makefile Generators`).
.. note::
For :ref:`Makefile Generators`, this option cannot be specified at the
same time as ``IMPLICIT_DEPENDS`` option.
Examples: Generating Files
^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
makefile-depfile
----------------
* The :command:`add_custom_command` command gained ``DEPFILE`` support on
:ref:`Makefile Generators`.

View File

@ -296,6 +296,12 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
status.SetError("given APPEND option with no OUTPUT.");
return false;
}
if (!implicit_depends.empty() && !depfile.empty() &&
mf.GetGlobalGenerator()->GetName() != "Ninja") {
// Makefiles generators does not support both at the same time
status.SetError("IMPLICIT_DEPENDS and DEPFILE can not both be specified.");
return false;
}
// Check for an append request.
if (append) {

View File

@ -19,6 +19,7 @@
#include "cmFileTime.h"
#include "cmGccDepfileReader.h"
#include "cmGccDepfileReaderTypes.h"
#include "cmGlobalUnixMakefileGenerator3.h"
#include "cmLocalUnixMakefileGenerator3.h"
#include "cmStringAlgorithms.h"
@ -86,7 +87,7 @@ bool cmDependsCompiler::CheckDependencies(
if (!forceReadDeps) {
depFileTime.Load(depFile);
}
if (forceReadDeps || depFileTime.Newer(internalDepFileTime)) {
if (forceReadDeps || depFileTime.Compare(internalDepFileTime) >= 0) {
status = false;
if (this->Verbose) {
cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile,
@ -95,59 +96,92 @@ bool cmDependsCompiler::CheckDependencies(
}
std::vector<std::string> depends;
if (format == "msvc"_s) {
cmsys::ifstream fin(depFile.c_str());
if (!fin) {
continue;
if (format == "custom"_s) {
std::string prefix;
if (this->LocalGenerator->GetCurrentBinaryDirectory() !=
this->LocalGenerator->GetBinaryDirectory()) {
prefix =
cmStrCat(this->LocalGenerator->MaybeConvertToRelativePath(
this->LocalGenerator->GetBinaryDirectory(),
this->LocalGenerator->GetCurrentBinaryDirectory()),
'/');
}
std::string line;
if (!isValidPath) {
// insert source as first dependency
depends.push_back(source);
}
while (cmSystemTools::GetLineFromStream(fin, line)) {
depends.emplace_back(std::move(line));
}
} else {
auto deps = cmReadGccDepfile(depFile.c_str());
auto deps = cmReadGccDepfile(depFile.c_str(), prefix);
if (!deps) {
continue;
}
// dependencies generated by the compiler contains only one target
depends = std::move(deps->front().paths);
if (depends.empty()) {
// unexpectedly empty, ignore it and continue
for (auto& entry : *deps) {
depends = std::move(entry.paths);
if (isValidPath) {
cm::erase_if(depends, isValidPath);
}
// copy depends for each target, except first one, which can be
// moved
for (auto index = entry.rules.size() - 1; index > 0; --index) {
dependencies[entry.rules[index]] = depends;
}
dependencies[entry.rules.front()] = std::move(depends);
}
} else {
if (format == "msvc"_s) {
cmsys::ifstream fin(depFile.c_str());
if (!fin) {
continue;
}
std::string line;
if (!isValidPath) {
// insert source as first dependency
depends.push_back(source);
}
while (cmSystemTools::GetLineFromStream(fin, line)) {
depends.emplace_back(std::move(line));
}
} else if (format == "gcc"_s) {
auto deps = cmReadGccDepfile(depFile.c_str());
if (!deps) {
continue;
}
// dependencies generated by the compiler contains only one target
depends = std::move(deps->front().paths);
if (depends.empty()) {
// unexpectedly empty, ignore it and continue
continue;
}
// 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());
}
// ensure source file is the first dependency
if (depends.front() != source) {
cm::erase(depends, source);
if (!isValidPath) {
depends.insert(depends.begin(), source);
}
} else if (isValidPath) {
// remove first dependency because it must not be filtered out
depends.erase(depends.begin());
}
} else {
// unknown format, ignore it
continue;
}
// 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) {
cm::erase_if(depends, isValidPath);
// insert source as first dependency
depends.insert(depends.begin(), source);
}
// ensure source file is the first dependency
if (depends.front() != source) {
cm::erase(depends, source);
if (!isValidPath) {
depends.insert(depends.begin(), source);
}
} else if (isValidPath) {
// remove first dependency because it must not be filtered out
depends.erase(depends.begin());
}
dependencies[target] = std::move(depends);
}
if (isValidPath) {
cm::erase_if(depends, isValidPath);
// insert source as first dependency
depends.insert(depends.begin(), source);
}
dependencies[target] = std::move(depends);
}
}
@ -168,6 +202,8 @@ void cmDependsCompiler::WriteDependencies(
// external dependencies file
for (auto& node : makeDependencies) {
auto target = LocalGenerator->ConvertToMakefilePath(
this->LocalGenerator->MaybeConvertToRelativePath(binDir, node.first));
auto& deps = node.second;
std::transform(
deps.cbegin(), deps.cend(), deps.begin(),
@ -176,13 +212,16 @@ void cmDependsCompiler::WriteDependencies(
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());
bool first_dep = true;
makeDepends << target << ": ";
for (const auto& dep : deps) {
makeDepends << ' ' << lineContinue << " " << dep;
if (first_dep) {
first_dep = false;
makeDepends << dep;
} else {
makeDepends << ' ' << lineContinue << " " << dep;
}
phonyTargets.emplace(dep.data(), dep.length());
}
makeDepends << std::endl << std::endl;

View File

@ -93,6 +93,12 @@ public:
*/
static bool SupportsPlatform() { return false; }
/**
* Utilized to determine if this generator
* supports DEPFILE option.
*/
bool SupportsCustomCommandDepfile() const override { return true; }
/** Get the documentation entry for this generator. */
static void GetDocumentation(cmDocumentationEntry& entry);

View File

@ -13,6 +13,7 @@
#include <cm/string_view>
#include <cm/vector>
#include <cmext/algorithm>
#include <cmext/string_view>
#include "cmsys/FStream.hxx"
#include "cmsys/Terminal.h"
@ -1435,7 +1436,7 @@ bool cmLocalUnixMakefileGenerator3::UpdateDependencies(
this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES");
if (!depends.empty()) {
// dependencies are managed by compiler
auto depFiles = cmExpandedList(depends);
auto depFiles = cmExpandedList(depends, true);
std::string const internalDepFile =
targetDir + "/compiler_depend.internal";
std::string const depFile = targetDir + "/compiler_depend.make";
@ -1998,18 +1999,32 @@ void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo(
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";
if (compilerLang.first == "CUSTOM"_s) {
for (auto const& compilerPair : compilerPairs) {
for (auto const& src : compilerPair.second) {
cmakefileStream << R"( "" ")"
<< this->MaybeConvertToRelativePath(
this->GetBinaryDirectory(), compilerPair.first)
<< R"(" "custom" ")"
<< this->MaybeConvertToRelativePath(
this->GetBinaryDirectory(), src)
<< "\"\n";
}
}
} else {
auto depFormat = this->Makefile->GetSafeDefinition(
cmStrCat("CMAKE_", compilerLang.first, "_DEPFILE_FORMAT"));
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";
}
}
}
}

View File

@ -1617,6 +1617,16 @@ void cmMakefileTargetGenerator::GenerateCustomRuleFile(
std::vector<std::string> depends;
this->LocalGenerator->AppendCustomDepend(depends, ccg);
if (!ccg.GetCC().GetDepfile().empty()) {
// Add dependency over timestamp file for dependencies management
auto dependTimestamp = cmSystemTools::ConvertToOutputPath(
this->LocalGenerator->MaybeConvertToRelativePath(
this->LocalGenerator->GetBinaryDirectory(),
cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts")));
depends.push_back(dependTimestamp);
}
// Write the rule.
const std::vector<std::string>& outputs = ccg.GetOutputs();
bool symbolic = this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs,
@ -1653,6 +1663,15 @@ void cmMakefileTargetGenerator::GenerateCustomRuleFile(
objFullPath, srcFullPath);
}
// Setup implicit depend for depfile if any
if (!ccg.GetCC().GetDepfile().empty()) {
std::string objFullPath = cmSystemTools::CollapseFullPath(
outputs[0], this->LocalGenerator->GetCurrentBinaryDirectory());
this->LocalGenerator->AddImplicitDepends(
this->GeneratorTarget, "CUSTOM", objFullPath, ccg.GetFullDepfile(),
cmDependencyScannerKind::Compiler);
}
this->CustomCommandOutputs.insert(outputs.begin(), outputs.end());
}

View File

@ -15,6 +15,7 @@
#include "cmLocalUnixMakefileGenerator3.h"
#include "cmMakefile.h"
#include "cmOSXBundleGenerator.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
cmMakefileUtilityTargetGenerator::cmMakefileUtilityTargetGenerator(
@ -36,10 +37,42 @@ void cmMakefileUtilityTargetGenerator::WriteRuleFiles()
*this->BuildFileStream << "# Utility rule file for "
<< this->GeneratorTarget->GetName() << ".\n\n";
const char* root = (this->Makefile->IsOn("CMAKE_MAKE_INCLUDE_FROM_ROOT")
? "$(CMAKE_BINARY_DIR)/"
: "");
// Include the dependencies for the target.
std::string dependFile =
cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make");
*this->BuildFileStream
<< "# Include any custom commands dependencies for this target.\n"
<< this->GlobalGenerator->IncludeDirective << " " << root
<< cmSystemTools::ConvertToOutputPath(
this->LocalGenerator->MaybeConvertToRelativePath(
this->LocalGenerator->GetBinaryDirectory(), dependFile))
<< "\n\n";
if (!cmSystemTools::FileExists(dependFile)) {
// Write an empty dependency file.
cmGeneratedFileStream depFileStream(
dependFile, false, this->GlobalGenerator->GetMakefileEncoding());
depFileStream << "# Empty custom commands generated dependencies file for "
<< this->GeneratorTarget->GetName() << ".\n"
<< "# This may be replaced when dependencies are built.\n";
}
std::string dependTimestamp =
cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts");
if (!cmSystemTools::FileExists(dependTimestamp)) {
// Write a dependency timestamp file.
cmGeneratedFileStream depFileStream(
dependTimestamp, false, this->GlobalGenerator->GetMakefileEncoding());
depFileStream << "# CMAKE generated file: DO NOT EDIT!\n"
<< "# Timestamp file for custom commands dependencies "
"management for "
<< this->GeneratorTarget->GetName() << ".\n";
}
if (!this->NoRuleMessages) {
const char* root = (this->Makefile->IsOn("CMAKE_MAKE_INCLUDE_FROM_ROOT")
? "$(CMAKE_BINARY_DIR)/"
: "");
// Include the progress variables for the target.
*this->BuildFileStream
<< "# Include the progress variables for this target.\n"

View File

@ -1272,6 +1272,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
snapshot.GetDirectory().SetCurrentBinary(startOutDir);
snapshot.GetDirectory().SetCurrentSource(startDir);
snapshot.GetDirectory().SetRelativePathTopSource(homeDir.c_str());
snapshot.GetDirectory().SetRelativePathTopBinary(homeOutDir.c_str());
cmMakefile mf(cm.GetGlobalGenerator(), snapshot);
auto lgd = cm.GetGlobalGenerator()->CreateLocalGenerator(&mf);

View File

@ -0,0 +1,4 @@
CMake Error at CustomCommandDependencies-BadArgs.cmake:[0-9]+ \(add_custom_command\):
add_custom_command IMPLICIT_DEPENDS and DEPFILE can not both be specified.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,10 @@
enable_language(C)
add_custom_command(OUTPUT main.c
DEPFILE main.c.d
IMPLICIT_DEPENDS C main.c.in
COMMAND "${CMAKE_COMMAND}" -DINFILE=main.c.in -DOUTFILE=main.c -DDEPFILE=main.c.d
-P "${CMAKE_CURRENT_SOURCE_DIR}/GenerateDepFile.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
add_custom_target(mainc ALL DEPENDS main.c)

View File

@ -0,0 +1,73 @@
enable_language(C)
add_custom_command(OUTPUT main.c
DEPFILE main.c.d
COMMAND "${CMAKE_COMMAND}" -DINFILE=main.c.in -DOUTFILE=main.c -DDEPFILE=main.c.d
-P "${CMAKE_CURRENT_SOURCE_DIR}/GenerateDepFile.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
add_custom_target(mainc ALL DEPENDS main.c)
add_executable(main ${CMAKE_CURRENT_BINARY_DIR}/main.c)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake CONTENT "
cmake_minimum_required(VERSION 3.19)
set(check_pairs
\"$<TARGET_FILE:main>|${CMAKE_CURRENT_BINARY_DIR}/main.c.in\"
\"$<TARGET_FILE:main>|${CMAKE_CURRENT_BINARY_DIR}/main.c\"
)
set(check_exes
\"$<TARGET_FILE:main>\"
)
if (check_step EQUAL 2)
include(\"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/Makefile.cmake\")
if (NOT CMAKE_DEPEND_INFO_FILES)
set(RunCMake_TEST_FAILED \"Variable CMAKE_DEPEND_INFO_FILES not found.\")
else()
foreach(DEPEND_INFO_FILE IN LISTS CMAKE_DEPEND_INFO_FILES)
include(\"${CMAKE_CURRENT_BINARY_DIR}/\${DEPEND_INFO_FILE}\")
if (NOT CMAKE_DEPENDS_DEPENDENCY_FILES)
set(RunCMake_TEST_FAILED \"Variable CMAKE_DEPENDS_DEPENDENCY_FILES not found.\")
else()
list(LENGTH CMAKE_DEPENDS_DEPENDENCY_FILES DEPENDENCY_FILES_SIZE)
math(EXPR STOP_INDEX \"\${DEPENDENCY_FILES_SIZE} - 1\")
foreach(INDEX RANGE 0 \${STOP_INDEX} 4)
math(EXPR OBJECT_INDEX \"\${INDEX} + 1\")
math(EXPR FORMAT_INDEX \"\${INDEX} + 2\")
math(EXPR DEP_INDEX \"\${INDEX} + 3\")
list(GET CMAKE_DEPENDS_DEPENDENCY_FILES \${OBJECT_INDEX} OBJECT_FILE)
list(GET CMAKE_DEPENDS_DEPENDENCY_FILES \${FORMAT_INDEX} DEP_FORMAT)
list(GET CMAKE_DEPENDS_DEPENDENCY_FILES \${DEP_INDEX} DEP_FILE)
if (NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/\${DEP_FILE}\")
set(RunCMake_TEST_FAILED \"File \${DEP_FILE} not found.\")
else()
cmake_path(APPEND TARGET_DEP_FILE \"${CMAKE_CURRENT_BINARY_DIR}\" \"\${DEPEND_INFO_FILE}\")
cmake_path(REPLACE_FILENAME TARGET_DEP_FILE \"compiler_depend.make\")
file(READ \"\${TARGET_DEP_FILE}\" DEPENDS_CONTENT)
if (WIN32)
string (REPLACE \"\\\\\" \"/\" DEPENDS_CONTENT \"\${DEPENDS_CONTENT}\")
string (TOLOWER \"\${DEPENDS_CONTENT}\" DEPENDS_CONTENT)
string (TOLOWER \"\${OBJECT_FILE}\" OBJECT_FILE)
else()
string(REPLACE \"\\\\ \" \" \" DEPENDS_CONTENT \"\${DEPENDS_CONTENT}\")
endif()
if(DEPEND_INFO_FILE MATCHES \"main\\\\.dir\")
if (DEP_FORMAT STREQUAL \"gcc\" AND NOT DEPENDS_CONTENT MATCHES \"\${OBJECT_FILE} *:.+main.c\")
set(RunCMake_TEST_FAILED \"Dependency file '\${TARGET_DEP_FILE}' badly generated:\\n\${DEPENDS_CONTENT}\")
endif()
if (DEP_FORMAT STREQUAL \"custom\" AND NOT DEPENDS_CONTENT MATCHES \"\${OBJECT_FILE} *:.+main.c.in\")
set(RunCMake_TEST_FAILED \"Dependency file '\${TARGET_DEP_FILE}' badly generated:\\n\${DEPENDS_CONTENT}\")
endif()
else()
if (NOT DEPENDS_CONTENT MATCHES \"\${OBJECT_FILE} *:.+main.c.in\")
set(RunCMake_TEST_FAILED \"Dependency file '\${TARGET_DEP_FILE}' badly generated:\\n\${DEPENDS_CONTENT}\")
endif()
endif()
endif()
endforeach()
endif()
endforeach()
endif()
endif()
")

View File

@ -0,0 +1,3 @@
file(WRITE "${RunCMake_TEST_BINARY_DIR}/main.c.in" [[
int main(void) { return 1; }
]])

View File

@ -0,0 +1,3 @@
file(WRITE "${RunCMake_TEST_BINARY_DIR}/main.c.in" [[
int main(void) { return 2; }
]])

View File

@ -0,0 +1,6 @@
file(READ "${INFILE}" INCONTENT)
file(WRITE "${OUTFILE}" "${INCONTENT}")
string(REPLACE [[ ]] [[\ ]] OUTFILE "${OUTFILE}")
string(REPLACE [[ ]] [[\ ]] INFILE "${INFILE}")
file(WRITE "${DEPFILE}" "${OUTFILE}: ${INFILE}\n")

View File

@ -122,4 +122,9 @@ if ((RunCMake_GENERATOR STREQUAL "Unix Makefiles"
AND MSVC_VERSION GREATER 1300
AND CMAKE_C_COMPILER_ID STREQUAL "MSVC"))
run_BuildDepends(CompilerDependencies)
run_BuildDepends(CustomCommandDependencies)
endif()
if (RunCMake_GENERATOR MATCHES "Makefiles")
run_cmake(CustomCommandDependencies-BadArgs)
endif()

View File

@ -1,5 +0,0 @@
^CMake Error at CustomCommandDepfile-ERROR.cmake:1 \(add_custom_command\):
add_custom_command Option DEPFILE not supported by [^
]+
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -1,8 +0,0 @@
add_custom_command(
OUTPUT hello.copy.c
COMMAND "${CMAKE_COMMAND}" -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/hello.c"
hello.copy.c
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DEPFILE "test.d"
)

View File

@ -39,7 +39,6 @@ function(run_VerboseBuild)
endfunction()
run_VerboseBuild()
run_cmake(CustomCommandDepfile-ERROR)
run_cmake(IncludeRegexSubdir)
function(run_MakefileConflict)