123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmQtAutoGen.h"
- #include "cmQtAutoGeneratorRcc.h"
- #include "cmAlgorithms.h"
- #include "cmCryptoHash.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) {
- valueConf = 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
- 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
- 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 (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_ = AutogenBuildDir_;
- RccFileOutput_ += '/';
- 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:
- 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();
- }
- }
- std::string cmQtAutoGeneratorRcc::MultiConfigOutput() const
- {
- static std::string const suffix = "_CMAKE_";
- std::string res;
- res += RccPathChecksum_;
- res += '/';
- res += AppendFilenameSuffix(RccFileName_, suffix);
- return res;
- }
- void 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);
- }
- }
- // Read old settings
- {
- 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;
- }
- }
- }
- 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;
- }
- }
- }
- 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()) {
- // Process success
- BuildFileChanged_ = true;
- } else {
- // 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;
- }
|