QtCanpool 3.3.0
载入中...
搜索中...
未找到
qxpluginmanager.cpp
浏览该文件的文档.
1
5#include "qxpluginmanager.h"
6#include "qxplugin.h"
7#include "qxplugincontext.h"
8#include "qxpluginspec.h"
9
10#include <QtCore/QDir>
11#include <QtCore/QDirIterator>
12#include <QtCore/QFileInfo>
13#include <QtCore/QJsonObject>
14#include <QtCore/QPluginLoader>
15#include <QtCore/QQueue>
16#include <QtCore/QSet>
17
18#include <algorithm>
19#include <utility>
20
22
23namespace
24{
25
31bool isPluginFileName(const QString &name)
32{
33 return name.endsWith(QLatin1String(".so")) || name.endsWith(QLatin1String(".dll")) ||
34 name.endsWith(QLatin1String(".dylib")) || name.endsWith(QLatin1String(".bundle"));
35}
36
37const QString kIidKey = QStringLiteral("IID");
38const QString kMetaDataKey = QStringLiteral("MetaData");
39
40} // namespace
41
42class QxPluginManagerPrivate
43{
44 QX_DECLARE_PUBLIC(QxPluginManager)
45public:
47 struct Entry {
48 QxPluginSpec *spec = Q_NULLPTR;
49 QPluginLoader *loader = Q_NULLPTR; // dynamic only
50 std::function<QxPlugin *()> factory; // static only
51 bool isStatic = false;
52 };
53 QStringList pluginPaths;
54 QSet<QString> disabledPlugins;
55 QSet<QString> enabledPlugins;
56 int requiredInterfaceVersion = 1;
57 QVersionNumber hostVersion;
58 bool hostVersionSet = false;
59 QxPluginContext *context = Q_NULLPTR;
60
61 // resolved state, rebuilt every loadPlugins()
62 QList<QxPluginSpec *> orderedSpecs;
63 QHash<QString, QxPluginSpec *> specById;
64
65 // ownership
74 QObject pluginOwner;
75 QList<QxPluginSpec *> staticSpecs;
76 QList<QxPluginSpec *> dynamicSpecs;
77 QList<QPluginLoader *> dynamicLoaders;
78 QList<QxPluginManagerPrivate::Entry *> staticEntries;
79 QList<QxPluginManagerPrivate::Entry *> dynamicEntries;
80 QList<QxPluginManagerPrivate::Entry *> entries; // static + dynamic, per load
81
82 QString errorString;
83 bool hasError = false;
84
85 void gatherDynamic();
86 void resolve();
87 QString cycleChain(QxPluginSpec *start, const QList<QxPluginSpec *> &live) const;
88 QxPlugin *createInstance(QxPluginSpec *spec);
89};
90
91void QxPluginManagerPrivate::gatherDynamic()
92{
93 qDeleteAll(dynamicSpecs);
94 dynamicSpecs.clear();
95 qDeleteAll(dynamicLoaders);
96 dynamicLoaders.clear();
97 qDeleteAll(dynamicEntries);
98 dynamicEntries.clear();
99 entries.clear();
100 entries.append(staticEntries);
101
102 for (const QString &path : std::as_const(pluginPaths)) {
103 QDir dir(path);
104 if (!dir.exists())
105 continue;
106 const QFileInfoList files = dir.entryInfoList(QDir::Files);
107 for (const QFileInfo &fi : files) {
108 if (!isPluginFileName(fi.fileName()))
109 continue;
110 auto *loader = new QPluginLoader(fi.absoluteFilePath());
111 dynamicLoaders.append(loader);
112 const QJsonObject md = loader->metaData();
113 if (md.value(kIidKey).toString() != QX_PLUGIN_IID)
114 continue; // not one of ours; leave the loader, it is just skipped
115 auto *spec = new QxPluginSpec;
116 dynamicSpecs.append(spec);
117 if (!spec->read(fi.absoluteFilePath(), md.value(kMetaDataKey).toObject()))
118 spec->setState(QxPluginState::Disabled);
119 auto *entry = new QxPluginManagerPrivate::Entry{spec, loader, {}, false};
120 dynamicEntries.append(entry);
121 entries.append(entry);
122 }
123 }
124}
125
126QString QxPluginManagerPrivate::cycleChain(QxPluginSpec *start, const QList<QxPluginSpec *> &live) const
127{
128 QStringList chain;
129 QSet<QString> seen;
130 QxPluginSpec *cur = start;
131 while (cur && !seen.contains(cur->id())) {
132 seen.insert(cur->id());
133 chain.append(cur->id());
134 QxPluginSpec *next = Q_NULLPTR;
135 // Both kinds of edge count here: a cycle that closes through an optional
136 // dependency is still a cycle, and naming only half of it would report a
137 // chain the reader cannot find in the metadata.
138 QStringList edges = cur->dependencies();
139 edges += cur->optionalDependencies();
140 for (const QString &dep : std::as_const(edges)) {
141 QxPluginSpec *depSpec = specById.value(dep);
142 // live holds exactly the candidates Kahn could not order; an edge that
143 // leaves it points at a node outside the cycle, so it must not be followed.
144 // We must not also require depSpec->state() == Read here: step 5 marks each
145 // node Disabled right before calling us, and that mutation would otherwise
146 // hide the partner's edge and collapse the reported chain (e.g. "a -> b"
147 // instead of "a -> b -> a").
148 if (live.contains(depSpec)) {
149 next = depSpec;
150 break;
151 }
152 }
153 cur = next;
154 }
155 if (cur)
156 chain.append(cur->id()); // close the loop at the revisited node
157 return chain.join(QStringLiteral(" -> "));
158}
159
160void QxPluginManagerPrivate::resolve()
161{
162 Q_Q(QxPluginManager);
163 Q_UNUSED(q)
164 orderedSpecs.clear();
165 specById.clear();
166
167 // 1. id map + duplicate detection
168 for (QxPluginManagerPrivate::Entry *e : std::as_const(entries)) {
169 QxPluginSpec *s = e->spec;
170 const QString id = s->id();
171 if (specById.contains(id)) {
172 s->setState(QxPluginState::Disabled);
173 s->setError(QStringLiteral("duplicate plugin id '%1'").arg(id));
174 continue;
175 }
176 specById.insert(id, s);
177 }
178
179 // 2. base assessment: config-off, skip-by-default, version, missing dep
180 for (QxPluginManagerPrivate::Entry *e : std::as_const(entries)) {
181 QxPluginSpec *s = e->spec;
182 if (s->state() == QxPluginState::Disabled)
183 continue;
184 if (disabledPlugins.contains(s->id())) {
185 s->setState(QxPluginState::Disabled); // forced off, no error
186 continue;
187 }
188 // A plugin that is off by default only stays off until someone asks for
189 // it by name. The forced-off check above still wins, so an id in both
190 // lists stays off.
191 if (!s->isEnabledByDefault() && !enabledPlugins.contains(s->id())) {
192 s->setState(QxPluginState::Disabled); // skip, no error
193 continue;
194 }
195 if (hostVersionSet) {
196 const QVersionNumber lo = s->compatVersion();
197 const QVersionNumber hi = s->version();
198 if (hostVersion < lo || hostVersion > hi) {
199 s->setState(QxPluginState::Disabled);
200 s->setError(QStringLiteral("plugin '%1' requires host version in [%2, %3], but the host is %4")
201 .arg(s->id(), lo.toString(), hi.toString(), hostVersion.toString()));
202 continue;
203 }
204 }
205 bool missing = false;
206 for (const QString &dep : s->dependencies()) {
207 if (!specById.contains(dep)) {
208 s->setState(QxPluginState::Disabled);
209 s->setError(QStringLiteral("plugin '%1' is missing required dependency '%2'").arg(s->id(), dep));
210 missing = true;
211 break;
212 }
213 }
214 if (missing)
215 continue;
216 // still a candidate (state Read)
217 }
218
219 // 3. candidate availability: a candidate whose required dep is Disabled is not available
220 bool changed = true;
221 while (changed) {
222 changed = false;
223 for (QxPluginManagerPrivate::Entry *e : std::as_const(entries)) {
224 QxPluginSpec *s = e->spec;
225 if (s->state() != QxPluginState::Read)
226 continue;
227 for (const QString &dep : s->dependencies()) {
228 QxPluginSpec *depSpec = specById.value(dep);
229 if (depSpec && depSpec->state() == QxPluginState::Disabled) {
230 s->setState(QxPluginState::Disabled);
231 if (s->error().isEmpty())
232 s->setError(
233 QStringLiteral("plugin '%1' depends on '%2', which is not available").arg(s->id(), dep));
234 changed = true;
235 break;
236 }
237 }
238 }
239 }
240
241 // 4. Kahn topological sort over the still-live candidates
242 QList<QxPluginSpec *> live;
243 for (QxPluginManagerPrivate::Entry *e : std::as_const(entries)) {
244 QxPluginSpec *s = e->spec;
245 if (s->state() == QxPluginState::Read)
246 live.append(s);
247 }
248 const QSet<QxPluginSpec *> liveSet(live.begin(), live.end());
249
250 QHash<QString, QList<QxPluginSpec *>> dependents;
251 QHash<QString, int> inDegree;
252 for (QxPluginSpec *s : live)
253 inDegree.insert(s->id(), 0);
254 for (QxPluginSpec *s : live) {
255 for (const QString &dep : s->dependencies()) {
256 dependents[dep].append(s);
257 inDegree[s->id()]++;
258 }
259 // An optional dependency that is actually there still orders the run -
260 // which is what makes it usable at all, since a plugin can only look up
261 // what has already published itself. What it never does is block: one
262 // that is absent, switched off, or already failed is dropped here, and
263 // the dependent starts regardless. Steps 2 and 3 deliberately read only
264 // dependencies(), which is where the difference between the two lives.
265 for (const QString &dep : s->optionalDependencies()) {
266 if (!liveSet.contains(specById.value(dep)))
267 continue;
268 dependents[dep].append(s);
269 inDegree[s->id()]++;
270 }
271 }
272
273 QQueue<QxPluginSpec *> queue;
274 for (QxPluginSpec *s : live)
275 if (inDegree.value(s->id()) == 0)
276 queue.enqueue(s);
277
278 QList<QxPluginSpec *> order;
279 while (!queue.isEmpty()) {
280 QxPluginSpec *s = queue.dequeue();
281 order.append(s);
282 s->setState(QxPluginState::Resolved);
283 for (QxPluginSpec *dep : dependents.value(s->id())) {
284 inDegree[dep->id()]--;
285 if (inDegree.value(dep->id()) == 0)
286 queue.enqueue(dep);
287 }
288 }
289
290 // 5. anything left in live is in a cycle
291 if (order.size() < live.size()) {
292 for (QxPluginSpec *s : live) {
293 if (order.contains(s))
294 continue;
295 s->setState(QxPluginState::Disabled);
296 s->setError(
297 QStringLiteral("plugin '%1' is part of a circular dependency: %2").arg(s->id(), cycleChain(s, live)));
298 }
299 }
300
301 orderedSpecs = order;
302}
303
304QxPlugin *QxPluginManagerPrivate::createInstance(QxPluginSpec *spec)
305{
306 for (QxPluginManagerPrivate::Entry *e : entries) {
307 if (e->spec != spec)
308 continue;
309 if (e->isStatic) {
310 QxPlugin *instance = e->factory ? e->factory() : Q_NULLPTR;
311 // Dynamic plugins are owned by their QPluginLoader; static ones are
312 // parented to a private holder so they are cleaned up with the
313 // manager without becoming reachable through parent(). See the note
314 // on pluginOwner - this is the difference between the documented
315 // "a plugin has no handle to the manager" and a comment.
316 if (instance)
317 instance->setParent(&pluginOwner);
318 return instance;
319 }
320 if (e->loader)
321 return qobject_cast<QxPlugin *>(e->loader->instance());
322 return Q_NULLPTR;
323 }
324 return Q_NULLPTR;
325}
326
327// ---------------------------------------------------------------------------
328
329QxPluginManager::QxPluginManager(QObject *parent)
330 : QObject(parent)
331 , d_ptr(new QxPluginManagerPrivate){QX_INIT_PRIVATE(QxPluginManager)}
332
334{
335 Q_D(QxPluginManager);
336 qDeleteAll(d->staticEntries);
337 qDeleteAll(d->staticSpecs);
338 qDeleteAll(d->dynamicEntries);
339 qDeleteAll(d->dynamicSpecs);
340 qDeleteAll(d->dynamicLoaders);
341 delete d_ptr;
342}
343
344void QxPluginManager::setPluginPaths(const QStringList &paths)
345{
346 Q_D(QxPluginManager);
347 d->pluginPaths = paths;
348}
349
351{
352 Q_D(const QxPluginManager);
353 return d->pluginPaths;
354}
355
356void QxPluginManager::setDisabledPlugins(const QStringList &ids)
357{
358 Q_D(QxPluginManager);
359 d->disabledPlugins = QSet<QString>(ids.begin(), ids.end());
360}
361
363{
364 Q_D(const QxPluginManager);
365 return QStringList(d->disabledPlugins.begin(), d->disabledPlugins.end());
366}
367
368void QxPluginManager::setEnabledPlugins(const QStringList &ids)
369{
370 Q_D(QxPluginManager);
371 d->enabledPlugins = QSet<QString>(ids.begin(), ids.end());
372}
373
375{
376 Q_D(const QxPluginManager);
377 return QStringList(d->enabledPlugins.begin(), d->enabledPlugins.end());
378}
379
381{
382 Q_D(QxPluginManager);
383 d->requiredInterfaceVersion = version;
384}
385
387{
388 Q_D(const QxPluginManager);
389 return d->requiredInterfaceVersion;
390}
391
392void QxPluginManager::setHostVersion(const QVersionNumber &version)
393{
394 Q_D(QxPluginManager);
395 d->hostVersion = version;
396 d->hostVersionSet = true;
397}
398
399QVersionNumber QxPluginManager::hostVersion() const
400{
401 Q_D(const QxPluginManager);
402 return d->hostVersion;
403}
404
406{
407 Q_D(const QxPluginManager);
408 return d->hostVersionSet;
409}
410
412{
413 Q_D(QxPluginManager);
414 d->context = context;
415}
416
418{
419 Q_D(const QxPluginManager);
420 return d->context;
421}
422
423void QxPluginManager::registerStaticPlugin(const QString &key, const QJsonObject &metaData,
424 std::function<QxPlugin *()> factory)
425{
426 Q_D(QxPluginManager);
427 auto *spec = new QxPluginSpec;
428 d->staticSpecs.append(spec);
429 if (!spec->read(key, metaData))
430 spec->setState(QxPluginState::Disabled);
431 d->staticEntries.append(new QxPluginManagerPrivate::Entry{spec, Q_NULLPTR, factory, true});
432}
433
435{
436 Q_D(QxPluginManager);
437 d->gatherDynamic();
438 d->resolve();
439
440 d->errorString.clear();
441 d->hasError = false;
442
443 for (QxPluginSpec *s : std::as_const(d->orderedSpecs)) {
444 // a required dep may have failed at load/init time (late cascade)
445 bool depFailed = false;
446 for (const QString &dep : s->dependencies()) {
447 QxPluginSpec *depSpec = d->specById.value(dep);
448 if (depSpec && depSpec->state() == QxPluginState::Disabled) {
449 s->setState(QxPluginState::Disabled);
450 if (s->error().isEmpty())
451 s->setError(
452 QStringLiteral("plugin '%1' depends on '%2', which is not available").arg(s->id(), dep));
453 depFailed = true;
454 break;
455 }
456 }
457 if (depFailed) {
458 emit pluginFailed(s, s->error());
459 continue;
460 }
461 if (s->isDisabled())
462 continue; // skipped by default or by config: nothing to instantiate
463
464 QxPlugin *instance = d->createInstance(s);
465 if (!instance) {
466 s->setState(QxPluginState::Disabled);
467 s->setError(QStringLiteral("plugin '%1' could not be instantiated").arg(s->id()));
468 emit pluginFailed(s, s->error());
469 continue;
470 }
471 s->setPlugin(instance);
472 s->setState(QxPluginState::Loaded);
473
474 if (instance->interfaceVersion() < d->requiredInterfaceVersion) {
475 s->setState(QxPluginState::Disabled);
476 s->setError(QStringLiteral("plugin '%1' interface version %2 is below the required %3")
477 .arg(s->id())
478 .arg(instance->interfaceVersion())
479 .arg(d->requiredInterfaceVersion));
480 emit pluginFailed(s, s->error());
481 continue;
482 }
483
484 instance->m_context = d->context;
485 QString err;
486 if (!instance->initialize(d->context, &err)) {
487 s->setState(QxPluginState::Disabled);
488 s->setError(err.isEmpty() ? QStringLiteral("plugin '%1' returned false from initialize()").arg(s->id())
489 : err);
490 emit pluginFailed(s, s->error());
491 continue;
492 }
493 s->setState(QxPluginState::Initialized);
494 emit pluginInitialized(s);
495 }
496
497 // let initialized plugins finish setup now the whole set is up, in the order
498 // they were initialized in. Peers are reachable from here exactly as they are
499 // from initialize() - through the object pool on the context - so a plugin
500 // that offers something may well prefer to publish it here, once its own
501 // setup is complete.
502 for (QxPluginSpec *s : std::as_const(d->orderedSpecs)) {
503 if (s->state() == QxPluginState::Initialized && s->plugin())
504 s->plugin()->extensionsInitialized();
505 }
506
507 // aggregate failures
508 QStringList lines;
509 for (QxPluginManagerPrivate::Entry *e : std::as_const(d->entries)) {
510 QxPluginSpec *s = e->spec;
511 if (!s->error().isEmpty()) {
512 lines.append(QStringLiteral("%1: %2").arg(s->id(), s->error()));
513 d->hasError = true;
514 }
515 }
516 d->errorString = lines.join(QLatin1String("\n"));
517}
518
520{
521 Q_D(QxPluginManager);
522 // reverse initialization order
523 QList<QxPluginSpec *> reverse = d->orderedSpecs;
524 std::reverse(reverse.begin(), reverse.end());
525 for (QxPluginSpec *s : reverse) {
526 if (s->state() == QxPluginState::Initialized && s->plugin()) {
527 s->plugin()->shutdown();
528 s->setState(QxPluginState::Stopped);
529 }
530 }
531}
532
533QList<QxPluginSpec *> QxPluginManager::specs() const
534{
535 Q_D(const QxPluginManager);
536 return d->orderedSpecs;
537}
538
539QList<QxPluginSpec *> QxPluginManager::allSpecs() const
540{
541 Q_D(const QxPluginManager);
542 QList<QxPluginSpec *> specs;
543 specs.reserve(d->staticEntries.count() + d->dynamicEntries.count());
544 for (QxPluginManagerPrivate::Entry *e : d->staticEntries)
545 specs.append(e->spec);
546 for (QxPluginManagerPrivate::Entry *e : d->dynamicEntries)
547 specs.append(e->spec);
548 return specs;
549}
550
551QxPluginSpec *QxPluginManager::spec(const QString &id) const
552{
553 Q_D(const QxPluginManager);
554 return d->specById.value(id);
555}
556
557QxPlugin *QxPluginManager::plugin(const QString &id) const
558{
559 Q_D(const QxPluginManager);
560 QxPluginSpec *s = d->specById.value(id);
561 return s ? s->plugin() : Q_NULLPTR;
562}
563
565{
566 Q_D(const QxPluginManager);
567 return d->hasError;
568}
569
571{
572 Q_D(const QxPluginManager);
573 return d->errorString;
574}
575
The thin surface a plugin programs against.
Discovers, orders, loads and isolates QtCanpool plugins.
void loadPlugins()
Scans the paths (or the registered static plugins), resolves dependencies and loads in order.
QStringList enabledPlugins() const
QList< QxPluginSpec * > specs() const
All specs in dependency order (the order loadPlugins() would load them).
void registerStaticPlugin(const QString &key, const QJsonObject &metaData, std::function< QxPlugin *()> factory)
Static-build entry (WASM / QTC_STATIC_BUILD): register a pre-built instance factory.
void setContext(QxPluginContext *context)
The context handed to every plugin's initialize(); set before loadPlugins().
void setEnabledPlugins(const QStringList &ids)
Ids to start even though their metadata says EnabledByDefault is false.
void shutdown()
Calls shutdown() on every initialized plugin, in reverse initialization order.
QxPlugin * plugin(const QString &id) const
void setHostVersion(const QVersionNumber &version)
The host version plugins must be compatible with; accept-all until set.
void setPluginPaths(const QStringList &paths)
void setRequiredInterfaceVersion(int version)
Minimum interface version a plugin must report to be accepted (default 1).
QVersionNumber hostVersion() const
bool hasError() const
True when at least one plugin failed (load or initialize).
QList< QxPluginSpec * > allSpecs() const
Every plugin the manager knows about, in discovery order (the static ones first, then the dynamic one...
QStringList pluginPaths() const
QStringList disabledPlugins() const
QxPluginSpec * spec(const QString &id) const
void pluginFailed(QxPluginSpec *spec, const QString &reason)
A plugin was set aside; reason is the same string spec()->error() holds.
void setDisabledPlugins(const QStringList &ids)
Ids that never start, whatever their metadata says.
void pluginInitialized(QxPluginSpec *spec)
A plugin reached the Initialized state.
QString errorString() const
Every failure joined once, each prefixed by its plugin id.
QxPluginContext * context() const
A read-only view of one plugin's metadata, plus the runtime state the manager attaches to it.
QString error() const
Why the plugin is not running; empty when it runs or is only skipped by default.
bool read(const QString &filePath, const QJsonObject &metaData)
Fills the spec from metadata; returns false (and records error()) on a malformed document.
QxPluginState state() const
QxPlugin * plugin() const
The live instance, or null until Loaded.
QString id() const
Stable plugin id; also the base name of its library and metadata file.
QxPluginContext * m_context
Set by the manager before initialize(); also reachable through context().
Definition qxplugin.h:99
virtual int interfaceVersion() const
The plugin interface version this build targets.
Definition qxplugin.cpp:27
virtual bool initialize(QxPluginContext *context, QString *errorString)=0
Called after every required dependency has been initialized.
Copyright (C) 2026 maminjie canpool@163.com SPDX-License-Identifier: MulanPSL-2.0
#define QX_INIT_PRIVATE(Class)
#define QX_DECLARE_PUBLIC(Class)
#define Q_NULLPTR
Copyleft (C) 2025 maminjie canpool@163.com SPDX-License-Identifier: MulanPSL-2.0
#define QX_PLUGIN_BEGIN_NAMESPACE
#define QX_PLUGIN_END_NAMESPACE
#define QX_PLUGIN_IID
const char * name
Definition qxtheme.cpp:25
const char * id
Definition qxtheme.cpp:24