Autogen: Switch to use custom commands for RCC

Instead of processing all `rcc` invocation requests in the
_autogen target that calls `cmake -E cmake_autogen ...` once,
use a dedicated custom command that calls
`cmake -E cmake_autorcc ...` for each `.qrc` file.
This allows parallel `.qrc` file processing and reduces the
workload (and complexity) in the _autogen target.
If only `AUTORCC` is enabled, the _autogen target won't be created
at all since it is now used for `AUTOMOC` and `AUTOUIC` only.

For `.qrc` files that are GENERATED a custom target is used
instead of a custom command.

Closes #17161
This commit is contained in:
Sebastian Holtermann
2017-11-16 15:17:14 +01:00
parent b2a0b549bb
commit a87f82e025
3 changed files with 340 additions and 218 deletions

View File

@@ -0,0 +1,11 @@
# Meta
set(ARCC_MULTI_CONFIG @_multi_config@)
# Directories and files
set(ARCC_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/")
set(ARCC_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/")
set(ARCC_CMAKE_CURRENT_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@/")
set(ARCC_CMAKE_CURRENT_BINARY_DIR "@CMAKE_CURRENT_BINARY_DIR@/")
set(ARCC_BUILD_DIR @_build_dir@)
# Qt environment
set(ARCC_QT_VERSION_MAJOR @_qt_version_major@)
set(ARCC_QT_RCC_EXECUTABLE @_qt_rcc_executable@)

View File

@@ -24,6 +24,8 @@ public:
std::string QrcFile;
std::string QrcName;
std::string PathChecksum;
std::string InfoFile;
std::string SettingsFile;
std::string RccFile;
bool Generated;
bool Unique;

View File

@@ -635,29 +635,6 @@ static std::string RccGetExecutable(cmGeneratorTarget const* target,
return rccExec;
}
static void SetupAutoTargetRcc(cmQtAutoGenDigest const& digest)
{
std::vector<std::string> rccFiles;
std::vector<std::string> rccBuilds;
std::vector<std::vector<std::string>> rccOptions;
std::vector<std::vector<std::string>> rccInputs;
for (cmQtAutoGenDigestQrc const& qrcDigest : digest.Qrcs) {
rccFiles.push_back(qrcDigest.QrcFile);
rccBuilds.push_back(qrcDigest.RccFile);
rccOptions.push_back(qrcDigest.Options);
rccInputs.push_back(qrcDigest.Resources);
}
cmMakefile* makefile = digest.Target->Target->GetMakefile();
AddDefinitionEscaped(makefile, "_qt_rcc_executable",
RccGetExecutable(digest.Target, digest.QtVersionMajor));
AddDefinitionEscaped(makefile, "_rcc_files", rccFiles);
AddDefinitionEscaped(makefile, "_rcc_builds", rccBuilds);
AddDefinitionEscaped(makefile, "_rcc_options", rccOptions);
AddDefinitionEscaped(makefile, "_rcc_inputs", rccInputs);
}
void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
cmQtAutoGenDigest& digest)
{
@@ -681,6 +658,24 @@ void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
std::set<cmTarget*> autogenDependTargets;
std::vector<std::string> autogenProvides;
// Autogen target FOLDER property
std::string autogenFolder;
{
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(target->Target->GetProperty("FOLDER"));
}
if (folder != nullptr) {
autogenFolder = folder;
}
}
// Remove build directories on cleanup
AddCleanFile(makefile, autogenBuildDir);
// Remove old settings on cleanup
@@ -699,45 +694,6 @@ void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
}
}
// Compose command lines
cmCustomCommandLines commandLines;
{
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back("-E");
currentLine.push_back("cmake_autogen");
currentLine.push_back(autogenInfoDir);
currentLine.push_back("$<CONFIGURATION>");
commandLines.push_back(currentLine);
}
// Compose target comment
std::string autogenComment;
{
std::vector<std::string> toolNames;
if (digest.MocEnabled) {
toolNames.emplace_back("MOC");
}
if (digest.UicEnabled) {
toolNames.emplace_back("UIC");
}
if (digest.RccEnabled) {
toolNames.emplace_back("RCC");
}
std::string tools = toolNames.front();
toolNames.erase(toolNames.begin());
if (!toolNames.empty()) {
while (toolNames.size() > 1) {
tools += ", ";
tools += toolNames.front();
toolNames.erase(toolNames.begin());
}
tools += " and " + toolNames.front();
}
autogenComment = "Automatic " + tools + " for target " + target->GetName();
}
// Add moc compilation to generated files list
if (digest.MocEnabled) {
std::string const mocsComp = autogenBuildDir + "/mocs_compilation.cpp";
@@ -921,18 +877,30 @@ void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
}
}
}
// Path checksum
// Path checksum and file names
{
cmFilePathChecksum const fpathCheckSum(makefile);
for (cmQtAutoGenDigestQrc& qrcDigest : digest.Qrcs) {
qrcDigest.PathChecksum = fpathCheckSum.getPart(qrcDigest.QrcFile);
// RCC output file name
std::string rccFile = autogenBuildDir + "/";
rccFile += qrcDigest.PathChecksum;
rccFile += "/qrc_";
rccFile += qrcDigest.QrcName;
rccFile += ".cpp";
qrcDigest.RccFile = std::move(rccFile);
{
std::string rccFile = autogenBuildDir + "/";
rccFile += qrcDigest.PathChecksum;
rccFile += "/qrc_";
rccFile += qrcDigest.QrcName;
rccFile += ".cpp";
qrcDigest.RccFile = std::move(rccFile);
}
{
std::string base = autogenInfoDir;
base += "/RCC";
base += qrcDigest.QrcName;
if (!qrcDigest.Unique) {
base += qrcDigest.PathChecksum;
}
qrcDigest.InfoFile = base + "Info.cmake";
qrcDigest.SettingsFile = base + "Settings.cmake";
}
}
}
// RCC options
@@ -959,159 +927,239 @@ void cmQtAutoGeneratorInitializer::InitializeAutogenTarget(
}
for (cmQtAutoGenDigestQrc& qrcDigest : digest.Qrcs) {
// Register file at target
std::vector<std::string> const ccOutput = AddGeneratedSource(
target, qrcDigest.RccFile, multiConfig, configsList, cmQtAutoGen::RCC);
cmCustomCommandLines commandLines;
{
auto files = AddGeneratedSource(target, qrcDigest.RccFile, multiConfig,
configsList, cmQtAutoGen::RCC);
for (std::string& file : files) {
autogenProvides.push_back(std::move(file));
}
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back("-E");
currentLine.push_back("cmake_autorcc");
currentLine.push_back(qrcDigest.InfoFile);
currentLine.push_back("$<CONFIGURATION>");
commandLines.push_back(std::move(currentLine));
}
// Dependencies
std::string ccComment = "Automatic RCC for ";
ccComment += qrcDigest.QrcFile;
if (qrcDigest.Generated) {
// Add the GENERATED .qrc file to the dependencies
autogenDependFiles.insert(qrcDigest.QrcFile);
} else {
// Add the resource files to the dependencies
// Create custom rcc target
std::string ccName;
{
std::string error;
if (cmQtAutoGen::RccListInputs(digest.QtVersionMajor, rcc,
qrcDigest.QrcFile,
qrcDigest.Resources, &error)) {
for (std::string const& fileName : qrcDigest.Resources) {
autogenDependFiles.insert(fileName);
}
} else {
cmSystemTools::Error(error.c_str());
ccName = target->GetName();
ccName += "_arcc_";
ccName += qrcDigest.QrcName;
if (!qrcDigest.Unique) {
ccName += "_";
ccName += qrcDigest.PathChecksum;
}
std::vector<std::string> ccDepends;
// Add the .qrc file to the custom target dependencies
ccDepends.push_back(qrcDigest.QrcFile);
cmTarget* autoRccTarget = makefile->AddUtilityCommand(
ccName, true, workingDirectory.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 (!autogenFolder.empty()) {
autoRccTarget->SetProperty("FOLDER", autogenFolder.c_str());
}
}
// Run cmake again when .qrc file changes
// Add autogen target to the origin target dependencies
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(qrcDigest.QrcFile);
// Add the resource files to the dependencies
{
std::string error;
if (cmQtAutoGen::RccListInputs(digest.QtVersionMajor, rcc,
qrcDigest.QrcFile,
qrcDigest.Resources, &error)) {
for (std::string const& fileName : qrcDigest.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(),
workingDirectory.c_str());
}
// Reconfigure when .qrc file changes
makefile->AddCMakeDependFile(qrcDigest.QrcFile);
}
}
}
// Add user defined autogen target dependencies
{
std::string const deps = GetSafeProperty(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);
}
}
}
}
// 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) {
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(),
workingDirectory.c_str());
cc.SetEscapeOldStyle(false);
cc.SetEscapeAllowMakeVars(true);
target->Target->AddPreBuildCommand(cc);
} else {
// Convert file dependencies std::set to std::vector
std::vector<std::string> autogenDepends(autogenDependFiles.begin(),
autogenDependFiles.end());
// Add link library target dependencies to the autogen target dependencies
for (std::string const& config : configsList) {
cmLinkImplementationLibraries const* libs =
target->GetLinkImplementationLibraries(config);
if (libs != nullptr) {
for (cmLinkItem const& item : libs->Libraries) {
cmGeneratorTarget const* libTarget = item.Target;
if ((libTarget != nullptr) &&
!StaticLibraryCycle(target, libTarget, config)) {
std::string util;
if (configsList.size() > 1) {
util += "$<$<CONFIG:";
util += config;
util += ">:";
}
util += libTarget->GetName();
if (configsList.size() > 1) {
util += ">";
}
autogenDepends.push_back(util);
// Create _autogen target
if (digest.MocEnabled || digest.UicEnabled) {
// Add user defined autogen target dependencies
{
std::string const deps =
GetSafeProperty(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);
}
}
}
}
// Create autogen target
cmTarget* autogenTarget = makefile->AddUtilityCommand(
autogenTargetName, true, workingDirectory.c_str(),
/*byproducts=*/autogenProvides, autogenDepends, 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 : 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
// Compose target comment
std::string autogenComment;
{
const char* autogenFolder =
makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER");
if (autogenFolder == nullptr) {
autogenFolder =
makefile->GetState()->GetGlobalProperty("AUTOGEN_TARGETS_FOLDER");
std::vector<std::string> toolNames;
if (digest.MocEnabled) {
toolNames.emplace_back("MOC");
}
// Inherit FOLDER property from target (#13688)
if (autogenFolder == nullptr) {
autogenFolder = SafeString(target->Target->GetProperty("FOLDER"));
if (digest.UicEnabled) {
toolNames.emplace_back("UIC");
}
if ((autogenFolder != nullptr) && (*autogenFolder != '\0')) {
autogenTarget->SetProperty("FOLDER", autogenFolder);
if (digest.RccEnabled) {
toolNames.emplace_back("RCC");
}
std::string tools = toolNames.front();
toolNames.erase(toolNames.begin());
if (!toolNames.empty()) {
while (toolNames.size() > 1) {
tools += ", ";
tools += toolNames.front();
toolNames.erase(toolNames.begin());
}
tools += " and " + toolNames.front();
}
autogenComment =
"Automatic " + tools + " for target " + target->GetName();
}
// Add autogen target to the origin target dependencies
target->Target->AddUtility(autogenTargetName, makefile);
// Compose command lines
cmCustomCommandLines commandLines;
{
cmCustomCommandLine currentLine;
currentLine.push_back(cmSystemTools::GetCMakeCommand());
currentLine.push_back("-E");
currentLine.push_back("cmake_autogen");
currentLine.push_back(autogenInfoDir);
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) {
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(),
workingDirectory.c_str());
cc.SetEscapeOldStyle(false);
cc.SetEscapeAllowMakeVars(true);
target->Target->AddPreBuildCommand(cc);
} else {
// Convert file dependencies std::set to std::vector
std::vector<std::string> autogenDepends(autogenDependFiles.begin(),
autogenDependFiles.end());
// Add link library target dependencies to the autogen target
// dependencies
for (std::string const& config : configsList) {
cmLinkImplementationLibraries const* libs =
target->GetLinkImplementationLibraries(config);
if (libs != nullptr) {
for (cmLinkItem const& item : libs->Libraries) {
cmGeneratorTarget const* libTarget = item.Target;
if ((libTarget != nullptr) &&
!StaticLibraryCycle(target, libTarget, config)) {
std::string util;
if (configsList.size() > 1) {
util += "$<$<CONFIG:";
util += config;
util += ">:";
}
util += libTarget->GetName();
if (configsList.size() > 1) {
util += ">";
}
autogenDepends.push_back(util);
}
}
}
}
// Create autogen target
cmTarget* autogenTarget = makefile->AddUtilityCommand(
autogenTargetName, true, workingDirectory.c_str(),
/*byproducts=*/autogenProvides, autogenDepends, 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 : 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 (!autogenFolder.empty()) {
autogenTarget->SetProperty("FOLDER", autogenFolder.c_str());
}
// Add autogen target to the origin target dependencies
target->Target->AddUtility(autogenTargetName, makefile);
}
}
}
@@ -1164,18 +1212,36 @@ void cmQtAutoGeneratorInitializer::SetupAutoGenerateTarget(
}
}
if (digest.RccEnabled) {
SetupAutoTargetRcc(digest);
AddDefinitionEscaped(
makefile, "_qt_rcc_executable",
RccGetExecutable(digest.Target, digest.QtVersionMajor));
}
}
// Generate info file
{
std::string const infoDir = GetAutogenTargetFilesDir(target);
if (!cmSystemTools::MakeDirectory(infoDir)) {
std::string emsg = ("Could not create directory: ");
emsg += cmQtAutoGen::Quoted(infoDir);
cmSystemTools::Error(emsg.c_str());
// Create info directory on demand
std::string const infoDir = GetAutogenTargetFilesDir(target);
if (!cmSystemTools::MakeDirectory(infoDir)) {
std::string emsg = ("Could not create directory: ");
emsg += cmQtAutoGen::Quoted(infoDir);
cmSystemTools::Error(emsg.c_str());
}
auto AdjustFilePermissions = [](std::string const& fileName) {
// 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);
}
};
// Generate autogen target info file
{
std::string const infoFile = infoDir + "/AutogenInfo.cmake";
{
std::string infoFileIn = cmSystemTools::GetCMakeRoot();
@@ -1188,16 +1254,7 @@ void cmQtAutoGeneratorInitializer::SetupAutoGenerateTarget(
// --------------------------------------
// Ensure we have write permission in case .in was read-only.
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(infoFile, perm);
if (!(perm & mode_write)) {
cmSystemTools::SetPermissions(infoFile, perm | mode_write);
}
AdjustFilePermissions(infoFile);
// Open and write file
cmsys::ofstream ofs(infoFile.c_str(), std::ios::app);
@@ -1222,4 +1279,56 @@ void cmQtAutoGeneratorInitializer::SetupAutoGenerateTarget(
cmSystemTools::Error(error.c_str());
}
}
// Generate auto RCC info files
{
std::string infoFileIn = cmSystemTools::GetCMakeRoot();
infoFileIn += "/Modules/AutoRccInfo.cmake.in";
for (cmQtAutoGenDigestQrc const& qrcDigest : digest.Qrcs) {
// Configure info file
makefile->ConfigureFile(infoFileIn.c_str(), qrcDigest.InfoFile.c_str(),
false, true, false);
// Append custom definitions to info file
// --------------------------------------
// Ensure we have write permission in case .in was read-only.
AdjustFilePermissions(qrcDigest.InfoFile);
// Open and write file
cmsys::ofstream ofs(qrcDigest.InfoFile.c_str(), std::ios::app);
if (ofs) {
{
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);
}
{
ofs << "# Job\n";
auto OfsWrite = [&ofs](const char* key, std::string const& value) {
ofs << "set(" << key << " "
<< cmOutputConverter::EscapeForCMake(value) << ")\n";
};
OfsWrite("ARCC_SETTINGS_FILE", qrcDigest.SettingsFile);
OfsWrite("ARCC_SOURCE", qrcDigest.QrcFile);
OfsWrite("ARCC_OUTPUT", qrcDigest.RccFile);
OfsWrite("ARCC_OPTIONS", cmJoin(qrcDigest.Options, ";"));
OfsWrite("ARCC_INPUTS", cmJoin(qrcDigest.Resources, ";"));
}
} else {
// File open error
std::string error = "Internal CMake error when trying to open file: ";
error += cmQtAutoGen::Quoted(qrcDigest.InfoFile);
error += " for writing.";
cmSystemTools::Error(error.c_str());
}
}
}
}