This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "CMake".
The branch, master has been updated via f9feab40a195716a6bba11b39a809a18830df956 (commit) via df6c4afa777098c6a2fcc01e4513ab5a6e444bc7 (commit) via 15004e431923035227bf502fe07c6bef2beb2d74 (commit) via bd6c3f8609b87f6995acb2aef21aa572f0f73fa7 (commit) via 54903af84bc8656344e7fe1ea0a11d5f09e94f86 (commit) via 081104fb003a56bb3d8330363795ace5b71bc3f8 (commit) via eff6e622d6cd66186ad42c9ec2ae6b2055008707 (commit) via 26025d6e106ffd4cd777fdc3a4343b33c8554c15 (commit) via 6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 (commit) from 342936963479d59400a5f6ba8ef4de339212d303 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=f9feab40a195716a6bba11b39a809a18830df956 commit f9feab40a195716a6bba11b39a809a18830df956 Merge: df6c4af 15004e4 Author: Brad King <brad.k...@kitware.com> AuthorDate: Mon May 13 14:41:50 2019 +0000 Commit: Kitware Robot <kwro...@kitware.com> CommitDate: Mon May 13 10:42:02 2019 -0400 Merge topic 'autorcc_timestamp' 15004e4319 AutoRcc: Simplify error logging with utility lambda bd6c3f8609 AutoRcc: Rebuild if the rcc executable is newer than its output 54903af84b AutoRcc: Don't read the info file time again 081104fb00 AutoRcc: Write re-generation reason and rcc command as one string eff6e622d6 Autogen: A missing info file is a critical error Acked-by: Kitware Robot <kwro...@kitware.com> Merge-request: !3311 https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=df6c4afa777098c6a2fcc01e4513ab5a6e444bc7 commit df6c4afa777098c6a2fcc01e4513ab5a6e444bc7 Merge: 3429369 26025d6 Author: Brad King <brad.k...@kitware.com> AuthorDate: Mon May 13 14:37:56 2019 +0000 Commit: Kitware Robot <kwro...@kitware.com> CommitDate: Mon May 13 10:38:09 2019 -0400 Merge topic 'cmuvprocesschain' 26025d6e10 cmUVProcessChain: Add cmUVProcessChain 6b04d1cdc2 cmUVStreambuf: Initialize all members on construction Acked-by: Kitware Robot <kwro...@kitware.com> Merge-request: !3275 https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=15004e431923035227bf502fe07c6bef2beb2d74 commit 15004e431923035227bf502fe07c6bef2beb2d74 Author: Sebastian Holtermann <sebh...@xwmw.org> AuthorDate: Sat May 11 21:39:41 2019 +0200 Commit: Sebastian Holtermann <sebh...@xwmw.org> CommitDate: Sat May 11 22:15:47 2019 +0200 AutoRcc: Simplify error logging with utility lambda diff --git a/Source/cmQtAutoRcc.cxx b/Source/cmQtAutoRcc.cxx index 922767d..7ac7339 100644 --- a/Source/cmQtAutoRcc.cxx +++ b/Source/cmQtAutoRcc.cxx @@ -3,6 +3,8 @@ #include "cmQtAutoRcc.h" #include "cmQtAutoGen.h" +#include <sstream> + #include "cmAlgorithms.h" #include "cmCryptoHash.h" #include "cmDuration.h" @@ -49,11 +51,16 @@ bool cmQtAutoRcc::Init(cmMakefile* makefile) cmSystemTools::ExpandListArgument(InfoGetConfig(key), list); return list; }; + auto LogInfoError = [this](std::string const& msg) -> bool { + std::ostringstream err; + err << "In " << Quoted(this->InfoFile()) << ":\n" << msg; + this->Log().Error(GenT::RCC, err.str()); + return false; + }; // -- Read info file if (!makefile->ReadListFile(InfoFile())) { - Log().ErrorFile(GenT::RCC, InfoFile(), "File processing failed."); - return false; + return LogInfoError("File processing failed."); } // - Configurations @@ -63,14 +70,12 @@ bool cmQtAutoRcc::Init(cmMakefile* makefile) // - Directories AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR"); if (AutogenBuildDir_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "Build directory empty."); - return false; + return LogInfoError("Build directory empty."); } IncludeDir_ = InfoGetConfig("ARCC_INCLUDE_DIR"); if (IncludeDir_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "Include directory empty."); - return false; + return LogInfoError("Include directory empty."); } // - Rcc executable @@ -79,8 +84,7 @@ bool cmQtAutoRcc::Init(cmMakefile* makefile) std::string error = "The rcc executable "; error += Quoted(RccExecutable_); error += " does not exist."; - Log().ErrorFile(GenT::RCC, InfoFile(), error); - return false; + return LogInfoError(error); } RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS"); @@ -99,28 +103,22 @@ bool cmQtAutoRcc::Init(cmMakefile* makefile) // - Validity checks if (LockFile_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "Lock file name missing."); - return false; + return LogInfoError("Lock file name missing."); } if (SettingsFile_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "Settings file name missing."); - return false; + return LogInfoError("Settings file name missing."); } if (AutogenBuildDir_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "Autogen build directory missing."); - return false; + return LogInfoError("Autogen build directory missing."); } if (RccExecutable_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "rcc executable missing."); - return false; + return LogInfoError("rcc executable missing."); } if (QrcFile_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "rcc input file missing."); - return false; + return LogInfoError("rcc input file missing."); } if (RccFileName_.empty()) { - Log().ErrorFile(GenT::RCC, InfoFile(), "rcc output file missing."); - return false; + return LogInfoError("rcc output file missing."); } // Init derived information https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=bd6c3f8609b87f6995acb2aef21aa572f0f73fa7 commit bd6c3f8609b87f6995acb2aef21aa572f0f73fa7 Author: Sebastian Holtermann <sebh...@xwmw.org> AuthorDate: Sat May 11 21:30:20 2019 +0200 Commit: Sebastian Holtermann <sebh...@xwmw.org> CommitDate: Sat May 11 22:15:47 2019 +0200 AutoRcc: Rebuild if the rcc executable is newer than its output In AUTORCC add a test if the rcc executable is newer that the rcc output. If the rcc executable is newer, rebuild the output. diff --git a/Source/cmQtAutoRcc.cxx b/Source/cmQtAutoRcc.cxx index 7063f6a..922767d 100644 --- a/Source/cmQtAutoRcc.cxx +++ b/Source/cmQtAutoRcc.cxx @@ -75,6 +75,13 @@ bool cmQtAutoRcc::Init(cmMakefile* makefile) // - Rcc executable RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE"); + if (!RccExecutableTime_.Load(RccExecutable_)) { + std::string error = "The rcc executable "; + error += Quoted(RccExecutable_); + error += " does not exist."; + Log().ErrorFile(GenT::RCC, InfoFile(), error); + return false; + } RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS"); // - Job @@ -336,6 +343,18 @@ bool cmQtAutoRcc::TestQrcRccFiles(bool& generate) return true; } + // Test if the rcc output file is older than the rcc executable + if (RccFileTime_.Older(RccExecutableTime_)) { + if (Log().Verbose()) { + Reason = "Generating "; + Reason += Quoted(RccFileOutput_); + Reason += ", because it is older than the rcc executable, from "; + Reason += Quoted(QrcFile_); + } + generate = true; + return true; + } + return true; } diff --git a/Source/cmQtAutoRcc.h b/Source/cmQtAutoRcc.h index 2dde574..636a667 100644 --- a/Source/cmQtAutoRcc.h +++ b/Source/cmQtAutoRcc.h @@ -54,6 +54,7 @@ private: std::string IncludeDir_; // -- Qt environment std::string RccExecutable_; + cmFileTime RccExecutableTime_; std::vector<std::string> RccListOptions_; // -- Job std::string LockFile_; https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=54903af84bc8656344e7fe1ea0a11d5f09e94f86 commit 54903af84bc8656344e7fe1ea0a11d5f09e94f86 Author: Sebastian Holtermann <sebh...@xwmw.org> AuthorDate: Sat May 11 22:13:39 2019 +0200 Commit: Sebastian Holtermann <sebh...@xwmw.org> CommitDate: Sat May 11 22:15:47 2019 +0200 AutoRcc: Don't read the info file time again In `AUTORCC` use the info file time that's available already instead of reading it again. diff --git a/Source/cmQtAutoRcc.cxx b/Source/cmQtAutoRcc.cxx index f841260..7063f6a 100644 --- a/Source/cmQtAutoRcc.cxx +++ b/Source/cmQtAutoRcc.cxx @@ -383,18 +383,7 @@ bool cmQtAutoRcc::TestResources(bool& generate) bool cmQtAutoRcc::TestInfoFile() { // Test if the rcc output file is older than the info file - - cmFileTime infoFileTime; - if (!infoFileTime.Load(InfoFile())) { - std::string error; - error = "Could not find the info file "; - error += Quoted(InfoFile()); - error += '\n'; - Log().ErrorFile(GenT::RCC, QrcFile_, error); - return false; - } - if (RccFileTime_.Older(infoFileTime)) { - + if (RccFileTime_.Older(InfoFileTime())) { if (Log().Verbose()) { std::string reason = "Touching "; reason += Quoted(RccFileOutput_); https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=081104fb003a56bb3d8330363795ace5b71bc3f8 commit 081104fb003a56bb3d8330363795ace5b71bc3f8 Author: Sebastian Holtermann <sebh...@xwmw.org> AuthorDate: Sat May 11 21:23:29 2019 +0200 Commit: Sebastian Holtermann <sebh...@xwmw.org> CommitDate: Sat May 11 21:23:29 2019 +0200 AutoRcc: Write re-generation reason and rcc command as one string In AUTORCC with verbose output write the rcc re-generation reason and the rcc command as on single string to avoid message chopping in concurrent builds. diff --git a/Source/cmQtAutoRcc.cxx b/Source/cmQtAutoRcc.cxx index bb40c39..f841260 100644 --- a/Source/cmQtAutoRcc.cxx +++ b/Source/cmQtAutoRcc.cxx @@ -301,12 +301,10 @@ bool cmQtAutoRcc::TestQrcRccFiles(bool& generate) // Test if the rcc output file exists if (!RccFileTime_.Load(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(GenT::RCC, reason); + Reason = "Generating "; + Reason += Quoted(RccFileOutput_); + Reason += ", because it doesn't exist, from "; + Reason += Quoted(QrcFile_); } generate = true; return true; @@ -315,12 +313,10 @@ bool cmQtAutoRcc::TestQrcRccFiles(bool& 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(GenT::RCC, reason); + Reason = "Generating "; + Reason += Quoted(RccFileOutput_); + Reason += ", because the rcc settings changed, from "; + Reason += Quoted(QrcFile_); } generate = true; return true; @@ -329,11 +325,12 @@ bool cmQtAutoRcc::TestQrcRccFiles(bool& generate) // Test if the rcc output file is older than the .qrc file if (RccFileTime_.Older(QrcFileTime_)) { if (Log().Verbose()) { - std::string reason = "Generating "; - reason += Quoted(RccFileOutput_); - reason += " because it is older than "; - reason += Quoted(QrcFile_); - Log().Info(GenT::RCC, reason); + Reason = "Generating "; + Reason += Quoted(RccFileOutput_); + Reason += ", because it is older than "; + Reason += Quoted(QrcFile_); + Reason += ", from "; + Reason += Quoted(QrcFile_); } generate = true; return true; @@ -354,6 +351,7 @@ bool cmQtAutoRcc::TestResources(bool& generate) } } + // Check if any resource file is newer than the rcc output file for (std::string const& resFile : Inputs_) { // Check if the resource file exists cmFileTime fileTime; @@ -365,16 +363,15 @@ bool cmQtAutoRcc::TestResources(bool& generate) Log().ErrorFile(GenT::RCC, QrcFile_, error); return false; } - // Check if the resource file is newer than the build file + // Check if the resource file is newer than the rcc output file if (RccFileTime_.Older(fileTime)) { 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(GenT::RCC, reason); + Reason = "Generating "; + Reason += Quoted(RccFileOutput_); + Reason += ", because it is older than "; + Reason += Quoted(resFile); + Reason += ", from "; + Reason += Quoted(QrcFile_); } generate = true; break; @@ -397,6 +394,7 @@ bool cmQtAutoRcc::TestInfoFile() return false; } if (RccFileTime_.Older(infoFileTime)) { + if (Log().Verbose()) { std::string reason = "Touching "; reason += Quoted(RccFileOutput_); @@ -424,7 +422,7 @@ bool cmQtAutoRcc::GenerateRcc() return false; } - // Start a rcc process + // Compose rcc command std::vector<std::string> cmd; cmd.push_back(RccExecutable_); cmd.insert(cmd.end(), Options_.begin(), Options_.end()); @@ -432,12 +430,15 @@ bool cmQtAutoRcc::GenerateRcc() cmd.push_back(RccFileOutput_); cmd.push_back(QrcFile_); - // Log command + // Log reason and command if (Log().Verbose()) { - std::string msg = "Running command:\n"; + std::string msg = Reason; + if (!msg.empty() && (msg.back() != '\n')) { + msg += '\n'; + } msg += QuotedCommand(cmd); msg += '\n'; - cmSystemTools::Stdout(msg); + Log().Info(GenT::RCC, msg); } std::string rccStdOut; diff --git a/Source/cmQtAutoRcc.h b/Source/cmQtAutoRcc.h index 01c3fb9..2dde574 100644 --- a/Source/cmQtAutoRcc.h +++ b/Source/cmQtAutoRcc.h @@ -67,6 +67,7 @@ private: std::string RccFileOutput_; std::string RccFilePublic_; cmFileTime RccFileTime_; + std::string Reason; std::vector<std::string> Options_; std::vector<std::string> Inputs_; // -- Settings file https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=eff6e622d6cd66186ad42c9ec2ae6b2055008707 commit eff6e622d6cd66186ad42c9ec2ae6b2055008707 Author: Sebastian Holtermann <sebh...@xwmw.org> AuthorDate: Sat May 11 21:17:05 2019 +0200 Commit: Sebastian Holtermann <sebh...@xwmw.org> CommitDate: Sat May 11 21:17:05 2019 +0200 Autogen: A missing info file is a critical error diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx index f7e377d..e1c435b 100644 --- a/Source/cmQtAutoGenerator.cxx +++ b/Source/cmQtAutoGenerator.cxx @@ -279,10 +279,11 @@ bool cmQtAutoGenerator::Run(std::string const& infoFile, InfoFile_ = infoFile; cmSystemTools::ConvertToUnixSlashes(InfoFile_); if (!InfoFileTime_.Load(InfoFile_)) { - std::string msg = "Autogen: The info file "; + std::string msg = "AutoGen: The info file "; msg += Quoted(InfoFile_); msg += " is not readable\n"; cmSystemTools::Stderr(msg); + return false; } InfoDir_ = cmSystemTools::GetFilenamePath(infoFile); InfoConfig_ = config; https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=26025d6e106ffd4cd777fdc3a4343b33c8554c15 commit 26025d6e106ffd4cd777fdc3a4343b33c8554c15 Author: Kyle Edwards <kyle.edwa...@kitware.com> AuthorDate: Tue Apr 30 11:29:30 2019 -0400 Commit: Kyle Edwards <kyle.edwa...@kitware.com> CommitDate: Tue May 7 13:40:06 2019 -0400 cmUVProcessChain: Add cmUVProcessChain This class is ultimately intended as a replacement for cmsys::Process. It spawns a series of processes using libuv, piping the output of each command into the next. Note: input support has not yet been implemented because write support has not yet been implemented on cmUVStreambuf. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 01c6cd7..42eed4d 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -388,6 +388,8 @@ set(SRCS cmUuid.cxx cmUVHandlePtr.cxx cmUVHandlePtr.h + cmUVProcessChain.cxx + cmUVProcessChain.h cmUVStreambuf.h cmUVSignalHackRAII.h cmVariableWatch.cxx diff --git a/Source/cmUVHandlePtr.cxx b/Source/cmUVHandlePtr.cxx index 27069ee..db67463 100644 --- a/Source/cmUVHandlePtr.cxx +++ b/Source/cmUVHandlePtr.cxx @@ -211,7 +211,6 @@ uv_pipe_ptr::operator uv_stream_t*() const return reinterpret_cast<uv_stream_t*>(handle.get()); } -#ifdef CMAKE_BUILD_WITH_CMAKE int uv_process_ptr::spawn(uv_loop_t& loop, uv_process_options_t const& options, void* data) { @@ -231,6 +230,7 @@ int uv_timer_ptr::start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat) return uv_timer_start(*this, cb, timeout, repeat); } +#ifdef CMAKE_BUILD_WITH_CMAKE uv_tty_ptr::operator uv_stream_t*() const { return reinterpret_cast<uv_stream_t*>(handle.get()); @@ -255,13 +255,13 @@ UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe) UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(stream) -#ifdef CMAKE_BUILD_WITH_CMAKE -UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async) - UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(process) UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(timer) +#ifdef CMAKE_BUILD_WITH_CMAKE +UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async) + UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty) #endif } diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx new file mode 100644 index 0000000..c4e30d4 --- /dev/null +++ b/Source/cmUVProcessChain.cxx @@ -0,0 +1,392 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmUVProcessChain.h" + +#include "cmAlgorithms.h" +#include "cmGetPipes.h" +#include "cmUVHandlePtr.h" +#include "cmUVStreambuf.h" +#include "cm_uv.h" + +#include <iterator> +#include <memory> +#include <utility> + +struct cmUVProcessChain::InternalData +{ + struct BasicStreamData + { + cmUVStreambuf Streambuf; + cm::uv_pipe_ptr BuiltinStream; + uv_stdio_container_t Stdio; + }; + + template <typename IOStream> + struct StreamData : public BasicStreamData + { + StreamData() + : BuiltinIOStream(&this->Streambuf) + { + } + + IOStream BuiltinIOStream; + + IOStream* GetBuiltinStream() + { + if (this->BuiltinStream.get()) { + return &this->BuiltinIOStream; + } + return nullptr; + } + }; + + struct ProcessData + { + cmUVProcessChain::InternalData* Data; + cm::uv_process_ptr Process; + cm::uv_pipe_ptr OutputPipe; + bool Finished = false; + Status ProcessStatus; + }; + + const cmUVProcessChainBuilder* Builder = nullptr; + + bool Valid = false; + + cm::uv_loop_ptr Loop; + + StreamData<std::istream> OutputStreamData; + StreamData<std::istream> ErrorStreamData; + + unsigned int ProcessesCompleted = 0; + std::vector<std::unique_ptr<ProcessData>> Processes; + + bool Prepare(const cmUVProcessChainBuilder* builder); + bool AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration& config, + bool first, bool last); + bool Finish(); + + static const Status* GetStatus(const ProcessData& data); +}; + +cmUVProcessChainBuilder::cmUVProcessChainBuilder() +{ + this->SetNoStream(Stream_INPUT) + .SetNoStream(Stream_OUTPUT) + .SetNoStream(Stream_ERROR); +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand( + const std::vector<std::string>& arguments) +{ + if (!arguments.empty()) { + this->Processes.emplace_back(); + this->Processes.back().Arguments = arguments; + } + return *this; +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio) +{ + switch (stdio) { + case Stream_INPUT: + case Stream_OUTPUT: + case Stream_ERROR: { + auto& streamData = this->Stdio[stdio]; + streamData.Type = None; + break; + } + } + return *this; +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream( + Stream stdio) +{ + switch (stdio) { + case Stream_INPUT: + // FIXME + break; + + case Stream_OUTPUT: + case Stream_ERROR: { + auto& streamData = this->Stdio[stdio]; + streamData.Type = Builtin; + break; + } + } + return *this; +} + +cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream( + Stream stdio, int fd) +{ + switch (stdio) { + case Stream_INPUT: + // FIXME + break; + + case Stream_OUTPUT: + case Stream_ERROR: { + auto& streamData = this->Stdio[stdio]; + streamData.Type = External; + streamData.FileDescriptor = fd; + break; + } + } + return *this; +} + +cmUVProcessChain cmUVProcessChainBuilder::Start() const +{ + cmUVProcessChain chain; + + if (!chain.Data->Prepare(this)) { + return chain; + } + + for (auto it = this->Processes.begin(); it != this->Processes.end(); ++it) { + if (!chain.Data->AddCommand(*it, it == this->Processes.begin(), + it == std::prev(this->Processes.end()))) { + return chain; + } + } + + chain.Data->Finish(); + + return chain; +} + +const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus( + const cmUVProcessChain::InternalData::ProcessData& data) +{ + if (data.Finished) { + return &data.ProcessStatus; + } + return nullptr; +} + +bool cmUVProcessChain::InternalData::Prepare( + const cmUVProcessChainBuilder* builder) +{ + this->Builder = builder; + + auto const& output = + this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT]; + auto& outputData = this->OutputStreamData; + switch (output.Type) { + case cmUVProcessChainBuilder::None: + outputData.Stdio.flags = UV_IGNORE; + break; + + case cmUVProcessChainBuilder::Builtin: + outputData.BuiltinStream.init(*this->Loop, 0); + outputData.Stdio.flags = + static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + outputData.Stdio.data.stream = outputData.BuiltinStream; + break; + + case cmUVProcessChainBuilder::External: + outputData.Stdio.flags = UV_INHERIT_FD; + outputData.Stdio.data.fd = output.FileDescriptor; + break; + } + + auto const& error = + this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR]; + auto& errorData = this->ErrorStreamData; + switch (error.Type) { + case cmUVProcessChainBuilder::None: + errorData.Stdio.flags = UV_IGNORE; + break; + + case cmUVProcessChainBuilder::Builtin: { + int pipeFd[2]; + if (cmGetPipes(pipeFd) < 0) { + return false; + } + + errorData.BuiltinStream.init(*this->Loop, 0); + if (uv_pipe_open(errorData.BuiltinStream, pipeFd[0]) < 0) { + return false; + } + errorData.Stdio.flags = UV_INHERIT_FD; + errorData.Stdio.data.fd = pipeFd[1]; + break; + } + + case cmUVProcessChainBuilder::External: + errorData.Stdio.flags = UV_INHERIT_FD; + errorData.Stdio.data.fd = error.FileDescriptor; + break; + } + + return true; +} + +bool cmUVProcessChain::InternalData::AddCommand( + const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first, + bool last) +{ + this->Processes.emplace_back(cm::make_unique<ProcessData>()); + auto& process = *this->Processes.back(); + process.Data = this; + + auto options = uv_process_options_t(); + + // Bounds were checked at add time, first element is guaranteed to exist + options.file = config.Arguments[0].c_str(); + + std::vector<const char*> arguments; + for (auto const& arg : config.Arguments) { + arguments.push_back(arg.c_str()); + } + arguments.push_back(nullptr); + options.args = const_cast<char**>(arguments.data()); + options.flags = UV_PROCESS_WINDOWS_HIDE; + + std::array<uv_stdio_container_t, 3> stdio; + stdio[0] = uv_stdio_container_t(); + if (first) { + stdio[0].flags = UV_IGNORE; + } else { + auto& prev = **std::prev(this->Processes.end(), 2); + stdio[0].flags = UV_INHERIT_STREAM; + stdio[0].data.stream = prev.OutputPipe; + } + if (last) { + stdio[1] = this->OutputStreamData.Stdio; + } else { + if (process.OutputPipe.init(*this->Loop, 0) < 0) { + return false; + } + stdio[1] = uv_stdio_container_t(); + stdio[1].flags = + static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); + stdio[1].data.stream = process.OutputPipe; + } + stdio[2] = this->ErrorStreamData.Stdio; + + options.stdio = stdio.data(); + options.stdio_count = 3; + options.exit_cb = [](uv_process_t* handle, int64_t exitStatus, + int termSignal) { + auto* processData = static_cast<ProcessData*>(handle->data); + processData->Finished = true; + processData->ProcessStatus.ExitStatus = exitStatus; + processData->ProcessStatus.TermSignal = termSignal; + processData->Data->ProcessesCompleted++; + }; + + return process.Process.spawn(*this->Loop, options, &process) >= 0; +} + +bool cmUVProcessChain::InternalData::Finish() +{ + if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type == + cmUVProcessChainBuilder::Builtin) { + this->OutputStreamData.Streambuf.open( + this->OutputStreamData.BuiltinStream); + } + + if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR].Type == + cmUVProcessChainBuilder::Builtin) { + cm::uv_pipe_ptr tmpPipe; + if (tmpPipe.init(*this->Loop, 0) < 0) { + return false; + } + if (uv_pipe_open(tmpPipe, this->ErrorStreamData.Stdio.data.fd) < 0) { + return false; + } + tmpPipe.reset(); + + this->ErrorStreamData.Streambuf.open(this->ErrorStreamData.BuiltinStream); + } + + this->Valid = true; + return true; +} + +cmUVProcessChain::cmUVProcessChain() + : Data(cm::make_unique<InternalData>()) +{ + this->Data->Loop.init(); +} + +cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept + : Data(std::move(other.Data)) +{ +} + +cmUVProcessChain::~cmUVProcessChain() = default; + +cmUVProcessChain& cmUVProcessChain::operator=( + cmUVProcessChain&& other) noexcept +{ + this->Data = std::move(other.Data); + return *this; +} + +uv_loop_t& cmUVProcessChain::GetLoop() +{ + return *this->Data->Loop; +} + +std::istream* cmUVProcessChain::OutputStream() +{ + return this->Data->OutputStreamData.GetBuiltinStream(); +} + +std::istream* cmUVProcessChain::ErrorStream() +{ + return this->Data->ErrorStreamData.GetBuiltinStream(); +} + +bool cmUVProcessChain::Valid() const +{ + return this->Data->Valid; +} + +bool cmUVProcessChain::Wait(int64_t milliseconds) +{ + bool timeout = false; + cm::uv_timer_ptr timer; + + if (milliseconds >= 0) { + timer.init(*this->Data->Loop, &timeout); + timer.start( + [](uv_timer_t* handle) { + auto* timeoutPtr = static_cast<bool*>(handle->data); + *timeoutPtr = true; + }, + milliseconds, 0); + } + + while (!timeout && + this->Data->ProcessesCompleted < this->Data->Processes.size()) { + uv_run(this->Data->Loop, UV_RUN_ONCE); + } + + return !timeout; +} + +std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus() + const +{ + std::vector<const cmUVProcessChain::Status*> statuses( + this->Data->Processes.size(), nullptr); + for (std::size_t i = 0; i < statuses.size(); i++) { + statuses[i] = this->GetStatus(i); + } + return statuses; +} + +const cmUVProcessChain::Status* cmUVProcessChain::GetStatus( + std::size_t index) const +{ + auto const& process = *this->Data->Processes[index]; + if (process.Finished) { + return &process.ProcessStatus; + } + return nullptr; +} diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h new file mode 100644 index 0000000..2b33520 --- /dev/null +++ b/Source/cmUVProcessChain.h @@ -0,0 +1,100 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmUVProcessChain_h +#define cmUVProcessChain_h + +#include "cm_uv.h" + +#include <array> +#include <iosfwd> +#include <memory> // IWYU pragma: keep +#include <string> +#include <vector> + +#include <stdint.h> + +class cmUVProcessChain; + +class cmUVProcessChainBuilder +{ +public: + enum Stream + { + Stream_INPUT = 0, + Stream_OUTPUT = 1, + Stream_ERROR = 2, + }; + + cmUVProcessChainBuilder(); + + cmUVProcessChainBuilder& AddCommand( + const std::vector<std::string>& arguments); + cmUVProcessChainBuilder& SetNoStream(Stream stdio); + cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio); + cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd); + + cmUVProcessChain Start() const; + +private: + enum StdioType + { + None, + Builtin, + External, + }; + + friend class cmUVProcessChain; + + struct StdioConfiguration + { + StdioType Type; + int FileDescriptor; + }; + + struct ProcessConfiguration + { + std::vector<std::string> Arguments; + }; + + std::array<StdioConfiguration, 3> Stdio; + std::vector<ProcessConfiguration> Processes; +}; + +class cmUVProcessChain +{ +public: + struct Status + { + int64_t ExitStatus; + int TermSignal; + }; + + cmUVProcessChain(const cmUVProcessChain& other) = delete; + cmUVProcessChain(cmUVProcessChain&& other) noexcept; + + ~cmUVProcessChain(); + + cmUVProcessChain& operator=(const cmUVProcessChain& other) = delete; + cmUVProcessChain& operator=(cmUVProcessChain&& other) noexcept; + + uv_loop_t& GetLoop(); + + // FIXME: Add stdin support + std::istream* OutputStream(); + std::istream* ErrorStream(); + + bool Valid() const; + bool Wait(int64_t milliseconds = -1); + std::vector<const Status*> GetStatus() const; + const Status* GetStatus(std::size_t index) const; + +private: + friend class cmUVProcessChainBuilder; + + cmUVProcessChain(); + + struct InternalData; + std::unique_ptr<InternalData> Data; +}; + +#endif diff --git a/Source/cmUVStreambuf.h b/Source/cmUVStreambuf.h index 0ae532b..873352b 100644 --- a/Source/cmUVStreambuf.h +++ b/Source/cmUVStreambuf.h @@ -208,7 +208,7 @@ void cmBasicUVStreambuf<CharT, Traits>::StreamRead(ssize_t nread) this->setg(this->eback(), this->gptr(), this->egptr() + nread / sizeof(CharT)); uv_read_stop(this->Stream); - } else if (nread < 0 || nread == UV_EOF) { + } else if (nread < 0 /*|| nread == UV_EOF*/) { this->EndOfFile = true; uv_read_stop(this->Stream); } diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt index e04bba2..a25f25a 100644 --- a/Tests/CMakeLib/CMakeLists.txt +++ b/Tests/CMakeLib/CMakeLists.txt @@ -15,11 +15,15 @@ set(CMakeLib_TESTS testXMLParser.cxx testXMLSafe.cxx testFindPackageCommand.cxx + testUVProcessChain.cxx testUVRAII.cxx testUVStreambuf.cxx ) +add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx) + set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR}) +set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>) set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>) if(WIN32) diff --git a/Tests/CMakeLib/testUVProcessChain.cxx b/Tests/CMakeLib/testUVProcessChain.cxx new file mode 100644 index 0000000..72ae602 --- /dev/null +++ b/Tests/CMakeLib/testUVProcessChain.cxx @@ -0,0 +1,335 @@ +#include "cmUVProcessChain.h" + +#include "cmAlgorithms.h" +#include "cmGetPipes.h" +#include "cmUVHandlePtr.h" +#include "cmUVStreambuf.h" + +#include "cm_uv.h" + +#include <algorithm> +#include <functional> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include <csignal> + +struct ExpectedStatus +{ + bool Finished; + bool MatchExitStatus; + bool MatchTermSignal; + cmUVProcessChain::Status Status; +}; + +static const std::vector<ExpectedStatus> status1 = { + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, +}; + +static const std::vector<ExpectedStatus> status2 = { + { true, true, true, { 0, 0 } }, + { false, false, false, { 0, 0 } }, + { false, false, false, { 0, 0 } }, +}; + +static const std::vector<ExpectedStatus> status3 = { + { true, true, true, { 0, 0 } }, + { true, true, true, { 1, 0 } }, +#ifdef _WIN32 + { true, true, true, { 2, 0 } }, +#else + { true, false, true, { 0, SIGABRT } }, +#endif +}; + +bool operator==(const cmUVProcessChain::Status* actual, + const ExpectedStatus& expected) +{ + if (!expected.Finished) { + return !actual; + } else if (!actual) { + return false; + } + if (expected.MatchExitStatus && + expected.Status.ExitStatus != actual->ExitStatus) { + return false; + } + if (expected.MatchTermSignal && + expected.Status.TermSignal != actual->TermSignal) { + return false; + } + return true; +} + +bool resultsMatch(const std::vector<const cmUVProcessChain::Status*>& actual, + const std::vector<ExpectedStatus>& expected) +{ + return actual.size() == expected.size() && + std::equal(actual.begin(), actual.end(), expected.begin()); +} + +std::string getInput(std::istream& input) +{ + char buffer[1024]; + std::ostringstream str; + do { + input.read(buffer, 1024); + str.write(buffer, input.gcount()); + } while (input.gcount() > 0); + return str.str(); +} + +template <typename T> +std::function<std::ostream&(std::ostream&)> printExpected(bool match, + const T& value) +{ + return [match, value](std::ostream& stream) -> std::ostream& { + if (match) { + stream << value; + } else { + stream << "*"; + } + return stream; + }; +} + +std::ostream& operator<<( + std::ostream& stream, + const std::function<std::ostream&(std::ostream&)>& func) +{ + return func(stream); +} + +void printResults(const std::vector<const cmUVProcessChain::Status*>& actual, + const std::vector<ExpectedStatus>& expected) +{ + std::cout << "Expected: " << std::endl; + for (auto const& e : expected) { + if (e.Finished) { + std::cout << " ExitStatus: " + << printExpected(e.MatchExitStatus, e.Status.ExitStatus) + << ", TermSignal: " + << printExpected(e.MatchTermSignal, e.Status.TermSignal) + << std::endl; + } else { + std::cout << " null" << std::endl; + } + } + std::cout << "Actual:" << std::endl; + for (auto const& a : actual) { + if (a) { + std::cout << " ExitStatus: " << a->ExitStatus + << ", TermSignal: " << a->TermSignal << std::endl; + } else { + std::cout << " null" << std::endl; + } + } +} + +bool checkExecution(cmUVProcessChainBuilder& builder, + std::unique_ptr<cmUVProcessChain>& chain) +{ + std::vector<const cmUVProcessChain::Status*> status; + + chain = cm::make_unique<cmUVProcessChain>(builder.Start()); + if (!chain->Valid()) { + std::cout << "Valid() returned false, should be true" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status1)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status1); + return false; + } + + if (chain->Wait(6000)) { + std::cout << "Wait() returned true, should be false" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status2)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status2); + return false; + } + + if (!chain->Wait()) { + std::cout << "Wait() returned false, should be true" << std::endl; + return false; + } + status = chain->GetStatus(); + if (!resultsMatch(status, status3)) { + std::cout << "GetStatus() did not produce expected output" << std::endl; + printResults(status, status3); + return false; + } + + return true; +} + +bool checkOutput(std::istream& outputStream, std::istream& errorStream) +{ + std::string output = getInput(outputStream); + if (output != "HELO WRD!") { + std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\"" + << std::endl; + return false; + } + + std::string error = getInput(errorStream); + if (error.length() != 3 || error.find('1') == std::string::npos || + error.find('2') == std::string::npos || + error.find('3') == std::string::npos) { + std::cout << "Error was \"" << error << "\", expected \"123\"" + << std::endl; + return false; + } + + return true; +} + +bool testUVProcessChainBuiltin(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }) + .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT) + .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (!chain->OutputStream()) { + std::cout << "OutputStream() was null, expecting not null" << std::endl; + return false; + } + if (!chain->ErrorStream()) { + std::cout << "ErrorStream() was null, expecting not null" << std::endl; + return false; + } + + if (!checkOutput(*chain->OutputStream(), *chain->ErrorStream())) { + return false; + } + + return true; +} + +bool testUVProcessChainExternal(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + int outputPipe[2], errorPipe[2]; + cm::uv_pipe_ptr outputInPipe, outputOutPipe, errorInPipe, errorOutPipe; + + if (cmGetPipes(outputPipe) < 0) { + std::cout << "Error creating pipes" << std::endl; + return false; + } + if (cmGetPipes(errorPipe) < 0) { + std::cout << "Error creating pipes" << std::endl; + return false; + } + + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }) + .SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, outputPipe[1]) + .SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, errorPipe[1]); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (chain->OutputStream()) { + std::cout << "OutputStream() was not null, expecting null" << std::endl; + return false; + } + if (chain->ErrorStream()) { + std::cout << "ErrorStream() was not null, expecting null" << std::endl; + return false; + } + + outputOutPipe.init(chain->GetLoop(), 0); + uv_pipe_open(outputOutPipe, outputPipe[1]); + outputOutPipe.reset(); + + errorOutPipe.init(chain->GetLoop(), 0); + uv_pipe_open(errorOutPipe, errorPipe[1]); + errorOutPipe.reset(); + + outputInPipe.init(chain->GetLoop(), 0); + uv_pipe_open(outputInPipe, outputPipe[0]); + cmUVStreambuf outputBuf; + outputBuf.open(outputInPipe); + std::istream outputStream(&outputBuf); + + errorInPipe.init(chain->GetLoop(), 0); + uv_pipe_open(errorInPipe, errorPipe[0]); + cmUVStreambuf errorBuf; + errorBuf.open(errorInPipe); + std::istream errorStream(&errorBuf); + + if (!checkOutput(outputStream, errorStream)) { + return false; + } + + return true; +} + +bool testUVProcessChainNone(const char* helperCommand) +{ + cmUVProcessChainBuilder builder; + std::unique_ptr<cmUVProcessChain> chain; + builder.AddCommand({ helperCommand, "echo" }) + .AddCommand({ helperCommand, "capitalize" }) + .AddCommand({ helperCommand, "dedup" }); + + if (!checkExecution(builder, chain)) { + return false; + } + + if (chain->OutputStream()) { + std::cout << "OutputStream() was not null, expecting null" << std::endl; + return false; + } + if (chain->ErrorStream()) { + std::cout << "ErrorStream() was not null, expecting null" << std::endl; + return false; + } + + return true; +} + +int testUVProcessChain(int argc, char** const argv) +{ + if (argc < 2) { + std::cout << "Invalid arguments.\n"; + return -1; + } + + if (!testUVProcessChainBuiltin(argv[1])) { + std::cout << "While executing testUVProcessChainBuiltin().\n"; + return -1; + } + + if (!testUVProcessChainExternal(argv[1])) { + std::cout << "While executing testUVProcessChainExternal().\n"; + return -1; + } + + if (!testUVProcessChainNone(argv[1])) { + std::cout << "While executing testUVProcessChainNone().\n"; + return -1; + } + + return 0; +} diff --git a/Tests/CMakeLib/testUVProcessChainHelper.cxx b/Tests/CMakeLib/testUVProcessChainHelper.cxx new file mode 100644 index 0000000..263665d --- /dev/null +++ b/Tests/CMakeLib/testUVProcessChainHelper.cxx @@ -0,0 +1,72 @@ +#include <chrono> +#include <iostream> +#include <set> +#include <sstream> +#include <string> +#include <thread> + +#include <cctype> +#include <cstdlib> + +std::string getStdin() +{ + char buffer[1024]; + std::ostringstream str; + do { + std::cin.read(buffer, 1024); + str.write(buffer, std::cin.gcount()); + } while (std::cin.gcount() > 0); + return str.str(); +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + return -1; + } + + std::string command = argv[1]; + if (command == "echo") { + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + std::cout << "HELLO world!" << std::flush; + std::cerr << "1" << std::flush; + return 0; + } + if (command == "capitalize") { + std::this_thread::sleep_for(std::chrono::milliseconds(9000)); + std::string input = getStdin(); + for (auto& c : input) { + c = static_cast<char>(std::toupper(c)); + } + std::cout << input << std::flush; + std::cerr << "2" << std::flush; + return 1; + } + if (command == "dedup") { + // Use a nested scope to free all resources before aborting below. + { + std::string input = getStdin(); + std::set<char> seen; + std::string output; + for (auto c : input) { + if (!seen.count(c)) { + seen.insert(c); + output += c; + } + } + std::cout << output << std::flush; + std::cerr << "3" << std::flush; + } + + // On Windows, the exit code of abort() is different between debug and + // release builds, and does not yield a term_signal in libuv in either + // case. For the sake of simplicity, we just return another non-zero code. +#ifdef _WIN32 + return 2; +#else + std::abort(); +#endif + } + + return -1; +} diff --git a/bootstrap b/bootstrap index 8b9c404..c5274ce 100755 --- a/bootstrap +++ b/bootstrap @@ -329,6 +329,7 @@ CMAKE_CXX_SOURCES="\ cmGetCMakePropertyCommand \ cmGetDirectoryPropertyCommand \ cmGetFilenameComponentCommand \ + cmGetPipes \ cmGetPropertyCommand \ cmGetSourceFilePropertyCommand \ cmGetTargetPropertyCommand \ @@ -427,6 +428,7 @@ CMAKE_CXX_SOURCES="\ cmUnexpectedCommand \ cmUnsetCommand \ cmUVHandlePtr \ + cmUVProcessChain \ cmVersion \ cmWhileCommand \ cmWorkingDirectory \ https://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 commit 6b04d1cdc281b9b0dee5f59394a1c41d8b96c4a1 Author: Brad King <brad.k...@kitware.com> AuthorDate: Thu May 2 10:15:30 2019 -0400 Commit: Kyle Edwards <kyle.edwa...@kitware.com> CommitDate: Tue May 7 12:35:22 2019 -0400 cmUVStreambuf: Initialize all members on construction Avoid leaving any members uninitialized after construction even if they are later initialized before use by methods. This helps convince static analysis tools that the members are not used uninitialized. diff --git a/Source/cmUVStreambuf.h b/Source/cmUVStreambuf.h index 29e4fde..0ae532b 100644 --- a/Source/cmUVStreambuf.h +++ b/Source/cmUVStreambuf.h @@ -68,10 +68,10 @@ protected: private: uv_stream_t* Stream = nullptr; - void* OldStreamData; - const std::size_t PutBack; + void* OldStreamData = nullptr; + const std::size_t PutBack = 0; std::vector<CharT> InputBuffer; - bool EndOfFile; + bool EndOfFile = false; void StreamReadStartStop(); ----------------------------------------------------------------------- Summary of changes: Source/CMakeLists.txt | 2 + Source/cmQtAutoGenerator.cxx | 3 +- Source/cmQtAutoRcc.cxx | 123 +++++---- Source/cmQtAutoRcc.h | 2 + Source/cmUVHandlePtr.cxx | 8 +- Source/cmUVProcessChain.cxx | 392 ++++++++++++++++++++++++++++ Source/cmUVProcessChain.h | 100 +++++++ Source/cmUVStreambuf.h | 8 +- Tests/CMakeLib/CMakeLists.txt | 4 + Tests/CMakeLib/testUVProcessChain.cxx | 335 ++++++++++++++++++++++++ Tests/CMakeLib/testUVProcessChainHelper.cxx | 72 +++++ bootstrap | 2 + 12 files changed, 984 insertions(+), 67 deletions(-) create mode 100644 Source/cmUVProcessChain.cxx create mode 100644 Source/cmUVProcessChain.h create mode 100644 Tests/CMakeLib/testUVProcessChain.cxx create mode 100644 Tests/CMakeLib/testUVProcessChainHelper.cxx hooks/post-receive -- CMake _______________________________________________ Cmake-commits mailing list Cmake-commits@cmake.org https://cmake.org/mailman/listinfo/cmake-commits