| /* 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; |
| } |