/* 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 // -- Class methods cmQtAutoGeneratorRcc::cmQtAutoGeneratorRcc() { // 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::vector 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::vector 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(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(-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 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 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 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(); 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; }