summaryrefslogtreecommitdiffstats
path: root/Source/cmQtAutoGeneratorRcc.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmQtAutoGeneratorRcc.cxx')
-rw-r--r--Source/cmQtAutoGeneratorRcc.cxx672
1 files changed, 672 insertions, 0 deletions
diff --git a/Source/cmQtAutoGeneratorRcc.cxx b/Source/cmQtAutoGeneratorRcc.cxx
new file mode 100644
index 0000000..021a15f
--- /dev/null
+++ b/Source/cmQtAutoGeneratorRcc.cxx
@@ -0,0 +1,672 @@
+/* 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"
+
+// -- Class methods
+
+cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc()
+{
+ // Initialize libuv asynchronous iteration request
+ UVRequest().init(*UVLoop(), &cmQtAutoGeneratorRcc::UVPollStage, this);
+}
+
+cmQtAutoGeneratorRcc::~cmQtAutoGeneratorRcc() = default;
+
+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())) {
+ 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.emplace_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(), [this] { UVRequest().send(); })) {
+ Log().ErrorFile(GeneratorT::RCC, QrcFile_, ProcessResult_.ErrorMessage);
+ Error_ = true;
+ // Clean up
+ Process_.reset();
+ ProcessResult_.reset();
+ return false;
+ }
+ return true;
+}