Files
CMake/Source/cmQtAutoGeneratorInitializer.cxx
T
Sebastian Holtermann 513eb014eb Autogen: Ignore not existing source files in cmMakefile
Until CMake 3.10 a list of source files that had the AUTOUIC_OPTIONS property
populated was kept in `cmMakefile::QtUiFilesWithOptions`.  In the process to
remove all AUTOUIC related code from `cmMakefile` for CMake 3.10, the pre
filtered list was replaced by a loop in `cmQtAutoGeneratorInitializer` over
all source files in the `cmMakefile`.  This loop introduced the problem that
file paths were computed for source files that weren't in the target's sources
and that might not even have existed.  If the path for an unused and not
existing file was computed a `cmake::FATAL_ERROR` with the error message
"Cannot find source file:" was thrown nevertheless.
This caused some projects to fail in CMake 3.10.

This patch adds a test for path errors in the loops in
`cmQtAutoGeneratorInitializer` that iterate over all source files in a
`cmMakefile`. If a path error appears, the file is silently ignored.
If the file is part of the target's sources, the path error will still be
caught in the loop over all the target's sources.

Closes #17573
Closes #17589
2018-01-10 18:57:08 +01:00

1359 lines
46 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoGen.h"
#include "cmQtAutoGeneratorInitializer.h"
#include "cmAlgorithms.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmFilePathChecksum.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmOutputConverter.h"
#include "cmPolicies.h"
#include "cmProcessOutput.h"
#include "cmSourceFile.h"
#include "cmSourceGroup.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cm_sys_stat.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
#include <algorithm>
#include <array>
#include <deque>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
inline static const char* SafeString(const char* value)
{
return (value != nullptr) ? value : "";
}
inline static std::string GetSafeProperty(cmGeneratorTarget const* target,
const char* key)
{
return std::string(SafeString(target->GetProperty(key)));
}
inline static std::string GetSafeProperty(cmSourceFile const* sf,
const char* key)
{
return std::string(SafeString(sf->GetProperty(key)));
}
static void AddDefinitionEscaped(cmMakefile* makefile, const char* key,
std::string const& value)
{
makefile->AddDefinition(key,
cmOutputConverter::EscapeForCMake(value).c_str());
}
static void AddDefinitionEscaped(cmMakefile* makefile, const char* key,
const std::vector<std::string>& values)
{
makefile->AddDefinition(
key, cmOutputConverter::EscapeForCMake(cmJoin(values, ";")).c_str());
}
static void AddDefinitionEscaped(cmMakefile* makefile, const char* key,
const std::set<std::string>& values)
{
makefile->AddDefinition(
key, cmOutputConverter::EscapeForCMake(cmJoin(values, ";")).c_str());
}
static void AddDefinitionEscaped(
cmMakefile* makefile, const char* key,
const std::vector<std::vector<std::string>>& lists)
{
std::vector<std::string> seplist;
for (const std::vector<std::string>& list : lists) {
std::string blist = "{";
blist += cmJoin(list, ";");
blist += "}";
seplist.push_back(std::move(blist));
}
makefile->AddDefinition(key, cmOutputConverter::EscapeForCMake(
cmJoin(seplist, cmQtAutoGen::listSep))
.c_str());
}
static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
cmQtAutoGen::Generator genType)
{
cmSourceGroup* sourceGroup = nullptr;
// Acquire source group
{
std::string property;
std::string groupName;
{
std::array<std::string, 2> props;
// Use generator specific group name
switch (genType) {
case cmQtAutoGen::MOC:
props[0] = "AUTOMOC_SOURCE_GROUP";
break;
case cmQtAutoGen::RCC:
props[0] = "AUTORCC_SOURCE_GROUP";
break;
default:
props[0] = "AUTOGEN_SOURCE_GROUP";
break;
}
props[1] = "AUTOGEN_SOURCE_GROUP";
for (std::string& prop : props) {
const char* propName = makefile->GetState()->GetGlobalProperty(prop);
if ((propName != nullptr) && (*propName != '\0')) {
groupName = propName;
property = std::move(prop);
break;
}
}
}
// Generate a source group on demand
if (!groupName.empty()) {
sourceGroup = makefile->GetOrCreateSourceGroup(groupName);
if (sourceGroup == nullptr) {
std::ostringstream ost;
ost << cmQtAutoGen::GeneratorNameUpper(genType);
ost << ": " << property;
ost << ": Could not find or create the source group ";
ost << cmQtAutoGen::Quoted(groupName);
cmSystemTools::Error(ost.str().c_str());
return false;
}
}
}
if (sourceGroup != nullptr) {
sourceGroup->AddGroupFile(fileName);
}
return true;
}
static void AddCleanFile(cmMakefile* makefile, std::string const& fileName)
{
makefile->AppendProperty("ADDITIONAL_MAKE_CLEAN_FILES", fileName.c_str(),
false);
}
static std::string FileProjectRelativePath(cmMakefile* makefile,
std::string const& fileName)
{
std::string res;
{
std::string pSource = cmSystemTools::RelativePath(
makefile->GetCurrentSourceDirectory(), fileName.c_str());
std::string pBinary = cmSystemTools::RelativePath(
makefile->GetCurrentBinaryDirectory(), fileName.c_str());
if (pSource.size() < pBinary.size()) {
res = std::move(pSource);
} else if (pBinary.size() < fileName.size()) {
res = std::move(pBinary);
} else {
res = fileName;
}
}
return res;
}
/* @brief Tests if targetDepend is a STATIC_LIBRARY and if any of its
* recursive STATIC_LIBRARY dependencies depends on targetOrigin
* (STATIC_LIBRARY cycle).
*/
static bool StaticLibraryCycle(cmGeneratorTarget const* targetOrigin,
cmGeneratorTarget const* targetDepend,
std::string const& config)
{
bool cycle = false;
if ((targetOrigin->GetType() == cmStateEnums::STATIC_LIBRARY) &&
(targetDepend->GetType() == cmStateEnums::STATIC_LIBRARY)) {
std::set<cmGeneratorTarget const*> knownLibs;
std::deque<cmGeneratorTarget const*> testLibs;
// Insert initial static_library dependency
knownLibs.insert(targetDepend);
testLibs.push_back(targetDepend);
while (!testLibs.empty()) {
cmGeneratorTarget const* testTarget = testLibs.front();
testLibs.pop_front();
// Check if the test target is the origin target (cycle)
if (testTarget == targetOrigin) {
cycle = true;
break;
}
// Collect all static_library dependencies from the test target
cmLinkImplementationLibraries const* libs =
testTarget->GetLinkImplementationLibraries(config);
if (libs != nullptr) {
for (cmLinkItem const& item : libs->Libraries) {
cmGeneratorTarget const* depTarget = item.Target;
if ((depTarget != nullptr) &&
(depTarget->GetType() == cmStateEnums::STATIC_LIBRARY) &&
knownLibs.insert(depTarget).second) {
testLibs.push_back(depTarget);
}
}
}
}
}
return cycle;
}
cmQtAutoGeneratorInitializer::cmQtAutoGeneratorInitializer(
cmGeneratorTarget* target, bool mocEnabled, bool uicEnabled, bool rccEnabled,
std::string const& qtVersionMajor)
: Target(target)
, MocEnabled(mocEnabled)
, UicEnabled(uicEnabled)
, RccEnabled(rccEnabled)
, QtVersionMajor(qtVersionMajor)
, MultiConfig(cmQtAutoGen::WRAP)
{
this->QtVersionMinor = cmQtAutoGeneratorInitializer::GetQtMinorVersion(
target, this->QtVersionMajor);
}
void cmQtAutoGeneratorInitializer::InitCustomTargets()
{
cmMakefile* makefile = this->Target->Target->GetMakefile();
cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
cmGlobalGenerator* globalGen = localGen->GetGlobalGenerator();
// Configurations
this->ConfigDefault = makefile->GetConfigurations(this->ConfigsList);
if (this->ConfigsList.empty()) {
this->ConfigsList.push_back(this->ConfigDefault);
}
// Multi configuration
{
if (!globalGen->IsMultiConfig()) {
this->MultiConfig = cmQtAutoGen::SINGLE;
}
// FIXME: Xcode does not support per-config sources, yet.
// (EXCLUDED_SOURCE_FILE_NAMES)
// if (globalGen->GetName().find("Xcode") != std::string::npos) {
// return cmQtAutoGen::FULL;
//}
// FIXME: Visual Studio does not support per-config sources, yet.
// (EXCLUDED_SOURCE_FILE_NAMES)
// if (globalGen->GetName().find("Visual Studio") != std::string::npos) {
// return cmQtAutoGen::FULL;
//}
}
// Autogen target name
this->AutogenTargetName = this->Target->GetName();
this->AutogenTargetName += "_autogen";
// Autogen directories
{
// Collapsed current binary directory
std::string const cbd = cmSystemTools::CollapseFullPath(
"", makefile->GetCurrentBinaryDirectory());
// Autogen info dir
this->DirInfo = cbd;
this->DirInfo += makefile->GetCMakeInstance()->GetCMakeFilesDirectory();
this->DirInfo += "/";
this->DirInfo += this->AutogenTargetName;
this->DirInfo += ".dir";
cmSystemTools::ConvertToUnixSlashes(this->DirInfo);
// Autogen build dir
this->DirBuild = GetSafeProperty(this->Target, "AUTOGEN_BUILD_DIR");
if (this->DirBuild.empty()) {
this->DirBuild = cbd;
this->DirBuild += "/";
this->DirBuild += this->AutogenTargetName;
}
cmSystemTools::ConvertToUnixSlashes(this->DirBuild);
// Working directory
this->DirWork = cbd;
cmSystemTools::ConvertToUnixSlashes(this->DirWork);
}
// Autogen files
{
this->AutogenInfoFile = this->DirInfo;
this->AutogenInfoFile += "/AutogenInfo.cmake";
this->AutogenSettingsFile = this->DirInfo;
this->AutogenSettingsFile += "/AutogenOldSettings.cmake";
}
// Autogen target FOLDER property
{
const char* folder =
makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER");
if (folder == nullptr) {
folder =
makefile->GetState()->GetGlobalProperty("AUTOGEN_TARGETS_FOLDER");
}
// Inherit FOLDER property from target (#13688)
if (folder == nullptr) {
folder = SafeString(this->Target->Target->GetProperty("FOLDER"));
}
if (folder != nullptr) {
this->AutogenFolder = folder;
}
}
std::set<std::string> autogenDependFiles;
std::set<cmTarget*> autogenDependTargets;
std::vector<std::string> autogenProvides;
// Remove build directories on cleanup
AddCleanFile(makefile, this->DirBuild);
// Remove old settings on cleanup
{
std::string base = this->DirInfo;
base += "/AutogenOldSettings";
if (this->MultiConfig == cmQtAutoGen::SINGLE) {
AddCleanFile(makefile, base.append(".cmake"));
} else {
for (std::string const& cfg : this->ConfigsList) {
std::string filename = base;
filename += "_";
filename += cfg;
filename += ".cmake";
AddCleanFile(makefile, filename);
}
}
}
// Add moc compilation to generated files list
if (this->MocEnabled) {
std::string const mocsComp = this->DirBuild + "/mocs_compilation.cpp";
auto files = this->AddGeneratedSource(mocsComp, cmQtAutoGen::MOC);
for (std::string& file : files) {
autogenProvides.push_back(std::move(file));
}
}
// Add autogen includes directory to the origin target INCLUDE_DIRECTORIES
if (this->MocEnabled || this->UicEnabled) {
std::string includeDir = this->DirBuild + "/include";
if (this->MultiConfig != cmQtAutoGen::SINGLE) {
includeDir += "_$<CONFIG>";
}
this->Target->AddIncludeDirectory(includeDir, true);
}
// Acquire rcc executable and features
if (this->RccEnabled) {
{
std::string err;
if (this->QtVersionMajor == "5") {
cmGeneratorTarget* tgt =
localGen->FindGeneratorTargetToUse("Qt5::rcc");
if (tgt != nullptr) {
this->RccExecutable = SafeString(tgt->ImportedGetLocation(""));
} else {
err = "AUTORCC: Qt5::rcc target not found";
}
} else if (QtVersionMajor == "4") {
cmGeneratorTarget* tgt =
localGen->FindGeneratorTargetToUse("Qt4::rcc");
if (tgt != nullptr) {
this->RccExecutable = SafeString(tgt->ImportedGetLocation(""));
} else {
err = "AUTORCC: Qt4::rcc target not found";
}
} else {
err = "The AUTORCC feature supports only Qt 4 and Qt 5";
}
if (!err.empty()) {
err += " (";
err += this->Target->GetName();
err += ")";
cmSystemTools::Error(err.c_str());
}
}
// Detect if rcc supports (-)-list
if (!this->RccExecutable.empty() && (this->QtVersionMajor == "5")) {
std::vector<std::string> command;
command.push_back(this->RccExecutable);
command.push_back("--help");
std::string rccStdOut;
std::string rccStdErr;
int retVal = 0;
bool result = cmSystemTools::RunSingleCommand(
command, &rccStdOut, &rccStdErr, &retVal, nullptr,
cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto);
if (result && retVal == 0 &&
rccStdOut.find("--list") != std::string::npos) {
this->RccListOptions.push_back("--list");
} else {
this->RccListOptions.push_back("-list");
}
}
}
// Extract relevant source files
std::vector<std::string> generatedSources;
std::vector<std::string> generatedHeaders;
{
std::string const qrcExt = "qrc";
std::vector<cmSourceFile*> srcFiles;
this->Target->GetConfigCommonSourceFiles(srcFiles);
for (cmSourceFile* sf : srcFiles) {
if (sf->GetPropertyAsBool("SKIP_AUTOGEN")) {
continue;
}
// sf->GetExtension() is only valid after sf->GetFullPath() ...
std::string const& fPath = sf->GetFullPath();
std::string const& ext = sf->GetExtension();
// Register generated files that will be scanned by moc or uic
if (this->MocEnabled || this->UicEnabled) {
cmSystemTools::FileFormat const fileType =
cmSystemTools::GetFileFormat(ext.c_str());
if ((fileType == cmSystemTools::CXX_FILE_FORMAT) ||
(fileType == cmSystemTools::HEADER_FILE_FORMAT)) {
std::string const absPath = cmSystemTools::GetRealPath(fPath);
if ((this->MocEnabled && !sf->GetPropertyAsBool("SKIP_AUTOMOC")) ||
(this->UicEnabled && !sf->GetPropertyAsBool("SKIP_AUTOUIC"))) {
// Register source
const bool generated = sf->GetPropertyAsBool("GENERATED");
if (fileType == cmSystemTools::HEADER_FILE_FORMAT) {
if (generated) {
generatedHeaders.push_back(absPath);
} else {
this->Headers.push_back(absPath);
}
} else {
if (generated) {
generatedSources.push_back(absPath);
} else {
this->Sources.push_back(absPath);
}
}
}
}
}
// Register rcc enabled files
if (this->RccEnabled && (ext == qrcExt) &&
!sf->GetPropertyAsBool("SKIP_AUTORCC")) {
// Register qrc file
{
Qrc qrc;
qrc.QrcFile = cmSystemTools::GetRealPath(fPath);
qrc.QrcName =
cmSystemTools::GetFilenameWithoutLastExtension(qrc.QrcFile);
qrc.Generated = sf->GetPropertyAsBool("GENERATED");
// RCC options
{
std::string const opts = GetSafeProperty(sf, "AUTORCC_OPTIONS");
if (!opts.empty()) {
cmSystemTools::ExpandListArgument(opts, qrc.Options);
}
}
this->Qrcs.push_back(std::move(qrc));
}
}
}
// cmGeneratorTarget::GetConfigCommonSourceFiles computes the target's
// sources meta data cache. Clear it so that OBJECT library targets that
// are AUTOGEN initialized after this target get their added
// mocs_compilation.cpp source acknowledged by this target.
this->Target->ClearSourcesCache();
}
// Read skip files from makefile sources
if (this->MocEnabled || this->UicEnabled) {
std::string pathError;
for (cmSourceFile* sf : makefile->GetSourceFiles()) {
// sf->GetExtension() is only valid after sf->GetFullPath() ...
// Since we're iterating over source files that might be not in the
// target we need to check for path errors (not existing files).
std::string const& fPath = sf->GetFullPath(&pathError);
if (!pathError.empty()) {
pathError.clear();
continue;
}
cmSystemTools::FileFormat const fileType =
cmSystemTools::GetFileFormat(sf->GetExtension().c_str());
if (!(fileType == cmSystemTools::CXX_FILE_FORMAT) &&
!(fileType == cmSystemTools::HEADER_FILE_FORMAT)) {
continue;
}
const bool skipAll = sf->GetPropertyAsBool("SKIP_AUTOGEN");
const bool mocSkip =
this->MocEnabled && (skipAll || sf->GetPropertyAsBool("SKIP_AUTOMOC"));
const bool uicSkip =
this->UicEnabled && (skipAll || sf->GetPropertyAsBool("SKIP_AUTOUIC"));
if (mocSkip || uicSkip) {
std::string const absFile = cmSystemTools::GetRealPath(fPath);
if (mocSkip) {
this->MocSkip.insert(absFile);
}
if (uicSkip) {
this->UicSkip.insert(absFile);
}
}
}
}
// Process GENERATED sources and headers
if (!generatedSources.empty() || !generatedHeaders.empty()) {
// Check status of policy CMP0071
bool policyAccept = false;
bool policyWarn = false;
cmPolicies::PolicyStatus const CMP0071_status =
makefile->GetPolicyStatus(cmPolicies::CMP0071);
switch (CMP0071_status) {
case cmPolicies::WARN:
policyWarn = true;
CM_FALLTHROUGH;
case cmPolicies::OLD:
// Ignore GENERATED file
break;
case cmPolicies::REQUIRED_IF_USED:
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::NEW:
// Process GENERATED file
policyAccept = true;
break;
}
if (policyAccept) {
// Accept GENERATED sources
for (std::string const& absFile : generatedHeaders) {
this->Headers.push_back(absFile);
autogenDependFiles.insert(absFile);
}
for (std::string const& absFile : generatedSources) {
this->Sources.push_back(absFile);
autogenDependFiles.insert(absFile);
}
} else {
if (policyWarn) {
std::string msg;
msg += cmPolicies::GetPolicyWarning(cmPolicies::CMP0071);
msg += "\n";
std::string tools;
std::string property;
if (this->MocEnabled && this->UicEnabled) {
tools = "AUTOMOC and AUTOUIC";
property = "SKIP_AUTOGEN";
} else if (this->MocEnabled) {
tools = "AUTOMOC";
property = "SKIP_AUTOMOC";
} else if (this->UicEnabled) {
tools = "AUTOUIC";
property = "SKIP_AUTOUIC";
}
msg += "For compatibility, CMake is excluding the GENERATED source "
"file(s):\n";
for (const std::string& absFile : generatedHeaders) {
msg.append(" ").append(cmQtAutoGen::Quoted(absFile)).append("\n");
}
for (const std::string& absFile : generatedSources) {
msg.append(" ").append(cmQtAutoGen::Quoted(absFile)).append("\n");
}
msg += "from processing by ";
msg += tools;
msg +=
". If any of the files should be processed, set CMP0071 to NEW. "
"If any of the files should not be processed, "
"explicitly exclude them by setting the source file property ";
msg += property;
msg += ":\n set_property(SOURCE file.h PROPERTY ";
msg += property;
msg += " ON)\n";
makefile->IssueMessage(cmake::AUTHOR_WARNING, msg);
}
}
// Clear lists
generatedSources.clear();
generatedHeaders.clear();
}
// Sort headers and sources
if (this->MocEnabled || this->UicEnabled) {
std::sort(this->Headers.begin(), this->Headers.end());
std::sort(this->Sources.begin(), this->Sources.end());
}
// Process qrc files
if (!this->Qrcs.empty()) {
const bool QtV5 = (this->QtVersionMajor == "5");
// Target rcc options
std::vector<std::string> optionsTarget;
cmSystemTools::ExpandListArgument(
GetSafeProperty(this->Target, "AUTORCC_OPTIONS"), optionsTarget);
// Check if file name is unique
for (Qrc& qrc : this->Qrcs) {
qrc.Unique = true;
for (Qrc const& qrc2 : this->Qrcs) {
if ((&qrc != &qrc2) && (qrc.QrcName == qrc2.QrcName)) {
qrc.Unique = false;
break;
}
}
}
// Path checksum and file names
{
cmFilePathChecksum const fpathCheckSum(makefile);
for (Qrc& qrc : this->Qrcs) {
qrc.PathChecksum = fpathCheckSum.getPart(qrc.QrcFile);
// RCC output file name
{
std::string rccFile = this->DirBuild + "/";
rccFile += qrc.PathChecksum;
rccFile += "/qrc_";
rccFile += qrc.QrcName;
rccFile += ".cpp";
qrc.RccFile = std::move(rccFile);
}
{
std::string base = this->DirInfo;
base += "/RCC";
base += qrc.QrcName;
if (!qrc.Unique) {
base += qrc.PathChecksum;
}
qrc.InfoFile = base;
qrc.InfoFile += "Info.cmake";
qrc.SettingsFile = base;
qrc.SettingsFile += "Settings.cmake";
}
}
}
// RCC options
for (Qrc& qrc : this->Qrcs) {
// Target options
std::vector<std::string> opts = optionsTarget;
// Merge computed "-name XYZ" option
{
std::string name = qrc.QrcName;
// Replace '-' with '_'. The former is not valid for symbol names.
std::replace(name.begin(), name.end(), '-', '_');
if (!qrc.Unique) {
name += "_";
name += qrc.PathChecksum;
}
std::vector<std::string> nameOpts;
nameOpts.emplace_back("-name");
nameOpts.emplace_back(std::move(name));
cmQtAutoGen::RccMergeOptions(opts, nameOpts, QtV5);
}
// Merge file option
cmQtAutoGen::RccMergeOptions(opts, qrc.Options, QtV5);
qrc.Options = std::move(opts);
}
for (Qrc& qrc : this->Qrcs) {
// Register file at target
std::vector<std::string> const ccOutput =
this->AddGeneratedSource(qrc.RccFile, cmQtAutoGen::RCC);
cmCustomCommandLines commandLines;
{
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back("-E");
currentLine.push_back("cmake_autorcc");
currentLine.push_back(qrc.InfoFile);
currentLine.push_back("$<CONFIGURATION>");
commandLines.push_back(std::move(currentLine));
}
std::string ccComment = "Automatic RCC for ";
ccComment += FileProjectRelativePath(makefile, qrc.QrcFile);
if (qrc.Generated) {
// Create custom rcc target
std::string ccName;
{
ccName = this->Target->GetName();
ccName += "_arcc_";
ccName += qrc.QrcName;
if (!qrc.Unique) {
ccName += "_";
ccName += qrc.PathChecksum;
}
std::vector<std::string> ccDepends;
// Add the .qrc file to the custom target dependencies
ccDepends.push_back(qrc.QrcFile);
cmTarget* autoRccTarget = makefile->AddUtilityCommand(
ccName, cmMakefile::TargetOrigin::Generator, true,
this->DirWork.c_str(), ccOutput, ccDepends, commandLines, false,
ccComment.c_str());
// Create autogen generator target
localGen->AddGeneratorTarget(
new cmGeneratorTarget(autoRccTarget, localGen));
// Set FOLDER property in autogen target
if (!this->AutogenFolder.empty()) {
autoRccTarget->SetProperty("FOLDER", this->AutogenFolder.c_str());
}
}
// Add autogen target to the origin target dependencies
this->Target->Target->AddUtility(ccName, makefile);
} else {
// Create custom rcc command
{
std::vector<std::string> ccByproducts;
std::vector<std::string> ccDepends;
// Add the .qrc file to the custom command dependencies
ccDepends.push_back(qrc.QrcFile);
// Add the resource files to the dependencies
{
std::string error;
if (cmQtAutoGen::RccListInputs(this->RccExecutable,
this->RccListOptions, qrc.QrcFile,
qrc.Resources, &error)) {
for (std::string const& fileName : qrc.Resources) {
// Add resource file to the custom command dependencies
ccDepends.push_back(fileName);
}
} else {
cmSystemTools::Error(error.c_str());
}
}
makefile->AddCustomCommandToOutput(ccOutput, ccByproducts, ccDepends,
/*main_dependency*/ std::string(),
commandLines, ccComment.c_str(),
this->DirWork.c_str());
}
// Reconfigure when .qrc file changes
makefile->AddCMakeDependFile(qrc.QrcFile);
}
}
}
// Create _autogen target
if (this->MocEnabled || this->UicEnabled) {
// Add user defined autogen target dependencies
{
std::string const deps =
GetSafeProperty(this->Target, "AUTOGEN_TARGET_DEPENDS");
if (!deps.empty()) {
std::vector<std::string> extraDeps;
cmSystemTools::ExpandListArgument(deps, extraDeps);
for (std::string const& depName : extraDeps) {
// Allow target and file dependencies
auto* depTarget = makefile->FindTargetToUse(depName);
if (depTarget != nullptr) {
autogenDependTargets.insert(depTarget);
} else {
autogenDependFiles.insert(depName);
}
}
}
}
// Compose target comment
std::string autogenComment;
{
std::string tools;
if (this->MocEnabled) {
tools += "MOC";
}
if (this->UicEnabled) {
if (!tools.empty()) {
tools += " and ";
}
tools += "UIC";
}
autogenComment = "Automatic ";
autogenComment += tools;
autogenComment += " for target ";
autogenComment += this->Target->GetName();
}
// Compose command lines
cmCustomCommandLines commandLines;
{
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back("-E");
currentLine.push_back("cmake_autogen");
currentLine.push_back(this->AutogenInfoFile);
currentLine.push_back("$<CONFIGURATION>");
commandLines.push_back(std::move(currentLine));
}
// Use PRE_BUILD on demand
bool usePRE_BUILD = false;
if (globalGen->GetName().find("Visual Studio") != std::string::npos) {
// Under VS use a PRE_BUILD event instead of a separate target to
// reduce the number of targets loaded into the IDE.
// This also works around a VS 11 bug that may skip updating the target:
// https://connect.microsoft.com/VisualStudio/feedback/details/769495
usePRE_BUILD = true;
}
// Disable PRE_BUILD in some cases
if (usePRE_BUILD) {
// Cannot use PRE_BUILD with file depends
if (!autogenDependFiles.empty()) {
usePRE_BUILD = false;
}
}
// Create the autogen target/command
if (usePRE_BUILD) {
// Add additional autogen target dependencies to origin target
for (cmTarget* depTarget : autogenDependTargets) {
this->Target->Target->AddUtility(depTarget->GetName(), makefile);
}
// Add the pre-build command directly to bypass the OBJECT_LIBRARY
// rejection in cmMakefile::AddCustomCommandToTarget because we know
// PRE_BUILD will work for an OBJECT_LIBRARY in this specific case.
//
// PRE_BUILD does not support file dependencies!
const std::vector<std::string> no_output;
const std::vector<std::string> no_deps;
cmCustomCommand cc(makefile, no_output, autogenProvides, no_deps,
commandLines, autogenComment.c_str(),
this->DirWork.c_str());
cc.SetEscapeOldStyle(false);
cc.SetEscapeAllowMakeVars(true);
this->Target->Target->AddPreBuildCommand(cc);
} else {
// Add link library target dependencies to the autogen target
// dependencies
{
// add_dependencies/addUtility do not support generator expressions.
// We depend only on the libraries found in all configs therefore.
std::map<cmGeneratorTarget const*, std::size_t> commonTargets;
for (std::string const& config : this->ConfigsList) {
cmLinkImplementationLibraries const* libs =
this->Target->GetLinkImplementationLibraries(config);
if (libs != nullptr) {
for (cmLinkItem const& item : libs->Libraries) {
cmGeneratorTarget const* libTarget = item.Target;
if ((libTarget != nullptr) &&
!StaticLibraryCycle(this->Target, libTarget, config)) {
// Increment target config count
commonTargets[libTarget]++;
}
}
}
}
for (auto const& item : commonTargets) {
if (item.second == this->ConfigsList.size()) {
autogenDependTargets.insert(item.first->Target);
}
}
}
// Create autogen target
cmTarget* autogenTarget = makefile->AddUtilityCommand(
this->AutogenTargetName, cmMakefile::TargetOrigin::Generator, true,
this->DirWork.c_str(), /*byproducts=*/autogenProvides,
std::vector<std::string>(autogenDependFiles.begin(),
autogenDependFiles.end()),
commandLines, false, autogenComment.c_str());
// Create autogen generator target
localGen->AddGeneratorTarget(
new cmGeneratorTarget(autogenTarget, localGen));
// Forward origin utilities to autogen target
for (std::string const& depName : this->Target->Target->GetUtilities()) {
autogenTarget->AddUtility(depName, makefile);
}
// Add additional autogen target dependencies to autogen target
for (cmTarget* depTarget : autogenDependTargets) {
autogenTarget->AddUtility(depTarget->GetName(), makefile);
}
// Set FOLDER property in autogen target
if (!this->AutogenFolder.empty()) {
autogenTarget->SetProperty("FOLDER", this->AutogenFolder.c_str());
}
// Add autogen target to the origin target dependencies
this->Target->Target->AddUtility(this->AutogenTargetName, makefile);
}
}
}
void cmQtAutoGeneratorInitializer::SetupCustomTargets()
{
cmMakefile* makefile = this->Target->Target->GetMakefile();
// forget the variables added here afterwards again:
cmMakefile::ScopePushPop varScope(makefile);
static_cast<void>(varScope);
// Configuration suffixes
std::map<std::string, std::string> configSuffixes;
for (std::string const& cfg : this->ConfigsList) {
std::string& suffix = configSuffixes[cfg];
suffix = "_";
suffix += cfg;
}
// Basic setup
AddDefinitionEscaped(makefile, "_multi_config",
cmQtAutoGen::MultiConfigName(this->MultiConfig));
AddDefinitionEscaped(makefile, "_build_dir", this->DirBuild);
if (this->MocEnabled || this->UicEnabled) {
AddDefinitionEscaped(makefile, "_qt_version_major", this->QtVersionMajor);
AddDefinitionEscaped(makefile, "_settings_file",
this->AutogenSettingsFile);
AddDefinitionEscaped(makefile, "_sources", this->Sources);
AddDefinitionEscaped(makefile, "_headers", this->Headers);
if (this->MocEnabled) {
this->SetupCustomTargetsMoc();
}
if (this->UicEnabled) {
this->SetupCustomTargetsUic();
}
}
if (this->RccEnabled) {
AddDefinitionEscaped(makefile, "_qt_rcc_executable", this->RccExecutable);
AddDefinitionEscaped(makefile, "_qt_rcc_list_options",
this->RccListOptions);
}
// Create info directory on demand
if (!cmSystemTools::MakeDirectory(this->DirInfo)) {
std::string emsg = ("Could not create directory: ");
emsg += cmQtAutoGen::Quoted(this->DirInfo);
cmSystemTools::Error(emsg.c_str());
}
auto ReOpenInfoFile = [](cmsys::ofstream& ofs,
std::string const& fileName) -> bool {
// Ensure we have write permission
mode_t perm = 0;
#if defined(_WIN32) && !defined(__CYGWIN__)
mode_t mode_write = S_IWRITE;
#else
mode_t mode_write = S_IWUSR;
#endif
cmSystemTools::GetPermissions(fileName, perm);
if (!(perm & mode_write)) {
cmSystemTools::SetPermissions(fileName, perm | mode_write);
}
ofs.open(fileName.c_str(), std::ios::app);
if (!ofs) {
// File open error
std::string error = "Internal CMake error when trying to open file: ";
error += cmQtAutoGen::Quoted(fileName);
error += " for writing.";
cmSystemTools::Error(error.c_str());
}
return static_cast<bool>(ofs);
};
// Generate autogen target info file
if (this->MocEnabled || this->UicEnabled) {
{
std::string infoFileIn = cmSystemTools::GetCMakeRoot();
infoFileIn += "/Modules/AutogenInfo.cmake.in";
makefile->ConfigureFile(
infoFileIn.c_str(), this->AutogenInfoFile.c_str(), false, true, false);
}
// Append custom definitions to info file
// --------------------------------------
cmsys::ofstream ofs;
if (ReOpenInfoFile(ofs, this->AutogenInfoFile)) {
auto OfsWriteMap = [&ofs](
const char* key, std::map<std::string, std::string> const& map) {
for (auto const& item : map) {
ofs << "set(" << key << "_" << item.first << " "
<< cmOutputConverter::EscapeForCMake(item.second) << ")\n";
}
};
ofs << "# Configurations options\n";
OfsWriteMap("AM_CONFIG_SUFFIX", configSuffixes);
OfsWriteMap("AM_MOC_DEFINITIONS", this->ConfigMocDefines);
OfsWriteMap("AM_MOC_INCLUDES", this->ConfigMocIncludes);
OfsWriteMap("AM_UIC_TARGET_OPTIONS", this->ConfigUicOptions);
// Settings files (only require for multi configuration generators)
if (this->MultiConfig != cmQtAutoGen::SINGLE) {
std::map<std::string, std::string> settingsFiles;
for (std::string const& cfg : this->ConfigsList) {
settingsFiles[cfg] = cmQtAutoGen::AppendFilenameSuffix(
this->AutogenSettingsFile, "_" + cfg);
}
OfsWriteMap("AM_SETTINGS_FILE", settingsFiles);
}
}
}
// Generate auto RCC info files
if (this->RccEnabled) {
std::string infoFileIn = cmSystemTools::GetCMakeRoot();
infoFileIn += "/Modules/AutoRccInfo.cmake.in";
for (Qrc const& qrc : this->Qrcs) {
// Configure info file
makefile->ConfigureFile(infoFileIn.c_str(), qrc.InfoFile.c_str(), false,
true, false);
// Append custom definitions to info file
// --------------------------------------
cmsys::ofstream ofs;
if (ReOpenInfoFile(ofs, qrc.InfoFile)) {
{
ofs << "# Job\n";
auto OfsWrite = [&ofs](const char* key, std::string const& value) {
ofs << "set(" << key << " "
<< cmOutputConverter::EscapeForCMake(value) << ")\n";
};
OfsWrite("ARCC_SETTINGS_FILE", qrc.SettingsFile);
OfsWrite("ARCC_SOURCE", qrc.QrcFile);
OfsWrite("ARCC_OUTPUT", qrc.RccFile);
OfsWrite("ARCC_OPTIONS", cmJoin(qrc.Options, ";"));
OfsWrite("ARCC_INPUTS", cmJoin(qrc.Resources, ";"));
}
{
ofs << "# Configurations options\n";
auto OfsWriteMap = [&ofs](
const char* key, std::map<std::string, std::string> const& map) {
for (auto const& item : map) {
ofs << "set(" << key << "_" << item.first << " "
<< cmOutputConverter::EscapeForCMake(item.second) << ")\n";
}
};
OfsWriteMap("ARCC_CONFIG_SUFFIX", configSuffixes);
// Settings files (only require for multi configuration generators)
if (this->MultiConfig != cmQtAutoGen::SINGLE) {
std::map<std::string, std::string> settingsFiles;
for (std::string const& cfg : this->ConfigsList) {
settingsFiles[cfg] =
cmQtAutoGen::AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
}
OfsWriteMap("ARCC_SETTINGS_FILE", settingsFiles);
}
}
} else {
break;
}
}
}
}
void cmQtAutoGeneratorInitializer::SetupCustomTargetsMoc()
{
cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
cmMakefile* makefile = this->Target->Target->GetMakefile();
AddDefinitionEscaped(makefile, "_moc_skip", this->MocSkip);
AddDefinitionEscaped(makefile, "_moc_options",
GetSafeProperty(this->Target, "AUTOMOC_MOC_OPTIONS"));
AddDefinitionEscaped(makefile, "_moc_relaxed_mode",
makefile->IsOn("CMAKE_AUTOMOC_RELAXED_MODE") ? "TRUE"
: "FALSE");
AddDefinitionEscaped(makefile, "_moc_macro_names",
GetSafeProperty(this->Target, "AUTOMOC_MACRO_NAMES"));
AddDefinitionEscaped(
makefile, "_moc_depend_filters",
GetSafeProperty(this->Target, "AUTOMOC_DEPEND_FILTERS"));
// Compiler predefines
if (this->Target->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") &&
this->QtVersionGreaterOrEqual(5, 8)) {
AddDefinitionEscaped(
makefile, "_moc_predefs_cmd",
makefile->GetSafeDefinition("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND"));
}
// Moc includes and compile definitions
{
auto GetIncludeDirs = [this,
localGen](std::string const& cfg) -> std::string {
// Get the include dirs for this target, without stripping the implicit
// include dirs off, see
// https://gitlab.kitware.com/cmake/cmake/issues/13667
std::vector<std::string> includeDirs;
localGen->GetIncludeDirectories(includeDirs, this->Target, "CXX", cfg,
false);
return cmJoin(includeDirs, ";");
};
auto GetCompileDefinitions =
[this, localGen](std::string const& cfg) -> std::string {
std::set<std::string> defines;
localGen->AddCompileDefinitions(defines, this->Target, cfg, "CXX");
return cmJoin(defines, ";");
};
// Default configuration settings
std::string const includeDirs = GetIncludeDirs(this->ConfigDefault);
std::string const compileDefs = GetCompileDefinitions(this->ConfigDefault);
// Other configuration settings
for (std::string const& cfg : this->ConfigsList) {
{
std::string const configIncludeDirs = GetIncludeDirs(cfg);
if (configIncludeDirs != includeDirs) {
this->ConfigMocIncludes[cfg] = configIncludeDirs;
}
}
{
std::string const configCompileDefs = GetCompileDefinitions(cfg);
if (configCompileDefs != compileDefs) {
this->ConfigMocDefines[cfg] = configCompileDefs;
}
}
}
AddDefinitionEscaped(makefile, "_moc_include_dirs", includeDirs);
AddDefinitionEscaped(makefile, "_moc_compile_defs", compileDefs);
}
// Moc executable
{
std::string mocExec;
std::string err;
if (this->QtVersionMajor == "5") {
cmGeneratorTarget* tgt = localGen->FindGeneratorTargetToUse("Qt5::moc");
if (tgt != nullptr) {
mocExec = SafeString(tgt->ImportedGetLocation(""));
} else {
err = "AUTOMOC: Qt5::moc target not found";
}
} else if (this->QtVersionMajor == "4") {
cmGeneratorTarget* tgt = localGen->FindGeneratorTargetToUse("Qt4::moc");
if (tgt != nullptr) {
mocExec = SafeString(tgt->ImportedGetLocation(""));
} else {
err = "AUTOMOC: Qt4::moc target not found";
}
} else {
err = "The AUTOMOC feature supports only Qt 4 and Qt 5";
}
if (err.empty()) {
AddDefinitionEscaped(makefile, "_qt_moc_executable", mocExec);
} else {
err += " (";
err += this->Target->GetName();
err += ")";
cmSystemTools::Error(err.c_str());
}
}
}
void cmQtAutoGeneratorInitializer::SetupCustomTargetsUic()
{
cmMakefile* makefile = this->Target->Target->GetMakefile();
// Uic search paths
{
std::vector<std::string> uicSearchPaths;
{
std::string const usp =
GetSafeProperty(this->Target, "AUTOUIC_SEARCH_PATHS");
if (!usp.empty()) {
cmSystemTools::ExpandListArgument(usp, uicSearchPaths);
std::string const srcDir = makefile->GetCurrentSourceDirectory();
for (std::string& path : uicSearchPaths) {
path = cmSystemTools::CollapseFullPath(path, srcDir);
}
}
}
AddDefinitionEscaped(makefile, "_uic_search_paths", uicSearchPaths);
}
// Uic target options
{
auto UicGetOpts = [this](std::string const& cfg) -> std::string {
std::vector<std::string> opts;
this->Target->GetAutoUicOptions(opts, cfg);
return cmJoin(opts, ";");
};
// Default settings
std::string const uicOpts = UicGetOpts(this->ConfigDefault);
AddDefinitionEscaped(makefile, "_uic_target_options", uicOpts);
// Configuration specific settings
for (std::string const& cfg : this->ConfigsList) {
std::string const configUicOpts = UicGetOpts(cfg);
if (configUicOpts != uicOpts) {
this->ConfigUicOptions[cfg] = configUicOpts;
}
}
}
// .ui files skip and options
{
std::vector<std::string> uiFileFiles;
std::vector<std::vector<std::string>> uiFileOptions;
{
std::string const uiExt = "ui";
std::string pathError;
for (cmSourceFile* sf : makefile->GetSourceFiles()) {
// sf->GetExtension() is only valid after sf->GetFullPath() ...
// Since we're iterating over source files that might be not in the
// target we need to check for path errors (not existing files).
std::string const& fPath = sf->GetFullPath(&pathError);
if (!pathError.empty()) {
pathError.clear();
continue;
}
if (sf->GetExtension() == uiExt) {
std::string const absFile = cmSystemTools::GetRealPath(fPath);
// Check if the .ui file should be skipped
if (sf->GetPropertyAsBool("SKIP_AUTOUIC") ||
sf->GetPropertyAsBool("SKIP_AUTOGEN")) {
this->UicSkip.insert(absFile);
}
// Check if the .ui file has uic options
std::string const uicOpts = GetSafeProperty(sf, "AUTOUIC_OPTIONS");
if (!uicOpts.empty()) {
// Check if file isn't skipped
if (this->UicSkip.count(absFile) == 0) {
uiFileFiles.push_back(absFile);
std::vector<std::string> optsVec;
cmSystemTools::ExpandListArgument(uicOpts, optsVec);
uiFileOptions.push_back(std::move(optsVec));
}
}
}
}
}
AddDefinitionEscaped(makefile, "_qt_uic_options_files", uiFileFiles);
AddDefinitionEscaped(makefile, "_qt_uic_options_options", uiFileOptions);
}
AddDefinitionEscaped(makefile, "_uic_skip", this->UicSkip);
// Uic executable
{
std::string err;
std::string uicExec;
cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
if (this->QtVersionMajor == "5") {
cmGeneratorTarget* tgt = localGen->FindGeneratorTargetToUse("Qt5::uic");
if (tgt != nullptr) {
uicExec = SafeString(tgt->ImportedGetLocation(""));
} else {
// Project does not use Qt5Widgets, but has AUTOUIC ON anyway
}
} else if (this->QtVersionMajor == "4") {
cmGeneratorTarget* tgt = localGen->FindGeneratorTargetToUse("Qt4::uic");
if (tgt != nullptr) {
uicExec = SafeString(tgt->ImportedGetLocation(""));
} else {
err = "AUTOUIC: Qt4::uic target not found";
}
} else {
err = "The AUTOUIC feature supports only Qt 4 and Qt 5";
}
if (err.empty()) {
AddDefinitionEscaped(makefile, "_qt_uic_executable", uicExec);
} else {
err += " (";
err += this->Target->GetName();
err += ")";
cmSystemTools::Error(err.c_str());
}
}
}
std::vector<std::string> cmQtAutoGeneratorInitializer::AddGeneratedSource(
std::string const& filename, cmQtAutoGen::Generator genType)
{
std::vector<std::string> genFiles;
// Register source file in makefile and source group
if (this->MultiConfig != cmQtAutoGen::FULL) {
genFiles.push_back(filename);
} else {
for (std::string const& cfg : this->ConfigsList) {
genFiles.push_back(
cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg));
}
}
{
cmMakefile* makefile = this->Target->Target->GetMakefile();
for (std::string const& genFile : genFiles) {
{
cmSourceFile* gFile = makefile->GetOrCreateSource(genFile, true);
gFile->SetProperty("GENERATED", "1");
gFile->SetProperty("SKIP_AUTOGEN", "On");
}
AddToSourceGroup(makefile, genFile, genType);
}
}
// Add source file to target
if (this->MultiConfig != cmQtAutoGen::FULL) {
this->Target->AddSource(filename);
} else {
for (std::string const& cfg : this->ConfigsList) {
std::string src = "$<$<CONFIG:";
src += cfg;
src += ">:";
src += cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg);
src += ">";
this->Target->AddSource(src);
}
}
return genFiles;
}
std::string cmQtAutoGeneratorInitializer::GetQtMajorVersion(
cmGeneratorTarget const* target)
{
cmMakefile* makefile = target->Target->GetMakefile();
std::string qtMajor = makefile->GetSafeDefinition("QT_VERSION_MAJOR");
if (qtMajor.empty()) {
qtMajor = makefile->GetSafeDefinition("Qt5Core_VERSION_MAJOR");
}
const char* targetQtVersion =
target->GetLinkInterfaceDependentStringProperty("QT_MAJOR_VERSION", "");
if (targetQtVersion != nullptr) {
qtMajor = targetQtVersion;
}
return qtMajor;
}
std::string cmQtAutoGeneratorInitializer::GetQtMinorVersion(
cmGeneratorTarget const* target, std::string const& qtVersionMajor)
{
cmMakefile* makefile = target->Target->GetMakefile();
std::string qtMinor;
if (qtVersionMajor == "5") {
qtMinor = makefile->GetSafeDefinition("Qt5Core_VERSION_MINOR");
}
if (qtMinor.empty()) {
qtMinor = makefile->GetSafeDefinition("QT_VERSION_MINOR");
}
const char* targetQtVersion =
target->GetLinkInterfaceDependentStringProperty("QT_MINOR_VERSION", "");
if (targetQtVersion != nullptr) {
qtMinor = targetQtVersion;
}
return qtMinor;
}
bool cmQtAutoGeneratorInitializer::QtVersionGreaterOrEqual(
unsigned long requestMajor, unsigned long requestMinor) const
{
unsigned long majorUL(0);
unsigned long minorUL(0);
if (cmSystemTools::StringToULong(this->QtVersionMajor.c_str(), &majorUL) &&
cmSystemTools::StringToULong(this->QtVersionMinor.c_str(), &minorUL)) {
return (majorUL > requestMajor) ||
(majorUL == requestMajor && minorUL >= requestMinor);
}
return false;
}