blob: 65c674110b3dc4c216910b4796e20820a064d970 [file] [log] [blame]
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoGeneratorRcc.h"
#include "cmQtAutoGen.h"
#include "cmAlgorithms.h"
#include "cmCryptoHash.h"
#include "cmFileLockResult.h"
#include "cmMakefile.h"
#include "cmSystemTools.h"
#include "cmUVHandlePtr.h"
#include <functional>
// -- Class methods
cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
: MultiConfig_(false)
, SettingsChanged_(false)
, Stage_(StageT::SETTINGS_READ)
, Error_(false)
, Generate_(false)
, BuildFileChanged_(false)
{
// Initialize libuv asynchronous iteration request
UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this);
}
cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc()
{
}
bool cmQtAutoGeneratorRcc::Init(cmMakefile* makefile)
{
// -- Utility lambdas
auto InfoGet = [makefile](std::string const& key) {
return makefile->GetSafeDefinition(key);
};
auto InfoGetList =
[makefile](std::string const& key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
return list;
};
auto InfoGetConfig = [makefile,
this](std::string const& key) -> std::string {
const char* valueConf = nullptr;
{
std::string keyConf = key;
keyConf += '_';
keyConf += InfoConfig();
valueConf = makefile->GetDefinition(keyConf);
}
if (valueConf == nullptr) {
return makefile->GetSafeDefinition(key);
}
return std::string(valueConf);
};
auto InfoGetConfigList =
[&InfoGetConfig](std::string const& key) -> std::vector<std::string> {
std::vector<std::string> list;
cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
return list;
};
// -- Read info file
if (!makefile->ReadListFile(InfoFile().c_str())) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "File processing failed");
return false;
}
// - Configurations
Log().RaiseVerbosity(InfoGet("ARCC_VERBOSITY"));
MultiConfig_ = makefile->IsOn("ARCC_MULTI_CONFIG");
// - Directories
AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
if (AutogenBuildDir_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Build directory empty");
return false;
}
IncludeDir_ = InfoGetConfig("ARCC_INCLUDE_DIR");
if (IncludeDir_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Include directory empty");
return false;
}
// - Rcc executable
RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
// - Job
LockFile_ = InfoGet("ARCC_LOCK_FILE");
QrcFile_ = InfoGet("ARCC_SOURCE");
QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
RccPathChecksum_ = InfoGet("ARCC_OUTPUT_CHECKSUM");
RccFileName_ = InfoGet("ARCC_OUTPUT_NAME");
Options_ = InfoGetConfigList("ARCC_OPTIONS");
Inputs_ = InfoGetList("ARCC_INPUTS");
// - Settings file
SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
// - Validity checks
if (LockFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Lock file name missing");
return false;
}
if (SettingsFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "Settings file name missing");
return false;
}
if (AutogenBuildDir_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(),
"Autogen build directory missing");
return false;
}
if (RccExecutable_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc executable missing");
return false;
}
if (QrcFile_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc input file missing");
return false;
}
if (RccFileName_.empty()) {
Log().ErrorFile(GeneratorT::RCC, InfoFile(), "rcc output file missing");
return false;
}
// Init derived information
// ------------------------
RccFilePublic_ = AutogenBuildDir_;
RccFilePublic_ += '/';
RccFilePublic_ += RccPathChecksum_;
RccFilePublic_ += '/';
RccFilePublic_ += RccFileName_;
// Compute rcc output file name
if (IsMultiConfig()) {
RccFileOutput_ = IncludeDir_;
RccFileOutput_ += '/';
RccFileOutput_ += MultiConfigOutput();
} else {
RccFileOutput_ = RccFilePublic_;
}
return true;
}
bool cmQtAutoGeneratorRcc::Process()
{
// Run libuv event loop
UVRequest().send();
if (uv_run(UVLoop(), UV_RUN_DEFAULT) == 0) {
if (Error_) {
return false;
}
} else {
return false;
}
return true;
}
void cmQtAutoGeneratorRcc::UVPollStage(uv_async_t* handle)
{
reinterpret_cast<cmQtAutoGeneratorRcc*>(handle->data)->PollStage();
}
void cmQtAutoGeneratorRcc::PollStage()
{
switch (Stage_) {
// -- Initialize
case StageT::SETTINGS_READ:
if (SettingsFileRead()) {
SetStage(StageT::TEST_QRC_RCC_FILES);
} else {
SetStage(StageT::FINISH);
}
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();
}
}
std::string cmQtAutoGeneratorRcc::MultiConfigOutput() const
{
static std::string const suffix = "_CMAKE_";
std::string res;
res += RccPathChecksum_;
res += '/';
res += AppendFilenameSuffix(RccFileName_, suffix);
return res;
}
bool cmQtAutoGeneratorRcc::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
std::string const sep(" ~~~ ");
{
std::string str;
str += RccExecutable_;
str += sep;
str += cmJoin(RccListOptions_, ";");
str += sep;
str += QrcFile_;
str += sep;
str += RccPathChecksum_;
str += sep;
str += RccFileName_;
str += sep;
str += cmJoin(Options_, ";");
str += sep;
str += cmJoin(Inputs_, ";");
str += sep;
SettingsString_ = crypt.HashString(str);
}
}
// Make sure the settings file exists
if (!FileSys().FileExists(SettingsFile_, true)) {
// Touch the settings file to make sure it exists
FileSys().Touch(SettingsFile_, true);
}
// Lock the lock file
{
// Make sure the lock file exists
if (!FileSys().FileExists(LockFile_, true)) {
if (!FileSys().Touch(LockFile_, true)) {
Log().ErrorFile(GeneratorT::RCC, LockFile_,
"Lock file creation failed");
Error_ = true;
return false;
}
}
// Lock the lock file
cmFileLockResult lockResult =
LockFileLock_.Lock(LockFile_, static_cast<unsigned long>(-1));
if (!lockResult.IsOk()) {
Log().ErrorFile(GeneratorT::RCC, LockFile_,
"File lock failed: " + lockResult.GetOutputMessage());
Error_ = true;
return false;
}
}
// Read old settings
{
std::string content;
if (FileSys().FileRead(content, SettingsFile_)) {
SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
// In case any setting changed clear 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().FileWrite(GeneratorT::RCC, SettingsFile_, "");
}
} else {
SettingsChanged_ = true;
}
}
return true;
}
void cmQtAutoGeneratorRcc::SettingsFileWrite()
{
// Only write if any setting changed
if (SettingsChanged_) {
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC,
"Writing settings file " + Quoted(SettingsFile_));
}
// Write settings file
std::string content = "rcc:";
content += SettingsString_;
content += '\n';
if (!FileSys().FileWrite(GeneratorT::RCC, SettingsFile_, content)) {
Log().ErrorFile(GeneratorT::RCC, SettingsFile_,
"Settings file writing failed");
// Remove old settings file to trigger a full rebuild on the next run
FileSys().FileRemove(SettingsFile_);
Error_ = true;
}
}
// Unlock the lock file
LockFileLock_.Release();
}
bool cmQtAutoGeneratorRcc::TestQrcRccFiles()
{
// Do basic checks if rcc generation is required
// Test if the rcc output file exists
if (!FileSys().FileExists(RccFileOutput_)) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileOutput_);
reason += " from its source file ";
reason += Quoted(QrcFile_);
reason += " because it doesn't exist";
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
return Generate_;
}
// Test if the settings changed
if (SettingsChanged_) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileOutput_);
reason += " from ";
reason += Quoted(QrcFile_);
reason += " because the RCC settings changed";
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
return Generate_;
}
// Test if the rcc output file is older than the .qrc file
{
bool isOlder = false;
{
std::string error;
isOlder = FileSys().FileIsOlderThan(RccFileOutput_, QrcFile_, &error);
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
}
}
if (isOlder) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileOutput_);
reason += " because it is older than ";
reason += Quoted(QrcFile_);
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
}
}
return Generate_;
}
bool cmQtAutoGeneratorRcc::TestResourcesRead()
{
if (!Inputs_.empty()) {
// Inputs are known already
return true;
}
if (!RccListOptions_.empty()) {
// Start a rcc list process and parse the output
if (Process_) {
// Process is running already
if (Process_->IsFinished()) {
// Process is finished
if (!ProcessResult_.error()) {
// Process success
std::string parseError;
if (!RccListParseOutput(ProcessResult_.StdOut, ProcessResult_.StdErr,
Inputs_, parseError)) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, parseError);
Error_ = true;
}
} else {
Log().ErrorFile(GeneratorT::RCC, QrcFile_,
ProcessResult_.ErrorMessage);
Error_ = true;
}
// Clean up
Process_.reset();
ProcessResult_.reset();
} else {
// Process is not finished, yet.
return false;
}
} else {
// Start a new process
// rcc prints relative entry paths when started in the directory of the
// qrc file with a pathless qrc file name argument.
// This is important because on Windows absolute paths returned by rcc
// might contain bad multibyte characters when the qrc file path
// contains non-ASCII pcharacters.
std::vector<std::string> cmd;
cmd.push_back(RccExecutable_);
cmd.insert(cmd.end(), RccListOptions_.begin(), RccListOptions_.end());
cmd.push_back(QrcFileName_);
// We're done here if the process fails to start
return !StartProcess(QrcFileDir_, cmd, false);
}
} else {
// rcc does not support the --list command.
// Read the qrc file content and parse it.
std::string qrcContent;
if (FileSys().FileRead(GeneratorT::RCC, qrcContent, QrcFile_)) {
RccListParseContent(qrcContent, Inputs_);
}
}
if (!Inputs_.empty()) {
// Convert relative paths to absolute paths
RccListConvertFullPath(QrcFileDir_, Inputs_);
}
return true;
}
bool cmQtAutoGeneratorRcc::TestResources()
{
if (Inputs_.empty()) {
return true;
}
{
std::string error;
for (std::string const& resFile : Inputs_) {
// Check if the resource file exists
if (!FileSys().FileExists(resFile)) {
error = "Could not find the resource file\n ";
error += Quoted(resFile);
error += '\n';
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
break;
}
// Check if the resource file is newer than the build file
if (FileSys().FileIsOlderThan(RccFileOutput_, resFile, &error)) {
if (Log().Verbose()) {
std::string reason = "Generating ";
reason += Quoted(RccFileOutput_);
reason += " from ";
reason += Quoted(QrcFile_);
reason += " because it is older than ";
reason += Quoted(resFile);
Log().Info(GeneratorT::RCC, reason);
}
Generate_ = true;
break;
}
// Print error and break on demand
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
break;
}
}
}
return Generate_;
}
void cmQtAutoGeneratorRcc::TestInfoFile()
{
// Test if the rcc output file is older than the info file
{
bool isOlder = false;
{
std::string error;
isOlder = FileSys().FileIsOlderThan(RccFileOutput_, InfoFile(), &error);
if (!error.empty()) {
Log().ErrorFile(GeneratorT::RCC, QrcFile_, error);
Error_ = true;
}
}
if (isOlder) {
if (Log().Verbose()) {
std::string reason = "Touching ";
reason += Quoted(RccFileOutput_);
reason += " because it is older than ";
reason += Quoted(InfoFile());
Log().Info(GeneratorT::RCC, reason);
}
// Touch build file
FileSys().Touch(RccFileOutput_);
BuildFileChanged_ = true;
}
}
}
void cmQtAutoGeneratorRcc::GenerateParentDir()
{
// Make sure the parent directory exists
if (!FileSys().MakeParentDirectory(GeneratorT::RCC, RccFileOutput_)) {
Error_ = true;
}
}
/**
* @return True when finished
*/
bool cmQtAutoGeneratorRcc::GenerateRcc()
{
if (!Generate_) {
// Nothing to do
return true;
}
if (Process_) {
// Process is running already
if (Process_->IsFinished()) {
// Process is finished
if (!ProcessResult_.error()) {
// Rcc process success
// Print rcc output
if (!ProcessResult_.StdOut.empty()) {
Log().Info(GeneratorT::RCC, ProcessResult_.StdOut);
}
BuildFileChanged_ = true;
} else {
// Rcc process failed
{
std::string emsg = "The rcc process failed to compile\n ";
emsg += Quoted(QrcFile_);
emsg += "\ninto\n ";
emsg += Quoted(RccFileOutput_);
if (ProcessResult_.error()) {
emsg += "\n";
emsg += ProcessResult_.ErrorMessage;
}
Log().ErrorCommand(GeneratorT::RCC, emsg, Process_->Setup().Command,
ProcessResult_.StdOut);
}
FileSys().FileRemove(RccFileOutput_);
Error_ = true;
}
// Clean up
Process_.reset();
ProcessResult_.reset();
} else {
// Process is not finished, yet.
return false;
}
} else {
// Start a rcc process
std::vector<std::string> cmd;
cmd.push_back(RccExecutable_);
cmd.insert(cmd.end(), Options_.begin(), Options_.end());
cmd.push_back("-o");
cmd.push_back(RccFileOutput_);
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 (IsMultiConfig()) {
// Wrapper file content
std::string content;
content += "// This is an autogenerated configuration wrapper file.\n";
content += "// Changes will be overwritten.\n";
content += "#include <";
content += MultiConfigOutput();
content += ">\n";
// Write content to file
if (FileSys().FileDiffers(RccFilePublic_, content)) {
// Write new wrapper file
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC,
"Generating RCC wrapper file " + RccFilePublic_);
}
if (!FileSys().FileWrite(GeneratorT::RCC, RccFilePublic_, content)) {
Log().ErrorFile(GeneratorT::RCC, RccFilePublic_,
"RCC wrapper file writing failed");
Error_ = true;
}
} else if (BuildFileChanged_) {
// Just touch the wrapper file
if (Log().Verbose()) {
Log().Info(GeneratorT::RCC,
"Touching RCC wrapper file " + RccFilePublic_);
}
FileSys().Touch(RccFilePublic_);
}
}
}
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;
}