diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2017-07-21 16:53:56 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2017-08-01 12:14:54 +0000 |
commit | cccf7a64cb312e6dfb89343cc6087d43cdfeb427 (patch) | |
tree | dbe429b66ff40393bcb1c7764fe61ff4c07653af | |
parent | d4c4d5743d11fd4af3b3b536cf446e1735773d44 (diff) | |
download | qbs-cccf7a64cb312e6dfb89343cc6087d43cdfeb427.tar.gz |
Introduce the Profile item
Allows users to provide project-specific profiles. Useful
for when a project has specific, well-known requirements
regarding the build environment and/or target platform.
[ChangeLog] Profiles can now be defined within a project
using the Profile item.
Task-number: QBS-895
Change-Id: Idf902fbb095bb153c15e31d2aa8eb73448b69936
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r-- | doc/reference/items/language/product.qdoc | 2 | ||||
-rw-r--r-- | doc/reference/items/language/profile.qdoc | 95 | ||||
-rw-r--r-- | doc/reference/items/language/project.qdoc | 2 | ||||
-rw-r--r-- | src/lib/corelib/language/builtindeclarations.cpp | 13 | ||||
-rw-r--r-- | src/lib/corelib/language/builtindeclarations.h | 1 | ||||
-rw-r--r-- | src/lib/corelib/language/itemtype.h | 1 | ||||
-rwxr-xr-x | src/lib/corelib/language/moduleloader.cpp | 111 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleloader.h | 7 | ||||
-rw-r--r-- | tests/auto/api/testdata/local-profiles/local-profiles.qbs | 44 | ||||
-rw-r--r-- | tests/auto/api/tst_api.cpp | 120 | ||||
-rw-r--r-- | tests/auto/api/tst_api.h | 2 |
11 files changed, 392 insertions, 6 deletions
diff --git a/doc/reference/items/language/product.qdoc b/doc/reference/items/language/product.qdoc index b96752f9f..9574190dc 100644 --- a/doc/reference/items/language/product.qdoc +++ b/doc/reference/items/language/product.qdoc @@ -28,7 +28,7 @@ \contentspage list-of-language-items.html \previouspage probe-item.html \page product-item.html - \nextpage project-item.html + \nextpage profile-item.html \ingroup list-of-language-items \ingroup list-of-items \keyword QML.Product diff --git a/doc/reference/items/language/profile.qdoc b/doc/reference/items/language/profile.qdoc new file mode 100644 index 000000000..c9dcc64cd --- /dev/null +++ b/doc/reference/items/language/profile.qdoc @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! + \contentspage list-of-language-items.html + \previouspage product-item.html + \page profile-item.html + \nextpage project-item.html + \ingroup list-of-language-items + \ingroup list-of-items + \keyword QML.Properties + + \title Profile Item + \brief Creates a profile within the project + + The profiles used by \QBS are normally set up on a user's machine and are then available + to all projects. See the \l Configuring section for information on how to set up and + use profiles on the command line. + In some rare cases, however, the creator of a project has complete knowledge about the system + on which that project is to be built. Then it can make sense to integrate the profile into + the project: + + \code + Product { + // ... + Profile { + name: "my-special-profile" + qbs.toolchain: ["gcc"] + qbs.targetOS: ["linux"] + qbs.architecture: "arm" + cpp.toolchainInstallPath: "/opt/special-gcc/bin" + cpp.toolchainPrefix: "arm-linux-special-" + } + qbs.profiles: ["my-special-profile"] + // ... + } + \endcode + + The project in the above example can be built in a particular well-known environment + without any additional setup. + + \c Profile items can appear inside \l{Product Item}{Product} and \l{Project Item}{Project} + items. + + \section1 Profile Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li baseProfile + \li \c string + \li \c undefined + \li The name of a profile from which this profile inherits. If the same property is + set in both this profile and the base profile, the value from this profile + takes precedence. + \row + \li condition + \li \c bool + \li \c true + \li If this property is set to \c false, the profile cannot be used. + \row + \li name + \li \c string + \li \c undefined + \li The name under which the profile can be referenced later. Setting this property + is required. The value must be unique among all profiles in an entire project. + \endtable +*/ diff --git a/doc/reference/items/language/project.qdoc b/doc/reference/items/language/project.qdoc index 581d20e21..18330f574 100644 --- a/doc/reference/items/language/project.qdoc +++ b/doc/reference/items/language/project.qdoc @@ -26,7 +26,7 @@ ****************************************************************************/ /*! \contentspage list-of-language-items.html - \previouspage product-item.html + \previouspage profile-item.html \page project-item.html \nextpage properties-item.html \ingroup list-of-language-items diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index fec9d8783..9eda212b7 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -71,6 +71,7 @@ BuiltinDeclarations::BuiltinDeclarations() { QLatin1String("Parameters"), ItemType::Parameters }, { QLatin1String("Probe"), ItemType::Probe }, { QLatin1String("Product"), ItemType::Product }, + { QLatin1String("Profile"), ItemType::Profile }, { QLatin1String("Project"), ItemType::Project }, { QLatin1String("Properties"), ItemType::Properties }, // Callers have to handle the SubProject case. { QLatin1String("PropertyOptions"), ItemType::PropertyOptions }, @@ -88,6 +89,7 @@ BuiltinDeclarations::BuiltinDeclarations() addModuleItem(); addProbeItem(); addProductItem(); + addProfileItem(); addProjectItem(); addPropertiesItem(); addPropertyOptionsItem(); @@ -317,6 +319,7 @@ void BuiltinDeclarations::addProductItem() << ItemType::FileTagger << ItemType::Export << ItemType::Probe + << ItemType::Profile << ItemType::PropertyOptions << ItemType::Rule); item << conditionProperty(); @@ -359,6 +362,15 @@ void BuiltinDeclarations::addProductItem() insert(item); } +void BuiltinDeclarations::addProfileItem() +{ + ItemDeclaration item(ItemType::Profile); + item << conditionProperty(); + item << nameProperty(); + item << PropertyDeclaration(QLatin1String("baseProfile"), PropertyDeclaration::String); + insert(item); +} + void BuiltinDeclarations::addProjectItem() { ItemDeclaration item(ItemType::Project); @@ -367,6 +379,7 @@ void BuiltinDeclarations::addProjectItem() << ItemType::PropertyOptions << ItemType::SubProject << ItemType::Product + << ItemType::Profile << ItemType::Probe << ItemType::FileTagger << ItemType::Rule); diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h index 745a5766f..8106b8a74 100644 --- a/src/lib/corelib/language/builtindeclarations.h +++ b/src/lib/corelib/language/builtindeclarations.h @@ -79,6 +79,7 @@ private: static ItemDeclaration moduleLikeItem(ItemType type); void addProbeItem(); void addProductItem(); + void addProfileItem(); void addProjectItem(); void addPropertiesItem(); void addPropertyOptionsItem(); diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h index 4901cbbe4..c0e76c94b 100644 --- a/src/lib/corelib/language/itemtype.h +++ b/src/lib/corelib/language/itemtype.h @@ -58,6 +58,7 @@ enum class ItemType { Parameters, Probe, Product, + Profile, Project, Properties, PropertiesInSubProject, diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index 7a5f2da8f..954367f78 100755 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -385,6 +385,8 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) Set<QString>() << QDir::cleanPath(parameters.projectFilePath())); result.root = root; result.qbsFiles = m_reader->filesRead(); + for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) + result.profileConfigs.remove(it.key()); printProfilingInfo(); return result; } @@ -634,6 +636,8 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, m_qbsVersion.toString())); } + handleProfileItems(projectItem, &projectContext); + QList<Item *> multiplexedProducts; for (Item * const child : projectItem->children()) { child->setScope(projectContext.scope); @@ -969,6 +973,8 @@ void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productI m_logger.qbsTrace() << "[MODLDR] prepareProduct " << productItem->file()->filePath(); ProductContext productContext; + productContext.item = productItem; + productContext.project = projectContext; productContext.name = m_evaluator->stringValue(productItem, QLatin1String("name")); QBS_CHECK(!productContext.name.isEmpty()); bool profilePropertySet; @@ -979,7 +985,13 @@ void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productI QBS_CHECK(profilePropertySet); const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName); if (it == projectContext->result->profileConfigs.constEnd()) { - const Profile profile(productContext.profileName, m_settings.get()); + const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles); + if (!profile.exists()) { + ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()), + productItem->location()); + handleProductError(error, &productContext); + return; + } const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( profile, m_parameters.configurationName()); productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( @@ -989,8 +1001,6 @@ void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productI } else { productContext.moduleProperties = it.value().toMap(); } - productContext.item = productItem; - productContext.project = projectContext; initProductProperties(productContext); ItemValuePtr itemValue = ItemValue::create(productItem); @@ -1356,8 +1366,10 @@ QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath, subItem->setParent(dummyContext.project->item); QList<Item *> loadedItems; loadedItems << subItem; - if (subItem->type() == ItemType::Product) + if (subItem->type() == ItemType::Product) { + handleProfileItems(subItem, dummyContext.project); loadedItems << multiplexProductItem(&dummyContext, subItem); + } return loadedItems; } @@ -1642,6 +1654,97 @@ Item *ModuleLoader::loadItemFromFile(const QString &filePath) return item; } +void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext) +{ + const std::vector<Item *> profileItems = collectProfileItems(item, projectContext); + for (Item * const profileItem : profileItems) { + try { + handleProfile(profileItem); + } catch (const ErrorInfo &e) { + handlePropertyError(e, m_parameters, m_logger); + } + } +} + +std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext) +{ + QList<Item *> childItems = item->children(); + std::vector<Item *> profileItems; + Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr; + for (auto it = childItems.begin(); it != childItems.end();) { + Item * const childItem = *it; + if (childItem->type() == ItemType::Profile) { + if (!scope) { + const ItemValuePtr itemValue = ItemValue::create(item); + scope = Item::create(m_pool, ItemType::Scope); + scope->setProperty(QLatin1String("product"), itemValue); + scope->setFile(item->file()); + scope->setScope(projectContext->scope); + } + childItem->setScope(scope); + profileItems.push_back(childItem); + it = childItems.erase(it); + } else { + if (childItem->type() == ItemType::Product) { + for (Item * const profileItem : collectProfileItems(childItem, projectContext)) + profileItems.push_back(profileItem); + } + ++it; + } + } + if (!profileItems.empty()) + item->setChildren(childItems); + return profileItems; +} + +void ModuleLoader::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) { + QualifiedId name = namePrefix; + name << it.key(); + switch (it.value()->type()) { + case Value::ItemValueType: + evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(), + profileItem, values); + break; + case Value::VariantValueType: + values.insert(name.join(QLatin1Char('.')), + std::static_pointer_cast<VariantValue>(it.value())->value()); + break; + case Value::JSSourceValueType: + item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type + if (item != profileItem) + item->setScope(profileItem); + values.insert(name.join(QLatin1Char('.')), + m_evaluator->value(item, it.key()).toVariant()); + break; + } + } +} + +void ModuleLoader::handleProfile(Item *profileItem) +{ + QVariantMap values; + evaluateProfileValues(QualifiedId(), profileItem, profileItem, values); + const bool condition = values.take(QLatin1String("condition")).toBool(); + if (!condition) + return; + const QString profileName = values.take(QLatin1String("name")).toString(); + if (profileName.isEmpty()) + throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location()); + if (profileName == Profile::fallbackName()) { + throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.") + .arg(profileName), profileItem->location()); + } + if (m_localProfiles.contains(profileName)) { + throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName), + profileItem->location()); + } + m_localProfiles.insert(profileName, values); +} + void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem, const ModuleDependencies &reverseDepencencies) { diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h index 99b824b57..3d62525aa 100644 --- a/src/lib/corelib/language/moduleloader.h +++ b/src/lib/corelib/language/moduleloader.h @@ -327,6 +327,12 @@ private: QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group); Item *loadItemFromFile(const QString &filePath); + void handleProfileItems(Item *item, ProjectContext *projectContext); + std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext); + void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, + QVariantMap &values); + void handleProfile(Item *profileItem); + ItemPool *m_pool; Logger &m_logger; ProgressObserver *m_progressObserver; @@ -357,6 +363,7 @@ private: QHash<QString, QList<ProbeConstPtr>> m_oldProductProbes; QHash<CodeLocation, QList<ProbeConstPtr>> m_currentProbes; QVariantMap m_storedProfiles; + QVariantMap m_localProfiles; std::multimap<QString, const ProductContext *> m_productsByName; SetupProjectParameters m_parameters; std::unique_ptr<Settings> m_settings; diff --git a/tests/auto/api/testdata/local-profiles/local-profiles.qbs b/tests/auto/api/testdata/local-profiles/local-profiles.qbs new file mode 100644 index 000000000..bc37f1229 --- /dev/null +++ b/tests/auto/api/testdata/local-profiles/local-profiles.qbs @@ -0,0 +1,44 @@ +import qbs + +Project { + property string windowsProfile: "windowsProfile" + property bool enableProfiles + property stringList mingwToolchain: ["mingw", "gcc"] + Profile { + name: windowsProfile + qbs.targetOS: ["windows"] + } + + Profile { + name: "mingwProfile" + condition: enableProfiles + baseProfile: project.windowsProfile + qbs.toolchain: project.mingwToolchain + } + + Application { + name: "app" + Depends { name: "cpp"; required: false } + multiplexByQbsProperties: ["buildVariants", "profiles"] + qbs.buildVariants: ["debug", "release"] + qbs.profiles: ["mingwProfile"] + } + DynamicLibrary { + name: "lib" + + Depends { name: "cpp"; required: false } + + property stringList clangToolchain: ["clang", "llvm", "gcc"] + property string clangProfileName: "clangProfile" + + Profile { + name: product.clangProfileName + condition: project.enableProfiles + qbs.targetOS: ["linux", "unix"] + qbs.toolchain: product.clangToolchain + } + + multiplexByQbsProperties: ["profiles"] + qbs.profiles: ["mingwProfile", "clangProfile"] + } +} diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp index 0b46f04ba..f101bb8e0 100644 --- a/tests/auto/api/tst_api.cpp +++ b/tests/auto/api/tst_api.cpp @@ -1489,6 +1489,126 @@ void TestApi::listBuildSystemFiles() + "/subproject2/subproject3/subproject3.qbs")); } +void TestApi::localProfiles() +{ + QFETCH(bool, enableProfiles); + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("local-profiles/local-profiles.qbs"); + setupParams.setOverriddenValues( + {std::make_pair(QString("project.enableProfiles"), enableProfiles)}); + QScopedPointer<qbs::SetupProjectJob> job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + QString taskDescriptions; + const auto taskDescHandler = [&taskDescriptions](const QString &desc, int, qbs::AbstractJob *) { + taskDescriptions += '\n' + desc; + }; + connect(job.data(), &qbs::AbstractJob::taskStarted, taskDescHandler); + waitForFinished(job.data()); + const QString error = job->error().toString(); + QVERIFY2(job->error().hasError() == !enableProfiles, qPrintable(error)); + if (!enableProfiles) { + QVERIFY2(error.contains("does not exist"), qPrintable(error)); + return; + } + QVERIFY2(taskDescriptions.contains("Resolving"), qPrintable(taskDescriptions)); + + qbs::ProjectData project = job->project().projectData(); + QList<qbs::ProductData> products = project.allProducts(); + QCOMPARE(products.count(), 4); + qbs::ProductData libMingw; + qbs::ProductData libClang; + qbs::ProductData appDebug; + qbs::ProductData appRelease; + for (const qbs::ProductData &p : qAsConst(products)) { + if (p.name() == "lib") { + if (p.profile() == "mingwProfile") + libMingw = p; + else if (p.profile() == "clangProfile") + libClang = p; + } else if (p.name() == "app") { + const QString buildVariant + = p.moduleProperties().getModuleProperty("qbs", "buildVariant").toString(); + if (buildVariant == "debug") + appDebug = p; + else if (buildVariant == "release") + appRelease = p; + + } + } + QVERIFY(libMingw.isValid()); + QVERIFY((libClang.isValid())); + QVERIFY(appDebug.isValid()); + QVERIFY(appRelease.isValid()); + QCOMPARE(appDebug.profile(), QLatin1String("mingwProfile")); + QCOMPARE(appRelease.profile(), QLatin1String("mingwProfile")); + + qbs::PropertyMap moduleProps = libMingw.moduleProperties(); + QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(), + QStringList({"windows"})); + QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(), + QStringList({"mingw", "gcc"})); + if (moduleProps.getModuleProperty("cpp", "present").toBool()) { + QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(), + QString("g++")); + } + moduleProps = libClang.moduleProperties(); + QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(), + QStringList({"linux", "unix"})); + QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(), + QStringList({"clang", "llvm", "gcc"})); + if (moduleProps.getModuleProperty("cpp", "present").toBool()) { + QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(), + QString("clang++")); + } + moduleProps = appDebug.moduleProperties(); + if (moduleProps.getModuleProperty("cpp", "present").toBool()) + QCOMPARE(moduleProps.getModuleProperty("cpp", "optimization").toString(), QString("none")); + moduleProps = appRelease.moduleProperties(); + if (moduleProps.getModuleProperty("cpp", "present").toBool()) + QCOMPARE(moduleProps.getModuleProperty("cpp", "optimization").toString(), QString("fast")); + + taskDescriptions.clear(); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + connect(job.data(), &qbs::AbstractJob::taskStarted, taskDescHandler); + waitForFinished(job.data()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + QVERIFY2(!taskDescriptions.contains("Resolving"), qPrintable(taskDescriptions)); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile projectFile(setupParams.projectFilePath()); + QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString())); + QByteArray content = projectFile.readAll(); + content.replace("\"clang\", \"llvm\", ", QByteArray()); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.data()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + project = job->project().projectData(); + products = project.allProducts(); + QCOMPARE(products.count(), 4); + int clangProfiles = 0; + for (const qbs::ProductData &p : qAsConst(products)) { + if (p.profile() == "clangProfile") { + ++clangProfiles; + moduleProps = p.moduleProperties(); + if (moduleProps.getModuleProperty("cpp", "present").toBool()) { + QCOMPARE(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString(), + QString("g++")); + } + } + } + QCOMPARE(clangProfiles, 1); +} + +void TestApi::localProfiles_data() +{ + QTest::addColumn<bool>("enableProfiles"); + QTest::newRow("profiles enabled") << true; + QTest::newRow("profiles disabled") << false; +} + void TestApi::missingSourceFile() { qbs::SetupProjectParameters setupParams diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h index beb0ab4ac..b70c69afb 100644 --- a/tests/auto/api/tst_api.h +++ b/tests/auto/api/tst_api.h @@ -100,6 +100,8 @@ private slots: void linkDynamicAndStaticLibs(); void linkStaticAndDynamicLibs(); void listBuildSystemFiles(); + void localProfiles(); + void localProfiles_data(); void missingSourceFile(); void mocCppIncluded(); void multiArch(); |