aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/corelib/CMakeLists.txt9
-rw-r--r--src/lib/corelib/api/internaljobs.cpp8
-rw-r--r--src/lib/corelib/api/jobs.cpp2
-rw-r--r--src/lib/corelib/api/project.cpp26
-rw-r--r--src/lib/corelib/api/project.h1
-rw-r--r--src/lib/corelib/api/projectdata.cpp79
-rw-r--r--src/lib/corelib/api/projectdata.h10
-rw-r--r--src/lib/corelib/api/projectfileupdater.cpp4
-rw-r--r--src/lib/corelib/api/runenvironment.cpp10
-rw-r--r--src/lib/corelib/buildgraph/abstractcommandexecutor.cpp11
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp4
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp102
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.h3
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp1
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp22
-rw-r--r--src/lib/corelib/buildgraph/nodetreedumper.cpp2
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp2
-rw-r--r--src/lib/corelib/buildgraph/rulecommands.cpp25
-rw-r--r--src/lib/corelib/buildgraph/rulecommands.h2
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp28
-rw-r--r--src/lib/corelib/buildgraph/transformerchangetracking.cpp2
-rw-r--r--src/lib/corelib/corelib.qbs9
-rw-r--r--src/lib/corelib/jsextensions/jsextension.h12
-rw-r--r--src/lib/corelib/jsextensions/pkgconfigjs.cpp3
-rw-r--r--src/lib/corelib/jsextensions/utilitiesextension.cpp8
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp48
-rw-r--r--src/lib/corelib/language/evaluator.cpp82
-rw-r--r--src/lib/corelib/language/evaluator.h15
-rw-r--r--src/lib/corelib/language/item.cpp133
-rw-r--r--src/lib/corelib/language/item.h97
-rw-r--r--src/lib/corelib/language/itempool.cpp2
-rw-r--r--src/lib/corelib/language/language.cpp132
-rw-r--r--src/lib/corelib/language/language.h52
-rw-r--r--src/lib/corelib/language/moduleproviderinfo.h19
-rw-r--r--src/lib/corelib/language/propertydeclaration.cpp109
-rw-r--r--src/lib/corelib/language/propertydeclaration.h14
-rw-r--r--src/lib/corelib/language/propertymapinternal.h2
-rw-r--r--src/lib/corelib/language/qualifiedid.cpp2
-rw-r--r--src/lib/corelib/language/scriptengine.cpp74
-rw-r--r--src/lib/corelib/language/scriptengine.h11
-rw-r--r--src/lib/corelib/language/value.cpp52
-rw-r--r--src/lib/corelib/language/value.h21
-rw-r--r--src/lib/corelib/loader/astimportshandler.cpp18
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.cpp19
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.h4
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.cpp759
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.h44
-rw-r--r--src/lib/corelib/loader/groupshandler.cpp112
-rw-r--r--src/lib/corelib/loader/groupshandler.h26
-rw-r--r--src/lib/corelib/loader/itemreader.cpp18
-rw-r--r--src/lib/corelib/loader/itemreader.h2
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.cpp22
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.h6
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.cpp116
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.h14
-rw-r--r--src/lib/corelib/loader/loaderutils.cpp901
-rw-r--r--src/lib/corelib/loader/loaderutils.h367
-rw-r--r--src/lib/corelib/loader/localprofiles.cpp48
-rw-r--r--src/lib/corelib/loader/localprofiles.h18
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.cpp247
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.h56
-rw-r--r--src/lib/corelib/loader/moduleloader.cpp458
-rw-r--r--src/lib/corelib/loader/moduleloader.h61
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.cpp171
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.h53
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.cpp243
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.h72
-rw-r--r--src/lib/corelib/loader/probesresolver.cpp137
-rw-r--r--src/lib/corelib/loader/probesresolver.h28
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.cpp123
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.h42
-rw-r--r--src/lib/corelib/loader/productresolver.cpp1618
-rw-r--r--src/lib/corelib/loader/productresolver.h (renamed from src/lib/corelib/loader/productshandler.h)21
-rw-r--r--src/lib/corelib/loader/productscollector.cpp178
-rw-r--r--src/lib/corelib/loader/productscollector.h1
-rw-r--r--src/lib/corelib/loader/productshandler.cpp336
-rw-r--r--src/lib/corelib/loader/productsresolver.cpp599
-rw-r--r--src/lib/corelib/loader/productsresolver.h (renamed from src/lib/corelib/loader/projecttreebuilder.h)53
-rw-r--r--src/lib/corelib/loader/projectresolver.cpp1921
-rw-r--r--src/lib/corelib/loader/projectresolver.h4
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.cpp297
-rw-r--r--src/lib/corelib/logging/categories.cpp1
-rw-r--r--src/lib/corelib/logging/categories.h1
-rw-r--r--src/lib/corelib/logging/logger.cpp2
-rw-r--r--src/lib/corelib/parser/qmljsastvisitor_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsengine_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsglobal_p.h35
-rw-r--r--src/lib/corelib/parser/qmljslexer.cpp6
-rw-r--r--src/lib/corelib/parser/qmljslexer_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsparser_p.h2
-rw-r--r--src/lib/corelib/tools/clangclinfo.cpp4
-rw-r--r--src/lib/corelib/tools/codelocation.cpp40
-rw-r--r--src/lib/corelib/tools/codelocation.h72
-rw-r--r--src/lib/corelib/tools/error.cpp7
-rw-r--r--src/lib/corelib/tools/error.h1
-rw-r--r--src/lib/corelib/tools/executablefinder.cpp2
-rw-r--r--src/lib/corelib/tools/launchersocket.cpp7
-rw-r--r--src/lib/corelib/tools/msvcinfo.cpp1
-rw-r--r--src/lib/corelib/tools/mutexdata.h104
-rw-r--r--src/lib/corelib/tools/persistence.cpp10
-rw-r--r--src/lib/corelib/tools/persistence.h18
-rw-r--r--src/lib/corelib/tools/profiling.cpp8
-rw-r--r--src/lib/corelib/tools/profiling.h2
-rw-r--r--src/lib/corelib/tools/progressobserver.h8
-rw-r--r--src/lib/corelib/tools/qbsassert.h5
-rw-r--r--src/lib/corelib/tools/qttools.h75
-rw-r--r--src/lib/corelib/tools/scripttools.cpp41
-rw-r--r--src/lib/corelib/tools/scripttools.h8
-rw-r--r--src/lib/corelib/tools/settings.cpp21
-rw-r--r--src/lib/corelib/tools/settings.h2
-rw-r--r--src/lib/corelib/tools/settingsmodel.cpp10
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.cpp48
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.h6
-rw-r--r--src/lib/corelib/tools/shellutils.cpp2
-rw-r--r--src/lib/corelib/tools/stringconstants.h1
-rw-r--r--src/lib/msbuild/io/visualstudiosolutionwriter.cpp14
-rw-r--r--src/lib/pkgconfig/pcpackage.cpp71
-rw-r--r--src/lib/pkgconfig/pcpackage.h22
-rw-r--r--src/lib/pkgconfig/pcparser.cpp83
-rw-r--r--src/lib/pkgconfig/pcparser.h1
-rw-r--r--src/lib/pkgconfig/pkgconfig.cpp157
-rw-r--r--src/lib/pkgconfig/pkgconfig.h3
122 files changed, 6397 insertions, 4973 deletions
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index 4bf7575c3..97b5e6fad 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -266,14 +266,14 @@ set(LOADER_SOURCES
probesresolver.h
productitemmultiplexer.cpp
productitemmultiplexer.h
+ productresolver.cpp
+ productresolver.h
productscollector.cpp
productscollector.h
- productshandler.cpp
- productshandler.h
+ productsresolver.cpp
+ productsresolver.h
projectresolver.cpp
projectresolver.h
- projecttreebuilder.cpp
- projecttreebuilder.h
)
list_transform_prepend(LOADER_SOURCES loader/)
@@ -412,6 +412,7 @@ set(TOOLS_HEADERS
generateoptions.h
installoptions.h
joblimits.h
+ mutexdata.h
preferences.h
processresult.h
profile.h
diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp
index 5e810f0b6..35766efa4 100644
--- a/src/lib/corelib/api/internaljobs.cpp
+++ b/src/lib/corelib/api/internaljobs.cpp
@@ -77,8 +77,8 @@ public:
{
std::lock_guard<std::mutex> lock(m_cancelMutex);
m_canceled = true;
- if (scriptEngine())
- scriptEngine()->cancel();
+ for (ScriptEngine * const engine : scriptEngines())
+ engine->cancel();
}
private:
@@ -324,9 +324,9 @@ void InternalSetupProjectJob::execute()
void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine)
{
- ProjectResolver resolver(engine, logger());
+ ProjectResolver resolver(m_parameters, engine, logger());
resolver.setProgressObserver(observer());
- m_newProject = resolver.resolve(m_parameters);
+ m_newProject = resolver.resolve();
QBS_CHECK(m_newProject);
}
diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp
index 7a845b0ac..10c96bfee 100644
--- a/src/lib/corelib/api/jobs.cpp
+++ b/src/lib/corelib/api/jobs.cpp
@@ -231,7 +231,7 @@ Project SetupProjectJob::project() const
{
auto const wrapper = qobject_cast<const InternalJobThreadWrapper *>(internalJob());
auto const job = qobject_cast<const InternalSetupProjectJob *>(wrapper->synchronousJob());
- return Project(job->project(), job->logger());
+ return {job->project(), job->logger()};
}
void SetupProjectJob::resolve(const Project &existingProject,
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 8d5152a24..53c711b49 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -269,14 +269,10 @@ GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup
for (const auto &sa : resolvedGroup->files) {
ArtifactData artifact = createApiSourceArtifact(sa);
setupInstallData(artifact, product);
- group.d->sourceArtifacts.push_back(artifact);
- }
- if (resolvedGroup->wildcards) {
- for (const auto &sa : resolvedGroup->wildcards->files) {
- ArtifactData artifact = createApiSourceArtifact(sa);
- setupInstallData(artifact, product);
+ if (sa->fromWildcard)
group.d->sourceArtifactsFromWildcards.push_back(artifact);
- }
+ else
+ group.d->sourceArtifacts.push_back(artifact);
}
std::sort(group.d->sourceArtifacts.begin(),
group.d->sourceArtifacts.end());
@@ -801,8 +797,14 @@ RunEnvironment Project::getRunEnvironment(const ProductData &product,
const QStringList &setupRunEnvConfig, Settings *settings) const
{
const ResolvedProductPtr resolvedProduct = d->internalProduct(product);
- return RunEnvironment(resolvedProduct, d->internalProject, installOptions, environment,
- setupRunEnvConfig, settings, d->logger);
+ return {
+ resolvedProduct,
+ d->internalProject,
+ installOptions,
+ environment,
+ setupRunEnvConfig,
+ settings,
+ d->logger};
}
/*!
@@ -960,6 +962,12 @@ std::set<QString> Project::buildSystemFiles() const
return rangeTo<std::set<QString>>(d->internalProject->buildSystemFiles);
}
+CodeLinks Project::codeLinks() const
+{
+ QBS_ASSERT(isValid(), return {});
+ return d->internalProject->codeLinks;
+}
+
RuleCommandList Project::ruleCommands(const ProductData &product,
const QString &inputFilePath, const QString &outputFileTag, ErrorInfo *error) const
{
diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h
index 7ea7645a7..7490eb41e 100644
--- a/src/lib/corelib/api/project.h
+++ b/src/lib/corelib/api/project.h
@@ -136,6 +136,7 @@ public:
QVariantMap projectConfiguration() const;
std::set<QString> buildSystemFiles() const;
+ CodeLinks codeLinks() const;
RuleCommandList ruleCommands(const ProductData &product, const QString &inputFilePath,
const QString &outputFileTag, ErrorInfo *error = nullptr) const;
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 9de2d00ee..628fe2a5d 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -40,16 +40,16 @@
#include "projectdata_p.h"
#include "propertymap_p.h"
+#include <language/builtindeclarations.h>
#include <language/language.h>
#include <language/propertymapinternal.h>
-#include <loader/productitemmultiplexer.h>
+#include <loader/loaderutils.h>
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
#include <tools/qbsassert.h>
#include <tools/qttools.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
-#include <tools/stringconstants.h>
#include <QtCore/qdir.h>
#include <QtCore/qjsonarray.h>
@@ -75,7 +75,7 @@ static QVariant getModuleProperty(const PropertyMap &properties, const QString &
{
const int lastDotIndex = fullPropertyName.lastIndexOf(QLatin1Char('.'));
if (lastDotIndex == -1)
- return QVariant();
+ return {};
return properties.getModuleProperty(fullPropertyName.left(lastDotIndex),
fullPropertyName.mid(lastDotIndex + 1));
}
@@ -559,7 +559,7 @@ const QString &ProductData::name() const
*/
QString ProductData::fullDisplayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name(), multiplexConfigurationId());
+ return fullProductDisplayName(name(), multiplexConfigurationId());
}
/*!
@@ -732,21 +732,16 @@ bool operator==(const ProductData &lhs, const ProductData &rhs)
if (!lhs.isValid() && !rhs.isValid())
return true;
- return lhs.isValid() == rhs.isValid()
- && lhs.name() == rhs.name()
- && lhs.targetName() == rhs.targetName()
- && lhs.type() == rhs.type()
- && lhs.version() == rhs.version()
- && lhs.dependencies() == rhs.dependencies()
- && lhs.profile() == rhs.profile()
- && lhs.multiplexConfigurationId() == rhs.multiplexConfigurationId()
- && lhs.location() == rhs.location()
- && lhs.groups() == rhs.groups()
- && lhs.generatedArtifacts() == rhs.generatedArtifacts()
- && lhs.properties() == rhs.properties()
- && lhs.moduleProperties() == rhs.moduleProperties()
- && lhs.isEnabled() == rhs.isEnabled()
- && lhs.isMultiplexed() == rhs.isMultiplexed();
+ return lhs.isValid() == rhs.isValid() && lhs.name() == rhs.name()
+ && lhs.targetName() == rhs.targetName() && lhs.type() == rhs.type()
+ && lhs.version() == rhs.version() && lhs.dependencies() == rhs.dependencies()
+ && lhs.profile() == rhs.profile()
+ && lhs.multiplexConfigurationId() == rhs.multiplexConfigurationId()
+ && lhs.location() == rhs.location() && lhs.groups() == rhs.groups()
+ && lhs.generatedArtifacts() == rhs.generatedArtifacts()
+ && qVariantMapsEqual(lhs.properties(), rhs.properties())
+ && lhs.moduleProperties() == rhs.moduleProperties() && lhs.isEnabled() == rhs.isEnabled()
+ && lhs.isMultiplexed() == rhs.isMultiplexed();
}
bool operator!=(const ProductData &lhs, const ProductData &rhs)
@@ -954,6 +949,52 @@ QStringList PropertyMap::allProperties() const
}
/*!
+ * \brief Returns the names of all modules whose properties can be requested.
+ */
+QStringList PropertyMap::allModules() const
+{
+ QStringList modules;
+ for (auto it = d->m_map->value().constBegin(); it != d->m_map->value().constEnd(); ++it) {
+ if (it.value().canConvert<QVariantMap>())
+ modules << it.key();
+ }
+ return modules;
+}
+
+/*!
+ * \brief Returns information about all properties of the given module.
+ */
+QList<PropertyMap::PropertyInfo> PropertyMap::allPropertiesForModule(const QString &module) const
+{
+ const QVariantMap moduleProps = d->m_map->value().value(module).toMap();
+ QList<PropertyInfo> properties;
+ const auto builtinProps = transformed<QStringList>(
+ BuiltinDeclarations::instance().declarationsForType(ItemType::Module).properties(),
+ [](const PropertyDeclaration &decl) { return decl.name(); });
+ for (auto it = moduleProps.begin(); it != moduleProps.end(); ++it) {
+ static const auto getType = [](const QVariant &v) -> QString {
+ switch (qVariantType(v)) {
+ case QMetaType::Bool:
+ return PropertyDeclaration::typeString(PropertyDeclaration::Boolean);
+ case QMetaType::Int:
+ return PropertyDeclaration::typeString(PropertyDeclaration::Integer);
+ case QMetaType::QVariantList:
+ return PropertyDeclaration::typeString(PropertyDeclaration::VariantList);
+ case QMetaType::QString:
+ return PropertyDeclaration::typeString(PropertyDeclaration::String);
+ case QMetaType::QStringList:
+ return PropertyDeclaration::typeString(PropertyDeclaration::StringList);
+ default:
+ return PropertyDeclaration::typeString(PropertyDeclaration::Variant);
+ }
+ };
+ properties << PropertyInfo{
+ it.key(), getType(it.value()), it.value(), builtinProps.contains(it.key())};
+ }
+ return properties;
+}
+
+/*!
* \brief Returns the value of the given property of a product or group.
*/
QVariant PropertyMap::getProperty(const QString &name) const
diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h
index 9fe6445c7..4b7bc2803 100644
--- a/src/lib/corelib/api/projectdata.h
+++ b/src/lib/corelib/api/projectdata.h
@@ -74,6 +74,14 @@ class QBS_EXPORT PropertyMap
friend QBS_EXPORT bool operator!=(const PropertyMap &, const PropertyMap &);
public:
+ struct PropertyInfo
+ {
+ QString name;
+ QString type;
+ QVariant value;
+ bool isBuiltin = false;
+ };
+
PropertyMap();
PropertyMap(const PropertyMap &other);
PropertyMap(PropertyMap &&other) Q_DECL_NOEXCEPT;
@@ -83,6 +91,8 @@ public:
PropertyMap &operator =(PropertyMap &&other) Q_DECL_NOEXCEPT;
QStringList allProperties() const;
+ QStringList allModules() const;
+ QList<PropertyInfo> allPropertiesForModule(const QString &module) const;
QVariant getProperty(const QString &name) const;
QStringList getModulePropertiesAsStringList(const QString &moduleName,
diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp
index 5b9f9bced..604600f8e 100644
--- a/src/lib/corelib/api/projectfileupdater.cpp
+++ b/src/lib/corelib/api/projectfileupdater.cpp
@@ -210,11 +210,7 @@ ProjectFileGroupInserter::ProjectFileGroupInserter(ProductData product, QString
static int extractLine(const QString &fileContent, int pos)
{
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
- return fileContent.leftRef(pos).count(QLatin1Char('\n'));
-#else
return QStringView{fileContent}.left(pos).count(QLatin1Char('\n'));
-#endif
}
void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast)
diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp
index 2544f549e..23d0359b0 100644
--- a/src/lib/corelib/api/runenvironment.cpp
+++ b/src/lib/corelib/api/runenvironment.cpp
@@ -225,7 +225,7 @@ int RunEnvironment::doRunShell()
static QString findExecutable(const QStringList &fileNames)
{
const QStringList path = QString::fromLocal8Bit(qgetenv("PATH"))
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
for (const QString &fileName : fileNames) {
const QString exeFileName = HostOsInfo::appendExecutableSuffix(fileName);
@@ -363,16 +363,16 @@ int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arg
<< arguments;
}
} else {
- if (QFileInfo(targetExecutable = findExecutable(QStringList()
- << QStringLiteral("iostool"))).isExecutable()) {
+ if (targetExecutable = findExecutable(QStringList{QStringLiteral("iostool")});
+ QFileInfo(targetExecutable).isExecutable()) {
targetArguments = QStringList()
<< QStringLiteral("-run")
<< QStringLiteral("-bundle")
<< QDir::cleanPath(bundlePath);
if (!arguments.empty())
targetArguments << QStringLiteral("-extra-args") << arguments;
- } else if (QFileInfo(targetExecutable = findExecutable(QStringList()
- << QStringLiteral("ios-deploy"))).isExecutable()) {
+ } else if (targetExecutable = findExecutable(QStringList{QStringLiteral("ios-deploy")});
+ QFileInfo(targetExecutable).isExecutable()) {
targetArguments = QStringList()
<< QStringLiteral("--no-wifi")
<< QStringLiteral("--noninteractive")
diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp
index 16c3621b6..ee82de43d 100644
--- a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp
@@ -60,13 +60,12 @@ AbstractCommandExecutor::AbstractCommandExecutor(Logger logger, QObject *parent)
, m_logger(std::move(logger))
{
m_watchdog.setSingleShot(true);
- connect(&m_watchdog, &QTimer::timeout,
- this, [this]() {
- cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout.")});
+ connect(&m_watchdog, &QTimer::timeout, this, [this]() {
+ cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout: %1")
+ .arg(m_command->descriptionForCancelMessage(
+ m_transformer->product()->fullDisplayName()))});
});
- connect(this, &AbstractCommandExecutor::finished,
- &m_watchdog, &QTimer::stop);
-
+ connect(this, &AbstractCommandExecutor::finished, &m_watchdog, &QTimer::stop);
}
void AbstractCommandExecutor::start(Transformer *transformer, AbstractCommand *cmd)
diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp
index d641627e7..10aae5991 100644
--- a/src/lib/corelib/buildgraph/buildgraph.cpp
+++ b/src/lib/corelib/buildgraph/buildgraph.cpp
@@ -710,7 +710,7 @@ void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product,
const QVariantMap oldModuleProperties = artifact->properties->value();
setArtifactData(artifact, sourceArtifact);
if (oldFileTags != artifact->fileTags()
- || oldModuleProperties != artifact->properties->value()) {
+ || !qVariantMapsEqual(oldModuleProperties, artifact->properties->value())) {
invalidateArtifactAsRuleInputIfNecessary(artifact);
}
}
@@ -766,7 +766,7 @@ void updateGeneratedArtifacts(ResolvedProduct *product)
provideFullFileTagsAndProperties(artifact);
applyPerArtifactProperties(artifact);
if (oldFileTags != artifact->fileTags()
- || oldModuleProperties != artifact->properties->value()) {
+ || !qVariantMapsEqual(oldModuleProperties, artifact->properties->value())) {
invalidateArtifactAsRuleInputIfNecessary(artifact);
}
}
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp
index fc05d292c..ffae52ab2 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp
@@ -307,8 +307,10 @@ void BuildGraphLoader::trackProjectChanges()
std::vector<ResolvedProductPtr> allRestoredProducts = restoredProject->allProducts();
std::vector<ResolvedProductPtr> changedProducts;
bool reResolvingNecessary = false;
- if (!checkConfigCompatibility())
+ if (!checkConfigCompatibility()) {
+ m_logger.qbsInfo() << Tr::tr("One or more properties have changed.");
reResolvingNecessary = true;
+ }
if (hasProductFileChanged(allRestoredProducts, restoredProject->lastStartResolveTime,
buildSystemFiles, changedProducts)) {
reResolvingNecessary = true;
@@ -334,11 +336,20 @@ void BuildGraphLoader::trackProjectChanges()
return;
}
+ for (const QString &file : m_changedProjectFiles) {
+ m_logger.qbsInfo() << Tr::tr("Project file '%1' has changed.")
+ .arg(QDir::toNativeSeparators(file));
+ }
+ for (const QString &file : m_removedProjectFiles) {
+ m_logger.qbsInfo() << Tr::tr("Project file '%1' was removed.")
+ .arg(QDir::toNativeSeparators(file));
+ }
+
restoredProject->buildData->setDirty();
markTransformersForChangeTracking(allRestoredProducts);
if (!m_parameters.overrideBuildGraphData())
m_parameters.setEnvironment(restoredProject->environment);
- ProjectResolver resolver(m_evalContext->engine(), m_logger);
+ ProjectResolver resolver(m_parameters, m_evalContext->engine(), m_logger);
resolver.setProgressObserver(m_evalContext->observer());
resolver.setOldProjectProbes(restoredProject->probes);
if (!m_parameters.forceProbeExecution())
@@ -350,7 +361,7 @@ void BuildGraphLoader::trackProjectChanges()
resolver.setOldProductProbes(restoredProbes);
if (!m_parameters.overrideBuildGraphData())
resolver.setStoredProfiles(restoredProject->profileConfigs);
- m_result.newlyResolvedProject = resolver.resolve(m_parameters);
+ m_result.newlyResolvedProject = resolver.resolve();
std::vector<ResolvedProductPtr> allNewlyResolvedProducts
= m_result.newlyResolvedProject->allProducts();
@@ -497,9 +508,11 @@ bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &rest
newEnv.remove(ldPreloadEnvVar);
if (oldEnv != newEnv) {
- qCDebug(lcBuildGraph) << "Set of environment variables changed. Must re-resolve project."
- << "\nold:" << restoredProject->environment.toStringList()
- << "\nnew:" << m_parameters.adjustedEnvironment().toStringList();
+ m_logger.qbsInfo() << Tr::tr("Environment changed.");
+ m_logger.qbsDebug() << Tr::tr("old: %1").arg(
+ restoredProject->environment.toStringList().join(QLatin1Char('\n')));
+ m_logger.qbsDebug() << Tr::tr("new: %2").arg(
+ m_parameters.adjustedEnvironment().toStringList().join(QLatin1Char('\n')));
return true;
}
return false;
@@ -510,8 +523,8 @@ bool BuildGraphLoader::hasCanonicalFilePathResultChanged(const TopLevelProjectCo
for (auto it = restoredProject->canonicalFilePathResults.constBegin();
it != restoredProject->canonicalFilePathResults.constEnd(); ++it) {
if (QFileInfo(it.key()).canonicalFilePath() != it.value()) {
- qCDebug(lcBuildGraph) << "Canonical file path for file" << it.key()
- << "changed, must re-resolve project.";
+ m_logger.qbsInfo() << Tr::tr("Canonical file path for file '%1' changed.")
+ .arg(QDir::toNativeSeparators(it.key()));
return true;
}
}
@@ -524,8 +537,8 @@ bool BuildGraphLoader::hasFileExistsResultChanged(const TopLevelProjectConstPtr
for (QHash<QString, bool>::ConstIterator it = restoredProject->fileExistsResults.constBegin();
it != restoredProject->fileExistsResults.constEnd(); ++it) {
if (FileInfo(it.key()).exists() != it.value()) {
- qCDebug(lcBuildGraph) << "Existence check for file" << it.key()
- << "changed, must re-resolve project.";
+ m_logger.qbsInfo() << Tr::tr("Existence check for file '%1' changed.")
+ .arg(QDir::toNativeSeparators(it.key()));
return true;
}
}
@@ -539,9 +552,8 @@ bool BuildGraphLoader::hasDirectoryEntriesResultChanged(const TopLevelProjectCon
it != restoredProject->directoryEntriesResults.constEnd(); ++it) {
if (QDir(it.key().first).entryList(static_cast<QDir::Filters>(it.key().second), QDir::Name)
!= it.value()) {
- qCDebug(lcBuildGraph) << "Entry list for directory" << it.key().first
- << static_cast<QDir::Filters>(it.key().second)
- << "changed, must re-resolve project.";
+ m_logger.qbsInfo() << Tr::tr("Entry list for directory '%1' changed.")
+ .arg(QDir::toNativeSeparators(it.key().first));
return true;
}
}
@@ -555,8 +567,8 @@ bool BuildGraphLoader::hasFileLastModifiedResultChanged(const TopLevelProjectCon
= restoredProject->fileLastModifiedResults.constBegin();
it != restoredProject->fileLastModifiedResults.constEnd(); ++it) {
if (FileInfo(it.key()).lastModified() != it.value()) {
- qCDebug(lcBuildGraph) << "Timestamp for file" << it.key()
- << "changed, must re-resolve project.";
+ m_logger.qbsInfo() << Tr::tr("Timestamp for file '%1' has changed.")
+ .arg(QDir::toNativeSeparators(it.key()));
return true;
}
}
@@ -574,17 +586,18 @@ bool BuildGraphLoader::hasProductFileChanged(const std::vector<ResolvedProductPt
const FileInfo pfi(filePath);
remainingBuildSystemFiles.remove(filePath);
if (!pfi.exists()) {
- qCDebug(lcBuildGraph) << "A product was removed, must re-resolve project";
+ m_removedProjectFiles << filePath;
hasChanged = true;
} else if (referenceTime < pfi.lastModified()) {
- qCDebug(lcBuildGraph) << "A product was changed, must re-resolve project";
+ m_changedProjectFiles << filePath;
hasChanged = true;
} else if (!contains(changedProducts, product)) {
bool foundMissingSourceFile = false;
for (const QString &file : std::as_const(product->missingSourceFiles)) {
if (FileInfo(file).exists()) {
- qCDebug(lcBuildGraph) << "Formerly missing file" << file << "in product"
- << product->name << "exists now, must re-resolve project";
+ m_logger.qbsInfo()
+ << Tr::tr("Formerly missing file '%1' in product '%2' exists now.")
+ .arg(QDir::toNativeSeparators(filePath), product->fullDisplayName());
foundMissingSourceFile = true;
break;
}
@@ -598,25 +611,14 @@ bool BuildGraphLoader::hasProductFileChanged(const std::vector<ResolvedProductPt
AccumulatingTimer wildcardTimer(m_parameters.logElapsedTime()
? &m_wildcardExpansionEffort : nullptr);
for (const GroupPtr &group : product->groups) {
- if (!group->wildcards)
- continue;
- const bool reExpansionRequired = Internal::any_of(group->wildcards->dirTimeStamps,
- [](const std::pair<QString, FileTime> &pair) {
- return FileInfo(pair.first).lastModified() > pair.second;
- });
- if (!reExpansionRequired)
- continue;
- const Set<QString> files = group->wildcards->expandPatterns(group,
- FileInfo::path(group->location.filePath()),
- product->topLevelProject()->buildDirectory);
- Set<QString> wcFiles;
- for (const auto &sourceArtifact : group->wildcards->files)
- wcFiles += sourceArtifact->absoluteFilePath;
- if (files == wcFiles)
- continue;
- hasChanged = true;
- changedProducts.push_back(product);
- break;
+ if (group->wildcards && group->wildcards->hasChangedSinceExpansion()) {
+ m_logger.qbsInfo()
+ << Tr::tr("Must re-expand wildcards for group '%1' in product '%2'.")
+ .arg(group->name, product->fullDisplayName());
+ hasChanged = true;
+ changedProducts.push_back(product);
+ break;
+ }
}
}
}
@@ -630,8 +632,7 @@ bool BuildGraphLoader::hasBuildSystemFileChanged(const Set<QString> &buildSystem
for (const QString &file : buildSystemFiles) {
const FileInfo fi(file);
if (!fi.exists()) {
- qCDebug(lcBuildGraph) << "Project file" << file
- << "no longer exists, must re-resolve project.";
+ m_removedProjectFiles << file;
return true;
}
const auto generatedChecker = [&file, restoredProject](const ModuleProviderInfo &mpi) {
@@ -642,7 +643,7 @@ bool BuildGraphLoader::hasBuildSystemFileChanged(const Set<QString> &buildSystem
const FileTime referenceTime = fileWasCreatedByModuleProvider
? restoredProject->lastEndResolveTime : restoredProject->lastStartResolveTime;
if (referenceTime < fi.lastModified()) {
- qCDebug(lcBuildGraph) << "Project file" << file << "changed, must re-resolve project.";
+ m_changedProjectFiles << file;
return true;
}
}
@@ -769,10 +770,11 @@ bool BuildGraphLoader::checkProductForInstallInfoChanges(const ResolvedProductPt
<< StringConstants::installDirProperty() << StringConstants::installPrefixProperty()
<< StringConstants::installRootProperty();
for (const QString &key : specialProperties) {
- if (restoredProduct->moduleProperties->qbsPropertyValue(key)
- != newlyResolvedProduct->moduleProperties->qbsPropertyValue(key)) {
+ if (!qVariantsEqual(
+ restoredProduct->moduleProperties->qbsPropertyValue(key),
+ newlyResolvedProduct->moduleProperties->qbsPropertyValue(key))) {
qCDebug(lcBuildGraph).noquote().nospace()
- << "Product property 'qbs." << key << "' changed.";
+ << "Product property 'qbs." << key << "' changed.";
return true;
}
}
@@ -858,11 +860,14 @@ void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPt
bool BuildGraphLoader::checkConfigCompatibility()
{
const TopLevelProjectConstPtr restoredProject = m_result.loadedProject;
- if (m_parameters.topLevelProfile().isEmpty())
+ if (m_parameters.topLevelProfile().isEmpty()) {
m_parameters.setTopLevelProfile(restoredProject->profile());
+ m_parameters.expandBuildConfiguration();
+ }
if (!m_parameters.overrideBuildGraphData()) {
if (!m_parameters.overriddenValues().empty()
- && m_parameters.overriddenValues() != restoredProject->overriddenValues) {
+ && !qVariantMapsEqual(
+ m_parameters.overriddenValues(), restoredProject->overriddenValues)) {
const auto toUserOutput = [](const QVariantMap &propMap) {
QString o;
for (auto it = propMap.begin(); it != propMap.end(); ++it) {
@@ -880,7 +885,7 @@ bool BuildGraphLoader::checkConfigCompatibility()
"you really want to rebuild with the new properties.")
.arg(toUserOutput(restoredProject->overriddenValues),
toUserOutput(m_parameters.overriddenValues())));
- }
+ }
m_parameters.setOverriddenValues(restoredProject->overriddenValues);
if (m_parameters.topLevelProfile() != restoredProject->profile()) {
throw ErrorInfo(Tr::tr("The current profile is '%1', but profile '%2' was used "
@@ -895,7 +900,8 @@ bool BuildGraphLoader::checkConfigCompatibility()
}
if (!m_parameters.overrideBuildGraphData())
return true;
- if (m_parameters.finalBuildConfigurationTree() != restoredProject->buildConfiguration())
+ if (!qVariantMapsEqual(
+ m_parameters.finalBuildConfigurationTree(), restoredProject->buildConfiguration()))
return false;
Settings settings(m_parameters.settingsDirectory());
const QVariantMap profileConfigsTree = restoredProject->fullProfileConfigsTree();
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h
index c62ba7fa7..02b00e29d 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.h
+++ b/src/lib/corelib/buildgraph/buildgraphloader.h
@@ -46,6 +46,7 @@
#include <language/forward_decls.h>
#include <logging/logger.h>
+#include <tools/set.h>
#include <tools/setupprojectparameters.h>
#include <QtCore/qprocess.h>
@@ -140,6 +141,8 @@ private:
Logger m_logger;
QStringList m_artifactsRemovedFromDisk;
std::unordered_map<QString, std::vector<SourceArtifactConstPtr>> m_changedSourcesByProduct;
+ Set<QString> m_changedProjectFiles;
+ Set<QString> m_removedProjectFiles;
Set<QString> m_productsWhoseArtifactsNeedUpdate;
qint64 m_wildcardExpansionEffort = 0;
qint64 m_propertyComparisonEffort = 0;
diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp
index 869d7552a..2b8232a0a 100644
--- a/src/lib/corelib/buildgraph/executor.cpp
+++ b/src/lib/corelib/buildgraph/executor.cpp
@@ -263,6 +263,7 @@ void Executor::doBuild()
QBS_CHECK(!m_project->buildData->evaluationContext);
m_project->buildData->evaluationContext = std::make_shared<RulesEvaluationContext>(m_logger);
m_evalContext = m_project->buildData->evaluationContext;
+ m_progressObserver->addScriptEngine(m_evalContext->engine());
m_elapsedTimeRules = m_elapsedTimeScanners = m_elapsedTimeInstalling = 0;
m_evalContext->engine()->enableProfiling(m_buildOptions.logElapsedTime());
diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
index 6d0c5cdbd..0c73f599f 100644
--- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp
+++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
@@ -103,9 +103,14 @@ static void resolveDepencency(const RawScannedDependency &dependency,
}
// prioritize found artifacts
- if ((result->file = dependencyInProduct)
- || (result->file = dependencyInOtherProduct)
- || (result->file = fileDependencyArtifact)) {
+ if (dependencyInProduct)
+ result->file = dependencyInProduct;
+ else if (dependencyInOtherProduct)
+ result->file = dependencyInOtherProduct;
+ else
+ result->file = fileDependencyArtifact;
+
+ if (result->file) {
result->filePath = result->file->filePath();
if (result->file == dependencyInOtherProduct && !productOfDependencyIsDependency) {
@@ -250,14 +255,9 @@ void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *sca
RawScanResults::ScanData &scanData = m_rawScanResults.findScanData(fileToBeScanned, scanner,
m_artifact->properties);
if (scanData.lastScanTime < fileToBeScanned->timestamp()) {
- try {
- qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned);
- scanWithScannerPlugin(scanner, inputArtifact, fileToBeScanned, &scanData.rawScanResult);
- scanData.lastScanTime = FileTime::currentTime();
- } catch (const ErrorInfo &error) {
- m_logger.printWarning(error);
- return;
- }
+ qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned);
+ scanWithScannerPlugin(scanner, inputArtifact, fileToBeScanned, &scanData.rawScanResult);
+ scanData.lastScanTime = FileTime::currentTime();
}
resolveScanResultDependencies(inputArtifact, scanData.rawScanResult, filesToScan, cache);
diff --git a/src/lib/corelib/buildgraph/nodetreedumper.cpp b/src/lib/corelib/buildgraph/nodetreedumper.cpp
index 8475a46cf..6ad597c70 100644
--- a/src/lib/corelib/buildgraph/nodetreedumper.cpp
+++ b/src/lib/corelib/buildgraph/nodetreedumper.cpp
@@ -120,7 +120,7 @@ bool NodeTreeDumper::doVisit(BuildGraphNode *node, const QString &nodeRepr)
QByteArray NodeTreeDumper::indentation() const
{
- return QByteArray(m_indentation, ' ');
+ return {m_indentation, ' '};
}
} // namespace Internal
diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
index b308e9c47..52a8fe75d 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
@@ -291,7 +291,7 @@ void ProcessCommandExecutor::getProcessOutput(bool stdOut, ProcessResult &result
} else {
if (!contentString.isEmpty() && contentString.endsWith(QLatin1Char('\n')))
contentString.chop(1);
- *target = contentString.split(QLatin1Char('\n'), QBS_SKIP_EMPTY_PARTS);
+ *target = contentString.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
}
}
diff --git a/src/lib/corelib/buildgraph/rulecommands.cpp b/src/lib/corelib/buildgraph/rulecommands.cpp
index 6acd1d68c..be90c2fd5 100644
--- a/src/lib/corelib/buildgraph/rulecommands.cpp
+++ b/src/lib/corelib/buildgraph/rulecommands.cpp
@@ -99,15 +99,11 @@ AbstractCommand::~AbstractCommand() = default;
bool AbstractCommand::equals(const AbstractCommand *other) const
{
- return type() == other->type()
- && m_description == other->m_description
- && m_extendedDescription == other->m_extendedDescription
- && m_highlight == other->m_highlight
- && m_ignoreDryRun == other->m_ignoreDryRun
- && m_silent == other->m_silent
- && m_jobPool == other->m_jobPool
- && m_timeout == other->m_timeout
- && m_properties == other->m_properties;
+ return type() == other->type() && m_description == other->m_description
+ && m_extendedDescription == other->m_extendedDescription
+ && m_highlight == other->m_highlight && m_ignoreDryRun == other->m_ignoreDryRun
+ && m_silent == other->m_silent && m_jobPool == other->m_jobPool
+ && m_timeout == other->m_timeout && qVariantMapsEqual(m_properties, other->m_properties);
}
void AbstractCommand::fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue,
@@ -139,6 +135,11 @@ QString AbstractCommand::fullDescription(const QString &productName) const
return description() + QLatin1String(" [") + productName + QLatin1Char(']');
}
+QString AbstractCommand::descriptionForCancelMessage(const QString &productName) const
+{
+ return fullDescription(productName);
+}
+
void AbstractCommand::load(PersistentPool &pool)
{
serializationOp<PersistentPool::Load>(pool);
@@ -339,6 +340,12 @@ void ProcessCommand::fillFromScriptValue(JSContext *ctx, const JSValue *scriptVa
applyCommandProperties(ctx, scriptValue);
}
+QString ProcessCommand::descriptionForCancelMessage(const QString &productName) const
+{
+ return description() + QLatin1String(" (") + QDir::toNativeSeparators(m_program)
+ + QLatin1String(") [") + productName + QLatin1Char(']');
+}
+
QStringList ProcessCommand::relevantEnvVars() const
{
QStringList vars = m_relevantEnvVars;
diff --git a/src/lib/corelib/buildgraph/rulecommands.h b/src/lib/corelib/buildgraph/rulecommands.h
index 7b08d1015..4296146d2 100644
--- a/src/lib/corelib/buildgraph/rulecommands.h
+++ b/src/lib/corelib/buildgraph/rulecommands.h
@@ -78,6 +78,7 @@ public:
virtual bool equals(const AbstractCommand *other) const;
virtual void fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue,
const CodeLocation &codeLocation);
+ virtual QString descriptionForCancelMessage(const QString &productName) const;
QString fullDescription(const QString &productName) const;
const QString description() const { return m_description; }
@@ -129,6 +130,7 @@ public:
bool equals(const AbstractCommand *otherAbstractCommand) const override;
void fillFromScriptValue(JSContext *ctx, const JSValue *scriptValue,
const CodeLocation &codeLocation) override;
+ QString descriptionForCancelMessage(const QString &productName) const override;
const QString program() const { return m_program; }
const QStringList arguments() const { return m_arguments; }
const QString workingDir() const { return m_workingDir; }
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp
index 5cc4be96e..94cee0c62 100644
--- a/src/lib/corelib/buildgraph/rulesapplicator.cpp
+++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp
@@ -293,8 +293,9 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, JSValue prepare
}
outputArtifact->properties->setValue(artifactModulesCfg);
if (!outputInfo.newlyCreated
- && (outputArtifact->fileTags() != outputInfo.oldFileTags
- || outputArtifact->properties->value() != outputInfo.oldProperties)) {
+ && (outputArtifact->fileTags() != outputInfo.oldFileTags
+ || !qVariantMapsEqual(
+ outputArtifact->properties->value(), outputInfo.oldProperties))) {
invalidateArtifactAsRuleInputIfNecessary(outputArtifact);
}
}
@@ -419,11 +420,7 @@ RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifactFromRul
RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifact(const QString &filePath,
const FileTags &fileTags, bool alwaysUpdated, const ArtifactSet &inputArtifacts)
{
- QString outputPath = filePath;
- // don't let the output artifact "escape" its build dir
- outputPath.replace(StringConstants::dotDot(), QStringLiteral("dotdot"));
- outputPath = resolveOutPath(outputPath);
-
+ const QString outputPath = resolveOutPath(filePath);
if (m_rule->isDynamic()) {
const Set<FileTag> undeclaredTags = fileTags - m_rule->collectedOutputFileTags();
if (!undeclaredTags.empty()) {
@@ -664,8 +661,10 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const JSValue &ob
connect(outputInfo.artifact, dependency);
}
ArtifactBindingsExtractor().apply(engine(), outputInfo.artifact, obj);
- if (!outputInfo.newlyCreated && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags
- || outputInfo.artifact->properties->value() != outputInfo.oldProperties)) {
+ if (!outputInfo.newlyCreated
+ && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags
+ || !qVariantMapsEqual(
+ outputInfo.artifact->properties->value(), outputInfo.oldProperties))) {
invalidateArtifactAsRuleInputIfNecessary(outputInfo.artifact);
}
return outputInfo.artifact;
@@ -673,9 +672,14 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const JSValue &ob
QString RulesApplicator::resolveOutPath(const QString &path) const
{
- QString buildDir = m_product->topLevelProject()->buildDirectory;
- QString result = FileInfo::resolvePath(buildDir, path);
- result = QDir::cleanPath(result);
+ const QString buildDir = m_product->topLevelProject()->buildDirectory;
+ QString result = QDir::cleanPath(FileInfo::resolvePath(buildDir, path));
+ if (!result.startsWith(buildDir + QLatin1Char('/'))) {
+ throw ErrorInfo(
+ Tr::tr("Refusing to create artifact '%1' outside of build directory '%2'.")
+ .arg(QDir::toNativeSeparators(result), QDir::toNativeSeparators(buildDir)),
+ m_rule->prepareScript.location());
+ }
return result;
}
diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.cpp b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
index f0b8986f4..ae43e8219 100644
--- a/src/lib/corelib/buildgraph/transformerchangetracking.cpp
+++ b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
@@ -158,7 +158,7 @@ bool TrafoChangeTracker::checkForPropertyChange(const Property &restoredProperty
case Property::PropertyInArtifact:
QBS_CHECK(false);
}
- if (restoredProperty.value != v) {
+ if (!qVariantsEqual(restoredProperty.value, v)) {
qCDebug(lcBuildGraph).noquote().nospace()
<< "Value for property '" << restoredProperty.moduleName << "."
<< restoredProperty.propertyName << "' has changed.\n"
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 5e837168e..1a7890166 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -355,14 +355,14 @@ QbsLibrary {
"probesresolver.h",
"productitemmultiplexer.cpp",
"productitemmultiplexer.h",
+ "productresolver.cpp",
+ "productresolver.h",
"productscollector.cpp",
"productscollector.h",
- "productshandler.cpp",
- "productshandler.h",
+ "productsresolver.cpp",
+ "productsresolver.h",
"projectresolver.cpp",
"projectresolver.h",
- "projecttreebuilder.cpp",
- "projecttreebuilder.h",
]
}
Group {
@@ -514,6 +514,7 @@ QbsLibrary {
"generateoptions.h",
"installoptions.h",
"joblimits.h",
+ "mutexdata.h",
"preferences.h",
"processresult.h",
"profile.h",
diff --git a/src/lib/corelib/jsextensions/jsextension.h b/src/lib/corelib/jsextensions/jsextension.h
index e34c8ff83..d34fb2b83 100644
--- a/src/lib/corelib/jsextensions/jsextension.h
+++ b/src/lib/corelib/jsextensions/jsextension.h
@@ -176,8 +176,10 @@ public:
static JSValue toJsValue(JSContext *ctx, const QByteArray &data)
{
const JSValue array = JS_NewArray(ctx);
- for (int i = 0; i < data.size(); ++i)
- JS_SetPropertyUint32(ctx, array, i, JS_NewInt32(ctx, data.at(i)));
+ for (int i = 0; i < data.size(); ++i) {
+ JS_SetPropertyUint32(ctx, array, i,
+ JS_NewUint32(ctx, static_cast<unsigned char>(data.at(i))));
+ }
return array;
}
static JSValue toJsValue(JSContext *ctx, const QVariantMap &m)
@@ -277,9 +279,9 @@ template<> struct FromArgHelper<QByteArray> {
const JSValue jsNumber = JS_GetPropertyUint32(ctx, v, i);
if (JS_VALUE_GET_TAG(jsNumber) != JS_TAG_INT)
throwError();
- int32_t n;
- JS_ToInt32(ctx, &n, jsNumber);
- if (n < 0 || n > 0xff)
+ uint32_t n;
+ JS_ToUint32(ctx, &n, jsNumber);
+ if (n > 0xff)
throwError();
data[i] = n;
}
diff --git a/src/lib/corelib/jsextensions/pkgconfigjs.cpp b/src/lib/corelib/jsextensions/pkgconfigjs.cpp
index 2d80ec770..817a3dfb9 100644
--- a/src/lib/corelib/jsextensions/pkgconfigjs.cpp
+++ b/src/lib/corelib/jsextensions/pkgconfigjs.cpp
@@ -121,7 +121,6 @@ QVariantMap packageToMap(const PcPackage &package)
case Type::AlwaysMatch:
break;
}
- result[QStringLiteral("comparison")] = QVariant::fromValue(qint32(version.comparison));
return result;
};
@@ -242,7 +241,7 @@ PkgConfig::Options PkgConfigJs::convertOptions(const QProcessEnvironment &env, c
[](const QString &str){ return str.toStdString(); });
result.disableUninstalled = map.value(QStringLiteral("disableUninstalled"), true).toBool();
result.staticMode = map.value(QStringLiteral("staticMode"), false).toBool();
- result.mergeDependencies = map.value(QStringLiteral("mergeDependencies"), true).toBool();
+ result.definePrefix = map.value(QStringLiteral("definePrefix"), false).toBool();
result.globalVariables =
variablesFromQVariantMap(map.value(QStringLiteral("globalVariables")).toMap());
result.systemVariables = envToVariablesMap(env);
diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp
index e733d618b..cdcee59fa 100644
--- a/src/lib/corelib/jsextensions/utilitiesextension.cpp
+++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp
@@ -782,7 +782,7 @@ static QStringList detectMachOArchs(QIODevice *device)
if (strncmp(ar_header, ARMAG, SARMAG) == 0) {
while (!device->atEnd()) {
static_assert(sizeof(ar_hdr) == 60, "sizeof(ar_hdr) != 60");
- ar_hdr header;
+ ar_hdr header{};
if (device->read(reinterpret_cast<char *>(&header),
sizeof(ar_hdr)) != sizeof(ar_hdr))
return {};
@@ -832,7 +832,7 @@ static QStringList detectMachOArchs(QIODevice *device)
pos = device->pos();
- fat_header fatheader;
+ fat_header fatheader{};
fatheader.magic = readInt(device, nullptr, false);
if (fatheader.magic == FAT_MAGIC || fatheader.magic == FAT_CIGAM ||
fatheader.magic == FAT_MAGIC_64 || fatheader.magic == FAT_CIGAM_64) {
@@ -845,7 +845,7 @@ static QStringList detectMachOArchs(QIODevice *device)
QStringList archs;
for (uint32_t n = 0; n < fatheader.nfat_arch; ++n) {
- fat_arch_64 fatarch;
+ fat_arch_64 fatarch{};
static_assert(sizeof(fat_arch_64) == 32, "sizeof(fat_arch_64) != 32");
static_assert(sizeof(fat_arch) == 20, "sizeof(fat_arch) != 20");
const qint64 expectedBytes = is64bit ? sizeof(fat_arch_64) : sizeof(fat_arch);
@@ -875,7 +875,7 @@ static QStringList detectMachOArchs(QIODevice *device)
return {};
bool swap = false;
- mach_header header;
+ mach_header header{};
header.magic = readInt(device, nullptr, swap);
switch (header.magic) {
case MH_CIGAM:
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index acf50b4f3..44dc8a326 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -166,25 +166,28 @@ void BuiltinDeclarations::insert(const ItemDeclaration &decl)
static PropertyDeclaration conditionProperty()
{
- return PropertyDeclaration(StringConstants::conditionProperty(), PropertyDeclaration::Boolean,
- StringConstants::trueValue());
+ return {
+ StringConstants::conditionProperty(),
+ PropertyDeclaration::Boolean,
+ StringConstants::trueValue()};
}
static PropertyDeclaration alwaysRunProperty()
{
- return PropertyDeclaration(StringConstants::alwaysRunProperty(), PropertyDeclaration::Boolean,
- StringConstants::falseValue());
+ return {
+ StringConstants::alwaysRunProperty(),
+ PropertyDeclaration::Boolean,
+ StringConstants::falseValue()};
}
static PropertyDeclaration nameProperty()
{
- return PropertyDeclaration(StringConstants::nameProperty(), PropertyDeclaration::String);
+ return {StringConstants::nameProperty(), PropertyDeclaration::String};
}
static PropertyDeclaration buildDirProperty()
{
- return PropertyDeclaration(StringConstants::buildDirectoryProperty(),
- PropertyDeclaration::Path);
+ return {StringConstants::buildDirectoryProperty(), PropertyDeclaration::Path};
}
static PropertyDeclaration prepareScriptProperty()
@@ -244,11 +247,11 @@ void BuiltinDeclarations::addDependsItem()
PropertyDeclaration::StringList);
item << PropertyDeclaration(StringConstants::limitToSubProjectProperty(),
PropertyDeclaration::Boolean, StringConstants::falseValue());
- item << PropertyDeclaration(StringConstants::multiplexConfigurationIdsProperty(),
- PropertyDeclaration::StringList, QString(),
- PropertyDeclaration::ReadOnlyFlag);
- item << PropertyDeclaration(StringConstants::enableFallbackProperty(),
- PropertyDeclaration::Boolean, StringConstants::trueValue());
+ item << PropertyDeclaration(
+ StringConstants::multiplexConfigurationIdsProperty(),
+ PropertyDeclaration::StringList,
+ QString(),
+ PropertyDeclaration::ReadOnlyFlag);
insert(item);
}
@@ -258,7 +261,6 @@ void BuiltinDeclarations::addExportItem()
item << PropertyDeclaration(StringConstants::prefixMappingProperty(),
PropertyDeclaration::Variant);
auto allowedChildTypes = item.allowedChildTypes();
- allowedChildTypes.insert(ItemType::Parameters);
allowedChildTypes.insert(ItemType::Properties);
item.setAllowedChildTypes(allowedChildTypes);
insert(item);
@@ -326,25 +328,23 @@ void BuiltinDeclarations::addModuleProviderItem()
ItemDeclaration item(ItemType::ModuleProvider);
item << nameProperty()
<< PropertyDeclaration(QStringLiteral("outputBaseDir"), PropertyDeclaration::String)
+ << PropertyDeclaration(StringConstants::isEagerProperty(),
+ PropertyDeclaration::Boolean,
+ StringConstants::trueValue())
+ << PropertyDeclaration(StringConstants::moduleNameProperty(), PropertyDeclaration::String)
<< PropertyDeclaration(QStringLiteral("relativeSearchPaths"),
PropertyDeclaration::StringList);
- item.setAllowedChildTypes({ItemType::Probe});
+ item.setAllowedChildTypes({ItemType::PropertyOptions, ItemType::Probe});
insert(item);
}
ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type)
{
ItemDeclaration item(type);
- item.setAllowedChildTypes(ItemDeclaration::TypeNames()
- << ItemType::Group
- << ItemType::Depends
- << ItemType::FileTagger
- << ItemType::JobLimit
- << ItemType::Rule
- << ItemType::Parameter
- << ItemType::Probe
- << ItemType::PropertyOptions
- << ItemType::Scanner);
+ item.setAllowedChildTypes({ItemType::Depends, ItemType::FileTagger, ItemType::Group,
+ ItemType::JobLimit, ItemType::Parameter, ItemType::Parameters,
+ ItemType::Probe, ItemType::PropertyOptions,
+ ItemType::Rule, ItemType::Scanner});
PropertyDeclaration nameDecl = nameProperty();
PropertyDeclaration::Flags nameFlags = nameDecl.flags();
nameFlags |= PropertyDeclaration::ReadOnlyFlag;
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp
index 9a19828bb..084f2f4a9 100644
--- a/src/lib/corelib/language/evaluator.cpp
+++ b/src/lib/corelib/language/evaluator.cpp
@@ -77,13 +77,15 @@ static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t
JSValueConst obj);
static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc,
JSValueConst obj, JSAtom prop);
+static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc,
+ JSValueConst obj, JSAtom prop);
static bool debugProperties = false;
Evaluator::Evaluator(ScriptEngine *scriptEngine)
: m_scriptEngine(scriptEngine)
, m_scriptClass(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED,
- getEvalPropertyNames, getEvalProperty))
+ getEvalPropertyNames, getEvalPropertySafe))
{
scriptEngine->registerEvaluator(this);
}
@@ -96,6 +98,7 @@ Evaluator::~Evaluator()
valuesToFree << data;
for (const JSValue cachedValue : evalData->valueCache)
JS_FreeValue(m_scriptEngine->context(), cachedValue);
+ evalData->item->removeObserver(this);
delete evalData;
}
for (const auto &scopes : std::as_const(m_fileContextScopesMap)) {
@@ -191,6 +194,15 @@ std::optional<QStringList> Evaluator::optionalStringListValue(
return toStringList(m_scriptEngine, v);
}
+QVariant Evaluator::variantValue(const Item *item, const QString &name, bool *propertySet)
+{
+ const ScopedJsValue jsValue(m_scriptEngine->context(), property(item, name));
+ handleEvaluationError(item, name);
+ if (propertySet)
+ *propertySet = isNonDefaultValue(item, name);
+ return getJsVariant(m_scriptEngine->context(), jsValue);
+}
+
bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const
{
const ValueConstPtr v = item->property(name);
@@ -215,24 +227,13 @@ JSValue Evaluator::scriptValue(const Item *item)
const auto edata = new EvaluationData;
edata->evaluator = this;
edata->item = item;
- edata->item->setObserver(this);
+ edata->item->addObserver(this);
scriptValue = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass);
attachPointerTo(scriptValue, edata);
return scriptValue;
}
-void Evaluator::clearCache(const Item *item)
-{
- const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item),
- m_scriptEngine->dataWithPtrClass());
- if (data) {
- for (const auto value : std::as_const(data->valueCache))
- JS_FreeValue(m_scriptEngine->context(), value);
- data->valueCache.clear();
- }
-}
-
void Evaluator::handleEvaluationError(const Item *item, const QString &name)
{
throwOnEvaluationError(m_scriptEngine, [&item, &name] () {
@@ -278,6 +279,42 @@ Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConst
return result;
}
+// This is the only function in this class that can be called from a thread that is not
+// the evaluating one. For this reason, we do not clear the cache here, as that would
+// incur enourmous synchronization overhead. Instead, we mark the item's cache as invalidated
+// and do the actual clearing only at the very few places where the cache is actually accessed.
+void Evaluator::invalidateCache(const Item *item)
+{
+ std::lock_guard lock(m_cacheInvalidationMutex);
+ m_invalidatedCaches << item;
+}
+
+void Evaluator::clearCache(const Item *item)
+{
+ std::lock_guard lock(m_cacheInvalidationMutex);
+ if (const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item),
+ m_scriptEngine->dataWithPtrClass())) {
+ clearCache(*data);
+ m_invalidatedCaches.remove(data->item);
+ }
+}
+
+void Evaluator::clearCacheIfInvalidated(EvaluationData &edata)
+{
+ std::lock_guard lock(m_cacheInvalidationMutex);
+ if (const auto it = m_invalidatedCaches.find(edata.item); it != m_invalidatedCaches.end()) {
+ clearCache(edata);
+ m_invalidatedCaches.erase(it);
+ }
+}
+
+void Evaluator::clearCache(EvaluationData &edata)
+{
+ for (const auto value : std::as_const(edata.valueCache))
+ JS_FreeValue(m_scriptEngine->context(), value);
+ edata.valueCache.clear();
+}
+
void throwOnEvaluationError(ScriptEngine *engine,
const std::function<CodeLocation()> &provideFallbackCodeLocation)
{
@@ -773,7 +810,7 @@ private:
void handle(VariantValue *variantValue) override
{
- *result = engine->toScriptValue(variantValue->value());
+ *result = engine->toScriptValue(variantValue->value(), variantValue->id());
engine->takeOwnership(*result);
}
};
@@ -853,10 +890,11 @@ static void collectValuesFromNextChain(
struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; };
static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item,
- const QString &name, const EvaluationData *data)
+ const QString &name, EvaluationData *data)
{
Evaluator * const evaluator = data->evaluator;
- const bool isModuleInstance = item->type() == ItemType::ModuleInstance;
+ const bool isModuleInstance = item->type() == ItemType::ModuleInstance
+ || item->type() == ItemType::ModuleInstancePlaceholder;
for (; item; item = item->prototype()) {
if (isModuleInstance
&& (item->type() == ItemType::Module || item->type() == ItemType::Export)) {
@@ -871,6 +909,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
evaluator->propertyDependencies());
JSValue result;
if (evaluator->cachingEnabled()) {
+ data->evaluator->clearCacheIfInvalidated(*data);
const auto result = data->valueCache.constFind(name);
if (result != data->valueCache.constEnd()) {
if (debugProperties)
@@ -893,6 +932,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
qDebug() << "[SC] cache miss " << name << ": "
<< resultToString(engine->context(), result);
if (evaluator->cachingEnabled()) {
+ data->evaluator->clearCacheIfInvalidated(*data);
const auto it = data->valueCache.find(name);
if (it != data->valueCache.end()) {
JS_FreeValue(engine->context(), it.value());
@@ -957,5 +997,15 @@ static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue o
return 0;
}
+static int getEvalPropertySafe(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop)
+{
+ try {
+ return getEvalProperty(ctx, desc, obj, prop);
+ } catch (const ErrorInfo &e) {
+ ScopedJsValue error(ctx, throwError(ctx, e.toString()));
+ return -1;
+ }
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h
index d791a4c5d..d86e08eb1 100644
--- a/src/lib/corelib/language/evaluator.h
+++ b/src/lib/corelib/language/evaluator.h
@@ -49,11 +49,13 @@
#include <QtCore/qhash.h>
#include <functional>
+#include <mutex>
#include <optional>
#include <stack>
namespace qbs {
namespace Internal {
+class EvaluationData;
class FileTags;
class Logger;
class PropertyDeclaration;
@@ -83,6 +85,8 @@ public:
std::optional<QStringList> optionalStringListValue(const Item *item, const QString &name,
bool *propertyWasSet = nullptr);
+ QVariant variantValue(const Item *item, const QString &name, bool *propertySet = nullptr);
+
void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc,
JSValue &v);
@@ -99,6 +103,8 @@ public:
void setCachingEnabled(bool enabled) { m_valueCacheEnabled = enabled; }
bool cachingEnabled() const { return m_valueCacheEnabled; }
void clearCache(const Item *item);
+ void invalidateCache(const Item *item);
+ void clearCacheIfInvalidated(EvaluationData &edata);
PropertyDependencies &propertyDependencies() { return m_propertyDependencies; }
void clearPropertyDependencies() { m_propertyDependencies.clear(); }
@@ -113,9 +119,10 @@ public:
bool isNonDefaultValue(const Item *item, const QString &name) const;
private:
- void onItemPropertyChanged(Item *item) override { clearCache(item); }
+ void onItemPropertyChanged(Item *item) override { invalidateCache(item); }
bool evaluateProperty(JSValue *result, const Item *item, const QString &name,
bool *propertyWasSet);
+ void clearCache(EvaluationData &edata);
ScriptEngine * const m_scriptEngine;
const JSClassID m_scriptClass;
@@ -124,6 +131,8 @@ private:
QString m_pathPropertiesBaseDir;
PropertyDependencies m_propertyDependencies;
std::stack<QualifiedId> m_requestedProperties;
+ std::mutex m_cacheInvalidationMutex;
+ Set<const Item *> m_invalidatedCaches;
bool m_valueCacheEnabled = false;
};
@@ -139,7 +148,9 @@ public:
m_evaluator->setPathPropertiesBaseDir(baseDir);
}
- ~EvalCacheEnabler()
+ ~EvalCacheEnabler() { reset(); }
+
+ void reset()
{
m_evaluator->setCachingEnabled(false);
m_evaluator->clearPathPropertiesBaseDir();
diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp
index 647d05aa2..e5de8f195 100644
--- a/src/lib/corelib/language/item.cpp
+++ b/src/lib/corelib/language/item.cpp
@@ -40,13 +40,13 @@
#include "item.h"
#include "builtindeclarations.h"
-#include "deprecationinfo.h"
#include "filecontext.h"
#include "itemobserver.h"
#include "itempool.h"
#include "value.h"
#include <api/languageinfo.h>
+#include <loader/loaderutils.h>
#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
@@ -60,27 +60,19 @@
namespace qbs {
namespace Internal {
-Item::Item(ItemPool *pool, ItemType type)
- : m_pool(pool)
- , m_observer(nullptr)
- , m_prototype(nullptr)
- , m_scope(nullptr)
- , m_outerItem(nullptr)
- , m_parent(nullptr)
- , m_type(type)
-{
-}
-
Item *Item::create(ItemPool *pool, ItemType type)
{
return pool->allocateItem(type);
}
-Item *Item::clone() const
+Item *Item::clone(ItemPool &pool) const
{
- Item *dup = create(pool(), type());
+ assertModuleLocked();
+
+ Item *dup = create(&pool, type());
dup->m_id = m_id;
dup->m_location = m_location;
+ dup->m_endPosition = m_endPosition;
dup->m_prototype = m_prototype;
dup->m_scope = m_scope;
dup->m_outerItem = m_outerItem;
@@ -91,14 +83,14 @@ Item *Item::clone() const
dup->m_children.reserve(m_children.size());
for (const Item * const child : std::as_const(m_children)) {
- Item *clonedChild = child->clone();
+ Item *clonedChild = child->clone(pool);
clonedChild->m_parent = dup;
dup->m_children.push_back(clonedChild);
}
for (PropertyMap::const_iterator it = m_properties.constBegin(); it != m_properties.constEnd();
++it) {
- dup->m_properties.insert(it.key(), it.value()->clone());
+ dup->m_properties.insert(it.key(), it.value()->clone(pool));
}
return dup;
@@ -128,6 +120,7 @@ QString Item::typeName() const
bool Item::hasProperty(const QString &name) const
{
+ assertModuleLocked();
const Item *item = this;
do {
if (item->m_properties.contains(name))
@@ -139,15 +132,18 @@ bool Item::hasProperty(const QString &name) const
bool Item::hasOwnProperty(const QString &name) const
{
+ assertModuleLocked();
return m_properties.contains(name);
}
ValuePtr Item::property(const QString &name) const
{
+ assertModuleLocked();
ValuePtr value;
const Item *item = this;
do {
- if ((value = item->m_properties.value(name)))
+ value = item->m_properties.value(name);
+ if (value)
break;
item = item->m_prototype;
} while (item);
@@ -156,29 +152,30 @@ ValuePtr Item::property(const QString &name) const
ValuePtr Item::ownProperty(const QString &name) const
{
+ assertModuleLocked();
return m_properties.value(name);
}
-ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate)
+ItemValuePtr Item::itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate)
{
- return itemProperty(name, itemTemplate, ItemValueConstPtr());
+ return itemProperty(name, itemTemplate, ItemValueConstPtr(), pool);
}
-ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value)
+ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool)
{
- return itemProperty(name, value->item(), value);
+ return itemProperty(name, value->item(), value, pool);
}
ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate,
- const ItemValueConstPtr &itemValue)
+ const ItemValueConstPtr &itemValue, ItemPool &pool)
{
const ValuePtr v = property(name);
if (v && v->type() == Value::ItemValueType)
return std::static_pointer_cast<ItemValue>(v);
if (!itemTemplate)
- return ItemValuePtr();
+ return {};
const bool createdByPropertiesBlock = itemValue && itemValue->createdByPropertiesBlock();
- ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()),
+ ItemValuePtr result = ItemValue::create(Item::create(&pool, itemTemplate->type()),
createdByPropertiesBlock);
setProperty(name, result);
return result;
@@ -188,7 +185,7 @@ JSSourceValuePtr Item::sourceProperty(const QString &name) const
{
ValuePtr v = property(name);
if (!v || v->type() != Value::JSSourceValueType)
- return JSSourceValuePtr();
+ return {};
return std::static_pointer_cast<JSSourceValue>(v);
}
@@ -196,7 +193,7 @@ VariantValuePtr Item::variantProperty(const QString &name) const
{
ValuePtr v = property(name);
if (!v || v->type() != Value::VariantValueType)
- return VariantValuePtr();
+ return {};
return std::static_pointer_cast<VariantValue>(v);
}
@@ -211,6 +208,28 @@ bool Item::isOfTypeOrhasParentOfType(ItemType type) const
return false;
}
+void Item::addObserver(ItemObserver *observer) const
+{
+ // Cached Module properties never change.
+ if (m_type == ItemType::Module)
+ return;
+
+ std::lock_guard lock(m_observersMutex);
+ if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS"))
+ QBS_CHECK(!contains(m_observers, observer));
+ m_observers << observer;
+}
+
+void Item::removeObserver(ItemObserver *observer) const
+{
+ if (m_type == ItemType::Module)
+ return;
+ std::lock_guard lock(m_observersMutex);
+ const auto it = std::find(m_observers.begin(), m_observers.end(), observer);
+ QBS_CHECK(it != m_observers.end());
+ m_observers.erase(it);
+}
+
PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExpired) const
{
auto it = m_propertyDeclarations.find(name);
@@ -230,12 +249,12 @@ void Item::addModule(const Item::Module &module)
QBS_CHECK(none_of(m_modules, [&](const Module &m) {
if (m.name != module.name)
return false;
- if (!!module.productInfo != !!m.productInfo)
+ if (!!module.product != !!m.product)
return true;
- if (!module.productInfo)
+ if (!module.product)
return true;
- if (module.productInfo->multiplexId == m.productInfo->multiplexId
- && module.productInfo->profile == m.productInfo->profile) {
+ if (module.product->multiplexConfigurationId == m.product->multiplexConfigurationId
+ && module.product->profileName == m.product->profileName) {
return true;
}
return false;
@@ -245,17 +264,13 @@ void Item::addModule(const Item::Module &module)
m_modules.push_back(module);
}
-void Item::setObserver(ItemObserver *observer) const
-{
- QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten
- m_observer = observer;
-}
-
void Item::setProperty(const QString &name, const ValuePtr &value)
{
+ assertModuleLocked();
m_properties.insert(name, value);
- if (m_observer)
- m_observer->onItemPropertyChanged(this);
+ std::lock_guard lock(m_observersMutex);
+ for (ItemObserver * const observer : m_observers)
+ observer->onItemPropertyChanged(this);
}
void Item::dump() const
@@ -270,8 +285,14 @@ bool Item::isPresentModule() const
return v && v->type() == Value::JSSourceValueType;
}
+bool Item::isFallbackModule() const
+{
+ return hasProperty(QLatin1String("__fallback"));
+}
+
void Item::setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger)
{
+ assertModuleLocked();
const BuiltinDeclarations &builtins = BuiltinDeclarations::instance();
const auto properties = builtins.declarationsForType(type()).properties();
for (const PropertyDeclaration &pd : properties) {
@@ -353,8 +374,39 @@ void Item::dump(int indentation) const
}
}
+void Item::lockModule() const
+{
+ QBS_CHECK(m_type == ItemType::Module);
+ m_moduleMutex.lock();
+#ifndef NDEBUG
+ QBS_CHECK(!m_moduleLocked);
+ m_moduleLocked = true;
+#endif
+}
+
+void Item::unlockModule() const
+{
+ QBS_CHECK(m_type == ItemType::Module);
+#ifndef NDEBUG
+ QBS_CHECK(m_moduleLocked);
+ m_moduleLocked = false;
+#endif
+ m_moduleMutex.unlock();
+}
+
+// This safeguard verifies that all contexts which access Module properties have really
+// acquired the lock via ModuleItemLocker, as they must.
+void Item::assertModuleLocked() const
+{
+#ifndef NDEBUG
+ if (m_type == ItemType::Module)
+ QBS_CHECK(m_moduleLocked);
+#endif
+}
+
void Item::removeProperty(const QString &name)
{
+ assertModuleLocked();
m_properties.remove(name);
}
@@ -457,5 +509,10 @@ void setScopeForDescendants(Item *item, Item *scope)
}
}
+CodeRange Item::codeRange() const
+{
+ return {{m_location.line(), m_location.column()}, m_endPosition};
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index 337cad78a..d0dde98c4 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -53,7 +53,9 @@
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
-#include <optional>
+#include <atomic>
+#include <mutex>
+#include <utility>
#include <vector>
namespace qbs {
@@ -64,32 +66,45 @@ namespace Internal {
class ItemObserver;
class ItemPool;
class Logger;
+class ModuleItemLocker;
+class ProductContext;
class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed
{
friend class ASTPropertiesItemHandler;
friend class ItemPool;
friend class ItemReaderASTVisitor;
+ friend class ModuleItemLocker;
Q_DISABLE_COPY(Item)
- Item(ItemPool *pool, ItemType type);
+ Item(ItemType type) : m_type(type) {}
public:
struct Module
{
QualifiedId name;
Item *item = nullptr;
- struct ProductInfo {
- ProductInfo(Item *i, const QString &m, const QString &p)
- : item(i), multiplexId(m), profile(p) {}
- Item *item = nullptr;
- QString multiplexId;
- QString profile;
- };
- std::optional<ProductInfo> productInfo; // Set if and only if the dep is a product.
+ ProductContext *product = nullptr; // Set if and only if the dep is a product.
- // All items that declared an explicit dependency on this module. Can contain any
+ // All the sites that declared an explicit dependency on this module. Can contain any
// number of module instances and at most one product.
- std::vector<Item *> loadingItems;
+ using ParametersWithPriority = std::pair<QVariantMap, int>;
+ struct LoadContext {
+ LoadContext(Item *dependsItem,
+ const ParametersWithPriority &parameters)
+ : dependsItem(dependsItem), parameters(parameters) {}
+ LoadContext(Item *dependsItem, ParametersWithPriority &&parameters)
+ : dependsItem(dependsItem), parameters(std::move(parameters)) {}
+
+ LoadContext(const LoadContext &) = default;
+ LoadContext(LoadContext &&) = default;
+ LoadContext &operator=(const LoadContext &) = default;
+ LoadContext &operator=(LoadContext &&) = default;
+
+ Item *loadingItem() const { return dependsItem ? dependsItem->parent() : nullptr; }
+ Item *dependsItem;
+ ParametersWithPriority parameters;
+ };
+ std::vector<LoadContext> loadContexts;
QVariantMap parameters;
VersionRange versionRange;
@@ -105,11 +120,11 @@ public:
using PropertyMap = QMap<QString, ValuePtr>;
static Item *create(ItemPool *pool, ItemType type);
- Item *clone() const;
- ItemPool *pool() const { return m_pool; }
+ Item *clone(ItemPool &pool) const;
const QString &id() const { return m_id; }
const CodeLocation &location() const { return m_location; }
+ CodeRange codeRange() const;
Item *prototype() const { return m_prototype; }
Item *rootPrototype();
Item *scope() const { return m_scope; }
@@ -119,8 +134,8 @@ public:
const QList<Item *> &children() const { return m_children; }
QList<Item *> &children() { return m_children; }
Item *child(ItemType type, bool checkForMultiple = true) const;
- const PropertyMap &properties() const { return m_properties; }
- PropertyMap &properties() { return m_properties; }
+ const PropertyMap &properties() const { assertModuleLocked(); return m_properties; }
+ PropertyMap &properties() { assertModuleLocked(); return m_properties; }
const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; }
PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const;
@@ -141,18 +156,20 @@ public:
bool hasOwnProperty(const QString &name) const;
ValuePtr property(const QString &name) const;
ValuePtr ownProperty(const QString &name) const;
- ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate = nullptr);
- ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value);
+ ItemValuePtr itemProperty(const QString &name, ItemPool &pool, const Item *itemTemplate = nullptr);
+ ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value, ItemPool &pool);
JSSourceValuePtr sourceProperty(const QString &name) const;
VariantValuePtr variantProperty(const QString &name) const;
bool isOfTypeOrhasParentOfType(ItemType type) const;
- void setObserver(ItemObserver *observer) const;
+ void addObserver(ItemObserver *observer) const;
+ void removeObserver(ItemObserver *observer) const;
void setProperty(const QString &name, const ValuePtr &value);
- void setProperties(const PropertyMap &props) { m_properties = props; }
+ void setProperties(const PropertyMap &props) { assertModuleLocked(); m_properties = props; }
void removeProperty(const QString &name);
void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration);
void setPropertyDeclarations(const PropertyDeclarationMap &decls);
void setLocation(const CodeLocation &location) { m_location = location; }
+ void setEndPosition(const CodePosition &position) { m_endPosition = position; }
void setPrototype(Item *prototype) { m_prototype = prototype; }
void setFile(const FileContextPtr &file) { m_file = file; }
void setId(const QString &id) { m_id = id; }
@@ -165,6 +182,7 @@ public:
static void removeChild(Item *parent, Item *child);
void dump() const;
bool isPresentModule() const;
+ bool isFallbackModule() const;
void setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger);
void copyProperty(const QString &propertyName, Item *target) const;
void overrideProperties(
@@ -180,18 +198,23 @@ public:
private:
ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate,
- const ItemValueConstPtr &itemValue);
+ const ItemValueConstPtr &itemValue, ItemPool &pool);
void dump(int indentation) const;
- ItemPool *m_pool;
- mutable ItemObserver *m_observer;
+ void lockModule() const;
+ void unlockModule() const;
+ void assertModuleLocked() const;
+
+ mutable std::vector<ItemObserver *> m_observers;
+ mutable std::mutex m_observersMutex;
QString m_id;
CodeLocation m_location;
- Item *m_prototype;
- Item *m_scope;
- Item *m_outerItem;
- Item *m_parent;
+ CodePosition m_endPosition;
+ Item *m_prototype = nullptr;
+ Item *m_scope = nullptr;
+ Item *m_outerItem = nullptr;
+ Item *m_parent = nullptr;
QList<Item *> m_children;
FileContextPtr m_file;
PropertyMap m_properties;
@@ -199,6 +222,10 @@ private:
PropertyDeclarationMap m_expiredPropertyDeclarations;
Modules m_modules;
ItemType m_type;
+ mutable std::mutex m_moduleMutex;
+#ifndef NDEBUG
+ mutable std::atomic_bool m_moduleLocked = false;
+#endif
};
inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; }
@@ -207,6 +234,22 @@ Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString
Item *module);
void setScopeForDescendants(Item *item, Item *scope);
+// This mechanism is needed because Module items are shared between products (not doing so
+// would be prohibitively expensive).
+// The competing accesses are between
+// - Attaching a temporary qbs module for evaluating the Module condition.
+// - Cloning the module when creating an instance.
+// - Directly accessing Module properties, which happens rarely (as opposed to properties of
+// an instance).
+class ModuleItemLocker
+{
+public:
+ ModuleItemLocker(const Item &item) : m_item(item) { item.lockModule(); }
+ ~ModuleItemLocker() { m_item.unlockModule(); }
+private:
+ const Item &m_item;
+};
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp
index ccd22fe2e..6552f92ef 100644
--- a/src/lib/corelib/language/itempool.cpp
+++ b/src/lib/corelib/language/itempool.cpp
@@ -53,7 +53,7 @@ ItemPool::~ItemPool()
Item *ItemPool::allocateItem(const ItemType &type)
{
- const auto item = new (&m_pool) Item(this, type);
+ const auto item = new (&m_pool) Item(type);
m_items.push_back(item);
return item;
}
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index 33fd3c6a4..b3f4b2a64 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -51,7 +51,8 @@
#include <buildgraph/rulegraph.h> // TODO: Move to language?
#include <buildgraph/transformer.h>
#include <jsextensions/jsextensions.h>
-#include <loader/productitemmultiplexer.h>
+#include <language/value.h>
+#include <loader/loaderutils.h>
#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -117,6 +118,12 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const
return Internal::any_of(m_importedFilesUsed, criterion);
}
+void Probe::restoreValues()
+{
+ for (auto it = m_properties.begin(), end = m_properties.end(); it != end; ++it) {
+ m_values[it.key()] = VariantValue::createStored(it.value());
+ }
+}
/*!
* \class SourceArtifact
@@ -137,41 +144,16 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const
/*!
* \variable ResolvedGroup::files
* \brief The files listed in the group item's "files" binding.
- * Note that these do not include expanded wildcards.
*/
/*!
* \variable ResolvedGroup::wildcards
- * \brief Represents the wildcard elements in this group's "files" binding.
+ * \brief Represents the wildcard patterns in this group's "files" binding.
* If no wildcards are specified there, this variable is null.
* \sa SourceWildCards
*/
/*!
- * \brief Returns all files specified in the group item as source artifacts.
- * This includes the expanded list of wildcards.
- */
-std::vector<SourceArtifactPtr> ResolvedGroup::allFiles() const
-{
- std::vector<SourceArtifactPtr> lst = files;
- if (wildcards)
- lst << wildcards->files;
- return lst;
-}
-
-void ResolvedGroup::load(PersistentPool &pool)
-{
- serializationOp<PersistentPool::Load>(pool);
- if (wildcards)
- wildcards->group = this;
-}
-
-void ResolvedGroup::store(PersistentPool &pool)
-{
- serializationOp<PersistentPool::Store>(pool);
-}
-
-/*!
* \class RuleArtifact
* \brief The \c RuleArtifact class represents an Artifact item encountered in the context
* of a Rule item.
@@ -325,7 +307,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const
{
std::vector<SourceArtifactPtr> lst;
for (const auto &group : groups)
- lst << group->allFiles();
+ lst << group->files;
return lst;
}
@@ -338,7 +320,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const
std::vector<SourceArtifactPtr> lst;
for (const auto &group : groups) {
if (group->enabled)
- lst << group->allFiles();
+ lst << group->files;
}
return lst;
}
@@ -374,6 +356,8 @@ void ResolvedProduct::load(PersistentPool &pool)
rule->product = this;
for (const ResolvedModulePtr &module : modules)
module->product = this;
+ for (const auto &group: groups)
+ group->restoreWildcards(buildDirectory());
}
void ResolvedProduct::store(PersistentPool &pool)
@@ -427,7 +411,7 @@ QString ResolvedProduct::uniqueName() const
QString ResolvedProduct::fullDisplayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+ return fullProductDisplayName(name, multiplexConfigurationId);
}
QString ResolvedProduct::profile() const
@@ -503,6 +487,19 @@ QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const
return m_executablePathCache.value(origFilePath);
}
+void ResolvedGroup::restoreWildcards(const QString &buildDir)
+{
+ if (wildcards) {
+ wildcards->buildDir = buildDir;
+ wildcards->prefix = prefix;
+ wildcards->baseDir = FileInfo::path(location.filePath());
+ for (const auto &sourceArtifact : files) {
+ if (sourceArtifact->fromWildcard)
+ wildcards->expandedFiles += sourceArtifact->absoluteFilePath;
+ }
+ }
+}
+
ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(nullptr)
{
@@ -710,25 +707,22 @@ void TopLevelProject::cleanupModuleProviderOutput()
* \brief The \c SourceArtifacts resulting from the expanded list of matching files.
*/
-Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
- const QString &baseDir, const QString &buildDir)
+void SourceWildCards::expandPatterns()
{
- Set<QString> files = expandPatterns(group, patterns, baseDir, buildDir);
- files -= expandPatterns(group, excludePatterns, baseDir, buildDir);
- return files;
+ dirTimeStamps.clear();
+ expandedFiles = expandPatterns(patterns) - expandPatterns(excludePatterns);
}
-Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
- const QStringList &patterns, const QString &baseDir, const QString &buildDir)
+Set<QString> SourceWildCards::expandPatterns(const QStringList &patterns)
{
Set<QString> files;
- QString expandedPrefix = group->prefix;
+ QString expandedPrefix = prefix;
if (expandedPrefix.startsWith(StringConstants::tildeSlash()))
expandedPrefix.replace(0, 1, QDir::homePath());
for (QString pattern : patterns) {
pattern.prepend(expandedPrefix);
pattern.replace(QLatin1Char('\\'), QLatin1Char('/'));
- QStringList parts = pattern.split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS);
+ QStringList parts = pattern.split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (FileInfo::isAbsolute(pattern)) {
QString rootDir;
if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) {
@@ -738,18 +732,17 @@ Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
} else {
rootDir = QLatin1Char('/');
}
- expandPatterns(files, group, parts, rootDir, buildDir);
+ expandPatterns(files, parts, rootDir);
} else {
- expandPatterns(files, group, parts, baseDir, buildDir);
+ expandPatterns(files, parts, baseDir);
}
}
return files;
}
-void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &group,
- const QStringList &parts,
- const QString &baseDir, const QString &buildDir)
+void SourceWildCards::expandPatterns(Set<QString> &result, const QStringList &parts,
+ const QString &baseDir)
{
// People might build directly in the project source directory. This is okay, since
// we keep the build data in a "container" directory. However, we must make sure we don't
@@ -757,8 +750,6 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &
if (baseDir.startsWith(buildDir))
return;
- dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified());
-
QStringList changed_parts = parts;
bool recursive = false;
QString part = changed_parts.takeFirst();
@@ -785,8 +776,12 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &
: QDir::Files | QDir::System
| QDir::Dirs; // This one is needed to get symbolic links to directories
- if (isDir && !FileInfo::isPattern(filePattern))
+ if (FileInfo::isPattern(filePattern)) {
+ if (!recursive)
+ dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified());
+ } else if (isDir) {
itFilters |= QDir::Hidden;
+ }
if (filePattern != StringConstants::dotDot() && filePattern != StringConstants::dot())
itFilters |= QDir::NoDotAndDotDot;
@@ -798,16 +793,28 @@ void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &
continue; // See above.
if (!isDir && it.fileInfo().isDir() && !it.fileInfo().isSymLink())
continue;
- if (isDir) {
- expandPatterns(result, group, changed_parts, filePath, buildDir);
- } else {
- if (parentDir != baseDir)
- dirTimeStamps.emplace_back(parentDir, FileInfo(baseDir).lastModified());
+ if (isDir)
+ expandPatterns(result, changed_parts, filePath);
+ else
result += QDir::cleanPath(filePath);
- }
}
}
+bool SourceWildCards::hasChangedSinceExpansion() const
+{
+ const bool reExpansionRequired =
+ Internal::any_of(dirTimeStamps,
+ [](const std::pair<QString, FileTime> &pair) {
+ return FileInfo(pair.first).lastModified() > pair.second;
+ });
+ if (reExpansionRequired)
+ return true;
+
+ auto wc = *this;
+ wc.expandPatterns();
+ return this->expandedFiles != wc.expandedFiles;
+}
+
template<typename L>
QMap<QString, typename L::value_type> listToMap(const L &list)
{
@@ -931,7 +938,7 @@ bool operator==(const ExportedProperty &p1, const ExportedProperty &p2)
bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2)
{
- return d1.name == d2.name && d1.moduleProperties == d2.moduleProperties;
+ return d1.name == d2.name && qVariantMapsEqual(d1.moduleProperties, d2.moduleProperties);
}
bool equals(const std::vector<ExportedItemPtr> &l1, const std::vector<ExportedItemPtr> &l2)
@@ -958,20 +965,19 @@ bool operator==(const ExportedModule &m1, const ExportedModule &m2)
for (auto it1 = m1.cbegin(), it2 = m2.cbegin(); it1 != m1.cend(); ++it1, ++it2) {
if (it1.key()->name != it2.key()->name)
return false;
- if (it1.value() != it2.value())
+ if (!qVariantMapsEqual(it1.value(), it2.value()))
return false;
}
return true;
};
- return m1.propertyValues == m2.propertyValues
- && m1.modulePropertyValues == m2.modulePropertyValues
- && equals(m1.children, m2.children)
- && m1.m_properties == m2.m_properties
- && m1.importStatements == m2.importStatements
- && m1.productDependencies.size() == m2.productDependencies.size()
- && m1.productDependencies == m2.productDependencies
- && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters);
+ return qVariantMapsEqual(m1.propertyValues, m2.propertyValues)
+ && qVariantMapsEqual(m1.modulePropertyValues, m2.modulePropertyValues)
+ && equals(m1.children, m2.children) && m1.m_properties == m2.m_properties
+ && m1.importStatements == m2.importStatements
+ && m1.productDependencies.size() == m2.productDependencies.size()
+ && m1.productDependencies == m2.productDependencies
+ && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters);
}
JSValue PrivateScriptFunction::getFunction(ScriptEngine *engine, const QString &errorMessage) const
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index 1dae572a1..774d703d0 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -49,6 +49,7 @@
#include <buildgraph/forward_decls.h>
#include <tools/codelocation.h>
+#include <tools/fileinfo.h>
#include <tools/filetime.h>
#include <tools/joblimits.h>
#include <tools/persistence.h>
@@ -116,17 +117,20 @@ public:
const QString &configureScript,
const QVariantMap &properties,
const QVariantMap &initialProperties,
+ const QMap<QString, VariantValuePtr> &values,
const std::vector<QString> &importedFilesUsed)
{
return ProbeConstPtr(new Probe(globalId, location, condition, configureScript, properties,
- initialProperties, importedFilesUsed));
+ initialProperties, values, importedFilesUsed));
}
const QString &globalId() const { return m_globalId; }
bool condition() const { return m_condition; }
+ const CodeLocation &location() const { return m_location; }
const QString &configureScript() const { return m_configureScript; }
const QVariantMap &properties() const { return m_properties; }
const QVariantMap &initialProperties() const { return m_initialProperties; }
+ const QMap<QString, VariantValuePtr> &values() const { return m_values; }
const std::vector<QString> &importedFilesUsed() const { return m_importedFilesUsed; }
bool needsReconfigure(const FileTime &referenceTime) const;
@@ -134,6 +138,8 @@ public:
{
pool.serializationOp<opType>(m_globalId, m_location, m_condition, m_configureScript,
m_properties, m_initialProperties, m_importedFilesUsed);
+ if constexpr (opType == PersistentPool::OpType::Load)
+ restoreValues();
}
private:
@@ -144,21 +150,27 @@ private:
QString configureScript,
QVariantMap properties,
QVariantMap initialProperties,
+ QMap<QString, VariantValuePtr> values,
std::vector<QString> importedFilesUsed)
: m_globalId(std::move(globalId))
, m_location(location)
, m_configureScript(std::move(configureScript))
, m_properties(std::move(properties))
, m_initialProperties(std::move(initialProperties))
+ , m_values(std::move(values))
, m_importedFilesUsed(std::move(importedFilesUsed))
, m_condition(condition)
- {}
+ {
+ }
+
+ void restoreValues();
QString m_globalId;
CodeLocation m_location;
QString m_configureScript;
QVariantMap m_properties;
QVariantMap m_initialProperties;
+ QMap<QString, VariantValuePtr> m_values;
std::vector<QString> m_importedFilesUsed;
bool m_condition = false;
};
@@ -230,11 +242,12 @@ public:
bool overrideFileTags;
QString targetOfModule;
PropertyMapPtr properties;
+ bool fromWildcard;
template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool)
{
pool.serializationOp<opType>(absoluteFilePath, fileTags, overrideFileTags, properties,
- targetOfModule);
+ targetOfModule, fromWildcard);
}
private:
@@ -248,26 +261,28 @@ inline bool operator!=(const SourceArtifactInternal &sa1, const SourceArtifactIn
class SourceWildCards
{
public:
- Set<QString> expandPatterns(const GroupConstPtr &group, const QString &baseDir,
- const QString &buildDir);
+ void expandPatterns();
+ bool hasChangedSinceExpansion() const;
- const ResolvedGroup *group = nullptr; // The owning group.
+ // to be restored by the owning class
+ QString prefix;
+ QString baseDir;
+ QString buildDir;
+ Set<QString> expandedFiles;
+
+ // stored
QStringList patterns;
QStringList excludePatterns;
std::vector<std::pair<QString, FileTime>> dirTimeStamps;
- std::vector<SourceArtifactPtr> files;
template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool)
{
- pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps, files);
+ pool.serializationOp<opType>(patterns, excludePatterns, dirTimeStamps);
}
private:
- Set<QString> expandPatterns(const GroupConstPtr &group, const QStringList &patterns,
- const QString &baseDir, const QString &buildDir);
- void expandPatterns(Set<QString> &result, const GroupConstPtr &group,
- const QStringList &parts, const QString &baseDir,
- const QString &buildDir);
+ Set<QString> expandPatterns(const QStringList &patterns);
+ void expandPatterns(Set<QString> &result, const QStringList &parts, const QString &baseDir);
};
class QBS_AUTOTEST_EXPORT ResolvedGroup
@@ -287,13 +302,9 @@ public:
QString targetOfModule;
bool overrideTags = false;
- std::vector<SourceArtifactPtr> allFiles() const;
-
- void load(PersistentPool &pool);
- void store(PersistentPool &pool);
+ void restoreWildcards(const QString &buildDir);
-private:
- template<PersistentPool::OpType opType> void serializationOp(PersistentPool &pool)
+ template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool)
{
pool.serializationOp<opType>(name, enabled, location, prefix, files, wildcards, properties,
fileTags, targetOfModule, overrideTags);
@@ -692,6 +703,7 @@ public:
QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()".
QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults; // Results of calls to "File.directoryEntries()".
QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()".
+ CodeLinks codeLinks;
std::unique_ptr<ProjectBuildData> buildData;
BuildGraphLocker *bgLocker; // This holds the system-wide build graph file lock.
bool locked; // This is the API-level lock for the project instance.
@@ -723,7 +735,7 @@ private:
directoryEntriesResults, fileLastModifiedResults, environment,
probes, profileConfigs, overriddenValues, buildSystemFiles,
lastStartResolveTime, lastEndResolveTime, warningsEncountered,
- buildData, moduleProviderInfo);
+ buildData, moduleProviderInfo, codeLinks);
}
void load(PersistentPool &pool) override;
void store(PersistentPool &pool) override;
diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h
index 500d370cc..c35ed220a 100644
--- a/src/lib/corelib/language/moduleproviderinfo.h
+++ b/src/lib/corelib/language/moduleproviderinfo.h
@@ -83,24 +83,25 @@ public:
QualifiedId name;
QVariantMap config;
QString providerFile;
+ bool isEager{true};
QStringList searchPaths;
+ QHash<QString, QStringList> searchPathsByModule;
bool transientOutput = false; // Not to be serialized.
};
-using ModuleProviderInfoList = std::vector<ModuleProviderInfo>;
+using ModuleProvidersCacheKey = std::tuple<
+ QString /*name*/,
+ QString /*moduleName*/,
+ QVariantMap /*config*/,
+ QVariantMap /*qbsModule*/,
+ int /*lookup*/
+>;
+using ModuleProvidersCache = QHash<ModuleProvidersCacheKey, ModuleProviderInfo>;
// Persistent info stored between sessions
class StoredModuleProviderInfo
{
public:
- using CacheKey = std::tuple<
- QString /*name*/,
- QVariantMap /*config*/,
- QVariantMap /*qbsModule*/,
- int /*lookup*/
- >;
- using ModuleProvidersCache = QHash<CacheKey, ModuleProviderInfo>;
-
ModuleProvidersCache providers;
template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool)
diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp
index 215918462..d56ab3bb0 100644
--- a/src/lib/corelib/language/propertydeclaration.cpp
+++ b/src/lib/corelib/language/propertydeclaration.cpp
@@ -46,6 +46,7 @@
#include "value.h"
#include <api/languageinfo.h>
+#include <loader/loaderutils.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/setupprojectparameters.h>
@@ -105,7 +106,6 @@ public:
DeprecationInfo deprecationInfo;
};
-
PropertyDeclaration::PropertyDeclaration()
: d(new PropertyDeclarationData)
{
@@ -308,17 +308,78 @@ QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t,
return c;
}
+QVariant PropertyDeclaration::typedNullValue() const
+{
+ switch (type()) {
+ case PropertyDeclaration::Boolean:
+ return typedNullVariant<bool>();
+ case PropertyDeclaration::Integer:
+ return typedNullVariant<int>();
+ case PropertyDeclaration::VariantList:
+ return typedNullVariant<QVariantList>();
+ case PropertyDeclaration::String:
+ case PropertyDeclaration::Path:
+ return typedNullVariant<QString>();
+ case PropertyDeclaration::StringList:
+ case PropertyDeclaration::PathList:
+ return typedNullVariant<QStringList>();
+ default:
+ return {};
+ }
+}
+
+bool PropertyDeclaration::shouldCheckAllowedValues() const
+{
+ return isValid()
+ && (d->type == PropertyDeclaration::String || d->type == PropertyDeclaration::StringList)
+ && !d->allowedValues.empty();
+}
+
+void PropertyDeclaration::checkAllowedValues(
+ const QVariant &value,
+ const CodeLocation &loc,
+ const QString &key,
+ LoaderState &loaderState) const
+{
+ const auto type = d->type;
+ if (!shouldCheckAllowedValues())
+ return;
+
+ if (value.isNull())
+ return;
+
+ const auto &allowedValues = d->allowedValues;
+
+ const auto checkValue = [&loc, &allowedValues, &key, &loaderState](const QString &value)
+ {
+ if (!allowedValues.contains(value)) {
+ const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.")
+ .arg(value, key);
+ ErrorInfo error(message, loc);
+ handlePropertyError(error, loaderState.parameters(), loaderState.logger());
+ }
+ };
+
+ if (type == PropertyDeclaration::StringList) {
+ const auto strings = value.toStringList();
+ for (const auto &string: strings) {
+ checkValue(string);
+ }
+ } else if (type == PropertyDeclaration::String) {
+ checkValue(value.toString());
+ }
+}
+
namespace {
class PropertyDeclarationCheck : public ValueHandler
{
public:
- PropertyDeclarationCheck(const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger)
- : m_disabledItems(disabledItems)
- , m_params(params)
- , m_logger(logger)
- { }
- void operator()(Item *item) { handleItem(item); }
+ PropertyDeclarationCheck(LoaderState &loaderState) : m_loaderState(loaderState) {}
+ void operator()(Item *item)
+ {
+ m_checkingProject = item->type() == ItemType::Project;
+ handleItem(item);
+ }
private:
void handle(JSSourceValue *value) override
@@ -326,7 +387,7 @@ private:
if (!value->createdByPropertiesBlock()) {
const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
.arg(m_currentName), value->location());
- handlePropertyError(error, m_params, m_logger);
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
}
}
void handle(ItemValue *value) override
@@ -365,7 +426,7 @@ private:
const ErrorInfo error(Tr::tr("Item '%1' is not declared. "
"Did you forget to add a Depends item?")
.arg(m_currentModuleName.toString()), location);
- handlePropertyError(error, m_params, m_logger);
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
return false;
}
@@ -373,16 +434,19 @@ private:
}
void handleItem(Item *item)
{
+ if (m_checkingProject && item->type() == ItemType::Product)
+ return;
if (!m_handledItems.insert(item).second)
return;
- if (m_disabledItems.contains(item)
- || item->type() == ItemType::Module
+ if (item->type() == ItemType::Module
|| item->type() == ItemType::Export
|| (item->type() == ItemType::ModuleInstance && !item->isPresentModule())
|| item->type() == ItemType::Properties
// The Properties child of a SubProject item is not a regular item.
- || item->type() == ItemType::PropertiesInSubProject) {
+ || item->type() == ItemType::PropertiesInSubProject
+
+ || m_loaderState.topLevelProject().isDisabledItem(item)) {
return;
}
@@ -395,9 +459,12 @@ private:
const PropertyDeclaration decl = item->propertyDeclaration(it.key());
if (decl.isValid()) {
const ErrorInfo deprecationError = decl.checkForDeprecation(
- m_params.deprecationWarningMode(), it.value()->location(), m_logger);
- if (deprecationError.hasError())
- handlePropertyError(deprecationError, m_params, m_logger);
+ m_loaderState.parameters().deprecationWarningMode(), it.value()->location(),
+ m_loaderState.logger());
+ if (deprecationError.hasError()) {
+ handlePropertyError(deprecationError, m_loaderState.parameters(),
+ m_loaderState.logger());
+ }
continue;
}
m_currentName = it.key();
@@ -429,20 +496,18 @@ private:
Item *parentItem() const { return m_parentItems.back(); }
- const Set<Item *> &m_disabledItems;
+ LoaderState &m_loaderState;
Set<Item *> m_handledItems;
std::vector<Item *> m_parentItems;
QualifiedId m_currentModuleName;
QString m_currentName;
- const SetupProjectParameters &m_params;
- Logger &m_logger;
+ bool m_checkingProject = false;
};
} // namespace
-void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger)
+void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState)
{
- PropertyDeclarationCheck(disabledItems, params, logger)(topLevelItem);
+ (PropertyDeclarationCheck(loaderState))(topLevelItem);
}
} // namespace Internal
diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h
index 8c87faedb..79a39ecbd 100644
--- a/src/lib/corelib/language/propertydeclaration.h
+++ b/src/lib/corelib/language/propertydeclaration.h
@@ -41,7 +41,6 @@
#define QBS_PROPERTYDECLARATION_H
#include <tools/deprecationwarningmode.h>
-#include <tools/set.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
@@ -53,11 +52,11 @@ QT_END_NAMESPACE
namespace qbs {
class CodeLocation;
class ErrorInfo;
-class SetupProjectParameters;
namespace Internal {
class DeprecationInfo;
class PropertyDeclarationData;
class Item;
+class LoaderState;
class Logger;
class PropertyDeclaration
@@ -129,13 +128,20 @@ public:
static QVariant convertToPropertyType(
const QVariant &v, Type t, const QStringList &namePrefix, const QString &key);
+ QVariant typedNullValue() const;
+
+ bool shouldCheckAllowedValues() const;
+ void checkAllowedValues(
+ const QVariant &value,
+ const CodeLocation &loc,
+ const QString &key,
+ LoaderState &loaderState) const;
private:
QSharedDataPointer<PropertyDeclarationData> d;
};
-void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger);
+void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState);
} // namespace Internal
diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h
index 83e18ba48..af551cf6f 100644
--- a/src/lib/corelib/language/propertymapinternal.h
+++ b/src/lib/corelib/language/propertymapinternal.h
@@ -77,7 +77,7 @@ private:
inline bool operator==(const PropertyMapInternal &lhs, const PropertyMapInternal &rhs)
{
- return lhs.m_value == rhs.m_value;
+ return qVariantsEqual(lhs.m_value, rhs.m_value);
}
QVariant QBS_AUTOTEST_EXPORT moduleProperty(const QVariantMap &properties,
diff --git a/src/lib/corelib/language/qualifiedid.cpp b/src/lib/corelib/language/qualifiedid.cpp
index 9eb0e9463..87248ac21 100644
--- a/src/lib/corelib/language/qualifiedid.cpp
+++ b/src/lib/corelib/language/qualifiedid.cpp
@@ -58,7 +58,7 @@ QualifiedId::QualifiedId(const QStringList &nameParts)
QualifiedId QualifiedId::fromString(const QString &str)
{
- return QualifiedId(str.split(QLatin1Char('.')));
+ return {str.split(QLatin1Char('.'))};
}
QString QualifiedId::toString() const
diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp
index 11d41b5c2..998384547 100644
--- a/src/lib/corelib/language/scriptengine.cpp
+++ b/src/lib/corelib/language/scriptengine.cpp
@@ -58,6 +58,7 @@
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
+#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qfile.h>
@@ -196,6 +197,10 @@ void ScriptEngine::reset()
JS_FreeValue(m_context, e.second);
m_jsFileCache.clear();
+ for (const JSValue &s : std::as_const(m_jsValueCache))
+ JS_FreeValue(m_context, s);
+ m_jsValueCache.clear();
+
for (auto it = m_evalResults.cbegin(); it != m_evalResults.cend(); ++it) {
for (int i = 0; i < it.value(); ++i)
JS_FreeValue(m_context, it.key());
@@ -481,6 +486,38 @@ void ScriptEngine::addInternalExtension(const char *name, JSValue ext)
m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext));
}
+JSValue ScriptEngine::asJsValue(const QVariant &v, quintptr id, bool frozen)
+{
+ if (v.isNull())
+ return JS_UNDEFINED;
+ switch (static_cast<QMetaType::Type>(v.userType())) {
+ case QMetaType::QByteArray:
+ return asJsValue(v.toByteArray());
+ case QMetaType::QString:
+ return asJsValue(v.toString());
+ case QMetaType::QStringList:
+ return asJsValue(v.toStringList());
+ case QMetaType::QVariantList:
+ return asJsValue(v.toList(), id, frozen);
+ case QMetaType::Int:
+ case QMetaType::UInt:
+ return JS_NewInt32(m_context, v.toInt());
+ case QMetaType::Long:
+ case QMetaType::ULong:
+ case QMetaType::LongLong:
+ case QMetaType::ULongLong:
+ return JS_NewInt64(m_context, v.toInt());
+ case QMetaType::Bool:
+ return JS_NewBool(m_context, v.toBool());
+ case QMetaType::QDateTime:
+ return JS_NewDate(m_context, v.toDateTime().toMSecsSinceEpoch());
+ case QMetaType::QVariantMap:
+ return asJsValue(v.toMap(), id, frozen);
+ default:
+ return JS_UNDEFINED;
+ }
+}
+
JSValue ScriptEngine::asJsValue(const QByteArray &s)
{
return JS_NewArrayBufferCopy(
@@ -506,12 +543,21 @@ JSValue ScriptEngine::asJsValue(const QStringList &l)
return array;
}
-JSValue ScriptEngine::asJsValue(const QVariantMap &m)
+JSValue ScriptEngine::asJsValue(const QVariantMap &m, quintptr id, bool frozen)
{
+ const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd();
+ if (it != m_jsValueCache.constEnd())
+ return JS_DupValue(m_context, it.value());
+ frozen = id || frozen;
JSValue obj = JS_NewObject(m_context);
for (auto it = m.begin(); it != m.end(); ++it)
- setJsProperty(m_context, obj, it.key(), makeJsVariant(m_context, it.value()));
- return obj;
+ setJsProperty(m_context, obj, it.key(), asJsValue(it.value(), 0, frozen));
+ if (frozen)
+ JS_ObjectSeal(m_context, obj, true);
+ if (!id)
+ return obj;
+ m_jsValueCache[id] = obj;
+ return JS_DupValue(m_context, obj);
}
void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue value)
@@ -520,13 +566,22 @@ void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue va
setJsProperty(m_context, globalObject, property, value);
}
-JSValue ScriptEngine::asJsValue(const QVariantList &l)
+JSValue ScriptEngine::asJsValue(const QVariantList &l, quintptr id, bool frozen)
{
+ const auto it = id ? m_jsValueCache.constFind(id) : m_jsValueCache.constEnd();
+ if (it != m_jsValueCache.constEnd())
+ return JS_DupValue(m_context, it.value());
+ frozen = id || frozen;
JSValue array = JS_NewArray(m_context);
setJsProperty(m_context, array, QLatin1String("length"), JS_NewInt32(m_context, l.size()));
for (int i = 0; i < l.size(); ++i)
- JS_SetPropertyUint32(m_context, array, i, makeJsVariant(m_context, l.at(i)));
- return array;
+ JS_SetPropertyUint32(m_context, array, i, asJsValue(l.at(i), 0, frozen));
+ if (frozen)
+ JS_ObjectSeal(m_context, array, true);
+ if (!id)
+ return array;
+ m_jsValueCache[id] = array;
+ return JS_DupValue(m_context, array);
}
JSValue ScriptEngine::loadInternalExtension(const QString &uri)
@@ -752,7 +807,7 @@ JSValue ScriptEngine::evaluate(JsValueOwner resultOwner, const QString &code,
m_scopeChains << scopeChain;
const QByteArray &codeStr = code.toUtf8();
- m_evalPositions.emplace(std::make_pair(filePath, line));
+ m_evalPositions.emplace(filePath, line);
const JSValue v = JS_EvalThis(m_context, globalObject(), codeStr.constData(), codeStr.length(),
filePath.toUtf8().constData(), line, JS_EVAL_TYPE_GLOBAL);
m_evalPositions.pop();
@@ -773,7 +828,7 @@ ScopedJsValueList ScriptEngine::argumentList(const QStringList &argumentNames,
JSValueList result;
for (const auto &name : argumentNames)
result.push_back(getJsProperty(m_context, context, name));
- return ScopedJsValueList(m_context, result);
+ return {m_context, result};
}
JSClassID ScriptEngine::registerClass(const char *name, JSClassCall *constructor,
@@ -1026,8 +1081,7 @@ ScriptEngine::Importer::Importer(
ScriptEngine::Importer::~Importer()
{
- if (m_engine.m_observeMode == ObserveMode::Enabled)
- m_engine.m_requireResults.clear();
+ m_engine.m_requireResults.clear();
m_engine.m_currentDirPathStack.pop();
m_engine.m_extensionSearchPathsStack.pop();
m_engine.uninstallImportFunctions();
diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h
index 4d797dd43..4a55392e3 100644
--- a/src/lib/corelib/language/scriptengine.h
+++ b/src/lib/corelib/language/scriptengine.h
@@ -58,6 +58,7 @@
#include <QtCore/qprocess.h>
#include <QtCore/qstring.h>
+#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
@@ -204,7 +205,7 @@ public:
JSValue newArray(int length, JsValueOwner owner);
void takeOwnership(JSValue v);
JSValue undefinedValue() const { return JS_UNDEFINED; }
- JSValue toScriptValue(const QVariant &v) const { return makeJsVariant(m_context, v); }
+ JSValue toScriptValue(const QVariant &v, quintptr id = 0) { return asJsValue(v, id); }
JSValue evaluate(JsValueOwner resultOwner, const QString &code,
const QString &filePath = QString(), int line = 1,
const JSValueList &scopeChain = {});
@@ -283,11 +284,12 @@ public:
JSValue getInternalExtension(const char *name) const;
void addInternalExtension(const char *name, JSValue ext);
+ JSValue asJsValue(const QVariant &v, quintptr id = 0, bool frozen = false);
JSValue asJsValue(const QByteArray &s);
JSValue asJsValue(const QString &s);
JSValue asJsValue(const QStringList &l);
- JSValue asJsValue(const QVariantList &l);
- JSValue asJsValue(const QVariantMap &m);
+ JSValue asJsValue(const QVariantList &l, quintptr id = 0, bool frozen = false);
+ JSValue asJsValue(const QVariantMap &m, quintptr id = 0, bool frozen = false);
QVariant property(const char *name) const { return m_properties.value(QLatin1String(name)); }
void setProperty(const char *k, const QVariant &v) { m_properties.insert(QLatin1String(k), v); }
@@ -359,7 +361,7 @@ private:
std::unordered_map<QString, JSValue> m_jsFileCache;
bool m_propertyCacheEnabled = true;
bool m_active = false;
- bool m_canceling = false;
+ std::atomic_bool m_canceling = false;
QHash<PropertyCacheKey, QVariant> m_propertyCache;
PropertySet m_propertiesRequestedInScript;
QHash<QString, PropertySet> m_propertiesRequestedFromArtifact;
@@ -396,6 +398,7 @@ private:
QHash<QString, JSClassID> m_classes;
QHash<QString, JSValue> m_internalExtensions;
QHash<QString, JSValue> m_stringCache;
+ QHash<quintptr, JSValue> m_jsValueCache;
QHash<JSValue, int> m_evalResults;
std::vector<JSValue *> m_externallyCachedValues;
QHash<QPair<Artifact *, QString>, JSValue> m_artifactsScriptValues;
diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp
index fabc64ccd..634f54faf 100644
--- a/src/lib/corelib/language/value.cpp
+++ b/src/lib/corelib/language/value.cpp
@@ -54,11 +54,11 @@ Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t)
m_flags |= OriginPropertiesBlock;
}
-Value::Value(const Value &other)
+Value::Value(const Value &other, ItemPool &pool)
: m_type(other.m_type),
m_scope(other.m_scope),
m_scopeName(other.m_scopeName),
- m_next(other.m_next ? other.m_next->clone() : ValuePtr()),
+ m_next(other.m_next ? other.m_next->clone(pool) : ValuePtr()),
m_candidates(other.m_candidates),
m_flags(other.m_flags)
{
@@ -134,18 +134,18 @@ JSSourceValue::JSSourceValue(bool createdByPropertiesBlock)
{
}
-JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other)
+JSSourceValue::JSSourceValue(const JSSourceValue &other, ItemPool &pool) : Value(other, pool)
{
m_sourceCode = other.m_sourceCode;
m_line = other.m_line;
m_column = other.m_column;
m_file = other.m_file;
m_baseValue = other.m_baseValue
- ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone())
+ ? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone(pool))
: JSSourceValuePtr();
m_alternatives = transformed<std::vector<Alternative>>(
- other.m_alternatives, [](const auto &alternative) {
- return alternative.clone(); });
+ other.m_alternatives, [&pool](const auto &alternative) {
+ return alternative.clone(pool); });
}
JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock)
@@ -155,9 +155,9 @@ JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock)
JSSourceValue::~JSSourceValue() = default;
-ValuePtr JSSourceValue::clone() const
+ValuePtr JSSourceValue::clone(ItemPool &pool) const
{
- return std::make_shared<JSSourceValue>(*this);
+ return std::make_shared<JSSourceValue>(*this, pool);
}
QString JSSourceValue::sourceCodeForEvaluation() const
@@ -235,29 +235,51 @@ ItemValuePtr ItemValue::create(Item *item, bool createdByPropertiesBlock)
return std::make_shared<ItemValue>(item, createdByPropertiesBlock);
}
-ValuePtr ItemValue::clone() const
+ValuePtr ItemValue::clone(ItemPool &pool) const
{
- return create(m_item->clone(), createdByPropertiesBlock());
+ return create(m_item->clone(pool), createdByPropertiesBlock());
}
+class StoredVariantValue : public VariantValue
+{
+public:
+ explicit StoredVariantValue(QVariant v) : VariantValue(std::move(v)) {}
+
+ quintptr id() const override { return quintptr(this); }
+};
+
VariantValue::VariantValue(QVariant v)
: Value(VariantValueType, false)
, m_value(std::move(v))
{
}
-VariantValuePtr VariantValue::create(const QVariant &v)
+VariantValue::VariantValue(const VariantValue &other, ItemPool &pool)
+ : Value(other, pool), m_value(other.m_value) {}
+
+template<typename T>
+VariantValuePtr createImpl(const QVariant &v)
{
if (!v.isValid())
- return invalidValue();
+ return VariantValue::invalidValue();
if (static_cast<QMetaType::Type>(v.userType()) == QMetaType::Bool)
return v.toBool() ? VariantValue::trueValue() : VariantValue::falseValue();
- return std::make_shared<VariantValue>(v);
+ return std::make_shared<T>(v);
+}
+
+VariantValuePtr VariantValue::create(const QVariant &v)
+{
+ return createImpl<VariantValue>(v);
+}
+
+VariantValuePtr VariantValue::createStored(const QVariant &v)
+{
+ return createImpl<StoredVariantValue>(v);
}
-ValuePtr VariantValue::clone() const
+ValuePtr VariantValue::clone(ItemPool &pool) const
{
- return std::make_shared<VariantValue>(*this);
+ return std::make_shared<VariantValue>(*this, pool);
}
const VariantValuePtr &VariantValue::falseValue()
diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h
index 262813841..1a6746e24 100644
--- a/src/lib/corelib/language/value.h
+++ b/src/lib/corelib/language/value.h
@@ -50,6 +50,7 @@
namespace qbs {
namespace Internal {
class Item;
+class ItemPool;
class ValueHandler;
class Value
@@ -76,12 +77,13 @@ public:
Q_DECLARE_FLAGS(Flags, Flag)
Value(Type t, bool createdByPropertiesBlock);
- Value(const Value &other);
+ Value(const Value &other) = delete;
+ Value(const Value &other, ItemPool &pool);
virtual ~Value();
Type type() const { return m_type; }
virtual void apply(ValueHandler *) = 0;
- virtual ValuePtr clone() const = 0;
+ virtual ValuePtr clone(ItemPool &) const = 0;
virtual CodeLocation location() const { return {}; }
Item *scope() const { return m_scope; }
@@ -144,13 +146,13 @@ class JSSourceValue : public Value
public:
explicit JSSourceValue(bool createdByPropertiesBlock);
- JSSourceValue(const JSSourceValue &other);
+ JSSourceValue(const JSSourceValue &other, ItemPool &pool);
static JSSourceValuePtr QBS_AUTOTEST_EXPORT create(bool createdByPropertiesBlock = false);
~JSSourceValue() override;
void apply(ValueHandler *handler) override { handler->handle(this); }
- ValuePtr clone() const override;
+ ValuePtr clone(ItemPool &pool) const override;
void setSourceCode(QStringView sourceCode) { m_sourceCode = sourceCode; }
QStringView sourceCode() const { return m_sourceCode; }
@@ -180,10 +182,10 @@ public:
Alternative() = default;
Alternative(PropertyData c, PropertyData o, JSSourceValuePtr v)
: condition(std::move(c)), overrideListProperties(std::move(o)), value(std::move(v)) {}
- Alternative clone() const
+ Alternative clone(ItemPool &pool) const
{
return Alternative(condition, overrideListProperties,
- std::static_pointer_cast<JSSourceValue>(value->clone()));
+ std::static_pointer_cast<JSSourceValue>(value->clone(pool)));
}
PropertyData condition;
@@ -222,7 +224,7 @@ public:
private:
void apply(ValueHandler *handler) override { handler->handle(this); }
- ValuePtr clone() const override;
+ ValuePtr clone(ItemPool &pool) const override;
Item *m_item;
};
@@ -232,12 +234,15 @@ class VariantValue : public Value
{
public:
explicit VariantValue(QVariant v);
+ VariantValue(const VariantValue &v, ItemPool &pool);
static VariantValuePtr create(const QVariant &v = QVariant());
+ static VariantValuePtr createStored(const QVariant &v = QVariant());
void apply(ValueHandler *handler) override { handler->handle(this); }
- ValuePtr clone() const override;
+ ValuePtr clone(ItemPool &pool) const override;
const QVariant &value() const { return m_value; }
+ virtual quintptr id() const { return 0; }
static const VariantValuePtr &falseValue();
static const VariantValuePtr &trueValue();
diff --git a/src/lib/corelib/loader/astimportshandler.cpp b/src/lib/corelib/loader/astimportshandler.cpp
index 90887e728..c0281ca24 100644
--- a/src/lib/corelib/loader/astimportshandler.cpp
+++ b/src/lib/corelib/loader/astimportshandler.cpp
@@ -268,21 +268,9 @@ void ASTImportsHandler::checkImportVersion(const QbsQmlJS::AST::SourceLocation &
void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as)
{
QStringList fileNames; // Yes, file *names*.
- if (m_visitorState.findDirectoryEntries(path, &fileNames)) {
- for (const QString &fileName : std::as_const(fileNames))
- addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false);
- return;
- }
-
- QDirIterator dirIter(path, StringConstants::qbsFileWildcards());
- while (dirIter.hasNext()) {
- const QString filePath = dirIter.next();
- const QString fileName = dirIter.fileName();
- if (addPrototype(fileName, filePath, as, true))
- fileNames << fileName;
- }
- m_visitorState.cacheDirectoryEntries(path, fileNames);
-
+ m_visitorState.findDirectoryEntries(path, &fileNames);
+ for (const QString &fileName : std::as_const(fileNames))
+ addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false);
}
void ASTImportsHandler::collectPrototypesAndJsCollections(const QString &path, const QString &as,
diff --git a/src/lib/corelib/loader/astpropertiesitemhandler.cpp b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
index fbb8761b5..8183e6b79 100644
--- a/src/lib/corelib/loader/astpropertiesitemhandler.cpp
+++ b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
@@ -49,7 +49,8 @@
namespace qbs {
namespace Internal {
-ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem) : m_parentItem(parentItem)
+ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem, ItemPool &itemPool)
+ : m_parentItem(parentItem), m_itemPool(itemPool)
{
}
@@ -82,9 +83,11 @@ class PropertiesBlockConverter
public:
PropertiesBlockConverter(const JSSourceValue::AltProperty &condition,
const JSSourceValue::AltProperty &overrideListProperties,
- Item *propertiesBlockContainer, const Item *propertiesBlock)
+ Item *propertiesBlockContainer, const Item *propertiesBlock,
+ ItemPool &pool)
: m_propertiesBlockContainer(propertiesBlockContainer)
, m_propertiesBlock(propertiesBlock)
+ , m_itemPool(pool)
{
m_alternative.condition = condition;
m_alternative.overrideListProperties = overrideListProperties;
@@ -99,6 +102,7 @@ private:
JSSourceValue::Alternative m_alternative;
Item * const m_propertiesBlockContainer;
const Item * const m_propertiesBlock;
+ ItemPool &m_itemPool;
void doApply(Item *outer, const Item *inner)
{
@@ -111,9 +115,9 @@ private:
}
if (it.value()->type() == Value::ItemValueType) {
Item * const innerVal = std::static_pointer_cast<ItemValue>(it.value())->item();
- ItemValuePtr outerVal = outer->itemProperty(it.key());
+ ItemValuePtr outerVal = outer->itemProperty(it.key(), m_itemPool);
if (!outerVal) {
- outerVal = ItemValue::create(Item::create(outer->pool(), innerVal->type()),
+ outerVal = ItemValue::create(Item::create(&m_itemPool, innerVal->type()),
true);
outer->setProperty(it.key(), outerVal);
}
@@ -155,8 +159,7 @@ static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, co
throw ErrorInfo(Tr::tr("Properties.condition must be provided."),
propertiesItem->location());
}
- return JSSourceValue::AltProperty(StringConstants::falseValue(),
- propertiesItem->location());
+ return {StringConstants::falseValue(), propertiesItem->location()};
}
if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) {
throw ErrorInfo(Tr::tr("Properties.%1 must be a value binding.").arg(name),
@@ -176,7 +179,7 @@ static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, co
}
const JSSourceValuePtr srcval = std::static_pointer_cast<JSSourceValue>(value);
- return JSSourceValue::AltProperty(srcval->sourceCodeForEvaluation(), srcval->location());
+ return {srcval->sourceCodeForEvaluation(), srcval->location()};
}
void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem)
@@ -185,7 +188,7 @@ void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem)
const auto overrideListProperties = getPropertyData(propertiesItem,
StringConstants::overrideListPropertiesProperty());
PropertiesBlockConverter(condition, overrideListProperties, m_parentItem,
- propertiesItem).apply();
+ propertiesItem, m_itemPool).apply();
}
} // namespace Internal
diff --git a/src/lib/corelib/loader/astpropertiesitemhandler.h b/src/lib/corelib/loader/astpropertiesitemhandler.h
index 413512ee5..804abb8a1 100644
--- a/src/lib/corelib/loader/astpropertiesitemhandler.h
+++ b/src/lib/corelib/loader/astpropertiesitemhandler.h
@@ -42,11 +42,12 @@
namespace qbs {
namespace Internal {
class Item;
+class ItemPool;
class ASTPropertiesItemHandler
{
public:
- ASTPropertiesItemHandler(Item *parentItem);
+ ASTPropertiesItemHandler(Item *parentItem, ItemPool &itemPool);
void handlePropertiesItems();
@@ -55,6 +56,7 @@ private:
void handlePropertiesBlock(const Item *propertiesItem);
Item * const m_parentItem;
+ ItemPool &m_itemPool;
};
} // namespace Internal
diff --git a/src/lib/corelib/loader/dependenciesresolver.cpp b/src/lib/corelib/loader/dependenciesresolver.cpp
index 06852afa9..059592e9c 100644
--- a/src/lib/corelib/loader/dependenciesresolver.cpp
+++ b/src/lib/corelib/loader/dependenciesresolver.cpp
@@ -43,8 +43,6 @@
#include "loaderutils.h"
#include "moduleinstantiator.h"
#include "moduleloader.h"
-#include "moduleproviderloader.h"
-#include "productitemmultiplexer.h"
#include <language/scriptengine.h>
#include <language/evaluator.h>
@@ -91,7 +89,6 @@ public:
VersionRange versionRange;
QVariantMap parameters;
bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
bool requiredLocally = true;
bool requiredGlobally = true;
};
@@ -125,7 +122,6 @@ public:
VersionRange versionRange;
QVariantMap parameters;
bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
bool requiredLocally = true;
bool requiredGlobally = true;
bool checkProduct = true;
@@ -141,24 +137,38 @@ public:
std::queue<FullyResolvedDependsItem> pendingResolvedDependencies;
bool requiredByLoadingItem = true;
};
-} // namespace
-static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
-static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties);
-static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v);
+class DependenciesContextImpl : public DependenciesContext
+{
+public:
+ DependenciesContextImpl(ProductContext &product, LoaderState &loaderState);
+
+ std::list<DependenciesResolvingState> stateStack;
+
+private:
+ std::pair<ProductDependency, ProductContext *> pendingDependency() const override;
-class DependenciesResolver::Private
+ void setSearchPathsForProduct(LoaderState &loaderState);
+
+ ProductContext &m_product;
+};
+
+class DependenciesResolver
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ DependenciesResolver(LoaderState &loaderState, ProductContext &product, Deferral deferral)
+ : m_loaderState(loaderState), m_product(product), m_deferral(deferral) {}
- void initializeState();
+ void resolve();
+ LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency);
+
+private:
void evaluateNextDependsItem();
HandleDependency handleResolvedDependencies();
- LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency);
std::pair<Item::Module *, Item *> findExistingModule(const FullyResolvedDependsItem &dependency,
Item *item);
void updateModule(Item::Module &module, const FullyResolvedDependsItem &dependency);
+ int dependsChainLength();
ProductContext *findMatchingProduct(const FullyResolvedDependsItem &dependency);
Item *findMatchingModule(const FullyResolvedDependsItem &dependency);
std::pair<bool, HandleDependency> checkProductDependency(
@@ -172,48 +182,63 @@ public:
std::optional<EvaluatedDependsItem> evaluateDependsItem(Item *item);
std::queue<FullyResolvedDependsItem> multiplexDependency(
const EvaluatedDependsItem &dependency);
- void setSearchPathsForProduct();
QVariantMap extractParameters(Item *dependsItem) const;
-
- LoaderState &loaderState;
- ModuleLoader moduleLoader{loaderState};
- std::unordered_map<ProductContext *, std::list<DependenciesResolvingState>> statePerProduct;
- qint64 elapsedTime = 0;
-
- ProductContext *product = nullptr;
- std::list<DependenciesResolvingState> *stateStack = nullptr;
- Deferral deferral = Deferral::Allowed;
+ void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules);
+ void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
+ const Item::Modules &modules);
+ std::list<DependenciesResolvingState> &stateStack();
+
+ LoaderState &m_loaderState;
+ ProductContext &m_product;
+ Deferral m_deferral;
};
-DependenciesResolver::DependenciesResolver(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) {}
-DependenciesResolver::~DependenciesResolver() = default;
+static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
+static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v);
-bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral deferral)
+} // namespace
+
+void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState)
{
- QBS_CHECK(!product.dependenciesResolved);
+ DependenciesResolver(loaderState, product, deferral).resolve();
+}
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
+Item *loadBaseModule(ProductContext &product, Item *item, LoaderState &loaderState)
+{
+ const auto baseDependency = FullyResolvedDependsItem::makeBaseDependency();
+ Item * const moduleItem = DependenciesResolver(loaderState, product, Deferral::NotAllowed)
+ .loadModule(item, baseDependency).moduleItem;
+ if (Q_UNLIKELY(!moduleItem))
+ throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
+ return moduleItem;
+}
- d->product = &product;
- d->deferral = deferral;
- d->stateStack = &d->statePerProduct[&product];
+namespace {
- d->initializeState();
- SearchPathsManager searchPathsMgr(d->loaderState.itemReader(), product.searchPaths);
+void DependenciesResolver::resolve()
+{
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.dependenciesResolving : nullptr);
+
+ if (!m_product.dependenciesContext) {
+ m_product.dependenciesContext = std::make_unique<DependenciesContextImpl>(
+ m_product, m_loaderState);
+ } else {
+ QBS_CHECK(!m_product.dependenciesContext->dependenciesResolved);
+ }
+ SearchPathsManager searchPathsMgr(m_loaderState.itemReader(), m_product.searchPaths);
- while (!d->stateStack->empty()) {
- auto &state = d->stateStack->front();
+ while (!stateStack().empty()) {
+ auto &state = stateStack().front();
// If we have pending FullyResolvedDependsItems, then these are handled first.
- if (d->handleResolvedDependencies() == HandleDependency::Defer)
- return false;
+ if (handleResolvedDependencies() == HandleDependency::Defer)
+ return;
// The above procedure might have pushed another state to the stack due to recursive
// dependencies (i.e. Depends items in the newly loaded module), in which case we
// continue with that one.
- if (&state != &d->stateStack->front())
+ if (&state != &stateStack().front())
continue;
// If we have a pending EvaluatedDependsItem, we multiplex it and then handle
@@ -223,17 +248,19 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
// We postpone handling Depends.productTypes for as long as possible, because
// the full type of a product becomes available only after its modules have been loaded.
- if (!state.currentDependsItem->productTypes.empty() && deferral == Deferral::Allowed)
- return false;
+ if (!state.currentDependsItem->productTypes.empty() && m_deferral == Deferral::Allowed)
+ return;
- state.pendingResolvedDependencies = d->multiplexDependency(*state.currentDependsItem);
+ state.pendingResolvedDependencies = multiplexDependency(*state.currentDependsItem);
state.currentDependsItem.reset();
+ m_deferral = Deferral::Allowed; // We made progress.
+
continue;
}
// Here we have no resolved/evaluated Depends items of any kind, so we evaluate the next
// pending Depends item.
- d->evaluateNextDependsItem();
+ evaluateNextDependsItem();
if (state.currentDependsItem)
continue;
@@ -244,9 +271,9 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
// This ensures our invariant: A sorted module list in the product
// (dependers after dependencies).
- if (d->stateStack->size() > 1) {
+ if (stateStack().size() > 1) {
QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
- Item::Modules &modules = product.item->modules();
+ Item::Modules &modules = m_product.item->modules();
const auto loadingItemModule = std::find_if(modules.begin(), modules.end(),
[&](const Item::Module &m) {
return m.item == state.loadingItem;
@@ -256,79 +283,14 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
modules.erase(loadingItemModule);
modules.push_back(tempModule);
}
- d->stateStack->pop_front();
- }
- return true;
-}
-
-void DependenciesResolver::checkDependencyParameterDeclarations(
- const Item *productItem, const QString &productName) const
-{
- d->moduleLoader.checkDependencyParameterDeclarations(productItem, productName);
-}
-
-void DependenciesResolver::setStoredModuleProviderInfo(
- const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->moduleLoader.setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-StoredModuleProviderInfo DependenciesResolver::storedModuleProviderInfo() const
-{
- return d->moduleLoader.storedModuleProviderInfo();
-}
-
-const Set<QString> &DependenciesResolver::tempQbsFiles() const
-{
- return d->moduleLoader.tempQbsFiles();
-}
-
-void DependenciesResolver::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Setting up product dependencies took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
- d->moduleLoader.printProfilingInfo(indent + 2);
-}
-
-Item *DependenciesResolver::loadBaseModule(ProductContext &product, Item *item)
-{
- d->product = &product;
- d->stateStack = &d->statePerProduct[&product];
- d->deferral = Deferral::NotAllowed;
- const auto baseDependency = FullyResolvedDependsItem::makeBaseDependency();
- Item * const moduleItem = d->loadModule(item, baseDependency).moduleItem;
- if (Q_UNLIKELY(!moduleItem))
- throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
- return moduleItem;
-}
-
-void DependenciesResolver::Private::initializeState()
-{
- if (!stateStack->empty())
- return;
-
- // Initialize the state with the direct Depends items of the product item.
- // This is executed once per product, while the function might be entered
- // multiple times due to deferrals.
- setSearchPathsForProduct();
- DependenciesResolvingState newState{product->item,};
- for (Item * const child : product->item->children()) {
- if (child->type() == ItemType::Depends)
- newState.pendingDependsItems.push(child);
+ stateStack().pop_front();
}
- stateStack->push_front(std::move(newState));
- stateStack->front().pendingResolvedDependencies.push(
- FullyResolvedDependsItem::makeBaseDependency());
+ m_product.dependenciesContext->dependenciesResolved = true;
}
-void DependenciesResolver::Private::evaluateNextDependsItem()
+void DependenciesResolver::evaluateNextDependsItem()
{
- auto &state = stateStack->front();
+ auto &state = stateStack().front();
while (!state.pendingDependsItems.empty()) {
QBS_CHECK(!state.currentDependsItem);
QBS_CHECK(state.pendingResolvedDependencies.empty());
@@ -344,9 +306,9 @@ void DependenciesResolver::Private::evaluateNextDependsItem()
}
}
-HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
+HandleDependency DependenciesResolver::handleResolvedDependencies()
{
- DependenciesResolvingState &state = stateStack->front();
+ DependenciesResolvingState &state = stateStack().front();
while (!state.pendingResolvedDependencies.empty()) {
QBS_CHECK(!state.currentDependsItem);
const FullyResolvedDependsItem dependency = state.pendingResolvedDependencies.front();
@@ -354,7 +316,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
const LoadModuleResult res = loadModule(state.loadingItem, dependency);
switch (res.handleDependency) {
case HandleDependency::Defer:
- QBS_CHECK(deferral == Deferral::Allowed);
+ QBS_CHECK(m_deferral == Deferral::Allowed);
// Optimization: We already looked up the product, so let's not do that again
// next time.
@@ -373,6 +335,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
state.pendingResolvedDependencies.pop();
continue;
}
+ m_deferral = Deferral::Allowed; // We made progress.
break;
}
@@ -385,42 +348,35 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
moduleDependsItems.push(child);
}
state.pendingResolvedDependencies.pop();
- stateStack->push_front(
+ stateStack().push_front(
{res.moduleItem, dependency, moduleDependsItems, {}, {},
dependency.requiredGlobally || state.requiredByLoadingItem});
- stateStack->front().pendingResolvedDependencies.push(
+ stateStack().front().pendingResolvedDependencies.push(
FullyResolvedDependsItem::makeBaseDependency());
break;
} catch (const ErrorInfo &e) {
if (dependency.name.toString() == StringConstants::qbsModule())
throw e;
- // This can happen when a property is set unconditionally on a non-required,
- // non-present dependency. We allow this for user convenience.
- if (!dependency.requiredLocally) {
- state.pendingResolvedDependencies.pop();
- continue;
- }
-
// See QBS-1338 for why we do not abort handling the product.
state.pendingResolvedDependencies.pop();
- Item::Modules &modules = product->item->modules();
+ Item::Modules &modules = m_product.item->modules();
// Unwind.
- while (stateStack->size() > 1) {
+ while (stateStack().size() > 1) {
const auto loadingItemModule = std::find_if(
modules.begin(), modules.end(), [&](const Item::Module &m) {
- return m.item == stateStack->front().loadingItem;
+ return m.item == stateStack().front().loadingItem;
});
for (auto it = loadingItemModule; it != modules.end(); ++it) {
- createNonPresentModule(loaderState.itemPool(), it->name.toString(),
+ createNonPresentModule(m_loaderState.itemPool(), it->name.toString(),
QLatin1String("error in Depends chain"), it->item);
}
modules.erase(loadingItemModule, modules.end());
- stateStack->pop_front();
+ stateStack().pop_front();
}
- product->handleError(e);
+ m_product.handleError(e);
return HandleDependency::Ignore;
}
}
@@ -434,7 +390,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
// created module is added to the module list of the product item and additionally to the
// loading item's one, if it is not the product. Its name is also injected into the respective
// scopes.
-LoadModuleResult DependenciesResolver::Private::loadModule(
+LoadModuleResult DependenciesResolver::loadModule(
Item *loadingItem, const FullyResolvedDependsItem &dependency)
{
qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
@@ -445,9 +401,15 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
ProductContext *productDep = nullptr;
Item *moduleItem = nullptr;
+ const auto addLoadContext = [&](Item::Module &module) {
+ module.loadContexts.emplace_back(dependency.item,
+ std::make_pair(dependency.parameters,
+ INT_MAX - dependsChainLength()));
+ };
+
// The module might already have been loaded for this product (directly or indirectly).
const auto &[existingModule, moduleWithSameName]
- = findExistingModule(dependency, product->item);
+ = findExistingModule(dependency, m_product.item);
if (existingModule) {
// Merge version range and required property. These will be checked again
// after probes resolving.
@@ -456,11 +418,19 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
QBS_CHECK(existingModule->item);
moduleItem = existingModule->item;
- if (!contains(existingModule->loadingItems, loadingItem))
- existingModule->loadingItems.push_back(loadingItem);
+ const auto matcher = [loadingItem](const Item::Module::LoadContext &context) {
+ return context.loadingItem() == loadingItem;
+ };
+ const auto it = std::find_if(existingModule->loadContexts.begin(),
+ existingModule->loadContexts.end(), matcher);
+ if (it == existingModule->loadContexts.end())
+ addLoadContext(*existingModule);
+ else
+ it->parameters.first = mergeDependencyParameters(it->parameters.first,
+ dependency.parameters);
} else if (dependency.product) {
productDep = dependency.product; // We have already done the look-up.
- } else if (!(productDep = findMatchingProduct(dependency))) {
+ } else if (productDep = findMatchingProduct(dependency); !productDep) {
moduleItem = findMatchingModule(dependency);
}
@@ -475,7 +445,7 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
if (checkResult.first) {
QBS_CHECK(productDep->mergedExportItem);
- moduleItem = productDep->mergedExportItem->clone();
+ moduleItem = productDep->mergedExportItem->clone(m_loaderState.itemPool());
moduleItem->setParent(nullptr);
// Needed for isolated Export item evaluation.
@@ -496,17 +466,16 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
// The loading name is only used to ensure consistent sorting in case of equal
// value priorities; see ModulePropertyMerger.
QString loadingName;
- if (loadingItem == product->item) {
- loadingName = product->name;
- } else if (!stateStack->empty()) {
- const auto &loadingItemOrigin = stateStack->front().loadingItemOrigin;
+ if (loadingItem == m_product.item) {
+ loadingName = m_product.name;
+ } else if (m_product.dependenciesContext && !stateStack().empty()) {
+ const auto &loadingItemOrigin = stateStack().front().loadingItemOrigin;
loadingName = loadingItemOrigin.name.toString() + loadingItemOrigin.multiplexId
+ loadingItemOrigin.profile;
}
- loaderState.moduleInstantiator().instantiate({
- product->item, product->name, loadingItem, loadingName, moduleItem, moduleWithSameName,
- productDep ? productDep->item : nullptr, product->scope, product->project->scope,
- dependency.name, dependency.id(), bool(existingModule)});
+ instantiateModule({m_product, loadingItem, loadingName, moduleItem, moduleWithSameName,
+ productDep ? productDep->item : nullptr, dependency.name, dependency.id(),
+ bool(existingModule)}, m_loaderState);
// At this point, a null module item is only possible for a non-required dependency.
// Note that we still needed to to the instantiation above, as that injects the module
@@ -532,26 +501,18 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
}
qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
- if (product->item) {
+ if (m_product.item) {
Item::Module module = createModule(dependency, moduleItem, productDep);
-
- if (module.name.toString() != StringConstants::qbsModule()) {
- // TODO: Why do we have default parameters only for Export items and
- // property declarations only for modules? Does that make any sense?
- if (productDep)
- module.parameters = productDep->defaultParameters;
- mergeParameters(module.parameters, dependency.parameters);
- }
module.required = dependency.requiredGlobally;
- module.loadingItems.push_back(loadingItem);
- module.maxDependsChainLength = stateStack->size();
- product->item->addModule(module);
+ addLoadContext(module);
+ module.maxDependsChainLength = dependsChainLength();
+ m_product.item->addModule(module);
addLocalModule();
}
return {moduleItem, nullptr, HandleDependency::Use};
}
-std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingModule(
+std::pair<Item::Module *, Item *> DependenciesResolver::findExistingModule(
const FullyResolvedDependsItem &dependency, Item *item)
{
if (!item) // Happens if and only if called via loadBaseModule().
@@ -560,12 +521,12 @@ std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingMod
for (Item::Module &m : item->modules()) {
if (m.name != dependency.name)
continue;
- if (!m.productInfo) {
+ if (!m.product) {
QBS_CHECK(!dependency.product);
return {&m, m.item};
}
- if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
- && m.productInfo->multiplexId == dependency.multiplexId) {
+ if ((dependency.profile.isEmpty() || (m.product->profileName == dependency.profile))
+ && m.product->multiplexConfigurationId == dependency.multiplexId) {
return {&m, m.item};
}
@@ -576,39 +537,39 @@ std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingMod
return {nullptr, moduleWithSameName};
}
-void DependenciesResolver::Private::updateModule(
+void DependenciesResolver::updateModule(
Item::Module &module, const FullyResolvedDependsItem &dependency)
{
- moduleLoader.forwardParameterDeclarations(dependency.item, product->item->modules());
-
- // TODO: Use priorities like for property values. See QBS-1300.
- mergeParameters(module.parameters, dependency.parameters);
-
+ forwardParameterDeclarations(dependency.item, m_product.item->modules());
module.versionRange.narrowDown(dependency.versionRange);
module.required |= dependency.requiredGlobally;
- if (int(stateStack->size()) > module.maxDependsChainLength)
- module.maxDependsChainLength = stateStack->size();
+ if (dependsChainLength() > module.maxDependsChainLength)
+ module.maxDependsChainLength = dependsChainLength();
}
-ProductContext *DependenciesResolver::Private::findMatchingProduct(
+int DependenciesResolver::dependsChainLength()
+{
+ return m_product.dependenciesContext ? stateStack().size() : 1;
+}
+
+ProductContext *DependenciesResolver::findMatchingProduct(
const FullyResolvedDependsItem &dependency)
{
- const auto candidates = product->project->topLevelProject
- ->productsByName.equal_range(dependency.name.toString());
- for (auto it = candidates.first; it != candidates.second; ++it) {
- ProductContext * const candidate = it->second;
- if (candidate->multiplexConfigurationId != dependency.multiplexId)
- continue;
- if (!dependency.profile.isEmpty() && dependency.profile != candidate->profileName)
- continue;
- if (dependency.limitToSubProject && !haveSameSubProject(*product, *candidate))
- continue;
- return candidate;
- }
- return nullptr;
+ const auto constraint = [this, &dependency](ProductContext &product) {
+ if (product.multiplexConfigurationId != dependency.multiplexId)
+ return false;
+ if (!dependency.profile.isEmpty() && dependency.profile != product.profileName)
+ return false;
+ if (dependency.limitToSubProject && !haveSameSubProject(m_product, product))
+ return false;
+ return true;
+
+ };
+ return m_product.project->topLevelProject->productWithNameAndConstraint(
+ dependency.name.toString(), constraint);
}
-Item *DependenciesResolver::Private::findMatchingModule(
+Item *DependenciesResolver::findMatchingModule(
const FullyResolvedDependsItem &dependency)
{
// If we can tell that this is supposed to be a product dependency, we can skip
@@ -618,40 +579,38 @@ Item *DependenciesResolver::Private::findMatchingModule(
if (!dependency.profile.isEmpty()) {
throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
"for the requested profile '%3'.")
- .arg(product->displayName(), dependency.displayName(),
+ .arg(m_product.displayName(), dependency.displayName(),
dependency.profile),
- product->item->location());
+ m_product.item->location());
}
throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(product->displayName(), dependency.displayName()),
- product->item->location());
+ .arg(m_product.displayName(), dependency.displayName()),
+ m_product.item->location());
}
return nullptr;
}
- const ModuleLoader::ProductContext loaderContext{
- product->item, product->project->item, product->name, product->uniqueName(),
- product->profileName, product->multiplexConfigurationId, product->moduleProperties,
- product->profileModuleProperties};
- const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile(
- loaderContext, dependency.location(), dependency.name, dependency.fallbackMode,
- dependency.requiredGlobally);
-
- Item *moduleItem = loaderResult.moduleItem;
- product->info.probes << loaderResult.providerProbes;
- if (moduleItem) {
+ if (Item *moduleItem = searchAndLoadModuleFile(
+ m_loaderState, m_product, dependency.location(), dependency.name)) {
+ QBS_CHECK(moduleItem->type() == ItemType::Module);
Item * const proto = moduleItem;
- moduleItem = moduleItem->clone();
+ ModuleItemLocker locker(*moduleItem);
+ moduleItem = moduleItem->clone(m_loaderState.itemPool());
moduleItem->setPrototype(proto); // For parameter declarations.
- } else if (dependency.requiredGlobally) {
- throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
- .arg(dependency.name.toString(), product->displayName()),
- dependency.location());
+ return moduleItem;
}
- return moduleItem;
+
+ if (!dependency.requiredGlobally) {
+ return createNonPresentModule(m_loaderState.itemPool(), dependency.name.toString(),
+ QStringLiteral("not found"), nullptr);
+ }
+
+ throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
+ .arg(dependency.name.toString(), m_product.displayName()),
+ dependency.location());
}
-std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDependency(
+std::pair<bool, HandleDependency> DependenciesResolver::checkProductDependency(
const FullyResolvedDependsItem &depSpec, const ProductContext &dep)
{
// Optimization: If we already checked the product earlier and then deferred, we don't
@@ -659,20 +618,18 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
if (!depSpec.checkProduct)
return {true, HandleDependency::Use};
- if (&dep == product) {
+ if (&dep == &m_product) {
throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.").arg(depSpec.name.toString()),
depSpec.location());
}
- if (any_of(product->project->topLevelProject->productsToHandle, [&dep](const auto &e) {
- return e.first == &dep;
- })) {
- if (deferral == Deferral::Allowed)
+ if (m_product.project->topLevelProject->isProductQueuedForHandling(dep)) {
+ if (m_deferral == Deferral::Allowed)
return {false, HandleDependency::Defer};
ErrorInfo e;
e.append(Tr::tr("Cyclic dependencies detected:"));
e.append(Tr::tr("First product is '%1'.")
- .arg(product->displayName()), product->item->location());
+ .arg(m_product.displayName()), m_product.item->location());
e.append(Tr::tr("Second product is '%1'.")
.arg(dep.displayName()), dep.item->location());
e.append(Tr::tr("Requested here."), depSpec.location());
@@ -681,12 +638,12 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
// This covers both the case of user-disabled products and products with errors.
// The latter are force-disabled in ProductContext::handleError().
- if (product->project->topLevelProject->disabledItems.contains(dep.item)) {
+ if (m_product.project->topLevelProject->isDisabledItem(dep.item)) {
if (depSpec.requiredGlobally) {
ErrorInfo e;
e.append(Tr::tr("Product '%1' depends on '%2',")
- .arg(product->displayName(), dep.displayName()),
- product->item->location());
+ .arg(m_product.displayName(), dep.displayName()),
+ m_product.item->location());
e.append(Tr::tr("but product '%1' is disabled.").arg(dep.displayName()),
dep.item->location());
throw e;
@@ -696,10 +653,15 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
return {true, HandleDependency::Use};
}
-void DependenciesResolver::Private::checkModule(
+void DependenciesResolver::checkModule(
const FullyResolvedDependsItem &dependency, Item *moduleItem, ProductContext *productDep)
{
- for (auto it = stateStack->begin(); it != stateStack->end(); ++it) {
+ // When loading a pseudo or temporary qbs module in early setup via loadBaseModule(),
+ // there is no proper state yet.
+ if (!m_product.dependenciesContext)
+ return;
+
+ for (auto it = stateStack().begin(); it != stateStack().end(); ++it) {
Item *itemToCheck = moduleItem;
if (it->loadingItem != itemToCheck) {
if (!productDep)
@@ -714,11 +676,11 @@ void DependenciesResolver::Private::checkModule(
e.append(it->loadingItemOrigin.name.toString(),
it->loadingItemOrigin.location());
if (it->loadingItem->type() == ItemType::ModuleInstance) {
- createNonPresentModule(loaderState.itemPool(),
+ createNonPresentModule(m_loaderState.itemPool(),
it->loadingItemOrigin.name.toString(),
QLatin1String("cyclic dependency"), it->loadingItem);
}
- if (it == stateStack->begin())
+ if (it == stateStack().begin())
break;
--it;
}
@@ -728,44 +690,30 @@ void DependenciesResolver::Private::checkModule(
checkForModuleNamePrefixCollision(dependency);
}
-void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *dependsItem)
+void DependenciesResolver::adjustDependsItemForMultiplexing(Item *dependsItem)
{
- Evaluator &evaluator = loaderState.evaluator();
+ if (m_product.name.startsWith(StringConstants::shadowProductPrefix()))
+ return;
+
+ Evaluator &evaluator = m_loaderState.evaluator();
const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
- const bool productIsMultiplexed = !product->multiplexConfigurationId.isEmpty();
- if (name == product->name) {
+ const bool productIsMultiplexed = !m_product.multiplexConfigurationId.isEmpty();
+ if (name == m_product.name) {
QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
return;
}
- const auto productRange = product->project->topLevelProject->productsByName.equal_range(name);
- if (productRange.first == productRange.second)
- return; // Dependency is a module. Nothing to adjust.
- bool profilesPropertyIsSet;
- const QStringList profiles = evaluator.stringListValue(
- dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
-
- std::vector<const ProductContext *> multiplexedDependencies;
bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty())
- multiplexedDependencies.push_back(it->second);
- else
+ const std::vector<ProductContext *> multiplexedDependencies = m_product.project
+ ->topLevelProject->productsWithNameAndConstraint(name, [&hasNonMultiplexedDependency]
+ (const ProductContext &product) {
+ if (product.multiplexConfigurationId.isEmpty()) {
hasNonMultiplexedDependency = true;
- }
- bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
-
- static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs,
- const QVariantMap &rhs) {
- QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
- for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
- const auto rhsProperty = rhs.find(lhsProperty.key());
- const bool isCommonProperty = rhsProperty != rhs.constEnd();
- if (isCommonProperty && lhsProperty.value() != rhsProperty.value())
- return false;
+ return false;
}
return true;
- };
+ });
+ const bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
// These are the allowed cases:
// (1) Normal dependency with no multiplexing whatsoever.
@@ -783,35 +731,38 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
// with a matching profile regardless of whether an aggregator exists or not.
// (4) The product is multiplexed, but the dependency is not. We don't have to adapt
// any Depends items.
- // (5) The product is a "shadow product". In that case, we know which product
- // it should have a dependency on, and we make sure we depend on that.
-
// (1) and (4)
if (!hasMultiplexedDependencies)
return;
+ bool profilesPropertyIsSet;
+ const QStringList profiles = evaluator.stringListValue(
+ dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
+
// (3a)
if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
return;
+ static const auto multiplexConfigurationIntersects = [](const QVariantMap &lhs,
+ const QVariantMap &rhs) {
+ QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty());
+ for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) {
+ const auto rhsProperty = rhs.find(lhsProperty.key());
+ const bool isCommonProperty = rhsProperty != rhs.constEnd();
+ if (isCommonProperty && !qVariantsEqual(lhsProperty.value(), rhsProperty.value()))
+ return false;
+ }
+ return true;
+ };
+
QStringList multiplexIds;
- const ShadowProductInfo shadowProductInfo = getShadowProductInfo(*product);
- const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
- const auto productMultiplexConfig
- = ProductItemMultiplexer::multiplexIdToVariantMap(product->multiplexConfigurationId);
+ const auto productMultiplexConfig = m_loaderState.topLevelProject().multiplexConfiguration(
+ m_product.multiplexConfigurationId);
for (const ProductContext *dependency : multiplexedDependencies) {
- const bool depMatchesShadowProduct = isShadowProduct
- && dependency->item == product->item->parent();
- const QString depMultiplexId = dependency->multiplexConfigurationId;
- if (depMatchesShadowProduct) { // (5)
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(depMultiplexId));
- return;
- }
if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
- if (dependency->multiplexConfigurationId == product->multiplexConfigurationId) {
- const ValuePtr &multiplexId = product->item->property(
+ if (dependency->multiplexConfigurationId == m_product.multiplexConfigurationId) {
+ const ValuePtr &multiplexId = m_product.item->property(
StringConstants::multiplexConfigurationIdProperty());
dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
multiplexId);
@@ -819,8 +770,8 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
}
// Otherwise collect partial matches and decide later
- const auto dependencyMultiplexConfig = ProductItemMultiplexer::multiplexIdToVariantMap(
- dependency->multiplexConfigurationId);
+ const auto dependencyMultiplexConfig = m_loaderState.topLevelProject()
+ .multiplexConfiguration(dependency->multiplexConfigurationId);
if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
multiplexIds << dependency->multiplexConfigurationId;
@@ -829,12 +780,11 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
|| profiles.contains(dependency->profileName);
if (profileMatch)
- multiplexIds << depMultiplexId;
+ multiplexIds << dependency->multiplexConfigurationId;
}
}
if (multiplexIds.empty()) {
- const QString productName = ProductItemMultiplexer::fullProductDisplayName(
- product->name, product->multiplexConfigurationId);
+ const QString productName = m_product.displayName();
throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
"There are no eligible multiplex candidates.").arg(productName,
name),
@@ -845,11 +795,11 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
QStringList candidateNames;
for (const auto &id : std::as_const(multiplexIds))
- candidateNames << ProductItemMultiplexer::fullProductDisplayName(name, id);
+ candidateNames << fullProductDisplayName(name, id);
throw ErrorInfo(
Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
"Eligible multiplex candidates: %3.").arg(
- product->displayName(), name, candidateNames.join(QLatin1String(", "))),
+ m_product.displayName(), name, candidateNames.join(QLatin1String(", "))),
dependsItem->location());
}
@@ -858,10 +808,10 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
}
-std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDependsItem(Item *item)
+std::optional<EvaluatedDependsItem> DependenciesResolver::evaluateDependsItem(Item *item)
{
- Evaluator &evaluator = loaderState.evaluator();
- if (!product->project->topLevelProject->checkItemCondition(item, evaluator)) {
+ Evaluator &evaluator = m_loaderState.evaluator();
+ if (!m_product.project->topLevelProject->checkItemCondition(item, evaluator)) {
qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
return {};
}
@@ -897,11 +847,6 @@ std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDepen
item->location());
}
- const FallbackMode fallbackMode
- = loaderState.parameters().fallbackProviderEnabled()
- && evaluator.boolValue(item, StringConstants::enableFallbackProperty())
- ? FallbackMode::Enabled : FallbackMode::Disabled;
-
bool profilesPropertyWasSet = false;
std::optional<QStringList> profiles;
bool required = true;
@@ -921,35 +866,39 @@ std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDepen
const QStringList multiplexIds = evaluator.stringListValue(
item, StringConstants::multiplexConfigurationIdsProperty());
adjustParametersScopes(item, item);
- moduleLoader.forwardParameterDeclarations(item, product->item->modules());
+ forwardParameterDeclarations(item, m_product.item->modules());
const QVariantMap parameters = extractParameters(item);
+ const FileTags productTypeTags = FileTags::fromStringList(productTypes);
+ if (!productTypeTags.empty())
+ m_product.bulkDependencies.emplace_back(productTypeTags, item->location());
return EvaluatedDependsItem{
- item, QualifiedId::fromString(name), submodules, FileTags::fromStringList(productTypes),
- multiplexIds, profiles, {minVersion, maxVersion}, parameters, limitToSubProject,
- fallbackMode, required};
+ item,
+ QualifiedId::fromString(name),
+ submodules,
+ productTypeTags,
+ multiplexIds,
+ profiles,
+ {minVersion, maxVersion},
+ parameters,
+ limitToSubProject,
+ required};
}
// Potentially multiplexes a dependency along Depends.productTypes, Depends.subModules and
// Depends.profiles, as well as internally set up multiplexing axes.
// Each entry in the resulting queue corresponds to exactly one product or module to pull in.
std::queue<FullyResolvedDependsItem>
-DependenciesResolver::Private::multiplexDependency(const EvaluatedDependsItem &dependency)
+DependenciesResolver::multiplexDependency(const EvaluatedDependsItem &dependency)
{
std::queue<FullyResolvedDependsItem> dependencies;
if (!dependency.productTypes.empty()) {
- std::vector<ProductContext *> matchingProducts;
- for (const FileTag &typeTag : dependency.productTypes) {
- const auto range = product->project->topLevelProject->productsByType.equal_range(typeTag);
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second != product
- && it->second->name != product->name
- && (!dependency.limitToSubProject
- || haveSameSubProject(*product, *it->second))) {
- matchingProducts.push_back(it->second);
- }
- }
- }
+ const auto constraint = [&](const ProductContext &product) {
+ return &product != &m_product && product.name != m_product.name
+ && (!dependency.limitToSubProject || haveSameSubProject(m_product, product));
+ };
+ const std::vector<ProductContext *> matchingProducts = m_product.project->topLevelProject
+ ->productsWithTypeAndConstraint(dependency.productTypes, constraint);
if (matchingProducts.empty()) {
qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
<< dependency.item->location();
@@ -979,80 +928,106 @@ DependenciesResolver::Private::multiplexDependency(const EvaluatedDependsItem &d
return dependencies;
}
-void DependenciesResolver::Private::setSearchPathsForProduct()
-{
- QBS_CHECK(product->searchPaths.isEmpty());
-
- product->searchPaths = loaderState.itemReader().readExtraSearchPaths(product->item);
- Settings settings(loaderState.parameters().settingsDirectory());
- const QStringList prefsSearchPaths = Preferences(&settings, product->profileModuleProperties)
- .searchPaths();
- const QStringList &currentSearchPaths = loaderState.itemReader().allSearchPaths();
- for (const QString &p : prefsSearchPaths) {
- if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
- product->searchPaths << p;
- }
-}
-
-QVariantMap DependenciesResolver::Private::extractParameters(Item *dependsItem) const
+QVariantMap DependenciesResolver::extractParameters(Item *dependsItem) const
{
- QVariantMap result;
- const Item::PropertyMap &itemProperties = filterItemProperties(dependsItem->properties());
- if (itemProperties.empty())
- return result;
- auto origProperties = dependsItem->properties();
-
- // TODO: This is not exception-safe. Also, can't we do the item value check along the
- // way, without allocationg an extra map and exchanging the list of children?
- dependsItem->setProperties(itemProperties);
-
- JSValue sv = loaderState.evaluator().scriptValue(dependsItem);
try {
- result = safeToVariant(loaderState.evaluator().engine()->context(), sv);
+ QVariantMap result;
+ const auto &properties = dependsItem->properties();
+ Evaluator &evaluator = m_loaderState.evaluator();
+ for (auto it = properties.begin(); it != properties.end(); ++it) {
+ if (it.value()->type() != Value::ItemValueType)
+ continue;
+ const JSValue sv = evaluator.scriptValue(
+ std::static_pointer_cast<ItemValue>(it.value())->item());
+ result.insert(it.key(), safeToVariant(evaluator.engine()->context(), sv));
+ }
+ return result;
} catch (const ErrorInfo &exception) {
auto ei = exception;
ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
throw ei;
}
- dependsItem->setProperties(origProperties);
- return result;
}
-void DependenciesResolver::Private::checkForModuleNamePrefixCollision(
+void DependenciesResolver::forwardParameterDeclarations(const Item *dependsItem,
+ const Item::Modules &modules)
+{
+ for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) {
+ if (it.value()->type() != Value::ItemValueType)
+ continue;
+ forwardParameterDeclarations(it.key(),
+ std::static_pointer_cast<ItemValue>(it.value())->item(),
+ modules);
+ }
+}
+
+void DependenciesResolver::forwardParameterDeclarations(
+ const QualifiedId &moduleName, Item *item, const Item::Modules &modules)
+{
+ auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) {
+ return m.name == moduleName;
+ });
+ if (it != modules.end()) {
+ item->setPropertyDeclarations(m_loaderState.topLevelProject().parameterDeclarations(
+ it->item->rootPrototype()));
+ } else {
+ for (auto it = item->properties().begin(); it != item->properties().end(); ++it) {
+ if (it.value()->type() != Value::ItemValueType)
+ continue;
+ forwardParameterDeclarations(QualifiedId(moduleName) << it.key(),
+ std::static_pointer_cast<ItemValue>(it.value())->item(),
+ modules);
+ }
+ }
+}
+
+std::list<DependenciesResolvingState> &DependenciesResolver::stateStack()
+{
+ QBS_CHECK(m_product.dependenciesContext);
+ return static_cast<DependenciesContextImpl *>(m_product.dependenciesContext.get())->stateStack;
+}
+
+void DependenciesResolver::checkForModuleNamePrefixCollision(
const FullyResolvedDependsItem &dependency)
{
- if (!product->item)
+ if (!m_product.item)
return;
- for (const Item::Module &m : product->item->modules()) {
- if (m.name.length() == dependency.name.length()
- || m.name.front() != dependency.name.front()) {
+ for (const Item::Module &m : m_product.item->modules()) {
+ if (m.name.length() == dependency.name.length())
continue;
- }
+
QualifiedId shortName;
QualifiedId longName;
- if (m.name < dependency.name) {
+ if (m.name.length() < dependency.name.length()) {
shortName = m.name;
longName = dependency.name;
} else {
shortName = dependency.name;
longName = m.name;
}
- throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the "
- "name of module '%2', which is not allowed")
+ const auto isPrefix = [&] {
+ for (int i = 0; i < shortName.length(); ++i) {
+ if (shortName.at(i) != longName.at(i))
+ return false;
+ }
+ return true;
+ };
+ if (!isPrefix())
+ continue;
+
+ throw ErrorInfo(Tr::tr("The name of module '%1' is a prefix of the name of module '%2', "
+ "which is not allowed")
.arg(shortName.toString(), longName.toString()), dependency.location());
}
}
-Item::Module DependenciesResolver::Private::createModule(
+Item::Module DependenciesResolver::createModule(
const FullyResolvedDependsItem &dependency, Item *item, ProductContext *productDep)
{
Item::Module m;
m.item = item;
- if (productDep) {
- m.productInfo.emplace(productDep->item, productDep->multiplexConfigurationId,
- productDep->profileName);
- }
+ m.product = productDep;
m.name = dependency.name;
m.required = dependency.requiredLocally;
m.versionRange = dependency.versionRange;
@@ -1061,28 +1036,33 @@ Item::Module DependenciesResolver::Private::createModule(
FullyResolvedDependsItem::FullyResolvedDependsItem(
ProductContext *product, const EvaluatedDependsItem &dependency)
- : product(product), item(dependency.item), name(product->name),
- versionRange(dependency.versionRange), parameters(dependency.parameters),
- fallbackMode(FallbackMode::Disabled), checkProduct(false) {}
+ : product(product)
+ , item(dependency.item)
+ , name(product->name)
+ , versionRange(dependency.versionRange)
+ , parameters(dependency.parameters)
+ , checkProduct(false)
+{}
FullyResolvedDependsItem FullyResolvedDependsItem::makeBaseDependency()
{
FullyResolvedDependsItem item;
- item.fallbackMode = FallbackMode::Disabled;
item.name = StringConstants::qbsModule();
return item;
}
FullyResolvedDependsItem::FullyResolvedDependsItem(
const EvaluatedDependsItem &dependency, QualifiedId name, QString profile, QString multiplexId)
- : item(dependency.item), name(std::move(name)),
- profile(std::move(profile)), multiplexId(std::move(multiplexId)),
- versionRange(dependency.versionRange),
- parameters(dependency.parameters),
- limitToSubProject(dependency.limitToSubProject),
- fallbackMode(dependency.fallbackMode),
- requiredLocally(dependency.requiredLocally),
- requiredGlobally(dependency.requiredGlobally) {}
+ : item(dependency.item)
+ , name(std::move(name))
+ , profile(std::move(profile))
+ , multiplexId(std::move(multiplexId))
+ , versionRange(dependency.versionRange)
+ , parameters(dependency.parameters)
+ , limitToSubProject(dependency.limitToSubProject)
+ , requiredLocally(dependency.requiredLocally)
+ , requiredGlobally(dependency.requiredGlobally)
+{}
QString FullyResolvedDependsItem::id() const
{
@@ -1104,7 +1084,7 @@ CodeLocation FullyResolvedDependsItem::location() const
QString FullyResolvedDependsItem::displayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
+ return fullProductDisplayName(name.toString(), multiplexId);
}
bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
@@ -1117,16 +1097,6 @@ bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
return false;
}
-Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
-{
- Item::PropertyMap result;
- for (auto it = properties.begin(); it != properties.end(); ++it) {
- if (it.value()->type() == Value::ItemValueType)
- result.insert(it.key(), it.value());
- }
- return result;
-}
-
QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
{
QVariantMap result;
@@ -1141,4 +1111,65 @@ QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
return result;
}
+DependenciesContextImpl::DependenciesContextImpl(ProductContext &product, LoaderState &loaderState)
+ : m_product(product)
+{
+ setSearchPathsForProduct(loaderState);
+
+ // Initialize the state with the direct Depends items of the product item.
+ DependenciesResolvingState newState{product.item,};
+ for (Item * const child : product.item->children()) {
+ if (child->type() == ItemType::Depends)
+ newState.pendingDependsItems.push(child);
+ }
+ stateStack.push_front(std::move(newState));
+ stateStack.front().pendingResolvedDependencies.push(
+ FullyResolvedDependsItem::makeBaseDependency());
+}
+
+std::pair<ProductDependency, ProductContext *> DependenciesContextImpl::pendingDependency() const
+{
+ QBS_CHECK(!stateStack.empty());
+ if (const auto &currentDependsItem = stateStack.front().currentDependsItem;
+ currentDependsItem && !currentDependsItem->productTypes.empty()) {
+ qCDebug(lcLoaderScheduling) << "product" << m_product.displayName()
+ << "to be delayed because of bulk dependency";
+ return {ProductDependency::Bulk, nullptr};
+ }
+ if (!stateStack.front().pendingResolvedDependencies.empty()) {
+ if (ProductContext * const dep = stateStack.front().pendingResolvedDependencies
+ .front().product) {
+ if (m_product.project->topLevelProject->isProductQueuedForHandling(*dep)) {
+ qCDebug(lcLoaderScheduling) << "product" << m_product.displayName()
+ << "to be delayed because of dependency "
+ "to unfinished product" << dep->displayName();
+ return {ProductDependency::Single, dep};
+ } else {
+ qCDebug(lcLoaderScheduling) << "product" << m_product.displayName()
+ << "to be re-scheduled, as dependency "
+ << dep->displayName()
+ << "appears to have finished in the meantime";
+ return {ProductDependency::None, dep};
+ }
+ }
+ }
+ return {ProductDependency::None, nullptr};
+}
+
+void DependenciesContextImpl::setSearchPathsForProduct(LoaderState &loaderState)
+{
+ QBS_CHECK(m_product.searchPaths.isEmpty());
+
+ m_product.searchPaths = loaderState.itemReader().readExtraSearchPaths(m_product.item);
+ Settings settings(loaderState.parameters().settingsDirectory());
+ const QStringList prefsSearchPaths = Preferences(&settings, m_product.profileModuleProperties)
+ .searchPaths();
+ const QStringList &currentSearchPaths = loaderState.itemReader().allSearchPaths();
+ for (const QString &p : prefsSearchPaths) {
+ if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
+ m_product.searchPaths << p;
+ }
+}
+
+} // namespace
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/dependenciesresolver.h b/src/lib/corelib/loader/dependenciesresolver.h
index 0b6d57023..48502adb7 100644
--- a/src/lib/corelib/loader/dependenciesresolver.h
+++ b/src/lib/corelib/loader/dependenciesresolver.h
@@ -39,53 +39,19 @@
#pragma once
-#include <tools/pimpl.h>
-#include <tools/set.h>
-
-#include <QtGlobal>
-
-#include <functional>
-
-QT_BEGIN_NAMESPACE
-class QString;
-QT_END_NAMESPACE
-
namespace qbs::Internal {
class Item;
class LoaderState;
class ProductContext;
-class StoredModuleProviderInfo;
enum class Deferral;
// Collects the products' dependencies and builds the list of modules from them.
// Actual loading of module files is offloaded to ModuleLoader.
-class DependenciesResolver
-{
-public:
- DependenciesResolver(LoaderState &loaderState);
- ~DependenciesResolver();
-
- // Returns false if the product has unhandled product dependencies and thus needs
- // to be deferred, true otherwise.
- bool resolveDependencies(ProductContext &product, Deferral deferral);
-
- void checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const;
-
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- StoredModuleProviderInfo storedModuleProviderInfo() const;
- const Set<QString> &tempQbsFiles() const;
-
- void printProfilingInfo(int indent);
-
- // Note: This function is never called for regular loading of the base module into a product,
- // but only for the special cases of loading the dummy base module into a project
- // and temporarily providing a base module for product multiplexing.
- Item *loadBaseModule(ProductContext &product, Item *item);
+void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState);
-private:
- class Private;
- Pimpl<Private> d;
-};
+// Note: This function is never called for regular loading of the base module into a product,
+// but only for the special cases of loading the dummy base module into a project
+// and temporarily providing a base module for product multiplexing.
+Item *loadBaseModule(ProductContext &product, Item *item, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/groupshandler.cpp b/src/lib/corelib/loader/groupshandler.cpp
index a0cab5367..3dbfad352 100644
--- a/src/lib/corelib/loader/groupshandler.cpp
+++ b/src/lib/corelib/loader/groupshandler.cpp
@@ -51,66 +51,47 @@
#include <tools/stringconstants.h>
namespace qbs::Internal {
-class GroupsHandler::Private
+class GroupsHandler
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ GroupsHandler(ProductContext &product, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState) {}
+ void run();
+
+private:
void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
QualifiedIdSet &properties);
void markModuleTargetGroups(Item *group, const Item::Module &module);
- void moveGroupsFromModuleToProduct(Item *product, Item *productScope,
- const Item::Module &module);
- void moveGroupsFromModulesToProduct(Item *product, Item *productScope);
+ void moveGroupsFromModuleToProduct(const Item::Module &module);
+ void moveGroupsFromModulesToProduct();
void propagateModulesFromParent(Item *group);
- void handleGroup(Item *product, Item *group);
+ void handleGroup(Item *group);
void adjustScopesInGroupModuleInstances(Item *groupItem, const Item::Module &module);
QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group);
- LoaderState &loaderState;
- std::unordered_map<const Item *, QualifiedIdSet> modulePropsSetInGroups;
- Set<Item *> disabledGroups;
- qint64 elapsedTime = 0;
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
};
-GroupsHandler::GroupsHandler(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-GroupsHandler::~GroupsHandler() = default;
-
-void GroupsHandler::setupGroups(Item *product, Item *productScope)
+void setupGroups(ProductContext &product, LoaderState &loaderState)
{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
-
- d->modulePropsSetInGroups.clear();
- d->disabledGroups.clear();
- d->moveGroupsFromModulesToProduct(product, productScope);
- for (Item * const child : product->children()) {
- if (child->type() == ItemType::Group)
- d->handleGroup(product, child);
- }
+ GroupsHandler(product, loaderState).run();
}
-std::unordered_map<const Item *, QualifiedIdSet> GroupsHandler::modulePropertiesSetInGroups() const
+void GroupsHandler::run()
{
- return d->modulePropsSetInGroups;
-}
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.groupsSetup : nullptr);
-Set<Item *> GroupsHandler::disabledGroups() const
-{
- return d->disabledGroups;
-}
-
-void GroupsHandler::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Setting up Groups took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
+ moveGroupsFromModulesToProduct();
+ for (Item * const child : m_product.item->children()) {
+ if (child->type() == ItemType::Group)
+ handleGroup(child);
+ }
}
-void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
+void GroupsHandler::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
QualifiedIdSet &properties)
{
const Item::PropertyMap &props = iv->item()->properties();
@@ -131,10 +112,10 @@ void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const Quali
}
}
-void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Module &module)
+void GroupsHandler::markModuleTargetGroups(Item *group, const Item::Module &module)
{
QBS_CHECK(group->type() == ItemType::Group);
- if (loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) {
+ if (m_loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) {
group->setProperty(StringConstants::modulePropertyInternal(),
VariantValue::create(module.name.toString()));
}
@@ -142,8 +123,7 @@ void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Mod
markModuleTargetGroups(child, module);
}
-void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *productScope,
- const Item::Module &module)
+void GroupsHandler::moveGroupsFromModuleToProduct(const Item::Module &module)
{
if (!module.item->isPresentModule())
return;
@@ -153,37 +133,41 @@ void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *
++it;
continue;
}
- child->setScope(productScope);
- setScopeForDescendants(child, productScope);
- Item::addChild(product, child);
+
+ Item * const scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
+ scope->setProperties(module.item->properties());
+ scope->setScope(m_product.scope);
+ child->setScope(scope);
+ setScopeForDescendants(child, scope);
+
+ Item::addChild(m_product.item, child);
markModuleTargetGroups(child, module);
it = module.item->children().erase(it);
}
}
-void GroupsHandler::Private::moveGroupsFromModulesToProduct(Item *product, Item *productScope)
+void GroupsHandler::moveGroupsFromModulesToProduct()
{
- for (const Item::Module &module : product->modules())
- moveGroupsFromModuleToProduct(product, productScope, module);
+ for (const Item::Module &module : m_product.item->modules())
+ moveGroupsFromModuleToProduct(module);
}
// TODO: I don't completely understand this function, and I suspect we do both too much
// and too little here. In particular, I'm not sure why we should even have to do anything
// with groups that don't attach properties.
// Set aside a day or two at some point to fully grasp all the details and rewrite accordingly.
-void GroupsHandler::Private::propagateModulesFromParent(Item *group)
+void GroupsHandler::propagateModulesFromParent(Item *group)
{
QBS_CHECK(group->type() == ItemType::Group);
QHash<QualifiedId, Item *> moduleInstancesForGroup;
// Step 1: "Instantiate" the product's modules for the group.
for (Item::Module m : group->parent()->modules()) {
- Item * const targetItem = loaderState.moduleInstantiator()
- .retrieveModuleInstanceItem(group, m.name);
+ Item * const targetItem = retrieveModuleInstanceItem(group, m.name, m_loaderState);
QBS_CHECK(targetItem->type() == ItemType::ModuleInstancePlaceholder);
targetItem->setPrototype(m.item);
- Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
+ Item * const moduleScope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
moduleScope->setFile(group->file());
moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids
moduleScope->setScope(group);
@@ -210,7 +194,8 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
adaptedModules << depMod;
if (depMod.name.front() == module.name.front())
continue;
- const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front());
+ const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front(),
+ m_loaderState.itemPool());
QBS_CHECK(modulePrefix);
module.item->setProperty(depMod.name.front(), modulePrefix);
}
@@ -220,7 +205,7 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(group);
if (propsSetInGroup.empty())
return;
- modulePropsSetInGroups.insert(std::make_pair(group, propsSetInGroup));
+ m_product.modulePropertiesSetInGroups.insert(std::make_pair(group, propsSetInGroup));
// Step 3: Adapt scopes in values. This is potentially necessary if module properties
// get assigned on the group level.
@@ -228,18 +213,17 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
adjustScopesInGroupModuleInstances(group, module);
}
-void GroupsHandler::Private::handleGroup(Item *product, Item *group)
+void GroupsHandler::handleGroup(Item *group)
{
propagateModulesFromParent(group);
- if (!loaderState.evaluator().boolValue(group, StringConstants::conditionProperty()))
- disabledGroups << group;
+ m_loaderState.topLevelProject().checkItemCondition(group, m_loaderState.evaluator());
for (Item * const child : group->children()) {
if (child->type() == ItemType::Group)
- handleGroup(product, child);
+ handleGroup(child);
}
}
-void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
+void GroupsHandler::adjustScopesInGroupModuleInstances(Item *groupItem,
const Item::Module &module)
{
if (!module.item->isPresentModule())
@@ -279,7 +263,7 @@ void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
continue;
if (propValue->scope())
- module.item->setProperty(propName, propValue->clone());
+ module.item->setProperty(propName, propValue->clone(m_loaderState.itemPool()));
}
for (const ValuePtr &prop : module.item->properties()) {
@@ -300,7 +284,7 @@ void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
}
}
-QualifiedIdSet GroupsHandler::Private::gatherModulePropertiesSetInGroup(const Item *group)
+QualifiedIdSet GroupsHandler::gatherModulePropertiesSetInGroup(const Item *group)
{
QualifiedIdSet propsSetInGroup;
const Item::PropertyMap &props = group->properties();
diff --git a/src/lib/corelib/loader/groupshandler.h b/src/lib/corelib/loader/groupshandler.h
index ff6afdad9..5de94ce7c 100644
--- a/src/lib/corelib/loader/groupshandler.h
+++ b/src/lib/corelib/loader/groupshandler.h
@@ -39,16 +39,9 @@
#pragma once
-#include <language/qualifiedid.h>
-#include <tools/set.h>
-#include <tools/pimpl.h>
-
-#include <unordered_map>
-#include <utility>
-
namespace qbs::Internal {
-class Item;
class LoaderState;
+class ProductContext;
// Sets up Group items for the actual resolving stage. Responsibilities:
// - Moving Group items located in modules over to the product.
@@ -58,21 +51,6 @@ class LoaderState;
// - As a side effect of the above point, collecting all properties set on the Group level
// to help the ProjectResolver decide which properties need to be re-evaluated at all,
// which is an important optimization (see commit 9cd8653eef).
-class GroupsHandler
-{
-public:
- GroupsHandler(LoaderState &loaderState);
- ~GroupsHandler();
-
- void setupGroups(Item *product, Item *productScope);
- std::unordered_map<const Item *, QualifiedIdSet> modulePropertiesSetInGroups() const;
- Set<Item *> disabledGroups() const;
-
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+void setupGroups(ProductContext &product, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp
index 8d9b2ccd4..0638d1af5 100644
--- a/src/lib/corelib/loader/itemreader.cpp
+++ b/src/lib/corelib/loader/itemreader.cpp
@@ -73,7 +73,8 @@ ItemReader::ItemReader(LoaderState &loaderState) : m_loaderState(loaderState) {}
void ItemReader::init()
{
- m_visitorState = std::make_unique<ItemReaderVisitorState>(m_loaderState.logger());
+ m_visitorState = std::make_unique<ItemReaderVisitorState>(
+ m_loaderState.topLevelProject().itemReaderCache(), m_loaderState.logger());
m_visitorState->setDeprecationWarningMode(m_loaderState.parameters().deprecationWarningMode());
m_projectFilePath = m_loaderState.parameters().projectFilePath();
setSearchPaths(m_loaderState.parameters().searchPaths());
@@ -150,11 +151,6 @@ Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referenc
}
}
-Set<QString> ItemReader::filesRead() const
-{
- return m_visitorState->filesRead();
-}
-
void ItemReader::handlePropertyOptions(Item *optionsItem)
{
Evaluator &evaluator = m_loaderState.evaluator();
@@ -218,6 +214,14 @@ void ItemReader::handleAllPropertyOptionsItems(Item *item)
Item *ItemReader::setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation)
{
Item *item = readFile(filePath, referencingLocation);
+
+ // This is technically not needed, because files are only set up once and then served
+ // from a cache. But it simplifies the checks in item.cpp if we require the locking invariant
+ // to always hold.
+ std::unique_ptr<ModuleItemLocker> locker;
+ if (item->type() == ItemType::Module)
+ locker = std::make_unique<ModuleItemLocker>(*item);
+
handleAllPropertyOptionsItems(item);
return item;
}
@@ -226,7 +230,7 @@ Item *ItemReader::wrapInProjectIfNecessary(Item *item)
{
if (item->type() == ItemType::Project)
return item;
- Item *prj = Item::create(item->pool(), ItemType::Project);
+ Item *prj = Item::create(&m_loaderState.itemPool(), ItemType::Project);
Item::addChild(prj, item);
prj->setFile(item->file());
prj->setLocation(item->location());
diff --git a/src/lib/corelib/loader/itemreader.h b/src/lib/corelib/loader/itemreader.h
index 01e9ae18d..e444db16c 100644
--- a/src/lib/corelib/loader/itemreader.h
+++ b/src/lib/corelib/loader/itemreader.h
@@ -87,8 +87,6 @@ public:
Item *wrapInProjectIfNecessary(Item *item);
QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
- Set<QString> filesRead() const;
-
qint64 elapsedTime() const { return m_elapsedTime; }
private:
diff --git a/src/lib/corelib/loader/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp
index 48bd0af1b..c3e6b9b89 100644
--- a/src/lib/corelib/loader/itemreaderastvisitor.cpp
+++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp
@@ -75,6 +75,8 @@ ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState,
{
}
+ItemReaderASTVisitor::~ItemReaderASTVisitor() = default;
+
bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram)
{
ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file);
@@ -106,6 +108,8 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast)
Item *item = Item::create(m_itemPool, ItemType::Unknown);
item->setFile(m_file);
item->setLocation(itemLocation);
+ const AST::SourceLocation &endLoc = ast->lastSourceLocation();
+ item->setEndPosition(CodePosition(endLoc.startLine, endLoc.startColumn));
// Inheritance resolving, part 1: Find out our actual type name (needed for setting
// up children and alternatives).
@@ -135,10 +139,15 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast)
item->m_type = itemType;
- if (m_item)
+ if (m_item) {
Item::addChild(m_item, item); // Add this item to the children of the parent item.
- else
+ } else {
m_item = item; // This is the root item.
+ if (itemType == ItemType::Module) {
+ QBS_CHECK(!m_moduleItemLocker);
+ m_moduleItemLocker = std::make_unique<ModuleItemLocker>(*m_item);
+ }
+ }
if (ast->initializer) {
Item *mdi = m_visitorState.mostDerivingItem();
@@ -153,7 +162,7 @@ bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast)
m_visitorState.setMostDerivingItem(mdi);
}
- ASTPropertiesItemHandler(item).handlePropertiesItems();
+ ASTPropertiesItemHandler(item, *m_itemPool).handlePropertiesItems();
// Inheritance resolving, part 2 (depends on alternatives having been set up).
if (baseItem) {
@@ -239,9 +248,11 @@ bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast)
const auto * const idExp = AST::cast<AST::IdentifierExpression *>(expStmt->expression);
if (Q_UNLIKELY(!idExp || idExp->name.isEmpty()))
throw ErrorInfo(Tr::tr("id: must be followed by identifier"));
+ if (m_item->type() == ItemType::Module)
+ throw ErrorInfo(Tr::tr("Module items cannot have an id property."));
m_item->m_id = idExp->name.toString();
m_file->ensureIdScope(m_itemPool);
- ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id());
+ ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id(), *m_itemPool);
if (existingId) {
ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id()));
e.append(Tr::tr("First occurrence is here."), existingId->item()->location());
@@ -335,6 +346,9 @@ void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src)
dst->setPropertyDeclaration(pd.name(), pd);
}
+ std::unique_ptr<ModuleItemLocker> locker;
+ if (src->type() == ItemType::Module)
+ locker = std::make_unique<ModuleItemLocker>(*src);
for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) {
ValuePtr &v = dst->m_properties[it.key()];
if (!v) {
diff --git a/src/lib/corelib/loader/itemreaderastvisitor.h b/src/lib/corelib/loader/itemreaderastvisitor.h
index a102b2821..c47501f2f 100644
--- a/src/lib/corelib/loader/itemreaderastvisitor.h
+++ b/src/lib/corelib/loader/itemreaderastvisitor.h
@@ -49,6 +49,8 @@
#include <QtCore/qhash.h>
#include <QtCore/qstringlist.h>
+#include <memory.h>
+
namespace qbs {
class CodeLocation;
@@ -56,12 +58,15 @@ namespace Internal {
class Item;
class ItemPool;
class ItemReaderVisitorState;
+class ModuleItemLocker;
class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor
{
public:
ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, FileContextPtr file,
ItemPool *itemPool, Logger &logger);
+ ~ItemReaderASTVisitor();
+
void checkItemTypes() { doCheckItemTypes(rootItem()); }
Item *rootItem() const { return m_item; }
@@ -88,6 +93,7 @@ private:
Logger &m_logger;
QHash<QStringList, QString> m_typeNameToFile;
Item *m_item = nullptr;
+ std::unique_ptr<ModuleItemLocker> m_moduleItemLocker;
ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder;
};
diff --git a/src/lib/corelib/loader/itemreadervisitorstate.cpp b/src/lib/corelib/loader/itemreadervisitorstate.cpp
index 57484043a..6ea23c021 100644
--- a/src/lib/corelib/loader/itemreadervisitorstate.cpp
+++ b/src/lib/corelib/loader/itemreadervisitorstate.cpp
@@ -39,6 +39,7 @@
#include "itemreadervisitorstate.h"
#include "itemreaderastvisitor.h"
+#include "loaderutils.h"
#include <language/asttools.h>
#include <language/filecontext.h>
@@ -47,89 +48,35 @@
#include <parser/qmljslexer_p.h>
#include <parser/qmljsparser_p.h>
#include <tools/error.h>
+#include <tools/stringconstants.h>
-#include <QtCore/qshareddata.h>
+#include <QtCore/qdiriterator.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
-#include <QtCore/qshareddata.h>
#include <QtCore/qtextstream.h>
namespace qbs {
namespace Internal {
-class ASTCacheValueData : public QSharedData
+ItemReaderVisitorState::ItemReaderVisitorState(ItemReaderCache &cache, Logger &logger)
+ : m_cache(cache), m_logger(logger)
{
- Q_DISABLE_COPY(ASTCacheValueData)
-public:
- ASTCacheValueData()
- : ast(nullptr)
- , processing(false)
- {
- }
-
- QString code;
- QbsQmlJS::Engine engine;
- QbsQmlJS::AST::UiProgram *ast;
- bool processing;
-};
-
-class ASTCacheValue
-{
-public:
- ASTCacheValue()
- : d(new ASTCacheValueData)
- {
- }
-
- ASTCacheValue(const ASTCacheValue &other) = default;
-
- void setProcessingFlag(bool b) { d->processing = b; }
- bool isProcessing() const { return d->processing; }
-
- void setCode(const QString &code) { d->code = code; }
- QString code() const { return d->code; }
-
- QbsQmlJS::Engine *engine() const { return &d->engine; }
-
- void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; }
- QbsQmlJS::AST::UiProgram *ast() const { return d->ast; }
- bool isValid() const { return d->ast; }
-
-private:
- QExplicitlySharedDataPointer<ASTCacheValueData> d;
-};
-
-class ItemReaderVisitorState::ASTCache : public std::unordered_map<QString, ASTCacheValue> {};
-
-
-ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger)
- : m_logger(logger)
- , m_astCache(std::make_unique<ASTCache>())
-{
-
}
-ItemReaderVisitorState::~ItemReaderVisitorState() = default;
-
Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringList &searchPaths,
ItemPool *itemPool)
{
- ASTCacheValue &cacheValue = (*m_astCache)[filePath];
- if (cacheValue.isValid()) {
- if (Q_UNLIKELY(cacheValue.isProcessing()))
- throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath));
- } else {
+ const auto setupCacheEntry = [&](ItemReaderCache::AstCacheEntry &entry) {
QFile file(filePath);
if (Q_UNLIKELY(!file.open(QFile::ReadOnly)))
throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath));
- m_filesRead.insert(filePath);
QTextStream stream(&file);
setupDefaultCodec(stream);
const QString &code = stream.readAll();
- QbsQmlJS::Lexer lexer(cacheValue.engine());
+ QbsQmlJS::Lexer lexer(&entry.engine);
lexer.setCode(code, 1);
- QbsQmlJS::Parser parser(cacheValue.engine());
+ QbsQmlJS::Parser parser(&entry.engine);
file.close();
if (!parser.parse()) {
@@ -142,42 +89,49 @@ Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringLis
}
}
- cacheValue.setCode(code);
- cacheValue.setAst(parser.ast());
- }
+ entry.code = code;
+ entry.ast = parser.ast();
+ };
+ ItemReaderCache::AstCacheEntry &cacheEntry = m_cache.retrieveOrSetupCacheEntry(
+ filePath, setupCacheEntry);
const FileContextPtr file = FileContext::create();
file->setFilePath(QFileInfo(filePath).absoluteFilePath());
- file->setContent(cacheValue.code());
+ file->setContent(cacheEntry.code);
file->setSearchPaths(searchPaths);
ItemReaderASTVisitor astVisitor(*this, file, itemPool, m_logger);
{
class ProcessingFlagManager {
public:
- ProcessingFlagManager(ASTCacheValue &v) : m_cacheValue(v) { v.setProcessingFlag(true); }
- ~ProcessingFlagManager() { m_cacheValue.setProcessingFlag(false); }
+ ProcessingFlagManager(ItemReaderCache::AstCacheEntry &e, const QString &filePath)
+ : m_cacheEntry(e)
+ {
+ if (!e.addProcessingThread())
+ throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath));
+ }
+ ~ProcessingFlagManager() { m_cacheEntry.removeProcessingThread(); }
+
private:
- ASTCacheValue &m_cacheValue;
- } processingFlagManager(cacheValue);
- cacheValue.ast()->accept(&astVisitor);
+ ItemReaderCache::AstCacheEntry &m_cacheEntry;
+ } processingFlagManager(cacheEntry, filePath);
+ cacheEntry.ast->accept(&astVisitor);
}
astVisitor.checkItemTypes();
return astVisitor.rootItem();
}
-void ItemReaderVisitorState::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries)
-{
- m_directoryEntries.insert(dirPath, entries);
-}
-
-bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const
+void ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const
{
- const auto it = m_directoryEntries.constFind(dirPath);
- if (it == m_directoryEntries.constEnd())
- return false;
- *entries = it.value();
- return true;
+ *entries = m_cache.retrieveOrSetDirectoryEntries(dirPath, [&dirPath] {
+ QStringList fileNames;
+ QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
+ while (dirIter.hasNext()) {
+ dirIter.next();
+ fileNames << dirIter.fileName();
+ }
+ return fileNames;
+ });
}
Item *ItemReaderVisitorState::mostDerivingItem() const
diff --git a/src/lib/corelib/loader/itemreadervisitorstate.h b/src/lib/corelib/loader/itemreadervisitorstate.h
index dc22cfb42..cbf1966b6 100644
--- a/src/lib/corelib/loader/itemreadervisitorstate.h
+++ b/src/lib/corelib/loader/itemreadervisitorstate.h
@@ -50,21 +50,19 @@ namespace qbs {
namespace Internal {
class Item;
class ItemPool;
+class ItemReaderCache;
class Logger;
class ItemReaderVisitorState
{
public:
- ItemReaderVisitorState(Logger &logger);
- ~ItemReaderVisitorState();
+ ItemReaderVisitorState(ItemReaderCache &cache, Logger &logger);
Logger &logger() { return m_logger; }
- Set<QString> filesRead() const { return m_filesRead; }
Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool);
- void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries);
- bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const;
+ void findDirectoryEntries(const QString &dirPath, QStringList *entries) const;
Item *mostDerivingItem() const;
void setMostDerivingItem(Item *item);
@@ -74,13 +72,9 @@ public:
private:
DeprecationWarningMode m_deprecationWarningMode = defaultDeprecationWarningMode();
+ ItemReaderCache &m_cache;
Logger &m_logger;
- Set<QString> m_filesRead;
- QHash<QString, QStringList> m_directoryEntries;
Item *m_mostDerivingItem = nullptr;
-
- class ASTCache;
- const std::unique_ptr<ASTCache> m_astCache;
};
} // namespace Internal
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp
index 98ac10a92..05a077dbe 100644
--- a/src/lib/corelib/loader/loaderutils.cpp
+++ b/src/lib/corelib/loader/loaderutils.cpp
@@ -39,47 +39,33 @@
#include "loaderutils.h"
-#include "dependenciesresolver.h"
#include "itemreader.h"
-#include "localprofiles.h"
-#include "moduleinstantiator.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-#include "productitemmultiplexer.h"
#include <language/evaluator.h>
-#include <language/item.h>
+#include <language/filecontext.h>
+#include <language/itempool.h>
#include <language/language.h>
+#include <language/resolvedfilecontext.h>
+#include <language/scriptengine.h>
#include <language/value.h>
#include <logging/categories.h>
#include <logging/translator.h>
+#include <tools/fileinfo.h>
#include <tools/progressobserver.h>
#include <tools/setupprojectparameters.h>
#include <tools/stringconstants.h>
namespace qbs::Internal {
-void mergeParameters(QVariantMap &dst, const QVariantMap &src)
+QString fullProductDisplayName(const QString &name, const QString &multiplexId)
{
- for (auto it = src.begin(); it != src.end(); ++it) {
- if (it.value().userType() == QMetaType::QVariantMap) {
- QVariant &vdst = dst[it.key()];
- QVariantMap mdst = vdst.toMap();
- mergeParameters(mdst, it.value().toMap());
- vdst = mdst;
- } else {
- dst[it.key()] = it.value();
- }
- }
-}
-
-ShadowProductInfo getShadowProductInfo(const ProductContext &product)
-{
- const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
- return std::make_pair(isShadowProduct,
- isShadowProduct
- ? product.name.mid(StringConstants::shadowProductPrefix().size())
- : QString());
+ static const auto multiplexIdToString =[](const QString &id) {
+ return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
+ };
+ QString result = name;
+ if (!multiplexId.isEmpty())
+ result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexId));
+ return result;
}
void adjustParametersScopes(Item *item, Item *scope)
@@ -102,15 +88,15 @@ QString ProductContext::uniqueName() const
QString ProductContext::displayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+ return fullProductDisplayName(name, multiplexConfigurationId);
}
void ProductContext::handleError(const ErrorInfo &error)
{
- const bool alreadyHadError = info.delayedError.hasError();
+ const bool alreadyHadError = delayedError.hasError();
if (!alreadyHadError) {
- info.delayedError.append(Tr::tr("Error while handling product '%1':")
- .arg(name), item->location());
+ delayedError.append(Tr::tr("Error while handling product '%1':")
+ .arg(name), item->location());
}
if (error.isInternalError()) {
if (alreadyHadError) {
@@ -121,76 +107,855 @@ void ProductContext::handleError(const ErrorInfo &error)
}
const auto errorItems = error.items();
for (const ErrorItem &ei : errorItems)
- info.delayedError.append(ei.description(), ei.codeLocation());
- project->topLevelProject->productInfos[item] = info;
- project->topLevelProject->disabledItems << item;
- project->topLevelProject->erroneousProducts.insert(name);
+ delayedError.append(ei.description(), ei.codeLocation());
+ project->topLevelProject->addDisabledItem(item);
}
+TopLevelProjectContext::~TopLevelProjectContext() { qDeleteAll(m_projects); }
+
bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator)
{
if (evaluator.boolValue(item, StringConstants::conditionProperty()))
return true;
- disabledItems += item;
+ addDisabledItem(item);
return false;
}
-void TopLevelProjectContext::checkCancelation(const SetupProjectParameters &parameters)
+void TopLevelProjectContext::checkCancelation()
+{
+ if (m_progressObserver && m_progressObserver->canceled())
+ m_canceled = true;
+ if (m_canceled)
+ throw CancelException();
+}
+
+QString TopLevelProjectContext::sourceCodeForEvaluation(const JSSourceValueConstPtr &value)
+{
+ const auto sourceCodeGuard = m_sourceCode.lock();
+ QString &code = sourceCodeGuard.get()[value->sourceCode()];
+ if (!code.isNull())
+ return code;
+ code = value->sourceCodeForEvaluation();
+ return code;
+}
+
+ScriptFunctionPtr TopLevelProjectContext::scriptFunctionValue(Item *item, const QString &name)
+{
+ const JSSourceValuePtr value = item->sourceProperty(name);
+ QBS_CHECK(value);
+ const auto scriptFunctionMapGuard = m_scriptFunctionMap.lock();
+ ScriptFunctionPtr &script = scriptFunctionMapGuard.get()[value->location()];
+ if (!script.get()) {
+ script = ScriptFunction::create();
+ const PropertyDeclaration decl = item->propertyDeclaration(name);
+ script->sourceCode = sourceCodeAsFunction(value, decl);
+ script->location = value->location();
+ script->fileContext = resolvedFileContext(value->file());
+ }
+ return script;
+}
+
+QString TopLevelProjectContext::sourceCodeAsFunction(const JSSourceValueConstPtr &value,
+ const PropertyDeclaration &decl)
+{
+ const auto scriptFunctionMapGuard = m_scriptFunctions.lock();
+ QString &scriptFunction = scriptFunctionMapGuard.get()[std::make_pair(value->sourceCode(),
+ decl.functionArgumentNames())];
+ if (!scriptFunction.isNull())
+ return scriptFunction;
+ const QString args = decl.functionArgumentNames().join(QLatin1Char(','));
+ if (value->hasFunctionForm()) {
+ // Insert the argument list.
+ scriptFunction = value->sourceCodeForEvaluation();
+ scriptFunction.insert(10, args);
+ // Remove the function application "()" that has been
+ // added in ItemReaderASTVisitor::visitStatement.
+ scriptFunction.chop(2);
+ } else {
+ scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ")
+ + value->sourceCode().toString() + QLatin1String(";})");
+ }
+ return scriptFunction;
+}
+
+const ResolvedFileContextPtr &TopLevelProjectContext::resolvedFileContext(
+ const FileContextConstPtr &ctx)
+{
+ ResolvedFileContextPtr &result = m_fileContextMap[ctx];
+ if (!result)
+ result = ResolvedFileContext::create(*ctx);
+ return result;
+}
+
+void TopLevelProjectContext::removeProductToHandle(const ProductContext &product)
+{
+ std::unique_lock lock(m_productsToHandle.mutex);
+ m_productsToHandle.data.remove(&product);
+}
+
+bool TopLevelProjectContext::isProductQueuedForHandling(const ProductContext &product) const
+{
+ std::shared_lock lock(m_productsToHandle.mutex);
+ return m_productsToHandle.data.contains(&product);
+}
+
+void TopLevelProjectContext::addDisabledItem(Item *item)
+{
+ m_disabledItems.lock().get() << item;
+}
+
+bool TopLevelProjectContext::isDisabledItem(const Item *item) const
+{
+ return m_disabledItems.lock_shared().get().contains(item);
+}
+
+void TopLevelProjectContext::setProgressObserver(ProgressObserver *observer)
+{
+ m_progressObserver = observer;
+}
+
+ProgressObserver *TopLevelProjectContext::progressObserver() const { return m_progressObserver; }
+
+void TopLevelProjectContext::addQueuedError(const ErrorInfo &error)
+{
+ m_queuedErrors.lock().get() << error;
+}
+
+void TopLevelProjectContext::addProfileConfig(const QString &profileName,
+ const QVariantMap &profileConfig)
+{
+ m_profileConfigs.insert(profileName, profileConfig);
+}
+
+std::optional<QVariantMap> TopLevelProjectContext::profileConfig(const QString &profileName) const
+{
+ const auto it = m_profileConfigs.constFind(profileName);
+ if (it == m_profileConfigs.constEnd())
+ return {};
+ return it.value().toMap();
+}
+
+void TopLevelProjectContext::addProjectLevelProbe(const ProbeConstPtr &probe)
+{
+ m_probesInfo.projectLevelProbes << probe;
+}
+
+const std::vector<ProbeConstPtr> TopLevelProjectContext::projectLevelProbes() const
+{
+ return m_probesInfo.projectLevelProbes;
+}
+
+void TopLevelProjectContext::addProduct(ProductContext &product)
+{
+ m_productsByName.insert({product.name, &product});
+}
+
+void TopLevelProjectContext::addProductByType(ProductContext &product, const FileTags &tags)
+{
+ const auto productsByTypeGuard = m_productsByType.lock();
+ for (const FileTag &tag : tags)
+ productsByTypeGuard.get().insert({tag, &product});
+}
+
+ProductContext *TopLevelProjectContext::productWithNameAndConstraint(
+ const QString &name, const std::function<bool (ProductContext &)> &constraint)
+{
+ const auto candidates = m_productsByName.equal_range(name);
+ for (auto it = candidates.first; it != candidates.second; ++it) {
+ ProductContext * const candidate = it->second;
+ if (constraint(*candidate))
+ return candidate;
+ }
+ return nullptr;
+}
+
+std::vector<ProductContext *> TopLevelProjectContext::productsWithNameAndConstraint(
+ const QString &name, const std::function<bool (ProductContext &)> &constraint)
+{
+ std::vector<ProductContext *> result;
+ const auto candidates = m_productsByName.equal_range(name);
+ for (auto it = candidates.first; it != candidates.second; ++it) {
+ ProductContext * const candidate = it->second;
+ if (constraint(*candidate))
+ result << candidate;
+ }
+ return result;
+}
+
+std::vector<ProductContext *> TopLevelProjectContext::productsWithTypeAndConstraint(
+ const FileTags &tags, const std::function<bool (ProductContext &)> &constraint)
+{
+ const auto productsByTypeGuard = m_productsByType.lock_shared();
+ std::vector<ProductContext *> matchingProducts;
+ for (const FileTag &typeTag : tags) {
+ const auto range = productsByTypeGuard.get().equal_range(typeTag);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (constraint(*it->second))
+ matchingProducts.push_back(it->second);
+ }
+ }
+ return matchingProducts;
+}
+
+std::vector<std::pair<ProductContext *, CodeLocation>>
+TopLevelProjectContext::finishedProductsWithBulkDependency(const FileTag &tag) const
+{
+ return m_reverseBulkDependencies.value(tag);
+}
+
+void TopLevelProjectContext::registerBulkDependencies(ProductContext &product)
+{
+ for (const auto &tagAndLoc : product.bulkDependencies) {
+ for (const FileTag &tag : tagAndLoc.first)
+ m_reverseBulkDependencies[tag].emplace_back(&product, tagAndLoc.second);
+ }
+}
+
+void TopLevelProjectContext::addProjectNameUsedInOverrides(const QString &name)
+{
+ m_projectNamesUsedInOverrides << name;
+}
+
+const Set<QString> &TopLevelProjectContext::projectNamesUsedInOverrides() const
+{
+ return m_projectNamesUsedInOverrides;
+}
+
+void TopLevelProjectContext::addProductNameUsedInOverrides(const QString &name)
+{
+ m_productNamesUsedInOverrides << name;
+}
+
+const Set<QString> &TopLevelProjectContext::productNamesUsedInOverrides() const
+{
+ return m_productNamesUsedInOverrides;
+}
+
+void TopLevelProjectContext::addMultiplexConfiguration(const QString &id, const QVariantMap &config)
+{
+ m_multiplexConfigsById.insert(std::make_pair(id, config));
+}
+
+QVariantMap TopLevelProjectContext::multiplexConfiguration(const QString &id) const
+{
+ if (id.isEmpty())
+ return {};
+ const auto it = m_multiplexConfigsById.find(id);
+ QBS_CHECK(it != m_multiplexConfigsById.end() && !it->second.isEmpty());
+ return it->second;
+}
+
+std::lock_guard<std::mutex> TopLevelProjectContext::moduleProvidersCacheLock()
+{
+ return std::lock_guard<std::mutex>(m_moduleProvidersCacheMutex);
+}
+
+void TopLevelProjectContext::setModuleProvidersCache(const ModuleProvidersCache &cache)
+{
+ m_moduleProvidersCache = cache;
+}
+
+ModuleProviderInfo *TopLevelProjectContext::moduleProvider(const ModuleProvidersCacheKey &key)
+{
+ if (const auto it = m_moduleProvidersCache.find(key); it != m_moduleProvidersCache.end())
+ return &(*it);
+ return nullptr;
+}
+
+ModuleProviderInfo &TopLevelProjectContext::addModuleProvider(const ModuleProvidersCacheKey &key,
+ const ModuleProviderInfo &provider)
+{
+ return m_moduleProvidersCache[key] = provider;
+}
+
+void TopLevelProjectContext::addParameterDeclarations(const Item *moduleProto,
+ const Item::PropertyDeclarationMap &decls)
+{
+ m_parameterDeclarations.lock().get().insert({moduleProto, decls});
+}
+
+Item::PropertyDeclarationMap TopLevelProjectContext::parameterDeclarations(Item *moduleProto) const
+{
+ const auto parameterDeclarationsGuard = m_parameterDeclarations.lock_shared();
+ const auto &declarations = parameterDeclarationsGuard.get();
+ if (const auto it = declarations.find(moduleProto); it != declarations.end()) {
+ return it->second;
+ }
+ return {};
+}
+
+void TopLevelProjectContext::setParameters(const Item *moduleProto, const QVariantMap &parameters)
+{
+ m_parameters.lock().get().insert({moduleProto, parameters});
+}
+
+QVariantMap TopLevelProjectContext::parameters(Item *moduleProto) const
+{
+ const auto parametersGuard = m_parameters.lock_shared();
+ const auto &parameters = parametersGuard.get();
+ if (const auto it = parameters.find(moduleProto); it != parameters.end()) {
+ return it->second;
+ }
+ return {};
+}
+
+void TopLevelProjectContext::addCodeLink(const QString &sourceFile, const CodeRange &sourceRange,
+ const CodeLocation &target)
+{
+ const auto codeLinksGuard = m_codeLinks.lock();
+ QList<CodeLocation> &links = codeLinksGuard.get()[sourceFile][sourceRange];
+ if (!links.contains(target))
+ links << target;
+}
+
+QString TopLevelProjectContext::findModuleDirectory(
+ const QualifiedId &module, const QString &searchPath,
+ const std::function<QString()> &findOnDisk)
+{
+ const auto modulePathCacheGuard = m_modulePathCache.lock();
+ auto &path = modulePathCacheGuard.get()[{searchPath, module}];
+ if (!path)
+ path = findOnDisk();
+ return *path;
+}
+
+QStringList TopLevelProjectContext::getModuleFilesForDirectory(
+ const QString &dir, const std::function<QStringList ()> &findOnDisk)
+{
+ const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock();
+ auto &list = moduleFilesGuard.get()[dir];
+ if (!list)
+ list = findOnDisk();
+ return *list;
+}
+
+void TopLevelProjectContext::removeModuleFileFromDirectoryCache(const QString &filePath)
+{
+ const auto moduleFilesGuard = m_moduleFilesPerDirectory.lock();
+ auto &moduleFiles = moduleFilesGuard.get();
+ const auto it = moduleFiles.find(FileInfo::path(filePath));
+ QBS_CHECK(it != moduleFiles.end());
+ auto &files = it->second;
+ QBS_CHECK(files);
+ files->removeOne(filePath);
+}
+
+void TopLevelProjectContext::addUnknownProfilePropertyError(const Item *moduleProto,
+ const ErrorInfo &error)
+{
+ m_unknownProfilePropertyErrors.lock().get()[moduleProto].push_back(error);
+}
+
+const std::vector<ErrorInfo> &TopLevelProjectContext::unknownProfilePropertyErrors(
+ const Item *moduleProto) const
+{
+ const auto errorsGuard = m_unknownProfilePropertyErrors.lock_shared();
+ const auto &errors = errorsGuard.get();
+ if (const auto it = errors.find(moduleProto); it != errors.end()) {
+ return it->second;
+ }
+ static const std::vector<ErrorInfo> empty;
+ return empty;
+}
+
+Item *TopLevelProjectContext::getModulePrototype(const QString &filePath, const QString &profile,
+ const std::function<Item *()> &produce)
+{
+ const auto modulePrototypesGuard = m_modulePrototypes.lock();
+ auto &prototypeList = modulePrototypesGuard.get()[filePath];
+ for (const auto &prototype : prototypeList) {
+ if (prototype.second == profile)
+ return prototype.first;
+ }
+ Item * const module = produce();
+ if (module)
+ prototypeList.emplace_back(module, profile);
+ return module;
+}
+
+void TopLevelProjectContext::addLocalProfile(const QString &name, const QVariantMap &values,
+ const CodeLocation &location)
+{
+ if (m_localProfiles.contains(name))
+ throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(name), location);
+ m_localProfiles.insert(name, values);
+}
+
+void TopLevelProjectContext::checkForLocalProfileAsTopLevelProfile(const QString &topLevelProfile)
+{
+ for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) {
+ if (it.key() != topLevelProfile)
+ continue;
+
+ // This covers the edge case that a locally defined profile was specified as the
+ // top-level profile, in which case we must invalidate the qbs module prototype that was
+ // created in early setup before local profiles were handled.
+ const auto prototypesGuard = m_modulePrototypes.lock();
+ auto &modulePrototypes = prototypesGuard.get();
+ QBS_CHECK(modulePrototypes.size() == 1);
+ modulePrototypes.clear();
+ break;
+ }
+}
+
+std::lock_guard<std::mutex> TopLevelProjectContext::probesCacheLock()
+{
+ return std::lock_guard<std::mutex>(m_probesMutex);
+}
+
+void TopLevelProjectContext::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
+{
+ for (const ProbeConstPtr& probe : oldProbes)
+ m_probesInfo.oldProjectProbes[probe->globalId()] << probe;
+}
+
+ProbeConstPtr TopLevelProjectContext::findOldProjectProbe(const QString &id,
+ const ProbeFilter &filter) const
+{
+ for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProjectProbes.value(id)) {
+ if (filter(oldProbe))
+ return oldProbe;
+ }
+ return {};
+}
+
+void TopLevelProjectContext::setOldProductProbes(
+ const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
+{
+ m_probesInfo.oldProductProbes = oldProbes;
+}
+
+ProbeConstPtr TopLevelProjectContext::findOldProductProbe(const QString &productName,
+ const ProbeFilter &filter) const
{
- if (progressObserver && progressObserver->canceled()) {
- throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
- .arg(TopLevelProject::deriveId(
- parameters.finalBuildConfigurationTree())));
+ for (const ProbeConstPtr &oldProbe : m_probesInfo.oldProductProbes.value(productName)) {
+ if (filter(oldProbe))
+ return oldProbe;
}
+ return {};
+}
+
+void TopLevelProjectContext::addNewlyResolvedProbe(const ProbeConstPtr &probe)
+{
+ m_probesInfo.currentProbes[probe->location()] << probe;
+}
+
+ProbeConstPtr TopLevelProjectContext::findCurrentProbe(const CodeLocation &location,
+ const ProbeFilter &filter) const
+{
+ for (const ProbeConstPtr &probe : m_probesInfo.currentProbes.value(location)) {
+ if (filter(probe))
+ return probe;
+ }
+ return {};
+}
+
+void TopLevelProjectContext::collectDataFromEngine(const ScriptEngine &engine)
+{
+ const auto project = dynamic_cast<TopLevelProject *>(m_projects.front()->project.get());
+ QBS_CHECK(project);
+ project->canonicalFilePathResults.insert(engine.canonicalFilePathResults());
+ project->fileExistsResults.insert(engine.fileExistsResults());
+ project->directoryEntriesResults.insert(engine.directoryEntriesResults());
+ project->fileLastModifiedResults.insert(engine.fileLastModifiedResults());
+ project->environment.insert(engine.environment());
+ project->buildSystemFiles.unite(engine.imports());
+}
+
+ItemPool &TopLevelProjectContext::createItemPool()
+{
+ m_itemPools.push_back(std::make_unique<ItemPool>());
+ return *m_itemPools.back();
}
class LoaderState::Private
{
public:
- Private(LoaderState &q, const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : parameters(parameters), itemPool(itemPool), evaluator(evaluator), logger(logger),
- itemReader(q), probesResolver(q), propertyMerger(q), localProfiles(q),
- moduleInstantiator(q), dependenciesResolver(q),
- multiplexer(q, [this](Item *productItem) {
- return moduleInstantiator.retrieveQbsItem(productItem);
- })
- {}
+ Private(LoaderState &q, const SetupProjectParameters &parameters,
+ TopLevelProjectContext &topLevelProject, ItemPool &itemPool, ScriptEngine &engine,
+ Logger &&logger)
+ : parameters(parameters), topLevelProject(topLevelProject), itemPool(itemPool),
+ logger(std::move(logger)), itemReader(q), evaluator(&engine)
+ {
+ this->logger.clearWarnings();
+ this->logger.storeWarnings();
+ }
const SetupProjectParameters &parameters;
+ TopLevelProjectContext &topLevelProject;
ItemPool &itemPool;
- Evaluator &evaluator;
- Logger &logger;
- TopLevelProjectContext topLevelProject;
+ Logger logger;
ItemReader itemReader;
- ProbesResolver probesResolver;
- ModulePropertyMerger propertyMerger;
- LocalProfiles localProfiles;
- ModuleInstantiator moduleInstantiator;
- DependenciesResolver dependenciesResolver;
- ProductItemMultiplexer multiplexer;
+ Evaluator evaluator;
};
-LoaderState::LoaderState(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : d(makePimpl<Private>(*this, parameters, itemPool, evaluator, logger))
+LoaderState::LoaderState(const SetupProjectParameters &parameters,
+ TopLevelProjectContext &topLevelProject, ItemPool &itemPool,
+ ScriptEngine &engine, Logger logger)
+ : d(makePimpl<Private>(*this, parameters, topLevelProject, itemPool, engine, std::move(logger)))
{
d->itemReader.init();
}
LoaderState::~LoaderState() = default;
const SetupProjectParameters &LoaderState::parameters() const { return d->parameters; }
-DependenciesResolver &LoaderState::dependenciesResolver() { return d->dependenciesResolver; }
ItemPool &LoaderState::itemPool() { return d->itemPool; }
Evaluator &LoaderState::evaluator() { return d->evaluator; }
Logger &LoaderState::logger() { return d->logger; }
-ModuleInstantiator &LoaderState::moduleInstantiator() { return d->moduleInstantiator; }
-ProductItemMultiplexer &LoaderState::multiplexer() { return d->multiplexer; }
ItemReader &LoaderState::itemReader() { return d->itemReader; }
-LocalProfiles &LoaderState::localProfiles() { return d->localProfiles; }
-ProbesResolver &LoaderState::probesResolver() { return d->probesResolver; }
-ModulePropertyMerger &LoaderState::propertyMerger() { return d->propertyMerger; }
TopLevelProjectContext &LoaderState::topLevelProject() { return d->topLevelProject; }
+static QString verbatimValue(LoaderState &state, const ValueConstPtr &value)
+{
+ QString result;
+ if (value && value->type() == Value::JSSourceValueType) {
+ const JSSourceValueConstPtr sourceValue = std::static_pointer_cast<const JSSourceValue>(
+ value);
+ result = state.topLevelProject().sourceCodeForEvaluation(sourceValue);
+ }
+ return result;
+}
+
+static void resolveRuleArtifactBinding(
+ LoaderState &state, const RuleArtifactPtr &ruleArtifact, Item *item,
+ const QStringList &namePrefix, QualifiedIdSet *seenBindings)
+{
+ for (auto it = item->properties().constBegin(); it != item->properties().constEnd(); ++it) {
+ const QStringList name = QStringList(namePrefix) << it.key();
+ if (it.value()->type() == Value::ItemValueType) {
+ resolveRuleArtifactBinding(state, ruleArtifact,
+ std::static_pointer_cast<ItemValue>(it.value())->item(),
+ name, seenBindings);
+ } else if (it.value()->type() == Value::JSSourceValueType) {
+ const auto insertResult = seenBindings->insert(name);
+ if (!insertResult.second)
+ continue;
+ JSSourceValuePtr sourceValue = std::static_pointer_cast<JSSourceValue>(it.value());
+ RuleArtifact::Binding rab;
+ rab.name = name;
+ rab.code = state.topLevelProject().sourceCodeForEvaluation(sourceValue);
+ rab.location = sourceValue->location();
+ ruleArtifact->bindings.push_back(rab);
+ } else {
+ QBS_ASSERT(!"unexpected value type", continue);
+ }
+ }
+}
+
+static void resolveRuleArtifact(LoaderState &state, const RulePtr &rule, Item *item)
+{
+ RuleArtifactPtr artifact = RuleArtifact::create();
+ rule->artifacts.push_back(artifact);
+ artifact->location = item->location();
+
+ if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty()))
+ artifact->filePathLocation = sourceProperty->location();
+
+ artifact->filePath = verbatimValue(state, item->property(StringConstants::filePathProperty()));
+ artifact->fileTags = state.evaluator().fileTagsValue(item, StringConstants::fileTagsProperty());
+ artifact->alwaysUpdated = state.evaluator().boolValue(
+ item, StringConstants::alwaysUpdatedProperty());
+
+ QualifiedIdSet seenBindings;
+ for (Item *obj = item; obj; obj = obj->prototype()) {
+ for (QMap<QString, ValuePtr>::const_iterator it = obj->properties().constBegin();
+ it != obj->properties().constEnd(); ++it)
+ {
+ if (it.value()->type() != Value::ItemValueType)
+ continue;
+ resolveRuleArtifactBinding(
+ state, artifact, std::static_pointer_cast<ItemValue>(it.value())->item(),
+ QStringList(it.key()), &seenBindings);
+ }
+ }
+}
+
+void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext, ModuleContext *moduleContext)
+{
+ Evaluator &evaluator = state.evaluator();
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return;
+
+ RulePtr rule = Rule::create();
+
+ // read artifacts
+ bool hasArtifactChildren = false;
+ for (Item * const child : item->children()) {
+ if (Q_UNLIKELY(child->type() != ItemType::Artifact)) {
+ throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."),
+ child->location());
+ }
+ hasArtifactChildren = true;
+ resolveRuleArtifact(state, rule, child);
+ }
+
+ rule->name = evaluator.stringValue(item, StringConstants::nameProperty());
+ rule->prepareScript.initialize(state.topLevelProject().scriptFunctionValue(
+ item, StringConstants::prepareProperty()));
+ rule->outputArtifactsScript.initialize(state.topLevelProject().scriptFunctionValue(
+ item, StringConstants::outputArtifactsProperty()));
+ rule->outputFileTags = evaluator.fileTagsValue(item, StringConstants::outputFileTagsProperty());
+ if (rule->outputArtifactsScript.isValid()) {
+ if (hasArtifactChildren)
+ throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules "
+ "that contain Artifact items."),
+ item->location());
+ }
+ if (!hasArtifactChildren && rule->outputFileTags.empty()) {
+ throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty "
+ "outputFileTags property."), item->location());
+ }
+ rule->multiplex = evaluator.boolValue(item, StringConstants::multiplexProperty());
+ rule->alwaysRun = evaluator.boolValue(item, StringConstants::alwaysRunProperty());
+ rule->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
+ rule->inputsFromDependencies
+ = evaluator.fileTagsValue(item, StringConstants::inputsFromDependenciesProperty());
+ bool requiresInputsSet = false;
+ rule->requiresInputs = evaluator.boolValue(item, StringConstants::requiresInputsProperty(),
+ &requiresInputsSet);
+ if (!requiresInputsSet)
+ rule->requiresInputs = rule->declaresInputs();
+ rule->auxiliaryInputs
+ = evaluator.fileTagsValue(item, StringConstants::auxiliaryInputsProperty());
+ rule->excludedInputs
+ = evaluator.fileTagsValue(item, StringConstants::excludedInputsProperty());
+ if (rule->excludedInputs.empty()) {
+ rule->excludedInputs = evaluator.fileTagsValue(
+ item, StringConstants::excludedAuxiliaryInputsProperty());
+ }
+ rule->explicitlyDependsOn
+ = evaluator.fileTagsValue(item, StringConstants::explicitlyDependsOnProperty());
+ rule->explicitlyDependsOnFromDependencies = evaluator.fileTagsValue(
+ item, StringConstants::explicitlyDependsOnFromDependenciesProperty());
+ rule->module = moduleContext ? moduleContext->module : projectContext->dummyModule;
+ if (!rule->multiplex && !rule->declaresInputs()) {
+ throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."),
+ item->location());
+ }
+ if (!rule->multiplex && !rule->requiresInputs) {
+ throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."),
+ item->location());
+ }
+ if (!rule->declaresInputs() && rule->requiresInputs) {
+ throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule "
+ "does not declare any input tags."), item->location());
+ }
+ if (productContext) {
+ rule->product = productContext->product.get();
+ productContext->product->rules.push_back(rule);
+ } else {
+ projectContext->rules.push_back(rule);
+ }
+}
+
+void resolveFileTagger(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext)
+{
+ Evaluator &evaluator = state.evaluator();
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return;
+ std::vector<FileTaggerConstPtr> &fileTaggers = productContext
+ ? productContext->product->fileTaggers
+ : projectContext->fileTaggers;
+ const QStringList patterns = evaluator.stringListValue(item,
+ StringConstants::patternsProperty());
+ if (patterns.empty())
+ throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location());
+
+ const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
+ if (fileTags.empty())
+ throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location());
+
+ for (const QString &pattern : patterns) {
+ if (pattern.isEmpty())
+ throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location());
+ }
+
+ const int priority = evaluator.intValue(item, StringConstants::priorityProperty());
+ fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority));
+}
+
+void resolveJobLimit(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext, ModuleContext *moduleContext)
+{
+ Evaluator &evaluator = state.evaluator();
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return;
+ const QString jobPool = evaluator.stringValue(item, StringConstants::jobPoolProperty());
+ if (jobPool.isEmpty())
+ throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.")
+ .arg(StringConstants::jobPoolProperty()), item->location());
+ bool jobCountWasSet;
+ const int jobCount = evaluator.intValue(item, StringConstants::jobCountProperty(), -1,
+ &jobCountWasSet);
+ if (!jobCountWasSet) {
+ throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.")
+ .arg(StringConstants::jobCountProperty()), item->location());
+ }
+ if (jobCount < 0) {
+ throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.")
+ .arg(StringConstants::jobCountProperty()), item->location());
+ }
+ JobLimits &jobLimits = moduleContext
+ ? moduleContext->jobLimits
+ : productContext ? productContext->product->jobLimits
+ : projectContext->jobLimits;
+ JobLimit jobLimit(jobPool, jobCount);
+ const int oldLimit = jobLimits.getLimit(jobPool);
+ if (oldLimit == -1 || oldLimit > jobCount)
+ jobLimits.setJobLimit(jobLimit);
+}
+
+const FileTag unknownFileTag()
+{
+ static const FileTag tag("unknown-file-tag");
+ return tag;
+}
+
+bool ProductContext::dependenciesResolvingPending() const
+{
+ return (!dependenciesContext || !dependenciesContext->dependenciesResolved)
+ && !product && !delayedError.hasError();
+}
+
+std::pair<ProductDependency, ProductContext *> ProductContext::pendingDependency() const
+{
+ return dependenciesContext ? dependenciesContext->pendingDependency()
+ : std::make_pair(ProductDependency::None, nullptr);
+}
+
+TimingData &TimingData::operator+=(const TimingData &other)
+{
+ dependenciesResolving += other.dependenciesResolving;
+ moduleProviders += other.moduleProviders;
+ moduleInstantiation += other.moduleInstantiation;
+ propertyMerging += other.propertyMerging;
+ groupsSetup += other.groupsSetup;
+ groupsResolving += other.groupsResolving;
+ preparingProducts += other.preparingProducts;
+ resolvingProducts += other.resolvingProducts;
+ probes += other.probes;
+ propertyEvaluation += other.propertyEvaluation;
+ propertyChecking += other.propertyChecking;
+ return *this;
+}
+
+DependenciesContext::~DependenciesContext() = default;
+
+ItemReaderCache::AstCacheEntry &ItemReaderCache::retrieveOrSetupCacheEntry(
+ const QString &filePath, const std::function<void (AstCacheEntry &)> &setup)
+{
+ const auto astCacheGuard = m_astCache.lock();
+ AstCacheEntry &entry = astCacheGuard.get()[filePath];
+ if (!entry.ast) {
+ setup(entry);
+ m_filesRead << filePath;
+ }
+ return entry;
+}
+
+const QStringList &ItemReaderCache::retrieveOrSetDirectoryEntries(
+ const QString &dir, const std::function<QStringList ()> &findOnDisk)
+{
+ const auto directoryEntriesGuard = m_directoryEntries.lock();
+ auto &entries = directoryEntriesGuard.get()[dir];
+ if (!entries)
+ entries = findOnDisk();
+ return *entries;
+}
+
+bool ItemReaderCache::AstCacheEntry::addProcessingThread()
+{
+ return m_processingThreads.lock().get().insert(std::this_thread::get_id()).second;
+}
+
+void ItemReaderCache::AstCacheEntry::removeProcessingThread()
+{
+ m_processingThreads.lock().get().remove(std::this_thread::get_id());
+}
+
+class DependencyParametersMerger
+{
+public:
+ DependencyParametersMerger(std::vector<Item::Module::ParametersWithPriority> &&candidates)
+ : m_candidates(std::move(candidates)) { }
+ QVariantMap merge();
+
+private:
+ void merge(QVariantMap &current, const QVariantMap &next, int nextPrio);
+
+ const std::vector<Item::Module::ParametersWithPriority> m_candidates;
+
+ struct Conflict {
+ Conflict(QStringList path, QVariant val1, QVariant val2, int prio)
+ : path(std::move(path)), val1(std::move(val1)), val2(std::move(val2)), priority(prio) {}
+ QStringList path;
+ QVariant val1;
+ QVariant val2;
+ int priority;
+ };
+ std::vector<Conflict> m_conflicts;
+ QVariantMap m_currentValue;
+ int m_currentPrio = INT_MIN;
+ QStringList m_path;
+};
+
+QVariantMap mergeDependencyParameters(std::vector<Item::Module::ParametersWithPriority> &&candidates)
+{
+ return DependencyParametersMerger(std::move(candidates)).merge();
+}
+
+QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2)
+{
+ return mergeDependencyParameters({std::make_pair(m1, 0), std::make_pair(m2, 0)});
+}
+
+QVariantMap DependencyParametersMerger::merge()
+{
+ for (const auto &next : m_candidates) {
+ merge(m_currentValue, next.first, next.second);
+ m_currentPrio = next.second;
+ }
+
+ if (!m_conflicts.empty()) {
+ ErrorInfo error(Tr::tr("Conflicting parameter values encountered:"));
+ for (const Conflict &conflict : m_conflicts) {
+ // TODO: Location would be nice ...
+ error.append(Tr::tr(" Parameter '%1' cannot be both '%2' and '%3'.")
+ .arg(conflict.path.join(QLatin1Char('.')),
+ conflict.val1.toString(), conflict.val2.toString()));
+ }
+ throw error;
+ }
+
+ return m_currentValue;
+}
+
+void DependencyParametersMerger::merge(QVariantMap &current, const QVariantMap &next, int nextPrio)
+{
+ for (auto it = next.begin(); it != next.end(); ++it) {
+ m_path << it.key();
+ const QVariant &newValue = it.value();
+ QVariant &currentValue = current[it.key()];
+ if (newValue.userType() == QMetaType::QVariantMap) {
+ QVariantMap mdst = currentValue.toMap();
+ merge(mdst, it.value().toMap(), nextPrio);
+ currentValue = mdst;
+ } else {
+ if (m_currentPrio == nextPrio) {
+ if (currentValue.isValid() && !qVariantsEqual(currentValue, newValue))
+ m_conflicts.emplace_back(m_path, currentValue, newValue, m_currentPrio);
+ } else {
+ removeIf(m_conflicts, [this](const Conflict &conflict) {
+ return m_path == conflict.path;
+ });
+ }
+ currentValue = newValue;
+ }
+ m_path.removeLast();
+ }
+}
+
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h
index db05698a5..4a4aadfb8 100644
--- a/src/lib/corelib/loader/loaderutils.h
+++ b/src/lib/corelib/loader/loaderutils.h
@@ -41,46 +41,114 @@
#include <language/filetags.h>
#include <language/forward_decls.h>
+#include <language/item.h>
+#include <language/moduleproviderinfo.h>
+#include <language/propertydeclaration.h>
#include <language/qualifiedid.h>
+#include <parser/qmljsengine_p.h>
+#include <tools/codelocation.h>
+#include <tools/filetime.h>
+#include <tools/mutexdata.h>
+#include <tools/joblimits.h>
#include <tools/pimpl.h>
#include <tools/set.h>
#include <tools/version.h>
+#include <QHash>
#include <QStringList>
#include <QVariant>
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <shared_mutex>
+#include <thread>
+#include <utility>
#include <vector>
namespace qbs {
class SetupProjectParameters;
namespace Internal {
-class DependenciesResolver;
class Evaluator;
-class Item;
class ItemPool;
class ItemReader;
-class LocalProfiles;
class Logger;
-class ModuleInstantiator;
-class ModulePropertyMerger;
-class ProbesResolver;
class ProductContext;
-class ProductItemMultiplexer;
class ProgressObserver;
class ProjectContext;
+class ScriptEngine;
using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
-using ShadowProductInfo = std::pair<bool, QString>;
+using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
-enum class FallbackMode { Enabled, Disabled };
enum class Deferral { Allowed, NotAllowed };
+enum class ProductDependency { None, Single, Bulk };
-class ProductInfo
+class CancelException { };
+
+template<typename DataType, typename MutexType = std::shared_mutex>
+struct GuardedData {
+ DataType data;
+ mutable MutexType mutex;
+};
+
+class TimingData
{
public:
- std::vector<ProbeConstPtr> probes;
- ModulePropertiesPerGroup modulePropertiesSetInGroups;
- ErrorInfo delayedError;
+ TimingData &operator+=(const TimingData &other);
+
+ qint64 dependenciesResolving = 0;
+ qint64 moduleProviders = 0;
+ qint64 moduleInstantiation = 0;
+ qint64 propertyMerging = 0;
+ qint64 groupsSetup = 0;
+ qint64 groupsResolving = 0;
+ qint64 preparingProducts = 0;
+ qint64 resolvingProducts = 0;
+ qint64 schedulingProducts = 0;
+ qint64 probes = 0;
+ qint64 propertyEvaluation = 0;
+ qint64 propertyChecking = 0;
+};
+
+class ItemReaderCache
+{
+public:
+ class AstCacheEntry
+ {
+ public:
+ QString code;
+ QbsQmlJS::Engine engine;
+ QbsQmlJS::AST::UiProgram *ast = nullptr;
+
+ bool addProcessingThread();
+ void removeProcessingThread();
+
+ private:
+ MutexData<Set<std::thread::id>, std::recursive_mutex> m_processingThreads;
+ };
+
+ const Set<QString> &filesRead() const { return m_filesRead; }
+ AstCacheEntry &retrieveOrSetupCacheEntry(const QString &filePath,
+ const std::function<void(AstCacheEntry &)> &setup);
+ const QStringList &retrieveOrSetDirectoryEntries(
+ const QString &dir, const std::function<QStringList()> &findOnDisk);
+
+private:
+ Set<QString> m_filesRead;
+ MutexData<std::unordered_map<QString, std::optional<QStringList>>, std::mutex> m_directoryEntries; // TODO: Merge with module dir entries cache?
+ MutexData<std::unordered_map<QString, AstCacheEntry>, std::mutex> m_astCache;
+};
+
+class DependenciesContext
+{
+public:
+ virtual ~DependenciesContext();
+ virtual std::pair<ProductDependency, ProductContext *> pendingDependency() const = 0;
+
+ bool dependenciesResolved = false;
};
class ProductContext
@@ -89,21 +157,40 @@ public:
QString uniqueName() const;
QString displayName() const;
void handleError(const ErrorInfo &error);
+ bool dependenciesResolvingPending() const;
+ std::pair<ProductDependency, ProductContext *> pendingDependency() const;
QString name;
+ QString buildDirectory;
Item *item = nullptr;
Item *scope = nullptr;
ProjectContext *project = nullptr;
+ std::unique_ptr<ProductContext> shadowProduct;
Item *mergedExportItem = nullptr;
- ProductInfo info;
+ std::vector<ProbeConstPtr> probes;
+ ModulePropertiesPerGroup modulePropertiesSetInGroups;
+ ErrorInfo delayedError;
QString profileName;
QString multiplexConfigurationId;
QVariantMap profileModuleProperties; // Tree-ified module properties from profile.
QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values.
+ std::optional<QVariantMap> providerConfig;
+ std::optional<QVariantMap> providerQbsModule;
QVariantMap defaultParameters; // In Export item.
QStringList searchPaths;
+ ResolvedProductPtr product;
+ TimingData timingData;
+ std::unique_ptr<DependenciesContext> dependenciesContext;
- bool dependenciesResolved = false;
+ // This is needed because complex cyclic dependencies that involve Depends.productTypes
+ // may only be detected after a product has already been fully resolved.
+ std::vector<std::pair<FileTags, CodeLocation>> bulkDependencies;
+
+ // The keys are module prototypes, the values specify whether the module's
+ // condition is true for this product.
+ std::unordered_map<Item *, bool> modulePrototypeEnabledInfo;
+
+ int dependsItemCount = -1;
};
class TopLevelProjectContext
@@ -112,27 +199,206 @@ public:
TopLevelProjectContext() = default;
TopLevelProjectContext(const TopLevelProjectContext &) = delete;
TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
- ~TopLevelProjectContext() { qDeleteAll(projects); }
+ ~TopLevelProjectContext();
bool checkItemCondition(Item *item, Evaluator &evaluator);
- void checkCancelation(const SetupProjectParameters &parameters);
-
- std::vector<ProjectContext *> projects;
- std::list<std::pair<ProductContext *, int>> productsToHandle;
- std::multimap<QString, ProductContext *> productsByName;
- std::unordered_map<Item *, ProductInfo> productInfos;
- Set<QString> projectNamesUsedInOverrides;
- Set<QString> productNamesUsedInOverrides;
- Set<Item *> disabledItems;
- Set<QString> erroneousProducts;
- std::vector<ProbeConstPtr> probes;
- QString buildDirectory;
- QVariantMap profileConfigs;
- ProgressObserver *progressObserver = nullptr;
+ QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value);
+ ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name);
+ QString sourceCodeAsFunction(const JSSourceValueConstPtr &value,
+ const PropertyDeclaration &decl);
+
+ void setCanceled() { m_canceled = true; }
+ void checkCancelation();
+ bool isCanceled() const { return m_canceled; }
+
+ int productCount() const { return m_productsByName.size(); }
+
+ void addProductToHandle(const ProductContext &product) { m_productsToHandle.data << &product; }
+ void removeProductToHandle(const ProductContext &product);
+ bool isProductQueuedForHandling(const ProductContext &product) const;
+ int productsToHandleCount() const { return m_productsToHandle.data.size(); }
+
+ void addDisabledItem(Item *item);
+ bool isDisabledItem(const Item *item) const;
+
+ void setProgressObserver(ProgressObserver *observer);
+ ProgressObserver *progressObserver() const;
+
+ void addProject(ProjectContext *project) { m_projects.push_back(project); }
+ const std::vector<ProjectContext *> &projects() const { return m_projects; }
+
+ using QueuedErrors = MutexData<std::vector<ErrorInfo>, std::mutex>;
+ void addQueuedError(const ErrorInfo &error);
+ QueuedErrors::UniqueConstGuard queuedErrors() const { return m_queuedErrors.lock(); }
+
+ void setProfileConfigs(const QVariantMap &profileConfigs) { m_profileConfigs = profileConfigs; }
+ void addProfileConfig(const QString &profileName, const QVariantMap &profileConfig);
+ const QVariantMap &profileConfigs() const { return m_profileConfigs; }
+ std::optional<QVariantMap> profileConfig(const QString &profileName) const;
+
+ void addProduct(ProductContext &product);
+ void addProductByType(ProductContext &product, const FileTags &tags);
+ ProductContext *productWithNameAndConstraint(
+ const QString &name, const std::function<bool(ProductContext &)> &constraint);
+ std::vector<ProductContext *> productsWithNameAndConstraint(
+ const QString &name, const std::function<bool(ProductContext &)> &constraint);
+ std::vector<ProductContext *> productsWithTypeAndConstraint(
+ const FileTags &tags, const std::function<bool(ProductContext &)> &constraint);
+ std::vector<std::pair<ProductContext *, CodeLocation>>
+ finishedProductsWithBulkDependency(const FileTag &tag) const;
+ void registerBulkDependencies(ProductContext &product);
+
+ void addProjectNameUsedInOverrides(const QString &name);
+ const Set<QString> &projectNamesUsedInOverrides() const;
+
+ void addProductNameUsedInOverrides(const QString &name);
+ const Set<QString> &productNamesUsedInOverrides() const;
+
+ void setBuildDirectory(const QString &buildDir) { m_buildDirectory = buildDir; }
+ const QString &buildDirectory() const { return m_buildDirectory; }
+
+ void addMultiplexConfiguration(const QString &id, const QVariantMap &config);
+ QVariantMap multiplexConfiguration(const QString &id) const;
+
+ void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; }
+ const FileTime &lastResolveTime() const { return m_lastResolveTime; }
+
+ Set<QString> buildSystemFiles() const { return m_itemReaderCache.filesRead(); }
+
+ std::lock_guard<std::mutex> moduleProvidersCacheLock();
+ void setModuleProvidersCache(const ModuleProvidersCache &cache);
+ const ModuleProvidersCache &moduleProvidersCache() const { return m_moduleProvidersCache; }
+ ModuleProviderInfo *moduleProvider(const ModuleProvidersCacheKey &key);
+ ModuleProviderInfo &addModuleProvider(const ModuleProvidersCacheKey &key,
+ const ModuleProviderInfo &provider);
+
+ void addParameterDeclarations(const Item *moduleProto,
+ const Item::PropertyDeclarationMap &decls);
+ Item::PropertyDeclarationMap parameterDeclarations(Item *moduleProto) const;
+
+ void setParameters(const Item *moduleProto, const QVariantMap &parameters);
+ QVariantMap parameters(Item *moduleProto) const;
+
+ void addCodeLink(const QString &sourceFile, const CodeRange &sourceRange,
+ const CodeLocation &target);
+ CodeLinks codeLinks() const { return m_codeLinks.lock().get(); }
+
+ // An empty string means no matching module directory was found.
+ QString findModuleDirectory(const QualifiedId &module, const QString &searchPath,
+ const std::function<QString()> &findOnDisk);
+
+ QStringList getModuleFilesForDirectory(const QString &dir,
+ const std::function<QStringList()> &findOnDisk);
+ void removeModuleFileFromDirectoryCache(const QString &filePath);
+
+ void addUnknownProfilePropertyError(const Item *moduleProto, const ErrorInfo &error);
+ const std::vector<ErrorInfo> &unknownProfilePropertyErrors(const Item *moduleProto) const;
+
+ Item *getModulePrototype(const QString &filePath, const QString &profile,
+ const std::function<Item *()> &produce);
+
+ void addLocalProfile(const QString &name, const QVariantMap &values,
+ const CodeLocation &location);
+ const QVariantMap localProfiles() { return m_localProfiles; }
+ void checkForLocalProfileAsTopLevelProfile(const QString &topLevelProfile);
+
+ using ProbeFilter = std::function<bool(const ProbeConstPtr &)>;
+ std::lock_guard<std::mutex> probesCacheLock();
+ void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
+ void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
+ void addNewlyResolvedProbe(const ProbeConstPtr &probe);
+ void addProjectLevelProbe(const ProbeConstPtr &probe);
+ const std::vector<ProbeConstPtr> projectLevelProbes() const;
+ ProbeConstPtr findOldProjectProbe(const QString &id, const ProbeFilter &filter) const;
+ ProbeConstPtr findOldProductProbe(const QString &productName, const ProbeFilter &filter) const;
+ ProbeConstPtr findCurrentProbe(const CodeLocation &location, const ProbeFilter &filter) const;
+ void incrementProbesCount() { ++m_probesInfo.probesEncountered; }
+ void incrementReusedCurrentProbesCount() { ++m_probesInfo.probesCachedCurrent; }
+ void incrementReusedOldProbesCount() { ++m_probesInfo.probesCachedOld; }
+ void incrementRunProbesCount() { ++m_probesInfo.probesRun; }
+ int probesEncounteredCount() const { return m_probesInfo.probesEncountered; }
+ int probesRunCount() const { return m_probesInfo.probesRun; }
+ int reusedOldProbesCount() const { return m_probesInfo.probesCachedOld; }
+ int reusedCurrentProbesCount() const { return m_probesInfo.probesCachedCurrent; }
+
+ TimingData &timingData() { return m_timingData; }
+ ItemReaderCache &itemReaderCache() { return m_itemReaderCache; }
+
+ void incProductDeferrals() { ++m_productDeferrals; }
+ int productDeferrals() const { return m_productDeferrals; }
+
+ void collectDataFromEngine(const ScriptEngine &engine);
+
+ ItemPool &createItemPool();
+
+private:
+ const ResolvedFileContextPtr &resolvedFileContext(const FileContextConstPtr &ctx);
+
+ std::vector<ProjectContext *> m_projects;
+ GuardedData<Set<const ProductContext *>> m_productsToHandle;
+ std::multimap<QString, ProductContext *> m_productsByName;
+ MutexData<std::unordered_map<QStringView, QString>, std::mutex> m_sourceCode;
+ std::unordered_map<QString, QVariantMap> m_multiplexConfigsById;
+ MutexData<QHash<CodeLocation, ScriptFunctionPtr>, std::mutex> m_scriptFunctionMap;
+ MutexData<std::unordered_map<std::pair<QStringView, QStringList>, QString>,
+ std::mutex> m_scriptFunctions;
+ std::unordered_map<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap;
+ Set<QString> m_projectNamesUsedInOverrides;
+ Set<QString> m_productNamesUsedInOverrides;
+ MutexData<Set<const Item *>> m_disabledItems;
+ QueuedErrors m_queuedErrors;
+ QString m_buildDirectory;
+ QVariantMap m_profileConfigs;
+ ProgressObserver *m_progressObserver = nullptr;
+ TimingData m_timingData;
+ ModuleProvidersCache m_moduleProvidersCache;
+ std::mutex m_moduleProvidersCacheMutex;
+ QVariantMap m_localProfiles;
+ ItemReaderCache m_itemReaderCache;
+ QHash<FileTag, std::vector<std::pair<ProductContext *, CodeLocation>>> m_reverseBulkDependencies;
// For fast look-up when resolving Depends.productTypes.
// The contract is that it contains fully handled, error-free, enabled products.
- std::multimap<FileTag, ProductContext *> productsByType;
+ MutexData<std::multimap<FileTag, ProductContext *>> m_productsByType;
+
+ // The keys are module prototypes.
+ MutexData<std::unordered_map<const Item *,
+ Item::PropertyDeclarationMap>> m_parameterDeclarations;
+ MutexData<std::unordered_map<const Item *, QVariantMap>> m_parameters;
+ MutexData<std::unordered_map<const Item *,
+ std::vector<ErrorInfo>>> m_unknownProfilePropertyErrors;
+
+ // The keys are search path + module name, the values are directories.
+ MutexData<QHash<std::pair<QString, QualifiedId>, std::optional<QString>>,
+ std::mutex> m_modulePathCache;
+
+ // The keys are file paths, the values are module prototype items accompanied by a profile.
+ MutexData<std::unordered_map<QString, std::vector<std::pair<Item *, QString>>>,
+ std::mutex> m_modulePrototypes;
+
+ MutexData<std::map<QString, std::optional<QStringList>>,
+ std::mutex> m_moduleFilesPerDirectory;
+ MutexData<CodeLinks> m_codeLinks;
+
+ struct {
+ QHash<QString, std::vector<ProbeConstPtr>> oldProjectProbes;
+ QHash<QString, std::vector<ProbeConstPtr>> oldProductProbes;
+ QHash<CodeLocation, std::vector<ProbeConstPtr>> currentProbes;
+ std::vector<ProbeConstPtr> projectLevelProbes;
+
+ quint64 probesEncountered = 0;
+ quint64 probesRun = 0;
+ quint64 probesCachedCurrent = 0;
+ quint64 probesCachedOld = 0;
+ } m_probesInfo;
+ std::mutex m_probesMutex;
+
+ std::vector<std::unique_ptr<ItemPool>> m_itemPools;
+
+ FileTime m_lastResolveTime;
+
+ std::atomic_bool m_canceled = false;
+ int m_productDeferrals = 0;
};
class ProjectContext
@@ -142,28 +408,36 @@ public:
Item *item = nullptr;
Item *scope = nullptr;
TopLevelProjectContext *topLevelProject = nullptr;
+ ProjectContext *parent = nullptr;
+ std::vector<ProjectContext *> children;
std::vector<ProductContext> products;
std::vector<QStringList> searchPathsStack;
+ ResolvedProjectPtr project;
+ std::vector<FileTaggerConstPtr> fileTaggers;
+ std::vector<RulePtr> rules;
+ JobLimits jobLimits;
+ ResolvedModulePtr dummyModule;
+};
+
+class ModuleContext
+{
+public:
+ ResolvedModulePtr module;
+ JobLimits jobLimits;
};
class LoaderState
{
public:
- LoaderState(const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
- Logger &logger);
+ LoaderState(const SetupProjectParameters &parameters, TopLevelProjectContext &topLevelProject,
+ ItemPool &itemPool, ScriptEngine &engine, Logger logger);
~LoaderState();
- DependenciesResolver &dependenciesResolver();
Evaluator &evaluator();
ItemPool &itemPool();
ItemReader &itemReader();
- LocalProfiles &localProfiles();
Logger &logger();
- ModuleInstantiator &moduleInstantiator();
- ProductItemMultiplexer &multiplexer();
const SetupProjectParameters &parameters() const;
- ProbesResolver &probesResolver();
- ModulePropertyMerger &propertyMerger();
TopLevelProjectContext &topLevelProject();
private:
@@ -171,9 +445,20 @@ private:
Pimpl<Private> d;
};
-void mergeParameters(QVariantMap &dst, const QVariantMap &src);
-ShadowProductInfo getShadowProductInfo(const ProductContext &product);
+// List must be sorted by priority in ascending order.
+[[nodiscard]] QVariantMap mergeDependencyParameters(
+ std::vector<Item::Module::ParametersWithPriority> &&candidates);
+[[nodiscard]] QVariantMap mergeDependencyParameters(const QVariantMap &m1, const QVariantMap &m2);
+
+QString fullProductDisplayName(const QString &name, const QString &multiplexId);
void adjustParametersScopes(Item *item, Item *scope);
+void resolveRule(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext, ModuleContext *moduleContext);
+void resolveJobLimit(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext, ModuleContext *moduleContext);
+void resolveFileTagger(LoaderState &state, Item *item, ProjectContext *projectContext,
+ ProductContext *productContext);
+const FileTag unknownFileTag();
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/loader/localprofiles.cpp b/src/lib/corelib/loader/localprofiles.cpp
index b59204492..e72128fff 100644
--- a/src/lib/corelib/loader/localprofiles.cpp
+++ b/src/lib/corelib/loader/localprofiles.cpp
@@ -52,34 +52,26 @@
#include <tools/stringconstants.h>
namespace qbs::Internal {
-class LocalProfiles::Private
+class LocalProfiles
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ LocalProfiles(LoaderState &loaderState) : m_loaderState(loaderState) {}
+ void collectProfiles(Item *productOrProject, Item *projectScope);
+
+private:
void handleProfile(Item *profileItem);
void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem,
QVariantMap &values);
- void collectProfiles(Item *productOrProject, Item *projectScope);
-
- LoaderState &loaderState;
- QVariantMap profiles;
+ LoaderState &m_loaderState;
};
-LocalProfiles::LocalProfiles(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-LocalProfiles::~LocalProfiles() = default;
-
-void LocalProfiles::collectProfilesFromItems(Item *productOrProject, Item *projectScope)
+void collectProfilesFromItems(Item *productOrProject, Item *projectScope, LoaderState &loaderState)
{
- d->collectProfiles(productOrProject, projectScope);
+ LocalProfiles(loaderState).collectProfiles(productOrProject, projectScope);
}
-const QVariantMap &LocalProfiles::profiles() const
-{
- return d->profiles;
-}
-
-void LocalProfiles::Private::handleProfile(Item *profileItem)
+void LocalProfiles::handleProfile(Item *profileItem)
{
QVariantMap values;
evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
@@ -93,15 +85,11 @@ void LocalProfiles::Private::handleProfile(Item *profileItem)
throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
.arg(profileName), profileItem->location());
}
- if (profiles.contains(profileName)) {
- throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
- profileItem->location());
- }
- profiles.insert(profileName, values);
+ m_loaderState.topLevelProject().addLocalProfile(profileName, values, profileItem->location());
}
-void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
- Item *profileItem, QVariantMap &values)
+void LocalProfiles::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
+ Item *profileItem, QVariantMap &values)
{
const Item::PropertyMap &props = item->properties();
for (auto it = props.begin(); it != props.end(); ++it) {
@@ -119,16 +107,16 @@ void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix
case Value::JSSourceValueType:
if (item != profileItem)
item->setScope(profileItem);
- const ScopedJsValue sv(loaderState.evaluator().engine()->context(),
- loaderState.evaluator().value(item, it.key()));
+ const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(),
+ m_loaderState.evaluator().value(item, it.key()));
values.insert(name.join(QLatin1Char('.')),
- getJsVariant(loaderState.evaluator().engine()->context(), sv));
+ getJsVariant(m_loaderState.evaluator().engine()->context(), sv));
break;
}
}
}
-void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *projectScope)
+void LocalProfiles::collectProfiles(Item *productOrProject, Item *projectScope)
{
Item * scope = productOrProject->type() == ItemType::Project ? projectScope : nullptr;
for (auto it = productOrProject->children().begin();
@@ -137,7 +125,7 @@ void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *proje
if (childItem->type() == ItemType::Profile) {
if (!scope) {
const ItemValuePtr itemValue = ItemValue::create(productOrProject);
- scope = Item::create(productOrProject->pool(), ItemType::Scope);
+ scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
scope->setProperty(StringConstants::productVar(), itemValue);
scope->setFile(productOrProject->file());
scope->setScope(projectScope);
@@ -146,7 +134,7 @@ void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *proje
try {
handleProfile(childItem);
} catch (const ErrorInfo &e) {
- handlePropertyError(e, loaderState.parameters(), loaderState.logger());
+ handlePropertyError(e, m_loaderState.parameters(), m_loaderState.logger());
}
it = productOrProject->children().erase(it); // TODO: delete item and scope
} else {
diff --git a/src/lib/corelib/loader/localprofiles.h b/src/lib/corelib/loader/localprofiles.h
index e31c8c81b..7d9eee88e 100644
--- a/src/lib/corelib/loader/localprofiles.h
+++ b/src/lib/corelib/loader/localprofiles.h
@@ -39,27 +39,11 @@
#pragma once
-#include <tools/pimpl.h>
-#include <QVariantMap>
-
namespace qbs::Internal {
class Item;
class LoaderState;
-// This class evaluates all Profile items encountered in the project tree and holds the results.
-class LocalProfiles
-{
-public:
- LocalProfiles(LoaderState &loaderState);
- ~LocalProfiles();
-
- void collectProfilesFromItems(Item *productOrProject, Item *projectScope);
- const QVariantMap &profiles() const;
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+void collectProfilesFromItems(Item *productOrProject, Item *projectScope, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleinstantiator.cpp b/src/lib/corelib/loader/moduleinstantiator.cpp
index 73b676be4..1c0359217 100644
--- a/src/lib/corelib/loader/moduleinstantiator.cpp
+++ b/src/lib/corelib/loader/moduleinstantiator.cpp
@@ -55,39 +55,39 @@
#include <utility>
namespace qbs::Internal {
-class ModuleInstantiator::Private
+
+static std::pair<const Item *, Item *>
+getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName,
+ const QString &id, bool replace, LoaderState &loaderState);
+
+class ModuleInstantiator
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ ModuleInstantiator(const InstantiationContext &context, LoaderState &loaderState)
+ : context(context), loaderState(loaderState) {}
+
+ void instantiate();
- void overrideProperties(const Context &context);
- void setupScope(const Context &context);
- void exchangePlaceholderItem(Item *product, Item *loadingItem, const QString &loadingName,
- Item *moduleItemForItemValues, const QualifiedId &moduleName, const QString &id,
- bool isProductDependency, bool alreadyLoaded);
- std::pair<const Item *, Item *>
- getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName,
- const QString &id, bool replace);
+private:
+ void overrideProperties();
+ void setupScope();
+ void exchangePlaceholderItem(Item *loadingItem, Item *moduleItemForItemValues);
+ const InstantiationContext &context;
LoaderState &loaderState;
- qint64 elapsedTime = 0;
};
-ModuleInstantiator::ModuleInstantiator(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) {}
-ModuleInstantiator::~ModuleInstantiator() = default;
-
-void ModuleInstantiator::instantiate(const Context &context)
+void ModuleInstantiator::instantiate()
{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
+ AccumulatingTimer timer(loaderState.parameters().logElapsedTime()
+ ? &context.product.timingData.moduleInstantiation : nullptr);
// This part needs to be done only once per module and product, and only if the module
// was successfully loaded.
if (context.module && !context.alreadyLoaded) {
context.module->setType(ItemType::ModuleInstance);
- d->overrideProperties(context);
- d->setupScope(context);
+ overrideProperties();
+ setupScope();
}
// This strange-looking code deals with the fact that our syntax cannot properly handle
@@ -109,37 +109,42 @@ void ModuleInstantiator::instantiate(const Context &context)
// }
// It's debatable whether that's a good feature, but it has been working (accidentally?)
// for a long time, and removing it now would break a lot of existing projects.
- d->exchangePlaceholderItem(
- context.product, context.loadingItem, context.loadingName, moduleItemForItemValues,
- context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
-
- if (!context.alreadyLoaded && context.product && context.product != context.loadingItem) {
- d->exchangePlaceholderItem(
- context.product, context.product, context.productName, moduleItemForItemValues,
- context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
+ exchangePlaceholderItem(context.loadingItem, moduleItemForItemValues);
+
+ if (!context.alreadyLoaded && context.product.item
+ && context.product.item != context.loadingItem) {
+ exchangePlaceholderItem(context.product.item, moduleItemForItemValues);
}
}
-void ModuleInstantiator::Private::exchangePlaceholderItem(
- Item *product, Item *loadingItem, const QString &loadingName, Item *moduleItemForItemValues,
- const QualifiedId &moduleName, const QString &id, bool isProductModule, bool alreadyLoaded)
+void ModuleInstantiator::exchangePlaceholderItem(Item *loadingItem, Item *moduleItemForItemValues)
{
// If we have a module item, set an item value pointing to it as a property on the loading item.
// Evict a possibly existing placeholder item, and return it to us, so we can merge its values
// into the instance.
const auto &[oldItem, newItem] = getOrSetModuleInstanceItem(
- loadingItem, moduleItemForItemValues, moduleName, id, true);
+ loadingItem, moduleItemForItemValues, context.moduleName, context.id, true, loaderState);
// The new item always exists, even if we don't have a module item. In that case, the
// function created a placeholder item for us, which we then have to turn into a
// non-present module.
QBS_CHECK(newItem);
if (!moduleItemForItemValues) {
- createNonPresentModule(loaderState.itemPool(), moduleName.toString(),
+ createNonPresentModule(loaderState.itemPool(), context.moduleName.toString(),
QLatin1String("not found"), newItem);
return;
}
+ if (!moduleItemForItemValues->isPresentModule())
+ return;
+
+ // This will yield false negatives for the case where there is an invalid property attached
+ // for a module that is actually found by pkg-config via the fallback provider.
+ // However, this is extremely rare compared to the case where the presence of the fallback
+ // module simply indicates "not present".
+ if (moduleItemForItemValues->isFallbackModule())
+ return;
+
// If the old and the new items are the same, it means the existing item value already
// pointed to a module instance (rather than a placeholder).
// This can happen in two cases:
@@ -148,7 +153,7 @@ void ModuleInstantiator::Private::exchangePlaceholderItem(
// (see getOrSetModuleInstanceItem() below for details).
if (oldItem == newItem) {
QBS_CHECK(oldItem->type() == ItemType::ModuleInstance);
- QBS_CHECK(alreadyLoaded || isProductModule);
+ QBS_CHECK(context.alreadyLoaded || context.exportingProduct);
return;
}
@@ -172,22 +177,96 @@ void ModuleInstantiator::Private::exchangePlaceholderItem(
}
// Now merge the locally attached values into the actual module instance.
- loaderState.propertyMerger().mergeFromLocalInstance(product, loadingItem, loadingName,
- oldItem, moduleItemForItemValues);
+ mergeFromLocalInstance(context.product, loadingItem, context.loadingName, oldItem,
+ moduleItemForItemValues, loaderState);
// TODO: We'd like to delete the placeholder item here, because it's not
// being referenced anymore and there's a lot of them. However, this
// is not supported by ItemPool. Investigate the use of std::pmr.
}
-Item *ModuleInstantiator::retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name)
+Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name,
+ LoaderState &loaderState)
+{
+ return getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false, loaderState).second;
+}
+
+Item *retrieveQbsItem(Item *containerItem, LoaderState &loaderState)
+{
+ return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule(), loaderState);
+}
+
+void ModuleInstantiator::overrideProperties()
+{
+ // Users can override module properties on the command line with the
+ // modules.<module-name>.<property-name>:<value> syntax.
+ // For simplicity and backwards compatibility, qbs properties can also be given without
+ // the "modules." prefix, i.e. just qbs.<property-name>:<value>.
+ // In addition, users can override module properties just for certain products
+ // using the products.<product-name>.<module-name>.<property-name>:<value> syntax.
+ // Such product-specific overrides have higher precedence.
+ const QString fullName = context.moduleName.toString();
+ const QString generalOverrideKey = QStringLiteral("modules.") + fullName;
+ const QString perProductOverrideKey = StringConstants::productsOverridePrefix()
+ + context.product.name + QLatin1Char('.') + fullName;
+ const SetupProjectParameters &parameters = loaderState.parameters();
+ Logger &logger = loaderState.logger();
+ context.module->overrideProperties(parameters.overriddenValuesTree(), generalOverrideKey,
+ parameters, logger);
+ if (fullName == StringConstants::qbsModule()) {
+ context.module->overrideProperties(parameters.overriddenValuesTree(), fullName, parameters,
+ logger);
+ }
+ context.module->overrideProperties(parameters.overriddenValuesTree(), perProductOverrideKey,
+ parameters, logger);
+}
+
+void ModuleInstantiator::setupScope()
{
- return d->getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false).second;
+ Item * const scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
+ QBS_CHECK(context.module->file());
+ scope->setFile(context.module->file());
+ QBS_CHECK(context.product.project->scope);
+ context.product.project->scope->copyProperty(StringConstants::projectVar(), scope);
+ if (context.product.scope)
+ context.product.scope->copyProperty(StringConstants::productVar(), scope);
+ else
+ QBS_CHECK(context.moduleName.toString() == StringConstants::qbsModule()); // Dummy product.
+
+ if (!context.module->id().isEmpty())
+ scope->setProperty(context.module->id(), ItemValue::create(context.module));
+ for (Item * const child : context.module->children()) {
+ child->setScope(scope);
+ if (!child->id().isEmpty())
+ scope->setProperty(child->id(), ItemValue::create(child));
+ }
+ context.module->setScope(scope);
+
+ if (context.exportingProduct) {
+ QBS_CHECK(context.exportingProduct->type() == ItemType::Product);
+
+ const auto exportingProductItemValue = ItemValue::create(context.exportingProduct);
+ scope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue);
+
+ const auto importingProductItemValue = ItemValue::create(context.product.item);
+ scope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue);
+
+ // FIXME: This looks wrong. Introduce exportingProject variable?
+ scope->setProperty(StringConstants::projectVar(),
+ ItemValue::create(context.exportingProduct->parent()));
+
+ PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(),
+ PropertyDeclaration::String, QString(),
+ PropertyDeclaration::PropertyNotAvailableInConfig);
+ context.module->setPropertyDeclaration(pd.name(), pd);
+ context.module->setProperty(pd.name(), context.exportingProduct->property(
+ StringConstants::sourceDirectoryProperty()));
+ }
}
-Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
+void instantiateModule(const InstantiationContext &context, LoaderState &loaderState)
{
- return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule());
+ ModuleInstantiator(context, loaderState).instantiate();
}
// This important function deals with retrieving and setting (pseudo-)module instances from/on
@@ -220,9 +299,9 @@ Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
// Use case 4: Module propagation to the the Group level.
// In all cases, the first returned item is the existing one, and the second returned item
// is the new one. Depending on the use case, they might be null and might also be the same item.
-std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInstanceItem(
+std::pair<const Item *, Item *> getOrSetModuleInstanceItem(
Item *container, Item *moduleItem, const QualifiedId &moduleName, const QString &id,
- bool replace)
+ bool replace, LoaderState &loaderState)
{
Item *instance = container;
const QualifiedId itemValueName
@@ -248,9 +327,9 @@ std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInsta
Item *newItem = i < itemValueName.size() - 1
? Item::create(&loaderState.itemPool(), ItemType::ModulePrefix)
: moduleItem
- ? moduleItem
- : Item::create(&loaderState.itemPool(),
- ItemType::ModuleInstancePlaceholder);
+ ? moduleItem
+ : Item::create(&loaderState.itemPool(),
+ ItemType::ModuleInstancePlaceholder);
instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
instance = newItem;
}
@@ -258,82 +337,4 @@ std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInsta
return {nullptr, instance};
}
-void ModuleInstantiator::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Instantiating modules took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
-}
-
-void ModuleInstantiator::Private::overrideProperties(const ModuleInstantiator::Context &context)
-{
- // Users can override module properties on the command line with the
- // modules.<module-name>.<property-name>:<value> syntax.
- // For simplicity and backwards compatibility, qbs properties can also be given without
- // the "modules." prefix, i.e. just qbs.<property-name>:<value>.
- // In addition, users can override module properties just for certain products
- // using the products.<product-name>.<module-name>.<property-name>:<value> syntax.
- // Such product-specific overrides have higher precedence.
- const QString fullName = context.moduleName.toString();
- const QString generalOverrideKey = QStringLiteral("modules.") + fullName;
- const QString perProductOverrideKey = StringConstants::productsOverridePrefix()
- + context.productName + QLatin1Char('.') + fullName;
- const SetupProjectParameters &parameters = loaderState.parameters();
- Logger &logger = loaderState.logger();
- context.module->overrideProperties(parameters.overriddenValuesTree(), generalOverrideKey,
- parameters, logger);
- if (fullName == StringConstants::qbsModule()) {
- context.module->overrideProperties(parameters.overriddenValuesTree(), fullName, parameters,
- logger);
- }
- context.module->overrideProperties(parameters.overriddenValuesTree(), perProductOverrideKey,
- parameters, logger);
-}
-
-void ModuleInstantiator::Private::setupScope(const ModuleInstantiator::Context &context)
-{
- Item * const scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
- QBS_CHECK(context.module->file());
- scope->setFile(context.module->file());
- QBS_CHECK(context.projectScope);
- context.projectScope->copyProperty(StringConstants::projectVar(), scope);
- if (context.productScope)
- context.productScope->copyProperty(StringConstants::productVar(), scope);
- else
- QBS_CHECK(context.moduleName.toString() == StringConstants::qbsModule()); // Dummy product.
-
- if (!context.module->id().isEmpty())
- scope->setProperty(context.module->id(), ItemValue::create(context.module));
- for (Item * const child : context.module->children()) {
- child->setScope(scope);
- if (!child->id().isEmpty())
- scope->setProperty(child->id(), ItemValue::create(child));
- }
- context.module->setScope(scope);
-
- if (context.exportingProduct) {
- QBS_CHECK(context.exportingProduct->type() == ItemType::Product);
-
- const auto exportingProductItemValue = ItemValue::create(context.exportingProduct);
- scope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue);
-
- const auto importingProductItemValue = ItemValue::create(context.product);
- scope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue);
-
- // FIXME: This looks wrong. Introduce exportingProject variable?
- scope->setProperty(StringConstants::projectVar(),
- ItemValue::create(context.exportingProduct->parent()));
-
- PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(),
- PropertyDeclaration::String, QString(),
- PropertyDeclaration::PropertyNotAvailableInConfig);
- context.module->setPropertyDeclaration(pd.name(), pd);
- context.module->setProperty(pd.name(), context.exportingProduct->property(
- StringConstants::sourceDirectoryProperty()));
- }
-}
-
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleinstantiator.h b/src/lib/corelib/loader/moduleinstantiator.h
index 7be886869..5e9ad2f44 100644
--- a/src/lib/corelib/loader/moduleinstantiator.h
+++ b/src/lib/corelib/loader/moduleinstantiator.h
@@ -39,7 +39,6 @@
#pragma once
-#include <tools/pimpl.h>
#include <QtGlobal>
QT_BEGIN_NAMESPACE
@@ -49,48 +48,35 @@ QT_END_NAMESPACE
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
class QualifiedId;
-// This class is responsible for setting up a proper module instance from a bunch of items:
+class InstantiationContext {
+public:
+ ProductContext &product;
+ Item * const loadingItem;
+ const QString &loadingName;
+ Item * const module;
+ Item * const moduleWithSameName;
+ Item * const exportingProduct;
+ const QualifiedId &moduleName;
+ const QString &id;
+ const bool alreadyLoaded;
+};
+
+// This function is responsible for setting up a proper module instance from a bunch of items:
// - Set the item type to ItemType::ModuleInstance (from Module or Export).
// - Apply possible command-line overrides for module properties.
// - Replace a possible module instance placeholder in the loading item with the actual instance
// and merge their values employing the ModulePropertyMerger.
// - Setting up the module instance scope.
-// In addition, it also provides helper functions for retrieving/setting module instance items
-// for special purposes.
-class ModuleInstantiator
-{
-public:
- ModuleInstantiator(LoaderState &loaderState);
- ~ModuleInstantiator();
+void instantiateModule(const InstantiationContext &context, LoaderState &loaderState);
- struct Context {
- Item * const product;
- const QString &productName;
- Item * const loadingItem;
- const QString &loadingName;
- Item * const module;
- Item * const moduleWithSameName;
- Item * const exportingProduct;
- Item * const productScope;
- Item * const projectScope;
- const QualifiedId &moduleName;
- const QString &id;
- const bool alreadyLoaded;
- };
- void instantiate(const Context &context);
-
- // Note that these will also create the respective item value if it does not exist yet.
- Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name);
- Item *retrieveQbsItem(Item *containerItem);
-
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+// Helper functions for retrieving/setting module instance items for special purposes.
+// Note that these will also create the respective item value if it does not exist yet.
+Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name,
+ LoaderState &loaderState);
+Item *retrieveQbsItem(Item *containerItem, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp
index 98548278f..d26849655 100644
--- a/src/lib/corelib/loader/moduleloader.cpp
+++ b/src/lib/corelib/loader/moduleloader.cpp
@@ -42,13 +42,14 @@
#include "itemreader.h"
#include "loaderutils.h"
#include "moduleproviderloader.h"
-#include "productitemmultiplexer.h"
#include <api/languageinfo.h>
#include <language/evaluator.h>
+#include <language/scriptengine.h>
#include <language/value.h>
#include <logging/categories.h>
#include <logging/translator.h>
+#include <tools/codelocation.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
#include <tools/hostosinfo.h>
@@ -64,42 +65,37 @@
namespace qbs::Internal {
-class ModuleLoader::Private
+class ModuleLoader
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- std::pair<Item *, bool> loadModuleFile(const ProductContext &product,
- const QString &moduleName, const QString &filePath);
- std::pair<Item *, bool> getModulePrototype(const ModuleLoader::ProductContext &product,
- const QString &moduleName, const QString &filePath);
- bool evaluateModuleCondition(const ModuleLoader::ProductContext &product, Item *module,
- const QString &fullModuleName);
- void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const Item::Modules &modules);
-
- LoaderState &loaderState;
- ModuleProviderLoader providerLoader{loaderState};
-
- // The keys are file paths, the values are module prototype items accompanied by a profile.
- std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> modulePrototypes;
-
- // The keys are module prototypes and products, the values specify whether the module's
- // condition is true for that product.
- std::unordered_map<std::pair<Item *, const Item *>, bool> modulePrototypeEnabledInfo;
-
- std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors;
- std::unordered_map<const Item *, Item::PropertyDeclarationMap> parameterDeclarations;
- std::unordered_map<const Item *, std::optional<QVariantMap>> providerConfigsPerProduct;
- QHash<std::pair<QString, QualifiedId>, std::optional<QString>> existingModulePathCache;
- std::map<QString, QStringList> moduleDirListCache;
-
- qint64 elapsedTimeModuleProviders = 0;
-};
+ ModuleLoader(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
+ : m_loaderState(loaderState)
+ , m_product(product)
+ , m_dependsItemLocation(dependsItemLocation)
+ , m_moduleName(moduleName)
+ {}
-ModuleLoader::ModuleLoader(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) { }
+ Item *load();
-ModuleLoader::~ModuleLoader() = default;
+private:
+ std::pair<Item *, bool> loadModuleFile(const QString &moduleName, const QString &filePath);
+ Item *getModulePrototype(const QString &moduleName, const QString &filePath);
+ Item *createAndInitModuleItem(const QString &moduleName, const QString &filePath);
+ bool evaluateModuleCondition(Item *module, const QString &fullModuleName);
+ void checkForUnknownProfileProperties(const Item *module);
+ QString findModuleDirectory(const QString &searchPath);
+ QStringList findModuleDirectories();
+ QStringList getModuleFilePaths(const QString &dir);
+
+ LoaderState &m_loaderState;
+ ProductContext &m_product;
+ const CodeLocation &m_dependsItemLocation;
+ const QualifiedId &m_moduleName;
+};
struct PrioritizedItem
{
@@ -148,180 +144,129 @@ static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidate
return maxIt->item;
}
-ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile(
- const ProductContext &productContext, const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName, FallbackMode fallbackMode, bool isRequired)
+Item *searchAndLoadModuleFile(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
{
- const auto findExistingModulePath = [&](const QString &searchPath) {
- // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the
- // modules and search paths we've already processed
- auto &moduleInfo = d->existingModulePathCache[{searchPath, moduleName}];
- if (moduleInfo)
- return *moduleInfo;
-
- QString dirPath = searchPath + QStringLiteral("/modules");
- for (const QString &moduleNamePart : moduleName) {
- dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
- if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) {
- return *(moduleInfo = QString());
- }
- }
-
- return *(moduleInfo = dirPath);
- };
- const auto findExistingModulePaths = [&] {
- const QStringList &searchPaths = d->loaderState.itemReader().allSearchPaths();
- QStringList result;
- result.reserve(searchPaths.size());
- for (const auto &path: searchPaths) {
- const QString dirPath = findExistingModulePath(path);
- if (!dirPath.isEmpty())
- result.append(dirPath);
- }
- return result;
- };
+ return ModuleLoader(loaderState, product, dependsItemLocation, moduleName).load();
+}
- SearchPathsManager searchPathsManager(d->loaderState.itemReader());
+Item *ModuleLoader::load()
+{
+ SearchPathsManager searchPathsManager(m_loaderState.itemReader());
- Result loadResult;
- auto existingPaths = findExistingModulePaths();
+ QStringList existingPaths = findModuleDirectories();
if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
- AccumulatingTimer providersTimer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTimeModuleProviders : nullptr);
- std::optional<QVariantMap> &providerConfig
- = d->providerConfigsPerProduct[productContext.productItem];
- auto result = d->providerLoader.executeModuleProviders(
- {productContext.productItem, productContext.projectItem, productContext.name,
- productContext.uniqueName, productContext.moduleProperties, providerConfig},
- dependsItemLocation,
- moduleName,
- fallbackMode);
- loadResult.providerProbes << result.probes;
- if (!providerConfig)
- providerConfig = result.providerConfig;
+ AccumulatingTimer providersTimer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.moduleProviders : nullptr);
+ auto result = ModuleProviderLoader(m_loaderState)
+ .executeModuleProviders(m_product, m_dependsItemLocation, m_moduleName);
if (result.searchPaths) {
- qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
+ qCDebug(lcModuleLoader) << "Re-checking for module" << m_moduleName.toString()
<< "with newly added search paths from module provider";
- d->loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths);
- existingPaths = findExistingModulePaths();
+ m_loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths);
+ existingPaths = findModuleDirectories();
}
}
- const auto getModuleFileNames = [&](const QString &dirPath) -> QStringList & {
- QStringList &moduleFileNames = d->moduleDirListCache[dirPath];
- if (moduleFileNames.empty()) {
- QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
- while (dirIter.hasNext())
- moduleFileNames += dirIter.next();
- }
- return moduleFileNames;
- };
-
- const QString fullName = moduleName.toString();
+ const QString fullName = m_moduleName.toString();
bool triedToLoadModule = false;
std::vector<PrioritizedItem> candidates;
candidates.reserve(size_t(existingPaths.size()));
for (int i = 0; i < existingPaths.size(); ++i) {
- const QString &dirPath = existingPaths.at(i);
- QStringList &moduleFileNames = getModuleFileNames(dirPath);
- for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) {
- const QString &filePath = *it;
- const auto [module, triedToLoad] = d->loadModuleFile(productContext, fullName,
- filePath);
+ const QStringList &moduleFileNames = getModuleFilePaths(existingPaths.at(i));
+ for (const QString &filePath : moduleFileNames) {
+ const auto [module, triedToLoad] = loadModuleFile(fullName, filePath);
if (module)
candidates.emplace_back(module, 0, i);
if (!triedToLoad)
- it = moduleFileNames.erase(it);
- else
- ++it;
+ m_loaderState.topLevelProject().removeModuleFileFromDirectoryCache(filePath);
triedToLoadModule = triedToLoadModule || triedToLoad;
}
}
- if (candidates.empty()) {
- if (!isRequired) {
- loadResult.moduleItem = createNonPresentModule(
- *productContext.projectItem->pool(), fullName, QStringLiteral("not found"),
- nullptr);
- return loadResult;
- }
- if (Q_UNLIKELY(triedToLoadModule)) {
- throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName),
- dependsItemLocation);
- }
- return loadResult;
- }
+ if (candidates.empty())
+ return nullptr;
+ Item *moduleItem = nullptr;
if (candidates.size() == 1) {
- loadResult.moduleItem = candidates.at(0).item;
+ moduleItem = candidates.at(0).item;
} else {
for (auto &candidate : candidates) {
- candidate.priority = d->loaderState.evaluator()
+ ModuleItemLocker lock(*candidate.item);
+ candidate.priority = m_loaderState.evaluator()
.intValue(candidate.item, StringConstants::priorityProperty(),
candidate.priority);
}
- loadResult.moduleItem = chooseModuleCandidate(candidates, fullName);
- }
-
- const QString fullProductName = ProductItemMultiplexer::fullProductDisplayName(
- productContext.name, productContext.multiplexId);
- const auto it = d->unknownProfilePropertyErrors.find(loadResult.moduleItem);
- if (it != d->unknownProfilePropertyErrors.cend()) {
- ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
- "in profile '%3':")
- .arg(moduleName.toString(), fullProductName, productContext.profile));
- for (const ErrorInfo &e : it->second)
- error.append(e.toString());
- handlePropertyError(error, d->loaderState.parameters(), d->loaderState.logger());
+ moduleItem = chooseModuleCandidate(candidates, fullName);
}
- return loadResult;
-}
-
-void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->providerLoader.setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-StoredModuleProviderInfo ModuleLoader::storedModuleProviderInfo() const
-{
- return d->providerLoader.storedModuleProviderInfo();
-}
-
-const Set<QString> &ModuleLoader::tempQbsFiles() const
-{
- return d->providerLoader.tempQbsFiles();
+ checkForUnknownProfileProperties(moduleItem);
+ return moduleItem;
}
-std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
- const ProductContext &product, const QString &moduleName, const QString &filePath)
+std::pair<Item *, bool> ModuleLoader::loadModuleFile(const QString &moduleName,
+ const QString &filePath)
{
qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath;
- const auto [module, triedToLoad] = getModulePrototype(product, moduleName, filePath);
+ Item * const module = getModulePrototype(moduleName, filePath);
if (!module)
- return {nullptr, triedToLoad};
+ return {nullptr, false};
- const auto key = std::make_pair(module, product.productItem);
- const auto it = modulePrototypeEnabledInfo.find(key);
- if (it != modulePrototypeEnabledInfo.end()) {
+ const auto it = m_product.modulePrototypeEnabledInfo.find(module);
+ if (it != m_product.modulePrototypeEnabledInfo.end()) {
qCDebug(lcModuleLoader) << "prototype cache hit (level 2)";
- return {it->second ? module : nullptr, triedToLoad};
+ return {it->second ? module : nullptr, true};
}
- if (!evaluateModuleCondition(product, module, moduleName)) {
+ if (!evaluateModuleCondition(module, moduleName)) {
qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false";
- modulePrototypeEnabledInfo.insert({key, false});
- return {nullptr, triedToLoad};
+ m_product.modulePrototypeEnabledInfo.insert({module, false});
+ return {nullptr, true};
+ }
+
+ m_product.modulePrototypeEnabledInfo.insert({module, true});
+ return {module, true};
+}
+
+Item * ModuleLoader::getModulePrototype(const QString &moduleName, const QString &filePath)
+{
+ bool fromCache = true;
+ Item * const module = m_loaderState.topLevelProject().getModulePrototype(
+ filePath, m_product.profileName, [&] {
+ fromCache = false;
+ return createAndInitModuleItem(moduleName, filePath);
+ });
+
+ if (fromCache)
+ qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
+ return module;
+}
+
+Item *ModuleLoader::createAndInitModuleItem(const QString &moduleName, const QString &filePath)
+{
+ Item * const module = m_loaderState.itemReader().setupItemFromFile(filePath, {});
+ if (module->type() != ItemType::Module) {
+ qCDebug(lcModuleLoader).nospace()
+ << "Alleged module " << moduleName << " has type '"
+ << module->typeName() << "', so it's not a module after all.";
+ return nullptr;
}
+ // Not technically needed, but we want to keep the invariant in item.cpp.
+ ModuleItemLocker locker(*module);
+
+ module->setProperty(StringConstants::nameProperty(), VariantValue::create(moduleName));
if (moduleName == StringConstants::qbsModule()) {
module->setProperty(QStringLiteral("hostPlatform"),
VariantValue::create(HostOsInfo::hostOSIdentifier()));
module->setProperty(QStringLiteral("hostArchitecture"),
VariantValue::create(HostOsInfo::hostOSArchitecture()));
module->setProperty(QStringLiteral("libexecPath"),
- VariantValue::create(loaderState.parameters().libexecPath()));
+ VariantValue::create(m_loaderState.parameters().libexecPath()));
const Version qbsVersion = LanguageInfo::qbsVersion();
module->setProperty(QStringLiteral("versionMajor"),
@@ -334,47 +279,29 @@ std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
Item::PropertyDeclarationMap decls;
const auto &moduleChildren = module->children();
for (Item *param : moduleChildren) {
- if (param->type() != ItemType::Parameter)
- continue;
- const auto &paramDecls = param->propertyDeclarations();
- for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
- decls.insert(it.key(), it.value());
- }
- parameterDeclarations.insert({module, decls});
- }
-
- modulePrototypeEnabledInfo.insert({key, true});
- return {module, triedToLoad};
-}
-
-std::pair<Item *, bool> ModuleLoader::Private::getModulePrototype(
- const ProductContext &product, const QString &moduleName, const QString &filePath)
-{
- auto &prototypeList = modulePrototypes[filePath];
- for (const auto &prototype : prototypeList) {
- if (prototype.second == product.profile) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
- return {prototype.first, true};
+ if (param->type() == ItemType::Parameter) {
+ const auto &paramDecls = param->propertyDeclarations();
+ for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
+ decls.insert(it.key(), it.value());
+ } else if (param->type() == ItemType::Parameters) {
+ adjustParametersScopes(param, param);
+ Evaluator &evaluator = m_loaderState.evaluator();
+ QVariantMap parameters = getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(param)).toMap();
+ m_loaderState.topLevelProject().setParameters(module, parameters);
+ }
}
+ m_loaderState.topLevelProject().addParameterDeclarations(module, decls);
}
- Item * const module = loaderState.itemReader().setupItemFromFile(filePath, CodeLocation());
- if (module->type() != ItemType::Module) {
- qCDebug(lcModuleLoader).nospace()
- << "Alleged module " << moduleName << " has type '"
- << module->typeName() << "', so it's not a module after all.";
- return {nullptr, false};
- }
- prototypeList.emplace_back(module, product.profile);
-
// Module properties that are defined in the profile are used as default values.
// This is the reason we need to have different items per profile.
const QVariantMap profileModuleProperties
- = product.profileModuleProperties.value(moduleName).toMap();
+ = m_product.profileModuleProperties.value(moduleName).toMap();
for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) {
if (Q_UNLIKELY(!module->hasProperty(it.key()))) {
- unknownProfilePropertyErrors[module].emplace_back(Tr::tr("Unknown property: %1.%2")
- .arg(moduleName, it.key()));
+ m_loaderState.topLevelProject().addUnknownProfilePropertyError(
+ module, {Tr::tr("Unknown property: %1.%2").arg(moduleName, it.key())});
continue;
}
const PropertyDeclaration decl = module->propertyDeclaration(it.key());
@@ -385,14 +312,12 @@ std::pair<Item *, bool> ModuleLoader::Private::getModulePrototype(
module->setProperty(it.key(), v);
}
- return {module, true};
+ return module;
}
-bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product,
- Item *module, const QString &fullModuleName)
+bool ModuleLoader::evaluateModuleCondition(Item *module, const QString &fullModuleName)
{
- // Evaluator reqires module name to be set.
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
+ ModuleItemLocker locker(*module);
// Temporarily make the product's qbs module instance available, so the condition
// can use qbs.targetOS etc.
@@ -405,7 +330,7 @@ bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &produc
if (m_needsQbsItem) {
m_prevQbsItemValue = module->property(StringConstants::qbsModule());
module->setProperty(StringConstants::qbsModule(),
- product.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
}
}
~TempQbsModuleProvider()
@@ -423,114 +348,63 @@ bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &produc
const bool m_needsQbsItem;
};
- const TempQbsModuleProvider tempQbs(product, module, fullModuleName);
- return loaderState.evaluator().boolValue(module, StringConstants::conditionProperty());
+ const TempQbsModuleProvider tempQbs(m_product, module, fullModuleName);
+ return m_loaderState.evaluator().boolValue(module, StringConstants::conditionProperty());
}
-class DependencyParameterDeclarationCheck
+void ModuleLoader::checkForUnknownProfileProperties(const Item *module)
{
-public:
- DependencyParameterDeclarationCheck(
- const QString &productName, const Item *productItem,
- const std::unordered_map<const Item *, Item::PropertyDeclarationMap> &decls)
- : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls)
- {}
-
- void operator()(const QVariantMap &parameters) const { check(parameters, QualifiedId()); }
-
-private:
- void check(const QVariantMap &parameters, const QualifiedId &moduleName) const
- {
- for (auto it = parameters.begin(); it != parameters.end(); ++it) {
- if (it.value().userType() == QMetaType::QVariantMap) {
- check(it.value().toMap(), QualifiedId(moduleName) << it.key());
- } else {
- const auto &deps = m_productItem->modules();
- auto m = std::find_if(deps.begin(), deps.end(),
- [&moduleName] (const Item::Module &module) {
- return module.name == moduleName;
- });
-
- if (m == deps.end()) {
- const QualifiedId fullName = QualifiedId(moduleName) << it.key();
- throw ErrorInfo(Tr::tr("Cannot set parameter '%1', "
- "because '%2' does not have a dependency on '%3'.")
- .arg(fullName.toString(), m_productName, moduleName.toString()),
- m_productItem->location());
- }
-
- const auto decls = m_parameterDeclarations.find(m->item->rootPrototype());
- if (decls == m_parameterDeclarations.end() || !decls->second.contains(it.key())) {
- const QualifiedId fullName = QualifiedId(moduleName) << it.key();
- throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.")
- .arg(fullName.toString()), m_productItem->location());
- }
- }
- }
- }
-
- bool moduleExists(const QualifiedId &name) const
- {
- const auto &deps = m_productItem->modules();
- return any_of(deps, [&name](const Item::Module &module) {
- return module.name == name;
- });
- }
-
- const QString &m_productName;
- const Item * const m_productItem;
- const std::unordered_map<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations;
-};
+ const std::vector<ErrorInfo> &errors
+ = m_loaderState.topLevelProject().unknownProfilePropertyErrors(module);
+ if (errors.empty())
+ return;
-void ModuleLoader::checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const
-{
- DependencyParameterDeclarationCheck dpdc(productName, productItem, d->parameterDeclarations);
- for (const Item::Module &dep : productItem->modules()) {
- if (!dep.parameters.empty())
- dpdc(dep.parameters);
- }
+ ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
+ "in profile '%3':")
+ .arg(m_moduleName.toString(), m_product.displayName(), m_product.profileName));
+ for (const ErrorInfo &e : errors)
+ error.append(e.toString());
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
}
-void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem,
- const Item::Modules &modules)
+QString ModuleLoader::findModuleDirectory(const QString &searchPath)
{
- for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- d->forwardParameterDeclarations(it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
- }
+ // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the
+ // modules and search paths we've already processed
+ return m_loaderState.topLevelProject().findModuleDirectory(m_moduleName, searchPath,
+ [&] {
+ QString dirPath = searchPath + QStringLiteral("/modules");
+ for (const QString &moduleNamePart : m_moduleName) {
+ dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
+ if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath))
+ return QString();
+ }
+ return dirPath;
+ });
}
-void ModuleLoader::Private::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const Item::Modules &modules)
+QStringList ModuleLoader::findModuleDirectories()
{
- auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) {
- return m.name == moduleName;
- });
- if (it != modules.end()) {
- item->setPropertyDeclarations(parameterDeclarations[it->item->rootPrototype()]);
- } else {
- for (auto it = item->properties().begin(); it != item->properties().end(); ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- forwardParameterDeclarations(QualifiedId(moduleName) << it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
- }
+ const QStringList &searchPaths = m_loaderState.itemReader().allSearchPaths();
+ QStringList result;
+ result.reserve(searchPaths.size());
+ for (const auto &path: searchPaths) {
+ const QString dirPath = findModuleDirectory(path);
+ if (!dirPath.isEmpty())
+ result.append(dirPath);
}
+ return result;
}
-void ModuleLoader::printProfilingInfo(int indent)
+QStringList ModuleLoader::getModuleFilePaths(const QString &dir)
{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Running module providers took %1.")
- .arg(elapsedTimeString(d->elapsedTimeModuleProviders));
+ return m_loaderState.topLevelProject().getModuleFilesForDirectory(dir, [&] {
+ QStringList moduleFiles;
+ QDirIterator dirIter(dir, StringConstants::qbsFileWildcards());
+ while (dirIter.hasNext())
+ moduleFiles += dirIter.next();
+ return moduleFiles;
+ });
}
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleloader.h b/src/lib/corelib/loader/moduleloader.h
index af5a8c0e9..b07f01836 100644
--- a/src/lib/corelib/loader/moduleloader.h
+++ b/src/lib/corelib/loader/moduleloader.h
@@ -39,62 +39,19 @@
#pragma once
-
-#include <language/forward_decls.h>
-#include <language/item.h>
-#include <tools/pimpl.h>
-#include <tools/set.h>
-
-#include <QString>
-#include <QVariantMap>
-
-#include <vector>
-
namespace qbs {
class CodeLocation;
namespace Internal {
-enum class FallbackMode;
+class Item;
class LoaderState;
-class StoredModuleProviderInfo;
-
-class ModuleLoader
-{
-public:
- ModuleLoader(LoaderState &loaderState);
- ~ModuleLoader();
-
- struct ProductContext {
- Item * const productItem;
- const Item * const projectItem;
- const QString &name;
- const QString &uniqueName;
- const QString &profile;
- const QString &multiplexId;
- const QVariantMap &moduleProperties;
- const QVariantMap &profileModuleProperties;
- };
- struct Result {
- Item *moduleItem = nullptr;
- std::vector<ProbeConstPtr> providerProbes;
- };
- Result searchAndLoadModuleFile(const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired);
-
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- StoredModuleProviderInfo storedModuleProviderInfo() const;
- const Set<QString> &tempQbsFiles() const;
-
- void checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const;
- void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules);
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+class ProductContext;
+class QualifiedId;
+
+Item *searchAndLoadModuleFile(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName);
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/loader/modulepropertymerger.cpp b/src/lib/corelib/loader/modulepropertymerger.cpp
index 5ac89d776..c9dd56ccf 100644
--- a/src/lib/corelib/loader/modulepropertymerger.cpp
+++ b/src/lib/corelib/loader/modulepropertymerger.cpp
@@ -49,89 +49,101 @@
#include <tools/set.h>
#include <tools/setupprojectparameters.h>
+#include <unordered_set>
+
namespace qbs::Internal {
-class ModulePropertyMerger::Private
+class ModulePropertyMerger
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- int compareValuePriorities(const Item *productItem, const ValueConstPtr &v1,
- const ValueConstPtr &v2);
- ValuePtr mergeListValues(const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem);
- void mergePropertyFromLocalInstance(const Item *productItem, Item *loadingItem,
- const QString &loadingName, Item *globalInstance,
- const QString &name, const ValuePtr &value);
- bool doFinalMerge(const Item *productItem, Item *moduleItem);
- bool doFinalMerge(const Item *productItem, const PropertyDeclaration &propertyDecl,
- ValuePtr &propertyValue);
-
- LoaderState &loaderState;
- qint64 elapsedTime = 0;
+ ModulePropertyMerger(ProductContext &product, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState) {}
+
+ void mergeFromLocalInstance(Item *loadingItem, const QString &loadingName,
+ const Item *localInstance, Item *globalInstance);
+ void doFinalMerge();
+
+private:
+ int compareValuePriorities(const ValueConstPtr &v1, const ValueConstPtr &v2);
+ ValuePtr mergeListValues(const ValuePtr &currentHead, const ValuePtr &newElem);
+ void mergePropertyFromLocalInstance(Item *loadingItem, const QString &loadingName,
+ Item *globalInstance, const QString &name,
+ const ValuePtr &value);
+ bool doFinalMerge(Item *moduleItem);
+ bool doFinalMerge(const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue);
+
+ ProductContext & m_product;
+ LoaderState &m_loaderState;
};
-void ModulePropertyMerger::mergeFromLocalInstance(
- const Item *productItem, Item *loadingItem, const QString &loadingName,
- const Item *localInstance, Item *globalInstance)
+void mergeFromLocalInstance(ProductContext &product, Item *loadingItem, const QString &loadingName,
+ const Item *localInstance, Item *globalInstance,
+ LoaderState &loaderState)
+{
+ ModulePropertyMerger(product, loaderState).mergeFromLocalInstance(
+ loadingItem, loadingName, localInstance, globalInstance);
+}
+
+void doFinalMerge(ProductContext &product, LoaderState &loaderState)
+{
+ ModulePropertyMerger(product, loaderState).doFinalMerge();
+}
+
+void ModulePropertyMerger::mergeFromLocalInstance(Item *loadingItem, const QString &loadingName,
+ const Item *localInstance, Item *globalInstance)
{
- AccumulatingTimer t(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+ AccumulatingTimer t(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyMerging : nullptr);
for (auto it = localInstance->properties().constBegin();
it != localInstance->properties().constEnd(); ++it) {
- d->mergePropertyFromLocalInstance(productItem, loadingItem, loadingName,
- globalInstance, it.key(), it.value());
+ mergePropertyFromLocalInstance(loadingItem, loadingName, globalInstance, it.key(),
+ it.value());
}
}
-void ModulePropertyMerger::doFinalMerge(const Item *productItem)
+void ModulePropertyMerger::doFinalMerge()
{
- AccumulatingTimer t(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+ AccumulatingTimer t(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyMerging : nullptr);
- Set<const Item *> itemsToInvalidate;
- for (const Item::Module &module : productItem->modules()) {
- if (d->doFinalMerge(productItem, module.item))
- itemsToInvalidate << module.item;
+ std::unordered_set<const Item *> itemsToInvalidate;
+ for (const Item::Module &module : m_product.item->modules()) {
+ if (doFinalMerge(module.item))
+ itemsToInvalidate.insert(module.item);
}
- const auto collectDependentItems = [&itemsToInvalidate](const Item *item,
- const auto &collect) -> bool {
- const bool alreadyInSet = itemsToInvalidate.contains(item);
+
+ // For each module item, if it requires invalidation, we also add modules that depend on
+ // that module item, all the way up to the Product item.
+ std::unordered_set<const Item *> visitedItems;
+ const auto collectDependentItems =
+ [&itemsToInvalidate, &visitedItems](const Item *item, const auto &collect) -> bool
+ {
+ const bool alreadyInSet = itemsToInvalidate.count(item);
+ if (!visitedItems.insert(item).second) // item handled already
+ return alreadyInSet;
bool addItem = false;
for (const Item::Module &m : item->modules()) {
if (collect(m.item, collect))
addItem = true;
}
if (addItem && !alreadyInSet)
- itemsToInvalidate << item;
+ itemsToInvalidate.insert(item);
return addItem || alreadyInSet;
};
- collectDependentItems(productItem, collectDependentItems);
+ collectDependentItems(m_product.item, collectDependentItems);
for (const Item * const item : itemsToInvalidate)
- d->loaderState.evaluator().clearCache(item);
+ m_loaderState.evaluator().clearCache(item);
}
-void ModulePropertyMerger::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Merging module property values took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
-}
-
-ModulePropertyMerger::ModulePropertyMerger(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) { }
-ModulePropertyMerger::~ModulePropertyMerger() = default;
-
-int ModulePropertyMerger::Private::compareValuePriorities(
- const Item *productItem, const ValueConstPtr &v1, const ValueConstPtr &v2)
+int ModulePropertyMerger::compareValuePriorities(const ValueConstPtr &v1, const ValueConstPtr &v2)
{
QBS_CHECK(v1);
QBS_CHECK(v2);
QBS_CHECK(v1->scope() != v2->scope());
QBS_CHECK(v1->type() == Value::JSSourceValueType || v2->type() == Value::JSSourceValueType);
- const int prio1 = v1->priority(productItem);
- const int prio2 = v2->priority(productItem);
+ const int prio1 = v1->priority(m_product.item);
+ const int prio2 = v2->priority(m_product.item);
if (prio1 != prio2)
return prio1 - prio2;
const int prioDiff = v1->scopeName().compare(v2->scopeName()); // Sic! See 8ff1dd0044
@@ -139,32 +151,36 @@ int ModulePropertyMerger::Private::compareValuePriorities(
return prioDiff;
}
-ValuePtr ModulePropertyMerger::Private::mergeListValues(
- const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem)
+ValuePtr ModulePropertyMerger::mergeListValues(const ValuePtr &currentHead, const ValuePtr &newElem)
{
QBS_CHECK(newElem);
QBS_CHECK(!newElem->next());
if (!currentHead)
- return !newElem->expired(productItem) ? newElem : newElem->next();
+ return !newElem->expired(m_product.item) ? newElem : newElem->next();
- QBS_CHECK(!currentHead->expired(productItem));
+ QBS_CHECK(!currentHead->expired(m_product.item));
- if (newElem->expired(productItem))
+ if (newElem->expired(m_product.item))
return currentHead;
- if (compareValuePriorities(productItem, currentHead, newElem) < 0) {
+ if (compareValuePriorities(currentHead, newElem) < 0) {
newElem->setNext(currentHead);
return newElem;
}
- currentHead->setNext(mergeListValues(productItem, currentHead->next(), newElem));
+ currentHead->setNext(mergeListValues(currentHead->next(), newElem));
return currentHead;
}
-void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
- const Item *productItem, Item *loadingItem, const QString &loadingName, Item *globalInstance,
+void ModulePropertyMerger::mergePropertyFromLocalInstance(
+ Item *loadingItem, const QString &loadingName, Item *globalInstance,
const QString &name, const ValuePtr &value)
{
+ if (loadingItem->type() == ItemType::Project) {
+ throw ErrorInfo(Tr::tr("Module properties cannot be set in Project items."),
+ value->location());
+ }
+
const PropertyDeclaration decl = globalInstance->propertyDeclaration(name);
if (!decl.isValid()) {
if (value->type() == Value::ItemValueType || value->createdByPropertiesBlock())
@@ -173,9 +189,9 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
.arg(name), value->location());
}
if (const ErrorInfo error = decl.checkForDeprecation(
- loaderState.parameters().deprecationWarningMode(), value->location(),
- loaderState.logger()); error.hasError()) {
- handlePropertyError(error, loaderState.parameters(), loaderState.logger());
+ m_loaderState.parameters().deprecationWarningMode(), value->location(),
+ m_loaderState.logger()); error.hasError()) {
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
return;
}
if (value->setInternally()) { // E.g. qbs.architecture after multiplexing.
@@ -196,9 +212,9 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
QBS_CHECK(value->type() == Value::JSSourceValueType);
if (decl.isScalar()) {
- QBS_CHECK(!globalVal->expired(productItem));
- QBS_CHECK(!value->expired(productItem));
- if (compareValuePriorities(productItem, globalVal, value) < 0) {
+ QBS_CHECK(!globalVal->expired(m_product.item));
+ QBS_CHECK(!value->expired(m_product.item));
+ if (compareValuePriorities(globalVal, value) < 0) {
value->setCandidates(globalVal->candidates());
globalVal->setCandidates({});
value->addCandidate(globalVal);
@@ -207,27 +223,27 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
globalVal->addCandidate(value);
}
} else {
- if (const ValuePtr &newChainStart = mergeListValues(productItem, globalVal, value);
+ if (const ValuePtr &newChainStart = mergeListValues(globalVal, value);
newChainStart != globalVal) {
globalInstance->setProperty(decl.name(), newChainStart);
}
}
}
-bool ModulePropertyMerger::Private::doFinalMerge(const Item *productItem, Item *moduleItem)
+bool ModulePropertyMerger::doFinalMerge(Item *moduleItem)
{
if (!moduleItem->isPresentModule())
return false;
bool mustInvalidateCache = false;
for (auto it = moduleItem->properties().begin(); it != moduleItem->properties().end(); ++it) {
- if (doFinalMerge(productItem, moduleItem->propertyDeclaration(it.key()), it.value()))
+ if (doFinalMerge(moduleItem->propertyDeclaration(it.key()), it.value()))
mustInvalidateCache = true;
}
return mustInvalidateCache;
}
-bool ModulePropertyMerger::Private::doFinalMerge(
- const Item *productItem, const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue)
+bool ModulePropertyMerger::doFinalMerge(const PropertyDeclaration &propertyDecl,
+ ValuePtr &propertyValue)
{
if (propertyValue->type() == Value::VariantValueType) {
QBS_CHECK(!propertyValue->next());
@@ -240,10 +256,10 @@ bool ModulePropertyMerger::Private::doFinalMerge(
if (propertyValue->candidates().empty())
return false;
std::pair<int, std::vector<ValuePtr>> candidatesWithHighestPrio;
- candidatesWithHighestPrio.first = propertyValue->priority(productItem);
+ candidatesWithHighestPrio.first = propertyValue->priority(m_product.item);
candidatesWithHighestPrio.second.push_back(propertyValue);
for (const ValuePtr &v : propertyValue->candidates()) {
- const int prio = v->priority(productItem);
+ const int prio = v->priority(m_product.item);
if (prio < candidatesWithHighestPrio.first)
continue;
if (prio > candidatesWithHighestPrio.first) {
@@ -271,11 +287,16 @@ bool ModulePropertyMerger::Private::doFinalMerge(
error.append({}, v->location());
}
if (error.items().size() > 2)
- loaderState.logger().printWarning(error);
+ m_loaderState.logger().printWarning(error);
}
if (propertyValue == chosenValue)
return false;
+ std::vector<ValuePtr> candidates = propertyValue->candidates();
+ candidates.erase(std::find(candidates.begin(), candidates.end(), chosenValue));
+ chosenValue->setCandidates(candidates);
+ chosenValue->addCandidate(propertyValue);
+ propertyValue->setCandidates({});
propertyValue = chosenValue;
return true;
}
@@ -291,7 +312,7 @@ bool ModulePropertyMerger::Private::doFinalMerge(
}
ValuePtr newValue;
for (const ValuePtr &v : singleValuesBefore)
- newValue = mergeListValues(productItem, newValue, v);
+ newValue = mergeListValues(newValue, v);
std::vector<ValuePtr> singleValuesAfter;
for (ValuePtr current = propertyValue; current; current = current->next())
singleValuesAfter.push_back(current);
diff --git a/src/lib/corelib/loader/modulepropertymerger.h b/src/lib/corelib/loader/modulepropertymerger.h
index 4ea72c2a4..a6643216f 100644
--- a/src/lib/corelib/loader/modulepropertymerger.h
+++ b/src/lib/corelib/loader/modulepropertymerger.h
@@ -39,15 +39,18 @@
#pragma once
-#include <tools/pimpl.h>
-
#include <QtGlobal>
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
-// This class comprises functions for collecting values attached to module properties
+// This module comprises functions for collecting values attached to module properties
// in different contexts.
// For example, in the Qt.core module you will find a property binding such as this:
// cpp.defines: "QT_CORE_LIB"
@@ -62,35 +65,23 @@ class LoaderState;
// with the same priority trigger a warning message.
// Since the right-hand side of a binding can refer to properties of the surrounding context,
// each such value gets its own scope.
-class ModulePropertyMerger
-{
-public:
- ModulePropertyMerger(LoaderState &loaderState);
- ~ModulePropertyMerger();
-
- // This function is called when a module is loaded via a Depends item.
- // loadingItem is the product or module containing the Depends item.
- // loadingName is the name of that module. It is used as a tie-breaker for list property values
- // with equal priority.
- // localInstance is the module instance placeholder in the ItemValue of a property binding,
- // i.e. the "cpp" in "cpp.defines".
- // globalInstance is the actual module into which the properties from localInstance get merged.
- void mergeFromLocalInstance(const Item *productItem, Item *loadingItem,
- const QString &loadingName, const Item *localInstance,
- Item *globalInstance);
-
- // This function is called after all dependencies have been resolved. It uses its global
- // knowledge of module priorities to potentially adjust the order of list values or
- // favor different scalar values. It can also remove previously merged-in values again;
- // this can happen if a module fails to load after it already merged some values, or
- // if it fails validation in the end.
- void doFinalMerge(const Item *productItem);
- void printProfilingInfo(int indent);
+// This function is called when a module is loaded via a Depends item.
+// loadingItem is the product or module containing the Depends item.
+// loadingName is the name of that module. It is used as a tie-breaker for list property values
+// with equal priority.
+// localInstance is the module instance placeholder in the ItemValue of a property binding,
+// i.e. the "cpp" in "cpp.defines".
+// globalInstance is the actual module into which the properties from localInstance get merged.
+void mergeFromLocalInstance(ProductContext &product, Item *loadingItem,
+ const QString &loadingName, const Item *localInstance,
+ Item *globalInstance, LoaderState &loaderState);
-private:
- class Private;
- Pimpl<Private> d;
-};
+// This function is called after all dependencies have been resolved. It uses its global
+// knowledge of module priorities to potentially adjust the order of list values or
+// favor different scalar values. It can also remove previously merged-in values again;
+// this can happen if a module fails to load after it already merged some values, or
+// if it fails validation in the end.
+void doFinalMerge(ProductContext &product, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleproviderloader.cpp b/src/lib/corelib/loader/moduleproviderloader.cpp
index 06123597a..d0789facd 100644
--- a/src/lib/corelib/loader/moduleproviderloader.cpp
+++ b/src/lib/corelib/loader/moduleproviderloader.cpp
@@ -57,80 +57,71 @@
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
-#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
namespace qbs {
namespace Internal {
+static QString getConfigHash(const QVariantMap& config)
+{
+ QJsonDocument doc;
+ doc.setObject(QJsonObject::fromVariantMap(config));
+ return QString::fromLatin1(
+ QCryptographicHash::hash(doc.toJson(), QCryptographicHash::Sha1).toHex().left(16));
+}
+
ModuleProviderLoader::ModuleProviderLoader(LoaderState &loaderState)
: m_loaderState(loaderState) {}
ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders(
- const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode)
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
{
ModuleProviderLoader::ModuleProviderResult result;
- std::vector<Provider> providersToRun;
- qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
- << "not found, checking for module providers";
- const auto providerNames = getModuleProviders(productContext.productItem);
- if (providerNames) {
- providersToRun = transformed<std::vector<Provider>>(*providerNames, [](const auto &name) {
- return Provider{name, ModuleProviderLookup::Named}; });
- } else {
- for (QualifiedId providerName = moduleName; !providerName.empty();
- providerName.pop_back()) {
- providersToRun.push_back({providerName, ModuleProviderLookup::Scoped});
+ try {
+ std::vector<Provider> providersToRun;
+ qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
+ << "not found, checking for module providers";
+ const auto providerNames = getModuleProviders(productContext.item);
+ if (providerNames) {
+ providersToRun = transformed<std::vector<Provider>>(*providerNames, [](const auto &name) {
+ return Provider{name, ModuleProviderLookup::Named}; });
+ } else {
+ for (QualifiedId providerName = moduleName; !providerName.empty();
+ providerName.pop_back()) {
+ providersToRun.push_back({providerName, ModuleProviderLookup::Scoped});
+ }
}
- }
- result = executeModuleProvidersHelper(productContext, dependsItemLocation, providersToRun);
-
- if (fallbackMode == FallbackMode::Enabled
- && !result.providerFound
- && !providerNames) {
- qCDebug(lcModuleLoader) << "Specific module provider not found for"
- << moduleName.toString() << ", setting up fallback.";
result = executeModuleProvidersHelper(
- productContext,
- dependsItemLocation,
- {{moduleName, ModuleProviderLookup::Fallback}});
+ productContext, dependsItemLocation, moduleName, providersToRun);
+ } catch (const ErrorInfo &error) {
+ auto ei = error;
+ ei.prepend(
+ Tr::tr("Error executing provider for module '%1':").arg(moduleName.toString()),
+ dependsItemLocation);
+ productContext.handleError(ei);
}
-
return result;
}
ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper(
- const ProductContext &product,
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const std::vector<Provider> &providers)
{
if (providers.empty())
return {};
QStringList allSearchPaths;
ModuleProviderResult result;
- result.providerConfig = product.providerConfig ? *product.providerConfig
- : getModuleProviderConfig(product);
+ setupModuleProviderConfig(product);
const auto qbsModule = evaluateQbsModule(product);
for (const auto &[name, lookupType] : providers) {
- const QVariantMap config = result.providerConfig.value(name.toString()).toMap();
- ModuleProviderInfo &info = m_storedModuleProviderInfo.providers[
- {name.toString(), config, qbsModule, int(lookupType)}];
- const bool fromCache = !info.name.isEmpty();
- if (!fromCache) {
- info.name = name;
- info.config = config;
- info.providerFile = findModuleProviderFile(name, lookupType);
- if (!info.providerFile.isEmpty()) {
- qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile;
- const auto evalResult = evaluateModuleProvider(
- product, dependsItemLocation, name, info.providerFile, config, qbsModule);
- info.searchPaths = evalResult.first;
- result.probes << evalResult.second;
- info.transientOutput = m_loaderState.parameters().dryRun();
- }
- }
+ const auto &[info, fromCache] = findOrCreateProviderInfo(
+ product, dependsItemLocation, moduleName, name, lookupType, qbsModule);
if (info.providerFile.isEmpty()) {
if (lookupType == ModuleProviderLookup::Named)
throw ErrorInfo(Tr::tr("Unknown provider '%1'").arg(name.toString()));
@@ -158,11 +149,52 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
return result;
}
-QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product)
+std::pair<const ModuleProviderInfo &, bool /*fromCache*/>
+ModuleProviderLoader::findOrCreateProviderInfo(
+ ProductContext &product, const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName, const QualifiedId &name, ModuleProviderLookup lookupType,
+ const QVariantMap &qbsModule)
+{
+ QBS_CHECK(product.providerConfig);
+ const QVariantMap config = product.providerConfig->value(name.toString()).toMap();
+ std::lock_guard lock(m_loaderState.topLevelProject().moduleProvidersCacheLock());
+ ModuleProvidersCacheKey cacheKey{name.toString(), {}, config, qbsModule, int(lookupType)};
+ // TODO: get rid of non-eager providers and eliminate following if-logic
+ // first, try to find eager provider (stored with an empty module name)
+ if (ModuleProviderInfo *provider = m_loaderState.topLevelProject().moduleProvider(cacheKey))
+ return {*provider, true};
+ // second, try to find non-eager provider for a specific module name
+ std::get<1>(cacheKey) = moduleName.toString(); // override moduleName
+ if (ModuleProviderInfo *provider = m_loaderState.topLevelProject().moduleProvider(cacheKey))
+ return {*provider, true};
+ bool isEager = false;
+ ModuleProviderInfo info;
+ info.name = name;
+ info.config = config;
+ info.providerFile = findModuleProviderFile(name, lookupType);
+ if (!info.providerFile.isEmpty()) {
+ qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile;
+ std::tie(info.searchPaths, isEager) = evaluateModuleProvider(
+ product,
+ dependsItemLocation,
+ moduleName,
+ name,
+ info.providerFile,
+ config,
+ qbsModule);
+ info.transientOutput = m_loaderState.parameters().dryRun();
+ }
+ std::get<1>(cacheKey) = isEager ? QString() : moduleName.toString();
+ return {m_loaderState.topLevelProject().addModuleProvider(cacheKey, info), false};
+}
+
+void ModuleProviderLoader::setupModuleProviderConfig(ProductContext &product)
{
+ if (product.providerConfig)
+ return;
QVariantMap providerConfig;
const ItemValueConstPtr configItemValue =
- product.productItem->itemProperty(StringConstants::moduleProviders());
+ product.item->itemProperty(StringConstants::moduleProviders(), m_loaderState.itemPool());
if (configItemValue) {
const std::function<void(const Item *, QualifiedId)> collectMap
= [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) {
@@ -177,6 +209,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
continue;
}
case Value::JSSourceValueType: {
+ it.value()->setScope(item->scope(), {});
const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(),
m_loaderState.evaluator().value(item, it.key()));
value = getJsVariant(m_loaderState.evaluator().engine()->context(), sv);
@@ -191,7 +224,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
providerConfig.insert(name.toString(), m);
}
};
- configItemValue->item()->setScope(product.productItem);
+ configItemValue->item()->setScope(product.item);
collectMap(configItemValue->item(), QualifiedId());
}
for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) {
@@ -208,7 +241,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
}
providerConfig.insert(provider, currentMapForProvider);
}
- return providerConfig;
+ product.providerConfig = providerConfig;
}
std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item)
@@ -245,9 +278,6 @@ QString ModuleProviderLoader::findModuleProviderFile(
fullPath = FileInfo::resolvePath(fullPath, component);
fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs"));
break;
- case ModuleProviderLookup::Fallback:
- fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback/provider.qbs"));
- break;
}
if (!FileInfo::exists(fullPath)) {
qCDebug(lcModuleLoader) << "No module provider found at" << fullPath;
@@ -258,14 +288,16 @@ QString ModuleProviderLoader::findModuleProviderFile(
return {};
}
-QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const
+QVariantMap ModuleProviderLoader::evaluateQbsModule(ProductContext &product) const
{
+ if (product.providerQbsModule)
+ return *product.providerQbsModule;
const QString properties[] = {
QStringLiteral("sysroot"),
QStringLiteral("toolchain"),
};
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
- product.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
QVariantMap result;
for (const auto &property : properties) {
const ScopedJsValue val(m_loaderState.evaluator().engine()->context(),
@@ -281,42 +313,38 @@ QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &produc
result[property] = std::move(value);
}
- return result;
+ return *(product.providerQbsModule = result);
}
-Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule)
+Item *ModuleProviderLoader::createProviderScope(
+ const ProductContext &product, const QVariantMap &qbsModule)
{
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
- product.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
- Item *fakeQbsModule = Item::create(product.productItem->pool(), ItemType::Scope);
+ Item *fakeQbsModule = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
for (auto it = qbsModule.begin(), end = qbsModule.end(); it != end; ++it) {
fakeQbsModule->setProperty(it.key(), VariantValue::create(it.value()));
}
- Item *scope = Item::create(product.productItem->pool(), ItemType::Scope);
+ Item *scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
scope->setFile(qbsItemValue->item()->file());
scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule));
return scope;
}
-std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product,
+ModuleProviderLoader::EvaluationResult ModuleProviderLoader::evaluateModuleProvider(
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const QualifiedId &name,
const QString &providerFile,
const QVariantMap &moduleConfig,
const QVariantMap &qbsModule)
{
- QTemporaryFile dummyItemFile;
- if (!dummyItemFile.open()) {
- throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider "
- "for dependency '%1': %2").arg(name.toString(),
- dummyItemFile.errorString()));
- }
- m_tempQbsFiles << dummyItemFile.fileName();
qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile;
- const QString projectBuildDir = product.projectItem->variantProperty(
+ const QString projectBuildDir = product.project->item->variantProperty(
StringConstants::buildDirectoryProperty())->value().toString();
const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
@@ -324,25 +352,9 @@ std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evalua
auto jsConfig = moduleConfig;
jsConfig[StringConstants::qbsModule()] = qbsModule;
- QTextStream stream(&dummyItemFile);
- using Qt::endl;
- setupDefaultCodec(stream);
- stream << "import qbs.FileInfo" << endl;
- stream << "import qbs.Utilities" << endl;
- stream << "import '" << providerFile << "' as Provider" << endl;
- stream << "Provider {" << endl;
- stream << " name: " << toJSLiteral(name.toString()) << endl;
- stream << " property var config: (" << toJSLiteral(jsConfig) << ')' << endl;
- stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, "
- " Utilities.getHash(JSON.stringify(config)))" << endl;
- stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl;
- stream << " property stringList searchPaths: (relativeSearchPaths || [])"
- " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })"
- << endl;
- stream << "}" << endl;
- stream.flush();
+ QString outputBaseDir = searchPathBaseDir + QLatin1Char('/') + getConfigHash(jsConfig);
Item * const providerItem = m_loaderState.itemReader().setupItemFromFile(
- dummyItemFile.fileName(), dependsItemLocation);
+ providerFile, dependsItemLocation);
if (providerItem->type() != ItemType::ModuleProvider) {
throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
"but '%3' was expected.")
@@ -350,17 +362,54 @@ std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evalua
BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider)));
}
- providerItem->setScope(createProviderScope(product, qbsModule));
- providerItem->overrideProperties(moduleConfig, name, m_loaderState.parameters(),
- m_loaderState.logger());
- std::vector<ProbeConstPtr> probes = m_loaderState.probesResolver().resolveProbes(
- {product.name, product.uniqueName}, providerItem);
+ Item * const scope = createProviderScope(product, qbsModule);
+ for (auto it = providerItem->properties().begin(); it != providerItem->properties().end(); ++it)
+ it.value()->setScope(scope, {});
+ providerItem->setProperty(
+ StringConstants::nameProperty(),
+ VariantValue::create(name.toString()));
+ providerItem->setProperty(
+ QStringLiteral("outputBaseDir"),
+ VariantValue::create(outputBaseDir));
+ providerItem->overrideProperties(moduleConfig, name,
+ m_loaderState.parameters(), m_loaderState.logger());
+
+ const bool isEager = m_loaderState.evaluator().boolValue(
+ providerItem, StringConstants::isEagerProperty());
+ if (!isEager) {
+ providerItem->setProperty(
+ StringConstants::moduleNameProperty(),
+ VariantValue::create(moduleName.toString()));
+ }
+
+ ProbesResolver(m_loaderState).resolveProbes(product, providerItem);
EvalContextSwitcher contextSwitcher(m_loaderState.evaluator().engine(),
EvalContext::ModuleProvider);
- return std::make_pair(m_loaderState.evaluator().stringListValue(
- providerItem, QStringLiteral("searchPaths")),
- std::move(probes));
+
+ checkAllowedValues(providerItem);
+ auto searchPaths = m_loaderState.evaluator().stringListValue(
+ providerItem, QStringLiteral("relativeSearchPaths"));
+ auto prependBaseDir = [&outputBaseDir](const auto &path) {
+ return outputBaseDir + QLatin1Char('/') + path;
+ };
+ std::transform(searchPaths.begin(), searchPaths.end(), searchPaths.begin(), prependBaseDir);
+ return {searchPaths, isEager};
+}
+
+void ModuleProviderLoader::checkAllowedValues(Item *providerItem)
+{
+ for (const auto &propertyDeclaration : providerItem->propertyDeclarations()) {
+ if (!propertyDeclaration.shouldCheckAllowedValues())
+ continue;
+ const auto &name = propertyDeclaration.name();
+ if (name == QStringLiteral("relativeSearchPaths"))
+ continue;
+ const auto value = m_loaderState.evaluator().variantValue(providerItem, name);
+ const auto propertyValue = providerItem->property(name);
+ propertyDeclaration.checkAllowedValues(
+ value, propertyValue->location(), name, m_loaderState);
+ }
}
} // namespace Internal
diff --git a/src/lib/corelib/loader/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h
index 221830f22..471cfbb02 100644
--- a/src/lib/corelib/loader/moduleproviderloader.h
+++ b/src/lib/corelib/loader/moduleproviderloader.h
@@ -46,87 +46,65 @@
#include <language/forward_decls.h>
#include <language/moduleproviderinfo.h>
-#include <QtCore/qmap.h>
#include <QtCore/qvariant.h>
#include <optional>
+#include <utility>
#include <vector>
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
class ModuleProviderLoader
{
public:
explicit ModuleProviderLoader(LoaderState &loaderState);
- enum class ModuleProviderLookup { Scoped, Named, Fallback };
-
- struct Provider
- {
- QualifiedId name;
- ModuleProviderLookup lookup;
- };
-
- struct ModuleProviderResult
- {
- std::vector<ProbeConstPtr> probes;
- QVariantMap providerConfig;
+ struct ModuleProviderResult {
bool providerFound = false;
std::optional<QStringList> searchPaths;
};
-
- const StoredModuleProviderInfo &storedModuleProviderInfo() const
- {
- return m_storedModuleProviderInfo;
- }
-
- void setStoredModuleProviderInfo(StoredModuleProviderInfo moduleProviderInfo)
- {
- m_storedModuleProviderInfo = std::move(moduleProviderInfo);
- }
-
- const Set<QString> &tempQbsFiles() const { return m_tempQbsFiles; }
-
- struct ProductContext {
- Item * const productItem;
- const Item * const projectItem;
- const QString &name;
- const QString &uniqueName;
- const QVariantMap &moduleProperties;
- const std::optional<QVariantMap> providerConfig;
- };
ModuleProviderResult executeModuleProviders(
- const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode);
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName);
private:
+ enum class ModuleProviderLookup { Scoped, Named };
+ struct Provider {
+ QualifiedId name;
+ ModuleProviderLookup lookup;
+ };
ModuleProviderResult executeModuleProvidersHelper(
- const ProductContext &product,
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const std::vector<Provider> &providers);
- QVariantMap getModuleProviderConfig(const ProductContext &product);
+ std::pair<const ModuleProviderInfo &, bool>
+ findOrCreateProviderInfo(ProductContext &product, const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName, const QualifiedId &name,
+ ModuleProviderLookup lookupType, const QVariantMap &qbsModule);
+ void setupModuleProviderConfig(ProductContext &product);
std::optional<std::vector<QualifiedId>> getModuleProviders(Item *item);
QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType);
- QVariantMap evaluateQbsModule(const ProductContext &product) const;
+ QVariantMap evaluateQbsModule(ProductContext &product) const;
Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule);
- std::pair<QStringList, std::vector<ProbeConstPtr>> evaluateModuleProvider(
- const ProductContext &product,
- const CodeLocation &location,
+ using EvaluationResult = std::pair<QStringList, bool /*isEager*/>;
+ EvaluationResult evaluateModuleProvider(
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const QualifiedId &name,
const QString &providerFile,
const QVariantMap &moduleConfig,
const QVariantMap &qbsModule);
+ void checkAllowedValues(Item *providerItem);
-private:
LoaderState &m_loaderState;
- StoredModuleProviderInfo m_storedModuleProviderInfo;
- Set<QString> m_tempQbsFiles;
};
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/probesresolver.cpp b/src/lib/corelib/loader/probesresolver.cpp
index 763f3ed29..269e9c4b6 100644
--- a/src/lib/corelib/loader/probesresolver.cpp
+++ b/src/lib/corelib/loader/probesresolver.cpp
@@ -83,40 +83,29 @@ static QString probeGlobalId(Item *probe)
ProbesResolver::ProbesResolver(LoaderState &loaderState) : m_loaderState(loaderState) {}
-void ProbesResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- m_oldProjectProbes.clear();
- for (const ProbeConstPtr& probe : oldProbes)
- m_oldProjectProbes[probe->globalId()] << probe;
-}
-
-void ProbesResolver::setOldProductProbes(
- const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- m_oldProductProbes = oldProbes;
-}
-
-std::vector<ProbeConstPtr> ProbesResolver::resolveProbes(const ProductContext &productContext, Item *item)
+void ProbesResolver::resolveProbes(ProductContext &productContext, Item *item)
{
AccumulatingTimer probesTimer(m_loaderState.parameters().logElapsedTime()
- ? &m_elapsedTimeProbes : nullptr);
+ ? &productContext.timingData.probes : nullptr);
+
EvalContextSwitcher evalContextSwitcher(m_loaderState.evaluator().engine(),
EvalContext::ProbeExecution);
- std::vector<ProbeConstPtr> probes;
- for (Item * const child : item->children())
+ for (Item * const child : item->children()) {
if (child->type() == ItemType::Probe)
- probes.push_back(resolveProbe(productContext, item, child));
- return probes;
+ resolveProbe(productContext, item, child);
+ }
}
-ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext, Item *parent,
+void ProbesResolver::resolveProbe(ProductContext &productContext, Item *parent,
Item *probe)
{
qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString();
- ++m_probesEncountered;
const QString &probeId = probeGlobalId(probe);
if (Q_UNLIKELY(probeId.isEmpty()))
throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location());
+ const bool isProjectLevelProbe
+ = parent->type() == ItemType::Project
+ || productContext.name.startsWith(StringConstants::shadowProductPrefix());
const JSSourceValueConstPtr configureScript
= probe->sourceProperty(StringConstants::configureProperty());
QBS_CHECK(configureScript);
@@ -143,29 +132,30 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
const bool condition = evaluator.boolValue(probe, StringConstants::conditionProperty());
const QString &sourceCode = configureScript->sourceCode().toString();
ProbeConstPtr resolvedProbe;
- if (parent->type() == ItemType::Project
- || productContext.name.startsWith(StringConstants::shadowProductPrefix())) {
+ std::lock_guard lock(m_loaderState.topLevelProject().probesCacheLock());
+ m_loaderState.topLevelProject().incrementProbesCount();
+ if (isProjectLevelProbe) {
resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode);
} else {
- resolvedProbe = findOldProductProbe(productContext.uniqueName, condition,
+ resolvedProbe = findOldProductProbe(productContext.uniqueName(), condition,
initialProperties, sourceCode);
}
if (!resolvedProbe) {
resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties);
if (resolvedProbe) {
qCDebug(lcModuleLoader) << "probe results cached from current run";
- ++m_probesCachedCurrent;
+ m_loaderState.topLevelProject().incrementReusedCurrentProbesCount();
}
} else {
qCDebug(lcModuleLoader) << "probe results cached from earlier run";
- ++m_probesCachedOld;
+ m_loaderState.topLevelProject().incrementReusedOldProbesCount();
}
ScopedJsValue configureScope(ctx, JS_UNDEFINED);
std::vector<QString> importedFilesUsedInConfigure;
if (!condition) {
qCDebug(lcModuleLoader) << "Probe disabled; skipping";
} else if (!resolvedProbe) {
- ++m_probesRun;
+ m_loaderState.topLevelProject().incrementRunProbesCount();
qCDebug(lcModuleLoader) << "configure script needs to run";
const Evaluator::FileContextScopes fileCtxScopes
= evaluator.fileContextScopes(configureScript->file());
@@ -183,6 +173,8 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed();
}
QVariantMap properties;
+ VariantValuePtr storedValue;
+ QMap<QString, VariantValuePtr> storedValues;
for (const ProbeProperty &b : probeBindings) {
QVariant newValue;
if (resolvedProbe) {
@@ -206,22 +198,45 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
if (JsException ex = engine->checkAndClearException({}))
throw ex.toErrorInfo();
newValue = getJsVariant(ctx, v);
+ // Special case, string and path lists are represented as js arrays but we don't
+ // want to make them const as we do for object lists. Converting to QStringList
+ // allows to distinguish between these two cases in ScriptEngine::asJsValue
+ if (newValue.userType() == QMetaType::QVariantList
+ && (decl.type() == PropertyDeclaration::StringList
+ || decl.type() == PropertyDeclaration::PathList)) {
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ newValue.convert(QMetaType(QMetaType::QStringList));
+#else
+ newValue.convert(QMetaType::QStringList);
+#endif
+ }
} else {
newValue = initialProperties.value(b.first);
}
}
- if (newValue != getJsVariant(ctx, b.second))
- probe->setProperty(b.first, VariantValue::create(newValue));
- if (!resolvedProbe)
+ if (!qVariantsEqual(newValue, getJsVariant(ctx, b.second))) {
+ if (!resolvedProbe)
+ storedValue = VariantValue::createStored(newValue);
+ else
+ storedValue = resolvedProbe->values().value(b.first);
+
+ probe->setProperty(b.first, storedValue);
+ }
+ if (!resolvedProbe) {
properties.insert(b.first, newValue);
+ storedValues[b.first] = storedValue;
+ }
}
if (!resolvedProbe) {
resolvedProbe = Probe::create(probeId, probe->location(), condition,
- sourceCode, properties, initialProperties,
+ sourceCode, properties, initialProperties, storedValues,
importedFilesUsedInConfigure);
- m_currentProbes[probe->location()] << resolvedProbe;
+ m_loaderState.topLevelProject().addNewlyResolvedProbe(resolvedProbe);
}
- return resolvedProbe;
+ if (isProjectLevelProbe)
+ m_loaderState.topLevelProject().addProjectLevelProbe(resolvedProbe);
+ else
+ productContext.probes << resolvedProbe;
}
ProbeConstPtr ProbesResolver::findOldProjectProbe(
@@ -232,13 +247,10 @@ ProbeConstPtr ProbesResolver::findOldProjectProbe(
{
if (m_loaderState.parameters().forceProbeExecution())
return {};
-
- for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) {
- if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes))
- return oldProbe;
- }
-
- return {};
+ return m_loaderState.topLevelProject().findOldProjectProbe(globalId,
+ [&](const ProbeConstPtr &oldProbe) {
+ return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes);
+ });
}
ProbeConstPtr ProbesResolver::findOldProductProbe(
@@ -249,13 +261,10 @@ ProbeConstPtr ProbesResolver::findOldProductProbe(
{
if (m_loaderState.parameters().forceProbeExecution())
return {};
-
- for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) {
- if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes))
- return oldProbe;
- }
-
- return {};
+ return m_loaderState.topLevelProject().findOldProductProbe(productName,
+ [&](const ProbeConstPtr &oldProbe) {
+ return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes);
+ });
}
ProbeConstPtr ProbesResolver::findCurrentProbe(
@@ -263,12 +272,10 @@ ProbeConstPtr ProbesResolver::findCurrentProbe(
bool condition,
const QVariantMap &initialProperties) const
{
- const std::vector<ProbeConstPtr> &cachedProbes = m_currentProbes.value(location);
- for (const ProbeConstPtr &probe : cachedProbes) {
- if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No))
- return probe;
- }
- return {};
+ return m_loaderState.topLevelProject().findCurrentProbe(location,
+ [&](const ProbeConstPtr &probe) {
+ return probeMatches(probe, condition, initialProperties, QString(), CompareScript::No);
+ });
}
bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition,
@@ -276,26 +283,10 @@ bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition,
CompareScript compareScript) const
{
return probe->condition() == condition
- && probe->initialProperties() == initialProperties
- && (compareScript == CompareScript::No
- || (probe->configureScript() == configureScript
- && !probe->needsReconfigure(m_lastResolveTime)));
-}
-
-void ProbesResolver::printProfilingInfo(int indent)
-{
- if (!m_loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- m_loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Running Probes took %1.").arg(elapsedTimeString(m_elapsedTimeProbes));
- m_loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("%1 probes encountered, %2 configure scripts executed, "
- "%3 re-used from current run, %4 re-used from earlier run.")
- .arg(m_probesEncountered).arg(m_probesRun).arg(m_probesCachedCurrent)
- .arg(m_probesCachedOld);
+ && qVariantMapsEqual(probe->initialProperties(), initialProperties)
+ && (compareScript == CompareScript::No
+ || (probe->configureScript() == configureScript
+ && !probe->needsReconfigure(m_loaderState.topLevelProject().lastResolveTime())));
}
} // namespace Internal
diff --git a/src/lib/corelib/loader/probesresolver.h b/src/lib/corelib/loader/probesresolver.h
index 3e304eec6..4c49861d3 100644
--- a/src/lib/corelib/loader/probesresolver.h
+++ b/src/lib/corelib/loader/probesresolver.h
@@ -42,30 +42,20 @@
#define PROBESRESOLVER_H
#include <language/forward_decls.h>
-
-#include <tools/filetime.h>
+#include <tools/codelocation.h>
#include <QString>
-#include <vector>
-
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
class ProbesResolver
{
public:
explicit ProbesResolver(LoaderState &loaderState);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void printProfilingInfo(int indent);
-
- struct ProductContext {
- const QString &name;
- const QString &uniqueName;
- };
- std::vector<ProbeConstPtr> resolveProbes(const ProductContext &productContext, Item *item);
+ void resolveProbes(ProductContext &productContext, Item *item);
private:
ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition,
@@ -80,19 +70,9 @@ private:
bool probeMatches(const ProbeConstPtr &probe, bool condition,
const QVariantMap &initialProperties, const QString &configureScript,
CompareScript compareScript) const;
- ProbeConstPtr resolveProbe(const ProductContext &productContext, Item *parent, Item *probe);
-
- qint64 m_elapsedTimeProbes = 0;
- quint64 m_probesEncountered = 0;
- quint64 m_probesRun = 0;
- quint64 m_probesCachedCurrent = 0;
- quint64 m_probesCachedOld = 0;
+ void resolveProbe(ProductContext &productContext, Item *parent, Item *probe);
LoaderState &m_loaderState;
- QHash<QString, std::vector<ProbeConstPtr>> m_oldProjectProbes;
- QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes;
- FileTime m_lastResolveTime;
- QHash<CodeLocation, std::vector<ProbeConstPtr>> m_currentProbes;
};
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productitemmultiplexer.cpp b/src/lib/corelib/loader/productitemmultiplexer.cpp
index f18c37caf..0adb3d7fd 100644
--- a/src/lib/corelib/loader/productitemmultiplexer.cpp
+++ b/src/lib/corelib/loader/productitemmultiplexer.cpp
@@ -40,6 +40,7 @@
#include "productitemmultiplexer.h"
#include "loaderutils.h"
+#include "moduleinstantiator.h"
#include <language/evaluator.h>
#include <language/item.h>
@@ -51,14 +52,14 @@
#include <tools/stringconstants.h>
#include <QJsonDocument>
-#include <QThreadStorage>
#include <vector>
-
+// This module deals with product multiplexing over the various defined axes.
+// For instance, a product with qbs.architectures: ["x86", "arm"] will get multiplexed into
+// two products with qbs.architecture: "x86" and qbs.architecture: "arm", respectively.
namespace qbs::Internal {
namespace {
-using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap>>;
using MultiplexRow = std::vector<VariantValuePtr>;
using MultiplexTable = std::vector<MultiplexRow>;
class MultiplexInfo
@@ -69,54 +70,50 @@ public:
bool aggregate = false;
VariantValuePtr multiplexedType;
- QString toIdString(size_t row) const;
+ QString toIdString(size_t row, LoaderState &loaderState) const;
};
} // namespace
-Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
-
-class ProductItemMultiplexer::Private
+class ProductItemMultiplexer
{
public:
- Private(LoaderState &loaderState, QbsItemRetriever qbsItemRetriever)
- : loaderState(loaderState), qbsItemRetriever(std::move(qbsItemRetriever)) {}
-
- MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
+ ProductItemMultiplexer(const QString &productName, Item *productItem, Item *tempQbsModuleItem,
+ const std::function<void()> &dropTempQbsModule, LoaderState &loaderState)
+ : m_loaderState(loaderState), m_productName(productName), m_productItem(productItem),
+ m_tempQbsModuleItem(tempQbsModuleItem), m_dropTempQbsModule(dropTempQbsModule) {}
+
+ QList<Item *> multiplex();
+private:
+ MultiplexInfo extractMultiplexInfo();
MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values);
- LoaderState &loaderState;
- const QbsItemRetriever qbsItemRetriever;
+ LoaderState &m_loaderState;
+ const QString &m_productName;
+ Item * const m_productItem;
+ Item * const m_tempQbsModuleItem;
+ const std::function<void()> &m_dropTempQbsModule;
};
-ProductItemMultiplexer::ProductItemMultiplexer(LoaderState &loaderState,
- const QbsItemRetriever &qbsItemRetriever)
- : d(makePimpl<Private>(loaderState, qbsItemRetriever)) {}
-
-ProductItemMultiplexer::~ProductItemMultiplexer() = default;
-
-QList<Item *> ProductItemMultiplexer::multiplex(
- const QString &productName,
- Item *productItem,
- Item *tempQbsModuleItem,
- const std::function<void ()> &dropTempQbsModule)
+QList<Item *> ProductItemMultiplexer::multiplex()
{
- const auto multiplexInfo = d->extractMultiplexInfo(productItem, tempQbsModuleItem);
- dropTempQbsModule();
+ const auto multiplexInfo = extractMultiplexInfo();
+ m_dropTempQbsModule();
if (multiplexInfo.table.size() > 1)
- productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
- VariantValuePtr productNameValue = VariantValue::create(productName);
- Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
+ m_productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
+ VariantValuePtr productNameValue = VariantValue::create(m_productName);
+ Item *aggregator = multiplexInfo.aggregate ? m_productItem->clone(m_loaderState.itemPool())
+ : nullptr;
QList<Item *> additionalProductItems;
std::vector<VariantValuePtr> multiplexConfigurationIdValues;
for (size_t row = 0; row < multiplexInfo.table.size(); ++row) {
- Item *item = productItem;
+ Item *item = m_productItem;
const auto &mprow = multiplexInfo.table.at(row);
QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
if (row > 0) {
- item = productItem->clone();
+ item = m_productItem->clone(m_loaderState.itemPool());
additionalProductItems.push_back(item);
}
- const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
+ const QString multiplexConfigurationId = multiplexInfo.toIdString(row, m_loaderState);
const VariantValuePtr multiplexConfigurationIdValue
= VariantValue::create(multiplexConfigurationId);
if (multiplexInfo.table.size() > 1 || aggregator) {
@@ -127,7 +124,7 @@ QList<Item *> ProductItemMultiplexer::multiplex(
if (multiplexInfo.multiplexedType)
item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
for (size_t column = 0; column < mprow.size(); ++column) {
- Item * const qbsItem = d->qbsItemRetriever(item);
+ Item * const qbsItem = retrieveQbsItem(item, m_loaderState);
const QString &propertyName = multiplexInfo.properties.at(column);
const VariantValuePtr &mpvalue = mprow.at(column);
qbsItem->setProperty(propertyName, mpvalue);
@@ -139,14 +136,14 @@ QList<Item *> ProductItemMultiplexer::multiplex(
// Add dependencies to all multiplexed instances.
for (const auto &v : multiplexConfigurationIdValues) {
- Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
+ Item *dependsItem = Item::create(&m_loaderState.itemPool(), ItemType::Depends);
dependsItem->setProperty(StringConstants::nameProperty(), productNameValue);
dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), v);
dependsItem->setProperty(StringConstants::profilesProperty(),
VariantValue::create(QStringList()));
dependsItem->setFile(aggregator->file());
- dependsItem->setupForBuiltinType(d->loaderState.parameters().deprecationWarningMode(),
- d->loaderState.logger());
+ dependsItem->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(),
+ m_loaderState.logger());
Item::addChild(aggregator, dependsItem);
}
}
@@ -154,23 +151,22 @@ QList<Item *> ProductItemMultiplexer::multiplex(
return additionalProductItems;
}
-MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *productItem,
- Item *qbsModuleItem)
+MultiplexInfo ProductItemMultiplexer::extractMultiplexInfo()
{
static const QString mpmKey = QStringLiteral("multiplexMap");
- Evaluator &evaluator = loaderState.evaluator();
+ Evaluator &evaluator = m_loaderState.evaluator();
JSContext * const ctx = evaluator.engine()->context();
- const ScopedJsValue multiplexMap(ctx, evaluator.value(qbsModuleItem, mpmKey));
+ const ScopedJsValue multiplexMap(ctx, evaluator.value(m_tempQbsModuleItem, mpmKey));
const QStringList multiplexByQbsProperties = evaluator.stringListValue(
- productItem, StringConstants::multiplexByQbsPropertiesProperty());
+ m_productItem, StringConstants::multiplexByQbsPropertiesProperty());
MultiplexInfo multiplexInfo;
multiplexInfo.aggregate = evaluator.boolValue(
- productItem, StringConstants::aggregateProperty());
+ m_productItem, StringConstants::aggregateProperty());
const QString multiplexedType = evaluator.stringValue(
- productItem, StringConstants::multiplexedTypeProperty());
+ m_productItem, StringConstants::multiplexedTypeProperty());
if (!multiplexedType.isEmpty())
multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
@@ -183,7 +179,7 @@ MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *produc
if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second)
continue;
- const ScopedJsValue arr(ctx, evaluator.value(qbsModuleItem, key));
+ const ScopedJsValue arr(ctx, evaluator.value(m_tempQbsModuleItem, key));
if (JS_IsUndefined(arr))
continue;
if (!JS_IsArray(ctx, arr))
@@ -210,8 +206,8 @@ MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *produc
return multiplexInfo;
}
-MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &table,
- const MultiplexRow &values)
+MultiplexTable ProductItemMultiplexer::combine(const MultiplexTable &table,
+ const MultiplexRow &values)
{
MultiplexTable result;
if (table.empty()) {
@@ -234,31 +230,7 @@ MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &ta
return result;
}
-QVariantMap ProductItemMultiplexer::multiplexIdToVariantMap(const QString &multiplexId)
-{
- if (multiplexId.isEmpty())
- return QVariantMap();
-
- // We assume that MultiplexInfo::toIdString() has been called for this
- // particular multiplex configuration.
- QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId);
- QBS_CHECK(!result.isEmpty());
- return result;
-}
-
-QString ProductItemMultiplexer::fullProductDisplayName(const QString &name,
- const QString &multiplexId)
-{
- static const auto multiplexIdToString =[](const QString &id) {
- return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
- };
- QString result = name;
- if (!multiplexId.isEmpty())
- result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexId));
- return result;
-}
-
-QString MultiplexInfo::toIdString(size_t row) const
+QString MultiplexInfo::toIdString(size_t row, LoaderState &loaderState) const
{
const auto &mprow = table.at(row);
QVariantMap multiplexConfiguration;
@@ -272,9 +244,16 @@ QString MultiplexInfo::toIdString(size_t row) const
.toBase64());
// Cache for later use in multiplexIdToVariantMap()
- multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
+ loaderState.topLevelProject().addMultiplexConfiguration(id, multiplexConfiguration);
return id;
}
+QList<Item *> multiplex(const QString &productName, Item *productItem, Item *tempQbsModuleItem,
+ const std::function<void ()> &dropTempQbsModule, LoaderState &loaderState)
+{
+ return ProductItemMultiplexer(productName, productItem, tempQbsModuleItem, dropTempQbsModule,
+ loaderState).multiplex();
+}
+
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productitemmultiplexer.h b/src/lib/corelib/loader/productitemmultiplexer.h
index 53eb1a722..e02e21793 100644
--- a/src/lib/corelib/loader/productitemmultiplexer.h
+++ b/src/lib/corelib/loader/productitemmultiplexer.h
@@ -39,42 +39,26 @@
#pragma once
-#include <tools/pimpl.h>
-
#include <QList>
-#include <QVariantMap>
#include <functional>
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
namespace qbs::Internal {
class Item;
class LoaderState;
-// This class deals with product multiplexing over the various defined axes.
-// For instance, a product with qbs.architectures: ["x86", "arm"] will get multiplexed into
-// two products with qbs.architecture: "x86" and qbs.architecture: "arm", respectively.
-class ProductItemMultiplexer
-{
-public:
- using QbsItemRetriever = std::function<Item *(Item *)>;
- ProductItemMultiplexer(LoaderState &loaderState, const QbsItemRetriever &qbsItemRetriever);
- ~ProductItemMultiplexer();
-
- // Checks whether the product item is to be multiplexed and returns the list of additional
- // product items. In the normal, non-multiplex case, this list is empty.
- QList<Item *> multiplex(
- const QString &productName,
- Item *productItem,
- Item *tempQbsModuleItem,
- const std::function<void()> &dropTempQbsModule
- );
-
- static QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
- static QString fullProductDisplayName(const QString &name, const QString &multiplexId);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+// Checks whether the product item is to be multiplexed and returns the list of additional
+// product items. In the normal, non-multiplex case, this list is empty.
+QList<Item *> multiplex(
+ const QString &productName,
+ Item *productItem,
+ Item *tempQbsModuleItem,
+ const std::function<void()> &dropTempQbsModule,
+ LoaderState &loaderState
+ );
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productresolver.cpp b/src/lib/corelib/loader/productresolver.cpp
new file mode 100644
index 000000000..a026a76d6
--- /dev/null
+++ b/src/lib/corelib/loader/productresolver.cpp
@@ -0,0 +1,1618 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "productresolver.h"
+
+#include "dependenciesresolver.h"
+#include "groupshandler.h"
+#include "loaderutils.h"
+#include "modulepropertymerger.h"
+#include "probesresolver.h"
+
+#include <jsextensions/jsextensions.h>
+#include <jsextensions/moduleproperties.h>
+#include <language/artifactproperties.h>
+#include <language/builtindeclarations.h>
+#include <language/evaluator.h>
+#include <language/filecontext.h>
+#include <language/item.h>
+#include <language/language.h>
+#include <language/propertymapinternal.h>
+#include <language/scriptengine.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/fileinfo.h>
+#include <tools/jsliterals.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <algorithm>
+
+namespace qbs::Internal {
+
+class PropertiesEvaluator
+{
+public:
+ PropertiesEvaluator(ProductContext &product, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState) {}
+
+ QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors);
+ QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer,
+ const QVariantMap &tmplt, bool lookupPrototype,
+ bool checkErrors);
+ void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue,
+ QVariantMap &result, bool checkErrors);
+
+private:
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
+};
+
+// Dependency resolving, Probe execution.
+// Run for real products and shadow products.
+class ProductResolverStage1
+{
+public:
+ ProductResolverStage1(ProductContext &product, Deferral deferral, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState), m_deferral(deferral) {}
+ void start();
+
+private:
+ void resolveProbes();
+ void resolveProbes(Item *item);
+ void runModuleProbes(const Item::Module &module);
+ void updateModulePresentState(const Item::Module &module);
+ bool validateModule(const Item::Module &module);
+ void handleModuleSetupError(const Item::Module &module, const ErrorInfo &error);
+ void checkPropertyDeclarations();
+ void mergeDependencyParameters();
+ void checkDependencyParameterDeclarations(const Item *productItem,
+ const QString &productName) const;
+ void collectCodeLinks();
+
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
+ const Deferral m_deferral;
+};
+
+// Setting up ResolvedProduct, incuding property evaluation and handling Product child items.
+// Run only for real products.
+class ProductResolverStage2
+{
+public:
+ ProductResolverStage2(ProductContext &product, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState) {}
+ void start();
+
+private:
+ void resolveProductFully();
+ void createProductConfig();
+ void resolveGroup(Item *item);
+ void resolveGroupFully(Item *item, bool isEnabled);
+ QVariantMap resolveAdditionalModuleProperties(const Item *group,
+ const QVariantMap &currentValues);
+ SourceArtifactPtr createSourceArtifact(const QString &fileName, const GroupPtr &group,
+ bool wildcard, const CodeLocation &filesLocation,
+ ErrorInfo *errorInfo);
+ void resolveExport(Item *exportItem);
+ std::unique_ptr<ExportedItem> resolveExportChild(const Item *item,
+ const ExportedModule &module);
+ void setupExportedProperties(const Item *item, const QString &namePrefix,
+ std::vector<ExportedProperty> &properties);
+ QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true);
+
+ void resolveScanner(Item *item, ModuleContext &moduleContext);
+ void resolveModules();
+ void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
+ const QVariantMap &parameters, JobLimits &jobLimits);
+ void applyFileTaggers();
+ void finalizeArtifactProperties();
+ void collectProductDependencies();
+
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
+ GroupConstPtr m_currentGroup;
+ FileLocations m_sourceArtifactLocations;
+ PropertiesEvaluator m_propertiesEvaluator{m_product, m_loaderState};
+
+ using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>;
+ QHash<QStringList, ArtifactPropertiesInfo> m_artifactPropertiesPerFilter;
+};
+
+class ExportsResolver
+{
+public:
+ ExportsResolver(ProductContext &product, LoaderState &loaderState)
+ : m_product(product), m_loaderState(loaderState) {}
+ void start();
+
+private:
+ void resolveShadowProduct();
+ void collectPropertiesForExportItem(Item *productModuleInstance);
+ void collectPropertiesForExportItem(const QualifiedId &moduleName, const ValuePtr &value,
+ Item *moduleInstance, QVariantMap &moduleProps);
+ void collectPropertiesForModuleInExportItem(const Item::Module &module);
+ void adaptExportedPropertyValues();
+ void collectExportedProductDependencies();
+
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
+ PropertiesEvaluator m_propertiesEvaluator{m_product, m_loaderState};
+};
+
+void resolveProduct(ProductContext &product, Deferral deferral, LoaderState &loaderState)
+{
+ try {
+ ProductResolverStage1(product, deferral, loaderState).start();
+ } catch (const ErrorInfo &err) {
+ if (err.isCancelException()) {
+ loaderState.topLevelProject().setCanceled();
+ return;
+ }
+ product.handleError(err);
+ }
+
+ if (product.dependenciesResolvingPending())
+ return;
+
+ if (product.name.startsWith(StringConstants::shadowProductPrefix()))
+ return;
+
+ // TODO: The weird double-forwarded error handling can hopefully be simplified now.
+ try {
+ ProductResolverStage2(product, loaderState).start();
+ } catch (const ErrorInfo &err) {
+ if (err.isCancelException()) {
+ loaderState.topLevelProject().setCanceled();
+ return;
+ }
+ loaderState.topLevelProject().addQueuedError(err);
+ }
+}
+
+void setupExports(ProductContext &product, LoaderState &loaderState)
+{
+ ExportsResolver(product, loaderState).start();
+}
+
+void ProductResolverStage1::start()
+{
+ TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject();
+ topLevelProject.checkCancelation();
+
+ if (m_product.delayedError.hasError())
+ return;
+
+ resolveDependencies(m_product, m_deferral, m_loaderState);
+ QBS_CHECK(m_product.dependenciesContext);
+ if (!m_product.dependenciesContext->dependenciesResolved)
+ return;
+ if (m_product.delayedError.hasError())
+ return;
+
+ // Run probes for modules and product.
+ resolveProbes();
+
+ // After the probes have run, we can switch on the evaluator cache.
+ Evaluator &evaluator = m_loaderState.evaluator();
+ FileTags fileTags = evaluator.fileTagsValue(m_product.item, StringConstants::typeProperty());
+ EvalCacheEnabler cacheEnabler(&evaluator, evaluator.stringValue(
+ m_product.item,
+ StringConstants::sourceDirectoryProperty()));
+
+ // Run module validation scripts.
+ for (const Item::Module &module : m_product.item->modules()) {
+ if (!validateModule(module))
+ return;
+ fileTags += evaluator.fileTagsValue(
+ module.item, StringConstants::additionalProductTypesProperty());
+ }
+
+ // Disable modules that have been pulled in only by now-disabled modules.
+ // Note that this has to happen in the reverse order compared to the other loops,
+ // with the leaves checked last.
+ for (auto it = m_product.item->modules().rbegin(); it != m_product.item->modules().rend(); ++it)
+ updateModulePresentState(*it);
+
+ // Now do the canonical module property values merge. Note that this will remove
+ // previously attached values from modules that failed validation.
+ // Evaluator cache entries that could potentially change due to this will be purged.
+ doFinalMerge(m_product, m_loaderState);
+
+ const bool enabled = topLevelProject.checkItemCondition(m_product.item, evaluator);
+
+ collectCodeLinks();
+ mergeDependencyParameters();
+ checkDependencyParameterDeclarations(m_product.item, m_product.name);
+
+ setupGroups(m_product, m_loaderState);
+
+ // Collect the full list of fileTags, including the values contributed by modules.
+ if (!m_product.delayedError.hasError() && enabled
+ && !m_product.name.startsWith(StringConstants::shadowProductPrefix())) {
+ topLevelProject.addProductByType(m_product, fileTags);
+ m_product.item->setProperty(StringConstants::typeProperty(),
+ VariantValue::create(sorted(fileTags.toStringList())));
+ }
+
+ checkPropertyDeclarations();
+}
+
+void ProductResolverStage1::resolveProbes()
+{
+ for (const Item::Module &module : m_product.item->modules()) {
+ runModuleProbes(module);
+ if (m_product.delayedError.hasError())
+ return;
+ }
+ resolveProbes(m_product.item);
+}
+
+void ProductResolverStage1::resolveProbes(Item *item)
+{
+ ProbesResolver(m_loaderState).resolveProbes(m_product, item);
+}
+
+void ProductResolverStage1::runModuleProbes(const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+ if (module.product && m_loaderState.topLevelProject().isDisabledItem(module.product->item)) {
+ createNonPresentModule(m_loaderState.itemPool(), module.name.toString(),
+ QLatin1String("module's exporting product is disabled"),
+ module.item);
+ return;
+ }
+ try {
+ resolveProbes(module.item);
+ if (module.versionRange.minimum.isValid()
+ || module.versionRange.maximum.isValid()) {
+ if (module.versionRange.maximum.isValid()
+ && module.versionRange.minimum >= module.versionRange.maximum) {
+ throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module "
+ "'%3'").arg(module.versionRange.minimum.toString(),
+ module.versionRange.maximum.toString(),
+ module.name.toString()));
+ }
+ const Version moduleVersion = Version::fromString(
+ m_loaderState.evaluator().stringValue(module.item,
+ StringConstants::versionProperty()));
+ if (moduleVersion < module.versionRange.minimum) {
+ throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
+ "at least %3.").arg(module.name.toString(),
+ moduleVersion.toString(),
+ module.versionRange.minimum.toString()));
+ }
+ if (module.versionRange.maximum.isValid()
+ && moduleVersion >= module.versionRange.maximum) {
+ throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
+ "lower than %3.").arg(module.name.toString(),
+ moduleVersion.toString(),
+ module.versionRange.maximum.toString()));
+ }
+ }
+ } catch (const ErrorInfo &error) {
+ handleModuleSetupError(module, error);
+ }
+}
+
+void ProductResolverStage1::updateModulePresentState(const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+ if (module.name.first() == StringConstants::qbsModule())
+ return;
+ bool hasPresentLoadingItem = false;
+ for (const Item::Module::LoadContext &loadContext : module.loadContexts) {
+ const Item * const loadingItem = loadContext.loadingItem();
+ if (loadingItem == m_product.item) {
+ hasPresentLoadingItem = true;
+ break;
+ }
+ if (!loadingItem->isPresentModule())
+ continue;
+ if (loadingItem->prototype() && loadingItem->prototype()->type() == ItemType::Export) {
+ QBS_CHECK(loadingItem->prototype()->parent()->type() == ItemType::Product);
+ if (m_loaderState.topLevelProject().isDisabledItem(loadingItem->prototype()->parent()))
+ continue;
+ }
+ hasPresentLoadingItem = true;
+ break;
+ }
+ if (!hasPresentLoadingItem) {
+ createNonPresentModule(m_loaderState.itemPool(), module.name.toString(),
+ QLatin1String("imported only by disabled module(s)"),
+ module.item);
+ }
+}
+
+bool ProductResolverStage1::validateModule(const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return true;
+ try {
+ m_loaderState.evaluator().boolValue(module.item, StringConstants::validateProperty());
+ for (const auto &dep : module.item->modules()) {
+ if (dep.required && !dep.item->isPresentModule()) {
+ throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not "
+ "loaded successfully")
+ .arg(module.name.toString(), dep.name.toString()));
+ }
+ }
+ } catch (const ErrorInfo &error) {
+ handleModuleSetupError(module, error);
+ if (m_product.delayedError.hasError())
+ return false;
+ }
+ return true;
+}
+
+void ProductResolverStage1::handleModuleSetupError(const Item::Module &module,
+ const ErrorInfo &error)
+{
+ if (module.required) {
+ m_product.handleError(error);
+ } else {
+ qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
+ << "found, but not usable in product" << m_product.name
+ << error.toString();
+ createNonPresentModule(m_loaderState.itemPool(), module.name.toString(),
+ QStringLiteral("failed validation"), module.item);
+ }
+}
+
+void ProductResolverStage1::checkPropertyDeclarations()
+{
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyChecking : nullptr);
+ qbs::Internal::checkPropertyDeclarations(m_product.item, m_loaderState);
+}
+
+void ProductResolverStage1::mergeDependencyParameters()
+{
+ if (m_loaderState.topLevelProject().isDisabledItem(m_product.item))
+ return;
+
+ for (Item::Module &module : m_product.item->modules()) {
+ if (module.name.toString() == StringConstants::qbsModule())
+ continue;
+ if (!module.item->isPresentModule())
+ continue;
+
+ using PP = Item::Module::ParametersWithPriority;
+ std::vector<PP> priorityList;
+ const QVariantMap defaultParameters = module.product
+ ? module.product->defaultParameters
+ : m_loaderState.topLevelProject().parameters(module.item->prototype());
+ priorityList.emplace_back(defaultParameters, INT_MIN);
+ for (const Item::Module::LoadContext &context : module.loadContexts) {
+ const QVariantMap &parameters = context.parameters.first;
+
+ // Empty parameter maps and inactive loading modules do not contribute to the
+ // final parameter map.
+ if (parameters.isEmpty())
+ continue;
+ if (context.loadingItem()->type() == ItemType::ModuleInstance
+ && !context.loadingItem()->isPresentModule()) {
+ continue;
+ }
+
+ // Build a list sorted by priority.
+ static const auto cmp = [](const PP &elem, int prio) { return elem.second < prio; };
+ const auto it = std::lower_bound(priorityList.begin(), priorityList.end(),
+ context.parameters.second, cmp);
+ priorityList.insert(it, context.parameters);
+ }
+
+ module.parameters = qbs::Internal::mergeDependencyParameters(std::move(priorityList));
+ }
+}
+
+class DependencyParameterDeclarationCheck
+{
+public:
+ DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem,
+ const TopLevelProjectContext &topLevelProject)
+ : m_productName(productName), m_productItem(productItem), m_topLevelProject(topLevelProject)
+ {}
+
+ void operator()(const QVariantMap &parameters) const { check(parameters, QualifiedId()); }
+
+private:
+ void check(const QVariantMap &parameters, const QualifiedId &moduleName) const
+ {
+ for (auto it = parameters.begin(); it != parameters.end(); ++it) {
+ if (it.value().userType() == QMetaType::QVariantMap) {
+ check(it.value().toMap(), QualifiedId(moduleName) << it.key());
+ } else {
+ const auto &deps = m_productItem->modules();
+ auto m = std::find_if(deps.begin(), deps.end(),
+ [&moduleName] (const Item::Module &module) {
+ return module.name == moduleName;
+ });
+
+ if (m == deps.end()) {
+ const QualifiedId fullName = QualifiedId(moduleName) << it.key();
+ throw ErrorInfo(Tr::tr("Cannot set parameter '%1', "
+ "because '%2' does not have a dependency on '%3'.")
+ .arg(fullName.toString(), m_productName, moduleName.toString()),
+ m_productItem->location());
+ }
+
+ const auto decls = m_topLevelProject.parameterDeclarations(
+ m->item->rootPrototype());
+ if (!decls.contains(it.key())) {
+ const QualifiedId fullName = QualifiedId(moduleName) << it.key();
+ throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.")
+ .arg(fullName.toString()), m_productItem->location());
+ }
+ }
+ }
+ }
+
+ bool moduleExists(const QualifiedId &name) const
+ {
+ const auto &deps = m_productItem->modules();
+ return any_of(deps, [&name](const Item::Module &module) {
+ return module.name == name;
+ });
+ }
+
+ const QString &m_productName;
+ const Item * const m_productItem;
+ const TopLevelProjectContext &m_topLevelProject;
+};
+
+void ProductResolverStage1::checkDependencyParameterDeclarations(const Item *productItem,
+ const QString &productName) const
+{
+ DependencyParameterDeclarationCheck dpdc(productName, productItem,
+ m_loaderState.topLevelProject());
+ for (const Item::Module &dep : productItem->modules()) {
+ if (!dep.parameters.empty())
+ dpdc(dep.parameters);
+ }
+}
+
+void ProductResolverStage1::collectCodeLinks()
+{
+ for (const Item::Module &module : m_product.item->modules()) {
+ if (module.name.first() == StringConstants::qbsModule())
+ continue;
+ for (const Item::Module::LoadContext &context : module.loadContexts) {
+ m_loaderState.topLevelProject().addCodeLink(
+ context.dependsItem->location().filePath(),
+ context.dependsItem->codeRange(),
+ module.product ? module.product->item->location()
+ : module.item->location());
+ }
+ }
+}
+
+
+void ProductResolverStage2::start()
+{
+ m_loaderState.evaluator().clearPropertyDependencies();
+
+ ResolvedProductPtr product = ResolvedProduct::create();
+ product->enabled = m_product.project->project->enabled;
+ product->moduleProperties = PropertyMapInternal::create();
+ product->project = m_product.project->project;
+ m_product.product = product;
+ product->location = m_product.item->location();
+ const auto errorFromDelayedError = [&] {
+ if (m_product.delayedError.hasError()) {
+ ErrorInfo errorInfo;
+
+ // First item is "main error", gets prepended again in the catch clause.
+ const QList<ErrorItem> &items = m_product.delayedError.items();
+ for (int i = 1; i < items.size(); ++i)
+ errorInfo.append(items.at(i));
+
+ m_product.delayedError.clear();
+ return errorInfo;
+ }
+ return ErrorInfo();
+ };
+
+ // Even if we previously encountered an error, try to continue for as long as possible
+ // to provide IDEs with useful data (e.g. the list of files).
+ // If we encounter a follow-up error, suppress it and report the original one instead.
+ try {
+ resolveProductFully();
+ if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
+ throw error;
+ } catch (ErrorInfo e) {
+ if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
+ e = error;
+ QString mainErrorString = !product->name.isEmpty()
+ ? Tr::tr("Error while handling product '%1':").arg(product->name)
+ : Tr::tr("Error while handling product:");
+ ErrorInfo fullError(mainErrorString, product->location);
+ appendError(fullError, e);
+ if (!product->enabled) {
+ qCDebug(lcProjectResolver) << fullError.toString();
+ return;
+ }
+ if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict)
+ throw fullError;
+ m_loaderState.logger().printWarning(fullError);
+ m_loaderState.logger().printWarning(
+ ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
+ .arg(product->name), product->location));
+ product->enabled = false;
+ }
+}
+
+void ProductResolverStage2::resolveProductFully()
+{
+ Item * const item = m_product.item;
+ const ResolvedProductPtr product = m_product.product;
+ Evaluator &evaluator = m_loaderState.evaluator();
+ product->name = evaluator.stringValue(item, StringConstants::nameProperty());
+
+ // product->buildDirectory() isn't valid yet, because the productProperties map is not ready.
+ m_product.buildDirectory = evaluator.stringValue(
+ item, StringConstants::buildDirectoryProperty());
+ product->multiplexConfigurationId = evaluator.stringValue(
+ item, StringConstants::multiplexConfigurationIdProperty());
+ qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
+ product->enabled = product->enabled
+ && evaluator.boolValue(item, StringConstants::conditionProperty());
+ const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty());
+ if (type)
+ product->fileTags = FileTags::fromStringList(type->value().toStringList());
+ product->targetName = evaluator.stringValue(item, StringConstants::targetNameProperty());
+ product->sourceDirectory = evaluator.stringValue(
+ item, StringConstants::sourceDirectoryProperty());
+ product->destinationDirectory = evaluator.stringValue(
+ item, StringConstants::destinationDirProperty());
+
+ if (product->destinationDirectory.isEmpty()) {
+ product->destinationDirectory = m_product.buildDirectory;
+ } else {
+ product->destinationDirectory = FileInfo::resolvePath(
+ product->topLevelProject()->buildDirectory,
+ product->destinationDirectory);
+ }
+ product->probes = m_product.probes;
+ createProductConfig();
+ product->productProperties.insert(StringConstants::destinationDirProperty(),
+ product->destinationDirectory);
+ ModuleProperties::init(evaluator.engine(), evaluator.scriptValue(item), product.get());
+
+ QList<Item *> subItems = item->children();
+ const ValuePtr filesProperty = item->property(StringConstants::filesProperty());
+ if (filesProperty) {
+ Item *fakeGroup = Item::create(&m_loaderState.itemPool(), ItemType::Group);
+ fakeGroup->setFile(item->file());
+ fakeGroup->setLocation(item->location());
+ fakeGroup->setScope(item);
+ fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name));
+ fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty);
+ fakeGroup->setProperty(StringConstants::excludeFilesProperty(),
+ item->property(StringConstants::excludeFilesProperty()));
+ fakeGroup->setProperty(StringConstants::overrideTagsProperty(),
+ VariantValue::falseValue());
+ fakeGroup->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(),
+ m_loaderState.logger());
+ subItems.prepend(fakeGroup);
+ }
+
+ for (Item * const child : std::as_const(subItems)) {
+ switch (child->type()) {
+ case ItemType::Rule:
+ resolveRule(m_loaderState, child, m_product.project, &m_product, nullptr);
+ break;
+ case ItemType::FileTagger:
+ resolveFileTagger(m_loaderState, child, nullptr, &m_product);
+ break;
+ case ItemType::JobLimit:
+ resolveJobLimit(m_loaderState, child, nullptr, &m_product, nullptr);
+ break;
+ case ItemType::Group:
+ resolveGroup(child);
+ break;
+ case ItemType::Export:
+ resolveExport(child);
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (const ProjectContext *p = m_product.project; p; p = p->parent) {
+ JobLimits tempLimits = p->jobLimits;
+ product->jobLimits = tempLimits.update(product->jobLimits);
+ }
+
+ resolveModules();
+ applyFileTaggers();
+ finalizeArtifactProperties();
+
+ for (const RulePtr &rule : m_product.project->rules) {
+ RulePtr clonedRule = rule->clone();
+ clonedRule->product = product.get();
+ product->rules.push_back(clonedRule);
+ }
+
+ collectProductDependencies();
+}
+
+void ProductResolverStage2::createProductConfig()
+{
+ EvalCacheEnabler cachingEnabler(&m_loaderState.evaluator(),
+ m_product.product->sourceDirectory);
+ m_product.product->moduleProperties->setValue(evaluateModuleValues(m_product.item));
+ m_product.product->productProperties = m_propertiesEvaluator.evaluateProperties(
+ m_product.item, m_product.item, QVariantMap(), true, true);
+}
+
+void ProductResolverStage2::resolveGroup(Item *item)
+{
+ const bool parentEnabled = m_currentGroup ? m_currentGroup->enabled
+ : m_product.product->enabled;
+ const bool isEnabled = parentEnabled
+ && m_loaderState.evaluator().boolValue(item, StringConstants::conditionProperty());
+ try {
+ resolveGroupFully(item, isEnabled);
+ } catch (const ErrorInfo &error) {
+ if (!isEnabled) {
+ qCDebug(lcProjectResolver) << "error resolving group at" << item->location()
+ << error.toString();
+ return;
+ }
+ if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict)
+ throw;
+ m_loaderState.logger().printWarning(error);
+ }
+}
+
+void ProductResolverStage2::resolveGroupFully(Item *item, bool isEnabled)
+{
+ AccumulatingTimer groupTimer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.groupsResolving
+ : nullptr);
+
+ const auto getGroupPropertyMap = [&](const ArtifactProperties *existingProps) {
+ PropertyMapPtr moduleProperties;
+ bool newPropertyMapRequired = false;
+ if (existingProps)
+ moduleProperties = existingProps->propertyMap();
+ if (!moduleProperties) {
+ newPropertyMapRequired = true;
+ moduleProperties = m_currentGroup
+ ? m_currentGroup->properties
+ : m_product.product->moduleProperties;
+ }
+ const QVariantMap newModuleProperties = resolveAdditionalModuleProperties(
+ item, moduleProperties->value());
+ if (!newModuleProperties.empty()) {
+ if (newPropertyMapRequired)
+ moduleProperties = PropertyMapInternal::create();
+ moduleProperties->setValue(newModuleProperties);
+ }
+ return moduleProperties;
+ };
+
+ Evaluator &evaluator = m_loaderState.evaluator();
+ QStringList files = evaluator.stringListValue(item, StringConstants::filesProperty());
+ bool fileTagsSet;
+ const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty(),
+ &fileTagsSet);
+ const QStringList fileTagsFilter
+ = evaluator.stringListValue(item, StringConstants::fileTagsFilterProperty());
+ if (!fileTagsFilter.empty()) {
+ if (Q_UNLIKELY(!files.empty()))
+ throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."),
+ item->location());
+
+ if (!isEnabled)
+ return;
+
+ ArtifactPropertiesInfo &apinfo = m_artifactPropertiesPerFilter[fileTagsFilter];
+ if (apinfo.first) {
+ const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(),
+ [item](const CodeLocation &loc) {
+ return item->location().filePath() == loc.filePath();
+ });
+ if (it != apinfo.second.cend()) {
+ ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items."));
+ error.append(Tr::tr("First item"), *it);
+ error.append(Tr::tr("Second item"), item->location());
+ throw error;
+ }
+ } else {
+ apinfo.first = ArtifactProperties::create();
+ apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter));
+ m_product.product->artifactProperties.push_back(apinfo.first);
+ }
+ apinfo.second.push_back(item->location());
+ apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get()));
+ apinfo.first->addExtraFileTags(fileTags);
+ return;
+ }
+ QStringList patterns;
+ for (int i = files.size(); --i >= 0;) {
+ if (FileInfo::isPattern(files[i]))
+ patterns.push_back(files.takeAt(i));
+ }
+ GroupPtr group = ResolvedGroup::create();
+ bool prefixWasSet = false;
+ group->prefix = evaluator.stringValue(item, StringConstants::prefixProperty(), QString(),
+ &prefixWasSet);
+ if (!prefixWasSet && m_currentGroup)
+ group->prefix = m_currentGroup->prefix;
+ if (!group->prefix.isEmpty()) {
+ for (auto it = files.rbegin(), end = files.rend(); it != end; ++it)
+ it->prepend(group->prefix);
+ }
+ group->location = item->location();
+ group->enabled = isEnabled;
+ group->properties = getGroupPropertyMap(nullptr);
+ group->fileTags = fileTags;
+ group->overrideTags = evaluator.boolValue(item, StringConstants::overrideTagsProperty());
+ if (group->overrideTags && fileTagsSet) {
+ if (group->fileTags.empty() )
+ group->fileTags.insert(unknownFileTag());
+ } else if (m_currentGroup) {
+ group->fileTags.unite(m_currentGroup->fileTags);
+ }
+
+ const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location();
+ const VariantValueConstPtr moduleProp = item->variantProperty(
+ StringConstants::modulePropertyInternal());
+ if (moduleProp)
+ group->targetOfModule = moduleProp->value().toString();
+ ErrorInfo fileError;
+ if (!patterns.empty()) {
+ group->wildcards = std::make_unique<SourceWildCards>();
+ SourceWildCards *wildcards = group->wildcards.get();
+ wildcards->excludePatterns = evaluator.stringListValue(
+ item, StringConstants::excludeFilesProperty());
+ wildcards->patterns = patterns;
+ wildcards->prefix = group->prefix;
+ wildcards->baseDir = FileInfo::path(item->file()->filePath());
+ wildcards->buildDir = m_product.project->project->topLevelProject()->buildDirectory;
+ wildcards->expandPatterns();
+ for (const QString &fileName : wildcards->expandedFiles)
+ createSourceArtifact(fileName, group, true, filesLocation, &fileError);
+ }
+
+ for (const QString &fileName : std::as_const(files))
+ createSourceArtifact(fileName, group, false, filesLocation, &fileError);
+ if (fileError.hasError()) {
+ if (group->enabled) {
+ if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict)
+ throw ErrorInfo(fileError);
+ m_loaderState.logger().printWarning(fileError);
+ } else {
+ qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString();
+ }
+ }
+ group->name = evaluator.stringValue(item, StringConstants::nameProperty());
+ if (group->name.isEmpty())
+ group->name = Tr::tr("Group %1").arg(m_product.product->groups.size());
+ m_product.product->groups.push_back(group);
+
+ class GroupContextSwitcher {
+ public:
+ GroupContextSwitcher(ProductResolverStage2 &resolver, const GroupConstPtr &newGroup)
+ : m_resolver(resolver), m_oldGroup(resolver.m_currentGroup) {
+ resolver.m_currentGroup = newGroup;
+ }
+ ~GroupContextSwitcher() { m_resolver.m_currentGroup = m_oldGroup; }
+ private:
+ ProductResolverStage2 &m_resolver;
+ const GroupConstPtr m_oldGroup;
+ };
+ GroupContextSwitcher groupSwitcher(*this, group);
+ for (Item * const childItem : item->children())
+ resolveGroup(childItem);
+}
+
+SourceArtifactPtr ProductResolverStage2::createSourceArtifact(
+ const QString &fileName, const GroupPtr &group, bool wildcard,
+ const CodeLocation &filesLocation, ErrorInfo *errorInfo)
+{
+ const QString &baseDir = FileInfo::path(group->location.filePath());
+ const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName));
+ if (!wildcard && !FileInfo(absFilePath).exists()) {
+ if (errorInfo)
+ errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation);
+ m_product.product->missingSourceFiles << absFilePath;
+ return {};
+ }
+ if (group->enabled) {
+ CodeLocation &loc = m_sourceArtifactLocations[
+ std::make_pair(group->targetOfModule, absFilePath)];
+ if (loc.isValid()) {
+ if (errorInfo) {
+ errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath));
+ errorInfo->append(Tr::tr("First occurrence is here."), loc);
+ errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation);
+ }
+ return {};
+ }
+ loc = filesLocation;
+ }
+ SourceArtifactPtr artifact = SourceArtifactInternal::create();
+ artifact->absoluteFilePath = absFilePath;
+ artifact->fileTags = group->fileTags;
+ artifact->overrideFileTags = group->overrideTags;
+ artifact->properties = group->properties;
+ artifact->targetOfModule = group->targetOfModule;
+ artifact->fromWildcard = wildcard;
+ group->files.push_back(artifact);
+ return artifact;
+}
+
+static QualifiedIdSet propertiesToEvaluate(std::deque<QualifiedId> initialProps,
+ const PropertyDependencies &deps)
+{
+ std::deque<QualifiedId> remainingProps = std::move(initialProps);
+ QualifiedIdSet allProperties;
+ while (!remainingProps.empty()) {
+ const QualifiedId prop = remainingProps.front();
+ remainingProps.pop_front();
+ const auto insertResult = allProperties.insert(prop);
+ if (!insertResult.second)
+ continue;
+ transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; });
+ }
+ return allProperties;
+}
+
+QVariantMap ProductResolverStage2::resolveAdditionalModuleProperties(
+ const Item *group, const QVariantMap &currentValues)
+{
+ // Step 1: Retrieve the properties directly set in the group
+ const ModulePropertiesPerGroup &mp = m_product.modulePropertiesSetInGroups;
+ const auto it = mp.find(group);
+ if (it == mp.end())
+ return {};
+ const QualifiedIdSet &propsSetInGroup = it->second;
+
+ // Step 2: Gather all properties that depend on these properties.
+ const QualifiedIdSet &propsToEval = propertiesToEvaluate(
+ rangeTo<std::deque<QualifiedId>>(propsSetInGroup),
+ m_loaderState.evaluator().propertyDependencies());
+
+ // Step 3: Evaluate all these properties and replace their values in the map
+ QVariantMap modulesMap = currentValues;
+ QHash<QString, QStringList> propsPerModule;
+ for (auto fullPropName : propsToEval) {
+ const QString moduleName
+ = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString();
+ propsPerModule[moduleName] << fullPropName.last();
+ }
+ EvalCacheEnabler cachingEnabler(&m_loaderState.evaluator(),
+ m_product.product->sourceDirectory);
+ for (const Item::Module &module : group->modules()) {
+ const QString &fullModName = module.name.toString();
+ const QStringList propsForModule = propsPerModule.take(fullModName);
+ if (propsForModule.empty())
+ continue;
+ QVariantMap reusableValues = modulesMap.value(fullModName).toMap();
+ for (const QString &prop : std::as_const(propsForModule))
+ reusableValues.remove(prop);
+ modulesMap.insert(fullModName, m_propertiesEvaluator.evaluateProperties(
+ module.item, module.item, reusableValues, true, true));
+ }
+ return modulesMap;
+}
+
+static QString getLineAtLocation(const CodeLocation &loc, const QString &content)
+{
+ int pos = 0;
+ int currentLine = 1;
+ while (currentLine < loc.line()) {
+ while (content.at(pos++) != QLatin1Char('\n'))
+ ;
+ ++currentLine;
+ }
+ const int eolPos = content.indexOf(QLatin1Char('\n'), pos);
+ return content.mid(pos, eolPos - pos);
+}
+
+static bool usesImport(const ExportedProperty &prop, const QRegularExpression &regex)
+{
+ return prop.sourceCode.indexOf(regex) != -1;
+}
+
+static bool usesImport(const ExportedItem &item, const QRegularExpression &regex)
+{
+ return any_of(item.properties,
+ [regex](const ExportedProperty &p) { return usesImport(p, regex); })
+ || any_of(item.children,
+ [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); });
+}
+
+static bool usesImport(const ExportedModule &module, const QString &name)
+{
+ // Imports are used in three ways:
+ // (1) var f = new TextFile(...);
+ // (2) var path = FileInfo.joinPaths(...)
+ // (3) var obj = DataCollection;
+ const QString pattern = QStringLiteral("\\b%1\\b");
+
+ const QRegularExpression regex(pattern.arg(name)); // std::regex is much slower
+ return any_of(module.m_properties,
+ [regex](const ExportedProperty &p) { return usesImport(p, regex); })
+ || any_of(module.children,
+ [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); });
+}
+
+void ProductResolverStage2::resolveExport(Item *exportItem)
+{
+ ExportedModule &exportedModule = m_product.product->exportedModule;
+ setupExportedProperties(exportItem, QString(), exportedModule.m_properties);
+ static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) {
+ return p1.fullName < p2.fullName;
+ };
+ std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc);
+
+ transform(exportItem->children(), exportedModule.children,
+ [&exportedModule, this](const auto &child) {
+ return resolveExportChild(child, exportedModule); });
+
+ for (const JsImport &jsImport : exportItem->file()->jsImports()) {
+ if (usesImport(exportedModule, jsImport.scopeName)) {
+ exportedModule.importStatements << getLineAtLocation(jsImport.location,
+ exportItem->file()->content());
+ }
+ }
+ const auto builtInImports = JsExtensions::extensionNames();
+ for (const QString &builtinImport: builtInImports) {
+ if (usesImport(exportedModule, builtinImport))
+ exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport;
+ }
+ exportedModule.importStatements.sort();
+}
+
+// TODO: This probably wouldn't be necessary if we had item serialization.
+std::unique_ptr<ExportedItem> ProductResolverStage2::resolveExportChild(
+ const Item *item, const ExportedModule &module)
+{
+ std::unique_ptr<ExportedItem> exportedItem(new ExportedItem);
+
+ // This is the type of the built-in base item. It may turn out that we need to support
+ // derived items under Export. In that case, we probably need a new Item member holding
+ // the original type name.
+ exportedItem->name = item->typeName();
+
+ transform(item->children(), exportedItem->children, [&module, this](const auto &child) {
+ return resolveExportChild(child, module); });
+
+ setupExportedProperties(item, QString(), exportedItem->properties);
+ return exportedItem;
+}
+
+void ProductResolverStage2::setupExportedProperties(const Item *item, const QString &namePrefix,
+ std::vector<ExportedProperty> &properties)
+{
+ const auto &props = item->properties();
+ for (auto it = props.cbegin(); it != props.cend(); ++it) {
+ const QString qualifiedName = namePrefix.isEmpty()
+ ? it.key() : namePrefix + QLatin1Char('.') + it.key();
+ if ((item->type() == ItemType::Export || item->type() == ItemType::Properties)
+ && qualifiedName == StringConstants::prefixMappingProperty()) {
+ continue;
+ }
+ const ValuePtr &v = it.value();
+ if (v->type() == Value::ItemValueType) {
+ setupExportedProperties(std::static_pointer_cast<ItemValue>(v)->item(),
+ qualifiedName, properties);
+ continue;
+ }
+ ExportedProperty exportedProperty;
+ exportedProperty.fullName = qualifiedName;
+ exportedProperty.type = item->propertyDeclaration(it.key()).type();
+ if (v->type() == Value::VariantValueType) {
+ exportedProperty.sourceCode = toJSLiteral(
+ std::static_pointer_cast<VariantValue>(v)->value());
+ } else {
+ QBS_CHECK(v->type() == Value::JSSourceValueType);
+ const JSSourceValue * const sv = static_cast<JSSourceValue *>(v.get());
+ exportedProperty.sourceCode = sv->sourceCode().toString();
+ }
+ const ItemDeclaration itemDecl
+ = BuiltinDeclarations::instance().declarationsForType(item->type());
+ PropertyDeclaration propertyDecl;
+ const auto itemProperties = itemDecl.properties();
+ for (const PropertyDeclaration &decl : itemProperties) {
+ if (decl.name() == it.key()) {
+ propertyDecl = decl;
+ exportedProperty.isBuiltin = true;
+ break;
+ }
+ }
+
+ // Do not add built-in properties that were left at their default value.
+ if (!exportedProperty.isBuiltin
+ || m_loaderState.evaluator().isNonDefaultValue(item, it.key())) {
+ properties.push_back(exportedProperty);
+ }
+ }
+
+ // Order the list of properties, so the output won't look so random.
+ static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool {
+ const int p1ComponentCount = p1.fullName.count(QLatin1Char('.'));
+ const int p2ComponentCount = p2.fullName.count(QLatin1Char('.'));
+ if (p1.isBuiltin && !p2.isBuiltin)
+ return true;
+ if (!p1.isBuiltin && p2.isBuiltin)
+ return false;
+ if (p1ComponentCount < p2ComponentCount)
+ return true;
+ if (p1ComponentCount > p2ComponentCount)
+ return false;
+ return p1.fullName < p2.fullName;
+ };
+ std::sort(properties.begin(), properties.end(), less);
+}
+
+QVariantMap ProductResolverStage2::evaluateModuleValues(Item *item, bool lookupPrototype)
+{
+ QVariantMap moduleValues;
+ for (const Item::Module &module : item->modules()) {
+ if (!module.item->isPresentModule())
+ continue;
+ const QString fullName = module.name.toString();
+ moduleValues[fullName] = m_propertiesEvaluator.evaluateProperties(
+ module.item, lookupPrototype, true);
+ }
+ return moduleValues;
+}
+
+void ProductResolverStage2::resolveScanner(Item *item, ModuleContext &moduleContext)
+{
+ Evaluator &evaluator = m_loaderState.evaluator();
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty())) {
+ qCDebug(lcProjectResolver) << "scanner condition is false";
+ return;
+ }
+
+ ResolvedScannerPtr scanner = ResolvedScanner::create();
+ scanner->module = moduleContext.module;
+ scanner->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
+ scanner->recursive = evaluator.boolValue(item, StringConstants::recursiveProperty());
+ scanner->searchPathsScript.initialize(m_loaderState.topLevelProject().scriptFunctionValue(
+ item, StringConstants::searchPathsProperty()));
+ scanner->scanScript.initialize(m_loaderState.topLevelProject().scriptFunctionValue(
+ item, StringConstants::scanProperty()));
+ m_product.product->scanners.push_back(scanner);
+}
+
+void ProductResolverStage2::resolveModules()
+{
+ JobLimits jobLimits;
+ for (const Item::Module &m : m_product.item->modules())
+ resolveModule(m.name, m.item, m.product, m.parameters, jobLimits);
+ for (int i = 0; i < jobLimits.count(); ++i) {
+ const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i);
+ if (m_product.product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
+ m_product.product->jobLimits.setJobLimit(moduleJobLimit);
+ }
+}
+
+void ProductResolverStage2::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
+ const QVariantMap &parameters, JobLimits &jobLimits)
+{
+ if (!item->isPresentModule())
+ return;
+
+ ModuleContext moduleContext;
+ moduleContext.module = ResolvedModule::create();
+
+ const ResolvedModulePtr &module = moduleContext.module;
+ module->name = moduleName.toString();
+ module->isProduct = isProduct;
+ module->product = m_product.product.get();
+ module->setupBuildEnvironmentScript.initialize(m_loaderState.topLevelProject()
+ .scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty()));
+ module->setupRunEnvironmentScript.initialize(m_loaderState.topLevelProject()
+ .scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty()));
+
+ for (const Item::Module &m : item->modules()) {
+ if (m.item->isPresentModule())
+ module->moduleDependencies += m.name.toString();
+ }
+
+ m_product.product->modules.push_back(module);
+ if (!parameters.empty())
+ m_product.product->moduleParameters[module] = parameters;
+
+ for (Item *child : item->children()) {
+ switch (child->type()) {
+ case ItemType::Rule:
+ resolveRule(m_loaderState, child, nullptr, &m_product, &moduleContext);
+ break;
+ case ItemType::FileTagger:
+ resolveFileTagger(m_loaderState, child, nullptr, &m_product);
+ break;
+ case ItemType::JobLimit:
+ resolveJobLimit(m_loaderState, child, nullptr, nullptr, &moduleContext);
+ break;
+ case ItemType::Scanner:
+ resolveScanner(child, moduleContext);
+ break;
+ default:
+ break;
+ }
+ }
+ for (int i = 0; i < moduleContext.jobLimits.count(); ++i) {
+ const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i);
+ const int oldLimit = jobLimits.getLimit(newJobLimit.pool());
+ if (oldLimit == -1 || oldLimit > newJobLimit.limit())
+ jobLimits.setJobLimit(newJobLimit);
+ }
+}
+
+void ProductResolverStage2::applyFileTaggers()
+{
+ m_product.product->fileTaggers << m_product.project->fileTaggers;
+ m_product.product->fileTaggers = sorted(m_product.product->fileTaggers,
+ [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) {
+ return a->priority() > b->priority();
+ });
+ for (const SourceArtifactPtr &artifact : m_product.product->allEnabledFiles()) {
+ if (!artifact->overrideFileTags || artifact->fileTags.empty()) {
+ const QString fileName = FileInfo::fileName(artifact->absoluteFilePath);
+ const FileTags fileTags = m_product.product->fileTagsForFileName(fileName);
+ artifact->fileTags.unite(fileTags);
+ if (artifact->fileTags.empty())
+ artifact->fileTags.insert(unknownFileTag());
+ qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags
+ << "to" << fileName;
+ }
+ }
+}
+
+void ProductResolverStage2::finalizeArtifactProperties()
+{
+ for (const SourceArtifactPtr &artifact : m_product.product->allEnabledFiles()) {
+ for (const auto &artifactProperties : m_product.product->artifactProperties) {
+ if (!artifact->isTargetOfModule()
+ && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) {
+ // FIXME: Should be merged, not overwritten.
+ artifact->properties = artifactProperties->propertyMap();
+ }
+ }
+
+ // Let a positive value of qbs.install imply the file tag "installable".
+ if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool())
+ artifact->fileTags += "installable";
+ }
+}
+
+void ProductResolverStage2::collectProductDependencies()
+{
+ const ResolvedProductPtr &product = m_product.product;
+ if (!product)
+ return;
+ for (const Item::Module &module : m_product.item->modules()) {
+ if (!module.product)
+ continue;
+ const ResolvedProductPtr &dep = module.product->product;
+ QBS_CHECK(dep);
+ QBS_CHECK(dep != product);
+ product->dependencies << dep;
+ product->dependencyParameters.insert(dep, module.parameters); // TODO: Streamline this with normal module dependencies?
+ }
+
+ // TODO: We might want to keep the topological sorting and get rid of "module module dependencies".
+ std::sort(product->dependencies.begin(),product->dependencies.end(),
+ [](const ResolvedProductPtr &p1, const ResolvedProductPtr &p2) {
+ return p1->fullDisplayName() < p2->fullDisplayName();
+ });
+}
+
+void ExportsResolver::start()
+{
+ resolveShadowProduct();
+ collectExportedProductDependencies();
+}
+
+void ExportsResolver::resolveShadowProduct()
+{
+ if (!m_product.product->enabled)
+ return;
+ if (!m_product.shadowProduct)
+ return;
+ for (const auto &m : m_product.shadowProduct->item->modules()) {
+ if (m.name.toString() != m_product.product->name)
+ continue;
+ collectPropertiesForExportItem(m.item);
+ for (const auto &dep : m.item->modules())
+ collectPropertiesForModuleInExportItem(dep);
+ break;
+ }
+ try {
+ adaptExportedPropertyValues();
+ } catch (const ErrorInfo &) {}
+}
+
+class TempScopeSetter
+{
+public:
+ TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope())
+ {
+ value->setScope(newScope, {});
+ }
+ ~TempScopeSetter() { if (m_value) m_value->setScope(m_oldScope, {}); }
+
+ TempScopeSetter(const TempScopeSetter &) = delete;
+ TempScopeSetter &operator=(const TempScopeSetter &) = delete;
+ TempScopeSetter &operator=(TempScopeSetter &&) = delete;
+
+ TempScopeSetter(TempScopeSetter &&other) noexcept
+ : m_value(std::move(other.m_value)), m_oldScope(other.m_oldScope)
+ {
+ other.m_value.reset();
+ other.m_oldScope = nullptr;
+ }
+
+private:
+ ValuePtr m_value;
+ Item *m_oldScope;
+};
+
+void ExportsResolver::collectPropertiesForExportItem(
+ const QualifiedId &moduleName, const ValuePtr &value, Item *moduleInstance,
+ QVariantMap &moduleProps)
+{
+ QBS_CHECK(value->type() == Value::ItemValueType);
+ Item * const itemValueItem = std::static_pointer_cast<ItemValue>(value)->item();
+ if (itemValueItem->propertyDeclarations().isEmpty()) {
+ for (const Item::Module &module : moduleInstance->modules()) {
+ if (module.name == moduleName) {
+ itemValueItem->setPropertyDeclarations(module.item->propertyDeclarations());
+ break;
+ }
+ }
+ }
+ if (itemValueItem->type() == ItemType::ModuleInstancePlaceholder) {
+ struct EvalPreparer {
+ EvalPreparer(Item *valueItem, const QualifiedId &moduleName)
+ : valueItem(valueItem),
+ hadName(!!valueItem->variantProperty(StringConstants::nameProperty()))
+ {
+ if (!hadName) {
+ // Evaluator expects a name here.
+ valueItem->setProperty(StringConstants::nameProperty(),
+ VariantValue::create(moduleName.toString()));
+ }
+ }
+ ~EvalPreparer()
+ {
+ if (!hadName)
+ valueItem->removeProperty(StringConstants::nameProperty());
+ }
+ Item * const valueItem;
+ const bool hadName;
+ };
+ EvalPreparer ep(itemValueItem, moduleName);
+ std::vector<TempScopeSetter> tss;
+ for (const ValuePtr &v : itemValueItem->properties())
+ tss.emplace_back(v, moduleInstance);
+ moduleProps.insert(moduleName.toString(), m_propertiesEvaluator.evaluateProperties(
+ itemValueItem, false, false));
+ return;
+ }
+ QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix);
+ const Item::PropertyMap &props = itemValueItem->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ QualifiedId fullModuleName = moduleName;
+ fullModuleName << it.key();
+ collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps);
+ }
+}
+
+void ExportsResolver::collectPropertiesForExportItem(Item *productModuleInstance)
+{
+ if (!productModuleInstance->isPresentModule())
+ return;
+ Item * const exportItem = productModuleInstance->prototype();
+ QBS_CHECK(exportItem);
+ QBS_CHECK(exportItem->type() == ItemType::Export);
+ const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance()
+ .declarationsForType(ItemType::Export).properties();
+ ExportedModule &exportedModule = m_product.product->exportedModule;
+ const auto &props = exportItem->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ const auto match
+ = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); };
+ if (it.key() != StringConstants::prefixMappingProperty() &&
+ std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) {
+ continue;
+ }
+ if (it.value()->type() == Value::ItemValueType) {
+ collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance,
+ exportedModule.modulePropertyValues);
+ } else {
+ TempScopeSetter tss(it.value(), productModuleInstance);
+ m_propertiesEvaluator.evaluateProperty(
+ exportItem, it.key(), it.value(), exportedModule.propertyValues, false);
+ }
+ }
+}
+
+// Collects module properties assigned to in other (higher-level) modules.
+void ExportsResolver::collectPropertiesForModuleInExportItem(const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+ ExportedModule &exportedModule = m_product.product->exportedModule;
+ if (module.product || module.name.first() == StringConstants::qbsModule())
+ return;
+ const auto checkName = [module](const ExportedModuleDependency &d) {
+ return module.name.toString() == d.name;
+ };
+ if (any_of(exportedModule.moduleDependencies, checkName))
+ return;
+
+ Item *modulePrototype = module.item->prototype();
+ while (modulePrototype && modulePrototype->type() != ItemType::Module)
+ modulePrototype = modulePrototype->prototype();
+ if (!modulePrototype) // Can happen for broken products in relaxed mode.
+ return;
+ ModuleItemLocker locker(*modulePrototype);
+ const Item::PropertyMap &props = modulePrototype->properties();
+ ExportedModuleDependency dep;
+ dep.name = module.name.toString();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ if (it.value()->type() == Value::ItemValueType)
+ collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties);
+ }
+ exportedModule.moduleDependencies.push_back(dep);
+
+ for (const auto &dep : module.item->modules())
+ collectPropertiesForModuleInExportItem(dep);
+}
+
+void ExportsResolver::adaptExportedPropertyValues()
+{
+ QBS_CHECK(m_product.shadowProduct);
+ ExportedModule &m = m_product.product->exportedModule;
+ const QVariantList prefixList = m.propertyValues.take(
+ StringConstants::prefixMappingProperty()).toList();
+ const QString shadowProductName = m_loaderState.evaluator().stringValue(
+ m_product.shadowProduct->item, StringConstants::nameProperty());
+ const QString shadowProductBuildDir = m_loaderState.evaluator().stringValue(
+ m_product.shadowProduct->item, StringConstants::buildDirectoryProperty());
+ QVariantMap prefixMap;
+ for (const QVariant &v : prefixList) {
+ const QVariantMap o = v.toMap();
+ prefixMap.insert(o.value(QStringLiteral("prefix")).toString(),
+ o.value(QStringLiteral("replacement")).toString());
+ }
+ const auto valueRefersToImportingProduct
+ = [shadowProductName, shadowProductBuildDir](const QString &value) {
+ return value.toLower().contains(shadowProductName.toLower())
+ || value.contains(shadowProductBuildDir);
+ };
+ static const auto stringMapper = [](const QVariantMap &mappings, const QString &value)
+ -> QString {
+ for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) {
+ if (value.startsWith(it.key()))
+ return it.value().toString() + value.mid(it.key().size());
+ }
+ return value;
+ };
+ const auto stringListMapper = [&valueRefersToImportingProduct](
+ const QVariantMap &mappings, const QStringList &value) -> QStringList {
+ QStringList result;
+ result.reserve(value.size());
+ for (const QString &s : value) {
+ if (!valueRefersToImportingProduct(s))
+ result.push_back(stringMapper(mappings, s));
+ }
+ return result;
+ };
+ const std::function<QVariant(const QVariantMap &, const QVariant &)> mapper
+ = [&stringListMapper, &mapper](
+ const QVariantMap &mappings, const QVariant &value) -> QVariant {
+ switch (static_cast<QMetaType::Type>(value.userType())) {
+ case QMetaType::QString:
+ return stringMapper(mappings, value.toString());
+ case QMetaType::QStringList:
+ return stringListMapper(mappings, value.toStringList());
+ case QMetaType::QVariantMap: {
+ QVariantMap m = value.toMap();
+ for (auto it = m.begin(); it != m.end(); ++it)
+ it.value() = mapper(mappings, it.value());
+ return m;
+ }
+ default:
+ return value;
+ }
+ };
+ for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it)
+ it.value() = mapper(prefixMap, it.value());
+ for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it)
+ it.value() = mapper(prefixMap, it.value());
+ for (ExportedModuleDependency &dep : m.moduleDependencies) {
+ for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it)
+ it.value() = mapper(prefixMap, it.value());
+ }
+}
+
+void ExportsResolver::collectExportedProductDependencies()
+{
+ if (!m_product.shadowProduct)
+ return;
+ const ResolvedProductPtr exportingProduct = m_product.product;
+ if (!exportingProduct || !exportingProduct->enabled)
+ return;
+ Item * const importingProductItem = m_product.shadowProduct->item;
+
+ std::vector<std::pair<ResolvedProductPtr, QVariantMap>> directDeps;
+ for (const Item::Module &m : importingProductItem->modules()) {
+ if (m.name.toString() != exportingProduct->name)
+ continue;
+ for (const Item::Module &dep : m.item->modules()) {
+ if (dep.product)
+ directDeps.emplace_back(dep.product->product, m.parameters);
+ }
+ }
+ for (const auto &dep : directDeps) {
+ if (!contains(exportingProduct->exportedModule.productDependencies,
+ dep.first->uniqueName())) {
+ exportingProduct->exportedModule.productDependencies.push_back(
+ dep.first->uniqueName());
+ }
+ if (!dep.second.isEmpty()) {
+ exportingProduct->exportedModule.dependencyParameters.insert(dep.first,
+ dep.second);
+ }
+ }
+ auto &productDeps = exportingProduct->exportedModule.productDependencies;
+ std::sort(productDeps.begin(), productDeps.end());
+}
+
+QVariantMap PropertiesEvaluator::evaluateProperties(
+ const Item *item, const Item *propertiesContainer, const QVariantMap &tmplt,
+ bool lookupPrototype, bool checkErrors)
+{
+ AccumulatingTimer propEvalTimer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyEvaluation
+ : nullptr);
+ QVariantMap result = tmplt;
+ for (auto it = propertiesContainer->properties().begin();
+ it != propertiesContainer->properties().end(); ++it) {
+ evaluateProperty(item, it.key(), it.value(), result, checkErrors);
+ }
+ return lookupPrototype && propertiesContainer->prototype()
+ && propertiesContainer->prototype()->type() != ItemType::Module
+ ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors)
+ : result;
+}
+
+QVariantMap PropertiesEvaluator::evaluateProperties(Item *item, bool lookupPrototype,
+ bool checkErrors)
+{
+ const QVariantMap tmplt;
+ return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors);
+}
+
+void PropertiesEvaluator::evaluateProperty(
+ const Item *item, const QString &propName, const ValuePtr &propValue, QVariantMap &result,
+ bool checkErrors)
+{
+ JSContext * const ctx = m_loaderState.evaluator().engine()->context();
+ switch (propValue->type()) {
+ case Value::ItemValueType:
+ {
+ // Ignore items. Those point to module instances
+ // and are handled in evaluateModuleValues().
+ break;
+ }
+ case Value::JSSourceValueType:
+ {
+ if (result.contains(propName))
+ break;
+ const PropertyDeclaration pd = item->propertyDeclaration(propName);
+ if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) {
+ break;
+ }
+ const ScopedJsValue scriptValue(ctx, m_loaderState.evaluator().property(item, propName));
+ if (JsException ex = m_loaderState.evaluator().engine()->checkAndClearException(
+ propValue->location())) {
+ if (checkErrors)
+ throw ex.toErrorInfo();
+ }
+
+ // NOTE: Loses type information if scriptValue.isUndefined == true,
+ // as such QScriptValues become invalid QVariants.
+ QVariant v;
+ if (JS_IsFunction(ctx, scriptValue)) {
+ v = getJsString(ctx, scriptValue);
+ } else {
+ v = getJsVariant(ctx, scriptValue);
+ QVariantMap m = v.toMap();
+ if (m.contains(StringConstants::importScopeNamePropertyInternal())) {
+ QVariantMap tmp = m;
+ const ScopedJsValue proto(ctx, JS_GetPrototype(ctx, scriptValue));
+ m = getJsVariant(ctx, proto).toMap();
+ for (auto it = tmp.begin(); it != tmp.end(); ++it)
+ m.insert(it.key(), it.value());
+ v = m;
+ }
+ }
+
+ if (pd.type() == PropertyDeclaration::Path && v.isValid()) {
+ v = v.toString();
+ } else if (pd.type() == PropertyDeclaration::PathList
+ || pd.type() == PropertyDeclaration::StringList) {
+ v = v.toStringList();
+ } else if (pd.type() == PropertyDeclaration::VariantList) {
+ v = v.toList();
+ }
+
+ // Enforce proper type for undefined values (note that path degrades to string).
+ if (!v.isValid())
+ v = pd.typedNullValue();
+
+ pd.checkAllowedValues(v, propValue->location(), propName, m_loaderState);
+
+ result[propName] = v;
+ break;
+ }
+ case Value::VariantValueType:
+ {
+ if (result.contains(propName))
+ break;
+ VariantValuePtr vvp = std::static_pointer_cast<VariantValue>(propValue);
+ QVariant v = vvp->value();
+
+ const PropertyDeclaration pd = item->propertyDeclaration(propName);
+ if (v.isNull() && !pd.isScalar()) // QTBUG-51237
+ v = QStringList();
+
+ pd.checkAllowedValues(v, propValue->location(), propName, m_loaderState);
+ result[propName] = v;
+ break;
+ }
+ }
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productshandler.h b/src/lib/corelib/loader/productresolver.h
index f1fc74a20..34c49b2bb 100644
--- a/src/lib/corelib/loader/productshandler.h
+++ b/src/lib/corelib/loader/productresolver.h
@@ -39,28 +39,19 @@
#pragma once
-#include <tools/pimpl.h>
-
namespace qbs::Internal {
+enum class Deferral;
class LoaderState;
+class ProductContext;
// Responsibilities:
// - Resolving dependencies to modules and other products (via DependenciesResolver).
// - Module validation.
// - Running probes (via ProbesResolver) in Product and Module items.
-// - Preparing Group items for property evaluation.
-class ProductsHandler
-{
-public:
- ProductsHandler(LoaderState &loaderState);
- ~ProductsHandler();
-
- void run();
- void printProfilingInfo(int indent);
+// - Evaluating product and module properties.
+// - Handling all Product child items, such as FileTaggers, Rules and so on.
+void resolveProduct(ProductContext &product, Deferral deferral, LoaderState &loaderState);
-private:
- class Private;
- Pimpl<Private> d;
-};
+void setupExports(ProductContext &product, LoaderState &loaderState);
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productscollector.cpp b/src/lib/corelib/loader/productscollector.cpp
index 95b248c73..042dbd160 100644
--- a/src/lib/corelib/loader/productscollector.cpp
+++ b/src/lib/corelib/loader/productscollector.cpp
@@ -73,9 +73,11 @@ class ProductsCollector::Private
public:
Private(LoaderState &loaderState) : loaderState(loaderState) {}
- void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths);
+ void handleProject(Item *projectItem, ProjectContext *parentProject,
+ const Set<QString> &referencedFilePaths);
QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem);
- void prepareProduct(ProjectContext &projectContext, Item *productItem);
+ void prepareProduct(ProjectContext &projectContext, Item *productItem,
+ ProductContext *mainProduct = nullptr);
void handleSubProject(ProjectContext &projectContext, Item *projectItem,
const Set<QString> &referencedFilePaths);
void copyProperties(const Item *sourceProject, Item *targetProject);
@@ -86,17 +88,18 @@ public:
bool checkExportItemCondition(Item *exportItem, const ProductContext &product);
void initProductProperties(const ProductContext &product);
void checkProjectNamesInOverrides();
- void collectProductsByName();
+ void collectProductsByNameAndItem();
void checkProductNamesInOverrides();
+ void mergeProperty(Item *dst, const QString &name, const ValuePtr &value);
LoaderState &loaderState;
Settings settings{loaderState.parameters().settingsDirectory()};
Set<QString> disabledProjects;
Version qbsVersion;
Item *tempScopeItem = nullptr;
- qint64 elapsedTimePrepareProducts = 0;
private:
+ // TODO: Put this in loaderutils
class TempBaseModuleAttacher {
public:
TempBaseModuleAttacher(Private *d, ProductContext &product);
@@ -118,24 +121,15 @@ ProductsCollector::~ProductsCollector() = default;
void ProductsCollector::run(Item *rootProject)
{
- d->handleProject(rootProject, {QDir::cleanPath(d->loaderState.parameters().projectFilePath())});
+ d->handleProject(rootProject, nullptr, {rootProject->file()->filePath()});
d->checkProjectNamesInOverrides();
- d->collectProductsByName();
+ d->collectProductsByNameAndItem();
d->checkProductNamesInOverrides();
+ d->loaderState.topLevelProject().checkForLocalProfileAsTopLevelProfile(
+ d->loaderState.parameters().topLevelProfile());
}
-void ProductsCollector::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Preparing products took %1.")
- .arg(elapsedTimeString(d->elapsedTimePrepareProducts));
-}
-
-void ProductsCollector::Private::handleProject(Item *projectItem,
+void ProductsCollector::Private::handleProject(Item *projectItem, ProjectContext *parentProject,
const Set<QString> &referencedFilePaths)
{
const SetupProjectParameters &parameters = loaderState.parameters();
@@ -146,9 +140,11 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
auto p = std::make_unique<ProjectContext>();
auto &projectContext = *p;
+ projectContext.item = projectItem;
+ projectContext.parent = parentProject;
projectContext.topLevelProject = &topLevelProject;
ItemValuePtr itemValue = ItemValue::create(projectItem);
- projectContext.scope = Item::create(projectItem->pool(), ItemType::Scope);
+ projectContext.scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
projectContext.scope->setFile(projectItem->file());
projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
ProductContext dummyProduct;
@@ -156,7 +152,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
dummyProduct.moduleProperties = parameters.finalBuildConfigurationTree();
dummyProduct.profileModuleProperties = dummyProduct.moduleProperties;
dummyProduct.profileName = parameters.topLevelProfile();
- loaderState.dependenciesResolver().loadBaseModule(dummyProduct, projectItem);
+ loadBaseModule(dummyProduct, projectItem, loaderState);
projectItem->overrideProperties(parameters.overriddenValuesTree(),
StringConstants::projectPrefix(), parameters, logger);
@@ -166,6 +162,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
projectItem->setProperty(StringConstants::nameProperty(),
VariantValue::create(projectContext.name));
}
+ if (parentProject)
+ parentProject->children.push_back(p.get());
+ topLevelProject.addProject(p.release());
projectItem->overrideProperties(parameters.overriddenValuesTree(),
StringConstants::projectsOverridePrefix() + projectContext.name,
parameters, logger);
@@ -173,11 +172,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
disabledProjects.insert(projectContext.name);
return;
}
- topLevelProject.projects.push_back(p.release());
SearchPathsManager searchPathsManager(itemReader, itemReader.readExtraSearchPaths(projectItem)
<< projectItem->file()->dirPath());
projectContext.searchPathsStack = itemReader.extraSearchPathsStack();
- projectContext.item = projectItem;
const QString minVersionStr
= evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(),
@@ -199,10 +196,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
for (Item * const child : projectItem->children())
child->setScope(projectContext.scope);
- projectContext.topLevelProject->probes << loaderState.probesResolver().resolveProbes(
- {dummyProduct.name, dummyProduct.uniqueName()}, projectItem);
+ ProbesResolver(loaderState).resolveProbes(dummyProduct, projectItem);
- loaderState.localProfiles().collectProfilesFromItems(projectItem, projectContext.scope);
+ collectProfilesFromItems(projectItem, projectContext.scope, loaderState);
QList<Item *> multiplexedProducts;
for (Item * const child : projectItem->children()) {
@@ -223,7 +219,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
break;
case ItemType::Project:
copyProperties(projectItem, child);
- handleProject(child, referencedFilePaths);
+ handleProject(child, &projectContext, referencedFilePaths);
break;
default:
break;
@@ -253,7 +249,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
break;
case ItemType::Project:
copyProperties(projectItem, subItem);
- handleProject(subItem,
+ handleProject(subItem, &projectContext,
Set<QString>(referencedFilePaths) << subItem->file()->filePath());
break;
default:
@@ -278,28 +274,36 @@ QList<Item *> ProductsCollector::Private::multiplexProductItem(ProductContext &d
loaderState.parameters(), loaderState.logger());
dummyContext.item = productItem;
TempBaseModuleAttacher tbma(this, dummyContext);
- return loaderState.multiplexer().multiplex(productName, productItem, tbma.tempBaseModuleItem(),
- [&] { tbma.drop(); });
+ return multiplex(productName, productItem, tbma.tempBaseModuleItem(),
+ [&] { tbma.drop(); }, loaderState);
}
-void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem)
+void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem,
+ ProductContext *mainProduct)
{
const SetupProjectParameters &parameters = loaderState.parameters();
Evaluator &evaluator = loaderState.evaluator();
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- AccumulatingTimer timer(parameters.logElapsedTime() ? &elapsedTimePrepareProducts : nullptr);
- topLevelProject.checkCancelation(parameters);
+ AccumulatingTimer timer(parameters.logElapsedTime()
+ ? &topLevelProject.timingData().preparingProducts : nullptr);
+ topLevelProject.checkCancelation();
qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
- ProductContext productContext;
+ if (mainProduct)
+ mainProduct->shadowProduct = std::make_unique<ProductContext>();
+ else
+ projectContext.products.emplace_back();
+ ProductContext &productContext = mainProduct
+ ? *mainProduct->shadowProduct : projectContext.products.back();
productContext.item = productItem;
productContext.project = &projectContext;
// Retrieve name, profile and multiplex id.
productContext.name = evaluator.stringValue(productItem, StringConstants::nameProperty());
QBS_CHECK(!productContext.name.isEmpty());
- const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule());
+ const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule(),
+ loaderState.itemPool());
if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
TempBaseModuleAttacher tbma(this, productContext);
productContext.profileName = evaluator.stringValue(
@@ -312,11 +316,11 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
QBS_CHECK(!productContext.profileName.isEmpty());
// Set up full module property map based on the profile.
- const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName);
- QVariantMap flatConfig;
- if (it == topLevelProject.profileConfigs.constEnd()) {
+ std::optional<QVariantMap> flatConfig
+ = topLevelProject.profileConfig(productContext.profileName);
+ if (!flatConfig) {
const Profile profile(productContext.profileName, &settings,
- loaderState.localProfiles().profiles());
+ loaderState.topLevelProject().localProfiles());
if (!profile.exists()) {
ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
productItem->location());
@@ -325,37 +329,50 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
}
flatConfig = SetupProjectParameters::expandedBuildConfiguration(
profile, parameters.configurationName());
- topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig);
- } else {
- flatConfig = it.value().toMap();
+ topLevelProject.addProfileConfig(productContext.profileName, *flatConfig);
}
productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- flatConfig, {});
+ *flatConfig, {});
productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- flatConfig, parameters.overriddenValues());
+ *flatConfig, parameters.overriddenValues());
initProductProperties(productContext);
// Set up product scope. This is mainly for using the "product" and "project"
// variables in some contexts.
ItemValuePtr itemValue = ItemValue::create(productItem);
- productContext.scope = Item::create(productItem->pool(), ItemType::Scope);
+ productContext.scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
productContext.scope->setProperty(StringConstants::productVar(), itemValue);
productContext.scope->setFile(productItem->file());
productContext.scope->setScope(productContext.project->scope);
+ // If there are any child items with an id, set up a scope for them. This is mostly
+ // relevant for Probe items. While we might get away with using the file's id scope
+ // in the absence of multiplexing, having a proper per-product scope seems cleaner.
+ QBS_CHECK(productItem->scope());
+ QBS_CHECK(productItem->scope() == productContext.project->scope);
+ for (Item * const child : productItem->children()) {
+ if (child->id().isEmpty())
+ continue;
+ if (productItem->scope() == productContext.project->scope) {
+ productItem->setScope(Item::create(&loaderState.itemPool(), ItemType::Scope));
+ productItem->scope()->setScope(productContext.project->scope);
+ }
+ const ItemValuePtr childValue = ItemValue::create(child);
+ productItem->scope()->setProperty(child->id(), childValue);
+ productContext.scope->setProperty(child->id(), childValue);
+ }
+
const bool hasExportItems = mergeExportItems(productContext);
setScopeForDescendants(productItem, productContext.scope);
- projectContext.products.push_back(productContext);
-
- if (!hasExportItems || getShadowProductInfo(productContext).first)
+ if (!hasExportItems)
return;
// This "shadow product" exists only to pull in a dependency on the actual product
// and nothing else, thus providing us with the pure environment that we need to
// evaluate the product's exported properties in isolation in the project resolver.
- Item * const importer = Item::create(productItem->pool(), ItemType::Product);
+ Item * const importer = Item::create(&loaderState.itemPool(), ItemType::Product);
importer->setProperty(QStringLiteral("name"),
VariantValue::create(StringConstants::shadowProductPrefix()
+ productContext.name));
@@ -363,15 +380,16 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
importer->setLocation(productItem->location());
importer->setScope(projectContext.scope);
importer->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger());
- Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
+ Item * const dependsItem = Item::create(&loaderState.itemPool(), ItemType::Depends);
dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name));
dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false));
dependsItem->setFile(importer->file());
dependsItem->setLocation(importer->location());
dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger());
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(productContext.multiplexConfigurationId));
Item::addChild(importer, dependsItem);
- Item::addChild(productItem, importer);
- prepareProduct(projectContext, importer);
+ prepareProduct(projectContext, importer, &productContext);
}
void ProductsCollector::Private::handleSubProject(
@@ -426,7 +444,7 @@ void ProductsCollector::Private::handleSubProject(
Item::addChild(projectItem, loadedItem);
projectItem->setScope(projectContext.scope);
- handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath);
+ handleProject(loadedItem, &projectContext, Set<QString>(referencedFilePaths) << subProjectFilePath);
}
void ProductsCollector::Private::copyProperties(const Item *sourceProject, Item *targetProject)
@@ -507,18 +525,19 @@ QList<Item *> ProductsCollector::Private::loadReferencedFile(
QList<Item *> loadedItems;
loadedItems << subItem;
if (subItem->type() == ItemType::Product) {
- loaderState.localProfiles().collectProfilesFromItems(subItem, dummyContext.project->scope);
+ collectProfilesFromItems(subItem, dummyContext.project->scope, loaderState);
loadedItems << multiplexProductItem(dummyContext, subItem);
}
return loadedItems;
}
-static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
+void ProductsCollector::Private::mergeProperty(Item *dst, const QString &name,
+ const ValuePtr &value)
{
if (value->type() == Value::ItemValueType) {
const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value);
const Item * const valueItem = itemValue->item();
- Item * const subItem = dst->itemProperty(name, itemValue)->item();
+ Item * const subItem = dst->itemProperty(name, itemValue, loaderState.itemPool())->item();
for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it)
mergeProperty(subItem, it.key(), it.value());
return;
@@ -533,7 +552,7 @@ static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
if (baseValue) {
QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
- baseValue->clone());
+ baseValue->clone(loaderState.itemPool()));
jsValue->setBaseValue(jsBaseValue);
std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
jsValue->clearAlternatives();
@@ -566,14 +585,14 @@ bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext
if (!exportItems.empty())
productContext.item->setChildren(children);
- Item *merged = Item::create(productContext.item->pool(), ItemType::Export);
+ Item *merged = Item::create(&loaderState.itemPool(), ItemType::Export);
const QString &nameKey = StringConstants::nameProperty();
const ValuePtr nameValue = VariantValue::create(productContext.name);
merged->setProperty(nameKey, nameValue);
Set<FileContextConstPtr> filesWithExportItem;
QVariantMap defaultParameters;
for (Item * const exportItem : exportItems) {
- topLevelProject.checkCancelation(parameters);
+ topLevelProject.checkCancelation();
if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
exportItem->location());
@@ -584,9 +603,9 @@ bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext
for (Item * const child : exportItem->children()) {
if (child->type() == ItemType::Parameters) {
adjustParametersScopes(child, child);
- mergeParameters(defaultParameters,
- getJsVariant(evaluator.engine()->context(),
- evaluator.scriptValue(child)).toMap());
+ defaultParameters = mergeDependencyParameters(defaultParameters,
+ getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(child)).toMap());
} else {
Item::addChild(merged, child);
}
@@ -626,11 +645,11 @@ bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem,
{
class ScopeHandler {
public:
- ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
- : m_exportItem(exportItem)
+ ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem,
+ ItemPool &itemPool) : m_exportItem(exportItem), m_itemPool(itemPool)
{
if (!*cachedScopeItem)
- *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
+ *cachedScopeItem = Item::create(&m_itemPool, ItemType::Scope);
Item * const scope = *cachedScopeItem;
QBS_CHECK(productContext.item->file());
scope->setFile(productContext.item->file());
@@ -644,7 +663,8 @@ bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem,
private:
Item * const m_exportItem;
- } scopeHandler(exportItem, product, &tempScopeItem);
+ ItemPool &m_itemPool;
+ } scopeHandler(exportItem, product, &tempScopeItem, loaderState.itemPool());
return loaderState.topLevelProject().checkItemCondition(exportItem, loaderState.evaluator());
}
@@ -652,7 +672,7 @@ void ProductsCollector::Private::initProductProperties(const ProductContext &pro
{
QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
product.multiplexConfigurationId);
- buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
+ buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory(), buildDir);
product.item->setProperty(StringConstants::buildDirectoryProperty(),
VariantValue::create(buildDir));
const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath();
@@ -663,10 +683,10 @@ void ProductsCollector::Private::initProductProperties(const ProductContext &pro
void ProductsCollector::Private::checkProjectNamesInOverrides()
{
for (const QString &projectNameInOverride
- : loaderState.topLevelProject().projectNamesUsedInOverrides) {
+ : loaderState.topLevelProject().projectNamesUsedInOverrides()) {
if (disabledProjects.contains(projectNameInOverride))
continue;
- if (!any_of(loaderState.topLevelProject().projects,
+ if (!any_of(loaderState.topLevelProject().projects(),
[&projectNameInOverride](const ProjectContext *p) {
return p->name == projectNameInOverride; })) {
handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
@@ -676,28 +696,26 @@ void ProductsCollector::Private::checkProjectNamesInOverrides()
}
}
-void ProductsCollector::Private::collectProductsByName()
+void ProductsCollector::Private::collectProductsByNameAndItem()
{
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- for (ProjectContext * const project : topLevelProject.projects) {
+ for (ProjectContext * const project : topLevelProject.projects()) {
for (ProductContext &product : project->products)
- topLevelProject.productsByName.insert({product.name, &product});
+ topLevelProject.addProduct(product);
}
}
void ProductsCollector::Private::checkProductNamesInOverrides()
{
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- for (const QString &productNameInOverride : topLevelProject.productNamesUsedInOverrides) {
- if (topLevelProject.erroneousProducts.contains(productNameInOverride))
- continue;
- if (!any_of(topLevelProject.productsByName, [&productNameInOverride](
- const std::pair<QString, ProductContext *> &elem) {
+ for (const QString &productNameInOverride : topLevelProject.productNamesUsedInOverrides()) {
+ if (!topLevelProject.productWithNameAndConstraint(
+ productNameInOverride, [&productNameInOverride](const ProductContext &product) {
// In an override string such as "a.b.c:d, we cannot tell whether we have a product
// "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take
// care not to emit false positives here.
- return elem.first == productNameInOverride
- || elem.first.startsWith(productNameInOverride + StringConstants::dot());
+ return product.name == productNameInOverride
+ || product.name.startsWith(productNameInOverride + StringConstants::dot());
})) {
handlePropertyError(Tr::tr("Unknown product '%1' in property override.")
.arg(productNameInOverride),
@@ -714,9 +732,9 @@ ProductsCollector::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
// Cloning is necessary because the original value will get "instantiated" now.
if (qbsValue)
- m_origQbsValue = qbsValue->clone();
+ m_origQbsValue = qbsValue->clone(d->loaderState.itemPool());
- m_tempBaseModule = d->loaderState.dependenciesResolver().loadBaseModule(product, m_productItem);
+ m_tempBaseModule = loadBaseModule(product, m_productItem, d->loaderState);
}
void ProductsCollector::Private::TempBaseModuleAttacher::drop()
diff --git a/src/lib/corelib/loader/productscollector.h b/src/lib/corelib/loader/productscollector.h
index fa2059872..246361b37 100644
--- a/src/lib/corelib/loader/productscollector.h
+++ b/src/lib/corelib/loader/productscollector.h
@@ -54,7 +54,6 @@ public:
~ProductsCollector();
void run(Item *rootProject);
- void printProfilingInfo(int indent);
private:
class Private;
diff --git a/src/lib/corelib/loader/productshandler.cpp b/src/lib/corelib/loader/productshandler.cpp
deleted file mode 100644
index e0ca79acc..000000000
--- a/src/lib/corelib/loader/productshandler.cpp
+++ /dev/null
@@ -1,336 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2023 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "productshandler.h"
-
-#include "dependenciesresolver.h"
-#include "groupshandler.h"
-#include "itemreader.h"
-#include "loaderutils.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-
-#include <language/evaluator.h>
-#include <language/item.h>
-#include <language/value.h>
-#include <logging/categories.h>
-#include <logging/translator.h>
-#include <tools/profiling.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/stringconstants.h>
-
-namespace qbs::Internal {
-
-class ProductsHandler::Private
-{
-public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- void handleNextProduct();
- void handleProduct(ProductContext &product, Deferral deferral);
- void resolveProbes(ProductContext &product);
- void resolveProbes(ProductContext &product, Item *item);
- void handleModuleSetupError(ProductContext &product, const Item::Module &module,
- const ErrorInfo &error);
- void runModuleProbes(ProductContext &product, const Item::Module &module);
- bool validateModule(ProductContext &product, const Item::Module &module);
- void updateModulePresentState(ProductContext &product, const Item::Module &module);
- void handleGroups(ProductContext &product);
-
- LoaderState &loaderState;
- GroupsHandler groupsHandler{loaderState};
- qint64 elapsedTime = 0;
-};
-
-ProductsHandler::ProductsHandler(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-
-void ProductsHandler::run()
-{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
-
- TopLevelProjectContext &topLevelProject = d->loaderState.topLevelProject();
- for (ProjectContext * const projectContext : topLevelProject.projects) {
- for (ProductContext &productContext : projectContext->products)
- topLevelProject.productsToHandle.emplace_back(&productContext, -1);
- }
- while (!topLevelProject.productsToHandle.empty())
- d->handleNextProduct();
-}
-
-void ProductsHandler::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Handling products took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
- d->groupsHandler.printProfilingInfo(indent + 2);
-}
-
-ProductsHandler::~ProductsHandler() = default;
-
-void ProductsHandler::Private::handleNextProduct()
-{
- TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
-
- auto [product, queueSizeOnInsert] = topLevelProject.productsToHandle.front();
- topLevelProject.productsToHandle.pop_front();
-
- // If the queue of in-progress products has shrunk since the last time we tried handling
- // this product, there has been forward progress and we can allow a deferral.
- const Deferral deferral = queueSizeOnInsert == -1
- || queueSizeOnInsert > int(topLevelProject.productsToHandle.size())
- ? Deferral::Allowed : Deferral::NotAllowed;
-
- loaderState.itemReader().setExtraSearchPathsStack(product->project->searchPathsStack);
- try {
- handleProduct(*product, deferral);
- if (product->name.startsWith(StringConstants::shadowProductPrefix()))
- topLevelProject.probes << product->info.probes;
- } catch (const ErrorInfo &err) {
- product->handleError(err);
- }
-
- // The search paths stack can change during dependency resolution (due to module providers);
- // check that we've rolled back all the changes
- QBS_CHECK(loaderState.itemReader().extraSearchPathsStack() == product->project->searchPathsStack);
-
- // If we encountered a dependency to an in-progress product or to a bulk dependency,
- // we defer handling this product if it hasn't failed yet and there is still forward progress.
- if (!product->info.delayedError.hasError() && !product->dependenciesResolved) {
- topLevelProject.productsToHandle.emplace_back(
- product, int(topLevelProject.productsToHandle.size()));
- }
-}
-
-void ProductsHandler::Private::handleProduct(ProductContext &product, Deferral deferral)
-{
- TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- topLevelProject.checkCancelation(loaderState.parameters());
-
- if (product.info.delayedError.hasError())
- return;
-
- product.dependenciesResolved = loaderState.dependenciesResolver()
- .resolveDependencies(product, deferral);
- if (!product.dependenciesResolved)
- return;
-
- // Run probes for modules and product.
- resolveProbes(product);
-
- // After the probes have run, we can switch on the evaluator cache.
- Evaluator &evaluator = loaderState.evaluator();
- FileTags fileTags = evaluator.fileTagsValue(product.item, StringConstants::typeProperty());
- EvalCacheEnabler cacheEnabler(&evaluator, evaluator.stringValue(
- product.item,
- StringConstants::sourceDirectoryProperty()));
-
- // Run module validation scripts.
- for (const Item::Module &module : product.item->modules()) {
- if (!validateModule(product, module))
- return;
- fileTags += evaluator.fileTagsValue(
- module.item, StringConstants::additionalProductTypesProperty());
- }
-
- // Disable modules that have been pulled in only by now-disabled modules.
- // Note that this has to happen in the reverse order compared to the other loops,
- // with the leaves checked last.
- for (auto it = product.item->modules().rbegin(); it != product.item->modules().rend(); ++it)
- updateModulePresentState(product, *it);
-
- // Now do the canonical module property values merge. Note that this will remove
- // previously attached values from modules that failed validation.
- // Evaluator cache entries that could potentially change due to this will be purged.
- loaderState.propertyMerger().doFinalMerge(product.item);
-
- const bool enabled = topLevelProject.checkItemCondition(product.item, evaluator);
- loaderState.dependenciesResolver().checkDependencyParameterDeclarations(
- product.item, product.name);
-
- handleGroups(product);
-
- // Collect the full list of fileTags, including the values contributed by modules.
- if (!product.info.delayedError.hasError() && enabled
- && !product.name.startsWith(StringConstants::shadowProductPrefix())) {
- for (const FileTag &tag : fileTags)
- topLevelProject.productsByType.insert({tag, &product});
- product.item->setProperty(StringConstants::typeProperty(),
- VariantValue::create(sorted(fileTags.toStringList())));
- }
- topLevelProject.productInfos[product.item] = product.info;
-}
-
-void ProductsHandler::Private::resolveProbes(ProductContext &product)
-{
- for (const Item::Module &module : product.item->modules()) {
- runModuleProbes(product, module);
- if (product.info.delayedError.hasError())
- return;
- }
- resolveProbes(product, product.item);
-}
-
-void ProductsHandler::Private::resolveProbes(ProductContext &product, Item *item)
-{
- product.info.probes << loaderState.probesResolver().resolveProbes(
- {product.name, product.uniqueName()}, item);
-}
-
-void ProductsHandler::Private::handleModuleSetupError(
- ProductContext &product, const Item::Module &module, const ErrorInfo &error)
-{
- if (module.required) {
- product.handleError(error);
- } else {
- qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
- << "found, but not usable in product" << product.name
- << error.toString();
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QStringLiteral("failed validation"), module.item);
- }
-}
-
-void ProductsHandler::Private::runModuleProbes(ProductContext &product, const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- if (module.productInfo && loaderState.topLevelProject().disabledItems.contains(
- module.productInfo->item)) {
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QLatin1String("module's exporting product is disabled"),
- module.item);
- return;
- }
- try {
- resolveProbes(product, module.item);
- if (module.versionRange.minimum.isValid()
- || module.versionRange.maximum.isValid()) {
- if (module.versionRange.maximum.isValid()
- && module.versionRange.minimum >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module "
- "'%3'").arg(module.versionRange.minimum.toString(),
- module.versionRange.maximum.toString(),
- module.name.toString()));
- }
- const Version moduleVersion = Version::fromString(
- loaderState.evaluator().stringValue(module.item,
- StringConstants::versionProperty()));
- if (moduleVersion < module.versionRange.minimum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "at least %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.minimum.toString()));
- }
- if (module.versionRange.maximum.isValid()
- && moduleVersion >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "lower than %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.maximum.toString()));
- }
- }
- } catch (const ErrorInfo &error) {
- handleModuleSetupError(product, module, error);
- }
-}
-
-bool ProductsHandler::Private::validateModule(ProductContext &product, const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return true;
- try {
- loaderState.evaluator().boolValue(module.item, StringConstants::validateProperty());
- for (const auto &dep : module.item->modules()) {
- if (dep.required && !dep.item->isPresentModule()) {
- throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not "
- "loaded successfully")
- .arg(module.name.toString(), dep.name.toString()));
- }
- }
- } catch (const ErrorInfo &error) {
- handleModuleSetupError(product, module, error);
- if (product.info.delayedError.hasError())
- return false;
- }
- return true;
-}
-
-void ProductsHandler::Private::updateModulePresentState(ProductContext &product,
- const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- bool hasPresentLoadingItem = false;
- for (const Item * const loadingItem : module.loadingItems) {
- if (loadingItem == product.item) {
- hasPresentLoadingItem = true;
- break;
- }
- if (!loadingItem->isPresentModule())
- continue;
- if (loadingItem->prototype() && loadingItem->prototype()->type() == ItemType::Export) {
- QBS_CHECK(loadingItem->prototype()->parent()->type() == ItemType::Product);
- if (loaderState.topLevelProject().disabledItems.contains(
- loadingItem->prototype()->parent())) {
- continue;
- }
- }
- hasPresentLoadingItem = true;
- break;
- }
- if (!hasPresentLoadingItem) {
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QLatin1String("imported only by disabled module(s)"),
- module.item);
- }
-}
-
-void ProductsHandler::Private::handleGroups(ProductContext &product)
-{
- groupsHandler.setupGroups(product.item, product.scope);
- product.info.modulePropertiesSetInGroups = groupsHandler.modulePropertiesSetInGroups();
- loaderState.topLevelProject().disabledItems.unite(groupsHandler.disabledGroups());
-}
-
-} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productsresolver.cpp b/src/lib/corelib/loader/productsresolver.cpp
new file mode 100644
index 000000000..997a4dc57
--- /dev/null
+++ b/src/lib/corelib/loader/productsresolver.cpp
@@ -0,0 +1,599 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "productsresolver.h"
+
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "productresolver.h"
+
+#include <language/language.h>
+#include <language/scriptengine.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/progressobserver.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <algorithm>
+#include <condition_variable>
+#include <future>
+#include <mutex>
+#include <queue>
+#include <system_error>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+namespace qbs::Internal {
+namespace {
+struct ThreadInfo {
+ ThreadInfo(std::future<void> &&future, LoaderState &loaderState)
+ : future(std::move(future)), loaderState(loaderState)
+ {}
+ std::future<void> future;
+ LoaderState &loaderState;
+ bool done = false;
+};
+
+struct ProductWithLoaderState {
+ ProductWithLoaderState(ProductContext &product, LoaderState *loaderState)
+ : product(&product), loaderState(loaderState) {}
+ ProductContext * const product;
+ LoaderState *loaderState;
+};
+
+class ThreadsLocker {
+public:
+ ThreadsLocker(std::launch mode, std::mutex &mutex) {
+ if (mode == std::launch::async)
+ lock = std::make_unique<std::unique_lock<std::mutex>>(mutex);
+ }
+ std::unique_ptr<std::unique_lock<std::mutex>> lock;
+};
+} // namespace
+
+class ProductsResolver
+{
+public:
+ ProductsResolver(LoaderState &loaderState) : m_loaderState(loaderState) {}
+ void resolve();
+
+private:
+ void initialize();
+ void initializeProductQueue();
+ void initializeLoaderStatePool();
+ void runScheduler();
+ void scheduleNext();
+ bool tryToReserveLoaderState(ProductWithLoaderState &product, Deferral deferral);
+ std::optional<std::pair<ProductContext *, Deferral>>
+ unblockProductWaitingForLoaderState(LoaderState &loaderState);
+ void startJob(const ProductWithLoaderState &product, Deferral deferral);
+ void checkForCancelation();
+ void handleFinishedThreads();
+ void queueProductForScheduling(const ProductWithLoaderState &product, Deferral deferral);
+ void waitForSingleDependency(const ProductWithLoaderState &product, ProductContext &dependency);
+ void waitForBulkDependency(const ProductWithLoaderState &product);
+ void unblockProductsWaitingForDependency(ProductContext &finishedProduct);
+ void postProcess();
+ void checkForMissedBulkDependencies(const ProductContext &product);
+
+ static int dependsItemCount(const Item *item);
+ static int dependsItemCount(ProductContext &product);
+
+ LoaderState &m_loaderState;
+ std::queue<std::pair<ProductWithLoaderState, int>> m_productsToSchedule;
+ std::vector<ProductContext *> m_finishedProducts;
+ std::unordered_map<ProductContext *,
+ std::vector<ProductWithLoaderState>> m_waitingForSingleDependency;
+ std::vector<ProductWithLoaderState> m_waitingForBulkDependency;
+ std::unordered_map<LoaderState *, std::queue<std::pair<ProductContext *, Deferral>>> m_waitingForLoaderState;
+ std::unordered_map<ProductContext *, ThreadInfo> m_runningThreads;
+ std::mutex m_threadsMutex;
+ std::condition_variable m_threadsNotifier;
+ std::vector<std::unique_ptr<ScriptEngine>> m_enginePool;
+ std::vector<std::unique_ptr<LoaderState>> m_loaderStatePool;
+ std::vector<LoaderState *> m_availableLoaderStates;
+ std::mutex m_cancelingMutex;
+ std::launch m_asyncMode = std::launch::async;
+ int m_maxJobCount = m_loaderState.parameters().maxJobCount();
+ bool m_canceling = false;
+};
+
+void resolveProducts(LoaderState &loaderState)
+{
+ ProductsResolver(loaderState).resolve();
+}
+
+void ProductsResolver::resolve()
+{
+ initialize();
+ try {
+ runScheduler();
+ } catch (const ErrorInfo &e) {
+ for (auto &thread : m_runningThreads)
+ thread.second.future.wait();
+ throw e;
+ }
+ postProcess();
+}
+
+void ProductsResolver::initialize()
+{
+ initializeProductQueue();
+ initializeLoaderStatePool();
+}
+
+void ProductsResolver::initializeProductQueue()
+{
+ TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject();
+ std::vector<ProductContext *> sortedProducts;
+ for (ProjectContext * const projectContext : topLevelProject.projects()) {
+ for (ProductContext &product : projectContext->products) {
+ topLevelProject.addProductToHandle(product);
+ const auto it = std::lower_bound(sortedProducts.begin(), sortedProducts.end(), product,
+ [&product](ProductContext *p1, const ProductContext &) {
+ return dependsItemCount(*p1) < dependsItemCount(product);
+ });
+ sortedProducts.insert(it, &product);
+ }
+ }
+
+ for (ProductContext * const product : sortedProducts) {
+ queueProductForScheduling(ProductWithLoaderState(*product, nullptr), Deferral::Allowed);
+ if (product->shadowProduct) {
+ topLevelProject.addProductToHandle(*product->shadowProduct);
+ queueProductForScheduling(ProductWithLoaderState(*product->shadowProduct, nullptr),
+ Deferral::Allowed);
+ }
+ }
+}
+
+void ProductsResolver::initializeLoaderStatePool()
+{
+ TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject();
+
+ // Adapt max job count: It makes no sense to have it be higher than the number of products
+ // or what can actually be run concurrently. In both cases, we would simply waste resources.
+ const int maxConcurrency = std::thread::hardware_concurrency();
+ if (maxConcurrency > 0 && maxConcurrency < m_maxJobCount)
+ m_maxJobCount = maxConcurrency;
+ if (m_maxJobCount > topLevelProject.productsToHandleCount())
+ m_maxJobCount = topLevelProject.productsToHandleCount();
+
+ // The number of engines and loader states we need to allocate here is one less than the
+ // total number of concurrent jobs, as we already have one loader state that we can re-use.
+ if (m_maxJobCount > 1)
+ m_enginePool.reserve(m_maxJobCount - 1);
+ m_loaderStatePool.reserve(m_enginePool.size());
+ m_availableLoaderStates.reserve(m_enginePool.size() + 1);
+ m_availableLoaderStates.push_back(&m_loaderState);
+ for (std::size_t i = 0; i < m_enginePool.capacity(); ++i) {
+ ScriptEngine &engine = *m_enginePool.emplace_back(
+ ScriptEngine::create(m_loaderState.logger(), EvalContext::PropertyEvaluation));
+ ItemPool &itemPool = topLevelProject.createItemPool();
+ engine.setEnvironment(m_loaderState.parameters().adjustedEnvironment());
+ auto loaderState = std::make_unique<LoaderState>(
+ m_loaderState.parameters(), topLevelProject, itemPool, engine,
+ m_loaderState.logger());
+ m_loaderStatePool.push_back(std::move(loaderState));
+ m_availableLoaderStates.push_back(m_loaderStatePool.back().get());
+ if (topLevelProject.progressObserver())
+ topLevelProject.progressObserver()->addScriptEngine(m_enginePool.back().get());
+ }
+ qCDebug(lcLoaderScheduling) << "using" << m_availableLoaderStates.size() << "loader states";
+ if (int(m_availableLoaderStates.size()) == 1)
+ m_asyncMode = std::launch::deferred;
+}
+
+void ProductsResolver::runScheduler()
+{
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &m_loaderState.topLevelProject().timingData().resolvingProducts
+ : nullptr);
+
+ ThreadsLocker threadsLock(m_asyncMode, m_threadsMutex);
+ while (true) {
+ scheduleNext();
+ if (m_runningThreads.empty())
+ break;
+ if (m_asyncMode == std::launch::async) {
+ qCDebug(lcLoaderScheduling()) << "scheduling paused, waiting for threads to finish";
+ m_threadsNotifier.wait(*threadsLock.lock);
+ }
+ checkForCancelation();
+ handleFinishedThreads();
+ }
+
+ QBS_CHECK(m_productsToSchedule.empty());
+ QBS_CHECK(m_loaderState.topLevelProject().productsToHandleCount() == 0);
+ QBS_CHECK(m_runningThreads.empty());
+ QBS_CHECK(m_waitingForSingleDependency.empty());
+ QBS_CHECK(m_waitingForBulkDependency.empty());
+}
+
+void ProductsResolver::scheduleNext()
+{
+ TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject();
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &topLevelProject.timingData().schedulingProducts : nullptr);
+ while (m_maxJobCount > int(m_runningThreads.size()) && !m_productsToSchedule.empty()) {
+ auto [product, toHandleCountOnInsert] = m_productsToSchedule.front();
+ m_productsToSchedule.pop();
+
+ qCDebug(lcLoaderScheduling) << "potentially scheduling product"
+ << product.product->displayName()
+ << "unhandled product count on queue insertion"
+ << toHandleCountOnInsert << "current unhandled product count"
+ << topLevelProject.productsToHandleCount();
+
+ // If the number of unfinished products has shrunk since the last time we tried handling
+ // this product, there has been forward progress and we can allow a deferral.
+ const Deferral deferral = toHandleCountOnInsert == -1
+ || toHandleCountOnInsert > topLevelProject.productsToHandleCount()
+ ? Deferral::Allowed : Deferral::NotAllowed;
+
+ if (!tryToReserveLoaderState(product, deferral))
+ continue;
+
+ startJob(product, deferral);
+ }
+
+ // There are jobs running, so forward progress is still possible.
+ if (!m_runningThreads.empty())
+ return;
+
+ QBS_CHECK(m_productsToSchedule.empty());
+
+ // If we end up here, nothing was scheduled in the loop above, which means that either ...
+ // a) ... we are done or
+ // b) ... we finally need to schedule our bulk dependencies or
+ // c) ... we need to schedule products waiting for an unhandled dependency.
+ // In the latter case, the project has at least one dependency cycle, and the
+ // DependencyResolver will emit an error.
+
+ // a)
+ if (m_waitingForBulkDependency.empty() && m_waitingForSingleDependency.empty())
+ return;
+
+ // b)
+ for (const ProductWithLoaderState &product : m_waitingForBulkDependency)
+ queueProductForScheduling(product, Deferral::NotAllowed);
+ if (!m_productsToSchedule.empty()) {
+ m_waitingForBulkDependency.clear();
+ scheduleNext();
+ return;
+ }
+
+ // c)
+ for (const auto &e : m_waitingForSingleDependency) {
+ for (const ProductWithLoaderState &p : e.second)
+ queueProductForScheduling(p, Deferral::NotAllowed);
+ }
+ QBS_CHECK(!m_productsToSchedule.empty());
+ scheduleNext();
+}
+
+bool ProductsResolver::tryToReserveLoaderState(ProductWithLoaderState &product, Deferral deferral)
+{
+ QBS_CHECK(!m_availableLoaderStates.empty());
+ if (!product.loaderState) {
+ product.loaderState = m_availableLoaderStates.back();
+ m_availableLoaderStates.pop_back();
+ return true;
+ }
+ if (const auto it = std::find(m_availableLoaderStates.begin(), m_availableLoaderStates.end(),
+ product.loaderState); it != m_availableLoaderStates.end()) {
+ m_availableLoaderStates.erase(it);
+ return true;
+ }
+ qCDebug(lcLoaderScheduling) << "loader state" << product.loaderState << " for product"
+ << product.product->displayName()
+ << "not available, adding product to wait queue";
+ m_waitingForLoaderState[product.loaderState].push({product.product, deferral});
+ return false;
+}
+
+std::optional<std::pair<ProductContext *, Deferral>>
+ProductsResolver::unblockProductWaitingForLoaderState(LoaderState &loaderState)
+{
+ auto &waitingForLoaderState = m_waitingForLoaderState[&loaderState];
+ if (waitingForLoaderState.empty())
+ return {};
+ const auto product = waitingForLoaderState.front();
+ waitingForLoaderState.pop();
+ qCDebug(lcLoaderScheduling) << "loader state" << &loaderState << "now available for product"
+ << product.first->displayName();
+ return product;
+}
+
+void ProductsResolver::startJob(const ProductWithLoaderState &product, Deferral deferral)
+{
+ QBS_CHECK(product.loaderState);
+ qCDebug(lcLoaderScheduling) << "scheduling product" << product.product->displayName()
+ << "with loader state" << product.loaderState
+ << "and deferral mode" << int(deferral);
+ try {
+ const auto it = m_runningThreads.emplace(product.product, ThreadInfo(std::async(m_asyncMode,
+ [this, product, deferral] {
+ product.loaderState->itemReader().setExtraSearchPathsStack(
+ product.product->project->searchPathsStack);
+ resolveProduct(*product.product, deferral, *product.loaderState);
+
+ // The search paths stack can change during dependency resolution
+ // (due to module providers); check that we've rolled back all the changes
+ QBS_CHECK(product.loaderState->itemReader().extraSearchPathsStack()
+ == product.product->project->searchPathsStack);
+
+ std::lock_guard cancelingLock(m_cancelingMutex);
+ if (m_canceling)
+ return;
+ ThreadsLocker threadsLock(m_asyncMode, m_threadsMutex);
+ if (const auto it = m_runningThreads.find(product.product);
+ it != m_runningThreads.end()) {
+ it->second.done = true;
+ qCDebug(lcLoaderScheduling) << "thread for product"
+ << product.product->displayName()
+ << "finished, waking up scheduler";
+ m_threadsNotifier.notify_one();
+ }
+ }), *product.loaderState));
+
+ // With just one worker thread, the notify/wait overhead would be excessive, so
+ // we run the task synchronously.
+ if (m_asyncMode == std::launch::deferred) {
+ qCDebug(lcLoaderScheduling) << "blocking on product thread immediately";
+ it.first->second.future.wait();
+ }
+ } catch (const std::system_error &e) {
+ if (e.code() != std::errc::resource_unavailable_try_again)
+ throw e;
+ qCWarning(lcLoaderScheduling) << "failed to create thread";
+ if (m_maxJobCount >= 2) {
+ m_maxJobCount /= 2;
+ qCWarning(lcLoaderScheduling) << "throttling down to" << m_maxJobCount << "jobs";
+ }
+ queueProductForScheduling(product, deferral);
+ m_availableLoaderStates.push_back(product.loaderState);
+ }
+}
+
+void ProductsResolver::checkForCancelation()
+{
+ if (m_loaderState.topLevelProject().isCanceled()) {
+ m_cancelingMutex.lock();
+ m_canceling = true;
+ m_cancelingMutex.unlock();
+ for (auto &thread : m_runningThreads)
+ thread.second.future.wait();
+ throw CancelException();
+ }
+}
+
+void ProductsResolver::handleFinishedThreads()
+{
+ TopLevelProjectContext &topLevelProject = m_loaderState.topLevelProject();
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &topLevelProject.timingData().schedulingProducts : nullptr);
+
+ std::vector<std::pair<ProductWithLoaderState, Deferral>> productsToScheduleDirectly;
+ for (auto it = m_runningThreads.begin(); it != m_runningThreads.end();) {
+ ThreadInfo &ti = it->second;
+ if (!ti.done) {
+ ++it;
+ continue;
+ }
+ ti.future.wait();
+ ProductContext &product = *it->first;
+ LoaderState &loaderState = ti.loaderState;
+ it = m_runningThreads.erase(it);
+
+ qCDebug(lcLoaderScheduling) << "handling finished thread for product"
+ << product.displayName()
+ << "current unhandled product count is"
+ << topLevelProject.productsToHandleCount();
+
+ // If there are products waiting for the loader state used in the finished thread,
+ // we can start a job for one of them right away (but not in the loop,
+ // because startJob() modifies the thread list we are currently iterating over).
+ if (const auto productInfo = unblockProductWaitingForLoaderState(loaderState)) {
+ productsToScheduleDirectly.emplace_back(
+ ProductWithLoaderState(*productInfo->first, &loaderState),
+ productInfo->second);
+ } else {
+ qCDebug(lcLoaderScheduling) << "making loader state" << &loaderState
+ << "available again";
+ m_availableLoaderStates.push_back(&loaderState);
+ }
+
+ // If we encountered a dependency to an in-progress product or to a bulk dependency,
+ // we defer handling this product.
+ if (product.dependenciesResolvingPending()) {
+ qCDebug(lcLoaderScheduling) << "dependencies resolving not finished for product"
+ << product.displayName();
+ const auto pending = product.pendingDependency();
+ switch (pending.first) {
+ case ProductDependency::Single:
+ waitForSingleDependency(ProductWithLoaderState(product, &loaderState),
+ *pending.second);
+ break;
+ case ProductDependency::Bulk:
+ waitForBulkDependency(ProductWithLoaderState(product, &loaderState));
+ break;
+ case ProductDependency::None:
+ // This can happen if the dependency has finished in between the check in
+ // DependencyResolver and the one here.
+ QBS_CHECK(pending.second);
+ queueProductForScheduling(ProductWithLoaderState(product, &loaderState),
+ Deferral::Allowed);
+ break;
+ }
+ topLevelProject.incProductDeferrals();
+ } else {
+ qCDebug(lcLoaderScheduling) << "product" << product.displayName() << "finished";
+ topLevelProject.removeProductToHandle(product);
+ if (!product.name.startsWith(StringConstants::shadowProductPrefix()))
+ m_finishedProducts.push_back(&product);
+ topLevelProject.timingData() += product.timingData;
+ checkForMissedBulkDependencies(product);
+ topLevelProject.registerBulkDependencies(product);
+ unblockProductsWaitingForDependency(product);
+ }
+ }
+
+ for (const auto &productInfo : productsToScheduleDirectly)
+ startJob(productInfo.first, productInfo.second);
+}
+
+void ProductsResolver::queueProductForScheduling(const ProductWithLoaderState &product,
+ Deferral deferral)
+{
+ qCDebug(lcLoaderScheduling) << "queueing product" << product.product->displayName()
+ << "with deferral mode" << int(deferral);
+ m_productsToSchedule.emplace(product, deferral == Deferral::Allowed
+ ? -1 : m_loaderState.topLevelProject().productsToHandleCount());
+}
+
+void ProductsResolver::waitForSingleDependency(const ProductWithLoaderState &product,
+ ProductContext &dependency)
+{
+ qCDebug(lcLoaderScheduling) << "product" << product.product->displayName()
+ << "now waiting for single dependency"
+ << dependency.displayName();
+ m_waitingForSingleDependency[&dependency].push_back(product);
+}
+
+void ProductsResolver::waitForBulkDependency(const ProductWithLoaderState &product)
+{
+ qCDebug(lcLoaderScheduling) << "product" << product.product->displayName()
+ << "now waiting for bulk dependency";
+ m_waitingForBulkDependency.push_back(product);
+}
+
+void ProductsResolver::unblockProductsWaitingForDependency(ProductContext &finishedProduct)
+{
+ const auto it = m_waitingForSingleDependency.find(&finishedProduct);
+ if (it == m_waitingForSingleDependency.end())
+ return;
+
+ qCDebug(lcLoaderScheduling) << "unblocking all products waiting for now-finished product" <<
+ finishedProduct.displayName();
+ for (const ProductWithLoaderState &p : it->second) {
+ qCDebug(lcLoaderScheduling) << " unblocking product" << p.product->displayName();
+ queueProductForScheduling(p, Deferral::Allowed);
+ }
+ m_waitingForSingleDependency.erase(it);
+}
+
+void ProductsResolver::postProcess()
+{
+ for (ProductContext * const product : m_finishedProducts) {
+ if (product->product)
+ product->product->project->products.push_back(product->product);
+
+ // This has to be done in post-processing, because we need both product and shadow product
+ // to be ready, and contrary to what one might assume, there is no proper ordering
+ // between them regarding dependency resolving.
+ setupExports(*product, m_loaderState);
+ }
+
+ for (const auto &engine : m_enginePool)
+ m_loaderState.topLevelProject().collectDataFromEngine(*engine);
+
+ QBS_CHECK(!m_loaderState.topLevelProject().projects().empty());
+ const auto project = std::dynamic_pointer_cast<TopLevelProject>(
+ m_loaderState.topLevelProject().projects().front()->project);
+ QBS_CHECK(project);
+ for (LoaderState * const loaderState : m_availableLoaderStates)
+ project->warningsEncountered << loaderState->logger().warnings();
+}
+
+void ProductsResolver::checkForMissedBulkDependencies(const ProductContext &product)
+{
+ if (!product.product || !product.product->enabled || !product.bulkDependencies.empty())
+ return;
+ for (const FileTag &tag : product.product->fileTags) {
+ for (const auto &[p, location]
+ : m_loaderState.topLevelProject().finishedProductsWithBulkDependency(tag)) {
+ if (!p->product->enabled)
+ continue;
+ if (p->name == product.name)
+ continue;
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ e.append(Tr::tr("First product is '%1'.")
+ .arg(product.displayName()), product.item->location());
+ e.append(Tr::tr("Second product is '%1'.")
+ .arg(p->displayName()), p->item->location());
+ e.append(Tr::tr("Dependency from %1 to %2 was established via Depends.productTypes.")
+ .arg(p->displayName(), product.displayName()), location);
+ if (m_loaderState.parameters().productErrorMode() == ErrorHandlingMode::Strict)
+ throw e;
+ m_loaderState.logger().printWarning(e);
+ m_loaderState.logger().printWarning(
+ ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
+ .arg(product.displayName()), product.item->location()));
+ m_loaderState.logger().printWarning(
+ ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
+ .arg(p->displayName()), p->item->location()));
+ product.product->enabled = false;
+ p->product->enabled = false;
+ }
+ }
+}
+
+int ProductsResolver::dependsItemCount(const Item *item)
+{
+ int count = 0;
+ for (const Item * const child : item->children()) {
+ if (child->type() == ItemType::Depends)
+ ++count;
+ }
+ return count;
+}
+
+int ProductsResolver::dependsItemCount(ProductContext &product)
+{
+ if (product.dependsItemCount == -1)
+ product.dependsItemCount = dependsItemCount(product.item);
+ return product.dependsItemCount;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/projecttreebuilder.h b/src/lib/corelib/loader/productsresolver.h
index a4f08a1f3..b8b4b7516 100644
--- a/src/lib/corelib/loader/projecttreebuilder.h
+++ b/src/lib/corelib/loader/productsresolver.h
@@ -39,54 +39,9 @@
#pragma once
-#include "loaderutils.h"
+namespace qbs::Internal {
+class LoaderState;
-#include <language/forward_decls.h>
-#include <language/moduleproviderinfo.h>
-#include <language/qualifiedid.h>
-#include <tools/pimpl.h>
+void resolveProducts(LoaderState &loaderState);
-#include <QString>
-#include <QVariant>
-
-namespace qbs {
-class SetupProjectParameters;
-namespace Internal {
-class Evaluator;
-class FileTime;
-class Item;
-class ItemPool;
-class ProgressObserver;
-
-class ProjectTreeBuilder
-{
-public:
- ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger);
- ~ProjectTreeBuilder();
-
- struct Result
- {
- Item *root = nullptr;
- std::unordered_map<Item *, ProductInfo> productInfos;
- std::vector<ProbeConstPtr> projectProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- Set<QString> qbsFiles;
- QVariantMap profileConfigs;
- };
- Result load();
-
- void setProgressObserver(ProgressObserver *progressObserver);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void setLastResolveTime(const FileTime &time);
- void setStoredProfiles(const QVariantMap &profiles);
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
-
-} // namespace Internal
-} // namespace qbs
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/projectresolver.cpp b/src/lib/corelib/loader/projectresolver.cpp
index d771e632c..be99d547d 100644
--- a/src/lib/corelib/loader/projectresolver.cpp
+++ b/src/lib/corelib/loader/projectresolver.cpp
@@ -39,7 +39,10 @@
#include "projectresolver.h"
-#include "projecttreebuilder.h"
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "productscollector.h"
+#include "productsresolver.h"
#include <jsextensions/jsextensions.h>
#include <jsextensions/moduleproperties.h>
@@ -87,169 +90,59 @@
namespace qbs {
namespace Internal {
-extern bool debugProperties;
-
-static const FileTag unknownFileTag()
-{
- static const FileTag tag("unknown-file-tag");
- return tag;
-}
-
-struct ResolverProjectContext
+static SetupProjectParameters finalizedProjectParameters(const SetupProjectParameters &in,
+ Logger &logger)
{
- ResolverProjectContext *parentContext = nullptr;
- ResolvedProjectPtr project;
- std::vector<FileTaggerConstPtr> fileTaggers;
- std::vector<RulePtr> rules;
- JobLimits jobLimits;
- ResolvedModulePtr dummyModule;
-};
+ SetupProjectParameters params = in;
+ if (params.topLevelProfile().isEmpty()) {
+ Settings settings(params.settingsDirectory());
+ QString profileName = settings.defaultProfile();
+ if (profileName.isEmpty()) {
+ logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. "
+ "Using default property values.");
+ profileName = Profile::fallbackName();
+ }
+ params.setTopLevelProfile(profileName);
+ params.expandBuildConfiguration();
+ }
+ params.finalizeProjectFilePath();
-using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
-struct ResolverProductContext
-{
- ResolvedProductPtr product;
- QString buildDirectory;
- Item *item = nullptr;
- using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>;
- QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter;
- FileLocations sourceArtifactLocations;
- GroupConstPtr currentGroup;
-};
+ QBS_CHECK(QFileInfo(params.projectFilePath()).isAbsolute());
+ QBS_CHECK(FileInfo::isAbsolute(params.buildRoot()));
-struct ModuleContext
-{
- ResolvedModulePtr module;
- JobLimits jobLimits;
-};
-
-class CancelException { };
+ return params;
+}
class ProjectResolver::Private
{
public:
- Private(ScriptEngine *engine, Logger &&logger) : engine(engine), logger(std::move(logger)) {}
+ Private(const SetupProjectParameters &parameters, ScriptEngine *engine, Logger &&logger)
+ : setupParams(finalizedProjectParameters(parameters, logger)), engine(engine),
+ logger(std::move(logger)) {}
- static void applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product);
- static SourceArtifactPtr createSourceArtifact(
- const ResolvedProductPtr &rproduct, const QString &fileName, const GroupPtr &group,
- bool wildcard, const CodeLocation &filesLocation = CodeLocation(),
- FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr);
- void finalizeProjectParameters();
- void checkCancelation() const;
- QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const;
- QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const;
- ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const;
- ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const;
- void ignoreItem(Item *item, ResolverProjectContext *projectContext);
TopLevelProjectPtr resolveTopLevelProject();
- void resolveProject(Item *item, ResolverProjectContext *projectContext);
- void resolveProjectFully(Item *item, ResolverProjectContext *projectContext);
- void resolveSubProject(Item *item, ResolverProjectContext *projectContext);
- void resolveProduct(Item *item, ResolverProjectContext *projectContext);
- void resolveProductFully(Item *item, ResolverProjectContext *projectContext);
- void resolveModules(const Item *item, ResolverProjectContext *projectContext);
- void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
- const QVariantMap &parameters, JobLimits &jobLimits,
- ResolverProjectContext *projectContext);
- void gatherProductTypes(ResolvedProduct *product, Item *item);
- QVariantMap resolveAdditionalModuleProperties(const Item *group,
- const QVariantMap &currentValues);
- void resolveGroup(Item *item, ResolverProjectContext *projectContext);
- void resolveGroupFully(Item *item, ResolverProjectContext *projectContext, bool isEnabled);
- void resolveShadowProduct(Item *item, ResolverProjectContext *);
- void resolveExport(Item *exportItem, ResolverProjectContext *);
- std::unique_ptr<ExportedItem> resolveExportChild(const Item *item,
- const ExportedModule &module);
- void resolveRule(Item *item, ResolverProjectContext *projectContext);
- void resolveRuleArtifact(const RulePtr &rule, Item *item);
- void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item,
- const QStringList &namePrefix,
- QualifiedIdSet *seenBindings);
- void resolveFileTagger(Item *item, ResolverProjectContext *projectContext);
- void resolveJobLimit(Item *item, ResolverProjectContext *projectContext);
- void resolveScanner(Item *item, ResolverProjectContext *projectContext);
- void resolveProductDependencies();
- void postProcess(const ResolvedProductPtr &product, ResolverProjectContext *projectContext) const;
- void applyFileTaggers(const ResolvedProductPtr &product) const;
- QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true);
- QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors);
- QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer,
- const QVariantMap &tmplt, bool lookupPrototype,
- bool checkErrors);
- void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue,
- QVariantMap &result, bool checkErrors);
- void checkAllowedValues(
- const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl,
- const QString &key) const;
- void createProductConfig(ResolvedProduct *product);
- ResolverProjectContext createProjectContext(ResolverProjectContext *parentProjectContext) const;
- void adaptExportedPropertyValues(const Item *shadowProductItem);
- void collectExportedProductDependencies();
+ void resolveProject(ProjectContext *projectContext);
+ void resolveProjectFully(ProjectContext *projectContext);
+ void resolveSubProject(Item *item, ProjectContext *projectContext);
+ void checkOverriddenValues();
+ void collectNameFromOverride(const QString &overrideString);
+ void loadTopLevelProjectItem();
+ void buildProjectTree();
- struct ProductDependencyInfo
- {
- ProductDependencyInfo(ResolvedProductPtr product,
- QVariantMap parameters = QVariantMap())
- : product(std::move(product)), parameters(std::move(parameters))
- {
- }
-
- ResolvedProductPtr product;
- QVariantMap parameters;
- };
-
- QString sourceCodeAsFunction(const JSSourceValueConstPtr &value,
- const PropertyDeclaration &decl) const;
- QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const;
- static void matchArtifactProperties(const ResolvedProductPtr &product,
- const std::vector<SourceArtifactPtr> &artifacts);
void printProfilingInfo();
- void collectPropertiesForExportItem(Item *productModuleInstance);
- void collectPropertiesForModuleInExportItem(const Item::Module &module);
-
- void collectPropertiesForExportItem(const QualifiedId &moduleName,
- const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps);
- void setupExportedProperties(const Item *item, const QString &namePrefix,
- std::vector<ExportedProperty> &properties);
-
- using ItemFuncPtr = void (ProjectResolver::Private::*)(Item *item,
- ResolverProjectContext *projectContext);
- using ItemFuncMap = QMap<ItemType, ItemFuncPtr>;
- void callItemFunction(const ItemFuncMap &mappings, Item *item, ResolverProjectContext *projectContext);
-
+ const SetupProjectParameters setupParams;
ScriptEngine * const engine;
mutable Logger logger;
- Evaluator evaluator{engine};
ItemPool itemPool;
- SetupProjectParameters setupParams;
- ProjectTreeBuilder::Result loadResult;
- ProgressObserver *progressObserver = nullptr;
- ResolverProductContext *productContext = nullptr;
- ModuleContext *moduleContext = nullptr;
- QHash<Item *, ResolvedProductPtr> productsByItem;
- mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> fileContextMap;
- mutable QHash<CodeLocation, ScriptFunctionPtr> scriptFunctionMap;
- mutable QHash<std::pair<QStringView, QStringList>, QString> scriptFunctions;
- mutable QHash<QStringView, QString> sourceCode;
- Set<CodeLocation> groupLocationWarnings;
- std::vector<std::pair<ResolvedProductPtr, Item *>> productExportInfo;
- std::vector<ErrorInfo> queuedErrors;
- std::vector<ProbeConstPtr> oldProjectProbes;
- QHash<QString, std::vector<ProbeConstPtr>> oldProductProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- QVariantMap storedProfiles;
- FileTime lastResolveTime;
- qint64 elapsedTimeModPropEval = 0;
- qint64 elapsedTimeAllPropEval = 0;
- qint64 elapsedTimeGroups = 0;
+ TopLevelProjectContext topLevelProject;
+ LoaderState state{setupParams, topLevelProject, itemPool, *engine, logger};
+ Item *rootProjectItem = nullptr;
};
-
-ProjectResolver::ProjectResolver(ScriptEngine *engine, Logger logger)
- : d(makePimpl<Private>(engine, std::move(logger)))
+ProjectResolver::ProjectResolver(const SetupProjectParameters &parameters, ScriptEngine *engine,
+ Logger logger)
+ : d(makePimpl<Private>(parameters, engine, std::move(logger)))
{
d->logger.storeWarnings();
}
@@ -258,33 +151,33 @@ ProjectResolver::~ProjectResolver() = default;
void ProjectResolver::setProgressObserver(ProgressObserver *observer)
{
- d->progressObserver = observer;
+ d->state.topLevelProject().setProgressObserver(observer);
}
void ProjectResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
{
- d->oldProjectProbes = oldProbes;
+ d->state.topLevelProject().setOldProjectProbes(oldProbes);
}
void ProjectResolver::setOldProductProbes(
const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
{
- d->oldProductProbes = oldProbes;
+ d->state.topLevelProject().setOldProductProbes(oldProbes);
}
void ProjectResolver::setLastResolveTime(const FileTime &time)
{
- d->lastResolveTime = time;
+ d->state.topLevelProject().setLastResolveTime(time);
}
void ProjectResolver::setStoredProfiles(const QVariantMap &profiles)
{
- d->storedProfiles = profiles;
+ d->state.topLevelProject().setProfileConfigs(profiles);
}
void ProjectResolver::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo)
{
- d->storedModuleProviderInfo = providerInfo;
+ d->state.topLevelProject().setModuleProvidersCache(providerInfo.providers);
}
static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project)
@@ -306,12 +199,8 @@ static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project
}
}
-TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parameters)
+TopLevelProjectPtr ProjectResolver::resolve()
{
- d->setupParams = parameters;
- d->finalizeProjectParameters();
- QBS_CHECK(FileInfo::isAbsolute(d->setupParams.buildRoot()));
-
qCDebug(lcProjectResolver) << "resolving" << d->setupParams.projectFilePath();
d->engine->setEnvironment(d->setupParams.adjustedEnvironment());
@@ -326,23 +215,18 @@ TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parame
// the project yet. That's why we use a placeholder here, so the user at least
// sees that an operation is starting. The real total effort will be set later when
// we have enough information.
- if (d->progressObserver) {
- d->progressObserver->initialize(Tr::tr("Resolving project for configuration %1")
+ if (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) {
+ observer->initialize(Tr::tr("Resolving project for configuration %1")
.arg(TopLevelProject::deriveId(d->setupParams.finalBuildConfigurationTree())), 1);
- d->progressObserver->setScriptEngine(d->engine);
+ observer->addScriptEngine(d->engine);
}
const FileTime resolveTime = FileTime::currentTime();
TopLevelProjectPtr tlp;
- ProjectTreeBuilder projectTreeBuilder(d->setupParams, d->itemPool, d->evaluator, d->logger);
- projectTreeBuilder.setProgressObserver(d->progressObserver);
- projectTreeBuilder.setOldProjectProbes(d->oldProjectProbes);
- projectTreeBuilder.setOldProductProbes(d->oldProductProbes);
- projectTreeBuilder.setLastResolveTime(d->lastResolveTime);
- projectTreeBuilder.setStoredProfiles(d->storedProfiles);
- projectTreeBuilder.setStoredModuleProviderInfo(d->storedModuleProviderInfo);
- d->loadResult = projectTreeBuilder.load();
try {
+ d->checkOverriddenValues();
+ d->loadTopLevelProjectItem();
+ d->buildProjectTree();
tlp = d->resolveTopLevelProject();
} catch (const CancelException &) {
throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.")
@@ -353,48 +237,13 @@ TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parame
tlp->lastEndResolveTime = FileTime::currentTime();
// E.g. if the top-level project is disabled.
- if (d->progressObserver) {
- d->progressObserver->setFinished();
+ if (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) {
+ observer->setFinished();
d->printProfilingInfo();
}
return tlp;
}
-void ProjectResolver::Private::checkCancelation() const
-{
- if (progressObserver && progressObserver->canceled())
- throw CancelException();
-}
-
-QString ProjectResolver::Private::verbatimValue(
- const ValueConstPtr &value, bool *propertyWasSet) const
-{
- QString result;
- if (value && value->type() == Value::JSSourceValueType) {
- const JSSourceValueConstPtr sourceValue = std::static_pointer_cast<const JSSourceValue>(
- value);
- result = sourceCodeForEvaluation(sourceValue);
- if (propertyWasSet)
- *propertyWasSet = !sourceValue->isBuiltinDefaultValue();
- } else {
- if (propertyWasSet)
- *propertyWasSet = false;
- }
- return result;
-}
-
-QString ProjectResolver::Private::verbatimValue(Item *item, const QString &name,
- bool *propertyWasSet) const
-{
- return verbatimValue(item->property(name), propertyWasSet);
-}
-
-void ProjectResolver::Private::ignoreItem(Item *item, ResolverProjectContext *projectContext)
-{
- Q_UNUSED(item);
- Q_UNUSED(projectContext);
-}
-
static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
{
Set<QString> subProjectNames;
@@ -420,65 +269,60 @@ static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject()
{
- if (progressObserver)
- progressObserver->setMaximum(int(loadResult.productInfos.size()));
+ if (ProgressObserver * const observer = state.topLevelProject().progressObserver())
+ observer->setMaximum(state.topLevelProject().productCount());
TopLevelProjectPtr project = TopLevelProject::create();
project->buildDirectory = TopLevelProject::deriveBuildDirectory(
setupParams.buildRoot(),
TopLevelProject::deriveId(setupParams.finalBuildConfigurationTree()));
- project->buildSystemFiles = loadResult.qbsFiles;
- project->profileConfigs = loadResult.profileConfigs;
- project->probes = loadResult.projectProbes;
- project->moduleProviderInfo = loadResult.storedModuleProviderInfo;
- ResolverProjectContext projectContext;
- projectContext.project = project;
-
- resolveProject(loadResult.root, &projectContext);
+ if (!state.topLevelProject().projects().empty()) {
+ ProjectContext * const projectContext = state.topLevelProject().projects().front();
+ QBS_CHECK(projectContext->item == rootProjectItem);
+ projectContext->project = project;
+ resolveProject(projectContext);
+ }
+ resolveProducts(state);
ErrorInfo accumulatedErrors;
- for (const ErrorInfo &e : queuedErrors)
- appendError(accumulatedErrors, e);
+ {
+ const auto queuedErrors = state.topLevelProject().queuedErrors();
+ for (const ErrorInfo &e : queuedErrors.get())
+ appendError(accumulatedErrors, e);
+ }
if (accumulatedErrors.hasError())
throw accumulatedErrors;
+ project->buildSystemFiles.unite(state.topLevelProject().buildSystemFiles());
+ project->profileConfigs = state.topLevelProject().profileConfigs();
+ project->codeLinks = state.topLevelProject().codeLinks();
+ const QVariantMap &profiles = state.topLevelProject().localProfiles();
+ for (auto it = profiles.begin(); it != profiles.end(); ++it)
+ project->profileConfigs.remove(it.key());
+ project->probes = state.topLevelProject().projectLevelProbes();
+ project->moduleProviderInfo.providers = state.topLevelProject().moduleProvidersCache();
project->setBuildConfiguration(setupParams.finalBuildConfigurationTree());
project->overriddenValues = setupParams.overriddenValues();
- project->canonicalFilePathResults = engine->canonicalFilePathResults();
- project->fileExistsResults = engine->fileExistsResults();
- project->directoryEntriesResults = engine->directoryEntriesResults();
- project->fileLastModifiedResults = engine->fileLastModifiedResults();
- project->environment = engine->environment();
- project->buildSystemFiles.unite(engine->imports());
+ state.topLevelProject().collectDataFromEngine(*engine);
makeSubProjectNamesUniqe(project);
- resolveProductDependencies();
- collectExportedProductDependencies();
checkForDuplicateProductNames(project);
+ project->warningsEncountered << logger.warnings();
- for (const ResolvedProductPtr &product : project->allProducts()) {
- if (!product->enabled)
- continue;
-
- applyFileTaggers(product);
- matchArtifactProperties(product, product->allEnabledFiles());
-
- // Let a positive value of qbs.install imply the file tag "installable".
- for (const SourceArtifactPtr &artifact : product->allFiles()) {
- if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool())
- artifact->fileTags += "installable";
- }
- }
- project->warningsEncountered = logger.warnings();
return project;
}
-void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveProject(ProjectContext *projectContext)
{
- checkCancelation();
+ state.topLevelProject().checkCancelation();
+
+ if (projectContext->parent) {
+ projectContext->project = ResolvedProject::create();
+ projectContext->parent->project->subProjects.push_back(projectContext->project);
+ projectContext->project->parentProject = projectContext->parent->project;
+ projectContext->project->enabled = projectContext->parent->project->enabled;
+ }
+ projectContext->project->location = projectContext->item->location();
- if (projectContext->parentContext)
- projectContext->project->enabled = projectContext->parentContext->project->enabled;
- projectContext->project->location = item->location();
try {
- resolveProjectFully(item, projectContext);
+ resolveProjectFully(projectContext);
} catch (const ErrorInfo &error) {
if (!projectContext->project->enabled) {
qCDebug(lcProjectResolver) << "error resolving project"
@@ -491,17 +335,20 @@ void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext
}
}
-void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveProjectFully(ProjectContext *projectContext)
{
+ Item * const item = projectContext->item;
projectContext->project->enabled = projectContext->project->enabled
- && evaluator.boolValue(item, StringConstants::conditionProperty());
- projectContext->project->name = evaluator.stringValue(item, StringConstants::nameProperty());
+ && state.evaluator().boolValue(item, StringConstants::conditionProperty());
+ projectContext->project->name = state.evaluator().stringValue(
+ item, StringConstants::nameProperty());
if (projectContext->project->name.isEmpty())
projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item?
QVariantMap projectProperties;
if (!projectContext->project->enabled) {
projectProperties.insert(StringConstants::profileProperty(),
- evaluator.stringValue(item, StringConstants::profileProperty()));
+ state.evaluator().stringValue(
+ item, StringConstants::profileProperty()));
projectContext->project->setProjectProperties(projectProperties);
return;
}
@@ -515,1484 +362,200 @@ void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectCo
continue;
const ValueConstPtr v = item->property(it.key());
QBS_ASSERT(v && v->type() != Value::ItemValueType, continue);
- const ScopedJsValue sv(engine->context(), evaluator.value(item, it.key()));
+ const ScopedJsValue sv(engine->context(), state.evaluator().value(item, it.key()));
projectProperties.insert(it.key(), getJsVariant(engine->context(), sv));
}
projectContext->project->setProjectProperties(projectProperties);
- static const ItemFuncMap mapping = {
- { ItemType::Project, &ProjectResolver::Private::resolveProject },
- { ItemType::SubProject, &ProjectResolver::Private::resolveSubProject },
- { ItemType::Product, &ProjectResolver::Private::resolveProduct },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem }
- };
-
for (Item * const child : item->children()) {
+ state.topLevelProject().checkCancelation();
try {
- callItemFunction(mapping, child, projectContext);
+ switch (child->type()) {
+ case ItemType::SubProject:
+ resolveSubProject(child, projectContext);
+ break;
+ case ItemType::FileTagger:
+ resolveFileTagger(state, child, projectContext, nullptr);
+ break;
+ case ItemType::JobLimit:
+ resolveJobLimit(state, child, projectContext, nullptr, nullptr);
+ break;
+ case ItemType::Rule:
+ resolveRule(state, child, projectContext, nullptr, nullptr);
+ break;
+ default:
+ break;
+ }
} catch (const ErrorInfo &e) {
- queuedErrors.push_back(e);
+ state.topLevelProject().addQueuedError(e);
}
}
- for (const ResolvedProductPtr &product : projectContext->project->products)
- postProcess(product, projectContext);
+ for (ProjectContext * const childContext : projectContext->children)
+ resolveProject(childContext);
}
-void ProjectResolver::Private::resolveSubProject(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveSubProject(Item *item, ProjectContext *projectContext)
{
- ResolverProjectContext subProjectContext = createProjectContext(projectContext);
-
- Item * const projectItem = item->child(ItemType::Project);
- if (projectItem) {
- resolveProject(projectItem, &subProjectContext);
+ // If we added a Project child item in ProductsCollector, then the sub-project will be
+ // resolved normally via resolveProject(). Otherwise, it was not loaded, for instance
+ // because its Properties condition was false, and special handling applies.
+ if (item->child(ItemType::Project))
return;
- }
- // No project item was found, which means the project was disabled.
- subProjectContext.project->enabled = false;
- Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject);
- if (propertiesItem) {
- subProjectContext.project->name = evaluator.stringValue(
- propertiesItem, StringConstants::nameProperty());
+ ResolvedProjectPtr project = ResolvedProject::create();
+ project->enabled = false;
+ project->parentProject = projectContext->project;
+ projectContext->project->subProjects << project;
+ if (Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject)) {
+ project->name = state.evaluator().stringValue(propertiesItem,
+ StringConstants::nameProperty());
}
}
-void ProjectResolver::Private::resolveProduct(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::checkOverriddenValues()
{
- checkCancelation();
- evaluator.clearPropertyDependencies();
- ResolverProductContext productContext;
- productContext.item = item;
- ResolvedProductPtr product = ResolvedProduct::create();
- product->enabled = projectContext->project->enabled;
- product->moduleProperties = PropertyMapInternal::create();
- product->project = projectContext->project;
- productContext.product = product;
- product->location = item->location();
- class ProductContextSwitcher {
- public:
- ProductContextSwitcher(ProjectResolver::Private &resolver, ResolverProductContext &newContext,
- ProgressObserver *progressObserver)
- : m_resolver(resolver), m_progressObserver(progressObserver) {
- QBS_CHECK(!m_resolver.productContext);
- m_resolver.productContext = &newContext;
- }
- ~ProductContextSwitcher() {
- if (m_progressObserver)
- m_progressObserver->incrementProgressValue();
- m_resolver.productContext = nullptr;
- }
- private:
- ProjectResolver::Private &m_resolver;
- ProgressObserver * const m_progressObserver;
- } contextSwitcher(*this, productContext, progressObserver);
- const auto errorFromDelayedError = [&] {
- ProductInfo &pi = loadResult.productInfos[item];
- if (pi.delayedError.hasError()) {
- ErrorInfo errorInfo;
-
- // First item is "main error", gets prepended again in the catch clause.
- const QList<ErrorItem> &items = pi.delayedError.items();
- for (int i = 1; i < items.size(); ++i)
- errorInfo.append(items.at(i));
-
- pi.delayedError.clear();
- return errorInfo;
- }
- return ErrorInfo();
+ static const auto matchesPrefix = [](const QString &key) {
+ static const QStringList prefixes({StringConstants::projectPrefix(),
+ QStringLiteral("projects"),
+ QStringLiteral("products"), QStringLiteral("modules"),
+ StringConstants::moduleProviders(),
+ StringConstants::qbsModule()});
+ return any_of(prefixes, [&key](const QString &prefix) {
+ return key.startsWith(prefix + QLatin1Char('.')); });
};
-
- // Even if we previously encountered an error, try to continue for as long as possible
- // to provide IDEs with useful data (e.g. the list of files).
- // If we encounter a follow-up error, suppress it and report the original one instead.
- try {
- resolveProductFully(item, projectContext);
- if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
- throw error;
- } catch (ErrorInfo e) {
- if (const ErrorInfo error = errorFromDelayedError(); error.hasError())
- e = error;
- QString mainErrorString = !product->name.isEmpty()
- ? Tr::tr("Error while handling product '%1':").arg(product->name)
- : Tr::tr("Error while handling product:");
- ErrorInfo fullError(mainErrorString, item->location());
- appendError(fullError, e);
- if (!product->enabled) {
- qCDebug(lcProjectResolver) << fullError.toString();
- return;
- }
- if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw fullError;
- logger.printWarning(fullError);
- logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
- .arg(product->name), item->location()));
- product->enabled = false;
- }
-}
-
-void ProjectResolver::Private::resolveProductFully(Item *item, ResolverProjectContext *projectContext)
-{
- const ResolvedProductPtr product = productContext->product;
- projectContext->project->products.push_back(product);
- product->name = evaluator.stringValue(item, StringConstants::nameProperty());
-
- // product->buildDirectory() isn't valid yet, because the productProperties map is not ready.
- productContext->buildDirectory
- = evaluator.stringValue(item, StringConstants::buildDirectoryProperty());
- product->multiplexConfigurationId
- = evaluator.stringValue(item, StringConstants::multiplexConfigurationIdProperty());
- qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
- productsByItem.insert(item, product);
- product->enabled = product->enabled
- && evaluator.boolValue(item, StringConstants::conditionProperty());
- ProductInfo &pi = loadResult.productInfos[item];
- gatherProductTypes(product.get(), item);
- product->targetName = evaluator.stringValue(item, StringConstants::targetNameProperty());
- product->sourceDirectory = evaluator.stringValue(
- item, StringConstants::sourceDirectoryProperty());
- product->destinationDirectory = evaluator.stringValue(
- item, StringConstants::destinationDirProperty());
-
- if (product->destinationDirectory.isEmpty()) {
- product->destinationDirectory = productContext->buildDirectory;
- } else {
- product->destinationDirectory = FileInfo::resolvePath(
- product->topLevelProject()->buildDirectory,
- product->destinationDirectory);
- }
- product->probes = pi.probes;
- createProductConfig(product.get());
- product->productProperties.insert(StringConstants::destinationDirProperty(),
- product->destinationDirectory);
- ModuleProperties::init(evaluator.engine(), evaluator.scriptValue(item), product.get());
-
- QList<Item *> subItems = item->children();
- const ValuePtr filesProperty = item->property(StringConstants::filesProperty());
- if (filesProperty) {
- Item *fakeGroup = Item::create(item->pool(), ItemType::Group);
- fakeGroup->setFile(item->file());
- fakeGroup->setLocation(item->location());
- fakeGroup->setScope(item);
- fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name));
- fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty);
- fakeGroup->setProperty(StringConstants::excludeFilesProperty(),
- item->property(StringConstants::excludeFilesProperty()));
- fakeGroup->setProperty(StringConstants::overrideTagsProperty(),
- VariantValue::falseValue());
- fakeGroup->setupForBuiltinType(setupParams.deprecationWarningMode(), logger);
- subItems.prepend(fakeGroup);
- }
-
- static const ItemFuncMap mapping = {
- { ItemType::Depends, &ProjectResolver::Private::ignoreItem },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Group, &ProjectResolver::Private::resolveGroup },
- { ItemType::Product, &ProjectResolver::Private::resolveShadowProduct },
- { ItemType::Export, &ProjectResolver::Private::resolveExport },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem }
- };
-
- for (Item * const child : std::as_const(subItems))
- callItemFunction(mapping, child, projectContext);
-
- for (const ResolverProjectContext *p = projectContext; p; p = p->parentContext) {
- JobLimits tempLimits = p->jobLimits;
- product->jobLimits = tempLimits.update(product->jobLimits);
- }
-
- resolveModules(item, projectContext);
-}
-
-void ProjectResolver::Private::resolveModules(const Item *item, ResolverProjectContext *projectContext)
-{
- JobLimits jobLimits;
- for (const Item::Module &m : item->modules()) {
- resolveModule(m.name, m.item, m.productInfo.has_value(), m.parameters, jobLimits,
- projectContext);
- }
- for (int i = 0; i < jobLimits.count(); ++i) {
- const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i);
- if (productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
- productContext->product->jobLimits.setJobLimit(moduleJobLimit);
- }
-}
-
-void ProjectResolver::Private::resolveModule(
- const QualifiedId &moduleName, Item *item, bool isProduct, const QVariantMap &parameters,
- JobLimits &jobLimits, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- if (!item->isPresentModule())
- return;
-
- ModuleContext * const oldModuleContext = moduleContext;
- ModuleContext newModuleContext;
- newModuleContext.module = ResolvedModule::create();
- moduleContext = &newModuleContext;
-
- const ResolvedModulePtr &module = newModuleContext.module;
- module->name = moduleName.toString();
- module->isProduct = isProduct;
- module->product = productContext->product.get();
- module->setupBuildEnvironmentScript.initialize(
- scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty()));
- module->setupRunEnvironmentScript.initialize(
- scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty()));
-
- for (const Item::Module &m : item->modules()) {
- if (m.item->isPresentModule())
- module->moduleDependencies += m.name.toString();
- }
-
- productContext->product->modules.push_back(module);
- if (!parameters.empty())
- productContext->product->moduleParameters[module] = parameters;
-
- static const ItemFuncMap mapping {
- { ItemType::Group, &ProjectResolver::Private::ignoreItem },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Scanner, &ProjectResolver::Private::resolveScanner },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem },
- { ItemType::Depends, &ProjectResolver::Private::ignoreItem },
- { ItemType::Parameter, &ProjectResolver::Private::ignoreItem },
- { ItemType::Properties, &ProjectResolver::Private::ignoreItem },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem }
- };
- for (Item *child : item->children())
- callItemFunction(mapping, child, projectContext);
- for (int i = 0; i < newModuleContext.jobLimits.count(); ++i) {
- const JobLimit &newJobLimit = newModuleContext.jobLimits.jobLimitAt(i);
- const int oldLimit = jobLimits.getLimit(newJobLimit.pool());
- if (oldLimit == -1 || oldLimit > newJobLimit.limit())
- jobLimits.setJobLimit(newJobLimit);
- }
-
- moduleContext = oldModuleContext; // FIXME: Exception safety
-}
-
-void ProjectResolver::Private::gatherProductTypes(ResolvedProduct *product, Item *item)
-{
- const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty());
- if (type)
- product->fileTags = FileTags::fromStringList(type->value().toStringList());
-}
-
-SourceArtifactPtr ProjectResolver::Private::createSourceArtifact(
- const ResolvedProductPtr &rproduct, const QString &fileName, const GroupPtr &group,
- bool wildcard, const CodeLocation &filesLocation, FileLocations *fileLocations,
- ErrorInfo *errorInfo)
-{
- const QString &baseDir = FileInfo::path(group->location.filePath());
- const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName));
- if (!wildcard && !FileInfo(absFilePath).exists()) {
- if (errorInfo)
- errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation);
- rproduct->missingSourceFiles << absFilePath;
- return {};
- }
- if (group->enabled && fileLocations) {
- CodeLocation &loc = (*fileLocations)[std::make_pair(group->targetOfModule, absFilePath)];
- if (loc.isValid()) {
- if (errorInfo) {
- errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath));
- errorInfo->append(Tr::tr("First occurrence is here."), loc);
- errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation);
- }
- return {};
- }
- loc = filesLocation;
- }
- SourceArtifactPtr artifact = SourceArtifactInternal::create();
- artifact->absoluteFilePath = absFilePath;
- artifact->fileTags = group->fileTags;
- artifact->overrideFileTags = group->overrideTags;
- artifact->properties = group->properties;
- artifact->targetOfModule = group->targetOfModule;
- (wildcard ? group->wildcards->files : group->files).push_back(artifact);
- return artifact;
-}
-
-void ProjectResolver::Private::finalizeProjectParameters()
-{
- if (setupParams.topLevelProfile().isEmpty()) {
- Settings settings(setupParams.settingsDirectory());
- QString profileName = settings.defaultProfile();
- if (profileName.isEmpty()) {
- logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. "
- "Using default property values.");
- profileName = Profile::fallbackName();
- }
- setupParams.setTopLevelProfile(profileName);
- setupParams.expandBuildConfiguration();
- }
- setupParams.finalizeProjectFilePath();
- QBS_CHECK(QFileInfo(setupParams.projectFilePath()).isAbsolute());
-}
-
-static QualifiedIdSet propertiesToEvaluate(std::deque<QualifiedId> initialProps,
- const PropertyDependencies &deps)
-{
- std::deque<QualifiedId> remainingProps = std::move(initialProps);
- QualifiedIdSet allProperties;
- while (!remainingProps.empty()) {
- const QualifiedId prop = remainingProps.front();
- remainingProps.pop_front();
- const auto insertResult = allProperties.insert(prop);
- if (!insertResult.second)
+ const QVariantMap &overriddenValues = state.parameters().overriddenValues();
+ for (auto it = overriddenValues.begin(); it != overriddenValues.end(); ++it) {
+ if (matchesPrefix(it.key())) {
+ collectNameFromOverride(it.key());
continue;
- transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; });
- }
- return allProperties;
-}
-
-QVariantMap ProjectResolver::Private::resolveAdditionalModuleProperties(
- const Item *group, const QVariantMap &currentValues)
-{
- // Step 1: Retrieve the properties directly set in the group
- const ModulePropertiesPerGroup &mp = mapValue(
- loadResult.productInfos, productContext->item).modulePropertiesSetInGroups;
- const auto it = mp.find(group);
- if (it == mp.end())
- return {};
- const QualifiedIdSet &propsSetInGroup = it->second;
-
- // Step 2: Gather all properties that depend on these properties.
- const QualifiedIdSet &propsToEval = propertiesToEvaluate(
- rangeTo<std::deque<QualifiedId>>(propsSetInGroup),
- evaluator.propertyDependencies());
-
- // Step 3: Evaluate all these properties and replace their values in the map
- QVariantMap modulesMap = currentValues;
- QHash<QString, QStringList> propsPerModule;
- for (auto fullPropName : propsToEval) {
- const QString moduleName
- = QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString();
- propsPerModule[moduleName] << fullPropName.last();
- }
- EvalCacheEnabler cachingEnabler(&evaluator, productContext->product->sourceDirectory);
- for (const Item::Module &module : group->modules()) {
- const QString &fullModName = module.name.toString();
- const QStringList propsForModule = propsPerModule.take(fullModName);
- if (propsForModule.empty())
- continue;
- QVariantMap reusableValues = modulesMap.value(fullModName).toMap();
- for (const QString &prop : std::as_const(propsForModule))
- reusableValues.remove(prop);
- modulesMap.insert(fullModName,
- evaluateProperties(module.item, module.item, reusableValues, true, true));
- }
- return modulesMap;
-}
-
-void ProjectResolver::Private::resolveGroup(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- const bool parentEnabled = productContext->currentGroup
- ? productContext->currentGroup->enabled
- : productContext->product->enabled;
- const bool isEnabled = parentEnabled
- && evaluator.boolValue(item, StringConstants::conditionProperty());
- try {
- resolveGroupFully(item, projectContext, isEnabled);
- } catch (const ErrorInfo &error) {
- if (!isEnabled) {
- qCDebug(lcProjectResolver) << "error resolving group at" << item->location()
- << error.toString();
- return;
- }
- if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- logger.printWarning(error);
- }
-}
-
-void ProjectResolver::Private::resolveGroupFully(
- Item *item, ResolverProjectContext *projectContext, bool isEnabled)
-{
- AccumulatingTimer groupTimer(setupParams.logElapsedTime() ? &elapsedTimeGroups : nullptr);
-
- const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) {
- PropertyMapPtr moduleProperties;
- bool newPropertyMapRequired = false;
- if (existingProps)
- moduleProperties = existingProps->propertyMap();
- if (!moduleProperties) {
- newPropertyMapRequired = true;
- moduleProperties = productContext->currentGroup
- ? productContext->currentGroup->properties
- : productContext->product->moduleProperties;
}
- const QVariantMap newModuleProperties
- = resolveAdditionalModuleProperties(item, moduleProperties->value());
- if (!newModuleProperties.empty()) {
- if (newPropertyMapRequired)
- moduleProperties = PropertyMapInternal::create();
- moduleProperties->setValue(newModuleProperties);
- }
- return moduleProperties;
- };
-
- QStringList files = evaluator.stringListValue(item, StringConstants::filesProperty());
- bool fileTagsSet;
- const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty(),
- &fileTagsSet);
- const QStringList fileTagsFilter
- = evaluator.stringListValue(item, StringConstants::fileTagsFilterProperty());
- if (!fileTagsFilter.empty()) {
- if (Q_UNLIKELY(!files.empty()))
- throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."),
- item->location());
- if (!isEnabled)
- return;
-
- ResolverProductContext::ArtifactPropertiesInfo &apinfo
- = productContext->artifactPropertiesPerFilter[fileTagsFilter];
- if (apinfo.first) {
- const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(),
- [item](const CodeLocation &loc) {
- return item->location().filePath() == loc.filePath();
- });
- if (it != apinfo.second.cend()) {
- ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items."));
- error.append(Tr::tr("First item"), *it);
- error.append(Tr::tr("Second item"), item->location());
- throw error;
- }
- } else {
- apinfo.first = ArtifactProperties::create();
- apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter));
- productContext->product->artifactProperties.push_back(apinfo.first);
- }
- apinfo.second.push_back(item->location());
- apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get()));
- apinfo.first->addExtraFileTags(fileTags);
- return;
+ ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(it.key()));
+ e.append(Tr::tr("Please use one of the following:"));
+ e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>."
+ "<property-name>:value"));
+ e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>."
+ "<property-name>:value"));
+ handlePropertyError(e, state.parameters(), state.logger());
}
- QStringList patterns;
- for (int i = files.size(); --i >= 0;) {
- if (FileInfo::isPattern(files[i]))
- patterns.push_back(files.takeAt(i));
- }
- GroupPtr group = ResolvedGroup::create();
- bool prefixWasSet = false;
- group->prefix = evaluator.stringValue(item, StringConstants::prefixProperty(), QString(),
- &prefixWasSet);
- if (!prefixWasSet && productContext->currentGroup)
- group->prefix = productContext->currentGroup->prefix;
- if (!group->prefix.isEmpty()) {
- for (auto it = files.rbegin(), end = files.rend(); it != end; ++it)
- it->prepend(group->prefix);
- }
- group->location = item->location();
- group->enabled = isEnabled;
- group->properties = getGroupPropertyMap(nullptr);
- group->fileTags = fileTags;
- group->overrideTags = evaluator.boolValue(item, StringConstants::overrideTagsProperty());
- if (group->overrideTags && fileTagsSet) {
- if (group->fileTags.empty() )
- group->fileTags.insert(unknownFileTag());
- } else if (productContext->currentGroup) {
- group->fileTags.unite(productContext->currentGroup->fileTags);
- }
-
- const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location();
- const VariantValueConstPtr moduleProp = item->variantProperty(
- StringConstants::modulePropertyInternal());
- if (moduleProp)
- group->targetOfModule = moduleProp->value().toString();
- ErrorInfo fileError;
- if (!patterns.empty()) {
- group->wildcards = std::make_unique<SourceWildCards>();
- SourceWildCards *wildcards = group->wildcards.get();
- wildcards->group = group.get();
- wildcards->excludePatterns = evaluator.stringListValue(
- item, StringConstants::excludeFilesProperty());
- wildcards->patterns = patterns;
- const Set<QString> files = wildcards->expandPatterns(group,
- FileInfo::path(item->file()->filePath()),
- projectContext->project->topLevelProject()->buildDirectory);
- for (const QString &fileName : files)
- createSourceArtifact(productContext->product, fileName, group, true, filesLocation,
- &productContext->sourceArtifactLocations, &fileError);
- }
-
- for (const QString &fileName : std::as_const(files)) {
- createSourceArtifact(productContext->product, fileName, group, false, filesLocation,
- &productContext->sourceArtifactLocations, &fileError);
- }
- if (fileError.hasError()) {
- if (group->enabled) {
- if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw ErrorInfo(fileError);
- logger.printWarning(fileError);
- } else {
- qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString();
- }
- }
- group->name = evaluator.stringValue(item, StringConstants::nameProperty());
- if (group->name.isEmpty())
- group->name = Tr::tr("Group %1").arg(productContext->product->groups.size());
- productContext->product->groups.push_back(group);
-
- class GroupContextSwitcher {
- public:
- GroupContextSwitcher(ResolverProductContext &context, const GroupConstPtr &newGroup)
- : m_context(context), m_oldGroup(context.currentGroup) {
- m_context.currentGroup = newGroup;
- }
- ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; }
- private:
- ResolverProductContext &m_context;
- const GroupConstPtr m_oldGroup;
- };
- GroupContextSwitcher groupSwitcher(*productContext, group);
- for (Item * const childItem : item->children())
- resolveGroup(childItem, projectContext);
}
-void ProjectResolver::Private::adaptExportedPropertyValues(const Item *shadowProductItem)
+void ProjectResolver::Private::collectNameFromOverride(const QString &overrideString)
{
- ExportedModule &m = productContext->product->exportedModule;
- const QVariantList prefixList = m.propertyValues.take(
- StringConstants::prefixMappingProperty()).toList();
- const QString shadowProductName = evaluator.stringValue(
- shadowProductItem, StringConstants::nameProperty());
- const QString shadowProductBuildDir = evaluator.stringValue(
- shadowProductItem, StringConstants::buildDirectoryProperty());
- QVariantMap prefixMap;
- for (const QVariant &v : prefixList) {
- const QVariantMap o = v.toMap();
- prefixMap.insert(o.value(QStringLiteral("prefix")).toString(),
- o.value(QStringLiteral("replacement")).toString());
- }
- const auto valueRefersToImportingProduct
- = [shadowProductName, shadowProductBuildDir](const QString &value) {
- return value.toLower().contains(shadowProductName.toLower())
- || value.contains(shadowProductBuildDir);
+ const auto extract = [&overrideString](const QString &prefix) {
+ if (!overrideString.startsWith(prefix))
+ return QString();
+ const int startPos = prefix.length();
+ const int endPos = overrideString.lastIndexOf(StringConstants::dot());
+ if (endPos == -1)
+ return QString();
+ return overrideString.mid(startPos, endPos - startPos);
};
- static const auto stringMapper = [](const QVariantMap &mappings, const QString &value)
- -> QString {
- for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) {
- if (value.startsWith(it.key()))
- return it.value().toString() + value.mid(it.key().size());
- }
- return value;
- };
- const auto stringListMapper = [&valueRefersToImportingProduct](
- const QVariantMap &mappings, const QStringList &value) -> QStringList {
- QStringList result;
- result.reserve(value.size());
- for (const QString &s : value) {
- if (!valueRefersToImportingProduct(s))
- result.push_back(stringMapper(mappings, s));
- }
- return result;
- };
- const std::function<QVariant(const QVariantMap &, const QVariant &)> mapper
- = [&stringListMapper, &mapper](
- const QVariantMap &mappings, const QVariant &value) -> QVariant {
- switch (static_cast<QMetaType::Type>(value.userType())) {
- case QMetaType::QString:
- return stringMapper(mappings, value.toString());
- case QMetaType::QStringList:
- return stringListMapper(mappings, value.toStringList());
- case QMetaType::QVariantMap: {
- QVariantMap m = value.toMap();
- for (auto it = m.begin(); it != m.end(); ++it)
- it.value() = mapper(mappings, it.value());
- return m;
- }
- default:
- return value;
- }
- };
- for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it)
- it.value() = mapper(prefixMap, it.value());
- for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it)
- it.value() = mapper(prefixMap, it.value());
- for (ExportedModuleDependency &dep : m.moduleDependencies) {
- for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it)
- it.value() = mapper(prefixMap, it.value());
- }
-}
-
-void ProjectResolver::Private::collectExportedProductDependencies()
-{
- ResolvedProductPtr dummyProduct = ResolvedProduct::create();
- dummyProduct->enabled = false;
- for (const auto &exportingProductInfo : std::as_const(productExportInfo)) {
- const ResolvedProductPtr exportingProduct = exportingProductInfo.first;
- if (!exportingProduct->enabled)
- continue;
- Item * const importingProductItem = exportingProductInfo.second;
-
- std::vector<std::pair<ResolvedProductPtr, QVariantMap>> directDeps;
- for (const Item::Module &m : importingProductItem->modules()) {
- if (m.name.toString() != exportingProduct->name)
- continue;
- for (const Item::Module &dep : m.item->modules()) {
- if (dep.productInfo) {
- directDeps.emplace_back(productsByItem.value(dep.productInfo->item),
- m.parameters);
- }
- }
- }
- for (const auto &dep : directDeps) {
- if (!contains(exportingProduct->exportedModule.productDependencies,
- dep.first->uniqueName())) {
- exportingProduct->exportedModule.productDependencies.push_back(
- dep.first->uniqueName());
- }
- if (!dep.second.isEmpty()) {
- exportingProduct->exportedModule.dependencyParameters.insert(dep.first,
- dep.second);
- }
- }
- auto &productDeps = exportingProduct->exportedModule.productDependencies;
- std::sort(productDeps.begin(), productDeps.end());
- }
-}
-
-void ProjectResolver::Private::resolveShadowProduct(Item *item, ResolverProjectContext *)
-{
- if (!productContext->product->enabled)
+ const QString &projectName = extract(StringConstants::projectsOverridePrefix());
+ if (!projectName.isEmpty()) {
+ state.topLevelProject().addProjectNameUsedInOverrides(projectName);
return;
- for (const auto &m : item->modules()) {
- if (m.name.toString() != productContext->product->name)
- continue;
- collectPropertiesForExportItem(m.item);
- for (const auto &dep : m.item->modules())
- collectPropertiesForModuleInExportItem(dep);
- break;
- }
- try {
- adaptExportedPropertyValues(item);
- } catch (const ErrorInfo &) {}
- productExportInfo.emplace_back(productContext->product, item);
-}
-
-void ProjectResolver::Private::setupExportedProperties(
- const Item *item, const QString &namePrefix, std::vector<ExportedProperty> &properties)
-{
- const auto &props = item->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- const QString qualifiedName = namePrefix.isEmpty()
- ? it.key() : namePrefix + QLatin1Char('.') + it.key();
- if ((item->type() == ItemType::Export || item->type() == ItemType::Properties)
- && qualifiedName == StringConstants::prefixMappingProperty()) {
- continue;
- }
- const ValuePtr &v = it.value();
- if (v->type() == Value::ItemValueType) {
- setupExportedProperties(std::static_pointer_cast<ItemValue>(v)->item(),
- qualifiedName, properties);
- continue;
- }
- ExportedProperty exportedProperty;
- exportedProperty.fullName = qualifiedName;
- exportedProperty.type = item->propertyDeclaration(it.key()).type();
- if (v->type() == Value::VariantValueType) {
- exportedProperty.sourceCode = toJSLiteral(
- std::static_pointer_cast<VariantValue>(v)->value());
- } else {
- QBS_CHECK(v->type() == Value::JSSourceValueType);
- const JSSourceValue * const sv = static_cast<JSSourceValue *>(v.get());
- exportedProperty.sourceCode = sv->sourceCode().toString();
- }
- const ItemDeclaration itemDecl
- = BuiltinDeclarations::instance().declarationsForType(item->type());
- PropertyDeclaration propertyDecl;
- const auto itemProperties = itemDecl.properties();
- for (const PropertyDeclaration &decl : itemProperties) {
- if (decl.name() == it.key()) {
- propertyDecl = decl;
- exportedProperty.isBuiltin = true;
- break;
- }
- }
-
- // Do not add built-in properties that were left at their default value.
- if (!exportedProperty.isBuiltin || evaluator.isNonDefaultValue(item, it.key()))
- properties.push_back(exportedProperty);
}
-
- // Order the list of properties, so the output won't look so random.
- static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool {
- const int p1ComponentCount = p1.fullName.count(QLatin1Char('.'));
- const int p2ComponentCount = p2.fullName.count(QLatin1Char('.'));
- if (p1.isBuiltin && !p2.isBuiltin)
- return true;
- if (!p1.isBuiltin && p2.isBuiltin)
- return false;
- if (p1ComponentCount < p2ComponentCount)
- return true;
- if (p1ComponentCount > p2ComponentCount)
- return false;
- return p1.fullName < p2.fullName;
- };
- std::sort(properties.begin(), properties.end(), less);
-}
-
-static bool usesImport(const ExportedProperty &prop, const QRegularExpression &regex)
-{
- return prop.sourceCode.indexOf(regex) != -1;
-}
-
-static bool usesImport(const ExportedItem &item, const QRegularExpression &regex)
-{
- return any_of(item.properties,
- [regex](const ExportedProperty &p) { return usesImport(p, regex); })
- || any_of(item.children,
- [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); });
-}
-
-static bool usesImport(const ExportedModule &module, const QString &name)
-{
- // Imports are used in three ways:
- // (1) var f = new TextFile(...);
- // (2) var path = FileInfo.joinPaths(...)
- // (3) var obj = DataCollection;
- const QString pattern = QStringLiteral("\\b%1\\b");
-
- const QRegularExpression regex(pattern.arg(name)); // std::regex is much slower
- return any_of(module.m_properties,
- [regex](const ExportedProperty &p) { return usesImport(p, regex); })
- || any_of(module.children,
- [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); });
-}
-
-static QString getLineAtLocation(const CodeLocation &loc, const QString &content)
-{
- int pos = 0;
- int currentLine = 1;
- while (currentLine < loc.line()) {
- while (content.at(pos++) != QLatin1Char('\n'))
- ;
- ++currentLine;
- }
- const int eolPos = content.indexOf(QLatin1Char('\n'), pos);
- return content.mid(pos, eolPos - pos);
-}
-
-void ProjectResolver::Private::resolveExport(Item *exportItem, ResolverProjectContext *)
-{
- ExportedModule &exportedModule = productContext->product->exportedModule;
- setupExportedProperties(exportItem, QString(), exportedModule.m_properties);
- static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) {
- return p1.fullName < p2.fullName;
- };
- std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc);
-
- transform(exportItem->children(), exportedModule.children,
- [&exportedModule, this](const auto &child) {
- return resolveExportChild(child, exportedModule); });
-
- for (const JsImport &jsImport : exportItem->file()->jsImports()) {
- if (usesImport(exportedModule, jsImport.scopeName)) {
- exportedModule.importStatements << getLineAtLocation(jsImport.location,
- exportItem->file()->content());
- }
- }
- const auto builtInImports = JsExtensions::extensionNames();
- for (const QString &builtinImport: builtInImports) {
- if (usesImport(exportedModule, builtinImport))
- exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport;
- }
- exportedModule.importStatements.sort();
-}
-
-// TODO: This probably wouldn't be necessary if we had item serialization.
-std::unique_ptr<ExportedItem> ProjectResolver::Private::resolveExportChild(
- const Item *item, const ExportedModule &module)
-{
- std::unique_ptr<ExportedItem> exportedItem(new ExportedItem);
-
- // This is the type of the built-in base item. It may turn out that we need to support
- // derived items under Export. In that case, we probably need a new Item member holding
- // the original type name.
- exportedItem->name = item->typeName();
-
- transform(item->children(), exportedItem->children, [&module, this](const auto &child) {
- return resolveExportChild(child, module); });
-
- setupExportedProperties(item, QString(), exportedItem->properties);
- return exportedItem;
-}
-
-QString ProjectResolver::Private::sourceCodeAsFunction(const JSSourceValueConstPtr &value,
- const PropertyDeclaration &decl) const
-{
- QString &scriptFunction = scriptFunctions[std::make_pair(value->sourceCode(),
- decl.functionArgumentNames())];
- if (!scriptFunction.isNull())
- return scriptFunction;
- const QString args = decl.functionArgumentNames().join(QLatin1Char(','));
- if (value->hasFunctionForm()) {
- // Insert the argument list.
- scriptFunction = value->sourceCodeForEvaluation();
- scriptFunction.insert(10, args);
- // Remove the function application "()" that has been
- // added in ItemReaderASTVisitor::visitStatement.
- scriptFunction.chop(2);
- } else {
- scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ")
- + value->sourceCode().toString() + QLatin1String(";})");
- }
- return scriptFunction;
-}
-
-QString ProjectResolver::Private::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const
-{
- QString &code = sourceCode[value->sourceCode()];
- if (!code.isNull())
- return code;
- code = value->sourceCodeForEvaluation();
- return code;
-}
-
-ScriptFunctionPtr ProjectResolver::Private::scriptFunctionValue(
- Item *item, const QString &name) const
-{
- JSSourceValuePtr value = item->sourceProperty(name);
- ScriptFunctionPtr &script = scriptFunctionMap[value ? value->location() : CodeLocation()];
- if (!script.get()) {
- script = ScriptFunction::create();
- const PropertyDeclaration decl = item->propertyDeclaration(name);
- script->sourceCode = sourceCodeAsFunction(value, decl);
- script->location = value->location();
- script->fileContext = resolvedFileContext(value->file());
- }
- return script;
-}
-
-ResolvedFileContextPtr ProjectResolver::Private::resolvedFileContext(
- const FileContextConstPtr &ctx) const
-{
- ResolvedFileContextPtr &result = fileContextMap[ctx];
- if (!result)
- result = ResolvedFileContext::create(*ctx);
- return result;
-}
-
-void ProjectResolver::Private::resolveRule(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
-
- if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ const QString &productName = extract(StringConstants::productsOverridePrefix());
+ if (!productName.isEmpty()) {
+ state.topLevelProject().addProductNameUsedInOverrides(productName.left(
+ productName.indexOf(StringConstants::dot())));
return;
-
- RulePtr rule = Rule::create();
-
- // read artifacts
- bool hasArtifactChildren = false;
- for (Item * const child : item->children()) {
- if (Q_UNLIKELY(child->type() != ItemType::Artifact)) {
- throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."),
- child->location());
- }
- hasArtifactChildren = true;
- resolveRuleArtifact(rule, child);
- }
-
- rule->name = evaluator.stringValue(item, StringConstants::nameProperty());
- rule->prepareScript.initialize(
- scriptFunctionValue(item, StringConstants::prepareProperty()));
- rule->outputArtifactsScript.initialize(
- scriptFunctionValue(item, StringConstants::outputArtifactsProperty()));
- rule->outputFileTags = evaluator.fileTagsValue(item, StringConstants::outputFileTagsProperty());
- if (rule->outputArtifactsScript.isValid()) {
- if (hasArtifactChildren)
- throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules "
- "that contain Artifact items."),
- item->location());
- }
- if (!hasArtifactChildren && rule->outputFileTags.empty()) {
- throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty "
- "outputFileTags property."), item->location());
- }
- rule->multiplex = evaluator.boolValue(item, StringConstants::multiplexProperty());
- rule->alwaysRun = evaluator.boolValue(item, StringConstants::alwaysRunProperty());
- rule->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
- rule->inputsFromDependencies
- = evaluator.fileTagsValue(item, StringConstants::inputsFromDependenciesProperty());
- bool requiresInputsSet = false;
- rule->requiresInputs = evaluator.boolValue(item, StringConstants::requiresInputsProperty(),
- &requiresInputsSet);
- if (!requiresInputsSet)
- rule->requiresInputs = rule->declaresInputs();
- rule->auxiliaryInputs
- = evaluator.fileTagsValue(item, StringConstants::auxiliaryInputsProperty());
- rule->excludedInputs
- = evaluator.fileTagsValue(item, StringConstants::excludedInputsProperty());
- if (rule->excludedInputs.empty()) {
- rule->excludedInputs = evaluator.fileTagsValue(
- item, StringConstants::excludedAuxiliaryInputsProperty());
- }
- rule->explicitlyDependsOn
- = evaluator.fileTagsValue(item, StringConstants::explicitlyDependsOnProperty());
- rule->explicitlyDependsOnFromDependencies = evaluator.fileTagsValue(
- item, StringConstants::explicitlyDependsOnFromDependenciesProperty());
- rule->module = moduleContext ? moduleContext->module : projectContext->dummyModule;
- if (!rule->multiplex && !rule->declaresInputs()) {
- throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."),
- item->location());
- }
- if (!rule->multiplex && !rule->requiresInputs) {
- throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."),
- item->location());
- }
- if (!rule->declaresInputs() && rule->requiresInputs) {
- throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule "
- "does not declare any input tags."), item->location());
- }
- if (productContext) {
- rule->product = productContext->product.get();
- productContext->product->rules.push_back(rule);
- } else {
- projectContext->rules.push_back(rule);
- }
-}
-
-void ProjectResolver::Private::resolveRuleArtifact(const RulePtr &rule, Item *item)
-{
- RuleArtifactPtr artifact = RuleArtifact::create();
- rule->artifacts.push_back(artifact);
- artifact->location = item->location();
-
- if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty()))
- artifact->filePathLocation = sourceProperty->location();
-
- artifact->filePath = verbatimValue(item, StringConstants::filePathProperty());
- artifact->fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
- artifact->alwaysUpdated = evaluator.boolValue(item,
- StringConstants::alwaysUpdatedProperty());
-
- QualifiedIdSet seenBindings;
- for (Item *obj = item; obj; obj = obj->prototype()) {
- for (QMap<QString, ValuePtr>::const_iterator it = obj->properties().constBegin();
- it != obj->properties().constEnd(); ++it)
- {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- resolveRuleArtifactBinding(artifact,
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- QStringList(it.key()), &seenBindings);
- }
}
}
-void ProjectResolver::Private::resolveRuleArtifactBinding(
- const RuleArtifactPtr &ruleArtifact, Item *item, const QStringList &namePrefix,
- QualifiedIdSet *seenBindings)
+void ProjectResolver::Private::loadTopLevelProjectItem()
{
- for (QMap<QString, ValuePtr>::const_iterator it = item->properties().constBegin();
- it != item->properties().constEnd(); ++it)
- {
- const QStringList name = QStringList(namePrefix) << it.key();
- if (it.value()->type() == Value::ItemValueType) {
- resolveRuleArtifactBinding(ruleArtifact,
- std::static_pointer_cast<ItemValue>(it.value())->item(), name,
- seenBindings);
- } else if (it.value()->type() == Value::JSSourceValueType) {
- const auto insertResult = seenBindings->insert(name);
- if (!insertResult.second)
- continue;
- JSSourceValuePtr sourceValue = std::static_pointer_cast<JSSourceValue>(it.value());
- RuleArtifact::Binding rab;
- rab.name = name;
- rab.code = sourceCodeForEvaluation(sourceValue);
- rab.location = sourceValue->location();
- ruleArtifact->bindings.push_back(rab);
- } else {
- QBS_ASSERT(!"unexpected value type", continue);
- }
- }
-}
-
-void ProjectResolver::Private::resolveFileTagger(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ const QStringList topLevelSearchPaths
+ = state.parameters().finalBuildConfigurationTree()
+ .value(StringConstants::projectPrefix()).toMap()
+ .value(StringConstants::qbsSearchPathsProperty()).toStringList();
+ SearchPathsManager searchPathsManager(state.itemReader(), topLevelSearchPaths);
+ Item * const root = state.itemReader().setupItemFromFile(
+ state.parameters().projectFilePath(), {});
+ if (!root)
return;
- std::vector<FileTaggerConstPtr> &fileTaggers = productContext
- ? productContext->product->fileTaggers
- : projectContext->fileTaggers;
- const QStringList patterns = evaluator.stringListValue(item,
- StringConstants::patternsProperty());
- if (patterns.empty())
- throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location());
-
- const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
- if (fileTags.empty())
- throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location());
-
- for (const QString &pattern : patterns) {
- if (pattern.isEmpty())
- throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location());
- }
-
- const int priority = evaluator.intValue(item, StringConstants::priorityProperty());
- fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority));
-}
-void ProjectResolver::Private::resolveJobLimit(Item *item, ResolverProjectContext *projectContext)
-{
- if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
- return;
- const QString jobPool = evaluator.stringValue(item, StringConstants::jobPoolProperty());
- if (jobPool.isEmpty())
- throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.")
- .arg(StringConstants::jobPoolProperty()), item->location());
- bool jobCountWasSet;
- const int jobCount = evaluator.intValue(item, StringConstants::jobCountProperty(), -1,
- &jobCountWasSet);
- if (!jobCountWasSet) {
- throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.")
- .arg(StringConstants::jobCountProperty()), item->location());
- }
- if (jobCount < 0) {
- throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.")
- .arg(StringConstants::jobCountProperty()), item->location());
+ switch (root->type()) {
+ case ItemType::Product:
+ rootProjectItem = state.itemReader().wrapInProjectIfNecessary(root);
+ break;
+ case ItemType::Project:
+ rootProjectItem = root;
+ break;
+ default:
+ throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it"
+ " is of type '%1'.").arg(root->typeName()), root->location());
}
- JobLimits &jobLimits = moduleContext
- ? moduleContext->jobLimits
- : productContext ? productContext->product->jobLimits
- : projectContext->jobLimits;
- JobLimit jobLimit(jobPool, jobCount);
- const int oldLimit = jobLimits.getLimit(jobPool);
- if (oldLimit == -1 || oldLimit > jobCount)
- jobLimits.setJobLimit(jobLimit);
}
-void ProjectResolver::Private::resolveScanner(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::buildProjectTree()
{
- checkCancelation();
- if (!evaluator.boolValue(item, StringConstants::conditionProperty())) {
- qCDebug(lcProjectResolver) << "scanner condition is false";
- return;
- }
+ state.topLevelProject().setBuildDirectory(TopLevelProject::deriveBuildDirectory(
+ state.parameters().buildRoot(),
+ TopLevelProject::deriveId(state.parameters().finalBuildConfigurationTree())));
+ rootProjectItem->setProperty(StringConstants::sourceDirectoryProperty(),
+ VariantValue::create(QFileInfo(rootProjectItem->file()->filePath())
+ .absolutePath()));
+ rootProjectItem->setProperty(StringConstants::buildDirectoryProperty(),
+ VariantValue::create(state.topLevelProject().buildDirectory()));
+ rootProjectItem->setProperty(StringConstants::profileProperty(),
+ VariantValue::create(state.parameters().topLevelProfile()));
+ ProductsCollector(state).run(rootProjectItem);
- ResolvedScannerPtr scanner = ResolvedScanner::create();
- scanner->module = moduleContext ? moduleContext->module : projectContext->dummyModule;
- scanner->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
- scanner->recursive = evaluator.boolValue(item, StringConstants::recursiveProperty());
- scanner->searchPathsScript.initialize(
- scriptFunctionValue(item, StringConstants::searchPathsProperty()));
- scanner->scanScript.initialize(
- scriptFunctionValue(item, StringConstants::scanProperty()));
- productContext->product->scanners.push_back(scanner);
-}
-
-void ProjectResolver::Private::matchArtifactProperties(const ResolvedProductPtr &product,
- const std::vector<SourceArtifactPtr> &artifacts)
-{
- for (const SourceArtifactPtr &artifact : artifacts) {
- for (const auto &artifactProperties : product->artifactProperties) {
- if (!artifact->isTargetOfModule()
- && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) {
- artifact->properties = artifactProperties->propertyMap();
- }
- }
- }
+ AccumulatingTimer timer(state.parameters().logElapsedTime()
+ ? &state.topLevelProject().timingData().propertyChecking : nullptr);
+ checkPropertyDeclarations(rootProjectItem, state);
}
void ProjectResolver::Private::printProfilingInfo()
{
if (!setupParams.logElapsedTime())
return;
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("All property evaluation took %1.")
- .arg(elapsedTimeString(elapsedTimeAllPropEval));
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("Module property evaluation took %1.")
- .arg(elapsedTimeString(elapsedTimeModPropEval));
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("Resolving groups (without module property "
- "evaluation) took %1.")
- .arg(elapsedTimeString(elapsedTimeGroups));
-}
-
-class TempScopeSetter
-{
-public:
- TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope())
- {
- value->setScope(newScope, {});
- }
- ~TempScopeSetter() { if (m_value) m_value->setScope(m_oldScope, {}); }
-
- TempScopeSetter(const TempScopeSetter &) = delete;
- TempScopeSetter &operator=(const TempScopeSetter &) = delete;
- TempScopeSetter &operator=(TempScopeSetter &&) = delete;
-
- TempScopeSetter(TempScopeSetter &&other) noexcept
- : m_value(std::move(other.m_value)), m_oldScope(other.m_oldScope)
- {
- other.m_value.reset();
- other.m_oldScope = nullptr;
- }
-
-private:
- ValuePtr m_value;
- Item *m_oldScope;
-};
-
-void ProjectResolver::Private::collectPropertiesForExportItem(Item *productModuleInstance)
-{
- if (!productModuleInstance->isPresentModule())
- return;
- Item * const exportItem = productModuleInstance->prototype();
- QBS_CHECK(exportItem);
- QBS_CHECK(exportItem->type() == ItemType::Export);
- const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance()
- .declarationsForType(ItemType::Export).properties();
- ExportedModule &exportedModule = productContext->product->exportedModule;
- const auto &props = exportItem->properties();
- for (auto it = props.begin(); it != props.end(); ++it) {
- const auto match
- = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); };
- if (it.key() != StringConstants::prefixMappingProperty() &&
- std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) {
- continue;
- }
- if (it.value()->type() == Value::ItemValueType) {
- collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance,
- exportedModule.modulePropertyValues);
- } else {
- TempScopeSetter tss(it.value(), productModuleInstance);
- evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues,
- false);
- }
- }
-}
-
-// Collects module properties assigned to in other (higher-level) modules.
-void ProjectResolver::Private::collectPropertiesForModuleInExportItem(const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- ExportedModule &exportedModule = productContext->product->exportedModule;
- if (module.productInfo || module.name.first() == StringConstants::qbsModule())
- return;
- const auto checkName = [module](const ExportedModuleDependency &d) {
- return module.name.toString() == d.name;
+ const auto print = [this](int indent, const QString &pattern, qint64 time) {
+ logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ')
+ << pattern.arg(elapsedTimeString(time));
};
- if (any_of(exportedModule.moduleDependencies, checkName))
- return;
-
- Item *modulePrototype = module.item->prototype();
- while (modulePrototype && modulePrototype->type() != ItemType::Module)
- modulePrototype = modulePrototype->prototype();
- if (!modulePrototype) // Can happen for broken products in relaxed mode.
- return;
- const Item::PropertyMap &props = modulePrototype->properties();
- ExportedModuleDependency dep;
- dep.name = module.name.toString();
- for (auto it = props.begin(); it != props.end(); ++it) {
- if (it.value()->type() == Value::ItemValueType)
- collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties);
- }
- exportedModule.moduleDependencies.push_back(dep);
-
- for (const auto &dep : module.item->modules())
- collectPropertiesForModuleInExportItem(dep);
-}
-
-void ProjectResolver::Private::resolveProductDependencies()
-{
- for (auto it = productsByItem.cbegin(); it != productsByItem.cend(); ++it) {
- const ResolvedProductPtr &product = it.value();
- for (const Item::Module &module : it.key()->modules()) {
- if (!module.productInfo)
- continue;
- const ResolvedProductPtr &dep = productsByItem.value(module.productInfo->item);
- QBS_CHECK(dep);
- QBS_CHECK(dep != product);
- it.value()->dependencies << dep;
- it.value()->dependencyParameters.insert(dep, module.parameters); // TODO: Streamline this with normal module dependencies?
- }
-
- // TODO: We might want to keep the topological sorting and get rid of "module module dependencies".
- std::sort(product->dependencies.begin(),product->dependencies.end(),
- [](const ResolvedProductPtr &p1, const ResolvedProductPtr &p2) {
- return p1->fullDisplayName() < p2->fullDisplayName();
- });
- }
-}
-
-void ProjectResolver::Private::postProcess(const ResolvedProductPtr &product,
- ResolverProjectContext *projectContext) const
-{
- product->fileTaggers << projectContext->fileTaggers;
- std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers),
- [] (const FileTaggerConstPtr &a, const FileTaggerConstPtr &b) {
- return a->priority() > b->priority();
- });
- for (const RulePtr &rule : projectContext->rules) {
- RulePtr clonedRule = rule->clone();
- clonedRule->product = product.get();
- product->rules.push_back(clonedRule);
- }
-}
-
-void ProjectResolver::Private::applyFileTaggers(const ResolvedProductPtr &product) const
-{
- for (const SourceArtifactPtr &artifact : product->allEnabledFiles())
- applyFileTaggers(artifact, product);
-}
-
-void ProjectResolver::Private::applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product)
-{
- if (!artifact->overrideFileTags || artifact->fileTags.empty()) {
- const QString fileName = FileInfo::fileName(artifact->absoluteFilePath);
- const FileTags fileTags = product->fileTagsForFileName(fileName);
- artifact->fileTags.unite(fileTags);
- if (artifact->fileTags.empty())
- artifact->fileTags.insert(unknownFileTag());
- qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags
- << "to" << fileName;
- }
-}
-
-QVariantMap ProjectResolver::Private::evaluateModuleValues(Item *item, bool lookupPrototype)
-{
- AccumulatingTimer modPropEvalTimer(setupParams.logElapsedTime()
- ? &elapsedTimeModPropEval : nullptr);
- QVariantMap moduleValues;
- for (const Item::Module &module : item->modules()) {
- if (!module.item->isPresentModule())
- continue;
- const QString fullName = module.name.toString();
- moduleValues[fullName] = evaluateProperties(module.item, lookupPrototype, true);
- }
-
- return moduleValues;
-}
-
-QVariantMap ProjectResolver::Private::evaluateProperties(Item *item, bool lookupPrototype,
- bool checkErrors)
-{
- const QVariantMap tmplt;
- return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors);
-}
-
-QVariantMap ProjectResolver::Private::evaluateProperties(
- const Item *item, const Item *propertiesContainer, const QVariantMap &tmplt,
- bool lookupPrototype, bool checkErrors)
-{
- AccumulatingTimer propEvalTimer(setupParams.logElapsedTime()
- ? &elapsedTimeAllPropEval : nullptr);
- QVariantMap result = tmplt;
- for (QMap<QString, ValuePtr>::const_iterator it = propertiesContainer->properties().begin();
- it != propertiesContainer->properties().end(); ++it) {
- checkCancelation();
- evaluateProperty(item, it.key(), it.value(), result, checkErrors);
- }
- return lookupPrototype && propertiesContainer->prototype()
- ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors)
- : result;
-}
-
-void ProjectResolver::Private::evaluateProperty(
- const Item *item, const QString &propName, const ValuePtr &propValue, QVariantMap &result,
- bool checkErrors)
-{
- JSContext * const ctx = engine->context();
- switch (propValue->type()) {
- case Value::ItemValueType:
- {
- // Ignore items. Those point to module instances
- // and are handled in evaluateModuleValues().
- break;
- }
- case Value::JSSourceValueType:
- {
- if (result.contains(propName))
- break;
- const PropertyDeclaration pd = item->propertyDeclaration(propName);
- if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) {
- break;
- }
- const ScopedJsValue scriptValue(ctx, evaluator.property(item, propName));
- if (JsException ex = evaluator.engine()->checkAndClearException(propValue->location())) {
- if (checkErrors)
- throw ex.toErrorInfo();
- }
-
- // NOTE: Loses type information if scriptValue.isUndefined == true,
- // as such QScriptValues become invalid QVariants.
- QVariant v;
- if (JS_IsFunction(ctx, scriptValue)) {
- v = getJsString(ctx, scriptValue);
- } else {
- v = getJsVariant(ctx, scriptValue);
- QVariantMap m = v.toMap();
- if (m.contains(StringConstants::importScopeNamePropertyInternal())) {
- QVariantMap tmp = m;
- const ScopedJsValue proto(ctx, JS_GetPrototype(ctx, scriptValue));
- m = getJsVariant(ctx, proto).toMap();
- for (auto it = tmp.begin(); it != tmp.end(); ++it)
- m.insert(it.key(), it.value());
- v = m;
- }
- }
-
- if (pd.type() == PropertyDeclaration::Path && v.isValid()) {
- v = v.toString();
- } else if (pd.type() == PropertyDeclaration::PathList
- || pd.type() == PropertyDeclaration::StringList) {
- v = v.toStringList();
- } else if (pd.type() == PropertyDeclaration::VariantList) {
- v = v.toList();
- }
- checkAllowedValues(v, propValue->location(), pd, propName);
- result[propName] = v;
- break;
- }
- case Value::VariantValueType:
- {
- if (result.contains(propName))
- break;
- VariantValuePtr vvp = std::static_pointer_cast<VariantValue>(propValue);
- QVariant v = vvp->value();
-
- const PropertyDeclaration pd = item->propertyDeclaration(propName);
- if (v.isNull() && !pd.isScalar()) // QTBUG-51237
- v = QStringList();
-
- checkAllowedValues(v, propValue->location(), pd, propName);
- result[propName] = v;
- break;
- }
- }
-}
-
-void ProjectResolver::Private::checkAllowedValues(
- const QVariant &value, const CodeLocation &loc, const PropertyDeclaration &decl,
- const QString &key) const
-{
- const auto type = decl.type();
- if (type != PropertyDeclaration::String && type != PropertyDeclaration::StringList)
- return;
-
- if (value.isNull())
- return;
-
- const auto &allowedValues = decl.allowedValues();
- if (allowedValues.isEmpty())
- return;
-
- const auto checkValue = [this, &loc, &allowedValues, &key](const QString &value)
- {
- if (!allowedValues.contains(value)) {
- const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.")
- .arg(value, key);
- ErrorInfo error(message, loc);
- handlePropertyError(error, setupParams, logger);
- }
- };
-
- if (type == PropertyDeclaration::StringList) {
- const auto strings = value.toStringList();
- for (const auto &string: strings) {
- checkValue(string);
- }
- } else if (type == PropertyDeclaration::String) {
- checkValue(value.toString());
- }
-}
-
-void ProjectResolver::Private::collectPropertiesForExportItem(const QualifiedId &moduleName,
- const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps)
-{
- QBS_CHECK(value->type() == Value::ItemValueType);
- Item * const itemValueItem = std::static_pointer_cast<ItemValue>(value)->item();
- if (itemValueItem->propertyDeclarations().isEmpty()) {
- for (const Item::Module &module : moduleInstance->modules()) {
- if (module.name == moduleName) {
- itemValueItem->setPropertyDeclarations(module.item->propertyDeclarations());
- break;
- }
- }
- }
- if (itemValueItem->type() == ItemType::ModuleInstancePlaceholder) {
- struct EvalPreparer {
- EvalPreparer(Item *valueItem, const QualifiedId &moduleName)
- : valueItem(valueItem),
- hadName(!!valueItem->variantProperty(StringConstants::nameProperty()))
- {
- if (!hadName) {
- // Evaluator expects a name here.
- valueItem->setProperty(StringConstants::nameProperty(),
- VariantValue::create(moduleName.toString()));
- }
- }
- ~EvalPreparer()
- {
- if (!hadName)
- valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr());
- }
- Item * const valueItem;
- const bool hadName;
- };
- EvalPreparer ep(itemValueItem, moduleName);
- std::vector<TempScopeSetter> tss;
- for (const ValuePtr &v : itemValueItem->properties())
- tss.emplace_back(v, moduleInstance);
- moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false));
- return;
- }
- QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix);
- const Item::PropertyMap &props = itemValueItem->properties();
- for (auto it = props.begin(); it != props.end(); ++it) {
- QualifiedId fullModuleName = moduleName;
- fullModuleName << it.key();
- collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps);
- }
-}
-
-void ProjectResolver::Private::createProductConfig(ResolvedProduct *product)
-{
- EvalCacheEnabler cachingEnabler(&evaluator, productContext->product->sourceDirectory);
- product->moduleProperties->setValue(evaluateModuleValues(productContext->item));
- product->productProperties = evaluateProperties(productContext->item, productContext->item,
- QVariantMap(), true, true);
-}
-
-void ProjectResolver::Private::callItemFunction(const ItemFuncMap &mappings, Item *item,
- ResolverProjectContext *projectContext)
-{
- const ItemFuncPtr f = mappings.value(item->type());
- QBS_CHECK(f);
- if (item->type() == ItemType::Project) {
- ResolverProjectContext subProjectContext = createProjectContext(projectContext);
- (this->*f)(item, &subProjectContext);
- } else {
- (this->*f)(item, projectContext);
- }
-}
-
-ResolverProjectContext ProjectResolver::Private::createProjectContext(
- ResolverProjectContext *parentProjectContext) const
-{
- ResolverProjectContext subProjectContext;
- subProjectContext.parentContext = parentProjectContext;
- subProjectContext.project = ResolvedProject::create();
- parentProjectContext->project->subProjects.push_back(subProjectContext.project);
- subProjectContext.project->parentProject = parentProjectContext->project;
- return subProjectContext;
+ print(2, Tr::tr("Project file loading and parsing took %1."), state.itemReader().elapsedTime());
+ print(2, Tr::tr("Preparing products took %1."),
+ state.topLevelProject().timingData().preparingProducts);
+ print(2, Tr::tr("Setting up Groups took %1."),
+ state.topLevelProject().timingData().groupsSetup);
+ print(2, Tr::tr("Scheduling products took %1."),
+ state.topLevelProject().timingData().schedulingProducts);
+ print(2, Tr::tr("Resolving products took %1."),
+ state.topLevelProject().timingData().resolvingProducts);
+ print(4, Tr::tr("Property evaluation took %1."),
+ state.topLevelProject().timingData().propertyEvaluation);
+ print(4, Tr::tr("Resolving groups (without module property evaluation) took %1."),
+ state.topLevelProject().timingData().groupsResolving);
+ print(4, Tr::tr("Setting up product dependencies took %1."),
+ state.topLevelProject().timingData().dependenciesResolving);
+ print(6, Tr::tr("Running module providers took %1."),
+ state.topLevelProject().timingData().moduleProviders);
+ print(6, Tr::tr("Instantiating modules took %1."),
+ state.topLevelProject().timingData().moduleInstantiation);
+ print(6, Tr::tr("Merging module property values took %1."),
+ state.topLevelProject().timingData().propertyMerging);
+ logger.qbsLog(LoggerInfo, true) << QByteArray(4, ' ') << "There were "
+ << state.topLevelProject().productDeferrals()
+ << " product deferrals with a total of "
+ << state.topLevelProject().productCount() << " products.";
+ print(2, Tr::tr("Running Probes took %1."), state.topLevelProject().timingData().probes);
+ state.logger().qbsLog(LoggerInfo, true)
+ << " "
+ << Tr::tr("%1 probes encountered, %2 configure scripts executed, "
+ "%3 re-used from current run, %4 re-used from earlier run.")
+ .arg(state.topLevelProject().probesEncounteredCount())
+ .arg(state.topLevelProject().probesRunCount())
+ .arg(state.topLevelProject().reusedCurrentProbesCount())
+ .arg(state.topLevelProject().reusedOldProbesCount());
+ print(2, Tr::tr("Property checking took %1."),
+ state.topLevelProject().timingData().propertyChecking);
}
} // namespace Internal
diff --git a/src/lib/corelib/loader/projectresolver.h b/src/lib/corelib/loader/projectresolver.h
index 2b5a55066..22ad82518 100644
--- a/src/lib/corelib/loader/projectresolver.h
+++ b/src/lib/corelib/loader/projectresolver.h
@@ -62,7 +62,7 @@ class StoredModuleProviderInfo;
class QBS_AUTOTEST_EXPORT ProjectResolver
{
public:
- ProjectResolver(ScriptEngine *engine, Logger logger);
+ ProjectResolver(const SetupProjectParameters &parameters, ScriptEngine *engine, Logger logger);
~ProjectResolver();
void setProgressObserver(ProgressObserver *observer);
@@ -71,7 +71,7 @@ public:
void setLastResolveTime(const FileTime &time);
void setStoredProfiles(const QVariantMap &profiles);
void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo);
- TopLevelProjectPtr resolve(const SetupProjectParameters &parameters);
+ TopLevelProjectPtr resolve();
private:
class Private;
diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp
deleted file mode 100644
index ec3869895..000000000
--- a/src/lib/corelib/loader/projecttreebuilder.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2023 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "projecttreebuilder.h"
-
-#include "dependenciesresolver.h"
-#include "itemreader.h"
-#include "localprofiles.h"
-#include "moduleinstantiator.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-#include "productitemmultiplexer.h"
-#include "productscollector.h"
-#include "productshandler.h"
-
-#include <language/builtindeclarations.h>
-#include <language/evaluator.h>
-#include <language/filecontext.h>
-#include <language/filetags.h>
-#include <language/item.h>
-#include <language/language.h>
-#include <language/scriptengine.h>
-#include <language/value.h>
-#include <logging/categories.h>
-#include <logging/translator.h>
-#include <tools/fileinfo.h>
-#include <tools/filetime.h>
-#include <tools/preferences.h>
-#include <tools/progressobserver.h>
-#include <tools/profile.h>
-#include <tools/profiling.h>
-#include <tools/scripttools.h>
-#include <tools/settings.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/stringconstants.h>
-#include <tools/version.h>
-
-#include <QDir>
-#include <QDirIterator>
-#include <QFileInfo>
-
-#include <list>
-#include <memory>
-#include <optional>
-#include <queue>
-#include <stack>
-#include <utility>
-#include <vector>
-
-namespace qbs::Internal {
-
-class ProjectTreeBuilder::Private
-{
-public:
- Private(const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
- Logger &logger) : state(parameters, itemPool, evaluator, logger) {}
-
- Item *loadTopLevelProjectItem();
- void checkOverriddenValues();
- void collectNameFromOverride(const QString &overrideString);
- void handleTopLevelProject(Item *projectItem);
- void printProfilingInfo();
-
- LoaderState state;
- ProductsCollector productsCollector{state};
- ProductsHandler productsHandler{state};
- FileTime lastResolveTime;
- QVariantMap storedProfiles;
-
- qint64 elapsedTimePropertyChecking = 0;
-};
-
-ProjectTreeBuilder::ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : d(makePimpl<Private>(parameters, itemPool, evaluator, logger)) {}
-ProjectTreeBuilder::~ProjectTreeBuilder() = default;
-
-void ProjectTreeBuilder::setProgressObserver(ProgressObserver *progressObserver)
-{
- d->state.topLevelProject().progressObserver = progressObserver;
-}
-
-void ProjectTreeBuilder::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- d->state.probesResolver().setOldProjectProbes(oldProbes);
-}
-
-void ProjectTreeBuilder::setOldProductProbes(
- const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- d->state.probesResolver().setOldProductProbes(oldProbes);
-}
-
-void ProjectTreeBuilder::setLastResolveTime(const FileTime &time) { d->lastResolveTime = time; }
-
-void ProjectTreeBuilder::setStoredProfiles(const QVariantMap &profiles)
-{
- d->storedProfiles = profiles;
-}
-
-void ProjectTreeBuilder::setStoredModuleProviderInfo(
- const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->state.dependenciesResolver().setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-ProjectTreeBuilder::Result ProjectTreeBuilder::load()
-{
- auto mainTimer = std::make_unique<TimedActivityLogger>(
- d->state.logger(), Tr::tr("ProjectTreeBuilder"),
- d->state.parameters().logElapsedTime());
- qCDebug(lcModuleLoader) << "load" << d->state.parameters().projectFilePath();
-
- d->checkOverriddenValues();
-
- Result result;
- TopLevelProjectContext &project = d->state.topLevelProject();
- project.profileConfigs = d->storedProfiles;
- result.root = d->loadTopLevelProjectItem();
- d->handleTopLevelProject(result.root);
-
- result.qbsFiles = d->state.itemReader().filesRead()
- - d->state.dependenciesResolver() .tempQbsFiles();
- result.productInfos = project.productInfos;
- result.profileConfigs = project.profileConfigs;
- const QVariantMap &profiles = d->state.localProfiles().profiles();
- for (auto it = profiles.begin(); it != profiles.end(); ++it)
- result.profileConfigs.remove(it.key());
- result.projectProbes = project.probes;
- result.storedModuleProviderInfo = d->state.dependenciesResolver().storedModuleProviderInfo();
-
- mainTimer.reset();
- d->printProfilingInfo();
-
- return result;
-}
-
-Item *ProjectTreeBuilder::Private::loadTopLevelProjectItem()
-{
- const QStringList topLevelSearchPaths
- = state.parameters().finalBuildConfigurationTree()
- .value(StringConstants::projectPrefix()).toMap()
- .value(StringConstants::qbsSearchPathsProperty()).toStringList();
- SearchPathsManager searchPathsManager(state.itemReader(), topLevelSearchPaths);
- Item * const root = state.itemReader().setupItemFromFile(
- state.parameters().projectFilePath(), {});
- if (!root)
- return {};
-
- switch (root->type()) {
- case ItemType::Product:
- return state.itemReader().wrapInProjectIfNecessary(root);
- case ItemType::Project:
- return root;
- default:
- throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it"
- " is of type '%1'.").arg(root->typeName()), root->location());
- }
-}
-
-void ProjectTreeBuilder::Private::checkOverriddenValues()
-{
- static const auto matchesPrefix = [](const QString &key) {
- static const QStringList prefixes({StringConstants::projectPrefix(),
- QStringLiteral("projects"),
- QStringLiteral("products"), QStringLiteral("modules"),
- StringConstants::moduleProviders(),
- StringConstants::qbsModule()});
- for (const auto &prefix : prefixes) {
- if (key.startsWith(prefix + QLatin1Char('.')))
- return true;
- }
- return false;
- };
- const QVariantMap &overriddenValues = state.parameters().overriddenValues();
- for (auto it = overriddenValues.begin(); it != overriddenValues.end(); ++it) {
- if (matchesPrefix(it.key())) {
- collectNameFromOverride(it.key());
- continue;
- }
-
- ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(it.key()));
- e.append(Tr::tr("Please use one of the following:"));
- e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>."
- "<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>."
- "<property-name>:value"));
- handlePropertyError(e, state.parameters(), state.logger());
- }
-}
-
-void ProjectTreeBuilder::Private::collectNameFromOverride(const QString &overrideString)
-{
- static const auto extract = [](const QString &prefix, const QString &overrideString) {
- if (!overrideString.startsWith(prefix))
- return QString();
- const int startPos = prefix.length();
- const int endPos = overrideString.lastIndexOf(StringConstants::dot());
- if (endPos == -1)
- return QString();
- return overrideString.mid(startPos, endPos - startPos);
- };
- const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString);
- if (!projectName.isEmpty()) {
- state.topLevelProject().projectNamesUsedInOverrides.insert(projectName);
- return;
- }
- const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString);
- if (!productName.isEmpty()) {
- state.topLevelProject().productNamesUsedInOverrides.insert(productName.left(
- productName.indexOf(StringConstants::dot())));
- return;
- }
-}
-
-void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem)
-{
- state.topLevelProject().buildDirectory = TopLevelProject::deriveBuildDirectory(
- state.parameters().buildRoot(),
- TopLevelProject::deriveId(state.parameters().finalBuildConfigurationTree()));
- projectItem->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(QFileInfo(projectItem->file()->filePath())
- .absolutePath()));
- projectItem->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(state.topLevelProject().buildDirectory));
- projectItem->setProperty(StringConstants::profileProperty(),
- VariantValue::create(state.parameters().topLevelProfile()));
- productsCollector.run(projectItem);
- productsHandler.run();
-
- state.itemReader().clearExtraSearchPathsStack(); // TODO: Unneeded?
- AccumulatingTimer timer(state.parameters().logElapsedTime()
- ? &elapsedTimePropertyChecking : nullptr);
- checkPropertyDeclarations(projectItem, state.topLevelProject().disabledItems,
- state.parameters(), state.logger());
-}
-
-void ProjectTreeBuilder::Private::printProfilingInfo()
-{
- if (!state.parameters().logElapsedTime())
- return;
- state.logger().qbsLog(LoggerInfo, true)
- << " "
- << Tr::tr("Project file loading and parsing took %1.")
- .arg(elapsedTimeString(state.itemReader().elapsedTime()));
- productsCollector.printProfilingInfo(2);
- productsHandler.printProfilingInfo(2);
- state.dependenciesResolver().printProfilingInfo(4);
- state.moduleInstantiator().printProfilingInfo(6);
- state.propertyMerger().printProfilingInfo(6);
- state.probesResolver().printProfilingInfo(4);
- state.logger().qbsLog(LoggerInfo, true)
- << " "
- << Tr::tr("Property checking took %1.")
- .arg(elapsedTimeString(elapsedTimePropertyChecking));
-}
-
-} // namespace qbs::Internal
diff --git a/src/lib/corelib/logging/categories.cpp b/src/lib/corelib/logging/categories.cpp
index 0f844f5b4..5738dc21d 100644
--- a/src/lib/corelib/logging/categories.cpp
+++ b/src/lib/corelib/logging/categories.cpp
@@ -50,6 +50,7 @@ Q_LOGGING_CATEGORY(lcModuleLoader, "qbs.moduleloader", QtCriticalMsg)
Q_LOGGING_CATEGORY(lcPluginManager, "qbs.pluginmanager", QtCriticalMsg)
Q_LOGGING_CATEGORY(lcProjectResolver, "qbs.projectresolver", QtCriticalMsg)
Q_LOGGING_CATEGORY(lcUpToDateCheck, "qbs.uptodate", QtCriticalMsg)
+Q_LOGGING_CATEGORY(lcLoaderScheduling, "qbs.loader.scheduling", QtCriticalMsg)
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/logging/categories.h b/src/lib/corelib/logging/categories.h
index 40c69845e..c8873c30c 100644
--- a/src/lib/corelib/logging/categories.h
+++ b/src/lib/corelib/logging/categories.h
@@ -53,6 +53,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcModuleLoader)
Q_DECLARE_LOGGING_CATEGORY(lcPluginManager)
Q_DECLARE_LOGGING_CATEGORY(lcProjectResolver)
Q_DECLARE_LOGGING_CATEGORY(lcUpToDateCheck)
+Q_DECLARE_LOGGING_CATEGORY(lcLoaderScheduling)
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/logging/logger.cpp b/src/lib/corelib/logging/logger.cpp
index d4f51cd30..65d0cc27f 100644
--- a/src/lib/corelib/logging/logger.cpp
+++ b/src/lib/corelib/logging/logger.cpp
@@ -231,7 +231,7 @@ void Logger::printWarning(const ErrorInfo &warning)
LogWriter Logger::qbsLog(LoggerLevel level, bool force) const
{
- return LogWriter(m_logSink, level, force);
+ return {m_logSink, level, force};
}
} // namespace Internal
diff --git a/src/lib/corelib/parser/qmljsastvisitor_p.h b/src/lib/corelib/parser/qmljsastvisitor_p.h
index bec174c65..4b7911aa4 100644
--- a/src/lib/corelib/parser/qmljsastvisitor_p.h
+++ b/src/lib/corelib/parser/qmljsastvisitor_p.h
@@ -58,7 +58,7 @@
namespace QbsQmlJS {
namespace AST {
-class QBS_AUTOTEST_EXPORT Visitor
+class QML_PARSER_EXPORT Visitor
{
public:
Visitor();
diff --git a/src/lib/corelib/parser/qmljsengine_p.h b/src/lib/corelib/parser/qmljsengine_p.h
index 2a616126d..9c603ee5c 100644
--- a/src/lib/corelib/parser/qmljsengine_p.h
+++ b/src/lib/corelib/parser/qmljsengine_p.h
@@ -93,7 +93,7 @@ public:
QString message;
};
-class QBS_AUTOTEST_EXPORT Engine
+class QML_PARSER_EXPORT Engine
{
Lexer *_lexer{nullptr};
Directives *_directives{nullptr};
diff --git a/src/lib/corelib/parser/qmljsglobal_p.h b/src/lib/corelib/parser/qmljsglobal_p.h
index c3d198ea5..02e0b38e4 100644
--- a/src/lib/corelib/parser/qmljsglobal_p.h
+++ b/src/lib/corelib/parser/qmljsglobal_p.h
@@ -41,31 +41,14 @@
#include <QtCore/qglobal.h>
-// Force QML_PARSER_EXPORT to be always empty.
-#ifndef QT_CREATOR
-# define QT_CREATOR
-#endif
-#ifdef QML_BUILD_STATIC_LIB
-# undef QML_BUILD_STATIC_LIB
-#endif
-#define QML_BUILD_STATIC_LIB 1
-
-#ifdef QT_CREATOR
-# ifdef QMLJS_BUILD_DIR
-# define QML_PARSER_EXPORT Q_DECL_EXPORT
-# elif QML_BUILD_STATIC_LIB
-# define QML_PARSER_EXPORT
-# else
-# define QML_PARSER_EXPORT Q_DECL_IMPORT
-# endif // QMLJS_BUILD_DIR
-
-#else // !QT_CREATOR
-# if defined(QT_BUILD_QMLDEVTOOLS_LIB) || defined(QT_QMLDEVTOOLS_LIB)
- // QmlDevTools is a static library
-# define QML_PARSER_EXPORT
-# else
-# define QML_PARSER_EXPORT Q_AUTOTEST_EXPORT
-# endif
-#endif // QT_CREATOR
+#ifdef QBS_STATIC_LIB
+#define QML_PARSER_EXPORT
+#else
+#ifdef QBS_LIBRARY
+#define QML_PARSER_EXPORT Q_DECL_EXPORT
+#else
+#define QML_PARSER_EXPORT Q_DECL_IMPORT
+#endif // QBS_LIBRARY
+#endif // QBS_STATIC_LIB
#endif // QMLJSGLOBAL_P_H
diff --git a/src/lib/corelib/parser/qmljslexer.cpp b/src/lib/corelib/parser/qmljslexer.cpp
index 684be9317..e148652ad 100644
--- a/src/lib/corelib/parser/qmljslexer.cpp
+++ b/src/lib/corelib/parser/qmljslexer.cpp
@@ -1071,7 +1071,7 @@ bool Lexer::scanDirectives(Directives *directives)
const int lineNumber = tokenStartLine();
- if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD))
+ if (_tokenKind != T_IDENTIFIER && _tokenKind != T_RESERVED_WORD)
return false; // expected a valid QML/JS directive
const QString directiveName = tokenText();
@@ -1083,7 +1083,7 @@ bool Lexer::scanDirectives(Directives *directives)
// it must be a pragma or an import directive.
if (directiveName == QLatin1String("pragma")) {
// .pragma library
- if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library")))
+ if (lex() != T_IDENTIFIER || tokenText() != QLatin1String("library"))
return false; // expected `library
// we found a .pragma library directive
@@ -1126,7 +1126,7 @@ bool Lexer::scanDirectives(Directives *directives)
//
// recognize the mandatory `as' followed by the module name
//
- if (! (lex() == T_RESERVED_WORD && tokenText() == QLatin1String("as")))
+ if (lex() != T_RESERVED_WORD || tokenText() != QLatin1String("as"))
return false; // expected `as'
if (lex() != T_IDENTIFIER)
diff --git a/src/lib/corelib/parser/qmljslexer_p.h b/src/lib/corelib/parser/qmljslexer_p.h
index aef68e0c5..c9801c0f5 100644
--- a/src/lib/corelib/parser/qmljslexer_p.h
+++ b/src/lib/corelib/parser/qmljslexer_p.h
@@ -86,7 +86,7 @@ public:
}
};
-class QBS_AUTOTEST_EXPORT Lexer: public QmlJSGrammar
+class QML_PARSER_EXPORT Lexer : public QmlJSGrammar
{
public:
enum {
diff --git a/src/lib/corelib/parser/qmljsparser_p.h b/src/lib/corelib/parser/qmljsparser_p.h
index c761bb25b..9744a7eb6 100644
--- a/src/lib/corelib/parser/qmljsparser_p.h
+++ b/src/lib/corelib/parser/qmljsparser_p.h
@@ -70,7 +70,7 @@ namespace QbsQmlJS {
class Engine;
-class QBS_AUTOTEST_EXPORT Parser: protected QmlJSGrammar
+class QML_PARSER_EXPORT Parser : protected QmlJSGrammar
{
public:
union Value {
diff --git a/src/lib/corelib/tools/clangclinfo.cpp b/src/lib/corelib/tools/clangclinfo.cpp
index 68486b91f..fd907ebf1 100644
--- a/src/lib/corelib/tools/clangclinfo.cpp
+++ b/src/lib/corelib/tools/clangclinfo.cpp
@@ -47,7 +47,7 @@ static std::vector<MSVCInstallInfo> compatibleMsvcs(Logger &logger)
return true;
bool ok = false;
const int major = versions.at(0).toInt(&ok);
- return !(ok && major >= 15); // support MSVC2017 and above
+ return !ok || major < 15; // support MSVC2017 and above
};
Internal::removeIf(msvcs, filter);
for (const auto &msvc: msvcs) {
@@ -121,7 +121,7 @@ std::vector<ClangClInfo> ClangClInfo::installedCompilers(
if (registry.contains(key)) {
const auto compilerPath = QDir::fromNativeSeparators(registry.value(key).toString())
+ QStringLiteral("/bin/") + compilerName;
- if (QFileInfo::exists(compilerPath))
+ if (QFileInfo::exists(compilerPath) && !contains(compilerPaths, compilerPath))
compilerPaths.push_back(compilerPath);
}
diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp
index ea77fee9d..f04041e14 100644
--- a/src/lib/corelib/tools/codelocation.cpp
+++ b/src/lib/corelib/tools/codelocation.cpp
@@ -182,4 +182,44 @@ bool operator<(const CodeLocation &cl1, const CodeLocation &cl2)
return cl1.toString() < cl2.toString();
}
+void CodePosition::load(Internal::PersistentPool &pool) { pool.load(m_line, m_column); }
+void CodePosition::store(Internal::PersistentPool &pool) const { pool.store(m_line, m_column); }
+
+bool operator==(const CodePosition &pos1, const CodePosition &pos2)
+{
+ return pos1.line() == pos2.line() && pos1.column() == pos2.column();
+}
+bool operator!=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 == pos2); }
+
+bool operator<(const CodePosition &pos1, const CodePosition &pos2)
+{
+ const int lineDiff = pos1.line() - pos2.line();
+ if (lineDiff < 0)
+ return true;
+ if (lineDiff > 0)
+ return false;
+ return pos1.column() < pos2.column();
+}
+bool operator>(const CodePosition &pos1, const CodePosition &pos2) { return pos2 < pos1; }
+bool operator<=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 > pos2); }
+bool operator>=(const CodePosition &pos1, const CodePosition &pos2) { return !(pos1 < pos2); }
+
+CodeRange::CodeRange(const CodePosition &start, const CodePosition &end)
+ : m_start(start), m_end(end) {}
+
+void CodeRange::load(Internal::PersistentPool &pool) { pool.load(m_start, m_end); }
+void CodeRange::store(Internal::PersistentPool &pool) const { pool.store(m_start, m_end); }
+
+bool CodeRange::contains(const CodePosition &pos) const
+{
+ return start() <= pos && end() > pos;
+}
+
+bool operator==(const CodeRange &r1, const CodeRange &r2)
+{
+ return r1.start() == r2.start() && r1.end() == r2.end();
+}
+bool operator!=(const CodeRange &r1, const CodeRange &r2) { return !(r1 == r2); }
+bool operator<(const CodeRange &r1, const CodeRange &r2) { return r1.start() < r2.start(); }
+
} // namespace qbs
diff --git a/src/lib/corelib/tools/codelocation.h b/src/lib/corelib/tools/codelocation.h
index 33b832df0..afcd2e075 100644
--- a/src/lib/corelib/tools/codelocation.h
+++ b/src/lib/corelib/tools/codelocation.h
@@ -86,11 +86,79 @@ private:
QBS_EXPORT bool operator==(const CodeLocation &cl1, const CodeLocation &cl2);
QBS_EXPORT bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2);
QBS_EXPORT bool operator<(const CodeLocation &cl1, const CodeLocation &cl2);
-
inline auto qHash(const CodeLocation &cl) { return qHash(cl.toString()); }
-
QDebug operator<<(QDebug debug, const CodeLocation &location);
+class QBS_EXPORT CodePosition
+{
+public:
+ CodePosition(int line, int column) : m_line(line), m_column(column) {}
+
+ CodePosition() = default;
+ CodePosition(const CodePosition &other) = default;
+ CodePosition(CodePosition &&other) = default;
+ CodePosition &operator=(const CodePosition &other) = default;
+ CodePosition &operator=(CodePosition &&other) = default;
+
+ int line() const { return m_line; }
+ void setLine(int newLine) { m_line = newLine; }
+
+ int column() const { return m_column; }
+ void setColumn(int newColumn) { m_column = newColumn; }
+
+ void load(Internal::PersistentPool &pool);
+ void store(Internal::PersistentPool &pool) const;
+
+private:
+ int m_line = 0;
+ int m_column = 0;
+};
+
+QBS_EXPORT bool operator==(const CodePosition &pos1, const CodePosition &pos2);
+QBS_EXPORT bool operator!=(const CodePosition &pos1, const CodePosition &pos2);
+QBS_EXPORT bool operator<(const CodePosition &pos1, const CodePosition &pos2);
+QBS_EXPORT bool operator>(const CodePosition &pos1, const CodePosition &pos2);
+QBS_EXPORT bool operator<=(const CodePosition &pos1, const CodePosition &pos2);
+QBS_EXPORT bool operator>=(const CodePosition &pos1, const CodePosition &pos2);
+inline auto qHash(const CodePosition &pos)
+{
+ return QT_PREPEND_NAMESPACE(qHash)(pos.line()) ^ QT_PREPEND_NAMESPACE(qHash)(pos.column());
+}
+
+class QBS_EXPORT CodeRange
+{
+public:
+ CodeRange(const CodePosition &start, const CodePosition &end);
+
+ CodeRange() = default;
+ CodeRange(const CodeRange &other) = default;
+ CodeRange(CodeRange &&other) = default;
+ CodeRange &operator=(const CodeRange &other) = default;
+ CodeRange &operator=(CodeRange &&other) = default;
+
+ const CodePosition &start() const & { return m_start; }
+ const CodePosition &end() const & { return m_end; }
+ CodePosition start() && { return std::move(m_start); }
+ CodePosition end() && { return std::move(m_end); }
+
+ bool contains(const CodePosition &pos) const;
+
+ void load(Internal::PersistentPool &pool);
+ void store(Internal::PersistentPool &pool) const;
+
+private:
+ CodePosition m_start;
+ CodePosition m_end;
+};
+
+QBS_EXPORT bool operator==(const CodeRange &r1, const CodeRange &r2);
+QBS_EXPORT bool operator!=(const CodeRange &r1, const CodeRange &r2);
+QBS_EXPORT bool operator<(const CodeRange &r1, const CodeRange &r2);
+inline auto qHash(const CodeRange &range) { return qHash(range.start()) ^ qHash(range.end()); }
+
+using CodeLinksInFile = QHash<CodeRange, QList<CodeLocation>>;
+using CodeLinks = QHash<QString, CodeLinksInFile>;
+
} // namespace qbs
#endif // QBS_SOURCELOCATION_H
diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp
index 963089fe8..f1b90b71e 100644
--- a/src/lib/corelib/tools/error.cpp
+++ b/src/lib/corelib/tools/error.cpp
@@ -317,6 +317,13 @@ bool ErrorInfo::hasLocation() const
return ei.codeLocation().isValid(); });
}
+bool ErrorInfo::isCancelException() const
+{
+ return Internal::any_of(d->items, [](const ErrorItem &ei) {
+ return ei.description() == QLatin1String("interrupted");
+ });
+}
+
void ErrorInfo::load(Internal::PersistentPool &pool)
{
pool.load(*d);
diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h
index 4c6370b1e..ba600a558 100644
--- a/src/lib/corelib/tools/error.h
+++ b/src/lib/corelib/tools/error.h
@@ -113,6 +113,7 @@ public:
QJsonObject toJson() const;
bool isInternalError() const;
bool hasLocation() const;
+ bool isCancelException() const;
void load(Internal::PersistentPool &pool);
void store(Internal::PersistentPool &pool) const;
diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp
index 0bdc861fa..b5d9c0151 100644
--- a/src/lib/corelib/tools/executablefinder.cpp
+++ b/src/lib/corelib/tools/executablefinder.cpp
@@ -120,7 +120,7 @@ QString ExecutableFinder::findInPath(const QString &filePath, const QString &wor
fullProgramPath = filePath;
qCDebug(lcExec) << "looking for executable in PATH" << fullProgramPath;
QStringList pathEnv = m_environment.value(StringConstants::pathEnvVar())
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
if (HostOsInfo::isWindowsHost())
pathEnv.prepend(StringConstants::dot());
for (QString directory : std::as_const(pathEnv)) {
diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp
index 4b72d7580..7d4788ee3 100644
--- a/src/lib/corelib/tools/launchersocket.cpp
+++ b/src/lib/corelib/tools/launchersocket.cpp
@@ -82,12 +82,7 @@ void LauncherSocket::setSocket(QLocalSocket *socket)
QBS_ASSERT(!m_socket, return);
m_socket.store(socket);
m_packetParser.setDevice(m_socket);
- connect(m_socket,
-#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
- static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
-#else
- &QLocalSocket::errorOccurred,
-#endif
+ connect(m_socket, &QLocalSocket::errorOccurred,
this, &LauncherSocket::handleSocketError);
connect(m_socket, &QLocalSocket::readyRead,
this, &LauncherSocket::handleSocketDataAvailable);
diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp
index 973284b2b..77b83023a 100644
--- a/src/lib/corelib/tools/msvcinfo.cpp
+++ b/src/lib/corelib/tools/msvcinfo.cpp
@@ -176,6 +176,7 @@ static QVariantMap getMsvcDefines(const QString &compilerFilePath,
<< qEnvironmentVariable("COMSPEC")
<< QStringLiteral("/c")
<< languageSwitch
+ << QStringLiteral("/Zs")
<< QStringLiteral("NUL"),
compilerEnv, true, commands)).split(QLatin1Char('\n'));
diff --git a/src/lib/corelib/tools/mutexdata.h b/src/lib/corelib/tools/mutexdata.h
new file mode 100644
index 000000000..160f6dd26
--- /dev/null
+++ b/src/lib/corelib/tools/mutexdata.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <shared_mutex>
+
+namespace qbs {
+namespace Internal {
+
+// adapted version of https://github.com/dragazo/rustex/blob/master/rustex.h
+
+// a data with a mutually exclusive access
+template<typename DataType, typename MutexType = std::shared_mutex>
+class MutexData
+{
+public:
+ template<typename T, template<typename> typename LockType>
+ class ReferenceGuard
+ {
+ friend class MutexData;
+ template<typename U>
+ ReferenceGuard(U &&data) noexcept
+ : m_ptr(std::addressof(data.m_data))
+ , m_lock(data.m_mutex)
+ {}
+ public:
+ ReferenceGuard(const ReferenceGuard &) = delete;
+ ReferenceGuard(ReferenceGuard &&) = default;
+ ReferenceGuard &operator=(const ReferenceGuard &) = delete;
+ ReferenceGuard &operator=(ReferenceGuard &&) = default;
+
+ T &get() const noexcept { return *m_ptr; }
+ T &data() const noexcept { return *m_ptr; }
+ operator T &() const noexcept { return *m_ptr; }
+
+ private:
+ T *m_ptr;
+ LockType<MutexType> m_lock;
+ };
+
+ using UniqueMutableGuard = ReferenceGuard<DataType, std::unique_lock>;
+ using UniqueConstGuard = ReferenceGuard<const DataType, std::unique_lock>;
+ using SharedGuard = ReferenceGuard<const DataType, std::shared_lock>;
+
+ template<
+ typename ...Args,
+ std::enable_if_t<std::is_constructible_v<DataType, Args...>, int> = 0
+ >
+ explicit MutexData(Args &&...args) : m_data(std::forward<Args>(args)...) {}
+
+ [[nodiscard]] UniqueMutableGuard lock() noexcept { return UniqueMutableGuard{*this}; }
+ [[nodiscard]] UniqueConstGuard lock() const noexcept { return UniqueConstGuard{*this}; }
+
+ template<
+ typename U = MutexType,
+ std::enable_if_t<std::is_same_v<U, std::shared_mutex>, int> = 0
+ >
+ [[nodiscard]] SharedGuard lock_shared() const noexcept
+ { return SharedGuard{*this}; }
+
+private:
+ DataType m_data;
+ mutable MutexType m_mutex;
+};
+
+} // namespace qbs
+} // namespace Internal
diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp
index cf5903349..0e545377a 100644
--- a/src/lib/corelib/tools/persistence.cpp
+++ b/src/lib/corelib/tools/persistence.cpp
@@ -48,7 +48,7 @@
namespace qbs {
namespace Internal {
-static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-132";
+static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-133";
NoBuildGraphError::NoBuildGraphError(const QString &filePath)
: ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.")
@@ -140,6 +140,11 @@ void PersistentPool::finalizeWriteStream()
void PersistentPool::storeVariant(const QVariant &variant)
{
+ if (variant.isNull()) {
+ m_stream << quint32(QMetaType::User);
+ m_stream << variant;
+ return;
+ }
const auto type = static_cast<quint32>(variant.userType());
m_stream << type;
switch (type) {
@@ -231,8 +236,5 @@ void PersistentPool::doStoreValue(const QProcessEnvironment &env)
store(env.value(key));
}
-const PersistentPool::PersistentObjectId PersistentPool::ValueNotFoundId;
-const PersistentPool::PersistentObjectId PersistentPool::EmptyValueId;
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/tools/persistence.h b/src/lib/corelib/tools/persistence.h
index b7aa543a4..86365c993 100644
--- a/src/lib/corelib/tools/persistence.h
+++ b/src/lib/corelib/tools/persistence.h
@@ -145,8 +145,9 @@ private:
template<typename T> QHash<T, PersistentObjectId> &idMap();
template<typename T> PersistentObjectId &lastStoredId();
- static const PersistentObjectId ValueNotFoundId = -1;
- static const PersistentObjectId EmptyValueId = -2;
+ static const inline PersistentObjectId ValueNotFoundId = -1;
+ static const inline PersistentObjectId EmptyValueId = -2;
+ static const inline PersistentObjectId NullValueId = -3;
std::unique_ptr<QIODevice> m_file;
QDataStream m_stream;
@@ -271,8 +272,13 @@ template<typename T> inline T PersistentPool::idLoadValue()
{
int id;
m_stream >> id;
- if (id == EmptyValueId)
+ if (id == NullValueId)
return T();
+ if (id == EmptyValueId) {
+ if constexpr (std::is_same_v<T, QString>)
+ return QString(0, QChar());
+ return T();
+ }
QBS_CHECK(id >= 0);
if (id >= static_cast<int>(idStorage<T>().size())) {
T value;
@@ -287,6 +293,12 @@ template<typename T> inline T PersistentPool::idLoadValue()
template<typename T>
void PersistentPool::idStoreValue(const T &value)
{
+ if constexpr (std::is_same_v<T, QString>) {
+ if (value.isNull()) {
+ m_stream << NullValueId;
+ return;
+ }
+ }
if (value.isEmpty()) {
m_stream << EmptyValueId;
return;
diff --git a/src/lib/corelib/tools/profiling.cpp b/src/lib/corelib/tools/profiling.cpp
index db64a73c6..b93d3fa17 100644
--- a/src/lib/corelib/tools/profiling.cpp
+++ b/src/lib/corelib/tools/profiling.cpp
@@ -72,7 +72,7 @@ void TimedActivityLogger::finishActivity()
{
if (!d)
return;
- const QString timeString = elapsedTimeString(d->timer.elapsed());
+ const QString timeString = elapsedTimeString(d->timer.nsecsElapsed());
d->logger.qbsLog(LoggerInfo, true)
<< Tr::tr("Activity '%2' took %3.").arg(d->activity, timeString);
d.reset();
@@ -98,13 +98,13 @@ void AccumulatingTimer::stop()
{
if (!m_timer.isValid())
return;
- *m_elapsedTime += m_timer.elapsed();
+ *m_elapsedTime += m_timer.nsecsElapsed();
m_timer.invalidate();
}
-QString elapsedTimeString(qint64 elapsedTimeInMs)
+QString elapsedTimeString(qint64 elapsedTimeInNs)
{
- qint64 ms = elapsedTimeInMs;
+ qint64 ms = elapsedTimeInNs / (1000ll * 1000ll);
qint64 s = ms/1000;
ms -= s*1000;
qint64 m = s/60;
diff --git a/src/lib/corelib/tools/profiling.h b/src/lib/corelib/tools/profiling.h
index c6fc9ede7..3220c2a82 100644
--- a/src/lib/corelib/tools/profiling.h
+++ b/src/lib/corelib/tools/profiling.h
@@ -52,7 +52,7 @@ namespace qbs {
namespace Internal {
class Logger;
-QString elapsedTimeString(qint64 elapsedTimeInMs);
+QString elapsedTimeString(qint64 elapsedTimeInNs);
class TimedActivityLogger
{
diff --git a/src/lib/corelib/tools/progressobserver.h b/src/lib/corelib/tools/progressobserver.h
index 9acb4b30c..73a61de37 100644
--- a/src/lib/corelib/tools/progressobserver.h
+++ b/src/lib/corelib/tools/progressobserver.h
@@ -41,6 +41,8 @@
#include <QtCore/qglobal.h>
+#include <vector>
+
QT_BEGIN_NAMESPACE
class QString;
QT_END_NAMESPACE
@@ -66,13 +68,13 @@ public:
// Call this to ensure that the progress bar always goes to 100%.
void setFinished();
- void setScriptEngine(ScriptEngine *engine) { m_scriptEngine = engine; }
+ void addScriptEngine(ScriptEngine *engine) { m_scriptEngines.push_back(engine); }
protected:
- ScriptEngine *scriptEngine() const { return m_scriptEngine; }
+ const std::vector<ScriptEngine *> &scriptEngines() const { return m_scriptEngines; }
private:
- ScriptEngine *m_scriptEngine = nullptr;
+ std::vector<ScriptEngine *> m_scriptEngines;
};
} // namespace Internal
diff --git a/src/lib/corelib/tools/qbsassert.h b/src/lib/corelib/tools/qbsassert.h
index e8dfcacf8..65352fdb3 100644
--- a/src/lib/corelib/tools/qbsassert.h
+++ b/src/lib/corelib/tools/qbsassert.h
@@ -59,6 +59,11 @@ QBS_EXPORT void writeAssertLocation(const char *condition, const char *file, int
// The do {} while (0) is here to enforce the use of a semicolon after QBS_ASSERT.
// action can also be continue or break. Copied from qtcassert.h in Qt Creator.
+#define QBS_GUARD(cond) \
+ (Q_LIKELY(cond) \
+ ? true \
+ : (::qbs::Internal::writeAssertLocation(#cond, __FILE__, __LINE__), false))
+
#define QBS_CHECK(cond)\
do {\
if (Q_LIKELY(cond)) {} else {\
diff --git a/src/lib/corelib/tools/qttools.h b/src/lib/corelib/tools/qttools.h
index bc1210d53..029948be4 100644
--- a/src/lib/corelib/tools/qttools.h
+++ b/src/lib/corelib/tools/qttools.h
@@ -55,19 +55,7 @@ QT_BEGIN_NAMESPACE
class QProcessEnvironment;
QT_END_NAMESPACE
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
-#define QBS_SKIP_EMPTY_PARTS QString::SkipEmptyParts
-#else
-#define QBS_SKIP_EMPTY_PARTS Qt::SkipEmptyParts
-#endif
-
namespace std {
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
-template<> struct hash<QString> {
- std::size_t operator()(const QString &s) const { return qHash(s); }
-};
-#endif
-
template<typename T1, typename T2> struct hash<std::pair<T1, T2>>
{
size_t operator()(const pair<T1, T2> &x) const
@@ -161,12 +149,6 @@ inline qbs::QHashValueType qHash(const QVariantHash &v)
return std::hash<QVariantHash>()(v) % std::numeric_limits<uint>::max();
}
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
-namespace Qt {
-inline QTextStream &endl(QTextStream &stream) { return stream << QT_PREPEND_NAMESPACE(endl); }
-} // namespace Qt
-#endif
-
QT_END_NAMESPACE
namespace qbs {
@@ -174,32 +156,20 @@ namespace qbs {
template <class T>
QSet<T> toSet(const QList<T> &list)
{
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
- return list.toSet();
-#else
return QSet<T>(list.begin(), list.end());
-#endif
}
template<class T>
QList<T> toList(const QSet<T> &set)
{
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
- return set.toList();
-#else
return QList<T>(set.begin(), set.end());
-#endif
}
template<typename K, typename V>
QHash<K, V> &unite(QHash<K, V> &h, const QHash<K, V> &other)
{
-#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
- return h.unite(other);
-#else
h.insert(other);
return h;
-#endif
}
inline void setupDefaultCodec(QTextStream &stream)
@@ -229,6 +199,51 @@ inline bool qVariantConvert(QVariant &variant, int typeId)
#endif
}
+inline QMetaType::Type qVariantType(const QVariant &v)
+{
+ return static_cast<QMetaType::Type>(
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ v.metaType().id()
+#else
+ v.type()
+#endif
+ );
+}
+
+template<typename T>
+inline QVariant typedNullVariant()
+{
+ const auto metaType = QMetaType::fromType<T>();
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ return QVariant(metaType, nullptr);
+#else
+ return QVariant(static_cast<QVariant::Type>(metaType.id()));
+#endif
+}
+
+inline bool qVariantsEqual(const QVariant &v1, const QVariant &v2)
+{
+ return v1.isNull() == v2.isNull() && v1 == v2;
+}
+
+inline bool qVariantMapsEqual(const QVariantMap &m1, const QVariantMap &m2)
+{
+ if (m1.size() != m2.size())
+ return false;
+ if (m1.isSharedWith(m2))
+ return true;
+
+ auto it1 = m1.cbegin();
+ auto it2 = m2.cbegin();
+ while (it1 != m1.cend()) {
+ if (it1.key() != it2.key() || !qVariantsEqual(it1.value(), it2.value()))
+ return false;
+ ++it2;
+ ++it1;
+ }
+ return true;
+}
+
} // namespace qbs
#endif // QBSQTTOOLS_H
diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp
index 83005c788..e89e4a0ad 100644
--- a/src/lib/corelib/tools/scripttools.cpp
+++ b/src/lib/corelib/tools/scripttools.cpp
@@ -123,7 +123,7 @@ ErrorInfo JsException::toErrorInfo() const
ErrorInfo e(msg, stackTrace());
if (e.hasLocation() || !m_fallbackLocation.isValid())
return e;
- return ErrorInfo(msg, m_fallbackLocation);
+ return {msg, m_fallbackLocation};
}
void defineJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, JSValue val)
@@ -231,44 +231,19 @@ QStringList getJsStringList(JSContext *ctx, JSValue val)
return l;
}
-JSValue makeJsVariant(JSContext *ctx, const QVariant &v)
+JSValue makeJsVariant(JSContext *ctx, const QVariant &v, quintptr id)
{
- switch (static_cast<QMetaType::Type>(v.userType())) {
- case QMetaType::QByteArray:
- return makeJsArrayBuffer(ctx, v.toByteArray());
- case QMetaType::QString:
- return makeJsString(ctx, v.toString());
- case QMetaType::QStringList:
- return makeJsStringList(ctx, v.toStringList());
- case QMetaType::QVariantList:
- return makeJsVariantList(ctx, v.toList());
- case QMetaType::Int:
- case QMetaType::UInt:
- return JS_NewInt32(ctx, v.toInt());
- case QMetaType::Long:
- case QMetaType::ULong:
- case QMetaType::LongLong:
- case QMetaType::ULongLong:
- return JS_NewInt64(ctx, v.toInt());
- case QMetaType::Bool:
- return JS_NewBool(ctx, v.toBool());
- case QMetaType::QDateTime:
- return JS_NewDate(ctx, v.toDateTime().toString(Qt::ISODateWithMs).toUtf8().constData());
- case QMetaType::QVariantMap:
- return makeJsVariantMap(ctx, v.toMap());
- default:
- return JS_UNDEFINED;
- }
+ return ScriptEngine::engineForContext(ctx)->asJsValue(v, id);
}
-JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l)
+JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l, quintptr id)
{
- return ScriptEngine::engineForContext(ctx)->asJsValue(l);
+ return ScriptEngine::engineForContext(ctx)->asJsValue(l, id);
}
-JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m)
+JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m, quintptr id)
{
- return ScriptEngine::engineForContext(ctx)->asJsValue(m);
+ return ScriptEngine::engineForContext(ctx)->asJsValue(m, id);
}
static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> path)
@@ -318,7 +293,7 @@ static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> pat
const auto tag = JS_VALUE_GET_TAG(val);
if (tag == JS_TAG_INT)
return JS_VALUE_GET_INT(val);
- else if (tag == JS_TAG_FLOAT64)
+ else if (JS_TAG_IS_FLOAT64(tag))
return JS_VALUE_GET_FLOAT64(val);
return {};
}
diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h
index ea7993485..ac7ed9928 100644
--- a/src/lib/corelib/tools/scripttools.h
+++ b/src/lib/corelib/tools/scripttools.h
@@ -75,9 +75,9 @@ QBS_AUTOTEST_EXPORT QVariant getJsVariant(JSContext *ctx, JSValueConst val);
JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s);
JSValue makeJsString(JSContext *ctx, const QString &s);
JSValue makeJsStringList(JSContext *ctx, const QStringList &l);
-JSValue makeJsVariant(JSContext *ctx, const QVariant &v);
-JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l);
-JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m);
+JSValue makeJsVariant(JSContext *ctx, const QVariant &v, quintptr id = 0);
+JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l, quintptr id = 0);
+JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m, quintptr id = 0);
QStringList getJsStringList(JSContext *ctx, JSValueConst val);
JSValue throwError(JSContext *ctx, const QString &message);
using PropertyHandler = std::function<void(const JSAtom &, const JSPropertyDescriptor &)>;
@@ -201,9 +201,11 @@ QVariant QBS_AUTOTEST_EXPORT getConfigProperty(const QVariantMap &cfg, const QSt
} // namespace qbs
// Only to be used for objects!
+#ifndef JS_NAN_BOXING
inline bool operator==(JSValue v1, JSValue v2) { return v1.u.ptr == v2.u.ptr; }
inline bool operator!=(JSValue v1, JSValue v2) { return !(v1 == v2); }
inline bool operator<(JSValue v1, JSValue v2) { return v1.u.ptr < v2.u.ptr; }
inline qbs::QHashValueType qHash(const JSValue &v) { return QT_PREPEND_NAMESPACE(qHash)(v.u.ptr); }
+#endif
#endif // QBS_SCRIPTTOOLS_H
diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp
index 8b22c45e5..4a3d9d727 100644
--- a/src/lib/corelib/tools/settings.cpp
+++ b/src/lib/corelib/tools/settings.cpp
@@ -135,13 +135,22 @@ QStringList Settings::allKeys(Scopes scopes) const
return keys;
}
-QStringList Settings::directChildren(const QString &parentGroup, Scope scope) const
+QStringList Settings::directChildren(const QString &parentGroup, Scopes scopes) const
{
- QSettings * const settings = settingsForScope(scope);
- settings->beginGroup(internalRepresentation(parentGroup));
- QStringList children = settings->childGroups();
- children << settings->childKeys();
- settings->endGroup();
+ auto helper = [this, &parentGroup](const Scope scope) {
+ QSettings * const settings = settingsForScope(scope);
+ settings->beginGroup(internalRepresentation(parentGroup));
+ QStringList children = settings->childGroups();
+ children << settings->childKeys();
+ settings->endGroup();
+ return children;
+ };
+
+ QStringList children;
+ if (scopes & UserScope)
+ children += helper(UserScope);
+ if (scopes & SystemScope)
+ children += helper(SystemScope);
fixupKeys(children);
return children;
}
diff --git a/src/lib/corelib/tools/settings.h b/src/lib/corelib/tools/settings.h
index 4ea148af0..7c9c52c9e 100644
--- a/src/lib/corelib/tools/settings.h
+++ b/src/lib/corelib/tools/settings.h
@@ -70,7 +70,7 @@ public:
QVariant value(const QString &key, Scopes scopes,
const QVariant &defaultValue = QVariant()) const;
QStringList allKeys(Scopes scopes) const;
- QStringList directChildren(const QString &parentGroup, Scope scope) const; // Keys and groups.
+ QStringList directChildren(const QString &parentGroup, Scopes scopes) const; // Keys and groups.
QStringList allKeysWithPrefix(const QString &group, Scopes scopes) const;
void setValue(const QString &key, const QVariant &value);
void remove(const QString &key);
diff --git a/src/lib/corelib/tools/settingsmodel.cpp b/src/lib/corelib/tools/settingsmodel.cpp
index 89d923496..4fa0e14da 100644
--- a/src/lib/corelib/tools/settingsmodel.cpp
+++ b/src/lib/corelib/tools/settingsmodel.cpp
@@ -281,10 +281,10 @@ bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int
const QString valueString = value.toString();
QString *toChange = nullptr;
if (index.column() == keyColumn() && !valueString.isEmpty()
- && !node->parent->hasDirectChildWithName(valueString)
- && !(node->parent->parent == &d->rootNode
- && node->parent->name == Internal::StringConstants::profilesSettingsKey()
- && valueString == Profile::fallbackName())) {
+ && !node->parent->hasDirectChildWithName(valueString)
+ && (node->parent->parent != &d->rootNode
+ || node->parent->name != Internal::StringConstants::profilesSettingsKey()
+ || valueString != Profile::fallbackName())) {
toChange = &node->name;
} else if (index.column() == valueColumn() && valueString != node->value) {
toChange = &node->value;
@@ -329,7 +329,7 @@ void SettingsModel::SettingsModelPrivate::readSettings()
addNodeFromSettings(&rootNode, topLevelKey);
for (QVariantMap::ConstIterator it = additionalProperties.constBegin();
it != additionalProperties.constEnd(); ++it) {
- const QStringList nameAsList = it.key().split(QLatin1Char('.'), QBS_SKIP_EMPTY_PARTS);
+ const QStringList nameAsList = it.key().split(QLatin1Char('.'), Qt::SkipEmptyParts);
addNode(&rootNode, nameAsList.front(), nameAsList.mid(1), it.value());
}
dirty = false;
diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp
index bc07acfef..e9212c165 100644
--- a/src/lib/corelib/tools/setupprojectparameters.cpp
+++ b/src/lib/corelib/tools/setupprojectparameters.cpp
@@ -38,6 +38,8 @@
****************************************************************************/
#include "setupprojectparameters.h"
+#include "buildoptions.h"
+
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -45,6 +47,7 @@
#include <tools/jsonhelper.h>
#include <tools/profile.h>
#include <tools/qbsassert.h>
+#include <tools/qttools.h>
#include <tools/scripttools.h>
#include <tools/settings.h>
#include <tools/stringconstants.h>
@@ -90,12 +93,12 @@ public:
mutable QVariantMap buildConfigurationTree;
mutable QVariantMap overriddenValuesTree;
mutable QVariantMap finalBuildConfigTree;
+ int maxJobCount = 0;
bool overrideBuildGraphData;
bool dryRun;
bool logElapsedTime;
bool forceProbeExecution;
bool waitLockBuildGraph;
- bool fallbackProviderEnabled = true;
SetupProjectParameters::RestoreBehavior restoreBehavior;
ErrorHandlingMode propertyCheckingMode;
ErrorHandlingMode productErrorMode;
@@ -154,12 +157,14 @@ SetupProjectParameters SetupProjectParameters::fromJson(const QJsonObject &data)
setValueFromJson(params.d->projectFilePath, data, "project-file-path");
setValueFromJson(params.d->buildRoot, data, "build-root");
setValueFromJson(params.d->settingsBaseDir, data, "settings-directory");
+ setValueFromJson(params.d->maxJobCount, data, "max-job-count");
+ if (params.maxJobCount() <= 0)
+ params.setMaxJobCount(BuildOptions::defaultMaxJobCount());
setValueFromJson(params.d->overriddenValues, data, "overridden-properties");
setValueFromJson(params.d->dryRun, data, "dry-run");
setValueFromJson(params.d->logElapsedTime, data, "log-time");
setValueFromJson(params.d->forceProbeExecution, data, "force-probe-execution");
setValueFromJson(params.d->waitLockBuildGraph, data, "wait-lock-build-graph");
- setValueFromJson(params.d->fallbackProviderEnabled, data, "fallback-provider-enabled");
setValueFromJson(params.d->environment, data, "environment");
setValueFromJson(params.d->restoreBehavior, data, "restore-behavior");
setValueFromJson(params.d->propertyCheckingMode, data, "error-handling-mode");
@@ -373,6 +378,27 @@ void SetupProjectParameters::setSettingsDirectory(const QString &settingsBaseDir
}
/*!
+ * \brief Returns the maximum number of threads to employ when resolving the project.
+ * If the value is not valid (i.e. <= 0), a sensible one will be derived from the number of
+ * available processor cores.
+ * The default is 0.
+ * \sa BuildOptions::defaultMaxJobCount
+ */
+int SetupProjectParameters::maxJobCount() const
+{
+ return d->maxJobCount;
+}
+
+/*!
+ * \brief Controls how many threads to employ when resolving the project.
+ * A value <= 0 leaves the decision to qbs.
+ */
+void SetupProjectParameters::setMaxJobCount(int jobCount)
+{
+ d->maxJobCount = jobCount;
+}
+
+/*!
* Returns the overridden values of the build configuration.
*/
QVariantMap SetupProjectParameters::overriddenValues() const
@@ -490,7 +516,7 @@ ErrorInfo SetupProjectParameters::expandBuildConfiguration()
QVariantMap expandedConfig = expandedBuildConfiguration(profile, configurationName(), &err);
if (err.hasError())
return err;
- if (d->buildConfiguration != expandedConfig) {
+ if (!qVariantMapsEqual(d->buildConfiguration, expandedConfig)) {
d->buildConfigurationTree.clear();
d->buildConfiguration = expandedConfig;
}
@@ -595,22 +621,6 @@ void SetupProjectParameters::setWaitLockBuildGraph(bool wait)
}
/*!
- * \brief Returns true if qbs should fall back to pkg-config if a dependency is not found.
- */
-bool SetupProjectParameters::fallbackProviderEnabled() const
-{
- return d->fallbackProviderEnabled;
-}
-
-/*!
- * Controls whether to fall back to pkg-config if a dependency is not found.
- */
-void SetupProjectParameters::setFallbackProviderEnabled(bool enable)
-{
- d->fallbackProviderEnabled = enable;
-}
-
-/*!
* \brief Gets the environment used while resolving the project.
*/
QProcessEnvironment SetupProjectParameters::environment() const
diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h
index 67bb5298a..5cd9700a9 100644
--- a/src/lib/corelib/tools/setupprojectparameters.h
+++ b/src/lib/corelib/tools/setupprojectparameters.h
@@ -99,6 +99,9 @@ public:
QString settingsDirectory() const;
void setSettingsDirectory(const QString &settingsBaseDir);
+ int maxJobCount() const;
+ void setMaxJobCount(int jobCount);
+
QVariantMap overriddenValues() const;
void setOverriddenValues(const QVariantMap &values);
QVariantMap overriddenValuesTree() const;
@@ -129,9 +132,6 @@ public:
bool waitLockBuildGraph() const;
void setWaitLockBuildGraph(bool wait);
- bool fallbackProviderEnabled() const;
- void setFallbackProviderEnabled(bool enable);
-
QProcessEnvironment environment() const;
void setEnvironment(const QProcessEnvironment &env);
QProcessEnvironment adjustedEnvironment() const;
diff --git a/src/lib/corelib/tools/shellutils.cpp b/src/lib/corelib/tools/shellutils.cpp
index d032aecac..5fff254f6 100644
--- a/src/lib/corelib/tools/shellutils.cpp
+++ b/src/lib/corelib/tools/shellutils.cpp
@@ -57,7 +57,7 @@ QString shellInterpreter(const QString &filePath) {
const QString shebang = ts.readLine();
if (shebang.startsWith(QLatin1String("#!"))) {
return (shebang.mid(2).split(QRegularExpression(QStringLiteral("\\s")),
- QBS_SKIP_EMPTY_PARTS) << QString()).front();
+ Qt::SkipEmptyParts) << QString()).front();
}
}
diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h
index 45903032c..799a140d9 100644
--- a/src/lib/corelib/tools/stringconstants.h
+++ b/src/lib/corelib/tools/stringconstants.h
@@ -110,6 +110,7 @@ public:
QBS_STRING_CONSTANT(installDirProperty, "installDir")
QBS_STRING_CONSTANT(installSourceBaseProperty, "installSourceBase")
QBS_STRING_CONSTANT(isEnabledKey, "is-enabled")
+ QBS_STRING_CONSTANT(isEagerProperty, "isEager")
QBS_STRING_CONSTANT(jobCountProperty, "jobCount")
QBS_STRING_CONSTANT(jobPoolProperty, "jobPool")
QBS_STRING_CONSTANT(lengthProperty, "length")
diff --git a/src/lib/msbuild/io/visualstudiosolutionwriter.cpp b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
index 625489ac6..874fbc71f 100644
--- a/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
+++ b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
@@ -108,20 +108,6 @@ bool VisualStudioSolutionWriter::write(const VisualStudioSolution *solution)
<< project->guid().toString().toStdString()
<< u8"\"\n";
- const auto dependencies = solution->dependencies(project);
- if (!dependencies.empty()) {
- out << u8"\tProjectSection(ProjectDependencies) = postProject\n";
-
- for (const auto &dependency : dependencies)
- out << u8"\t\t"
- << dependency->guid().toString().toStdString()
- << u8" = "
- << dependency->guid().toString().toStdString()
- << u8"\n";
-
- out << u8"\tEndProjectSection\n";
- }
-
out << u8"EndProject\n";
}
diff --git a/src/lib/pkgconfig/pcpackage.cpp b/src/lib/pkgconfig/pcpackage.cpp
index 984936bd1..9aaefcfde 100644
--- a/src/lib/pkgconfig/pcpackage.cpp
+++ b/src/lib/pkgconfig/pcpackage.cpp
@@ -43,6 +43,8 @@
namespace qbs {
+using Internal::startsWith;
+
using ComparisonType = PcPackage::RequiredVersion::ComparisonType;
std::string_view PcPackage::Flag::typeToString(Type t)
@@ -123,6 +125,26 @@ std::optional<ComparisonType> PcPackage::RequiredVersion::comparisonFromString(s
return std::nullopt;
}
+// see https://github.com/pkgconf/pkgconf/blob/pkgconf-2.1.0/libpkgconf/tuple.c#L194
+bool PcPackage::shouldRewriteSysroot(std::string_view sysroot, std::string_view value)
+{
+ if (sysroot.empty() || value.empty())
+ return false;
+
+ if (value.front() != '/')
+ return false;
+
+ if (sysroot == "/")
+ return false;
+
+ if (startsWith(value, sysroot))
+ return false;
+
+ return true;
+}
+
+// TODO: pkg-config only prepends sysroot to flags; while pkgconf does this as a part of the
+// variable substitution and thus this affects all variables, not only flags
PcPackage PcPackage::prependSysroot(std::string_view sysroot) &&
{
PcPackage package(std::move(*this));
@@ -132,6 +154,8 @@ PcPackage PcPackage::prependSysroot(std::string_view sysroot) &&
if (sysroot.empty())
return flags;
for (auto &flag : flags) {
+ if (!shouldRewriteSysroot(sysroot, flag.value))
+ continue;
if (flag.type == Flag::Type::IncludePath
|| flag.type == Flag::Type::SystemIncludePath
|| flag.type == Flag::Type::DirAfterIncludePath
@@ -169,4 +193,51 @@ PcPackage PcPackage::removeSystemLibraryPaths(
return package;
}
+namespace Internal {
+
+std::string_view fileName(std::string_view filePath)
+{
+ const auto pos = filePath.rfind('/');
+ if (pos == std::string_view::npos) {
+#if defined(WIN32)
+ if (filePath.size() >= 2 && filePath[1] == ':')
+ return filePath.substr(2);
+#endif
+ return filePath;
+ }
+ return filePath.substr(pos + 1);
+}
+
+std::string_view completeBaseName(std::string_view filePath)
+{
+ filePath = fileName(filePath);
+ const auto pos = filePath.rfind('.');
+ return pos == std::string_view::npos
+ ? filePath
+ : filePath.substr(0, pos);
+}
+
+std::string_view parentPath(std::string_view path)
+{
+ if (path.empty())
+ return {};
+ auto pos = path.rfind('/');
+ if (pos == std::string_view::npos) {
+#if defined(WIN32)
+ if (path.size() >= 2 && path[1] == ':')
+ return path.substr(0, 2);
+#endif
+ return ".";
+ }
+ if (pos == 0)
+ return "/";
+#if defined(WIN32)
+ if (pos == 2 && path[1] == ':')
+ return path.substr(0, pos + 1);
+#endif
+ return path.substr(0, pos);
+};
+
+} // namespace Internal
+
} // namespace qbs
diff --git a/src/lib/pkgconfig/pcpackage.h b/src/lib/pkgconfig/pcpackage.h
index 9d86816aa..794e2fc40 100644
--- a/src/lib/pkgconfig/pcpackage.h
+++ b/src/lib/pkgconfig/pcpackage.h
@@ -112,12 +112,14 @@ public:
std::vector<RequiredVersion> requiresPublic;
std::vector<RequiredVersion> requiresPrivate;
std::vector<RequiredVersion> conflicts;
+ std::optional<std::string> oldPrefix;
using VariablesMap = std::map<std::string, std::string, std::less<>>;
VariablesMap variables;
bool uninstalled{false};
+ static bool shouldRewriteSysroot(std::string_view sysroot, std::string_view value);
PcPackage prependSysroot(std::string_view sysroot) &&;
PcPackage removeSystemLibraryPaths(const std::unordered_set<std::string> &libraryPaths) &&;
};
@@ -181,6 +183,26 @@ inline bool operator!=(const PcPackage::Flag &lhs, const PcPackage::Flag &rhs)
return !(lhs == rhs);
}
+namespace Internal {
+
+// fast versions (no allocations) of the QFileInfo methods
+std::string_view fileName(std::string_view filePath);
+std::string_view completeBaseName(std::string_view filePath);
+std::string_view parentPath(std::string_view path);
+
+inline bool startsWith(std::string_view haystack, std::string_view needle)
+{
+ return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0;
+}
+
+inline bool endsWith(std::string_view haystack, std::string_view needle)
+{
+ return haystack.size() >= needle.size()
+ && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0;
+}
+
+} // Internal
+
} // namespace qbs
namespace std {
diff --git a/src/lib/pkgconfig/pcparser.cpp b/src/lib/pkgconfig/pcparser.cpp
index 388363af6..eb07aa5ef 100644
--- a/src/lib/pkgconfig/pcparser.cpp
+++ b/src/lib/pkgconfig/pcparser.cpp
@@ -62,6 +62,11 @@ namespace std {
namespace qbs {
+using Internal::completeBaseName;
+using Internal::parentPath;
+using Internal::startsWith;
+using Internal::endsWith;
+
namespace {
// workaround for a missing ctor before c++20
@@ -195,17 +200,6 @@ std::optional<std::vector<std::string>> splitCommand(std::string_view s)
return result;
}
-bool startsWith(std::string_view haystack, std::string_view needle)
-{
- return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0;
-}
-
-bool endsWith(std::string_view haystack, std::string_view needle)
-{
- return haystack.size() >= needle.size()
- && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0;
-}
-
[[noreturn]] void raizeUnknownComparisonException(const PcPackage &pkg, std::string_view verName, std::string_view comp)
{
std::string message;
@@ -397,17 +391,6 @@ PcPackage::RequiredVersion::ComparisonType comparisonFromString(
raizeUnknownComparisonException(pkg, verName, comp);
}
-std::string baseName(const std::string_view &filePath)
-{
- auto pos = filePath.rfind('/');
- const auto fileName =
- pos == std::string_view::npos ? std::string_view() : filePath.substr(pos + 1);
- pos = fileName.rfind('.');
- return std::string(pos == std::string_view::npos
- ? std::string_view()
- : fileName.substr(0, pos));
-}
-
} // namespace
PcParser::PcParser(const PkgConfig &pkgConfig)
@@ -429,7 +412,7 @@ try
if (!file.is_open())
throw PcException(std::string("Can't open file ") + path);
- package.baseFileName = baseName(path);
+ package.baseFileName = std::string{completeBaseName(path)};
#if HAS_STD_FILESYSTEM
const auto fsPath = std::filesystem::path(path);
package.filePath = fsPath.generic_string();
@@ -445,7 +428,7 @@ try
parseLine(package, line);
return package;
} catch(const PcException &ex) {
- return PcBrokenPackage{path, baseName(path), ex.what()};
+ return PcBrokenPackage{path, std::string{completeBaseName(path)}, ex.what()};
}
std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view str) const
@@ -488,6 +471,35 @@ std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view s
return result;
}
+std::string PcParser::evaluateVariable(
+ PcPackage &pkg, std::string_view tag, std::string_view str) const
+{
+ static constexpr std::string_view prefixVariable = "prefix";
+ if (m_pkgConfig.options().definePrefix) {
+ if (tag == prefixVariable) {
+ std::string_view prefix = pkg.filePath;
+ prefix = parentPath(prefix);
+ if (completeBaseName(prefix) == "pkgconfig") {
+ prefix = parentPath(prefix);
+ prefix = parentPath(prefix);
+ }
+ pkg.oldPrefix = std::string(str);
+ if (!prefix.empty())
+ str = prefix;
+ return std::string(str);
+ } else if (pkg.oldPrefix
+ && str.size() > pkg.oldPrefix->size()
+ && str.substr(0, pkg.oldPrefix->size()) == *pkg.oldPrefix
+ && str[pkg.oldPrefix->size()] == '/') {
+ auto result = pkg.variables["prefix"];
+ result += str.substr(pkg.oldPrefix->size());
+ return trimAndSubstitute(pkg, result);
+ }
+ }
+
+ return trimAndSubstitute(pkg, str);
+}
+
void PcParser::parseStringField(
PcPackage &pkg,
std::string &field,
@@ -512,9 +524,11 @@ void PcParser::parseLibs(
raiseDuplicateFieldException(fieldName, pkg.filePath);
const auto trimmed = trimAndSubstitute(pkg, str);
+ if (trimmed.empty())
+ return;
const auto argv = splitCommand(trimmed);
- if (!trimmed.empty() && !argv)
+ if (!argv)
throw PcException("Couldn't parse Libs field into an argument vector");
libs = doParseLibs(*argv);
@@ -581,9 +595,11 @@ void PcParser::parseCFlags(PcPackage &pkg, std::string_view str)
raiseDuplicateFieldException("Cflags", pkg.filePath);
const auto command = trimAndSubstitute(pkg, str);
+ if (command.empty())
+ return;
const auto argv = splitCommand(command);
- if (!command.empty() && !argv)
+ if (!argv)
throw PcException("Couldn't parse Cflags field into an argument vector");
std::vector<PcPackage::Flag> cflags;
@@ -710,10 +726,8 @@ void PcParser::parseLine(PcPackage &pkg, std::string_view str)
size_t pos = 0;
for (; pos < s.size(); ++pos) {
auto p = s.data() + pos;
- if (!((*p >= 'A' && *p <= 'Z') ||
- (*p >= 'a' && *p <= 'z') ||
- (*p >= '0' && *p <= '9') ||
- *p == '_' || *p == '.')) {
+ if ((*p < 'A' || *p > 'Z') && (*p < 'a' || *p > 'z') && (*p < '0' || *p > '9')
+ && *p != '_' && *p != '.') {
break;
}
}
@@ -765,14 +779,7 @@ void PcParser::parseLine(PcPackage &pkg, std::string_view str)
str.remove_prefix(1); // cut '='
str = trimmed(str);
- // TODO: support guesstimating of the prefix variable (pkg-config's --define-prefix option)
- // from doc: "try to override the value of prefix for each .pc file found with a
- // guesstimated value based on the location of the .pc file"
- // https://gitlab.freedesktop.org/pkg-config/pkg-config/-/blob/pkg-config-0.29.2/parse.c#L998
- // This option is disabled by default, and Qbs doesn't allow to override it yet, so we can
- // ignore this feature for now
-
- const auto value = trimAndSubstitute(pkg, str);
+ const auto value = evaluateVariable(pkg, tag, str);
if (!pkg.variables.insert({std::string(tag), value}).second)
raizeDuplicateVariableException(pkg, tag);
}
diff --git a/src/lib/pkgconfig/pcparser.h b/src/lib/pkgconfig/pcparser.h
index ffdf86aaa..a8a5e5cd4 100644
--- a/src/lib/pkgconfig/pcparser.h
+++ b/src/lib/pkgconfig/pcparser.h
@@ -55,6 +55,7 @@ public:
private:
std::string trimAndSubstitute(const PcPackage &pkg, std::string_view str) const;
+ std::string evaluateVariable(PcPackage &pkg, std::string_view tag, std::string_view str) const;
void parseStringField(
PcPackage &pkg,
std::string &field,
diff --git a/src/lib/pkgconfig/pkgconfig.cpp b/src/lib/pkgconfig/pkgconfig.cpp
index b2c3d4d71..6b3bde46c 100644
--- a/src/lib/pkgconfig/pkgconfig.cpp
+++ b/src/lib/pkgconfig/pkgconfig.cpp
@@ -106,50 +106,6 @@ constexpr inline char listSeparator() noexcept
#endif
}
-// based on https://stackoverflow.com/a/33135699/295518
-int compareVersions(std::string_view v1, std::string_view v2)
-{
- for (size_t i = 0, j = 0; i < v1.length() || j < v2.length(); ) {
- size_t acc1 = 0;
- size_t acc2 = 0;
-
- while (i < v1.length() && v1[i] != '.') {
- acc1 = acc1 * 10 + (v1[i] - '0');
- i++;
- }
- while (j < v2.length() && v2[j] != '.') {
- acc2 = acc2 * 10 + (v2[j] - '0');
- j++;
- }
-
- if (acc1 < acc2)
- return -1;
- if (acc1 > acc2)
- return +1;
-
- ++i;
- ++j;
- }
- return 0;
-}
-
-using ComparisonType = PcPackage::RequiredVersion::ComparisonType;
-
-bool versionTest(ComparisonType comparison, std::string_view a, std::string_view b)
-{
- switch (comparison) {
- case ComparisonType::LessThan: return compareVersions(a, b) < 0;
- case ComparisonType::GreaterThan: return compareVersions(a, b) > 0;
- case ComparisonType::LessThanEqual: return compareVersions(a, b) <= 0;
- case ComparisonType::GreaterThanEqual: return compareVersions(a, b) >= 0;
- case ComparisonType::Equal: return compareVersions(a, b) == 0;
- case ComparisonType::NotEqual: return compareVersions(a, b) != 0;
- case ComparisonType::AlwaysMatch: return true;
- }
-
- return false;
-}
-
[[noreturn]] void raizeUnknownPackageException(std::string_view package)
{
std::string message;
@@ -334,116 +290,6 @@ PcBrokenPackage makeVersionMismatchDependency(
package.filePath, package.baseFileName, std::move(message)};
}
-PkgConfig::Packages PkgConfig::mergeDependencies(const PkgConfig::Packages &packages) const
-{
- std::unordered_map<std::string_view, const PcPackageVariant *> packageHash;
-
- struct MergedHashEntry
- {
- PcPackageVariant package; // merged package or broken package
- std::vector<const PcPackage *> deps; // unmerged transitive deps, including Package itself
- };
- std::unordered_map<std::string, MergedHashEntry> mergedHash;
-
- for (const auto &package: packages)
- packageHash[package.getBaseFileName()] = &package;
-
- auto func = [&](const PcPackageVariant &package, auto &f) -> const MergedHashEntry &
- {
- const auto it = mergedHash.find(package.getBaseFileName());
- if (it != mergedHash.end())
- return it->second;
-
- auto &entry = mergedHash[package.getBaseFileName()];
-
- auto visitor = [&](auto &&package) -> PcPackageVariant {
-
- using T = std::decay_t<decltype(package)>;
- if constexpr (std::is_same_v<T, PcPackage>) { // NOLINT
-
- using Flags = std::vector<PcPackage::Flag>;
-
- // returns true if multiple copies of the flag can present in the same package
- // we can't properly merge flags that have multiple parameters except for
- // -framework which we handle correctly.
- auto canHaveDuplicates = [](const PcPackage::Flag::Type &type) {
- return type == PcPackage::Flag::Type::LinkerFlag
- || type == PcPackage::Flag::Type::CompilerFlag;
- };
-
- std::unordered_set<PcPackage::Flag> visitedFlags;
- // appends only those flags to the target that were not seen before (except for
- // ones that can have duplicates)
- auto mergeFlags = [&](Flags &target, const Flags &source)
- {
- for (const auto &flag: source) {
- if (canHaveDuplicates(flag.type) || visitedFlags.insert(flag).second)
- target.push_back(flag);
- }
- };
-
- std::unordered_set<const PcPackage *> visitedDeps;
-
- PcPackage result;
- // copy only meta info for now
- result.filePath = package.filePath;
- result.baseFileName = package.baseFileName;
- result.name = package.name;
- result.version = package.version;
- result.description = package.description;
- result.url = package.url;
- result.variables = package.variables;
-
- auto allDependencies = package.requiresPublic;
- if (m_options.staticMode)
- allDependencies << package.requiresPrivate;
-
- for (const auto &dependency: allDependencies) {
- const auto it = packageHash.find(dependency.name);
- if (it == packageHash.end())
- return makeMissingDependency(result, dependency);
-
- const auto childEntry = f(*it->second, f);
- if (childEntry.package.isBroken())
- return makeBrokenDependency(result, dependency);
-
- const auto &mergedPackage = childEntry.package.asPackage();
- const bool versionOk = versionTest(
- dependency.comparison, mergedPackage.version, dependency.version);
- if (!versionOk)
- return makeVersionMismatchDependency(result, mergedPackage, dependency);
-
- for (const auto *dep: childEntry.deps) {
- if (visitedDeps.insert(dep).second)
- entry.deps.push_back(dep);
- }
- }
-
- entry.deps.push_back(&package);
-
- for (const auto *dep: entry.deps) {
- mergeFlags(result.libs, dep->libs);
- mergeFlags(result.cflags, dep->cflags);
- }
-
- return result;
- }
- return package;
- };
- entry.package = package.visit(visitor);
-
- return entry;
- };
-
- for (const auto &package: packages)
- func(package, func);
-
- Packages result;
- for (auto &[key, value]: mergedHash)
- result.push_back(std::move(value.package));
- return result;
-}
-
PkgConfig::Packages PkgConfig::findPackages() const
{
Packages result;
@@ -480,9 +326,6 @@ PkgConfig::Packages PkgConfig::findPackages() const
result.emplace_back(std::move(pkg));
}
- if (m_options.mergeDependencies)
- result = mergeDependencies(result);
-
const auto lessThanPackage = [](const PcPackageVariant &lhs, const PcPackageVariant &rhs)
{
return lhs.getBaseFileName() < rhs.getBaseFileName();
diff --git a/src/lib/pkgconfig/pkgconfig.h b/src/lib/pkgconfig/pkgconfig.h
index c1cc634e3..fd4cc3253 100644
--- a/src/lib/pkgconfig/pkgconfig.h
+++ b/src/lib/pkgconfig/pkgconfig.h
@@ -58,7 +58,7 @@ public:
std::vector<std::string> systemLibraryPaths; // PKG_CONFIG_SYSTEM_LIBRARY_PATH
bool disableUninstalled{true}; // PKG_CONFIG_DISABLE_UNINSTALLED
bool staticMode{false};
- bool mergeDependencies{true};
+ bool definePrefix{false};
VariablesMap globalVariables;
VariablesMap systemVariables;
};
@@ -77,7 +77,6 @@ public:
private:
Packages findPackages() const;
- Packages mergeDependencies(const Packages &packages) const;
private:
Options m_options;