Merge topic 'autogen_moc_uic_single_job_queue'

58f04b6ecf Autogen: Add ManySources test
a3f062091f Autogen: Rename `cmQtAutoGeneratorMocUic` class to `cmQtAutoMocUic`
8cb26a0a2a Autogen: Factor out concurrency framework to cmWorkerPool class

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !3224
This commit is contained in:
Brad King
2019-04-16 17:38:08 +00:00
committed by Kitware Robot
16 changed files with 1709 additions and 1272 deletions
+4 -2
View File
@@ -350,8 +350,8 @@ set(SRCS
cmQtAutoGenGlobalInitializer.h
cmQtAutoGenInitializer.cxx
cmQtAutoGenInitializer.h
cmQtAutoGeneratorMocUic.cxx
cmQtAutoGeneratorMocUic.h
cmQtAutoMocUic.cxx
cmQtAutoMocUic.h
cmQtAutoRcc.cxx
cmQtAutoRcc.h
cmRST.cxx
@@ -391,6 +391,8 @@ set(SRCS
cmVariableWatch.h
cmVersion.cxx
cmVersion.h
cmWorkerPool.cxx
cmWorkerPool.h
cmWorkingDirectory.cxx
cmWorkingDirectory.h
cmXMLParser.cxx
-232
View File
@@ -14,12 +14,6 @@
#include "cmSystemTools.h"
#include "cmake.h"
#include <algorithm>
#include <sstream>
#include <utility>
// -- Class methods
cmQtAutoGenerator::Logger::Logger()
{
// Initialize logger
@@ -431,232 +425,6 @@ bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
return cmQtAutoGenerator::MakeParentDirectory(filename);
}
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_.data();
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;
}
static std::string getUVError(const char* prefixString, int uvErrorCode)
{
std::ostringstream ost;
ost << prefixString << ": " << uv_strerror(uvErrorCode);
return ost.str();
}
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_.data());
UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
UVOptions_.stdio = UVOptionsStdIO_.data();
// -- Spawn process
int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
if (uvErrorCode != 0) {
Result()->ErrorMessage =
getUVError("libuv process spawn failed ", uvErrorCode);
}
}
// -- 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() = default;
cmQtAutoGenerator::~cmQtAutoGenerator() = default;
-102
View File
@@ -7,14 +7,8 @@
#include "cmFilePathChecksum.h"
#include "cmQtAutoGen.h"
#include "cmUVHandlePtr.h"
#include "cm_uv.h"
#include <array>
#include <functional>
#include <mutex>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
@@ -137,102 +131,6 @@ public:
cmFilePathChecksum FilePathChecksum_;
};
/// @brief Return value and output of an external process
struct ProcessResultT
{
void reset();
bool error() const
{
return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
}
std::int64_t ExitStatus = 0;
int TermSignal = 0;
std::string StdOut;
std::string StdErr;
std::string ErrorMessage;
};
/// @brief External process management class
struct ReadOnlyProcessT
{
// -- Types
/// @brief libuv pipe buffer class
class PipeT
{
public:
int init(uv_loop_t* uv_loop, ReadOnlyProcessT* process);
int startRead(std::string* target);
void reset();
// -- Libuv casts
uv_pipe_t* uv_pipe() { return UVPipe_.get(); }
uv_stream_t* uv_stream()
{
return reinterpret_cast<uv_stream_t*>(uv_pipe());
}
uv_handle_t* uv_handle()
{
return reinterpret_cast<uv_handle_t*>(uv_pipe());
}
// -- Libuv callbacks
static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf);
static void UVData(uv_stream_t* stream, ssize_t nread,
const uv_buf_t* buf);
private:
ReadOnlyProcessT* Process_ = nullptr;
std::string* Target_ = nullptr;
std::vector<char> Buffer_;
cm::uv_pipe_ptr UVPipe_;
};
/// @brief Process settings
struct SetupT
{
std::string WorkingDirectory;
std::vector<std::string> Command;
ProcessResultT* Result = nullptr;
bool MergedOutput = false;
};
// -- Const accessors
const SetupT& Setup() const { return Setup_; }
ProcessResultT* Result() const { return Setup_.Result; }
bool IsStarted() const { return IsStarted_; }
bool IsFinished() const { return IsFinished_; }
// -- Runtime
void setup(ProcessResultT* result, bool mergedOutput,
std::vector<std::string> const& command,
std::string const& workingDirectory = std::string());
bool start(uv_loop_t* uv_loop, std::function<void()>&& finishedCallback);
private:
// -- Friends
friend class PipeT;
// -- Libuv callbacks
static void UVExit(uv_process_t* handle, int64_t exitStatus,
int termSignal);
void UVTryFinish();
// -- Setup
SetupT Setup_;
// -- Runtime
bool IsStarted_ = false;
bool IsFinished_ = false;
std::function<void()> FinishedCallback_;
std::vector<const char*> CommandPtr_;
std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
uv_process_options_t UVOptions_;
cm::uv_process_ptr UVProcess_;
PipeT UVPipeOut_;
PipeT UVPipeErr_;
};
public:
// -- Constructors
cmQtAutoGenerator();
File diff suppressed because it is too large Load Diff
@@ -1,26 +1,22 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmQtAutoGeneratorMocUic_h
#define cmQtAutoGeneratorMocUic_h
#ifndef cmQtAutoMocUic_h
#define cmQtAutoMocUic_h
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmQtAutoGen.h"
#include "cmQtAutoGenerator.h"
#include "cmUVHandlePtr.h"
#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
#include "cm_uv.h"
#include "cmWorkerPool.h"
#include "cmsys/RegularExpression.hxx"
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <array>
#include <atomic>
#include <map>
#include <memory> // IWYU pragma: keep
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -28,18 +24,18 @@
class cmMakefile;
// @brief AUTOMOC and AUTOUIC generator
class cmQtAutoGeneratorMocUic : public cmQtAutoGenerator
class cmQtAutoMocUic : public cmQtAutoGenerator
{
public:
cmQtAutoGeneratorMocUic();
~cmQtAutoGeneratorMocUic() override;
cmQtAutoMocUic();
~cmQtAutoMocUic() override;
cmQtAutoGeneratorMocUic(cmQtAutoGeneratorMocUic const&) = delete;
cmQtAutoGeneratorMocUic& operator=(cmQtAutoGeneratorMocUic const&) = delete;
cmQtAutoMocUic(cmQtAutoMocUic const&) = delete;
cmQtAutoMocUic& operator=(cmQtAutoMocUic const&) = delete;
public:
// -- Types
class WorkerT;
typedef std::multimap<std::string, std::array<std::string, 2>> IncludesMap;
/// @brief Search key plus regular expression pair
///
@@ -173,31 +169,71 @@ public:
cmsys::RegularExpression RegExpInclude;
};
/// @brief Abstract job class for threaded processing
/// @brief Abstract job class for concurrent job processing
///
class JobT
class JobT : public cmWorkerPool::JobT
{
public:
JobT() = default;
virtual ~JobT() = default;
protected:
/**
* @brief Protected default constructor
*/
JobT(bool fence = false)
: cmWorkerPool::JobT(fence)
{
}
JobT(JobT const&) = delete;
JobT& operator=(JobT const&) = delete;
//! Get the generator. Only valid during Process() call!
cmQtAutoMocUic* Gen() const
{
return static_cast<cmQtAutoMocUic*>(UserData());
};
// -- Abstract processing interface
virtual void Process(WorkerT& wrk) = 0;
//! Get the file system interface. Only valid during Process() call!
FileSystem& FileSys() { return Gen()->FileSys(); }
//! Get the logger. Only valid during Process() call!
Logger& Log() { return Gen()->Log(); }
// -- Error logging with automatic abort
void LogError(GenT genType, std::string const& message) const;
void LogFileError(GenT genType, std::string const& filename,
std::string const& message) const;
void LogCommandError(GenT genType, std::string const& message,
std::vector<std::string> const& command,
std::string const& output) const;
/**
* @brief Run an external process. Use only during Process() call!
*/
bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command);
};
// Job management types
typedef std::unique_ptr<JobT> JobHandleT;
typedef std::deque<JobHandleT> JobQueueT;
/// @brief Fence job utility class
///
class JobFenceT : public JobT
{
public:
JobFenceT()
: JobT(true)
{
}
void Process() override{};
};
/// @brief Parse source job
/// @brief Generate moc_predefs.h
///
class JobMocPredefsT : public JobT
{
private:
void Process() override;
};
/// @brief Parses a source file
///
class JobParseT : public JobT
{
public:
JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false)
JobParseT(std::string fileName, bool moc, bool uic, bool header = false)
: FileName(std::move(fileName))
, AutoMoc(moc)
, AutoUic(uic)
@@ -213,18 +249,15 @@ public:
std::string FileBase;
};
void Process(WorkerT& wrk) override;
bool ParseMocSource(WorkerT& wrk, MetaT const& meta);
bool ParseMocHeader(WorkerT& wrk, MetaT const& meta);
std::string MocStringHeaders(WorkerT& wrk,
std::string const& fileBase) const;
std::string MocFindIncludedHeader(WorkerT& wrk,
std::string const& includerDir,
void Process() override;
bool ParseMocSource(MetaT const& meta);
bool ParseMocHeader(MetaT const& meta);
std::string MocStringHeaders(std::string const& fileBase) const;
std::string MocFindIncludedHeader(std::string const& includerDir,
std::string const& includeBase);
bool ParseUic(WorkerT& wrk, MetaT const& meta);
bool ParseUicInclude(WorkerT& wrk, MetaT const& meta,
std::string&& includeString);
std::string UicFindIncludedFile(WorkerT& wrk, MetaT const& meta,
bool ParseUic(MetaT const& meta);
bool ParseUicInclude(MetaT const& meta, std::string&& includeString);
std::string UicFindIncludedFile(MetaT const& meta,
std::string const& includeString);
private:
@@ -234,12 +267,20 @@ public:
bool Header = false;
};
/// @brief Generate moc_predefs
/// @brief Generates additional jobs after all files have been parsed
///
class JobMocPredefsT : public JobT
class JobPostParseT : public JobFenceT
{
private:
void Process(WorkerT& wrk) override;
void Process() override;
};
/// @brief Generate mocs_compilation.cpp
///
class JobMocsCompilationT : public JobFenceT
{
private:
void Process() override;
};
/// @brief Moc a file job
@@ -247,20 +288,20 @@ public:
class JobMocT : public JobT
{
public:
JobMocT(std::string&& sourceFile, std::string includerFile,
std::string&& includeString)
JobMocT(std::string sourceFile, std::string includerFile,
std::string includeString)
: SourceFile(std::move(sourceFile))
, IncluderFile(std::move(includerFile))
, IncludeString(std::move(includeString))
{
}
void FindDependencies(WorkerT& wrk, std::string const& content);
void FindDependencies(std::string const& content);
private:
void Process(WorkerT& wrk) override;
bool UpdateRequired(WorkerT& wrk);
void GenerateMoc(WorkerT& wrk);
void Process() override;
bool UpdateRequired();
void GenerateMoc();
public:
std::string SourceFile;
@@ -276,8 +317,8 @@ public:
class JobUicT : public JobT
{
public:
JobUicT(std::string&& sourceFile, std::string includerFile,
std::string&& includeString)
JobUicT(std::string sourceFile, std::string includerFile,
std::string includeString)
: SourceFile(std::move(sourceFile))
, IncluderFile(std::move(includerFile))
, IncludeString(std::move(includeString))
@@ -285,9 +326,9 @@ public:
}
private:
void Process(WorkerT& wrk) override;
bool UpdateRequired(WorkerT& wrk);
void GenerateUic(WorkerT& wrk);
void Process() override;
bool UpdateRequired();
void GenerateUic();
public:
std::string SourceFile;
@@ -296,80 +337,12 @@ public:
std::string BuildFile;
};
/// @brief Worker Thread
/// @brief The last job
///
class WorkerT
class JobFinishT : public JobFenceT
{
public:
WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop);
~WorkerT();
WorkerT(WorkerT const&) = delete;
WorkerT& operator=(WorkerT const&) = delete;
// -- Const accessors
cmQtAutoGeneratorMocUic& Gen() const { return *Gen_; }
Logger& Log() const { return Gen_->Log(); }
FileSystem& FileSys() const { return Gen_->FileSys(); }
const BaseSettingsT& Base() const { return Gen_->Base(); }
const MocSettingsT& Moc() const { return Gen_->Moc(); }
const UicSettingsT& Uic() const { return Gen_->Uic(); }
// -- Log info
void LogInfo(GenT genType, std::string const& message) const;
// -- Log warning
void LogWarning(GenT genType, std::string const& message) const;
void LogFileWarning(GenT genType, std::string const& filename,
std::string const& message) const;
// -- Log error
void LogError(GenT genType, std::string const& message) const;
void LogFileError(GenT genType, std::string const& filename,
std::string const& message) const;
void LogCommandError(GenT genType, std::string const& message,
std::vector<std::string> const& command,
std::string const& output) const;
// -- External processes
/// @brief Verbose logging version
bool RunProcess(GenT genType, ProcessResultT& result,
std::vector<std::string> const& command);
private:
/// @brief Thread main loop
void Loop();
// -- Libuv callbacks
static void UVProcessStart(uv_async_t* handle);
void UVProcessFinished();
private:
// -- Generator
cmQtAutoGeneratorMocUic* Gen_;
// -- Job handle
JobHandleT JobHandle_;
// -- Process management
std::mutex ProcessMutex_;
cm::uv_async_ptr ProcessRequest_;
std::condition_variable ProcessCondition_;
std::unique_ptr<ReadOnlyProcessT> Process_;
// -- System thread
std::thread Thread_;
};
/// @brief Processing stage
enum class StageT
{
SETTINGS_READ,
CREATE_DIRECTORIES,
PARSE_SOURCES,
PARSE_HEADERS,
MOC_PREDEFS,
MOC_PROCESS,
MOCS_COMPILATION,
UIC_PROCESS,
SETTINGS_WRITE,
FINISH,
END
void Process() override;
};
// -- Const settings interface
@@ -377,41 +350,39 @@ public:
const MocSettingsT& Moc() const { return this->Moc_; }
const UicSettingsT& Uic() const { return this->Uic_; }
// -- Worker thread interface
void WorkerSwapJob(JobHandleT& jobHandle);
// -- Parallel job processing interface
void ParallelRegisterJobError();
bool ParallelJobPushMoc(JobHandleT& jobHandle);
bool ParallelJobPushUic(JobHandleT& jobHandle);
bool ParallelMocIncluded(std::string const& sourceFile);
cmWorkerPool& WorkerPool() { return WorkerPool_; }
void AbortError() { Abort(true); }
void AbortSuccess() { Abort(false); }
bool ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle);
bool ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle);
// -- Mocs compilation include file updated flag
void ParallelMocAutoUpdated() { MocAutoFileUpdated_.store(true); }
bool MocAutoFileUpdated() const { return MocAutoFileUpdated_.load(); }
// -- Mocs compilation file register
std::string ParallelMocAutoRegister(std::string const& baseName);
void ParallelMocAutoUpdated();
bool ParallelMocIncluded(std::string const& sourceFile);
std::set<std::string> const& MocAutoFiles() const
{
return this->MocAutoFiles_;
}
private:
// -- Utility accessors
Logger& Log() { return Logger_; }
FileSystem& FileSys() { return FileSys_; }
// -- libuv loop accessors
uv_loop_t* UVLoop() { return UVLoop_.get(); }
cm::uv_async_ptr& UVRequest() { return UVRequest_; }
// -- Abstract processing interface
bool Init(cmMakefile* makefile) override;
bool Process() override;
// -- Process stage
static void UVPollStage(uv_async_t* handle);
void PollStage();
void SetStage(StageT stage);
// -- Settings file
void SettingsFileRead();
void SettingsFileWrite();
bool SettingsFileWrite();
// -- Thread processing
bool ThreadsStartJobs(JobQueueT& queue);
bool ThreadsJobsDone();
void ThreadsStop();
void RegisterJobError();
void Abort(bool error);
// -- Generation
void CreateDirectories();
void MocGenerateCompilation();
bool CreateDirectories();
private:
// -- Utility
@@ -421,39 +392,22 @@ private:
BaseSettingsT Base_;
MocSettingsT Moc_;
UicSettingsT Uic_;
// -- libuv loop
#ifdef CMAKE_UV_SIGNAL_HACK
std::unique_ptr<cmUVSignalHackRAII> UVHackRAII_;
#endif
std::unique_ptr<uv_loop_t> UVLoop_;
cm::uv_async_ptr UVRequest_;
StageT Stage_ = StageT::SETTINGS_READ;
// -- Job queues
std::mutex JobsMutex_;
struct
{
JobQueueT Sources;
JobQueueT Headers;
JobQueueT MocPredefs;
JobQueueT Moc;
JobQueueT Uic;
} JobQueues_;
JobQueueT JobQueue_;
std::size_t volatile JobsRemain_ = 0;
bool volatile JobError_ = false;
bool volatile JobThreadsAbort_ = false;
std::condition_variable JobsConditionRead_;
// -- Moc meta
std::set<std::string> MocIncludedStrings_;
std::mutex MocMetaMutex_;
std::set<std::string> MocIncludedFiles_;
IncludesMap MocIncludes_;
std::set<std::string> MocAutoFiles_;
bool volatile MocAutoFileUpdated_ = false;
std::atomic<bool> MocAutoFileUpdated_ = ATOMIC_VAR_INIT(false);
// -- Uic meta
std::mutex UicMetaMutex_;
IncludesMap UicIncludes_;
// -- Settings file
std::string SettingsFile_;
std::string SettingsStringMoc_;
std::string SettingsStringUic_;
// -- Threads and loops
std::vector<std::unique_ptr<WorkerT>> Workers_;
// -- Thread pool and job queue
std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
cmWorkerPool WorkerPool_;
};
#endif
+770
View File
@@ -0,0 +1,770 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmWorkerPool.h"
#include "cmRange.h"
#include "cmUVHandlePtr.h"
#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
#include "cm_uv.h"
#include <algorithm>
#include <array>
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <stddef.h>
#include <thread>
/**
* @brief libuv pipe buffer class
*/
class cmUVPipeBuffer
{
public:
typedef cmRange<char const*> DataRange;
typedef std::function<void(DataRange)> DataFunction;
/// On error the ssize_t argument is a non zero libuv error code
typedef std::function<void(ssize_t)> EndFunction;
public:
/**
* Reset to construction state
*/
void reset();
/**
* Initializes uv_pipe(), uv_stream() and uv_handle()
* @return true on success
*/
bool init(uv_loop_t* uv_loop);
/**
* Start reading
* @return true on success
*/
bool startRead(DataFunction dataFunction, EndFunction endFunction);
//! libuv pipe
uv_pipe_t* uv_pipe() const { return UVPipe_.get(); }
//! uv_pipe() casted to libuv stream
uv_stream_t* uv_stream() const { return static_cast<uv_stream_t*>(UVPipe_); }
//! uv_pipe() casted to libuv handle
uv_handle_t* uv_handle() { return static_cast<uv_handle_t*>(UVPipe_); }
private:
// -- Libuv callbacks
static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf);
static void UVData(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
private:
cm::uv_pipe_ptr UVPipe_;
std::vector<char> Buffer_;
DataFunction DataFunction_;
EndFunction EndFunction_;
};
void cmUVPipeBuffer::reset()
{
if (UVPipe_.get() != nullptr) {
EndFunction_ = nullptr;
DataFunction_ = nullptr;
Buffer_.clear();
Buffer_.shrink_to_fit();
UVPipe_.reset();
}
}
bool cmUVPipeBuffer::init(uv_loop_t* uv_loop)
{
reset();
if (uv_loop == nullptr) {
return false;
}
int ret = UVPipe_.init(*uv_loop, 0, this);
return (ret == 0);
}
bool cmUVPipeBuffer::startRead(DataFunction dataFunction,
EndFunction endFunction)
{
if (UVPipe_.get() == nullptr) {
return false;
}
if (!dataFunction || !endFunction) {
return false;
}
DataFunction_ = std::move(dataFunction);
EndFunction_ = std::move(endFunction);
int ret = uv_read_start(uv_stream(), &cmUVPipeBuffer::UVAlloc,
&cmUVPipeBuffer::UVData);
return (ret == 0);
}
void cmUVPipeBuffer::UVAlloc(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf)
{
auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(handle->data);
pipe.Buffer_.resize(suggestedSize);
buf->base = pipe.Buffer_.data();
buf->len = static_cast<unsigned long>(pipe.Buffer_.size());
}
void cmUVPipeBuffer::UVData(uv_stream_t* stream, ssize_t nread,
const uv_buf_t* buf)
{
auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(stream->data);
if (nread > 0) {
if (buf->base != nullptr) {
// Call data function
pipe.DataFunction_(DataRange(buf->base, buf->base + nread));
}
} else if (nread < 0) {
// Save the end function on the stack before resetting the pipe
EndFunction efunc;
efunc.swap(pipe.EndFunction_);
// Reset pipe before calling the end function
pipe.reset();
// Call end function
efunc((nread == UV_EOF) ? 0 : nread);
}
}
/**
* @brief External process management class
*/
class cmUVReadOnlyProcess
{
public:
// -- Types
//! @brief Process settings
struct SetupT
{
std::string WorkingDirectory;
std::vector<std::string> Command;
cmWorkerPool::ProcessResultT* Result = nullptr;
bool MergedOutput = false;
};
public:
// -- Const accessors
SetupT const& Setup() const { return Setup_; }
cmWorkerPool::ProcessResultT* Result() const { return Setup_.Result; }
bool IsStarted() const { return IsStarted_; }
bool IsFinished() const { return IsFinished_; }
// -- Runtime
void setup(cmWorkerPool::ProcessResultT* result, bool mergedOutput,
std::vector<std::string> const& command,
std::string const& workingDirectory = std::string());
bool start(uv_loop_t* uv_loop, std::function<void()> finishedCallback);
private:
// -- Libuv callbacks
static void UVExit(uv_process_t* handle, int64_t exitStatus, int termSignal);
void UVPipeOutData(cmUVPipeBuffer::DataRange data);
void UVPipeOutEnd(ssize_t error);
void UVPipeErrData(cmUVPipeBuffer::DataRange data);
void UVPipeErrEnd(ssize_t error);
void UVTryFinish();
private:
// -- Setup
SetupT Setup_;
// -- Runtime
bool IsStarted_ = false;
bool IsFinished_ = false;
std::function<void()> FinishedCallback_;
std::vector<const char*> CommandPtr_;
std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
uv_process_options_t UVOptions_;
cm::uv_process_ptr UVProcess_;
cmUVPipeBuffer UVPipeOut_;
cmUVPipeBuffer UVPipeErr_;
};
void cmUVReadOnlyProcess::setup(cmWorkerPool::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 cmUVReadOnlyProcess::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)) {
Result()->ErrorMessage = "libuv stdout pipe initialization failed";
}
}
if (!Result()->error()) {
if (!UVPipeErr_.init(uv_loop)) {
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 = &cmUVReadOnlyProcess::UVExit;
UVOptions_.file = CommandPtr_[0];
UVOptions_.args = const_cast<char**>(CommandPtr_.data());
UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
UVOptions_.stdio = UVOptionsStdIO_.data();
// -- Spawn process
int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
if (uvErrorCode != 0) {
Result()->ErrorMessage = "libuv process spawn failed";
if (const char* uvErr = uv_strerror(uvErrorCode)) {
Result()->ErrorMessage += ": ";
Result()->ErrorMessage += uvErr;
}
}
}
// -- Start reading from stdio streams
if (!Result()->error()) {
if (!UVPipeOut_.startRead(
[this](cmUVPipeBuffer::DataRange range) {
this->UVPipeOutData(range);
},
[this](ssize_t error) { this->UVPipeOutEnd(error); })) {
Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
}
}
if (!Result()->error()) {
if (!UVPipeErr_.startRead(
[this](cmUVPipeBuffer::DataRange range) {
this->UVPipeErrData(range);
},
[this](ssize_t error) { this->UVPipeErrEnd(error); })) {
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 cmUVReadOnlyProcess::UVExit(uv_process_t* handle, int64_t exitStatus,
int termSignal)
{
auto& proc = *reinterpret_cast<cmUVReadOnlyProcess*>(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
proc.UVProcess_.reset();
// Try finish
proc.UVTryFinish();
}
}
void cmUVReadOnlyProcess::UVPipeOutData(cmUVPipeBuffer::DataRange data)
{
Result()->StdOut.append(data.begin(), data.end());
}
void cmUVReadOnlyProcess::UVPipeOutEnd(ssize_t error)
{
// Process pipe error
if ((error != 0) && !Result()->error()) {
Result()->ErrorMessage =
"Reading from stdout pipe failed with libuv error code ";
Result()->ErrorMessage += std::to_string(error);
}
// Try finish
UVTryFinish();
}
void cmUVReadOnlyProcess::UVPipeErrData(cmUVPipeBuffer::DataRange data)
{
std::string* str =
Setup_.MergedOutput ? &Result()->StdOut : &Result()->StdErr;
str->append(data.begin(), data.end());
}
void cmUVReadOnlyProcess::UVPipeErrEnd(ssize_t error)
{
// Process pipe error
if ((error != 0) && !Result()->error()) {
Result()->ErrorMessage =
"Reading from stderr pipe failed with libuv error code ";
Result()->ErrorMessage += std::to_string(error);
}
// Try finish
UVTryFinish();
}
void cmUVReadOnlyProcess::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) || (UVPipeOut_.uv_pipe() != nullptr) ||
(UVPipeErr_.uv_pipe() != nullptr)) {
return;
}
IsFinished_ = true;
FinishedCallback_();
}
/**
* @brief Private worker pool internals
*/
class cmWorkerPoolInternal
{
public:
// -- Types
/**
* @brief Worker thread
*/
class WorkerT
{
public:
WorkerT(unsigned int index);
~WorkerT();
WorkerT(WorkerT const&) = delete;
WorkerT& operator=(WorkerT const&) = delete;
/**
* Start the thread
*/
void Start(cmWorkerPoolInternal* internal);
/**
* @brief Run an external process
*/
bool RunProcess(cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command,
std::string const& workingDirectory);
// -- Accessors
unsigned int Index() const { return Index_; }
cmWorkerPool::JobHandleT& JobHandle() { return JobHandle_; }
private:
// -- Libuv callbacks
static void UVProcessStart(uv_async_t* handle);
void UVProcessFinished();
private:
//! @brief Job handle
cmWorkerPool::JobHandleT JobHandle_;
//! @brief Worker index
unsigned int Index_;
// -- Process management
struct
{
std::mutex Mutex;
cm::uv_async_ptr Request;
std::condition_variable Condition;
std::unique_ptr<cmUVReadOnlyProcess> ROP;
} Proc_;
// -- System thread
std::thread Thread_;
};
public:
// -- Constructors
cmWorkerPoolInternal(cmWorkerPool* pool);
~cmWorkerPoolInternal();
/**
* @brief Runs the libuv loop
*/
bool Process();
/**
* @brief Clear queue and abort threads
*/
void Abort();
/**
* @brief Push a job to the queue and notify a worker
*/
bool PushJob(cmWorkerPool::JobHandleT&& jobHandle);
/**
* @brief Worker thread main loop method
*/
void Work(WorkerT* worker);
// -- Request slots
static void UVSlotBegin(uv_async_t* handle);
static void UVSlotEnd(uv_async_t* handle);
public:
// -- UV loop
#ifdef CMAKE_UV_SIGNAL_HACK
std::unique_ptr<cmUVSignalHackRAII> UVHackRAII;
#endif
std::unique_ptr<uv_loop_t> UVLoop;
cm::uv_async_ptr UVRequestBegin;
cm::uv_async_ptr UVRequestEnd;
// -- Thread pool and job queue
std::mutex Mutex;
bool Aborting = false;
bool FenceProcessing = false;
unsigned int WorkersRunning = 0;
unsigned int WorkersIdle = 0;
unsigned int JobsProcessing = 0;
std::deque<cmWorkerPool::JobHandleT> Queue;
std::condition_variable Condition;
std::vector<std::unique_ptr<WorkerT>> Workers;
// -- References
cmWorkerPool* Pool = nullptr;
};
cmWorkerPoolInternal::WorkerT::WorkerT(unsigned int index)
: Index_(index)
{
}
cmWorkerPoolInternal::WorkerT::~WorkerT()
{
if (Thread_.joinable()) {
Thread_.join();
}
}
void cmWorkerPoolInternal::WorkerT::Start(cmWorkerPoolInternal* internal)
{
Proc_.Request.init(*(internal->UVLoop), &WorkerT::UVProcessStart, this);
Thread_ = std::thread(&cmWorkerPoolInternal::Work, internal, this);
}
bool cmWorkerPoolInternal::WorkerT::RunProcess(
cmWorkerPool::ProcessResultT& result,
std::vector<std::string> const& command, std::string const& workingDirectory)
{
if (command.empty()) {
return false;
}
// Create process instance
{
std::lock_guard<std::mutex> lock(Proc_.Mutex);
Proc_.ROP = cm::make_unique<cmUVReadOnlyProcess>();
Proc_.ROP->setup(&result, true, command, workingDirectory);
}
// Send asynchronous process start request to libuv loop
Proc_.Request.send();
// Wait until the process has been finished and destroyed
{
std::unique_lock<std::mutex> ulock(Proc_.Mutex);
while (Proc_.ROP) {
Proc_.Condition.wait(ulock);
}
}
return !result.error();
}
void cmWorkerPoolInternal::WorkerT::UVProcessStart(uv_async_t* handle)
{
auto* wrk = reinterpret_cast<WorkerT*>(handle->data);
bool startFailed = false;
{
auto& Proc = wrk->Proc_;
std::lock_guard<std::mutex> lock(Proc.Mutex);
if (Proc.ROP && !Proc.ROP->IsStarted()) {
startFailed =
!Proc.ROP->start(handle->loop, [wrk] { wrk->UVProcessFinished(); });
}
}
// Clean up if starting of the process failed
if (startFailed) {
wrk->UVProcessFinished();
}
}
void cmWorkerPoolInternal::WorkerT::UVProcessFinished()
{
{
std::lock_guard<std::mutex> lock(Proc_.Mutex);
if (Proc_.ROP && (Proc_.ROP->IsFinished() || !Proc_.ROP->IsStarted())) {
Proc_.ROP.reset();
}
}
// Notify idling thread
Proc_.Condition.notify_one();
}
void cmWorkerPool::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();
}
}
cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool)
: Pool(pool)
{
// 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.get());
}
cmWorkerPoolInternal::~cmWorkerPoolInternal()
{
uv_loop_close(UVLoop.get());
}
bool cmWorkerPoolInternal::Process()
{
// Reset state
Aborting = false;
// Initialize libuv asynchronous request
UVRequestBegin.init(*UVLoop, &cmWorkerPoolInternal::UVSlotBegin, this);
UVRequestEnd.init(*UVLoop, &cmWorkerPoolInternal::UVSlotEnd, this);
// Send begin request
UVRequestBegin.send();
// Run libuv loop
return (uv_run(UVLoop.get(), UV_RUN_DEFAULT) == 0);
}
void cmWorkerPoolInternal::Abort()
{
bool firstCall = false;
// Clear all jobs and set abort flag
{
std::lock_guard<std::mutex> guard(Mutex);
if (!Aborting) {
// Register abort and clear queue
Aborting = true;
Queue.clear();
firstCall = true;
}
}
if (firstCall) {
// Wake threads
Condition.notify_all();
}
}
inline bool cmWorkerPoolInternal::PushJob(cmWorkerPool::JobHandleT&& jobHandle)
{
std::lock_guard<std::mutex> guard(Mutex);
if (Aborting) {
return false;
}
// Append the job to the queue
Queue.emplace_back(std::move(jobHandle));
// Notify an idle worker if there's one
if (WorkersIdle != 0) {
Condition.notify_one();
}
return true;
}
void cmWorkerPoolInternal::UVSlotBegin(uv_async_t* handle)
{
auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
// Create worker threads
{
unsigned int const num = gint.Pool->ThreadCount();
// Create workers
gint.Workers.reserve(num);
for (unsigned int ii = 0; ii != num; ++ii) {
gint.Workers.emplace_back(cm::make_unique<WorkerT>(ii));
}
// Start workers
for (auto& wrk : gint.Workers) {
wrk->Start(&gint);
}
}
// Destroy begin request
gint.UVRequestBegin.reset();
}
void cmWorkerPoolInternal::UVSlotEnd(uv_async_t* handle)
{
auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
// Join and destroy worker threads
gint.Workers.clear();
// Destroy end request
gint.UVRequestEnd.reset();
}
void cmWorkerPoolInternal::Work(WorkerT* worker)
{
std::unique_lock<std::mutex> uLock(Mutex);
// Increment running workers count
++WorkersRunning;
// Enter worker main loop
while (true) {
// Abort on request
if (Aborting) {
break;
}
// Wait for new jobs
if (Queue.empty()) {
++WorkersIdle;
Condition.wait(uLock);
--WorkersIdle;
continue;
}
// Check for fence jobs
if (FenceProcessing || Queue.front()->IsFence()) {
if (JobsProcessing != 0) {
Condition.wait(uLock);
continue;
}
// No jobs get processed. Set the fence job processing flag.
FenceProcessing = true;
}
// Pop next job from queue
worker->JobHandle() = std::move(Queue.front());
Queue.pop_front();
// Unlocked scope for job processing
++JobsProcessing;
{
uLock.unlock();
worker->JobHandle()->Work(Pool, worker->Index()); // Process job
worker->JobHandle().reset(); // Destroy job
uLock.lock();
}
--JobsProcessing;
// Was this a fence job?
if (FenceProcessing) {
FenceProcessing = false;
Condition.notify_all();
}
}
// Decrement running workers count
if (--WorkersRunning == 0) {
// Last worker thread about to finish. Send libuv event.
UVRequestEnd.send();
}
}
cmWorkerPool::JobT::~JobT() = default;
bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result,
std::vector<std::string> const& command,
std::string const& workingDirectory)
{
// Get worker by index
auto* wrk = Pool_->Int_->Workers.at(WorkerIndex_).get();
return wrk->RunProcess(result, command, workingDirectory);
}
cmWorkerPool::cmWorkerPool()
: Int_(cm::make_unique<cmWorkerPoolInternal>(this))
{
}
cmWorkerPool::~cmWorkerPool() = default;
bool cmWorkerPool::Process(unsigned int threadCount, void* userData)
{
// Setup user data
UserData_ = userData;
ThreadCount_ = (threadCount > 0) ? threadCount : 1u;
// Run libuv loop
bool success = Int_->Process();
// Clear user data
UserData_ = nullptr;
ThreadCount_ = 0;
return success;
}
bool cmWorkerPool::PushJob(JobHandleT&& jobHandle)
{
return Int_->PushJob(std::move(jobHandle));
}
void cmWorkerPool::Abort()
{
Int_->Abort();
}
+219
View File
@@ -0,0 +1,219 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmWorkerPool_h
#define cmWorkerPool_h
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmAlgorithms.h" // IWYU pragma: keep
#include <memory> // IWYU pragma: keep
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
// -- Types
class cmWorkerPoolInternal;
/** @class cmWorkerPool
* @brief Thread pool with job queue
*/
class cmWorkerPool
{
public:
/**
* Return value and output of an external process.
*/
struct ProcessResultT
{
void reset();
bool error() const
{
return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
}
std::int64_t ExitStatus = 0;
int TermSignal = 0;
std::string StdOut;
std::string StdErr;
std::string ErrorMessage;
};
/**
* Abstract job class for concurrent job processing.
*/
class JobT
{
public:
JobT(JobT const&) = delete;
JobT& operator=(JobT const&) = delete;
/**
* @brief Virtual destructor.
*/
virtual ~JobT();
/**
* @brief Fence job flag
*
* Fence jobs require that:
* - all jobs before in the queue have been processed
* - no jobs later in the queue will be processed before this job was
* processed
*/
bool IsFence() const { return Fence_; }
protected:
/**
* @brief Protected default constructor
*/
JobT(bool fence = false)
: Fence_(fence)
{
}
/**
* Abstract processing interface that must be implement in derived classes.
*/
virtual void Process() = 0;
/**
* Get the worker pool.
* Only valid during the JobT::Process() call!
*/
cmWorkerPool* Pool() const { return Pool_; }
/**
* Get the user data.
* Only valid during the JobT::Process() call!
*/
void* UserData() const { return Pool_->UserData(); };
/**
* Get the worker index.
* This is the index of the thread processing this job and is in the range
* [0..ThreadCount).
* Concurrently processing jobs will never have the same WorkerIndex().
* Only valid during the JobT::Process() call!
*/
unsigned int WorkerIndex() const { return WorkerIndex_; }
/**
* Run an external read only process.
* Use only during JobT::Process() call!
*/
bool RunProcess(ProcessResultT& result,
std::vector<std::string> const& command,
std::string const& workingDirectory);
private:
//! Needs access to Work()
friend class cmWorkerPoolInternal;
//! Worker thread entry method.
void Work(cmWorkerPool* pool, unsigned int workerIndex)
{
Pool_ = pool;
WorkerIndex_ = workerIndex;
this->Process();
}
private:
cmWorkerPool* Pool_ = nullptr;
unsigned int WorkerIndex_ = 0;
bool Fence_ = false;
};
/**
* @brief Job handle type
*/
typedef std::unique_ptr<JobT> JobHandleT;
/**
* @brief Fence job base class
*/
class JobFenceT : public JobT
{
public:
JobFenceT()
: JobT(true)
{
}
//! Does nothing
void Process() override{};
};
/**
* @brief Fence job that aborts the worker pool.
* This class is useful as the last job in the job queue.
*/
class JobEndT : JobFenceT
{
public:
//! Does nothing
void Process() override { Pool()->Abort(); }
};
public:
// -- Methods
cmWorkerPool();
~cmWorkerPool();
/**
* @brief Blocking function that starts threads to process all Jobs in
* the queue.
*
* This method blocks until a job calls the Abort() method.
* @arg threadCount Number of threads to process jobs.
* @arg userData Common user data pointer available in all Jobs.
*/
bool Process(unsigned int threadCount, void* userData = nullptr);
/**
* Number of worker threads passed to Process().
* Only valid during Process().
*/
unsigned int ThreadCount() const { return ThreadCount_; }
/**
* User data reference passed to Process().
* Only valid during Process().
*/
void* UserData() const { return UserData_; }
// -- Job processing interface
/**
* @brief Clears the job queue and aborts all worker threads.
*
* This method is thread safe and can be called from inside a job.
*/
void Abort();
/**
* @brief Push job to the queue.
*
* This method is thread safe and can be called from inside a job or before
* Process().
*/
bool PushJob(JobHandleT&& jobHandle);
/**
* @brief Push job to the queue
*
* This method is thread safe and can be called from inside a job or before
* Process().
*/
template <class T, typename... Args>
bool EmplaceJob(Args&&... args)
{
return PushJob(cm::make_unique<T>(std::forward<Args>(args)...));
}
private:
void* UserData_ = nullptr;
unsigned int ThreadCount_ = 0;
std::unique_ptr<cmWorkerPoolInternal> Int_;
};
#endif
+2 -2
View File
@@ -7,7 +7,7 @@
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmQtAutoGeneratorMocUic.h"
#include "cmQtAutoMocUic.h"
#include "cmQtAutoRcc.h"
#include "cmRange.h"
#include "cmState.h"
@@ -1018,7 +1018,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args)
#ifdef CMAKE_BUILD_WITH_CMAKE
if ((args[1] == "cmake_autogen") && (args.size() >= 4)) {
cmQtAutoGeneratorMocUic autoGen;
cmQtAutoMocUic autoGen;
std::string const& infoDir = args[2];
std::string const& config = args[3];
return autoGen.Run(infoDir, config) ? 0 : 1;
@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.10)
project(ManySources)
include("../AutogenGuiTest.cmake")
# Test AUTOMOC and AUTOUIC on many source files to stress the concurrent
# parsing and processing framework.
set(CSD ${CMAKE_CURRENT_SOURCE_DIR})
set(CBD ${CMAKE_CURRENT_BINARY_DIR})
set(SRCS main.cpp)
set(MAIN_INCLUDES "\n// Item includes\n")
set(MAIN_ITEMS "\n// Items\n")
set(NUM 24)
foreach(III RANGE 1 ${NUM})
configure_file(${CSD}/object.h.in ${CBD}/object_${III}.h)
configure_file(${CSD}/item.h.in ${CBD}/item_${III}.h)
configure_file(${CSD}/item.cpp.in ${CBD}/item_${III}.cpp)
configure_file(${CSD}/view.ui.in ${CBD}/view_${III}.ui)
configure_file(${CSD}/data.qrc.in ${CBD}/data_${III}.qrc)
list(APPEND SRCS ${CBD}/item_${III}.cpp)
list(APPEND SRCS ${CBD}/data_${III}.qrc)
string(APPEND MAIN_INCLUDES "#include \"item_${III}.h\"\n")
string(APPEND MAIN_ITEMS "Item_${III} item_${III};\n")
string(APPEND MAIN_ITEMS "item_${III}.TheSlot();\n")
endforeach()
configure_file(${CSD}/main.cpp.in ${CBD}/main.cpp)
add_executable(manySources ${SRCS} ${CBD}/main.cpp)
target_link_libraries(manySources ${QT_LIBRARIES})
set_target_properties(manySources PROPERTIES AUTOMOC 1 AUTOUIC 1 AUTORCC 1)
+7
View File
@@ -0,0 +1,7 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>object_@III@.h</file>
<file>item_@III@.h</file>
<file>item_@III@.cpp</file>
</qresource>
</RCC>
+27
View File
@@ -0,0 +1,27 @@
#include "item_@III@.h"
#include "object_@III@.h"
// AUTOUIC include
#include <ui_view_@III@.h>
class LocalObject_@III@ : public QObject
{
Q_OBJECT;
public:
LocalObject_@III@() = default;
~LocalObject_@III@() = default;
};
void Item_@III@ ::TheSlot()
{
LocalObject_@III@ localObject;
Object_@III@ obj;
obj.ObjectSlot();
Ui_View_@III@ ui_view;
}
// AUTOMOC includes
#include "item_@III@.moc"
#include "moc_item_@III@.cpp"
#include "moc_object_@III@.cpp"
+15
View File
@@ -0,0 +1,15 @@
#ifndef ITEM_@III@HPP
#define ITEM_@III@HPP
#include <QObject>
class Item_@III@ : public QObject
{
Q_OBJECT
public:
Q_SLOT
void TheSlot();
};
#endif
+7
View File
@@ -0,0 +1,7 @@
@MAIN_INCLUDES@
int main(int argv, char** args)
{
@MAIN_ITEMS@
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef OBJECT_@III@H
#define OBJECT_@III@H
#include <QObject>
class Object_@III@ : public QObject
{
Q_OBJECT
public:
Q_SLOT
void ObjectSlot(){};
};
#endif
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>View_@III@</class>
<widget class="QWidget" name="Base">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeView" name="treeView"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+1
View File
@@ -5,6 +5,7 @@ ADD_AUTOGEN_TEST(AutogenTargetDepends)
ADD_AUTOGEN_TEST(Complex QtAutogen)
ADD_AUTOGEN_TEST(GlobalAutogenTarget)
ADD_AUTOGEN_TEST(LowMinimumVersion lowMinimumVersion)
ADD_AUTOGEN_TEST(ManySources manySources)
ADD_AUTOGEN_TEST(MocOnly mocOnly)
ADD_AUTOGEN_TEST(MocOptions mocOptions)
ADD_AUTOGEN_TEST(ObjectLibrary someProgram)