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.
This commit is contained in:
Sebastian Holtermann
2018-01-03 16:59:40 +01:00
parent 488baaf0d6
commit a008578dee
13 changed files with 3682 additions and 2337 deletions
-4
View File
@@ -1,10 +1,6 @@
# 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_RCC_EXECUTABLE @_qt_rcc_executable@)
+1
View File
@@ -1,5 +1,6 @@
# Meta
set(AM_MULTI_CONFIG @_multi_config@)
set(AM_PARALLEL @_parallel@)
# Directories and files
set(AM_CMAKE_BINARY_DIR "@CMAKE_BINARY_DIR@/")
set(AM_CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@/")
+121 -195
View File
@@ -2,16 +2,13 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoGen.h"
#include "cmAlgorithms.h"
#include "cmProcessOutput.h"
#include "cmSystemTools.h"
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include <algorithm>
#include <iterator>
#include <sstream>
#include <stddef.h>
// - Static variables
@@ -21,8 +18,8 @@ std::string const genNameUic = "AutoUic";
std::string const genNameRcc = "AutoRcc";
std::string const mcNameSingle = "SINGLE";
std::string const mcNameWrap = "WRAP";
std::string const mcNameFull = "FULL";
std::string const mcNameWrapper = "WRAPPER";
std::string const mcNameMulti = "MULTI";
// - Static functions
@@ -80,203 +77,53 @@ void MergeOptions(std::vector<std::string>& baseOpts,
baseOpts.insert(baseOpts.end(), extraOpts.begin(), extraOpts.end());
}
/// @brief Reads the resource files list from from a .qrc file - Qt4 version
/// @return True if the .qrc file was successfully parsed
static bool RccListInputsQt4(std::string const& fileName,
std::vector<std::string>& files,
std::string* errorMessage)
{
bool allGood = true;
// Read qrc file content into string
std::string qrcContents;
{
cmsys::ifstream ifs(fileName.c_str());
if (ifs) {
std::ostringstream osst;
osst << ifs.rdbuf();
qrcContents = osst.str();
} else {
if (errorMessage != nullptr) {
std::string& err = *errorMessage;
err = "rcc file not readable:\n ";
err += cmQtAutoGen::Quoted(fileName);
err += "\n";
}
allGood = false;
}
}
if (allGood) {
// qrc file directory
std::string qrcDir(cmSystemTools::GetFilenamePath(fileName));
if (!qrcDir.empty()) {
qrcDir += '/';
}
cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
size_t offset = 0;
while (fileMatchRegex.find(qrcContents.c_str() + offset)) {
std::string qrcEntry = fileMatchRegex.match(1);
offset += qrcEntry.size();
{
fileReplaceRegex.find(qrcEntry);
std::string tag = fileReplaceRegex.match(1);
qrcEntry = qrcEntry.substr(tag.size());
}
if (!cmSystemTools::FileIsFullPath(qrcEntry.c_str())) {
qrcEntry = qrcDir + qrcEntry;
}
files.push_back(qrcEntry);
}
}
return allGood;
}
/// @brief Reads the resource files list from from a .qrc file - Qt5 version
/// @return True if the .qrc file was successfully parsed
static bool RccListInputsQt5(std::string const& rccCommand,
std::vector<std::string> const& rccListOptions,
std::string const& fileName,
std::vector<std::string>& files,
std::string* errorMessage)
{
if (rccCommand.empty()) {
cmSystemTools::Error("rcc executable not available");
return false;
}
std::string const fileDir = cmSystemTools::GetFilenamePath(fileName);
std::string const fileNameName = cmSystemTools::GetFilenameName(fileName);
// Run rcc list command
bool result = false;
int retVal = 0;
std::string rccStdOut;
std::string rccStdErr;
{
std::vector<std::string> command;
command.push_back(rccCommand);
command.insert(command.end(), rccListOptions.begin(),
rccListOptions.end());
command.push_back(fileNameName);
result = cmSystemTools::RunSingleCommand(
command, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto);
}
if (!result || retVal) {
if (errorMessage != nullptr) {
std::string& err = *errorMessage;
err = "rcc list process failed for:\n ";
err += cmQtAutoGen::Quoted(fileName);
err += "\n";
err += rccStdOut;
err += "\n";
err += rccStdErr;
err += "\n";
}
return false;
}
// Lambda to strip CR characters
auto StripCR = [](std::string& line) {
std::string::size_type cr = line.find('\r');
if (cr != std::string::npos) {
line = line.substr(0, cr);
}
};
// Parse rcc std output
{
std::istringstream ostr(rccStdOut);
std::string oline;
while (std::getline(ostr, oline)) {
StripCR(oline);
if (!oline.empty()) {
files.push_back(oline);
}
}
}
// Parse rcc error output
{
std::istringstream estr(rccStdErr);
std::string eline;
while (std::getline(estr, eline)) {
StripCR(eline);
if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
static std::string searchString = "Cannot find file '";
std::string::size_type pos = eline.find(searchString);
if (pos == std::string::npos) {
if (errorMessage != nullptr) {
std::string& err = *errorMessage;
err = "rcc lists unparsable output:\n";
err += cmQtAutoGen::Quoted(eline);
err += "\n";
}
return false;
}
pos += searchString.length();
std::string::size_type sz = eline.size() - pos - 1;
files.push_back(eline.substr(pos, sz));
}
}
}
// Convert relative paths to absolute paths
for (std::string& resFile : files) {
resFile = cmSystemTools::CollapseCombinedPath(fileDir, resFile);
}
return true;
}
// - Class definitions
std::string const cmQtAutoGen::listSep = "<<<S>>>";
std::string const cmQtAutoGen::ListSep = "<<<S>>>";
unsigned int const cmQtAutoGen::ParallelMax = 64;
std::string const& cmQtAutoGen::GeneratorName(Generator type)
std::string const& cmQtAutoGen::GeneratorName(GeneratorT type)
{
switch (type) {
case Generator::GEN:
case GeneratorT::GEN:
return genNameGen;
case Generator::MOC:
case GeneratorT::MOC:
return genNameMoc;
case Generator::UIC:
case GeneratorT::UIC:
return genNameUic;
case Generator::RCC:
case GeneratorT::RCC:
return genNameRcc;
}
return genNameGen;
}
std::string cmQtAutoGen::GeneratorNameUpper(Generator genType)
std::string cmQtAutoGen::GeneratorNameUpper(GeneratorT genType)
{
return cmSystemTools::UpperCase(cmQtAutoGen::GeneratorName(genType));
}
std::string const& cmQtAutoGen::MultiConfigName(MultiConfig config)
std::string const& cmQtAutoGen::MultiConfigName(MultiConfigT config)
{
switch (config) {
case MultiConfig::SINGLE:
case MultiConfigT::SINGLE:
return mcNameSingle;
case MultiConfig::WRAP:
return mcNameWrap;
case MultiConfig::FULL:
return mcNameFull;
case MultiConfigT::WRAPPER:
return mcNameWrapper;
case MultiConfigT::MULTI:
return mcNameMulti;
}
return mcNameWrap;
return mcNameWrapper;
}
cmQtAutoGen::MultiConfig cmQtAutoGen::MultiConfigType(std::string const& name)
cmQtAutoGen::MultiConfigT cmQtAutoGen::MultiConfigType(std::string const& name)
{
if (name == mcNameSingle) {
return MultiConfig::SINGLE;
return MultiConfigT::SINGLE;
}
if (name == mcNameFull) {
return MultiConfig::FULL;
if (name == mcNameMulti) {
return MultiConfigT::MULTI;
}
return MultiConfig::WRAP;
return MultiConfigT::WRAPPER;
}
std::string cmQtAutoGen::Quoted(std::string const& text)
@@ -294,6 +141,33 @@ std::string cmQtAutoGen::Quoted(std::string const& text)
return res;
}
std::string cmQtAutoGen::QuotedCommand(std::vector<std::string> const& command)
{
std::string res;
for (std::string const& item : command) {
if (!res.empty()) {
res.push_back(' ');
}
std::string const cesc = cmQtAutoGen::Quoted(item);
if (item.empty() || (cesc.size() > (item.size() + 2)) ||
(cesc.find(' ') != std::string::npos)) {
res += cesc;
} else {
res += item;
}
}
return res;
}
std::string cmQtAutoGen::SubDirPrefix(std::string const& filename)
{
std::string res(cmSystemTools::GetFilenamePath(filename));
if (!res.empty()) {
res += '/';
}
return res;
}
std::string cmQtAutoGen::AppendFilenameSuffix(std::string const& filename,
std::string const& suffix)
{
@@ -333,27 +207,79 @@ void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts,
MergeOptions(baseOpts, newOpts, valueOpts, isQt5);
}
bool cmQtAutoGen::RccListInputs(std::string const& rccCommand,
std::vector<std::string> const& rccListOptions,
std::string const& fileName,
std::vector<std::string>& files,
std::string* errorMessage)
void cmQtAutoGen::RccListParseContent(std::string const& content,
std::vector<std::string>& files)
{
bool allGood = false;
if (cmSystemTools::FileExists(fileName.c_str())) {
if (rccListOptions.empty()) {
allGood = RccListInputsQt4(fileName, files, errorMessage);
} else {
allGood = RccListInputsQt5(rccCommand, rccListOptions, fileName, files,
errorMessage);
}
} else {
if (errorMessage != nullptr) {
std::string& err = *errorMessage;
err = "rcc resource file does not exist:\n ";
err += cmQtAutoGen::Quoted(fileName);
err += "\n";
cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
const char* contentChars = content.c_str();
while (fileMatchRegex.find(contentChars)) {
std::string const qrcEntry = fileMatchRegex.match(1);
contentChars += qrcEntry.size();
{
fileReplaceRegex.find(qrcEntry);
std::string const tag = fileReplaceRegex.match(1);
files.push_back(qrcEntry.substr(tag.size()));
}
}
return allGood;
}
bool cmQtAutoGen::RccListParseOutput(std::string const& rccStdOut,
std::string const& rccStdErr,
std::vector<std::string>& files,
std::string& error)
{
// Lambda to strip CR characters
auto StripCR = [](std::string& line) {
std::string::size_type cr = line.find('\r');
if (cr != std::string::npos) {
line = line.substr(0, cr);
}
};
// Parse rcc std output
{
std::istringstream ostr(rccStdOut);
std::string oline;
while (std::getline(ostr, oline)) {
StripCR(oline);
if (!oline.empty()) {
files.push_back(oline);
}
}
}
// Parse rcc error output
{
std::istringstream estr(rccStdErr);
std::string eline;
while (std::getline(estr, eline)) {
StripCR(eline);
if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
static std::string const searchString = "Cannot find file '";
std::string::size_type pos = eline.find(searchString);
if (pos == std::string::npos) {
error = "rcc lists unparsable output:\n";
error += cmQtAutoGen::Quoted(eline);
error += "\n";
return false;
}
pos += searchString.length();
std::string::size_type sz = eline.size() - pos - 1;
files.push_back(eline.substr(pos, sz));
}
}
}
return true;
}
void cmQtAutoGen::RccListConvertFullPath(std::string const& qrcFileDir,
std::vector<std::string>& files)
{
for (std::string& entry : files) {
std::string tmp = cmSystemTools::CollapseCombinedPath(qrcFileDir, entry);
entry = std::move(tmp);
}
}
+36 -19
View File
@@ -9,14 +9,18 @@
#include <vector>
/** \class cmQtAutoGen
* \brief Class used as namespace for QtAutogen related types and functions
* \brief Common base class for QtAutoGen classes
*/
class cmQtAutoGen
{
public:
static std::string const listSep;
/// @brief Nested lists separator
static std::string const ListSep;
/// @brief Maximum number of parallel threads/processes in a generator
static unsigned int const ParallelMax;
enum Generator
/// @brief AutoGen generator type
enum class GeneratorT
{
GEN, // General
MOC,
@@ -24,27 +28,33 @@ public:
RCC
};
enum MultiConfig
/// @brief Multiconfiguration type
enum class MultiConfigT
{
SINGLE, // Single configuration
WRAP, // Multi configuration using wrapper files
FULL // Full multi configuration using per config sources
SINGLE, // Single configuration
WRAPPER, // Multi configuration using wrapper files
MULTI // Multi configuration using per config sources
};
public:
/// @brief Returns the generator name
static std::string const& GeneratorName(Generator genType);
static std::string const& GeneratorName(GeneratorT genType);
/// @brief Returns the generator name in upper case
static std::string GeneratorNameUpper(Generator genType);
static std::string GeneratorNameUpper(GeneratorT genType);
/// @brief Returns the multi configuration name string
static std::string const& MultiConfigName(MultiConfig config);
static std::string const& MultiConfigName(MultiConfigT config);
/// @brief Returns the multi configuration type
static MultiConfig MultiConfigType(std::string const& name);
static MultiConfigT MultiConfigType(std::string const& name);
/// @brief Returns a the string escaped and enclosed in quotes
static std::string Quoted(std::string const& text);
static std::string QuotedCommand(std::vector<std::string> const& command);
/// @brief Returns the parent directory of the file with a "/" suffix
static std::string SubDirPrefix(std::string const& filename);
/// @brief Appends the suffix to the filename before the last dot
static std::string AppendFilenameSuffix(std::string const& filename,
std::string const& suffix);
@@ -59,14 +69,21 @@ public:
std::vector<std::string> const& newOpts,
bool isQt5);
/// @brief Reads the resource files list from from a .qrc file
/// @arg fileName Must be the absolute path of the .qrc file
/// @return True if the rcc file was successfully read
static bool RccListInputs(std::string const& rccCommand,
std::vector<std::string> const& rccListOptions,
std::string const& fileName,
std::vector<std::string>& files,
std::string* errorMessage = nullptr);
/// @brief Parses the content of a qrc file
///
/// Use when rcc does not support the "--list" option
static void RccListParseContent(std::string const& content,
std::vector<std::string>& files);
/// @brief Parses the output of the "rcc --list ..." command
static bool RccListParseOutput(std::string const& rccStdOut,
std::string const& rccStdErr,
std::vector<std::string>& files,
std::string& error);
/// @brief Converts relative qrc entry paths to full paths
static void RccListConvertFullPath(std::string const& qrcFileDir,
std::vector<std::string>& files);
};
#endif
+516 -205
View File
@@ -4,7 +4,6 @@
#include "cmQtAutoGenerator.h"
#include "cmsys/FStream.hxx"
#include "cmsys/Terminal.h"
#include "cmAlgorithms.h"
#include "cmGlobalGenerator.h"
@@ -14,9 +13,21 @@
#include "cmSystemTools.h"
#include "cmake.h"
// -- Static functions
#include <algorithm>
static std::string HeadLine(std::string const& title)
// -- Class methods
void cmQtAutoGenerator::Logger::SetVerbose(bool value)
{
Verbose_ = value;
}
void cmQtAutoGenerator::Logger::SetColorOutput(bool value)
{
ColorOutput_ = value;
}
std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title)
{
std::string head = title;
head += '\n';
@@ -25,153 +36,93 @@ static std::string HeadLine(std::string const& title)
return head;
}
static std::string QuotedCommand(std::vector<std::string> const& command)
void cmQtAutoGenerator::Logger::Info(GeneratorT genType,
std::string const& message)
{
std::string res;
for (std::string const& item : command) {
if (!res.empty()) {
res.push_back(' ');
}
std::string const cesc = cmQtAutoGen::Quoted(item);
if (item.empty() || (cesc.size() > (item.size() + 2)) ||
(cesc.find(' ') != std::string::npos)) {
res += cesc;
} else {
res += item;
}
}
return res;
}
// -- Class methods
cmQtAutoGenerator::cmQtAutoGenerator()
: Verbose(cmSystemTools::HasEnv("VERBOSE"))
, ColorOutput(true)
{
{
std::string colorEnv;
cmSystemTools::GetEnv("COLOR", colorEnv);
if (!colorEnv.empty()) {
this->ColorOutput = cmSystemTools::IsOn(colorEnv.c_str());
}
}
}
bool cmQtAutoGenerator::Run(std::string const& infoFile,
std::string const& config)
{
// Info settings
this->InfoFile = infoFile;
cmSystemTools::ConvertToUnixSlashes(this->InfoFile);
this->InfoDir = cmSystemTools::GetFilenamePath(infoFile);
this->InfoConfig = config;
cmake cm(cmake::RoleScript);
cm.SetHomeOutputDirectory(this->InfoDir);
cm.SetHomeDirectory(this->InfoDir);
cm.GetCurrentSnapshot().SetDefaultDefinitions();
cmGlobalGenerator gg(&cm);
cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
snapshot.GetDirectory().SetCurrentBinary(this->InfoDir);
snapshot.GetDirectory().SetCurrentSource(this->InfoDir);
auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
// The OLD/WARN behavior for policy CMP0053 caused a speed regression.
// https://gitlab.kitware.com/cmake/cmake/issues/17570
makefile->SetPolicyVersion("3.9");
gg.SetCurrentMakefile(makefile.get());
return this->Process(makefile.get());
}
void cmQtAutoGenerator::LogBold(std::string const& message) const
{
cmSystemTools::MakefileColorEcho(cmsysTerminal_Color_ForegroundBlue |
cmsysTerminal_Color_ForegroundBold,
message.c_str(), true, this->ColorOutput);
}
void cmQtAutoGenerator::LogInfo(cmQtAutoGen::Generator genType,
std::string const& message) const
{
std::string msg = cmQtAutoGen::GeneratorName(genType);
std::string msg = GeneratorName(genType);
msg += ": ";
msg += message;
if (msg.back() != '\n') {
msg.push_back('\n');
}
cmSystemTools::Stdout(msg.c_str(), msg.size());
}
void cmQtAutoGenerator::LogWarning(cmQtAutoGen::Generator genType,
std::string const& message) const
{
std::string msg = cmQtAutoGen::GeneratorName(genType);
msg += " warning:";
if (message.find('\n') == std::string::npos) {
// Single line message
msg.push_back(' ');
} else {
// Multi line message
msg.push_back('\n');
{
std::lock_guard<std::mutex> lock(Mutex_);
cmSystemTools::Stdout(msg.c_str(), msg.size());
}
// Message
msg += message;
if (msg.back() != '\n') {
msg.push_back('\n');
}
msg.push_back('\n');
cmSystemTools::Stdout(msg.c_str(), msg.size());
}
void cmQtAutoGenerator::LogFileWarning(cmQtAutoGen::Generator genType,
std::string const& filename,
std::string const& message) const
{
std::string msg = " ";
msg += cmQtAutoGen::Quoted(filename);
msg.push_back('\n');
// Message
msg += message;
this->LogWarning(genType, msg);
}
void cmQtAutoGenerator::LogError(cmQtAutoGen::Generator genType,
std::string const& message) const
void cmQtAutoGenerator::Logger::Warning(GeneratorT genType,
std::string const& message)
{
std::string msg;
msg.push_back('\n');
msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " error");
if (message.find('\n') == std::string::npos) {
// Single line message
msg += GeneratorName(genType);
msg += " warning: ";
} else {
// Multi line message
msg += HeadLine(GeneratorName(genType) + " warning");
}
// Message
msg += message;
if (msg.back() != '\n') {
msg.push_back('\n');
}
msg.push_back('\n');
cmSystemTools::Stderr(msg.c_str(), msg.size());
{
std::lock_guard<std::mutex> lock(Mutex_);
cmSystemTools::Stdout(msg.c_str(), msg.size());
}
}
void cmQtAutoGenerator::LogFileError(cmQtAutoGen::Generator genType,
std::string const& filename,
std::string const& message) const
void cmQtAutoGenerator::Logger::WarningFile(GeneratorT genType,
std::string const& filename,
std::string const& message)
{
std::string msg = " ";
msg += Quoted(filename);
msg.push_back('\n');
// Message
msg += message;
Warning(genType, msg);
}
void cmQtAutoGenerator::Logger::Error(GeneratorT genType,
std::string const& message)
{
std::string msg;
msg += HeadLine(GeneratorName(genType) + " error");
// Message
msg += message;
if (msg.back() != '\n') {
msg.push_back('\n');
}
msg.push_back('\n');
{
std::lock_guard<std::mutex> lock(Mutex_);
cmSystemTools::Stderr(msg.c_str(), msg.size());
}
}
void cmQtAutoGenerator::Logger::ErrorFile(GeneratorT genType,
std::string const& filename,
std::string const& message)
{
std::string emsg = " ";
emsg += cmQtAutoGen::Quoted(filename);
emsg += Quoted(filename);
emsg += '\n';
// Message
emsg += message;
this->LogError(genType, emsg);
Error(genType, emsg);
}
void cmQtAutoGenerator::LogCommandError(
cmQtAutoGen::Generator genType, std::string const& message,
std::vector<std::string> const& command, std::string const& output) const
void cmQtAutoGenerator::Logger::ErrorCommand(
GeneratorT genType, std::string const& message,
std::vector<std::string> const& command, std::string const& output)
{
std::string msg;
msg.push_back('\n');
msg += HeadLine(cmQtAutoGen::GeneratorName(genType) + " subprocess error");
msg += HeadLine(GeneratorName(genType) + " subprocess error");
msg += message;
if (msg.back() != '\n') {
msg.push_back('\n');
@@ -189,135 +140,495 @@ void cmQtAutoGenerator::LogCommandError(
msg.push_back('\n');
}
msg.push_back('\n');
cmSystemTools::Stderr(msg.c_str(), msg.size());
{
std::lock_guard<std::mutex> lock(Mutex_);
cmSystemTools::Stderr(msg.c_str(), msg.size());
}
}
/**
* @brief Generates the parent directory of the given file on demand
* @return True on success
*/
bool cmQtAutoGenerator::MakeParentDirectory(cmQtAutoGen::Generator genType,
std::string const& filename) const
std::string cmQtAutoGenerator::FileSystem::RealPath(
std::string const& filename)
{
bool success = true;
std::string const dirName = cmSystemTools::GetFilenamePath(filename);
if (!dirName.empty()) {
if (!cmSystemTools::MakeDirectory(dirName)) {
this->LogFileError(genType, filename,
"Could not create parent directory");
success = false;
std::lock_guard<std::mutex> lock(Mutex_);
return cmSystemTools::GetRealPath(filename);
}
bool cmQtAutoGenerator::FileSystem::FileExists(std::string const& filename)
{
std::lock_guard<std::mutex> lock(Mutex_);
return cmSystemTools::FileExists(filename);
}
bool cmQtAutoGenerator::FileSystem::FileIsOlderThan(
std::string const& buildFile, std::string const& sourceFile,
std::string* error)
{
bool res(false);
int result = 0;
{
std::lock_guard<std::mutex> lock(Mutex_);
res = cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result);
}
if (res) {
res = (result < 0);
} else {
if (error != nullptr) {
error->append(
"File modification time comparison failed for the files\n ");
error->append(Quoted(buildFile));
error->append("\nand\n ");
error->append(Quoted(sourceFile));
}
}
return success;
return res;
}
/**
* @brief Tests if buildFile is older than sourceFile
* @return True if buildFile is older than sourceFile.
* False may indicate an error.
*/
bool cmQtAutoGenerator::FileIsOlderThan(std::string const& buildFile,
std::string const& sourceFile,
std::string* error)
{
int result = 0;
if (cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result)) {
return (result < 0);
}
if (error != nullptr) {
error->append(
"File modification time comparison failed for the files\n ");
error->append(cmQtAutoGen::Quoted(buildFile));
error->append("\nand\n ");
error->append(cmQtAutoGen::Quoted(sourceFile));
}
return false;
}
bool cmQtAutoGenerator::FileRead(std::string& content,
std::string const& filename,
std::string* error)
bool cmQtAutoGenerator::FileSystem::FileRead(std::string& content,
std::string const& filename,
std::string* error)
{
bool success = false;
if (cmSystemTools::FileExists(filename)) {
std::size_t const length = cmSystemTools::FileLength(filename);
cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
if (ifs) {
content.resize(length);
ifs.read(&content.front(), content.size());
{
std::lock_guard<std::mutex> lock(Mutex_);
if (cmSystemTools::FileExists(filename)) {
std::size_t const length = cmSystemTools::FileLength(filename);
cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
if (ifs) {
success = true;
} else {
content.clear();
if (error != nullptr) {
error->append("Reading from the file failed.");
content.resize(length);
ifs.read(&content.front(), content.size());
if (ifs) {
success = true;
} else {
content.clear();
if (error != nullptr) {
error->append("Reading from the file failed.");
}
}
} else if (error != nullptr) {
error->append("Opening the file for reading failed.");
}
} else if (error != nullptr) {
error->append("Opening the file for reading failed.");
error->append("The file does not exist.");
}
} else if (error != nullptr) {
error->append("The file does not exist.");
}
return success;
}
bool cmQtAutoGenerator::FileWrite(cmQtAutoGen::Generator genType,
std::string const& filename,
std::string const& content)
bool cmQtAutoGenerator::FileSystem::FileRead(GeneratorT genType,
std::string& content,
std::string const& filename)
{
std::string error;
if (!FileRead(content, filename, &error)) {
Log()->ErrorFile(genType, filename, error);
return false;
}
return true;
}
bool cmQtAutoGenerator::FileSystem::FileWrite(std::string const& filename,
std::string const& content,
std::string* error)
{
bool success = false;
// Make sure the parent directory exists
if (this->MakeParentDirectory(genType, filename)) {
if (MakeParentDirectory(filename)) {
std::lock_guard<std::mutex> lock(Mutex_);
cmsys::ofstream outfile;
outfile.open(filename.c_str(),
(std::ios::out | std::ios::binary | std::ios::trunc));
if (outfile) {
outfile << content;
// Check for write errors
if (!outfile.good()) {
error = "File writing failed";
if (outfile.good()) {
success = true;
} else {
if (error != nullptr) {
error->assign("File writing failed");
}
}
} else {
error = "Opening file for writing failed";
if (error != nullptr) {
error->assign("Opening file for writing failed");
}
}
} else {
if (error != nullptr) {
error->assign("Could not create parent directory");
}
}
if (!error.empty()) {
this->LogFileError(genType, filename, error);
return success;
}
bool cmQtAutoGenerator::FileSystem::FileWrite(GeneratorT genType,
std::string const& filename,
std::string const& content)
{
std::string error;
if (!FileWrite(filename, content, &error)) {
Log()->ErrorFile(genType, filename, error);
return false;
}
return true;
}
bool cmQtAutoGenerator::FileDiffers(std::string const& filename,
std::string const& content)
bool cmQtAutoGenerator::FileSystem::FileDiffers(std::string const& filename,
std::string const& content)
{
bool differs = true;
{
std::string oldContents;
if (this->FileRead(oldContents, filename)) {
if (FileRead(oldContents, filename)) {
differs = (oldContents != content);
}
}
return differs;
}
/**
* @brief Runs a command and returns true on success
* @return True on success
*/
bool cmQtAutoGenerator::RunCommand(std::vector<std::string> const& command,
std::string& output) const
bool cmQtAutoGenerator::FileSystem::FileRemove(std::string const& filename)
{
// Log command
if (this->Verbose) {
std::string qcmd = QuotedCommand(command);
qcmd.push_back('\n');
cmSystemTools::Stdout(qcmd.c_str(), qcmd.size());
}
// Execute command
int retVal = 0;
bool res = cmSystemTools::RunSingleCommand(
command, &output, &output, &retVal, nullptr, cmSystemTools::OUTPUT_NONE);
return (res && (retVal == 0));
std::lock_guard<std::mutex> lock(Mutex_);
return cmSystemTools::RemoveFile(filename);
}
bool cmQtAutoGenerator::FileSystem::Touch(std::string const& filename)
{
std::lock_guard<std::mutex> lock(Mutex_);
return cmSystemTools::Touch(filename, false);
}
bool cmQtAutoGenerator::FileSystem::MakeDirectory(std::string const& dirname)
{
std::lock_guard<std::mutex> lock(Mutex_);
return cmSystemTools::MakeDirectory(dirname);
}
bool cmQtAutoGenerator::FileSystem::MakeDirectory(GeneratorT genType,
std::string const& dirname)
{
if (!MakeDirectory(dirname)) {
Log()->ErrorFile(genType, dirname, "Could not create directory");
return false;
}
return true;
}
bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
std::string const& filename)
{
bool success = true;
std::string const dirName = cmSystemTools::GetFilenamePath(filename);
if (!dirName.empty()) {
success = MakeDirectory(dirName);
}
return success;
}
bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
GeneratorT genType, std::string const& filename)
{
if (!MakeParentDirectory(filename)) {
Log()->ErrorFile(genType, filename, "Could not create parent directory");
return false;
}
return true;
}
int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop,
ReadOnlyProcessT* process)
{
Process_ = process;
Target_ = nullptr;
return UVPipe_.init(*uv_loop, 0, this);
}
int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target)
{
Target_ = target;
return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData);
}
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset()
{
Process_ = nullptr;
Target_ = nullptr;
UVPipe_.reset();
Buffer_.clear();
Buffer_.shrink_to_fit();
}
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle,
size_t suggestedSize,
uv_buf_t* buf)
{
auto& pipe = *reinterpret_cast<PipeT*>(handle->data);
pipe.Buffer_.resize(suggestedSize);
buf->base = &pipe.Buffer_.front();
buf->len = pipe.Buffer_.size();
}
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream,
ssize_t nread,
const uv_buf_t* buf)
{
auto& pipe = *reinterpret_cast<PipeT*>(stream->data);
if (nread > 0) {
// Append data to merged output
if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) {
pipe.Target_->append(buf->base, nread);
}
} else if (nread < 0) {
// EOF or error
auto* proc = pipe.Process_;
// Check it this an unusual error
if (nread != UV_EOF) {
if (!proc->Result()->error()) {
proc->Result()->ErrorMessage =
"libuv reading from pipe failed with error code ";
proc->Result()->ErrorMessage += std::to_string(nread);
}
}
// Clear libuv pipe handle and try to finish
pipe.reset();
proc->UVTryFinish();
}
}
void cmQtAutoGenerator::ProcessResultT::reset()
{
ExitStatus = 0;
TermSignal = 0;
if (!StdOut.empty()) {
StdOut.clear();
StdOut.shrink_to_fit();
}
if (!StdErr.empty()) {
StdErr.clear();
StdErr.shrink_to_fit();
}
if (!ErrorMessage.empty()) {
ErrorMessage.clear();
ErrorMessage.shrink_to_fit();
}
}
void cmQtAutoGenerator::ReadOnlyProcessT::setup(
ProcessResultT* result, bool mergedOutput,
std::vector<std::string> const& command, std::string const& workingDirectory)
{
Setup_.WorkingDirectory = workingDirectory;
Setup_.Command = command;
Setup_.Result = result;
Setup_.MergedOutput = mergedOutput;
}
bool cmQtAutoGenerator::ReadOnlyProcessT::start(
uv_loop_t* uv_loop, std::function<void()>&& finishedCallback)
{
if (IsStarted() || (Result() == nullptr)) {
return false;
}
// Reset result before the start
Result()->reset();
// Fill command string pointers
if (!Setup().Command.empty()) {
CommandPtr_.reserve(Setup().Command.size() + 1);
for (std::string const& arg : Setup().Command) {
CommandPtr_.push_back(arg.c_str());
}
CommandPtr_.push_back(nullptr);
} else {
Result()->ErrorMessage = "Empty command";
}
if (!Result()->error()) {
if (UVPipeOut_.init(uv_loop, this) != 0) {
Result()->ErrorMessage = "libuv stdout pipe initialization failed";
}
}
if (!Result()->error()) {
if (UVPipeErr_.init(uv_loop, this) != 0) {
Result()->ErrorMessage = "libuv stderr pipe initialization failed";
}
}
if (!Result()->error()) {
// -- Setup process stdio options
// stdin
UVOptionsStdIO_[0].flags = UV_IGNORE;
UVOptionsStdIO_[0].data.stream = nullptr;
// stdout
UVOptionsStdIO_[1].flags =
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
// stderr
UVOptionsStdIO_[2].flags =
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
// -- Setup process options
std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit;
UVOptions_.file = CommandPtr_[0];
UVOptions_.args = const_cast<char**>(&CommandPtr_.front());
UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
UVOptions_.stdio = &UVOptionsStdIO_.front();
// -- Spawn process
if (UVProcess_.spawn(*uv_loop, UVOptions_, this) != 0) {
Result()->ErrorMessage = "libuv process spawn failed";
}
}
// -- Start reading from stdio streams
if (!Result()->error()) {
if (UVPipeOut_.startRead(&Result()->StdOut) != 0) {
Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
}
}
if (!Result()->error()) {
if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut
: &Result()->StdErr) != 0) {
Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
}
}
if (!Result()->error()) {
IsStarted_ = true;
FinishedCallback_ = std::move(finishedCallback);
} else {
// Clear libuv handles and finish
UVProcess_.reset();
UVPipeOut_.reset();
UVPipeErr_.reset();
CommandPtr_.clear();
}
return IsStarted();
}
void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle,
int64_t exitStatus,
int termSignal)
{
auto& proc = *reinterpret_cast<ReadOnlyProcessT*>(handle->data);
if (proc.IsStarted() && !proc.IsFinished()) {
// Set error message on demand
proc.Result()->ExitStatus = exitStatus;
proc.Result()->TermSignal = termSignal;
if (!proc.Result()->error()) {
if (termSignal != 0) {
proc.Result()->ErrorMessage = "Process was terminated by signal ";
proc.Result()->ErrorMessage +=
std::to_string(proc.Result()->TermSignal);
} else if (exitStatus != 0) {
proc.Result()->ErrorMessage = "Process failed with return value ";
proc.Result()->ErrorMessage +=
std::to_string(proc.Result()->ExitStatus);
}
}
// Reset process handle and try to finish
proc.UVProcess_.reset();
proc.UVTryFinish();
}
}
void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish()
{
// There still might be data in the pipes after the process has finished.
// Therefore check if the process is finished AND all pipes are closed before
// signaling the worker thread to continue.
if (UVProcess_.get() == nullptr) {
if (UVPipeOut_.uv_pipe() == nullptr) {
if (UVPipeErr_.uv_pipe() == nullptr) {
IsFinished_ = true;
FinishedCallback_();
}
}
}
}
cmQtAutoGenerator::cmQtAutoGenerator()
: FileSys_(&Logger_)
{
// Initialize logger
Logger_.SetVerbose(cmSystemTools::HasEnv("VERBOSE"));
{
std::string colorEnv;
cmSystemTools::GetEnv("COLOR", colorEnv);
if (!colorEnv.empty()) {
Logger_.SetColorOutput(cmSystemTools::IsOn(colorEnv.c_str()));
} else {
Logger_.SetColorOutput(true);
}
}
// Initialize libuv loop
uv_disable_stdio_inheritance();
#ifdef CMAKE_UV_SIGNAL_HACK
UVHackRAII_ = cm::make_unique<cmUVSignalHackRAII>();
#endif
UVLoop_ = cm::make_unique<uv_loop_t>();
uv_loop_init(UVLoop());
}
cmQtAutoGenerator::~cmQtAutoGenerator()
{
// Close libuv loop
uv_loop_close(UVLoop());
}
bool cmQtAutoGenerator::Run(std::string const& infoFile,
std::string const& config)
{
// Info settings
InfoFile_ = infoFile;
cmSystemTools::ConvertToUnixSlashes(InfoFile_);
InfoDir_ = cmSystemTools::GetFilenamePath(infoFile);
InfoConfig_ = config;
bool success = false;
{
cmake cm(cmake::RoleScript);
cm.SetHomeOutputDirectory(InfoDir());
cm.SetHomeDirectory(InfoDir());
cm.GetCurrentSnapshot().SetDefaultDefinitions();
cmGlobalGenerator gg(&cm);
cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
snapshot.GetDirectory().SetCurrentBinary(InfoDir());
snapshot.GetDirectory().SetCurrentSource(InfoDir());
auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
// The OLD/WARN behavior for policy CMP0053 caused a speed regression.
// https://gitlab.kitware.com/cmake/cmake/issues/17570
makefile->SetPolicyVersion("3.9");
gg.SetCurrentMakefile(makefile.get());
success = this->Init(makefile.get());
}
if (success) {
success = this->Process();
}
return success;
}
std::string cmQtAutoGenerator::SettingsFind(std::string const& content,
const char* key)
{
std::string prefix(key);
prefix += ':';
std::string::size_type pos = content.find(prefix);
if (pos != std::string::npos) {
pos += prefix.size();
if (pos < content.size()) {
std::string::size_type posE = content.find('\n', pos);
if ((posE != std::string::npos) && (posE != pos)) {
return content.substr(pos, posE - pos);
}
}
}
return std::string();
}
+260 -48
View File
@@ -6,71 +6,283 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmQtAutoGen.h"
#include "cmUVHandlePtr.h"
#include "cm_uv.h"
#include <array>
#include <functional>
#include <mutex>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
class cmMakefile;
class cmQtAutoGenerator
/// @brief Base class for QtAutoGen gernerators
class cmQtAutoGenerator : public cmQtAutoGen
{
CM_DISABLE_COPY(cmQtAutoGenerator)
public:
// -- Types
/// @brief Thread safe logging
class Logger
{
public:
// -- Verbosity
bool Verbose() const { return this->Verbose_; }
void SetVerbose(bool value);
bool ColorOutput() const { return this->ColorOutput_; }
void SetColorOutput(bool value);
// -- Log info
void Info(GeneratorT genType, std::string const& message);
// -- Log warning
void Warning(GeneratorT genType, std::string const& message);
void WarningFile(GeneratorT genType, std::string const& filename,
std::string const& message);
// -- Log error
void Error(GeneratorT genType, std::string const& message);
void ErrorFile(GeneratorT genType, std::string const& filename,
std::string const& message);
void ErrorCommand(GeneratorT genType, std::string const& message,
std::vector<std::string> const& command,
std::string const& output);
private:
static std::string HeadLine(std::string const& title);
private:
std::mutex Mutex_;
bool volatile Verbose_ = false;
bool volatile ColorOutput_ = false;
};
/// @brief Thread safe file system interface
class FileSystem
{
public:
FileSystem(Logger* log)
: Log_(log)
{
}
Logger* Log() const { return Log_; }
std::string RealPath(std::string const& filename);
bool FileExists(std::string const& filename);
bool FileIsOlderThan(std::string const& buildFile,
std::string const& sourceFile,
std::string* error = nullptr);
bool FileRead(std::string& content, std::string const& filename,
std::string* error = nullptr);
/// @brief Error logging version
bool FileRead(GeneratorT genType, std::string& content,
std::string const& filename);
bool FileWrite(std::string const& filename, std::string const& content,
std::string* error = nullptr);
/// @brief Error logging version
bool FileWrite(GeneratorT genType, std::string const& filename,
std::string const& content);
bool FileDiffers(std::string const& filename, std::string const& content);
bool FileRemove(std::string const& filename);
bool Touch(std::string const& filename);
bool MakeDirectory(std::string const& dirname);
/// @brief Error logging version
bool MakeDirectory(GeneratorT genType, std::string const& dirname);
bool MakeParentDirectory(std::string const& filename);
/// @brief Error logging version
bool MakeParentDirectory(GeneratorT genType, std::string const& filename);
private:
std::mutex Mutex_;
Logger* Log_;
};
/// @brief Return value and output of an external process
struct ProcessResultT
{
void reset();
bool error() const
{
return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
}
std::int64_t ExitStatus = 0;
int TermSignal = 0;
std::string StdOut;
std::string StdErr;
std::string ErrorMessage;
};
/// @brief External process management class
struct ReadOnlyProcessT
{
// -- Types
/// @brief libuv pipe buffer class
class PipeT
{
public:
int init(uv_loop_t* uv_loop, ReadOnlyProcessT* process);
int startRead(std::string* target);
void reset();
// -- Libuv casts
uv_pipe_t* uv_pipe() { return UVPipe_.get(); }
uv_stream_t* uv_stream()
{
return reinterpret_cast<uv_stream_t*>(uv_pipe());
}
uv_handle_t* uv_handle()
{
return reinterpret_cast<uv_handle_t*>(uv_pipe());
}
// -- Libuv callbacks
static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf);
static void UVData(uv_stream_t* stream, ssize_t nread,
const uv_buf_t* buf);
private:
ReadOnlyProcessT* Process_ = nullptr;
std::string* Target_ = nullptr;
std::vector<char> Buffer_;
cm::uv_pipe_ptr UVPipe_;
};
/// @brief Process settings
struct SetupT
{
std::string WorkingDirectory;
std::vector<std::string> Command;
ProcessResultT* Result = nullptr;
bool MergedOutput = false;
};
// -- Constructor
ReadOnlyProcessT() = default;
// -- Const accessors
const SetupT& Setup() const { return Setup_; }
ProcessResultT* Result() const { return Setup_.Result; }
bool IsStarted() const { return IsStarted_; }
bool IsFinished() const { return IsFinished_; }
// -- Runtime
void setup(ProcessResultT* result, bool mergedOutput,
std::vector<std::string> const& command,
std::string const& workingDirectory = std::string());
bool start(uv_loop_t* uv_loop, std::function<void()>&& finishedCallback);
private:
// -- Friends
friend class PipeT;
// -- Libuv callbacks
static void UVExit(uv_process_t* handle, int64_t exitStatus,
int termSignal);
void UVTryFinish();
// -- Setup
SetupT Setup_;
// -- Runtime
bool IsStarted_ = false;
bool IsFinished_ = false;
std::function<void()> FinishedCallback_;
std::vector<const char*> CommandPtr_;
std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
uv_process_options_t UVOptions_;
cm::uv_process_ptr UVProcess_;
PipeT UVPipeOut_;
PipeT UVPipeErr_;
};
#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) && \
UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19
#define CMAKE_UV_SIGNAL_HACK
/*
libuv does not use SA_RESTART on its signal handler, but C++ streams
depend on it for reliable i/o operations. This RAII helper convinces
libuv to install its handler, and then revises the handler to add the
SA_RESTART flag. We use a distinct uv loop that never runs to avoid
ever really getting a callback. libuv may fill the hack loop's signal
pipe and then stop writing, but that won't break any real loops.
*/
class cmUVSignalHackRAII
{
uv_loop_t HackLoop;
cm::uv_signal_ptr HackSignal;
static void HackCB(uv_signal_t*, int) {}
public:
cmUVSignalHackRAII()
{
uv_loop_init(&this->HackLoop);
this->HackSignal.init(this->HackLoop);
this->HackSignal.start(HackCB, SIGCHLD);
struct sigaction hack_sa;
sigaction(SIGCHLD, NULL, &hack_sa);
if (!(hack_sa.sa_flags & SA_RESTART)) {
hack_sa.sa_flags |= SA_RESTART;
sigaction(SIGCHLD, &hack_sa, NULL);
}
}
~cmUVSignalHackRAII()
{
this->HackSignal.stop();
uv_loop_close(&this->HackLoop);
}
};
#endif
public:
// -- Constructors
cmQtAutoGenerator();
virtual ~cmQtAutoGenerator() = default;
virtual ~cmQtAutoGenerator();
// -- Run
bool Run(std::string const& infoFile, std::string const& config);
std::string const& GetInfoFile() const { return InfoFile; }
std::string const& GetInfoDir() const { return InfoDir; }
std::string const& GetInfoConfig() const { return InfoConfig; }
bool GetVerbose() const { return Verbose; }
// -- Accessors
// Logging
Logger& Log() { return Logger_; }
// File System
FileSystem& FileSys() { return FileSys_; }
// InfoFile
std::string const& InfoFile() const { return InfoFile_; }
std::string const& InfoDir() const { return InfoDir_; }
std::string const& InfoConfig() const { return InfoConfig_; }
// libuv loop
uv_loop_t* UVLoop() { return UVLoop_.get(); }
cm::uv_async_ptr& UVRequest() { return UVRequest_; }
// -- Utility
static std::string SettingsFind(std::string const& content, const char* key);
protected:
// -- Central processing
virtual bool Process(cmMakefile* makefile) = 0;
// -- Log info
void LogBold(std::string const& message) const;
void LogInfo(cmQtAutoGen::Generator genType,
std::string const& message) const;
// -- Log warning
void LogWarning(cmQtAutoGen::Generator genType,
std::string const& message) const;
void LogFileWarning(cmQtAutoGen::Generator genType,
std::string const& filename,
std::string const& message) const;
// -- Log error
void LogError(cmQtAutoGen::Generator genType,
std::string const& message) const;
void LogFileError(cmQtAutoGen::Generator genType,
std::string const& filename,
std::string const& message) const;
void LogCommandError(cmQtAutoGen::Generator genType,
std::string const& message,
std::vector<std::string> const& command,
std::string const& output) const;
// -- Utility
bool MakeParentDirectory(cmQtAutoGen::Generator genType,
std::string const& filename) const;
bool FileIsOlderThan(std::string const& buildFile,
std::string const& sourceFile,
std::string* error = nullptr);
bool FileRead(std::string& content, std::string const& filename,
std::string* error = nullptr);
bool FileWrite(cmQtAutoGen::Generator genType, std::string const& filename,
std::string const& content);
bool FileDiffers(std::string const& filename, std::string const& content);
bool RunCommand(std::vector<std::string> const& command,
std::string& output) const;
// -- Abstract processing interface
virtual bool Init(cmMakefile* makefile) = 0;
virtual bool Process() = 0;
private:
// -- Logging
Logger Logger_;
FileSystem FileSys_;
// -- Info settings
std::string InfoFile;
std::string InfoDir;
std::string InfoConfig;
// -- Settings
bool Verbose;
bool ColorOutput;
std::string InfoFile_;
std::string InfoDir_;
std::string InfoConfig_;
// -- libuv loop
#ifdef CMAKE_UV_SIGNAL_HACK
std::unique_ptr<cmUVSignalHackRAII> UVHackRAII_;
#endif
std::unique_ptr<uv_loop_t> UVLoop_;
cm::uv_async_ptr UVRequest_;
};
#endif
+140 -37
View File
@@ -24,6 +24,7 @@
#include "cm_sys_stat.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
#include "cmsys/SystemInformation.hxx"
#include <algorithm>
#include <array>
@@ -52,6 +53,20 @@ inline static std::string GetSafeProperty(cmSourceFile const* sf,
return std::string(SafeString(sf->GetProperty(key)));
}
static std::size_t GetParallelCPUCount()
{
static std::size_t count = 0;
// Detect only on the first call
if (count == 0) {
cmsys::SystemInformation info;
info.RunCPUCheck();
count = info.GetNumberOfPhysicalCPU();
count = std::max<std::size_t>(count, 1);
count = std::min<std::size_t>(count, cmQtAutoGen::ParallelMax);
}
return count;
}
static void AddDefinitionEscaped(cmMakefile* makefile, const char* key,
std::string const& value)
{
@@ -85,12 +100,12 @@ static void AddDefinitionEscaped(
seplist.push_back(std::move(blist));
}
makefile->AddDefinition(key, cmOutputConverter::EscapeForCMake(
cmJoin(seplist, cmQtAutoGen::listSep))
cmJoin(seplist, cmQtAutoGen::ListSep))
.c_str());
}
static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
cmQtAutoGen::Generator genType)
cmQtAutoGen::GeneratorT genType)
{
cmSourceGroup* sourceGroup = nullptr;
// Acquire source group
@@ -101,10 +116,10 @@ static bool AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
std::array<std::string, 2> props;
// Use generator specific group name
switch (genType) {
case cmQtAutoGen::MOC:
case cmQtAutoGen::GeneratorT::MOC:
props[0] = "AUTOMOC_SOURCE_GROUP";
break;
case cmQtAutoGen::RCC:
case cmQtAutoGen::GeneratorT::RCC:
props[0] = "AUTORCC_SOURCE_GROUP";
break;
default:
@@ -219,7 +234,7 @@ cmQtAutoGeneratorInitializer::cmQtAutoGeneratorInitializer(
, UicEnabled(uicEnabled)
, RccEnabled(rccEnabled)
, QtVersionMajor(qtVersionMajor)
, MultiConfig(cmQtAutoGen::WRAP)
, MultiConfig(MultiConfigT::WRAPPER)
{
this->QtVersionMinor = cmQtAutoGeneratorInitializer::GetQtMinorVersion(
target, this->QtVersionMajor);
@@ -240,19 +255,19 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
// Multi configuration
{
if (!globalGen->IsMultiConfig()) {
this->MultiConfig = cmQtAutoGen::SINGLE;
this->MultiConfig = MultiConfigT::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;
// return MultiConfigT::MULTI;
//}
// 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;
// return MultiConfigT::MULTI;
//}
}
@@ -294,7 +309,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
this->AutogenInfoFile += "/AutogenInfo.cmake";
this->AutogenSettingsFile = this->DirInfo;
this->AutogenSettingsFile += "/AutogenOldSettings.cmake";
this->AutogenSettingsFile += "/AutogenOldSettings.txt";
}
// Autogen target FOLDER property
@@ -324,7 +339,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
{
std::string base = this->DirInfo;
base += "/AutogenOldSettings";
if (this->MultiConfig == cmQtAutoGen::SINGLE) {
if (this->MultiConfig == MultiConfigT::SINGLE) {
AddCleanFile(makefile, base.append(".cmake"));
} else {
for (std::string const& cfg : this->ConfigsList) {
@@ -340,7 +355,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
// 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);
auto files = this->AddGeneratedSource(mocsComp, GeneratorT::MOC);
for (std::string& file : files) {
autogenProvides.push_back(std::move(file));
}
@@ -349,7 +364,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
// 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) {
if (this->MultiConfig != MultiConfigT::SINGLE) {
includeDir += "_$<CONFIG>";
}
this->Target->AddIncludeDirectory(includeDir, true);
@@ -560,10 +575,10 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
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");
msg.append(" ").append(Quoted(absFile)).append("\n");
}
for (const std::string& absFile : generatedSources) {
msg.append(" ").append(cmQtAutoGen::Quoted(absFile)).append("\n");
msg.append(" ").append(Quoted(absFile)).append("\n");
}
msg += "from processing by ";
msg += tools;
@@ -630,7 +645,7 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
qrc.InfoFile = base;
qrc.InfoFile += "Info.cmake";
qrc.SettingsFile = base;
qrc.SettingsFile += "Settings.cmake";
qrc.SettingsFile += "Settings.txt";
}
}
}
@@ -650,16 +665,16 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
std::vector<std::string> nameOpts;
nameOpts.emplace_back("-name");
nameOpts.emplace_back(std::move(name));
cmQtAutoGen::RccMergeOptions(opts, nameOpts, QtV5);
RccMergeOptions(opts, nameOpts, QtV5);
}
// Merge file option
cmQtAutoGen::RccMergeOptions(opts, qrc.Options, QtV5);
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);
this->AddGeneratedSource(qrc.RccFile, GeneratorT::RCC);
cmCustomCommandLines commandLines;
{
@@ -686,8 +701,9 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
ccName += qrc.PathChecksum;
}
std::vector<std::string> ccDepends;
// Add the .qrc file to the custom target dependencies
// Add the .qrc and info file to the custom target dependencies
ccDepends.push_back(qrc.QrcFile);
ccDepends.push_back(qrc.InfoFile);
cmTarget* autoRccTarget = makefile->AddUtilityCommand(
ccName, cmMakefile::TargetOrigin::Generator, true,
@@ -709,15 +725,14 @@ void cmQtAutoGeneratorInitializer::InitCustomTargets()
{
std::vector<std::string> ccByproducts;
std::vector<std::string> ccDepends;
// Add the .qrc file to the custom command dependencies
// Add the .qrc and info file to the custom command dependencies
ccDepends.push_back(qrc.QrcFile);
ccDepends.push_back(qrc.InfoFile);
// Add the resource files to the dependencies
{
std::string error;
if (cmQtAutoGen::RccListInputs(this->RccExecutable,
this->RccListOptions, qrc.QrcFile,
qrc.Resources, &error)) {
if (RccListInputs(qrc.QrcFile, qrc.Resources, error)) {
for (std::string const& fileName : qrc.Resources) {
// Add resource file to the custom command dependencies
ccDepends.push_back(fileName);
@@ -903,8 +918,16 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
// Basic setup
AddDefinitionEscaped(makefile, "_multi_config",
cmQtAutoGen::MultiConfigName(this->MultiConfig));
MultiConfigName(this->MultiConfig));
AddDefinitionEscaped(makefile, "_build_dir", this->DirBuild);
{
std::string parallel = GetSafeProperty(this->Target, "AUTOGEN_PARALLEL");
// Autodetect number of CPUs
if (parallel.empty() || (parallel == "AUTO")) {
parallel = std::to_string(GetParallelCPUCount());
}
AddDefinitionEscaped(makefile, "_parallel", parallel);
}
if (this->MocEnabled || this->UicEnabled) {
AddDefinitionEscaped(makefile, "_qt_version_major", this->QtVersionMajor);
@@ -929,7 +952,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
// Create info directory on demand
if (!cmSystemTools::MakeDirectory(this->DirInfo)) {
std::string emsg = ("Could not create directory: ");
emsg += cmQtAutoGen::Quoted(this->DirInfo);
emsg += Quoted(this->DirInfo);
cmSystemTools::Error(emsg.c_str());
}
@@ -951,7 +974,7 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
if (!ofs) {
// File open error
std::string error = "Internal CMake error when trying to open file: ";
error += cmQtAutoGen::Quoted(fileName);
error += Quoted(fileName);
error += " for writing.";
cmSystemTools::Error(error.c_str());
}
@@ -984,11 +1007,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
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) {
if (this->MultiConfig != MultiConfigT::SINGLE) {
std::map<std::string, std::string> settingsFiles;
for (std::string const& cfg : this->ConfigsList) {
settingsFiles[cfg] = cmQtAutoGen::AppendFilenameSuffix(
this->AutogenSettingsFile, "_" + cfg);
settingsFiles[cfg] =
AppendFilenameSuffix(this->AutogenSettingsFile, "_" + cfg);
}
OfsWriteMap("AM_SETTINGS_FILE", settingsFiles);
}
@@ -1033,11 +1056,11 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargets()
OfsWriteMap("ARCC_CONFIG_SUFFIX", configSuffixes);
// Settings files (only require for multi configuration generators)
if (this->MultiConfig != cmQtAutoGen::SINGLE) {
if (this->MultiConfig != MultiConfigT::SINGLE) {
std::map<std::string, std::string> settingsFiles;
for (std::string const& cfg : this->ConfigsList) {
settingsFiles[cfg] =
cmQtAutoGen::AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
}
OfsWriteMap("ARCC_SETTINGS_FILE", settingsFiles);
}
@@ -1267,16 +1290,15 @@ void cmQtAutoGeneratorInitializer::SetupCustomTargetsUic()
}
std::vector<std::string> cmQtAutoGeneratorInitializer::AddGeneratedSource(
std::string const& filename, cmQtAutoGen::Generator genType)
std::string const& filename, GeneratorT genType)
{
std::vector<std::string> genFiles;
// Register source file in makefile and source group
if (this->MultiConfig != cmQtAutoGen::FULL) {
if (this->MultiConfig != MultiConfigT::MULTI) {
genFiles.push_back(filename);
} else {
for (std::string const& cfg : this->ConfigsList) {
genFiles.push_back(
cmQtAutoGen::AppendFilenameSuffix(filename, "_" + cfg));
genFiles.push_back(AppendFilenameSuffix(filename, "_" + cfg));
}
}
{
@@ -1292,14 +1314,14 @@ std::vector<std::string> cmQtAutoGeneratorInitializer::AddGeneratedSource(
}
// Add source file to target
if (this->MultiConfig != cmQtAutoGen::FULL) {
if (this->MultiConfig != MultiConfigT::MULTI) {
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 += AppendFilenameSuffix(filename, "_" + cfg);
src += ">";
this->Target->AddSource(src);
}
@@ -1356,3 +1378,84 @@ bool cmQtAutoGeneratorInitializer::QtVersionGreaterOrEqual(
}
return false;
}
/// @brief Reads the resource files list from from a .qrc file
/// @arg fileName Must be the absolute path of the .qrc file
/// @return True if the rcc file was successfully read
bool cmQtAutoGeneratorInitializer::RccListInputs(
std::string const& fileName, std::vector<std::string>& files,
std::string& error)
{
if (!cmSystemTools::FileExists(fileName.c_str())) {
error = "rcc resource file does not exist:\n ";
error += Quoted(fileName);
error += "\n";
return false;
}
if (!RccListOptions.empty()) {
// Use rcc for file listing
if (RccExecutable.empty()) {
error = "rcc executable not available";
return false;
}
// Run rcc list command in the directory of the qrc file with the pathless
// qrc file name argument. This way rcc prints relative paths.
// This avoids issues on Windows when the qrc file is in a path that
// contains non-ASCII characters.
std::string const fileDir = cmSystemTools::GetFilenamePath(fileName);
std::string const fileNameName = cmSystemTools::GetFilenameName(fileName);
bool result = false;
int retVal = 0;
std::string rccStdOut;
std::string rccStdErr;
{
std::vector<std::string> cmd;
cmd.push_back(RccExecutable);
cmd.insert(cmd.end(), RccListOptions.begin(), RccListOptions.end());
cmd.push_back(fileNameName);
result = cmSystemTools::RunSingleCommand(
cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
cmSystemTools::OUTPUT_NONE, 0.0, cmProcessOutput::Auto);
}
if (!result || retVal) {
error = "rcc list process failed for:\n ";
error += Quoted(fileName);
error += "\n";
error += rccStdOut;
error += "\n";
error += rccStdErr;
error += "\n";
return false;
}
if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) {
return false;
}
} else {
// We can't use rcc for the file listing.
// Read the qrc file content into string and parse it.
{
std::string qrcContents;
{
cmsys::ifstream ifs(fileName.c_str());
if (ifs) {
std::ostringstream osst;
osst << ifs.rdbuf();
qrcContents = osst.str();
} else {
error = "rcc file not readable:\n ";
error += Quoted(fileName);
error += "\n";
return false;
}
}
// Parse string content
RccListParseContent(qrcContents, files);
}
}
// Convert relative paths to absolute paths
RccListConvertFullPath(cmSystemTools::GetFilenamePath(fileName), files);
return true;
}
+8 -3
View File
@@ -13,7 +13,8 @@
class cmGeneratorTarget;
class cmQtAutoGeneratorInitializer
/// @brief Initializes the QtAutoGen generators
class cmQtAutoGeneratorInitializer : public cmQtAutoGen
{
public:
static std::string GetQtMajorVersion(cmGeneratorTarget const* target);
@@ -55,11 +56,15 @@ private:
void SetupCustomTargetsUic();
std::vector<std::string> AddGeneratedSource(std::string const& filename,
cmQtAutoGen::Generator genType);
GeneratorT genType);
bool QtVersionGreaterOrEqual(unsigned long requestMajor,
unsigned long requestMinor) const;
bool RccListInputs(std::string const& fileName,
std::vector<std::string>& files,
std::string& errorMessage);
private:
cmGeneratorTarget* Target;
bool MocEnabled;
@@ -73,7 +78,7 @@ private:
// Configurations
std::string ConfigDefault;
std::vector<std::string> ConfigsList;
cmQtAutoGen::MultiConfig MultiConfig;
MultiConfigT MultiConfig;
// Names
std::string AutogenTargetName;
std::string AutogenFolder;
File diff suppressed because it is too large Load Diff
+373 -127
View File
@@ -8,188 +8,434 @@
#include "cmFilePathChecksum.h"
#include "cmQtAutoGen.h"
#include "cmQtAutoGenerator.h"
#include "cmUVHandlePtr.h"
#include "cm_uv.h"
#include "cmsys/RegularExpression.hxx"
#include <algorithm>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <map>
#include <memory> // IWYU pragma: keep
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <vector>
class cmMakefile;
// @brief AUTOMOC and AUTOUIC generator
class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator
{
CM_DISABLE_COPY(cmQtAutoGeneratorMocUic)
public:
cmQtAutoGeneratorMocUic();
~cmQtAutoGeneratorMocUic() override;
private:
public:
// -- Types
class WorkerT;
/// @brief Search key plus regular expression pair
struct KeyRegExp
///
struct KeyExpT
{
KeyRegExp() = default;
KeyExpT() = default;
KeyRegExp(const char* key, const char* regExp)
KeyExpT(const char* key, const char* exp)
: Key(key)
, RegExp(regExp)
, Exp(exp)
{
}
KeyRegExp(std::string const& key, std::string const& regExp)
KeyExpT(std::string const& key, std::string const& exp)
: Key(key)
, RegExp(regExp)
, Exp(exp)
{
}
std::string Key;
cmsys::RegularExpression RegExp;
cmsys::RegularExpression Exp;
};
/// @brief Source file job
struct SourceJob
/// @brief Common settings
///
class BaseSettingsT
{
bool Moc = false;
bool Uic = false;
CM_DISABLE_COPY(BaseSettingsT)
public:
// -- Volatile methods
BaseSettingsT(FileSystem* fileSystem)
: MultiConfig(MultiConfigT::WRAPPER)
, IncludeProjectDirsBefore(false)
, QtVersionMajor(4)
, NumThreads(1)
, FileSys(fileSystem)
{
}
// -- Const methods
std::string AbsoluteBuildPath(std::string const& relativePath) const;
bool FindHeader(std::string& header,
std::string const& testBasePath) const;
// -- Attributes
// - Config
std::string ConfigSuffix;
MultiConfigT MultiConfig;
bool IncludeProjectDirsBefore;
unsigned int QtVersionMajor;
unsigned int NumThreads;
// - Directories
std::string ProjectSourceDir;
std::string ProjectBinaryDir;
std::string CurrentSourceDir;
std::string CurrentBinaryDir;
std::string AutogenBuildDir;
std::string AutogenIncludeDirRel;
std::string AutogenIncludeDirAbs;
// - Files
cmFilePathChecksum FilePathChecksum;
std::vector<std::string> HeaderExtensions;
// - File system
FileSystem* FileSys;
};
/// @brief MOC job
struct MocJobAuto
/// @brief Moc settings
///
class MocSettingsT
{
CM_DISABLE_COPY(MocSettingsT)
public:
MocSettingsT(FileSystem* fileSys)
: FileSys(fileSys)
{
}
// -- Const methods
bool skipped(std::string const& fileName) const;
std::string FindMacro(std::string const& content) const;
std::string MacrosString() const;
std::string FindIncludedFile(std::string const& sourcePath,
std::string const& includeString) const;
void FindDependencies(std::string const& content,
std::set<std::string>& depends) const;
// -- Attributes
bool Enabled = false;
bool SettingsChanged = false;
bool RelaxedMode = false;
std::string Executable;
std::string CompFileRel;
std::string CompFileAbs;
std::string PredefsFileRel;
std::string PredefsFileAbs;
std::set<std::string> SkipList;
std::vector<std::string> IncludePaths;
std::vector<std::string> Includes;
std::vector<std::string> Definitions;
std::vector<std::string> Options;
std::vector<std::string> AllOptions;
std::vector<std::string> PredefsCmd;
std::vector<KeyExpT> DependFilters;
std::vector<KeyExpT> MacroFilters;
cmsys::RegularExpression RegExpInclude;
// - File system
FileSystem* FileSys;
};
/// @brief Uic settings
///
class UicSettingsT
{
CM_DISABLE_COPY(UicSettingsT)
public:
UicSettingsT() = default;
// -- Const methods
bool skipped(std::string const& fileName) const;
// -- Attributes
bool Enabled = false;
bool SettingsChanged = false;
std::string Executable;
std::set<std::string> SkipList;
std::vector<std::string> TargetOptions;
std::map<std::string, std::vector<std::string>> Options;
std::vector<std::string> SearchPaths;
cmsys::RegularExpression RegExpInclude;
};
/// @brief Abstract job class for threaded processing
///
class JobT
{
CM_DISABLE_COPY(JobT)
public:
JobT() = default;
virtual ~JobT() = default;
// -- Abstract processing interface
virtual void Process(WorkerT& wrk) = 0;
};
/// @brief Deleter for classes derived from Job
///
struct JobDeleterT
{
void operator()(JobT* job);
};
// Job management types
typedef std::unique_ptr<JobT, JobDeleterT> JobHandleT;
typedef std::deque<JobHandleT> JobQueueT;
/// @brief Parse source job
///
class JobParseT : public JobT
{
public:
JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false)
: FileName(std::move(fileName))
, AutoMoc(moc)
, AutoUic(uic)
, Header(header)
{
}
private:
struct MetaT
{
std::string Content;
std::string FileDir;
std::string FileBase;
};
void Process(WorkerT& wrk) override;
bool ParseMocSource(WorkerT& wrk, MetaT const& meta);
bool ParseMocHeader(WorkerT& wrk, MetaT const& meta);
std::string MocStringHeaders(WorkerT& wrk,
std::string const& fileBase) const;
std::string MocFindIncludedHeader(WorkerT& wrk,
std::string const& includerDir,
std::string const& includeBase);
bool ParseUic(WorkerT& wrk, MetaT const& meta);
bool ParseUicInclude(WorkerT& wrk, MetaT const& meta,
std::string&& includeString);
std::string UicFindIncludedFile(WorkerT& wrk, MetaT const& meta,
std::string const& includeString);
private:
std::string FileName;
bool AutoMoc = false;
bool AutoUic = false;
bool Header = false;
};
/// @brief Generate moc_predefs
///
class JobMocPredefsT : public JobT
{
private:
void Process(WorkerT& wrk) override;
};
/// @brief Moc a file job
///
class JobMocT : public JobT
{
public:
JobMocT(std::string&& sourceFile, std::string const& includerFile,
std::string&& includeString)
: SourceFile(std::move(sourceFile))
, IncluderFile(includerFile)
, IncludeString(std::move(includeString))
{
}
void FindDependencies(WorkerT& wrk, std::string const& content);
private:
void Process(WorkerT& wrk) override;
bool UpdateRequired(WorkerT& wrk);
void GenerateMoc(WorkerT& wrk);
public:
std::string SourceFile;
std::string BuildFileRel;
std::string IncluderFile;
std::string IncludeString;
std::string BuildFile;
bool DependsValid = false;
std::set<std::string> Depends;
};
/// @brief MOC job
struct MocJobIncluded : MocJobAuto
/// @brief Uic a file job
///
class JobUicT : public JobT
{
bool DependsValid = false;
std::string Includer;
std::string IncludeString;
};
public:
JobUicT(std::string&& sourceFile, std::string const& includerFile,
std::string&& includeString)
: SourceFile(std::move(sourceFile))
, IncluderFile(includerFile)
, IncludeString(std::move(includeString))
{
}
/// @brief UIC job
struct UicJob
{
private:
void Process(WorkerT& wrk) override;
bool UpdateRequired(WorkerT& wrk);
void GenerateUic(WorkerT& wrk);
public:
std::string SourceFile;
std::string BuildFileRel;
std::string Includer;
std::string IncluderFile;
std::string IncludeString;
std::string BuildFile;
};
// -- Initialization
bool InitInfoFile(cmMakefile* makefile);
// -- Settings file
void SettingsFileRead(cmMakefile* makefile);
bool SettingsFileWrite();
bool SettingsChanged() const
/// @brief Worker Thread
///
class WorkerT
{
return (this->MocSettingsChanged || this->UicSettingsChanged);
}
CM_DISABLE_COPY(WorkerT)
public:
WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop);
~WorkerT();
// -- Central processing
bool Process(cmMakefile* makefile) override;
// -- Const accessors
cmQtAutoGeneratorMocUic& Gen() const { return *Gen_; }
Logger& Log() const { return Gen_->Log(); }
FileSystem& FileSys() const { return Gen_->FileSys(); }
const BaseSettingsT& Base() const { return Gen_->Base(); }
const MocSettingsT& Moc() const { return Gen_->Moc(); }
const UicSettingsT& Uic() const { return Gen_->Uic(); }
// -- Source parsing
bool ParseSourceFile(std::string const& absFilename, const SourceJob& job);
bool ParseHeaderFile(std::string const& absFilename, const SourceJob& job);
bool ParsePostprocess();
// -- Log info
void LogInfo(GeneratorT genType, std::string const& message) const;
// -- Log warning
void LogWarning(GeneratorT genType, std::string const& message) const;
void LogFileWarning(GeneratorT genType, std::string const& filename,
std::string const& message) const;
// -- Log error
void LogError(GeneratorT genType, std::string const& message) const;
void LogFileError(GeneratorT genType, std::string const& filename,
std::string const& message) const;
void LogCommandError(GeneratorT genType, std::string const& message,
std::vector<std::string> const& command,
std::string const& output) const;
// -- Moc
bool MocEnabled() const { return !this->MocExecutable.empty(); }
bool MocSkip(std::string const& absFilename) const;
bool MocRequired(std::string const& contentText,
std::string* macroName = nullptr);
// Moc strings
std::string MocStringMacros() const;
std::string MocStringHeaders(std::string const& fileBase) const;
std::string MocFindIncludedHeader(std::string const& sourcePath,
std::string const& includeBase) const;
bool MocFindIncludedFile(std::string& absFile, std::string const& sourceFile,
std::string const& includeString) const;
// Moc depends
bool MocDependFilterPush(std::string const& key, std::string const& regExp);
void MocFindDepends(std::string const& absFilename,
std::string const& contentText,
std::set<std::string>& depends);
// Moc
bool MocParseSourceContent(std::string const& absFilename,
std::string const& contentText);
void MocParseHeaderContent(std::string const& absFilename,
std::string const& contentText);
// -- External processes
/// @brief Verbose logging version
bool RunProcess(GeneratorT genType, ProcessResultT& result,
std::vector<std::string> const& command);
bool MocGenerateAll();
bool MocGenerateFile(const MocJobAuto& mocJob, bool* generated = nullptr);
private:
/// @brief Thread main loop
void Loop();
// -- Uic
bool UicEnabled() const { return !this->UicExecutable.empty(); }
bool UicSkip(std::string const& absFilename) const;
bool UicParseContent(std::string const& fileName,
std::string const& contentText);
bool UicFindIncludedFile(std::string& absFile, std::string const& sourceFile,
std::string const& includeString);
bool UicGenerateAll();
bool UicGenerateFile(const UicJob& uicJob);
// -- Libuv callbacks
static void UVProcessStart(uv_async_t* handle);
void UVProcessFinished();
// -- Utility
bool FindHeader(std::string& header, std::string const& testBasePath) const;
private:
// -- Generator
cmQtAutoGeneratorMocUic* Gen_;
// -- Job handle
JobHandleT JobHandle_;
// -- Process management
std::mutex ProcessMutex_;
cm::uv_async_ptr ProcessRequest_;
std::condition_variable ProcessCondition_;
std::unique_ptr<ReadOnlyProcessT> Process_;
// -- System thread
std::thread Thread_;
};
// -- Meta
std::string ConfigSuffix;
cmQtAutoGen::MultiConfig MultiConfig;
/// @brief Processing stage
enum class StageT
{
SETTINGS_READ,
CREATE_DIRECTORIES,
PARSE_SOURCES,
PARSE_HEADERS,
MOC_PREDEFS,
MOC_PROCESS,
MOCS_COMPILATION,
UIC_PROCESS,
SETTINGS_WRITE,
FINISH,
END
};
// -- Const settings interface
const BaseSettingsT& Base() const { return this->Base_; }
const MocSettingsT& Moc() const { return this->Moc_; }
const UicSettingsT& Uic() const { return this->Uic_; }
// -- Worker thread interface
void WorkerSwapJob(JobHandleT& jobHandle);
// -- Parallel job processing interface
void ParallelRegisterJobError();
bool ParallelJobPushMoc(JobHandleT& jobHandle);
bool ParallelJobPushUic(JobHandleT& jobHandle);
bool ParallelMocIncluded(std::string const& sourceFile);
void ParallelMocAutoRegister(std::string const& mocFile);
void ParallelMocAutoUpdated();
private:
// -- Abstract processing interface
bool Init(cmMakefile* makefile) override;
bool Process() override;
// -- Process stage
static void UVPollStage(uv_async_t* handle);
void PollStage();
void SetStage(StageT stage);
// -- Settings file
void SettingsFileRead();
void SettingsFileWrite();
// -- Thread processing
bool ThreadsStartJobs(JobQueueT& queue);
bool ThreadsJobsDone();
void ThreadsStop();
void RegisterJobError();
// -- Generation
void CreateDirectories();
void MocGenerateCompilation();
private:
// -- Settings
bool IncludeProjectDirsBefore;
std::string SettingsFile;
std::string SettingsStringMoc;
std::string SettingsStringUic;
// -- Directories
std::string ProjectSourceDir;
std::string ProjectBinaryDir;
std::string CurrentSourceDir;
std::string CurrentBinaryDir;
std::string AutogenBuildDir;
std::string AutogenIncludeDir;
// -- Qt environment
unsigned long QtVersionMajor;
std::string MocExecutable;
std::string UicExecutable;
// -- File lists
std::map<std::string, SourceJob> HeaderJobs;
std::map<std::string, SourceJob> SourceJobs;
std::vector<std::string> HeaderExtensions;
cmFilePathChecksum FilePathChecksum;
// -- Moc
bool MocSettingsChanged;
bool MocPredefsChanged;
bool MocRelaxedMode;
std::string MocCompFileRel;
std::string MocCompFileAbs;
std::string MocPredefsFileRel;
std::string MocPredefsFileAbs;
std::vector<std::string> MocSkipList;
std::vector<std::string> MocIncludePaths;
std::vector<std::string> MocIncludes;
std::vector<std::string> MocDefinitions;
std::vector<std::string> MocOptions;
std::vector<std::string> MocAllOptions;
std::vector<std::string> MocPredefsCmd;
std::vector<KeyRegExp> MocDependFilters;
std::vector<KeyRegExp> MocMacroFilters;
cmsys::RegularExpression MocRegExpInclude;
std::vector<std::unique_ptr<MocJobIncluded>> MocJobsIncluded;
std::vector<std::unique_ptr<MocJobAuto>> MocJobsAuto;
// -- Uic
bool UicSettingsChanged;
std::vector<std::string> UicSkipList;
std::vector<std::string> UicTargetOptions;
std::map<std::string, std::vector<std::string>> UicOptions;
std::vector<std::string> UicSearchPaths;
cmsys::RegularExpression UicRegExpInclude;
std::vector<std::unique_ptr<UicJob>> UicJobs;
BaseSettingsT Base_;
MocSettingsT Moc_;
UicSettingsT Uic_;
// -- Progress
StageT Stage_;
// -- Job queues
std::mutex JobsMutex_;
struct
{
JobQueueT Sources;
JobQueueT Headers;
JobQueueT MocPredefs;
JobQueueT Moc;
JobQueueT Uic;
} JobQueues_;
JobQueueT JobQueue_;
std::size_t volatile JobsRemain_;
bool volatile JobError_;
bool volatile JobThreadsAbort_;
std::condition_variable JobsConditionRead_;
// -- Moc meta
std::set<std::string> MocIncludedStrings_;
std::set<std::string> MocIncludedFiles_;
std::set<std::string> MocAutoFiles_;
bool volatile MocAutoFileUpdated_;
// -- Settings file
std::string SettingsFile_;
std::string SettingsStringMoc_;
std::string SettingsStringUic_;
// -- Threads and loops
std::vector<std::unique_ptr<WorkerT>> Workers_;
};
#endif
+459 -268
View File
@@ -6,22 +6,30 @@
#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmMakefile.h"
#include "cmOutputConverter.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
// -- Static variables
static const char* SettingsKeyRcc = "ARCC_SETTINGS_HASH";
#include <functional>
// -- Class methods
cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
: MultiConfig(cmQtAutoGen::WRAP)
, SettingsChanged(false)
: 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::InfoFileRead(cmMakefile* makefile)
bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile)
{
// Utility lambdas
auto InfoGet = [makefile](const char* key) {
@@ -37,7 +45,7 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile)
{
std::string keyConf = key;
keyConf += '_';
keyConf += this->GetInfoConfig();
keyConf += InfoConfig();
valueConf = makefile->GetDefinition(keyConf);
}
if (valueConf == nullptr) {
@@ -53,79 +61,180 @@ bool cmQtAutoGeneratorRcc::InfoFileRead(cmMakefile* makefile)
};
// -- Read info file
if (!makefile->ReadListFile(this->GetInfoFile().c_str())) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"File processing failed");
if (!makefile->ReadListFile(InfoFile().c_str())) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed");
return false;
}
// -- Meta
this->MultiConfig =
cmQtAutoGen::MultiConfigType(InfoGet("ARCC_MULTI_CONFIG"));
this->ConfigSuffix = InfoGetConfig("ARCC_CONFIG_SUFFIX");
if (this->ConfigSuffix.empty()) {
this->ConfigSuffix = "_";
this->ConfigSuffix += this->GetInfoConfig();
MultiConfig_ = MultiConfigType(InfoGet("ARCC_MULTI_CONFIG"));
ConfigSuffix_ = InfoGetConfig("ARCC_CONFIG_SUFFIX");
if (ConfigSuffix_.empty()) {
ConfigSuffix_ = "_";
ConfigSuffix_ += InfoConfig();
}
this->SettingsFile = InfoGetConfig("ARCC_SETTINGS_FILE");
SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
// - Files and directories
this->ProjectSourceDir = InfoGet("ARCC_CMAKE_SOURCE_DIR");
this->ProjectBinaryDir = InfoGet("ARCC_CMAKE_BINARY_DIR");
this->CurrentSourceDir = InfoGet("ARCC_CMAKE_CURRENT_SOURCE_DIR");
this->CurrentBinaryDir = InfoGet("ARCC_CMAKE_CURRENT_BINARY_DIR");
this->AutogenBuildDir = InfoGet("ARCC_BUILD_DIR");
AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
// - Qt environment
this->RccExecutable = InfoGet("ARCC_RCC_EXECUTABLE");
this->RccListOptions = InfoGetList("ARCC_RCC_LIST_OPTIONS");
RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
// - Job
this->QrcFile = InfoGet("ARCC_SOURCE");
this->RccFile = InfoGet("ARCC_OUTPUT");
this->Options = InfoGetConfigList("ARCC_OPTIONS");
this->Inputs = InfoGetList("ARCC_INPUTS");
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 (this->SettingsFile.empty()) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"Settings file name missing");
if (SettingsFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing");
return false;
}
if (this->AutogenBuildDir.empty()) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"Autogen build directory missing");
if (AutogenBuildDir_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(),
"Autogen build directory missing");
return false;
}
if (this->RccExecutable.empty()) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"rcc executable missing");
if (RccExecutable_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing");
return false;
}
if (this->QrcFile.empty()) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"rcc input file missing");
if (QrcFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing");
return false;
}
if (this->RccFile.empty()) {
this->LogFileError(cmQtAutoGen::RCC, this->GetInfoFile(),
"rcc output file missing");
if (RccFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing");
return false;
}
// Init derived information
// ------------------------
// Init file path checksum generator
this->FilePathChecksum.setupParentDirs(
this->CurrentSourceDir, this->CurrentBinaryDir, this->ProjectSourceDir,
this->ProjectBinaryDir);
// 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;
}
void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile)
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
{
@@ -133,293 +242,375 @@ void cmQtAutoGeneratorRcc::SettingsFileRead(cmMakefile* makefile)
std::string const sep(" ~~~ ");
{
std::string str;
str += this->RccExecutable;
str += RccExecutable_;
str += sep;
str += cmJoin(this->RccListOptions, ";");
str += cmJoin(RccListOptions_, ";");
str += sep;
str += this->QrcFile;
str += QrcFile_;
str += sep;
str += this->RccFile;
str += RccFile_;
str += sep;
str += cmJoin(this->Options, ";");
str += cmJoin(Options_, ";");
str += sep;
str += cmJoin(this->Inputs, ";");
str += cmJoin(Inputs_, ";");
str += sep;
this->SettingsString = crypt.HashString(str);
SettingsString_ = crypt.HashString(str);
}
}
// Read old settings
if (makefile->ReadListFile(this->SettingsFile.c_str())) {
{
auto SMatch = [makefile](const char* key, std::string const& value) {
return (value == makefile->GetSafeDefinition(key));
};
if (!SMatch(SettingsKeyRcc, this->SettingsString)) {
this->SettingsChanged = true;
{
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;
}
// 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 (this->SettingsChanged) {
cmSystemTools::RemoveFile(this->SettingsFile);
}
} else {
// If the file could not be read re-generate everythiung.
this->SettingsChanged = true;
}
}
bool cmQtAutoGeneratorRcc::SettingsFileWrite()
void cmQtAutoGeneratorRcc::SettingsFileWrite()
{
bool success = true;
// Only write if any setting changed
if (this->SettingsChanged) {
if (this->GetVerbose()) {
this->LogInfo(cmQtAutoGen::RCC, "Writing settings file " +
cmQtAutoGen::Quoted(this->SettingsFile));
}
// Compose settings file content
std::string settings;
{
auto SettingAppend = [&settings](const char* key,
std::string const& value) {
settings += "set(";
settings += key;
settings += " ";
settings += cmOutputConverter::EscapeForCMake(value);
settings += ")\n";
};
SettingAppend(SettingsKeyRcc, this->SettingsString);
if (SettingsChanged_) {
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC,
"Writing settings file " + Quoted(SettingsFile_));
}
// Write settings file
if (!this->FileWrite(cmQtAutoGen::RCC, this->SettingsFile, settings)) {
this->LogFileError(cmQtAutoGen::RCC, this->SettingsFile,
"Settings file writing failed");
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
cmSystemTools::RemoveFile(this->SettingsFile);
success = false;
FileSys().FileRemove(SettingsFile_);
Error_ = true;
}
}
return success;
}
bool cmQtAutoGeneratorRcc::Process(cmMakefile* makefile)
bool cmQtAutoGeneratorRcc::TestQrcRccFiles()
{
// Read info file
if (!this->InfoFileRead(makefile)) {
return false;
// 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_;
}
// Read latest settings
this->SettingsFileRead(makefile);
// Generate rcc file
if (!this->RccGenerate()) {
return false;
// 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_;
}
// Write latest settings
if (!this->SettingsFileWrite()) {
return false;
// 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;
}
/**
* @return True on success
*/
bool cmQtAutoGeneratorRcc::RccGenerate()
bool cmQtAutoGeneratorRcc::TestResources()
{
bool success = true;
bool rccGenerated = false;
std::string rccFileAbs;
if (Inputs_.empty()) {
return true;
}
{
std::string suffix;
switch (this->MultiConfig) {
case cmQtAutoGen::SINGLE:
break;
case cmQtAutoGen::WRAP:
suffix = "_CMAKE";
suffix += this->ConfigSuffix;
suffix += "_";
break;
case cmQtAutoGen::FULL:
suffix = this->ConfigSuffix;
break;
}
rccFileAbs = cmQtAutoGen::AppendFilenameSuffix(this->RccFile, suffix);
}
std::string const rccFileRel = cmSystemTools::RelativePath(
this->AutogenBuildDir.c_str(), rccFileAbs.c_str());
// Check if regeneration is required
bool generate = false;
std::string generateReason;
if (!cmSystemTools::FileExists(this->QrcFile)) {
{
std::string error = "Could not find the file\n ";
error += cmQtAutoGen::Quoted(this->QrcFile);
this->LogError(cmQtAutoGen::RCC, error);
}
success = false;
}
if (success && !generate && !cmSystemTools::FileExists(rccFileAbs.c_str())) {
if (this->GetVerbose()) {
generateReason = "Generating ";
generateReason += cmQtAutoGen::Quoted(rccFileAbs);
generateReason += " from its source file ";
generateReason += cmQtAutoGen::Quoted(this->QrcFile);
generateReason += " because it doesn't exist";
}
generate = true;
}
if (success && !generate && this->SettingsChanged) {
if (this->GetVerbose()) {
generateReason = "Generating ";
generateReason += cmQtAutoGen::Quoted(rccFileAbs);
generateReason += " from ";
generateReason += cmQtAutoGen::Quoted(this->QrcFile);
generateReason += " because the RCC settings changed";
}
generate = true;
}
if (success && !generate) {
std::string error;
if (FileIsOlderThan(rccFileAbs, this->QrcFile, &error)) {
if (this->GetVerbose()) {
generateReason = "Generating ";
generateReason += cmQtAutoGen::Quoted(rccFileAbs);
generateReason += " because it is older than ";
generateReason += cmQtAutoGen::Quoted(this->QrcFile);
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;
}
generate = true;
} else {
// 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()) {
this->LogError(cmQtAutoGen::RCC, error);
success = false;
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
break;
}
}
}
if (success && !generate) {
// Acquire input file list
std::vector<std::string> readFiles;
std::vector<std::string> const* files = nullptr;
if (!this->Inputs.empty()) {
files = &this->Inputs;
} else {
// Read input file list from qrc file
return Generate_;
}
void cmQtAutoGeneratorRcc::TestInfoFile()
{
// Test if the rcc output file is older than the info file
{
bool isOlder = false;
{
std::string error;
if (cmQtAutoGen::RccListInputs(this->RccExecutable, this->RccListOptions,
this->QrcFile, readFiles, &error)) {
files = &readFiles;
} else {
this->LogFileError(cmQtAutoGen::RCC, this->QrcFile, error);
success = false;
isOlder = FileSys().FileIsOlderThan(RccFileBuild_, InfoFile(), &error);
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
}
}
// Test if any input file is newer than the build file
if (files != nullptr) {
std::string error;
for (std::string const& resFile : *files) {
if (!cmSystemTools::FileExists(resFile.c_str())) {
error = "Could not find the file\n ";
error += cmQtAutoGen::Quoted(resFile);
error += "\nwhich is listed in\n ";
error += cmQtAutoGen::Quoted(this->QrcFile);
break;
}
if (FileIsOlderThan(rccFileAbs, resFile, &error)) {
if (this->GetVerbose()) {
generateReason = "Generating ";
generateReason += cmQtAutoGen::Quoted(rccFileAbs);
generateReason += " from ";
generateReason += cmQtAutoGen::Quoted(this->QrcFile);
generateReason += " because it is older than ";
generateReason += cmQtAutoGen::Quoted(resFile);
}
generate = true;
break;
}
if (!error.empty()) {
break;
}
}
// Print error
if (!error.empty()) {
this->LogError(cmQtAutoGen::RCC, error);
success = false;
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;
}
}
// Regenerate on demand
if (generate) {
// Log
if (this->GetVerbose()) {
this->LogBold("Generating RCC source " + rccFileRel);
this->LogInfo(cmQtAutoGen::RCC, generateReason);
}
}
// Make sure the parent directory exists
if (this->MakeParentDirectory(cmQtAutoGen::RCC, rccFileAbs)) {
// Compose rcc command
std::vector<std::string> cmd;
cmd.push_back(this->RccExecutable);
cmd.insert(cmd.end(), this->Options.begin(), this->Options.end());
cmd.push_back("-o");
cmd.push_back(rccFileAbs);
cmd.push_back(this->QrcFile);
void cmQtAutoGeneratorRcc::GenerateParentDir()
{
// Make sure the parent directory exists
if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileBuild_)) {
Error_ = true;
}
}
std::string output;
if (this->RunCommand(cmd, output)) {
// Success
rccGenerated = 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 = "rcc failed for\n ";
emsg += cmQtAutoGen::Quoted(this->QrcFile);
this->LogCommandError(cmQtAutoGen::RCC, emsg, cmd, output);
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);
}
cmSystemTools::RemoveFile(rccFileAbs);
success = false;
FileSys().FileRemove(RccFileBuild_);
Error_ = true;
}
// Clean up
Process_.reset();
ProcessResult_.reset();
} else {
// Parent directory creation failed
success = false;
// 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 (success && (this->MultiConfig == cmQtAutoGen::WRAP)) {
if (MultiConfig_ == MultiConfigT::WRAPPER) {
// Wrapper file name
std::string const& wrapperFileAbs = this->RccFile;
std::string const wrapperFileRel = cmSystemTools::RelativePath(
this->AutogenBuildDir.c_str(), wrapperFileAbs.c_str());
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(rccFileRel);
content += cmSystemTools::GetFilenameName(RccFileBuild_);
content += "\"\n";
// Write content to file
if (this->FileDiffers(wrapperFileAbs, content)) {
if (FileSys().FileDiffers(wrapperAbs, content)) {
// Write new wrapper file
if (this->GetVerbose()) {
this->LogBold("Generating RCC wrapper " + wrapperFileRel);
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC, "Generating RCC wrapper " + wrapperAbs);
}
if (!this->FileWrite(cmQtAutoGen::RCC, wrapperFileAbs, content)) {
this->LogFileError(cmQtAutoGen::RCC, wrapperFileAbs,
"rcc wrapper file writing failed");
success = false;
if (!FileSys().FileWrite(GeneratorT::RCC, wrapperAbs, content)) {
Log().ErrorFile(GeneratorT::RCC, wrapperAbs,
"RCC wrapper file writing failed");
Error_ = true;
}
} else if (rccGenerated) {
} else if (BuildFileChanged_) {
// Just touch the wrapper file
if (this->GetVerbose()) {
this->LogInfo(cmQtAutoGen::RCC,
"Touching RCC wrapper " + wrapperFileRel);
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC, "Touching RCC wrapper " + wrapperAbs);
}
cmSystemTools::Touch(wrapperFileAbs, false);
FileSys().Touch(wrapperAbs);
}
}
return success;
}
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;
}
+71 -26
View File
@@ -5,52 +5,97 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmFilePathChecksum.h"
#include "cmQtAutoGen.h"
#include "cmQtAutoGenerator.h"
#include "cm_uv.h"
#include <string>
#include <vector>
class cmMakefile;
// @brief AUTORCC generator
class cmQtAutoGeneratorRcc : public cmQtAutoGenerator
{
CM_DISABLE_COPY(cmQtAutoGeneratorRcc)
public:
cmQtAutoGeneratorRcc();
~cmQtAutoGeneratorRcc() override;
private:
// -- Initialization & settings
bool InfoFileRead(cmMakefile* makefile);
void SettingsFileRead(cmMakefile* makefile);
bool SettingsFileWrite();
// -- Central processing
bool Process(cmMakefile* makefile) override;
bool RccGenerate();
// -- Types
/// @brief Processing stage
enum class StageT
{
SETTINGS_READ,
TEST_QRC_RCC_FILES,
TEST_RESOURCES_READ,
TEST_RESOURCES,
TEST_INFO_FILE,
GENERATE,
GENERATE_RCC,
GENERATE_WRAPPER,
SETTINGS_WRITE,
FINISH,
END
};
// -- Abstract processing interface
bool Init(cmMakefile* makefile) override;
bool Process() override;
// -- Process stage
static void UVPollStage(uv_async_t* handle);
void PollStage();
void SetStage(StageT stage);
// -- Settings file
void SettingsFileRead();
void SettingsFileWrite();
// -- Tests
bool TestQrcRccFiles();
bool TestResourcesRead();
bool TestResources();
void TestInfoFile();
// -- Generation
void GenerateParentDir();
bool GenerateRcc();
void GenerateWrapper();
// -- Utility
bool StartProcess(std::string const& workingDirectory,
std::vector<std::string> const& command,
bool mergedOutput);
private:
// -- Config settings
std::string ConfigSuffix;
cmQtAutoGen::MultiConfig MultiConfig;
// -- Settings
bool SettingsChanged;
std::string SettingsFile;
std::string SettingsString;
bool SettingsChanged_;
std::string ConfigSuffix_;
MultiConfigT MultiConfig_;
// -- Directories
std::string ProjectSourceDir;
std::string ProjectBinaryDir;
std::string CurrentSourceDir;
std::string CurrentBinaryDir;
std::string AutogenBuildDir;
cmFilePathChecksum FilePathChecksum;
std::string AutogenBuildDir_;
// -- Qt environment
std::string RccExecutable;
std::vector<std::string> RccListOptions;
std::string RccExecutable_;
std::vector<std::string> RccListOptions_;
// -- Job
std::string QrcFile;
std::string RccFile;
std::vector<std::string> Options;
std::vector<std::string> Inputs;
std::string QrcFile_;
std::string QrcFileName_;
std::string QrcFileDir_;
std::string RccFile_;
std::string RccFileWrapper_;
std::string RccFileBuild_;
std::vector<std::string> Options_;
std::vector<std::string> Inputs_;
// -- Subprocess
ProcessResultT ProcessResult_;
std::unique_ptr<ReadOnlyProcessT> Process_;
// -- Settings file
std::string SettingsFile_;
std::string SettingsString_;
// -- libuv loop
StageT Stage_;
bool Error_;
bool Generate_;
bool BuildFileChanged_;
};
#endif
+1
View File
@@ -247,6 +247,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
this->SetPropertyDefault("AUTOMOC", nullptr);
this->SetPropertyDefault("AUTOUIC", nullptr);
this->SetPropertyDefault("AUTORCC", nullptr);
this->SetPropertyDefault("AUTOGEN_PARALLEL", nullptr);
this->SetPropertyDefault("AUTOMOC_COMPILER_PREDEFINES", nullptr);
this->SetPropertyDefault("AUTOMOC_DEPEND_FILTERS", nullptr);
this->SetPropertyDefault("AUTOMOC_MACRO_NAMES", nullptr);