Files
CMake/Source/cmQtAutoGeneratorRcc.cxx
Sebastian Holtermann a008578dee Autogen: Process files concurrently in AUTOMOC and AUTOUIC
This introduces concurrent thread processing in the `_autogen`
target wich processes AUTOMOC and AUTOUIC.
Source file parsing is distributed among the threads by
using a job queue from which the threads pull new parse jobs.
Each thread might start an independent ``moc`` or ``uic`` process.
Altogether this roughly speeds up the AUTOMOC and AUTOUIC build
process by the number of physical CPUs on the host system.

The exact number of threads to start in  the `_autogen` target
is controlled by the new AUTOGEN_PARALLEL target property which
is initialized by the new CMAKE_AUTOGEN_PARALLEL variable.
If AUTOGEN_PARALLEL is empty or unset (which is the default)
the thread count is set to the number of physical CPUs on
the host system.

The AUTOMOC/AUTOUIC generator and the AUTORCC generator are
refactored to use a libuv loop internally.

Closes #17422.
2018-01-17 17:23:49 +01:00

617 lines
17 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 "cmQtAutoGeneratorRcc.h"
#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmMakefile.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include <functional>
// -- Class methods
cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
: SettingsChanged_(false)
, MultiConfig_(MultiConfigT::WRAPPER)
, Stage_(StageT::SETTINGS_READ)
, Error_(false)
, Generate_(false)
, BuildFileChanged_(false)
{
// Initialize libuv asynchronous iteration request
UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this);
}
cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc()
{
}
bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile)
{
// Utility lambdas
auto InfoGet = [makefile](const char* key) {
return makefile->GetSafeDefinition(key);
};
auto InfoGetList = [makefile](const char* key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
return list;
};
auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
const char* valueConf = nullptr;
{
std::string keyConf = key;
keyConf += '_';
keyConf += InfoConfig();
valueConf = makefile->GetDefinition(keyConf);
}
if (valueConf == nullptr) {
valueConf = makefile->GetSafeDefinition(key);
}
return std::string(valueConf);
};
auto InfoGetConfigList =
[&InfoGetConfig](const char* key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
return list;
};
// -- Read info file
if (!makefile->ReadListFile(InfoFile().c_str())) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed");
return false;
}
// -- Meta
MultiConfig_ = MultiConfigType(InfoGet("ARCC_MULTI_CONFIG"));
ConfigSuffix_ = InfoGetConfig("ARCC_CONFIG_SUFFIX");
if (ConfigSuffix_.empty()) {
ConfigSuffix_ = "_";
ConfigSuffix_ += InfoConfig();
}
SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
// - Files and directories
AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
// - Qt environment
RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
// - Job
QrcFile_ = InfoGet("ARCC_SOURCE");
QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
RccFile_ = InfoGet("ARCC_OUTPUT");
Options_ = InfoGetConfigList("ARCC_OPTIONS");
Inputs_ = InfoGetList("ARCC_INPUTS");
// - Validity checks
if (SettingsFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing");
return false;
}
if (AutogenBuildDir_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(),
"Autogen build directory missing");
return false;
}
if (RccExecutable_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing");
return false;
}
if (QrcFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing");
return false;
}
if (RccFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing");
return false;
}
// Init derived information
// ------------------------
// Compute rcc output file name
{
std::string suffix;
switch (MultiConfig_) {
case MultiConfigT::SINGLE:
break;
case MultiConfigT::WRAPPER:
suffix = "_CMAKE";
suffix += ConfigSuffix_;
suffix += "_";
break;
case MultiConfigT::MULTI:
suffix = ConfigSuffix_;
break;
}
RccFileBuild_ = AppendFilenameSuffix(RccFile_, suffix);
}
return true;
}
bool cmQtAutoGeneratorRcc::Process()
{
// Run libuv event loop
UVRequest().send();
if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
if (Error_) {
return false;
}
} else {
return false;
}
return true;
}
void cmQtAutoGeneratorRcc::UVPollStage(uv_async_t* handle)
{
reinterpret_cast<cmQtAutoGeneratorRcc*>(handle->data)->PollStage();
}
void cmQtAutoGeneratorRcc::PollStage()
{
switch (Stage_) {
// -- Initialize
case StageT::SETTINGS_READ:
SettingsFileRead();
SetStage(StageT::TEST_QRC_RCC_FILES);
break;
// -- Change detection
case StageT::TEST_QRC_RCC_FILES:
if (TestQrcRccFiles()) {
SetStage(StageT::GENERATE);
} else {
SetStage(StageT::TEST_RESOURCES_READ);
}
break;
case StageT::TEST_RESOURCES_READ:
if (TestResourcesRead()) {
SetStage(StageT::TEST_RESOURCES);
}
break;
case StageT::TEST_RESOURCES:
if (TestResources()) {
SetStage(StageT::GENERATE);
} else {
SetStage(StageT::TEST_INFO_FILE);
}
break;
case StageT::TEST_INFO_FILE:
TestInfoFile();
SetStage(StageT::GENERATE_WRAPPER);
break;
// -- Generation
case StageT::GENERATE:
GenerateParentDir();
SetStage(StageT::GENERATE_RCC);
break;
case StageT::GENERATE_RCC:
if (GenerateRcc()) {
SetStage(StageT::GENERATE_WRAPPER);
}
break;
case StageT::GENERATE_WRAPPER:
GenerateWrapper();
SetStage(StageT::SETTINGS_WRITE);
break;
// -- Finalize
case StageT::SETTINGS_WRITE:
SettingsFileWrite();
SetStage(StageT::FINISH);
break;
case StageT::FINISH:
// Clear all libuv handles
UVRequest().reset();
// Set highest END stage manually
Stage_ = StageT::END;
break;
case StageT::END:
break;
}
}
void cmQtAutoGeneratorRcc::SetStage(StageT stage)
{
if (Error_) {
stage = StageT::FINISH;
}
// Only allow to increase the stage
if (Stage_ < stage) {
Stage_ = stage;
UVRequest().send();
}
}
void cmQtAutoGeneratorRcc::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
std::string const sep(" ~~~ ");
{
std::string str;
str += RccExecutable_;
str += sep;
str += cmJoin(RccListOptions_, ";");
str += sep;
str += QrcFile_;
str += sep;
str += RccFile_;
str += sep;
str += cmJoin(Options_, ";");
str += sep;
str += cmJoin(Inputs_, ";");
str += sep;
SettingsString_ = crypt.HashString(str);
}
}
// Read old settings
{
std::string content;
if (FileSys().FileRead(content, SettingsFile_)) {
SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
// In case any setting changed remove the old settings file.
// This triggers a full rebuild on the next run if the current
// build is aborted before writing the current settings in the end.
if (SettingsChanged_) {
FileSys().FileRemove(SettingsFile_);
}
} else {
SettingsChanged_ = true;
}
}
}
void cmQtAutoGeneratorRcc::SettingsFileWrite()
{
// Only write if any setting changed
if (SettingsChanged_) {
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC,
"Writing settings file " + Quoted(SettingsFile_));
}
// Write settings file
std::string content = "rcc:";
content += SettingsString_;
content += '\n';
if (!FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, content)) {
Log().ErrorFile(GeneratorT::RCC, SettingsFile_,
"Settings file writing failed");
// Remove old settings file to trigger a full rebuild on the next run
FileSys().FileRemove(SettingsFile_);
Error_ = true;
}
}
}
bool cmQtAutoGeneratorRcc::TestQrcRccFiles()
{
// Do basic checks if rcc generation is required
// Test if the rcc output file exists
if (!FileSys().FileExists(RccFileBuild_)) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileBuild_);
reason += " from its source file ";
reason += Quoted(QrcFile_);
reason += " because it doesn't exist";
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
return Generate_;
}
// Test if the settings changed
if (SettingsChanged_) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileBuild_);
reason += " from ";
reason += Quoted(QrcFile_);
reason += " because the RCC settings changed";
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
return Generate_;
}
// Test if the rcc output file is older than the .qrc file
{
bool isOlder = false;
{
std::string error;
isOlder = FileSys().FileIsOlderThan(RccFileBuild_, QrcFile_, &error);
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
}
}
if (isOlder) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileBuild_);
reason += " because it is older than ";
reason += Quoted(QrcFile_);
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
}
}
return Generate_;
}
bool cmQtAutoGeneratorRcc::TestResourcesRead()
{
if (!Inputs_.empty()) {
// Inputs are known already
return true;
}
if (!RccListOptions_.empty()) {
// Start a rcc list process and parse the output
if (Process_) {
// Process is running already
if (Process_->IsFinished()) {
// Process is finished
if (!ProcessResult_.error()) {
// Process success
std::string parseError;
if (!RccListParseOutput(ProcessResult_.StdOut, ProcessResult_.StdErr,
Inputs_, parseError)) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, parseError);
Error_ = true;
}
} else {
Log().ErrorFile(GeneratorT::RCC, QrcFile_,
ProcessResult_.ErrorMessage);
Error_ = true;
}
// Clean up
Process_.reset();
ProcessResult_.reset();
} else {
// Process is not finished, yet.
return false;
}
} else {
// Start a new process
// rcc prints relative entry paths when started in the directory of the
// qrc file with a pathless qrc file name argument.
// This is important because on Windows absolute paths returned by rcc
// might contain bad multibyte characters when the qrc file path
// contains non-ASCII pcharacters.
std::vector<std::string> cmd;
cmd.push_back(RccExecutable_);
cmd.insert(cmd.end(), RccListOptions_.begin(), RccListOptions_.end());
cmd.push_back(QrcFileName_);
// We're done here if the process fails to start
return !StartProcess(QrcFileDir_, cmd, false);
}
} else {
// rcc does not support the --list command.
// Read the qrc file content and parse it.
std::string qrcContent;
if (FileSys().FileRead(GeneratorT::RCC, qrcContent, QrcFile_)) {
RccListParseContent(qrcContent, Inputs_);
}
}
if (!Inputs_.empty()) {
// Convert relative paths to absolute paths
RccListConvertFullPath(QrcFileDir_, Inputs_);
}
return true;
}
bool cmQtAutoGeneratorRcc::TestResources()
{
if (Inputs_.empty()) {
return true;
}
{
std::string error;
for (std::string const& resFile : Inputs_) {
// Check if the resource file exists
if (!FileSys().FileExists(resFile)) {
error = "Could not find the resource file\n ";
error += Quoted(resFile);
error += '\n';
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
break;
}
// Check if the resource file is newer than the build file
if (FileSys().FileIsOlderThan(RccFileBuild_, resFile, &error)) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileBuild_);
reason += " from ";
reason += Quoted(QrcFile_);
reason += " because it is older than ";
reason += Quoted(resFile);
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
break;
}
// Print error and break on demand
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
break;
}
}
}
return Generate_;
}
void cmQtAutoGeneratorRcc::TestInfoFile()
{
// Test if the rcc output file is older than the info file
{
bool isOlder = false;
{
std::string error;
isOlder = FileSys().FileIsOlderThan(RccFileBuild_, InfoFile(), &error);
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
}
}
if (isOlder) {
if (Log().Verbose()) {
std::string reason = "Touching ";
reason += Quoted(RccFileBuild_);
reason += " because it is older than ";
reason += Quoted(InfoFile());
Log().Info(GeneratorT::RCC, reason);
}
// Touch build file
FileSys().Touch(RccFileBuild_);
BuildFileChanged_ = true;
}
}
}
void cmQtAutoGeneratorRcc::GenerateParentDir()
{
// Make sure the parent directory exists
if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileBuild_)) {
Error_ = true;
}
}
/**
* @return True when finished
*/
bool cmQtAutoGeneratorRcc::GenerateRcc()
{
if (!Generate_) {
// Nothing to do
return true;
}
if (Process_) {
// Process is running already
if (Process_->IsFinished()) {
// Process is finished
if (!ProcessResult_.error()) {
// Process success
BuildFileChanged_ = true;
} else {
// Process failed
{
std::string emsg = "The rcc process failed to compile\n ";
emsg += Quoted(QrcFile_);
emsg += "\ninto\n ";
emsg += Quoted(RccFileBuild_);
if (ProcessResult_.error()) {
emsg += "\n";
emsg += ProcessResult_.ErrorMessage;
}
Log().ErrorCommand(GeneratorT::RCC, emsg, Process_->Setup().Command,
ProcessResult_.StdOut);
}
FileSys().FileRemove(RccFileBuild_);
Error_ = true;
}
// Clean up
Process_.reset();
ProcessResult_.reset();
} else {
// Process is not finished, yet.
return false;
}
} else {
// Start a rcc process
std::vector<std::string> cmd;
cmd.push_back(RccExecutable_);
cmd.insert(cmd.end(), Options_.begin(), Options_.end());
cmd.push_back("-o");
cmd.push_back(RccFileBuild_);
cmd.push_back(QrcFile_);
// We're done here if the process fails to start
return !StartProcess(AutogenBuildDir_, cmd, true);
}
return true;
}
void cmQtAutoGeneratorRcc::GenerateWrapper()
{
// Generate a wrapper source file on demand
if (MultiConfig_ == MultiConfigT::WRAPPER) {
// Wrapper file name
std::string const& wrapperAbs = RccFile_;
// Wrapper file content
std::string content = "// This is an autogenerated configuration "
"wrapper file. Changes will be overwritten.\n"
"#include \"";
content += cmSystemTools::GetFilenameName(RccFileBuild_);
content += "\"\n";
// Write content to file
if (FileSys().FileDiffers(wrapperAbs, content)) {
// Write new wrapper file
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC, "Generating RCC wrapper " + wrapperAbs);
}
if (!FileSys().FileWrite(GeneratorT::RCC, wrapperAbs, content)) {
Log().ErrorFile(GeneratorT::RCC, wrapperAbs,
"RCC wrapper file writing failed");
Error_ = true;
}
} else if (BuildFileChanged_) {
// Just touch the wrapper file
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC, "Touching RCC wrapper " + wrapperAbs);
}
FileSys().Touch(wrapperAbs);
}
}
}
bool cmQtAutoGeneratorRcc::StartProcess(
std::string const& workingDirectory, std::vector<std::string> const& command,
bool mergedOutput)
{
// Log command
if (Log().Verbose()) {
std::string msg = "Running command:\n";
msg += QuotedCommand(command);
msg += '\n';
Log().Info(GeneratorT::RCC, msg);
}
// Create process handler
Process_ = cm::make_unique<ReadOnlyProcessT>();
Process_->setup(&ProcessResult_, mergedOutput, command, workingDirectory);
// Start process
if (!Process_->start(UVLoop(),
std::bind(&cm::uv_async_ptr::send, &UVRequest()))) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, ProcessResult_.ErrorMessage);
Error_ = true;
// Clean up
Process_.reset();
ProcessResult_.reset();
return false;
}
return true;
}