
Internally we mark `file(GENERATE)` outputs as `GENERATED` in order to tell custom command dependency tracing logic not to expect the files to exist on disk yet. This is because we do not generate the files until after that tracing is done. The Ninja generator also interprets the `GENERATED` property to mean that it is expected that some build rule will generate the file if another build rule depends on it. If the generator does not know of a custom command that generates the file then it adds an empty one so that the `ninja` build tool does not complain about a dependency on a file that does not exist and has no rule to generate it. However, this step is not necessary for `file(GENERATE)` outputs because there is no build rule to generate them and they will exist before `ninja` runs. Add an additional `__CMAKE_GENERATED_BY_CMAKE` property internally to tell the Ninja generator that a `GENERATED` file will exist before the build starts and is not expected to have a build rule producing it. Fixes: #17942
234 lines
7.6 KiB
C++
234 lines
7.6 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmGeneratorExpressionEvaluationFile.h"
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
#include <memory> // IWYU pragma: keep
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmListFileCache.h"
|
|
#include "cmLocalGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmSourceFile.h"
|
|
#include "cmSourceFileLocationKind.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmake.h"
|
|
|
|
cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
|
|
const std::string& input,
|
|
std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
|
|
std::unique_ptr<cmCompiledGeneratorExpression> condition,
|
|
bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070)
|
|
: Input(input)
|
|
, OutputFileExpr(std::move(outputFileExpr))
|
|
, Condition(std::move(condition))
|
|
, InputIsContent(inputIsContent)
|
|
, PolicyStatusCMP0070(policyStatusCMP0070)
|
|
{
|
|
}
|
|
|
|
void cmGeneratorExpressionEvaluationFile::Generate(
|
|
cmLocalGenerator* lg, const std::string& config, const std::string& lang,
|
|
cmCompiledGeneratorExpression* inputExpression,
|
|
std::map<std::string, std::string>& outputFiles, mode_t perm)
|
|
{
|
|
std::string rawCondition = this->Condition->GetInput();
|
|
if (!rawCondition.empty()) {
|
|
std::string condResult = this->Condition->Evaluate(
|
|
lg, config, false, nullptr, nullptr, nullptr, lang);
|
|
if (condResult == "0") {
|
|
return;
|
|
}
|
|
if (condResult != "1") {
|
|
std::ostringstream e;
|
|
e << "Evaluation file condition \"" << rawCondition
|
|
<< "\" did "
|
|
"not evaluate to valid content. Got \""
|
|
<< condResult << "\".";
|
|
lg->IssueMessage(cmake::FATAL_ERROR, e.str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::string outputFileName = this->OutputFileExpr->Evaluate(
|
|
lg, config, false, nullptr, nullptr, nullptr, lang);
|
|
const std::string outputContent = inputExpression->Evaluate(
|
|
lg, config, false, nullptr, nullptr, nullptr, lang);
|
|
|
|
if (cmSystemTools::FileIsFullPath(outputFileName)) {
|
|
outputFileName = cmSystemTools::CollapseFullPath(outputFileName);
|
|
} else {
|
|
outputFileName = this->FixRelativePath(outputFileName, PathForOutput, lg);
|
|
}
|
|
|
|
std::map<std::string, std::string>::iterator it =
|
|
outputFiles.find(outputFileName);
|
|
|
|
if (it != outputFiles.end()) {
|
|
if (it->second == outputContent) {
|
|
return;
|
|
}
|
|
std::ostringstream e;
|
|
e << "Evaluation file to be written multiple times with different "
|
|
"content. "
|
|
"This is generally caused by the content evaluating the "
|
|
"configuration type, language, or location of object files:\n "
|
|
<< outputFileName;
|
|
lg->IssueMessage(cmake::FATAL_ERROR, e.str());
|
|
return;
|
|
}
|
|
|
|
lg->GetMakefile()->AddCMakeOutputFile(outputFileName);
|
|
this->Files.push_back(outputFileName);
|
|
outputFiles[outputFileName] = outputContent;
|
|
|
|
cmGeneratedFileStream fout(outputFileName.c_str());
|
|
fout.SetCopyIfDifferent(true);
|
|
fout << outputContent;
|
|
if (fout.Close() && perm) {
|
|
cmSystemTools::SetPermissions(outputFileName.c_str(), perm);
|
|
}
|
|
}
|
|
|
|
void cmGeneratorExpressionEvaluationFile::CreateOutputFile(
|
|
cmLocalGenerator* lg, std::string const& config)
|
|
{
|
|
std::vector<std::string> enabledLanguages;
|
|
cmGlobalGenerator* gg = lg->GetGlobalGenerator();
|
|
gg->GetEnabledLanguages(enabledLanguages);
|
|
|
|
for (std::string const& le : enabledLanguages) {
|
|
std::string name = this->OutputFileExpr->Evaluate(
|
|
lg, config, false, nullptr, nullptr, nullptr, le);
|
|
cmSourceFile* sf = lg->GetMakefile()->GetOrCreateSource(
|
|
name, false, cmSourceFileLocationKind::Known);
|
|
// Tell TraceDependencies that the file is not expected to exist
|
|
// on disk yet. We generate it after that runs.
|
|
sf->SetProperty("GENERATED", "1");
|
|
|
|
// Tell the build system generators that there is no build rule
|
|
// to generate the file.
|
|
sf->SetProperty("__CMAKE_GENERATED_BY_CMAKE", "1");
|
|
|
|
gg->SetFilenameTargetDepends(
|
|
sf, this->OutputFileExpr->GetSourceSensitiveTargets());
|
|
}
|
|
}
|
|
|
|
void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
|
|
{
|
|
mode_t perm = 0;
|
|
std::string inputContent;
|
|
if (this->InputIsContent) {
|
|
inputContent = this->Input;
|
|
} else {
|
|
std::string inputFileName = this->Input;
|
|
if (cmSystemTools::FileIsFullPath(inputFileName)) {
|
|
inputFileName = cmSystemTools::CollapseFullPath(inputFileName);
|
|
} else {
|
|
inputFileName = this->FixRelativePath(inputFileName, PathForInput, lg);
|
|
}
|
|
lg->GetMakefile()->AddCMakeDependFile(inputFileName);
|
|
cmSystemTools::GetPermissions(inputFileName.c_str(), perm);
|
|
cmsys::ifstream fin(inputFileName.c_str());
|
|
if (!fin) {
|
|
std::ostringstream e;
|
|
e << "Evaluation file \"" << inputFileName << "\" cannot be read.";
|
|
lg->IssueMessage(cmake::FATAL_ERROR, e.str());
|
|
return;
|
|
}
|
|
|
|
std::string line;
|
|
std::string sep;
|
|
while (cmSystemTools::GetLineFromStream(fin, line)) {
|
|
inputContent += sep + line;
|
|
sep = "\n";
|
|
}
|
|
inputContent += sep;
|
|
}
|
|
|
|
cmListFileBacktrace lfbt = this->OutputFileExpr->GetBacktrace();
|
|
cmGeneratorExpression contentGE(lfbt);
|
|
std::unique_ptr<cmCompiledGeneratorExpression> inputExpression =
|
|
contentGE.Parse(inputContent);
|
|
|
|
std::map<std::string, std::string> outputFiles;
|
|
|
|
std::vector<std::string> allConfigs;
|
|
lg->GetMakefile()->GetConfigurations(allConfigs);
|
|
|
|
if (allConfigs.empty()) {
|
|
allConfigs.emplace_back();
|
|
}
|
|
|
|
std::vector<std::string> enabledLanguages;
|
|
cmGlobalGenerator* gg = lg->GetGlobalGenerator();
|
|
gg->GetEnabledLanguages(enabledLanguages);
|
|
|
|
for (std::string const& le : enabledLanguages) {
|
|
for (std::string const& li : allConfigs) {
|
|
this->Generate(lg, li, le, inputExpression.get(), outputFiles, perm);
|
|
if (cmSystemTools::GetFatalErrorOccured()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string cmGeneratorExpressionEvaluationFile::FixRelativePath(
|
|
std::string const& relativePath, PathRole role, cmLocalGenerator* lg)
|
|
{
|
|
std::string resultPath;
|
|
switch (this->PolicyStatusCMP0070) {
|
|
case cmPolicies::WARN: {
|
|
std::string arg;
|
|
switch (role) {
|
|
case PathForInput:
|
|
arg = "INPUT";
|
|
break;
|
|
case PathForOutput:
|
|
arg = "OUTPUT";
|
|
break;
|
|
}
|
|
std::ostringstream w;
|
|
/* clang-format off */
|
|
w <<
|
|
cmPolicies::GetPolicyWarning(cmPolicies::CMP0070) << "\n"
|
|
"file(GENERATE) given relative " << arg << " path:\n"
|
|
" " << relativePath << "\n"
|
|
"This is not defined behavior unless CMP0070 is set to NEW. "
|
|
"For compatibility with older versions of CMake, the previous "
|
|
"undefined behavior will be used."
|
|
;
|
|
/* clang-format on */
|
|
lg->IssueMessage(cmake::AUTHOR_WARNING, w.str());
|
|
}
|
|
CM_FALLTHROUGH;
|
|
case cmPolicies::OLD:
|
|
// OLD behavior is to use the relative path unchanged,
|
|
// which ends up being used relative to the working dir.
|
|
resultPath = relativePath;
|
|
break;
|
|
case cmPolicies::REQUIRED_IF_USED:
|
|
case cmPolicies::REQUIRED_ALWAYS:
|
|
case cmPolicies::NEW:
|
|
// NEW behavior is to interpret the relative path with respect
|
|
// to the current source or binary directory.
|
|
switch (role) {
|
|
case PathForInput:
|
|
resultPath = cmSystemTools::CollapseFullPath(
|
|
relativePath, lg->GetCurrentSourceDirectory());
|
|
break;
|
|
case PathForOutput:
|
|
resultPath = cmSystemTools::CollapseFullPath(
|
|
relativePath, lg->GetCurrentBinaryDirectory());
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return resultPath;
|
|
}
|