| /* 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 "cmQtAutoGenerator.h" |
| |
| #include "cmsys/FStream.hxx" |
| |
| #include "cmAlgorithms.h" |
| #include "cmGlobalGenerator.h" |
| #include "cmMakefile.h" |
| #include "cmStateDirectory.h" |
| #include "cmStateSnapshot.h" |
| #include "cmSystemTools.h" |
| #include "cmake.h" |
| |
| #include <algorithm> |
| |
| // -- Class methods |
| |
| void cmQtAutoGenerator::Logger::SetVerbose(bool value) |
| { |
| Verbose_ = value; |
| } |
| |
| void cmQtAutoGenerator::Logger::SetColorOutput(bool value) |
| { |
| ColorOutput_ = value; |
| } |
| |
| std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title) |
| { |
| std::string head = title; |
| head += '\n'; |
| head.append(head.size() - 1, '-'); |
| head += '\n'; |
| return head; |
| } |
| |
| void cmQtAutoGenerator::Logger::Info(GeneratorT genType, |
| std::string const& message) |
| { |
| std::string msg = GeneratorName(genType); |
| msg += ": "; |
| msg += message; |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| cmSystemTools::Stdout(msg.c_str(), msg.size()); |
| } |
| } |
| |
| void cmQtAutoGenerator::Logger::Warning(GeneratorT genType, |
| std::string const& message) |
| { |
| std::string msg; |
| if (message.find('\n') == std::string::npos) { |
| // Single line message |
| msg += GeneratorName(genType); |
| msg += " warning: "; |
| } else { |
| // Multi line message |
| msg += HeadLine(GeneratorName(genType) + " warning"); |
| } |
| // Message |
| msg += message; |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| msg.push_back('\n'); |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| cmSystemTools::Stdout(msg.c_str(), msg.size()); |
| } |
| } |
| |
| void cmQtAutoGenerator::Logger::WarningFile(GeneratorT genType, |
| std::string const& filename, |
| std::string const& message) |
| { |
| std::string msg = " "; |
| msg += Quoted(filename); |
| msg.push_back('\n'); |
| // Message |
| msg += message; |
| Warning(genType, msg); |
| } |
| |
| void cmQtAutoGenerator::Logger::Error(GeneratorT genType, |
| std::string const& message) |
| { |
| std::string msg; |
| msg += HeadLine(GeneratorName(genType) + " error"); |
| // Message |
| msg += message; |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| msg.push_back('\n'); |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| cmSystemTools::Stderr(msg.c_str(), msg.size()); |
| } |
| } |
| |
| void cmQtAutoGenerator::Logger::ErrorFile(GeneratorT genType, |
| std::string const& filename, |
| std::string const& message) |
| { |
| std::string emsg = " "; |
| emsg += Quoted(filename); |
| emsg += '\n'; |
| // Message |
| emsg += message; |
| Error(genType, emsg); |
| } |
| |
| void cmQtAutoGenerator::Logger::ErrorCommand( |
| GeneratorT genType, std::string const& message, |
| std::vector<std::string> const& command, std::string const& output) |
| { |
| std::string msg; |
| msg.push_back('\n'); |
| msg += HeadLine(GeneratorName(genType) + " subprocess error"); |
| msg += message; |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| msg.push_back('\n'); |
| msg += HeadLine("Command"); |
| msg += QuotedCommand(command); |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| msg.push_back('\n'); |
| msg += HeadLine("Output"); |
| msg += output; |
| if (msg.back() != '\n') { |
| msg.push_back('\n'); |
| } |
| msg.push_back('\n'); |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| cmSystemTools::Stderr(msg.c_str(), msg.size()); |
| } |
| } |
| |
| std::string cmQtAutoGenerator::FileSystem::RealPath( |
| std::string const& filename) |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| return cmSystemTools::GetRealPath(filename); |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileExists(std::string const& filename) |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| return cmSystemTools::FileExists(filename); |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileIsOlderThan( |
| std::string const& buildFile, std::string const& sourceFile, |
| std::string* error) |
| { |
| bool res(false); |
| int result = 0; |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| res = cmSystemTools::FileTimeCompare(buildFile, sourceFile, &result); |
| } |
| if (res) { |
| res = (result < 0); |
| } else { |
| if (error != nullptr) { |
| error->append( |
| "File modification time comparison failed for the files\n "); |
| error->append(Quoted(buildFile)); |
| error->append("\nand\n "); |
| error->append(Quoted(sourceFile)); |
| } |
| } |
| return res; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileRead(std::string& content, |
| std::string const& filename, |
| std::string* error) |
| { |
| bool success = false; |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| if (cmSystemTools::FileExists(filename)) { |
| std::size_t const length = cmSystemTools::FileLength(filename); |
| cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary)); |
| if (ifs) { |
| content.resize(length); |
| ifs.read(&content.front(), content.size()); |
| if (ifs) { |
| success = true; |
| } else { |
| content.clear(); |
| if (error != nullptr) { |
| error->append("Reading from the file failed."); |
| } |
| } |
| } else if (error != nullptr) { |
| error->append("Opening the file for reading failed."); |
| } |
| } else if (error != nullptr) { |
| error->append("The file does not exist."); |
| } |
| } |
| return success; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileRead(GeneratorT genType, |
| std::string& content, |
| std::string const& filename) |
| { |
| std::string error; |
| if (!FileRead(content, filename, &error)) { |
| Log()->ErrorFile(genType, filename, error); |
| return false; |
| } |
| return true; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileWrite(std::string const& filename, |
| std::string const& content, |
| std::string* error) |
| { |
| bool success = false; |
| // Make sure the parent directory exists |
| if (MakeParentDirectory(filename)) { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| cmsys::ofstream outfile; |
| outfile.open(filename.c_str(), |
| (std::ios::out | std::ios::binary | std::ios::trunc)); |
| if (outfile) { |
| outfile << content; |
| // Check for write errors |
| if (outfile.good()) { |
| success = true; |
| } else { |
| if (error != nullptr) { |
| error->assign("File writing failed"); |
| } |
| } |
| } else { |
| if (error != nullptr) { |
| error->assign("Opening file for writing failed"); |
| } |
| } |
| } else { |
| if (error != nullptr) { |
| error->assign("Could not create parent directory"); |
| } |
| } |
| return success; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileWrite(GeneratorT genType, |
| std::string const& filename, |
| std::string const& content) |
| { |
| std::string error; |
| if (!FileWrite(filename, content, &error)) { |
| Log()->ErrorFile(genType, filename, error); |
| return false; |
| } |
| return true; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileDiffers(std::string const& filename, |
| std::string const& content) |
| { |
| bool differs = true; |
| { |
| std::string oldContents; |
| if (FileRead(oldContents, filename)) { |
| differs = (oldContents != content); |
| } |
| } |
| return differs; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::FileRemove(std::string const& filename) |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| return cmSystemTools::RemoveFile(filename); |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::Touch(std::string const& filename) |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| return cmSystemTools::Touch(filename, false); |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::MakeDirectory(std::string const& dirname) |
| { |
| std::lock_guard<std::mutex> lock(Mutex_); |
| return cmSystemTools::MakeDirectory(dirname); |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::MakeDirectory(GeneratorT genType, |
| std::string const& dirname) |
| { |
| if (!MakeDirectory(dirname)) { |
| Log()->ErrorFile(genType, dirname, "Could not create directory"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::MakeParentDirectory( |
| std::string const& filename) |
| { |
| bool success = true; |
| std::string const dirName = cmSystemTools::GetFilenamePath(filename); |
| if (!dirName.empty()) { |
| success = MakeDirectory(dirName); |
| } |
| return success; |
| } |
| |
| bool cmQtAutoGenerator::FileSystem::MakeParentDirectory( |
| GeneratorT genType, std::string const& filename) |
| { |
| if (!MakeParentDirectory(filename)) { |
| Log()->ErrorFile(genType, filename, "Could not create parent directory"); |
| return false; |
| } |
| return true; |
| } |
| |
| int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop, |
| ReadOnlyProcessT* process) |
| { |
| Process_ = process; |
| Target_ = nullptr; |
| return UVPipe_.init(*uv_loop, 0, this); |
| } |
| |
| int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target) |
| { |
| Target_ = target; |
| return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData); |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset() |
| { |
| Process_ = nullptr; |
| Target_ = nullptr; |
| UVPipe_.reset(); |
| Buffer_.clear(); |
| Buffer_.shrink_to_fit(); |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle, |
| size_t suggestedSize, |
| uv_buf_t* buf) |
| { |
| auto& pipe = *reinterpret_cast<PipeT*>(handle->data); |
| pipe.Buffer_.resize(suggestedSize); |
| buf->base = &pipe.Buffer_.front(); |
| buf->len = pipe.Buffer_.size(); |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream, |
| ssize_t nread, |
| const uv_buf_t* buf) |
| { |
| auto& pipe = *reinterpret_cast<PipeT*>(stream->data); |
| if (nread > 0) { |
| // Append data to merged output |
| if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) { |
| pipe.Target_->append(buf->base, nread); |
| } |
| } else if (nread < 0) { |
| // EOF or error |
| auto* proc = pipe.Process_; |
| // Check it this an unusual error |
| if (nread != UV_EOF) { |
| if (!proc->Result()->error()) { |
| proc->Result()->ErrorMessage = |
| "libuv reading from pipe failed with error code "; |
| proc->Result()->ErrorMessage += std::to_string(nread); |
| } |
| } |
| // Clear libuv pipe handle and try to finish |
| pipe.reset(); |
| proc->UVTryFinish(); |
| } |
| } |
| |
| void cmQtAutoGenerator::ProcessResultT::reset() |
| { |
| ExitStatus = 0; |
| TermSignal = 0; |
| if (!StdOut.empty()) { |
| StdOut.clear(); |
| StdOut.shrink_to_fit(); |
| } |
| if (!StdErr.empty()) { |
| StdErr.clear(); |
| StdErr.shrink_to_fit(); |
| } |
| if (!ErrorMessage.empty()) { |
| ErrorMessage.clear(); |
| ErrorMessage.shrink_to_fit(); |
| } |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::setup( |
| ProcessResultT* result, bool mergedOutput, |
| std::vector<std::string> const& command, std::string const& workingDirectory) |
| { |
| Setup_.WorkingDirectory = workingDirectory; |
| Setup_.Command = command; |
| Setup_.Result = result; |
| Setup_.MergedOutput = mergedOutput; |
| } |
| |
| bool cmQtAutoGenerator::ReadOnlyProcessT::start( |
| uv_loop_t* uv_loop, std::function<void()>&& finishedCallback) |
| { |
| if (IsStarted() || (Result() == nullptr)) { |
| return false; |
| } |
| |
| // Reset result before the start |
| Result()->reset(); |
| |
| // Fill command string pointers |
| if (!Setup().Command.empty()) { |
| CommandPtr_.reserve(Setup().Command.size() + 1); |
| for (std::string const& arg : Setup().Command) { |
| CommandPtr_.push_back(arg.c_str()); |
| } |
| CommandPtr_.push_back(nullptr); |
| } else { |
| Result()->ErrorMessage = "Empty command"; |
| } |
| |
| if (!Result()->error()) { |
| if (UVPipeOut_.init(uv_loop, this) != 0) { |
| Result()->ErrorMessage = "libuv stdout pipe initialization failed"; |
| } |
| } |
| if (!Result()->error()) { |
| if (UVPipeErr_.init(uv_loop, this) != 0) { |
| Result()->ErrorMessage = "libuv stderr pipe initialization failed"; |
| } |
| } |
| if (!Result()->error()) { |
| // -- Setup process stdio options |
| // stdin |
| UVOptionsStdIO_[0].flags = UV_IGNORE; |
| UVOptionsStdIO_[0].data.stream = nullptr; |
| // stdout |
| UVOptionsStdIO_[1].flags = |
| static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); |
| UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream(); |
| // stderr |
| UVOptionsStdIO_[2].flags = |
| static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); |
| UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream(); |
| |
| // -- Setup process options |
| std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0); |
| UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit; |
| UVOptions_.file = CommandPtr_[0]; |
| UVOptions_.args = const_cast<char**>(&CommandPtr_.front()); |
| UVOptions_.cwd = Setup_.WorkingDirectory.c_str(); |
| UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE; |
| UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size()); |
| UVOptions_.stdio = &UVOptionsStdIO_.front(); |
| |
| // -- Spawn process |
| if (UVProcess_.spawn(*uv_loop, UVOptions_, this) != 0) { |
| Result()->ErrorMessage = "libuv process spawn failed"; |
| } |
| } |
| // -- Start reading from stdio streams |
| if (!Result()->error()) { |
| if (UVPipeOut_.startRead(&Result()->StdOut) != 0) { |
| Result()->ErrorMessage = "libuv start reading from stdout pipe failed"; |
| } |
| } |
| if (!Result()->error()) { |
| if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut |
| : &Result()->StdErr) != 0) { |
| Result()->ErrorMessage = "libuv start reading from stderr pipe failed"; |
| } |
| } |
| |
| if (!Result()->error()) { |
| IsStarted_ = true; |
| FinishedCallback_ = std::move(finishedCallback); |
| } else { |
| // Clear libuv handles and finish |
| UVProcess_.reset(); |
| UVPipeOut_.reset(); |
| UVPipeErr_.reset(); |
| CommandPtr_.clear(); |
| } |
| |
| return IsStarted(); |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle, |
| int64_t exitStatus, |
| int termSignal) |
| { |
| auto& proc = *reinterpret_cast<ReadOnlyProcessT*>(handle->data); |
| if (proc.IsStarted() && !proc.IsFinished()) { |
| // Set error message on demand |
| proc.Result()->ExitStatus = exitStatus; |
| proc.Result()->TermSignal = termSignal; |
| if (!proc.Result()->error()) { |
| if (termSignal != 0) { |
| proc.Result()->ErrorMessage = "Process was terminated by signal "; |
| proc.Result()->ErrorMessage += |
| std::to_string(proc.Result()->TermSignal); |
| } else if (exitStatus != 0) { |
| proc.Result()->ErrorMessage = "Process failed with return value "; |
| proc.Result()->ErrorMessage += |
| std::to_string(proc.Result()->ExitStatus); |
| } |
| } |
| |
| // Reset process handle and try to finish |
| proc.UVProcess_.reset(); |
| proc.UVTryFinish(); |
| } |
| } |
| |
| void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish() |
| { |
| // There still might be data in the pipes after the process has finished. |
| // Therefore check if the process is finished AND all pipes are closed before |
| // signaling the worker thread to continue. |
| if (UVProcess_.get() == nullptr) { |
| if (UVPipeOut_.uv_pipe() == nullptr) { |
| if (UVPipeErr_.uv_pipe() == nullptr) { |
| IsFinished_ = true; |
| FinishedCallback_(); |
| } |
| } |
| } |
| } |
| |
| cmQtAutoGenerator::cmQtAutoGenerator() |
| : FileSys_(&Logger_) |
| { |
| // Initialize logger |
| Logger_.SetVerbose(cmSystemTools::HasEnv("VERBOSE")); |
| { |
| std::string colorEnv; |
| cmSystemTools::GetEnv("COLOR", colorEnv); |
| if (!colorEnv.empty()) { |
| Logger_.SetColorOutput(cmSystemTools::IsOn(colorEnv.c_str())); |
| } else { |
| Logger_.SetColorOutput(true); |
| } |
| } |
| |
| // Initialize libuv loop |
| uv_disable_stdio_inheritance(); |
| #ifdef CMAKE_UV_SIGNAL_HACK |
| UVHackRAII_ = cm::make_unique<cmUVSignalHackRAII>(); |
| #endif |
| UVLoop_ = cm::make_unique<uv_loop_t>(); |
| uv_loop_init(UVLoop()); |
| } |
| |
| cmQtAutoGenerator::~cmQtAutoGenerator() |
| { |
| // Close libuv loop |
| uv_loop_close(UVLoop()); |
| } |
| |
| bool cmQtAutoGenerator::Run(std::string const& infoFile, |
| std::string const& config) |
| { |
| // Info settings |
| InfoFile_ = infoFile; |
| cmSystemTools::ConvertToUnixSlashes(InfoFile_); |
| InfoDir_ = cmSystemTools::GetFilenamePath(infoFile); |
| InfoConfig_ = config; |
| |
| bool success = false; |
| { |
| cmake cm(cmake::RoleScript); |
| cm.SetHomeOutputDirectory(InfoDir()); |
| cm.SetHomeDirectory(InfoDir()); |
| cm.GetCurrentSnapshot().SetDefaultDefinitions(); |
| cmGlobalGenerator gg(&cm); |
| |
| cmStateSnapshot snapshot = cm.GetCurrentSnapshot(); |
| snapshot.GetDirectory().SetCurrentBinary(InfoDir()); |
| snapshot.GetDirectory().SetCurrentSource(InfoDir()); |
| |
| auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot); |
| // The OLD/WARN behavior for policy CMP0053 caused a speed regression. |
| // https://gitlab.kitware.com/cmake/cmake/issues/17570 |
| makefile->SetPolicyVersion("3.9"); |
| gg.SetCurrentMakefile(makefile.get()); |
| success = this->Init(makefile.get()); |
| } |
| if (success) { |
| success = this->Process(); |
| } |
| return success; |
| } |
| |
| std::string cmQtAutoGenerator::SettingsFind(std::string const& content, |
| const char* key) |
| { |
| std::string prefix(key); |
| prefix += ':'; |
| std::string::size_type pos = content.find(prefix); |
| if (pos != std::string::npos) { |
| pos += prefix.size(); |
| if (pos < content.size()) { |
| std::string::size_type posE = content.find('\n', pos); |
| if ((posE != std::string::npos) && (posE != pos)) { |
| return content.substr(pos, posE - pos); |
| } |
| } |
| } |
| return std::string(); |
| } |