diff options
author | Christian Kandeler <christian.kandeler@digia.com> | 2014-05-22 12:02:12 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@digia.com> | 2014-07-02 16:46:11 +0200 |
commit | e264281c4810bb0afb07e8f590fc31e504d5e4d0 (patch) | |
tree | 1bf8a9f0aaf1f6734e4acf53003a681048156efd | |
parent | 9297ea217284e2279f2d4471b3f8fc754dc5fe71 (diff) |
Make it possible to set profiles per product.
Use case 1: Build product A for architecture X and product B
for architecture Y (e.g. host tools vs target libraries).
Use case 2: Build product A for architectures X and Y and
product B only for architecture X (e.g. Android
multi-arch packages).
Change-Id: I2eb721c37cdd12c298ee12bad60e21e94b04676b
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
30 files changed, 540 insertions, 187 deletions
diff --git a/doc/reference/items/depends.qdoc b/doc/reference/items/depends.qdoc index 17816a455..7015c47f8 100644 --- a/doc/reference/items/depends.qdoc +++ b/doc/reference/items/depends.qdoc @@ -66,6 +66,13 @@ \li undefined \li The name of the dependent product or module. \row + \li profiles + \li stringList + \li \c{[product.profile]} + \li If the dependency is on a product and that product is going to be built for more than + one profile, then you can specify here which instance of the product the dependency is on. + See the \c profiles property of the \c Product item for more information. + \row \li submodules \li stringList \li undefined diff --git a/doc/reference/items/product.qdoc b/doc/reference/items/product.qdoc index 28737c9b9..b27f76c3d 100644 --- a/doc/reference/items/product.qdoc +++ b/doc/reference/items/product.qdoc @@ -85,6 +85,15 @@ \li empty string \li The name of the product. Used to identify the product in a \c Depends item, for example. \row + \li profiles + \li stringList + \li \c{[project.profile]} + \li The profiles for which the product should be built. For each profile listed here, + one instance of the product will be built according to the properties set in + the respective profile. + This property is only relevant for projects that require products being built for + different architectures. Otherwise it can be left at its default value. + \row \li type \li stringList \li empty list @@ -146,6 +155,11 @@ \li The build directory for this product. This is the directory where generated files are placed. \row + \li profile + \li string + \li The profile for building this particular instance of the product. Derived + automatically from the \c profiles property. + \row \li sourceDirectory \li path \li The source directory for this product. This is the directory of the file where this diff --git a/doc/reference/items/project.qdoc b/doc/reference/items/project.qdoc index c1a62bc40..91a548999 100644 --- a/doc/reference/items/project.qdoc +++ b/doc/reference/items/project.qdoc @@ -72,6 +72,12 @@ \li basename of the file the project is defined in \li The project name. Only relevant for e.g. displaying a project tree in an IDE. \row + \li profile + \li string + \li n/a + \li The top-level profile for building the project. This property is read-only and + is set by \QBS when the project is being set up. + \row \li condition \li bool \li true diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index b77ee138d..25750d6a1 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -115,16 +115,17 @@ public: QList<ResolvedProductPtr> internalProducts(const QList<ProductData> &products) const; QList<ResolvedProductPtr> allEnabledInternalProducts() const; ResolvedProductPtr internalProduct(const ProductData &product) const; - ProductData findProductData(const QString &productName) const; + ProductData findProductData(const ProductData &product) const; + QList<ProductData> findProductsByName(const QString &name) const; GroupData findGroupData(const ProductData &product, const QString &groupName) const; GroupData createGroupDataFromGroup(const GroupPtr &resolvedGroup); struct GroupUpdateContext { - ResolvedProductPtr resolvedProduct; - GroupPtr resolvedGroup; - ProductData currentProduct; - GroupData currentGroup; + QList<ResolvedProductPtr> resolvedProducts; + QList<GroupPtr> resolvedGroups; + QList<ProductData> products; + QList<GroupData> groups; }; struct FileListUpdateContext { @@ -242,8 +243,10 @@ static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPt const ProductData &product) { foreach (const ResolvedProductPtr &resolvedProduct, project->products) { - if (product.name() == resolvedProduct->name) + if (product.name() == resolvedProduct->name + && product.profile() == resolvedProduct->profile) { return resolvedProduct; + } } foreach (const ResolvedProjectConstPtr &subProject, project->subProjects) { const ResolvedProductPtr &p = internalProductForProject(subProject, product); @@ -258,15 +261,25 @@ ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) c return internalProductForProject(internalProject, product); } -ProductData ProjectPrivate::findProductData(const QString &productName) const +ProductData ProjectPrivate::findProductData(const ProductData &product) const { foreach (const ProductData &p, m_projectData.allProducts()) { - if (p.name() == productName) + if (p.name() == product.name() && p.profile() == product.profile()) return p; } return ProductData(); } +QList<ProductData> ProjectPrivate::findProductsByName(const QString &name) const +{ + QList<ProductData> list; + foreach (const ProductData &p, m_projectData.allProducts()) { + if (p.name() == name) + list << p; + } + return list; +} + GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const { foreach (const GroupData &g, product.groups()) { @@ -301,22 +314,20 @@ void ProjectPrivate::addGroup(const ProductData &product, const QString &groupNa throw ErrorInfo(Tr::tr("Group has an empty name.")); if (!product.isValid()) throw ErrorInfo(Tr::tr("Product is invalid.")); - const ResolvedProductPtr resolvedProduct = internalProduct(product); - if (!resolvedProduct) + QList<ProductData> products = findProductsByName(product.name()); + if (products.isEmpty()) throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + const QList<ResolvedProductPtr> resolvedProducts = internalProducts(products); + QBS_CHECK(products.count() == resolvedProducts.count()); - // Guard against calls with outdated product data. - const ProductData currentProduct= findProductData(product.name()); - QBS_CHECK(currentProduct.isValid()); - - foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) { + foreach (const GroupPtr &resolvedGroup, resolvedProducts.first()->groups) { if (resolvedGroup->name == groupName) { throw ErrorInfo(Tr::tr("Group '%1' already exists in product '%2'.") .arg(groupName, product.name()), resolvedGroup->location); } } - ProjectFileGroupInserter groupInserter(currentProduct, groupName); + ProjectFileGroupInserter groupInserter(products.first(), groupName); groupInserter.apply(); m_projectData.d.detach(); // The data we already gave out must stay as it is. @@ -326,19 +337,18 @@ void ProjectPrivate::addGroup(const ProductData &product, const QString &groupNa updateExternalCodeLocations(m_projectData, groupInserter.itemPosition(), groupInserter.lineOffset()); - GroupPtr resolvedGroup = ResolvedGroup::create(); - resolvedGroup->location = groupInserter.itemPosition(); - resolvedGroup->enabled = true; - resolvedGroup->name = groupName; - resolvedGroup->properties = resolvedProduct->moduleProperties; - resolvedGroup->overrideTags = false; - resolvedProduct->groups << resolvedGroup; - foreach (const ProductData &newProduct, m_projectData.allProducts()) { - if (newProduct.name() == product.name()) { - newProduct.d->groups << createGroupDataFromGroup(resolvedGroup); - qSort(newProduct.d->groups); - break; - } + products = findProductsByName(products.first().name()); // These are new objects. + QBS_CHECK(products.count() == resolvedProducts.count()); + for (int i = 0; i < products.count(); ++i) { + const GroupPtr resolvedGroup = ResolvedGroup::create(); + resolvedGroup->location = groupInserter.itemPosition(); + resolvedGroup->enabled = true; + resolvedGroup->name = groupName; + resolvedGroup->properties = resolvedProducts[i]->moduleProperties; + resolvedGroup->overrideTags = false; + resolvedProducts.at(i)->groups << resolvedGroup; + products.at(i).d->groups << createGroupDataFromGroup(resolvedGroup); + qSort(products.at(i).d->groups); } } @@ -348,24 +358,30 @@ ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const Product GroupUpdateContext context; if (!product.isValid()) throw ErrorInfo(Tr::tr("Product is invalid.")); - context.resolvedProduct = internalProduct(product); - if (!context.resolvedProduct) + context.products = findProductsByName(product.name()); + if (context.products.isEmpty()) throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); - - context.currentProduct = findProductData(product.name()); - QBS_CHECK(context.currentProduct.isValid()); + context.resolvedProducts = internalProducts(context.products); const QString groupName = group.isValid() ? group.name() : product.name(); - foreach (const GroupPtr &g, context.resolvedProduct->groups) { - if (g->name == groupName) { - context.resolvedGroup = g; - break; + foreach (const ResolvedProductPtr &p, context.resolvedProducts) { + foreach (const GroupPtr &g, p->groups) { + if (g->name == groupName) { + context.resolvedGroups << g; + break; + } } } - if (!context.resolvedGroup) + if (context.resolvedGroups.isEmpty()) throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName)); - context.currentGroup = findGroupData(context.currentProduct, groupName); - QBS_CHECK(context.currentGroup.isValid()); + foreach (const ProductData &p, context.products) { + const GroupData &g = findGroupData(p, groupName); + QBS_CHECK(p.isValid()); + context.groups << g; + } + QBS_CHECK(context.resolvedProducts.count() == context.products.count()); + QBS_CHECK(context.resolvedProducts.count() == context.resolvedGroups.count()); + QBS_CHECK(context.products.count() == context.groups.count()); return context; } @@ -379,12 +395,18 @@ ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const P if (filePaths.isEmpty()) throw ErrorInfo(Tr::tr("No files supplied.")); - if (!groupContext.resolvedGroup->prefix.isEmpty() && - !groupContext.resolvedGroup->prefix.endsWith(QLatin1Char('/'))) { - throw ErrorInfo(Tr::tr("Group has non-directory prefix.")); + QString prefix; + for (int i = 0; i < groupContext.resolvedGroups.count(); ++i) { + const GroupPtr &g = groupContext.resolvedGroups.at(i); + if (!g->prefix.isEmpty() && !g->prefix.endsWith(QLatin1Char('/'))) + throw ErrorInfo(Tr::tr("Group has non-directory prefix.")); + if (i == 0) + prefix = g->prefix; + else if (prefix != g->prefix) + throw ErrorInfo(Tr::tr("Cannot update: Group prefix depends on properties.")); } QString baseDirPath = QFileInfo(product.location().fileName()).dir().absolutePath() - + QLatin1Char('/') + groupContext.resolvedGroup->prefix; + + QLatin1Char('/') + prefix; QDir baseDir(baseDirPath); foreach (const QString &filePath, filePaths) { const QString absPath = QDir::cleanPath(FileInfo::resolvePath(baseDirPath, filePath)); @@ -407,17 +429,20 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group // We do not check for entries in other groups, because such doublettes might be legitimate // due to conditions. - foreach (const QString &filePath, filesContext.absoluteFilePaths) { - foreach (const SourceArtifactConstPtr &sa, groupContext.resolvedGroup->files) { - if (sa->absoluteFilePath == filePath) { - throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") - .arg(filePath, group.name())); + foreach (const GroupPtr &group, groupContext.resolvedGroups) { + foreach (const QString &filePath, filesContext.absoluteFilePaths) { + foreach (const SourceArtifactConstPtr &sa, group->files) { + if (sa->absoluteFilePath == filePath) { + throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") + .arg(filePath, group->name)); + } } } } - ProjectFileFilesAdder adder(groupContext.currentProduct, - group.isValid() ? groupContext.currentGroup : GroupData(), filesContext.relativeFilePaths); + ProjectFileFilesAdder adder(groupContext.products.first(), + group.isValid() ? groupContext.groups.first() : GroupData(), + filesContext.relativeFilePaths); adder.apply(); m_projectData.d.detach(); @@ -425,25 +450,31 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group updateExternalCodeLocations(m_projectData, adder.itemPosition(), adder.lineOffset()); QList<SourceArtifactPtr> addedSourceArtifacts; - foreach (const QString &file, filesContext.absoluteFilePaths) { - const SourceArtifactPtr artifact = SourceArtifact::create(); - artifact->absoluteFilePath = file; - artifact->properties = groupContext.resolvedGroup->properties; - artifact->fileTags = groupContext.resolvedGroup->fileTags; - artifact->overrideFileTags = groupContext.resolvedGroup->overrideTags; - ProjectResolver::applyFileTaggers(artifact, groupContext.resolvedProduct, logger); - addedSourceArtifacts << artifact; - groupContext.resolvedGroup->files << artifact; - } - if (groupContext.resolvedProduct->enabled) { - foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) { - Artifact * const artifact = createArtifact(groupContext.resolvedProduct, sa, logger); - groupContext.resolvedProduct->registerAddedArtifact(artifact); + for (int i = 0; i < groupContext.resolvedGroups.count(); ++i) { + const ResolvedProductPtr &resolvedProduct = groupContext.resolvedProducts.at(i); + const GroupPtr &resolvedGroup = groupContext.resolvedGroups.at(i); + foreach (const QString &file, filesContext.absoluteFilePaths) { + const SourceArtifactPtr artifact = SourceArtifact::create(); + artifact->absoluteFilePath = file; + artifact->properties = resolvedGroup->properties; + artifact->fileTags = resolvedGroup->fileTags; + artifact->overrideFileTags = resolvedGroup->overrideTags; + ProjectResolver::applyFileTaggers(artifact, resolvedProduct, logger); + addedSourceArtifacts << artifact; + resolvedGroup->files << artifact; + } + if (resolvedProduct->enabled) { + foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) { + Artifact * const artifact = createArtifact(resolvedProduct, sa, logger); + resolvedProduct->registerAddedArtifact(artifact); + } } } doSanityChecks(internalProject, logger); - groupContext.currentGroup.d->filePaths << filesContext.absoluteFilePaths; - qSort(groupContext.currentGroup.d->filePaths); + foreach (const GroupData &g, groupContext.groups) { + g.d->filePaths << filesContext.absoluteFilePaths; + qSort(g.d->filePaths); + } } void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group, @@ -454,7 +485,7 @@ void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &gr QStringList filesNotFound = filesContext.absoluteFilePaths; QList<SourceArtifactPtr> sourceArtifacts; - foreach (const SourceArtifactPtr &sa, groupContext.resolvedGroup->files) { + foreach (const SourceArtifactPtr &sa, groupContext.resolvedGroups.first()->files) { if (filesNotFound.removeOne(sa->absoluteFilePath)) sourceArtifacts << sa; } @@ -463,24 +494,25 @@ void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &gr .arg(filesNotFound.join(QLatin1String(", ")))); } - ProjectFileFilesRemover remover(groupContext.currentProduct, - group.isValid() ? groupContext.currentGroup - : GroupData(), filesContext.relativeFilePaths); + ProjectFileFilesRemover remover(groupContext.products.first(), + group.isValid() ? groupContext.groups.first() : GroupData(), + filesContext.relativeFilePaths); remover.apply(); - removeFilesFromBuildGraph(groupContext.resolvedProduct, sourceArtifacts); - foreach (const SourceArtifactPtr &sa, sourceArtifacts) { - const bool removed = groupContext.resolvedGroup->files.removeOne(sa); - QBS_CHECK(removed); + for (int i = 0; i < groupContext.resolvedProducts.count(); ++i) { + removeFilesFromBuildGraph(groupContext.resolvedProducts.at(i), sourceArtifacts); + foreach (const SourceArtifactPtr &sa, sourceArtifacts) + groupContext.resolvedGroups.at(i)->files.removeOne(sa); } doSanityChecks(internalProject, logger); m_projectData.d.detach(); updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); - foreach (const QString &filePath, filesContext.absoluteFilePaths) { - const bool removed = groupContext.currentGroup.d->filePaths.removeOne(filePath); - QBS_CHECK(removed); + foreach (const GroupData &g, groupContext.groups) { + foreach (const QString &filePath, filesContext.absoluteFilePaths) { + g.d->filePaths.removeOne(filePath); + } } } @@ -488,19 +520,25 @@ void ProjectPrivate::removeGroup(const ProductData &product, const GroupData &gr { GroupUpdateContext context = getGroupContext(product, group); - ProjectFileGroupRemover remover(context.currentProduct, context.currentGroup); + ProjectFileGroupRemover remover(context.products.first(), context.groups.first()); remover.apply(); - removeFilesFromBuildGraph(context.resolvedProduct, context.resolvedGroup->allFiles()); - bool removed = context.resolvedProduct->groups.removeOne(context.resolvedGroup); - QBS_CHECK(removed); + for (int i = 0; i < context.resolvedProducts.count(); ++i) { + const ResolvedProductPtr &product = context.resolvedProducts.at(i); + const GroupPtr &group = context.resolvedGroups.at(i); + removeFilesFromBuildGraph(product, group->allFiles()); + const bool removed = product->groups.removeOne(group); + QBS_CHECK(removed); + } doSanityChecks(internalProject, logger); m_projectData.d.detach(); updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset()); updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset()); - removed = context.currentProduct.d->groups.removeOne(context.currentGroup); - QBS_CHECK(removed); + for (int i = 0; i < context.products.count(); ++i) { + const bool removed = context.products.at(i).d->groups.removeOne(context.groups.at(i)); + QBS_CHECK(removed); + } } void ProjectPrivate::removeFilesFromBuildGraph(const ResolvedProductConstPtr &product, @@ -610,6 +648,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, foreach (const ResolvedProductConstPtr &resolvedProduct, internalProject->products) { ProductData product; product.d->name = resolvedProduct->name; + product.d->profile = resolvedProduct->profile; product.d->location = resolvedProduct->location; product.d->isEnabled = resolvedProduct->enabled; product.d->isRunnable = productIsRunnable(resolvedProduct); diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp index 9e1c1e984..105f144b5 100644 --- a/src/lib/corelib/api/projectdata.cpp +++ b/src/lib/corelib/api/projectdata.cpp @@ -379,6 +379,14 @@ QString ProductData::name() const } /*! + * \brief The profile this product will be built for. + */ +QString ProductData::profile() const +{ + return d->profile; +} + +/*! * \brief The location at which the product is defined in the source file. */ CodeLocation ProductData::location() const @@ -423,6 +431,7 @@ bool ProductData::isRunnable() const bool operator==(const ProductData &lhs, const ProductData &rhs) { return lhs.name() == rhs.name() + && lhs.profile() == rhs.profile() && lhs.location() == rhs.location() && lhs.groups() == rhs.groups() && lhs.targetArtifacts() == rhs.targetArtifacts() @@ -436,7 +445,12 @@ bool operator!=(const ProductData &lhs, const ProductData &rhs) bool operator<(const ProductData &lhs, const ProductData &rhs) { - return lhs.name() < rhs.name(); + const int nameCmp = lhs.name().compare(rhs.name()); + if (nameCmp < 0) + return true; + if (nameCmp > 0) + return false; + return lhs.profile() < rhs.profile(); } /*! diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h index 1d7625b82..c1195e7f5 100644 --- a/src/lib/corelib/api/projectdata.h +++ b/src/lib/corelib/api/projectdata.h @@ -172,6 +172,7 @@ public: bool isValid() const; QString name() const; + QString profile() const; CodeLocation location() const; QList<TargetArtifact> targetArtifacts() const; QList<GroupData> groups() const; diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h index b0006e89a..64f7a3084 100644 --- a/src/lib/corelib/api/projectdata_p.h +++ b/src/lib/corelib/api/projectdata_p.h @@ -80,6 +80,7 @@ public: { } QString name; + QString profile; CodeLocation location; QList<GroupData> groups; QList<TargetArtifact> targetArtifacts; diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp index 11e98b864..0a231a67a 100644 --- a/src/lib/corelib/buildgraph/buildgraph.cpp +++ b/src/lib/corelib/buildgraph/buildgraph.cpp @@ -367,7 +367,7 @@ Artifact *lookupArtifact(const ResolvedProductConstPtr &product, it != lookupResults.constEnd(); ++it) { Artifact *artifact = dynamic_cast<Artifact *>(*it); if (artifact && (compareByName - ? artifact->product->name == product->name + ? artifact->product->uniqueName() == product->uniqueName() : artifact->product == product)) return artifact; } @@ -425,10 +425,10 @@ void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const if (lookupArtifact(otherProduct, artifact->filePath())) { if (artifact->artifactType == Artifact::Generated) { QString pl; - pl.append(QString::fromLatin1(" - %1 \n").arg(product->name)); + pl.append(QString::fromLatin1(" - %1 \n").arg(product->uniqueName())); foreach (const ResolvedProductConstPtr &p, product->project->products) { if (lookupArtifact(p, artifact->filePath())) - pl.append(QString::fromLatin1(" - %1 \n").arg(p->name)); + pl.append(QString::fromLatin1(" - %1 \n").arg(p->uniqueName())); } throw ErrorInfo(QString::fromLatin1("BUG: already inserted in this project: %1\n%2") .arg(artifact->filePath()).arg(pl), CodeLocation(), true); @@ -449,7 +449,7 @@ void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact, const static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, const Logger &logger) { - logger.qbsDebug() << "Sanity checking product '" << product->name << "'"; + logger.qbsDebug() << "Sanity checking product '" << product->uniqueName() << "'"; CycleDetector cycleDetector(logger); cycleDetector.visitProduct(product); const ProductBuildData * const buildData = product->buildData.data(); @@ -532,8 +532,8 @@ static void doSanityChecks(const ResolvedProjectPtr &project, QSet<QString> &pro QBS_CHECK(product->project == project); QBS_CHECK(product->topLevelProject() == project->topLevelProject()); doSanityChecksForProduct(product, logger); - QBS_CHECK(!productNames.contains(product->name)); - productNames << product->name; + QBS_CHECK(!productNames.contains(product->uniqueName())); + productNames << product->uniqueName(); } } diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp index c3370711b..25c5319ba 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.cpp +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -206,7 +206,7 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met QList<ResolvedProductPtr> allNewlyResolvedProducts = m_result.newlyResolvedProject->allProducts(); foreach (const ResolvedProductPtr &cp, allNewlyResolvedProducts) - freshProductsByName.insert(cp->name, cp); + freshProductsByName.insert(cp->uniqueName(), cp); checkAllProductsForChanges(allRestoredProducts, freshProductsByName, changedProducts, productsWithChangedFiles); @@ -235,12 +235,14 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met // if needed. QHash<QString, AllRescuableArtifactData> rescuableArtifactData; foreach (const ResolvedProductPtr &product, changedProducts) { - ResolvedProductPtr freshProduct = freshProductsByName.value(product->name); + ResolvedProductPtr freshProduct = freshProductsByName.value(product->uniqueName()); if (!freshProduct) continue; onProductRemoved(product, product->topLevelProject()->buildData.data(), false); - if (product->buildData) - rescuableArtifactData.insert(product->name, product->buildData->rescuableArtifactData); + if (product->buildData) { + rescuableArtifactData.insert(product->uniqueName(), + product->buildData->rescuableArtifactData); + } allRestoredProducts.removeOne(product); productsWithChangedFiles.removeOne(product); } @@ -253,7 +255,7 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met const ResolvedProductPtr &newlyResolvedProduct = allNewlyResolvedProducts.at(i); for (int j = allRestoredProducts.count() - 1; j >= 0; --j) { const ResolvedProductPtr &restoredProduct = allRestoredProducts.at(j); - if (newlyResolvedProduct->name == restoredProduct->name) { + if (newlyResolvedProduct->uniqueName() == restoredProduct->uniqueName()) { if (newlyResolvedProduct->enabled) { newlyResolvedProduct->buildData.swap(restoredProduct->buildData); } else { @@ -303,16 +305,16 @@ void BuildGraphLoader::trackProjectChanges(const SetupProjectParameters ¶met // For products where only the list of files has changed, we adapt the existing build data // so we won't recompile existing files just because new ones have been added. foreach (const ResolvedProductPtr &product, productsWithChangedFiles) { - ResolvedProductPtr freshProduct = freshProductsByName.value(product->name); + ResolvedProductPtr freshProduct = freshProductsByName.value(product->uniqueName()); if (!freshProduct) continue; onProductFileListChanged(product, freshProduct, oldBuildData.data()); } foreach (const ResolvedProductConstPtr &changedProduct, changedProducts) { - rescueOldBuildData(changedProduct, freshProductsByName.value(changedProduct->name), + rescueOldBuildData(changedProduct, freshProductsByName.value(changedProduct->uniqueName()), oldBuildData.data(), childLists, - rescuableArtifactData.value(changedProduct->name)); + rescuableArtifactData.value(changedProduct->uniqueName())); } EmptyDirectoriesRemover(m_result.newlyResolvedProject.data(), m_logger) @@ -424,18 +426,18 @@ void BuildGraphLoader::checkAllProductsForChanges(const QList<ResolvedProductPtr if (changedProducts.contains(restoredProduct)) continue; const ResolvedProductPtr newlyResolvedProduct - = newlyResolvedProductsByName.value(restoredProduct->name); + = newlyResolvedProductsByName.value(restoredProduct->uniqueName()); if (!newlyResolvedProduct) continue; if (!productsWithChangedFiles.contains(restoredProduct) && !sourceArtifactSetsAreEqual(restoredProduct->allFiles(), newlyResolvedProduct->allFiles())) { - m_logger.qbsDebug() << "File list of product '" << restoredProduct->name + m_logger.qbsDebug() << "File list of product '" << restoredProduct->uniqueName() << "' was changed."; productsWithChangedFiles += restoredProduct; } if (checkProductForChanges(restoredProduct, newlyResolvedProduct)) { - m_logger.qbsDebug() << "Product '" << restoredProduct->name + m_logger.qbsDebug() << "Product '" << restoredProduct->uniqueName() << "' was changed, must set up build data from scratch"; changedProducts << restoredProduct; } @@ -450,9 +452,9 @@ static bool dependenciesAreEqual(const ResolvedProductConstPtr &p1, QSet<QString> names1; QSet<QString> names2; foreach (const ResolvedProductConstPtr &dep, p1->dependencies) - names1 << dep->name; + names1 << dep->uniqueName(); foreach (const ResolvedProductConstPtr &dep, p2->dependencies) - names2 << dep->name; + names2 << dep->uniqueName(); return names1 == names2; } @@ -494,7 +496,7 @@ bool BuildGraphLoader::checkForPropertyChanges(const ResolvedProductPtr &restore const ResolvedProductPtr &newlyResolvedProduct) { m_logger.qbsDebug() << "Checking for changes in properties requested in prepare scripts for " - "product '" << restoredProduct->name << "'."; + "product '" << restoredProduct->uniqueName() << "'."; if (!restoredProduct->buildData) return false; @@ -526,7 +528,7 @@ bool BuildGraphLoader::checkTransformersForPropertyChanges(const ResolvedProduct } if (transformerChanges) { m_logger.qbsDebug() << "Property changes in product '" - << newlyResolvedProduct->name << "'."; + << newlyResolvedProduct->uniqueName() << "'."; } return transformerChanges; } @@ -534,7 +536,7 @@ bool BuildGraphLoader::checkTransformersForPropertyChanges(const ResolvedProduct void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, ProjectBuildData *projectBuildData, bool removeArtifactsFromDisk) { - m_logger.qbsDebug() << "[BG] product '" << product->name << "' removed."; + m_logger.qbsDebug() << "[BG] product '" << product->uniqueName() << "' removed."; product->project->products.removeOne(product); if (product->buildData) { @@ -549,7 +551,7 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restoredProduct, const ResolvedProductPtr &newlyResolvedProduct, const ProjectBuildData *oldBuildData) { - m_logger.qbsDebug() << "[BG] product '" << restoredProduct->name << "' changed."; + m_logger.qbsDebug() << "[BG] product '" << restoredProduct->uniqueName() << "' changed."; QBS_CHECK(newlyResolvedProduct->enabled); @@ -565,7 +567,7 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor if (!oldArtifacts.contains(a->absoluteFilePath)) { // artifact added m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath - << "' added to product " << restoredProduct->name; + << "' added to product " << restoredProduct->uniqueName(); Artifact *newArtifact = lookupArtifact(newlyResolvedProduct, oldBuildData, a->absoluteFilePath, true); if (newArtifact) { @@ -601,7 +603,7 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor if (!changedArtifact) { // artifact removed m_logger.qbsDebug() << "[BG] artifact '" << a->absoluteFilePath - << "' removed from product " << restoredProduct->name; + << "' removed from product " << restoredProduct->uniqueName(); Artifact *artifact = lookupArtifact(restoredProduct, oldBuildData, a->absoluteFilePath, true); QBS_CHECK(artifact); @@ -785,7 +787,7 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore if (m_logger.traceEnabled()) { m_logger.qbsTrace() << QString::fromLocal8Bit("[BG] rescue data of " - "product '%1'").arg(restoredProduct->name); + "product '%1'").arg(restoredProduct->uniqueName()); } QBS_CHECK(newlyResolvedProduct->buildData); QBS_CHECK(newlyResolvedProduct->buildData->rescuableArtifactData.isEmpty()); @@ -852,7 +854,8 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore const ChildrenInfo &childrenInfo = childLists.value(oldArtifact); foreach (Artifact * const child, childrenInfo.children) { rad.children << RescuableArtifactData::ChildData(child->product->name, - child->filePath(), childrenInfo.childrenAddedByScanner.contains(child)); + child->product->profile, child->filePath(), + childrenInfo.childrenAddedByScanner.contains(child)); } newlyResolvedProduct->buildData->rescuableArtifactData.insert( oldArtifact->filePath(), rad); diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp index 0846fd39e..a8072c97c 100644 --- a/src/lib/corelib/buildgraph/executor.cpp +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -680,6 +680,7 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0) ResolvedProductPtr pseudoProduct = ResolvedProduct::create(); foreach (const RescuableArtifactData::ChildData &cd, rad.children) { pseudoProduct->name = cd.productName; + pseudoProduct->profile = cd.productProfile; Artifact * const child = lookupArtifact(pseudoProduct, m_project->buildData.data(), cd.childFilePath, true); if (!child || artifact->children.contains(child)) @@ -842,15 +843,17 @@ void Executor::finish() { QBS_ASSERT(m_state != ExecutorIdle, /* ignore */); - QStringList unbuiltProductNames; + QList<ResolvedProductPtr> unbuiltProducts; foreach (const ResolvedProductPtr &product, m_productsToBuild) { + bool productBuilt = true; foreach (BuildGraphNode *rootNode, product->buildData->roots) { if (rootNode->buildState != BuildGraphNode::Built) { - unbuiltProductNames += product->name; + productBuilt = false; + unbuiltProducts += product; break; } } - if (!unbuiltProductNames.contains(product->name)) { + if (productBuilt) { // Any element still left after a successful build has not been re-created // by any rule and therefore does not exist anymore as an artifact. foreach (const QString &filePath, product->buildData->rescuableArtifactData.keys()) { @@ -865,11 +868,16 @@ void Executor::finish() } } - if (unbuiltProductNames.isEmpty()) { + if (unbuiltProducts.isEmpty()) { m_logger.qbsInfo() << Tr::tr("Build done%1.").arg(configString()); } else { - m_error.append(Tr::tr("The following products could not be built%1: %2.") - .arg(configString(), unbuiltProductNames.join(QLatin1String(", ")))); + m_error.append(Tr::tr("The following products could not be built%1:").arg(configString())); + foreach (const ResolvedProductConstPtr &p, unbuiltProducts) { + QString errorMessage = Tr::tr("\t%1").arg(p->name); + if (p->profile != m_project->profile()) + errorMessage += Tr::tr(" (for profile '%1')").arg(p->profile); + m_error.append(errorMessage); + } } if (m_explicitlyCanceled) diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp index 0f6161235..b75184597 100644 --- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp +++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp @@ -364,7 +364,7 @@ void InputArtifactScanner::handleDependency(ResolvedDependency &dependency) ResolvedProduct * const otherProduct = artifactDependency->product; if (m_logger.traceEnabled()) { m_logger.qbsTrace() << "[DEPSCAN] add artifact dependency " << dependency.filePath - << " (from product " << otherProduct->name << ')'; + << " (from product " << otherProduct->uniqueName() << ')'; } insertIntoProduct = false; } diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp index b244e571e..f6b564d9a 100644 --- a/src/lib/corelib/buildgraph/projectbuilddata.cpp +++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp @@ -397,7 +397,7 @@ private: m_product->buildData->nodes += node; if (m_logger.debugEnabled()) { m_logger.qbsDebug() << "[BG] create " << node->toString() - << " for product " << m_product->name; + << " for product " << m_product->uniqueName(); } } if (parentRule) { diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.cpp b/src/lib/corelib/buildgraph/rescuableartifactdata.cpp index b3620a317..025cba876 100644 --- a/src/lib/corelib/buildgraph/rescuableartifactdata.cpp +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.cpp @@ -49,6 +49,7 @@ void RescuableArtifactData::load(PersistentPool &pool) for (int i = 0; i < c; ++i) { ChildData cd; cd.productName = pool.idLoadString(); + cd.productProfile = pool.idLoadString(); cd.childFilePath = pool.idLoadString(); pool.stream() >> cd.addedByScanner; children << cd; @@ -64,6 +65,7 @@ void RescuableArtifactData::store(PersistentPool &pool) const pool.stream() << children.count(); foreach (const ChildData &cd, children) { pool.storeString(cd.productName); + pool.storeString(cd.productProfile); pool.storeString(cd.childFilePath); pool.stream() << cd.addedByScanner; } diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.h b/src/lib/corelib/buildgraph/rescuableartifactdata.h index 2229c497b..c4a169c6d 100644 --- a/src/lib/corelib/buildgraph/rescuableartifactdata.h +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.h @@ -51,11 +51,12 @@ public: struct ChildData { - ChildData(const QString &p = QString(), const QString &c = QString(), - bool byScanner = false) - : productName(p), childFilePath(c), addedByScanner(byScanner) + ChildData(const QString &n = QString(), const QString &p = QString(), + const QString &c = QString(), bool byScanner = false) + : productName(n), productProfile(p), childFilePath(c), addedByScanner(byScanner) {} QString productName; + QString productProfile; QString childFilePath; bool addedByScanner; }; diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index ca028cc4f..2f97d7158 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -146,6 +146,9 @@ void BuiltinDeclarations::addDependsItem() PropertyDeclaration requiredDecl(QLatin1String("required"), PropertyDeclaration::Boolean); requiredDecl.setInitialValueSource(QLatin1String("true")); item << requiredDecl; + PropertyDeclaration profileDecl(QLatin1String("profiles"), PropertyDeclaration::StringList); + profileDecl.setInitialValueSource(QLatin1String("[product.profile]")); + item << profileDecl; insert(item); } @@ -245,6 +248,10 @@ void BuiltinDeclarations::addProductItem() decl.setInitialValueSource(QLatin1String("[]")); item << decl; item << nameProperty(); + decl = PropertyDeclaration(QLatin1String("profiles"), PropertyDeclaration::StringList); + decl.setInitialValueSource(QLatin1String("[project.profile]")); + item << decl; + item << PropertyDeclaration(QLatin1String("profile"), PropertyDeclaration::String); // Internal decl = PropertyDeclaration(QLatin1String("targetName"), PropertyDeclaration::String); decl.setInitialValueSource(QLatin1String("name")); item << buildDirProperty(); diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index 324739aaa..300b41d2d 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -462,6 +462,7 @@ void ResolvedProduct::load(PersistentPool &pool) >> enabled >> fileTags >> name + >> profile >> targetName >> sourceDirectory >> destinationDirectory @@ -485,6 +486,7 @@ void ResolvedProduct::store(PersistentPool &pool) const << enabled << fileTags << name + << profile << targetName << sourceDirectory << destinationDirectory @@ -792,7 +794,18 @@ ArtifactSet ResolvedProduct::targetArtifacts() const TopLevelProject *ResolvedProduct::topLevelProject() const { - return project->topLevelProject(); + return project->topLevelProject(); +} + +QString ResolvedProduct::uniqueName(const QString &name, const QString &profile) +{ + QBS_CHECK(!profile.isEmpty()); + return name + QLatin1Char('.') + profile; +} + +QString ResolvedProduct::uniqueName() const +{ + return uniqueName(name, profile); } static QStringList findGeneratedFiles(const Artifact *base, const FileTags &tags) diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index f62e63333..c1605394a 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -355,6 +355,7 @@ public: FileTags fileTags; QString name; QString targetName; + QString profile; QString sourceDirectory; QString destinationDirectory; CodeLocation location; @@ -398,6 +399,9 @@ public: TopLevelProject *topLevelProject() const; + static QString uniqueName(const QString &name, const QString &profile); + QString uniqueName() const; + QStringList generatedFiles(const QString &baseFile, const FileTags &tags) const; QString buildDirectory() const; diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index 23b0d5980..3c70fa532 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -44,13 +44,17 @@ #include <tools/error.h> #include <tools/fileinfo.h> #include <tools/hostosinfo.h> +#include <tools/profile.h> #include <tools/progressobserver.h> #include <tools/qbsassert.h> #include <tools/qttools.h> +#include <tools/scripttools.h> +#include <tools/settings.h> #include <QDebug> #include <QDir> #include <QDirIterator> +#include <QPair> namespace qbs { namespace Internal { @@ -216,6 +220,7 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, ProductContext dummyProductContext; dummyProductContext.project = &projectContext; + dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree(); loadBaseModule(&dummyProductContext, item); overrideItemProperties(item, QLatin1String("project"), m_parameters.overriddenValuesTree()); @@ -230,6 +235,13 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, foreach (Item *child, item->children()) { child->setScope(projectContext.scope); if (child->typeName() == QLatin1String("Product")) { + foreach (Item * const additionalProductItem, multiplexProductItem(child)) + Item::addChild(item, additionalProductItem); + } + } + + foreach (Item *child, item->children()) { + if (child->typeName() == QLatin1String("Product")) { handleProduct(&projectContext, child); } else if (child->typeName() == QLatin1String("SubProject")) { handleSubProject(&projectContext, child, referencedFilePaths); @@ -241,6 +253,8 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, const QString projectFileDirPath = FileInfo::path(item->file()->filePath()); const QStringList refs = m_evaluator->stringListValue(item, QLatin1String("references")); + typedef QPair<Item *, QString> ItemAndRefPath; + QList<ItemAndRefPath> additionalProjectChildren; foreach (const QString &filePath, refs) { QString absReferencePath = FileInfo::resolvePath(projectFileDirPath, filePath); if (FileInfo(absReferencePath).isDir()) { @@ -267,15 +281,21 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, Item *subItem = m_reader->readFile(absReferencePath); subItem->setScope(projectContext.scope); subItem->setParent(projectContext.item); - QList<Item *> projectChildren = projectContext.item->children(); - projectChildren += subItem; - projectContext.item->setChildren(projectChildren); + additionalProjectChildren << qMakePair(subItem, absReferencePath); + if (subItem->typeName() == QLatin1String("Product")) { + foreach (Item * const additionalProductItem, multiplexProductItem(subItem)) + additionalProjectChildren << qMakePair(additionalProductItem, absReferencePath); + } + } + foreach (const ItemAndRefPath &irp, additionalProjectChildren) { + Item * const subItem = irp.first; + Item::addChild(projectContext.item, subItem); if (subItem->typeName() == QLatin1String("Product")) { handleProduct(&projectContext, subItem); } else if (subItem->typeName() == QLatin1String("Project")) { copyProperties(item, subItem); handleProject(loadResult, subItem, buildDirectory, - QSet<QString>(referencedFilePaths) << absReferencePath); + QSet<QString>(referencedFilePaths) << irp.second); } else { throw ErrorInfo(Tr::tr("The top-level item of a file in a \"references\" list must be " "a Product or a Project, but it is \"%1\".").arg(subItem->typeName()), @@ -291,6 +311,43 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, m_reader->popExtraSearchPaths(); } +QList<Item *> ModuleLoader::multiplexProductItem(Item *productItem) +{ + // Overriding the product item properties must be done here already, because otherwise + // the "profiles" property would not be overridable. + QString productName = m_evaluator->stringValue(productItem, QLatin1String("name")); + if (productName.isEmpty()) { + productName = FileInfo::completeBaseName(productItem->file()->filePath()); + productItem->setProperty(QLatin1String("name"), VariantValue::create(productName)); + } + overrideItemProperties(productItem, productName, m_parameters.overriddenValuesTree()); + + const QString profilesKey = QLatin1String("profiles"); + const ValueConstPtr profilesValue = productItem->property(profilesKey); + QBS_CHECK(profilesValue); // Default value set in BuiltinDeclarations. + const QStringList profileNames = m_evaluator->stringListValue(productItem, profilesKey); + if (profileNames.isEmpty()) { + throw ErrorInfo(Tr::tr("The 'profiles' property cannot be an empty list."), + profilesValue->location()); + } + foreach (const QString &profileName, profileNames) { + if (profileNames.count(profileName) > 1) { + throw ErrorInfo(Tr::tr("The profile '%1' appears in the 'profiles' list twice, " + "which is not allowed.").arg(profileName), profilesValue->location()); + } + } + + QList<Item *> additionalProductItems; + const QString profileKey = QLatin1String("profile"); + productItem->setProperty(profileKey, VariantValue::create(profileNames.first())); + for (int i = 1; i < profileNames.count(); ++i) { + Item * const cloned = productItem->clone(productItem->pool()); + cloned->setProperty(profileKey, VariantValue::create(profileNames.at(i))); + additionalProductItems << cloned; + } + return additionalProductItems; +} + void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) { checkCancelation(); @@ -299,6 +356,15 @@ void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) initProductProperties(projectContext, item); ProductContext productContext; + bool profilePropertySet; + productContext.profileName = m_evaluator->stringValue(item, QLatin1String("profile"), + QString(), &profilePropertySet); + QBS_CHECK(profilePropertySet); + const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration( + m_parameters.settingsDirectory(), productContext.profileName, + m_parameters.buildVariant()); + productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( + buildConfig, m_parameters.overriddenValues()); productContext.project = projectContext; bool extraSearchPathsSet = false; const QStringList extraSearchPaths = readExtraSearchPaths(item, &extraSearchPathsSet); @@ -308,6 +374,7 @@ void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) } else { productContext.extraSearchPaths = projectContext->extraSearchPaths; } + productContext.item = item; ItemValuePtr itemValue = ItemValue::create(item); productContext.scope = Item::create(m_pool); @@ -342,15 +409,13 @@ void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) void ModuleLoader::initProductProperties(const ProjectContext *project, Item *item) { - QString productName = m_evaluator->stringValue(item, QLatin1String("name")); - if (productName.isEmpty()) { - productName = FileInfo::completeBaseName(item->file()->filePath()); - item->setProperty(QLatin1String("name"), VariantValue::create(productName)); - } - + const QString productName = m_evaluator->stringValue(item, QLatin1String("name")); + const QString profile = m_evaluator->stringValue(item, QLatin1String("profile")); + QBS_CHECK(!profile.isEmpty()); + const QString uniqueName = ResolvedProduct::uniqueName(productName, profile); item->setProperty(QLatin1String("buildDirectory"), VariantValue::create( - FileInfo::resolvePath(project->buildDirectory, productName))); + FileInfo::resolvePath(project->buildDirectory, uniqueName))); item->setProperty(QLatin1String("sourceDirectory"), VariantValue::create( @@ -613,12 +678,20 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *item result.item = moduleItem; moduleResults->append(result); } else { - ModuleLoaderResult::ProductInfo::Dependency dependency; - dependency.name = moduleName; - dependency.required = m_evaluator->property(item, QLatin1String("required")).toBool(); - dependency.failureMessage - = m_evaluator->property(item, QLatin1String("failureMessage")).toString(); - productResults->append(ProductDependencyResult(dependsItem, dependency)); + const QString profilesKey = QLatin1String("profiles"); + const QStringList profiles = m_evaluator->stringListValue(dependsItem, profilesKey); + if (profiles.isEmpty()) { + throw ErrorInfo(Tr::tr("Empty 'profiles' list not allowed in 'Depends' item."), + dependsItem->property(profilesKey)->location()); + } + const bool required = m_evaluator->property(item, QLatin1String("required")).toBool(); + foreach (const QString &profile, profiles) { + ModuleLoaderResult::ProductInfo::Dependency dependency; + dependency.name = moduleName; + dependency.profile = profile; + dependency.required = required; + productResults->append(ProductDependencyResult(dependsItem, dependency)); + } } } } @@ -787,14 +860,15 @@ Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] trying to load " << fullModuleName << " from " << filePath; - Item *module = productContext->moduleItemCache.value(filePath); + const ContextBase::ModuleItemCache::key_type cacheKey(filePath, productContext->profileName); + Item *module = productContext->moduleItemCache.value(cacheKey); if (module) { m_logger.qbsTrace() << "[LDR] loadModuleFile cache hit for " << filePath; *cacheHit = true; return module; } - module = productContext->project->moduleItemCache.value(filePath); + module = productContext->project->moduleItemCache.value(cacheKey); if (module) { m_logger.qbsTrace() << "[LDR] loadModuleFile returns clone for " << filePath; *cacheHit = true; @@ -817,20 +891,21 @@ Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString // Module properties that are defined in the profile are used as default values. const QVariantMap profileModuleProperties - = m_parameters.buildConfigurationTree().value(fullModuleName).toMap(); + = productContext->moduleProperties.value(fullModuleName).toMap(); for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); vmit != profileModuleProperties.end(); ++vmit) { if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) throw ErrorInfo(Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); const PropertyDeclaration decl = module->propertyDeclaration(vmit.key()); - module->setProperty(vmit.key(), - VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), - QStringList(fullModuleName), vmit.key()))); + VariantValuePtr v = VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), + QStringList(fullModuleName), vmit.key())); + module->setProperty(vmit.key(), v); } - productContext->moduleItemCache.insert(filePath, module); - productContext->project->moduleItemCache.insert(filePath, module); + productContext->moduleItemCache.insert(cacheKey, module); + productContext->project->moduleItemCache.insert(cacheKey, module); + return module; } @@ -1175,5 +1250,10 @@ void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfig } } +QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const +{ + return ResolvedProduct::uniqueName(name, profile); +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h index 5df1747fc..42e82046f 100644 --- a/src/lib/corelib/language/moduleloader.h +++ b/src/lib/corelib/language/moduleloader.h @@ -69,8 +69,10 @@ struct ModuleLoaderResult struct Dependency { QString name; + QString profile; bool required; - QString failureMessage; + + QString uniqueName() const; }; QList<Dependency> usedProducts; @@ -102,8 +104,6 @@ public: ModuleLoaderResult load(const SetupProjectParameters ¶meters); static QString fullModuleName(const QStringList &moduleName); - static void overrideItemProperties(Item *item, const QString &buildConfigKey, - const QVariantMap &buildConfig); private: class ContextBase @@ -116,7 +116,8 @@ private: Item *item; Item *scope; QStringList extraSearchPaths; - QMap<QString, Item *> moduleItemCache; + typedef QMap<QPair<QString, QString>, Item *> ModuleItemCache; + ModuleItemCache moduleItemCache; }; class ProjectContext : public ContextBase @@ -132,8 +133,10 @@ private: public: ProjectContext *project; ModuleLoaderResult::ProductInfo info; + QString profileName; QSet<FileContextConstPtr> filesWithExportItem; QList<Item *> exportItems; + QVariantMap moduleProperties; }; class DependsContext @@ -148,6 +151,7 @@ private: void handleProject(ModuleLoaderResult *loadResult, Item *item, const QString &buildDirectory, const QSet<QString> &referencedFilePaths); + QList<Item *> multiplexProductItem(Item *productItem); void handleProduct(ProjectContext *projectContext, Item *item); void initProductProperties(const ProjectContext *project, Item *item); void handleSubProject(ProjectContext *projectContext, Item *item, @@ -188,6 +192,10 @@ private: const QStringList &moduleName); static void copyProperty(const QString &propertyName, const Item *source, Item *destination); static void setScopeForDescendants(Item *item, Item *scope); + static void overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig); + + ScriptEngine *m_engine; ItemPool *m_pool; diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp index bf01fd0d8..ebe60e032 100644 --- a/src/lib/corelib/language/projectresolver.cpp +++ b/src/lib/corelib/language/projectresolver.cpp @@ -87,12 +87,12 @@ static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project const QList<ResolvedProductPtr> allProducts = project->allProducts(); for (int i = 0; i < allProducts.count(); ++i) { const ResolvedProductConstPtr product1 = allProducts.at(i); - const QString productName = product1->name; + const QString productName = product1->uniqueName(); for (int j = i + 1; j < allProducts.count(); ++j) { const ResolvedProductConstPtr product2 = allProducts.at(j); - if (product2->name == productName) { + if (product2->uniqueName() == productName) { ErrorInfo error; - error.append(Tr::tr("Duplicate product name '%1'.").arg(productName)); + error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); error.append(Tr::tr("First product defined here."), product1->location); error.append(Tr::tr("Second product defined here."), product2->location); throw error; @@ -286,10 +286,12 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) projectContext->project->products += product; productContext.product = product; product->name = m_evaluator->stringValue(item, QLatin1String("name")); - m_logger.qbsTrace() << "[PR] resolveProduct " << product->name; // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. productContext.buildDirectory = m_evaluator->stringValue(item, QLatin1String("buildDirectory")); + product->profile = m_evaluator->stringValue(item, QLatin1String("profile")); + QBS_CHECK(!product->profile.isEmpty()); + m_logger.qbsTrace() << "[PR] resolveProduct " << product->uniqueName(); if (std::find_if(item->modules().begin(), item->modules().end(), ModuleNameEquals(product->name)) != item->modules().end()) { @@ -298,8 +300,7 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) item->location()); } - ModuleLoader::overrideItemProperties(item, product->name, m_setupParams.overriddenValuesTree()); - m_productsByName.insert(product->name, product); + m_productsByName.insert(product->uniqueName(), product); product->enabled = m_evaluator->boolValue(item, QLatin1String("condition")); // TODO: Remove in 1.3. @@ -802,7 +803,7 @@ void ProjectResolver::resolveExport(Item *item, ProjectContext *projectContext) { Q_UNUSED(projectContext); checkCancelation(); - const QString &productName = m_productContext->product->name; + const QString &productName = m_productContext->product->uniqueName(); m_exports[productName] = evaluateModuleValues(item); } @@ -826,10 +827,10 @@ static void addUsedProducts(ModuleLoaderResult::ProductInfo *productInfo, QSet<QString> usedProductNames; foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, productInfo->usedProducts) - usedProductNames += usedProduct.name; + usedProductNames += usedProduct.uniqueName(); foreach (const ModuleLoaderResult::ProductInfo::Dependency &usedProduct, usedProductInfo.usedProductsFromExportItem) { - if (!usedProductNames.contains(usedProduct.name)) + if (!usedProductNames.contains(usedProduct.uniqueName())) productInfo->usedProducts += usedProduct; } *productsAdded = (oldCount != productInfo->usedProducts.count()); @@ -850,11 +851,11 @@ void ProjectResolver::resolveProductDependencies(ProjectContext *projectContext) = projectContext->loadResult->productInfos[productItem]; foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, productInfo.usedProducts) { - ResolvedProductPtr usedProduct - = m_productsByName.value(dependency.name); - if (Q_UNLIKELY(!usedProduct)) - throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(dependency.name), - productItem->location()); + ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName()); + if (Q_UNLIKELY(!usedProduct)) { + throw ErrorInfo(Tr::tr("Product dependency '%1' not found for profile '%2'.") + .arg(dependency.name, dependency.profile), productItem->location()); + } Item *usedProductItem = m_productItemMap.value(usedProduct); const ModuleLoaderResult::ProductInfo usedProductInfo = projectContext->loadResult->productInfos.value(usedProductItem); @@ -873,7 +874,7 @@ void ProjectResolver::resolveProductDependencies(ProjectContext *projectContext) Item *productItem = m_productItemMap.value(rproduct); foreach (const ModuleLoaderResult::ProductInfo::Dependency &dependency, projectContext->loadResult->productInfos.value(productItem).usedProducts) { - const QString &usedProductName = dependency.name; + const QString &usedProductName = dependency.uniqueName(); ResolvedProductPtr usedProduct = m_productsByName.value(usedProductName); if (Q_UNLIKELY(!usedProduct)) throw ErrorInfo(Tr::tr("Product dependency '%1' not found.").arg(usedProductName), diff --git a/src/lib/corelib/language/tst_language.cpp b/src/lib/corelib/language/tst_language.cpp index d8c47c90b..333968450 100644 --- a/src/lib/corelib/language/tst_language.cpp +++ b/src/lib/corelib/language/tst_language.cpp @@ -400,7 +400,7 @@ void TestLanguage::exports() ResolvedProductPtr product; product = products.value("myapp"); QVERIFY(product); - QStringList propertyName = QStringList() << "modules" << "mylib" + QStringList propertyName = QStringList() << "modules" << "mylib.qbs_autotests" << "modules" << "dummy" << "defines"; QVariant propertyValue = getConfigProperty(product->moduleProperties->value(), propertyName); QCOMPARE(propertyValue.toStringList(), QStringList() << "USE_MYLIB"); @@ -428,11 +428,11 @@ void TestLanguage::exports() product = products.value("myapp2"); QVERIFY(product); - propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + propertyName = QStringList() << "modules" << "productWithInheritedExportItem.qbs_autotests" << "modules" << "dummy" << "cxxFlags"; propertyValue = getConfigProperty(product->moduleProperties->value(), propertyName); QCOMPARE(propertyValue.toStringList(), QStringList() << "-bar"); - propertyName = QStringList() << "modules" << "productWithInheritedExportItem" + propertyName = QStringList() << "modules" << "productWithInheritedExportItem.qbs_autotests" << "modules" << "dummy" << "defines"; propertyValue = getConfigProperty(product->moduleProperties->value(), propertyName); QCOMPARE(propertyValue.toStringList(), QStringList() << "ABC"); @@ -1114,7 +1114,7 @@ void TestLanguage::productDirectories() QVERIFY(product); const QVariantMap config = product->productProperties; QCOMPARE(config.value(QLatin1String("buildDirectory")).toString(), - buildDir(defaultParameters) + QLatin1Char('/') + product->name); + buildDir(defaultParameters) + QLatin1Char('/') + product->uniqueName()); QCOMPARE(config.value(QLatin1String("sourceDirectory")).toString(), testDataDir()); } catch (const ErrorInfo &e) { diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp index 3426bd41c..f5068d77a 100644 --- a/src/lib/corelib/tools/persistence.cpp +++ b/src/lib/corelib/tools/persistence.cpp @@ -40,7 +40,7 @@ namespace qbs { namespace Internal { -static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-69"; +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-70"; PersistentPool::PersistentPool(const Logger &logger) : m_logger(logger) { diff --git a/src/lib/corelib/tools/profile.h b/src/lib/corelib/tools/profile.h index 468a72f2b..1c4911230 100644 --- a/src/lib/corelib/tools/profile.h +++ b/src/lib/corelib/tools/profile.h @@ -79,7 +79,8 @@ private: }; namespace Internal { -class TemporaryProfile { +// Exported for autotests. +class QBS_EXPORT TemporaryProfile { public: TemporaryProfile(const QString &name, Settings *settings) : p(name, settings) {} ~TemporaryProfile() { p.removeProfile(); } diff --git a/tests/auto/api/testdata/multi-arch/host+target.input b/tests/auto/api/testdata/multi-arch/host+target.input new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/multi-arch/host+target.input diff --git a/tests/auto/api/testdata/multi-arch/host-tool.input b/tests/auto/api/testdata/multi-arch/host-tool.input new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/multi-arch/host-tool.input diff --git a/tests/auto/api/testdata/multi-arch/project.qbs b/tests/auto/api/testdata/multi-arch/project.qbs new file mode 100644 index 000000000..1d8221b97 --- /dev/null +++ b/tests/auto/api/testdata/multi-arch/project.qbs @@ -0,0 +1,44 @@ +import qbs +import qbs.FileInfo +import qbs.TextFile + +Project { + property string hostProfile + property string targetProfile + Product { + name: "p1" + type: "output" + profiles: [project.targetProfile, project.hostProfile] + Group { + files: "host+target.input" + fileTags: "input" + } + } + Product { + name: "p2" + type: "output" + profiles: project.hostProfile + Group { + files: "host-tool.input" + fileTags: "input" + } + } + + Rule { + inputs: "input" + Artifact { + fileName: FileInfo.baseName(input.fileName) + ".output" + fileTags: "output" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.write(product.moduleProperty("qbs", "architecture")); + file.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp index 124665b1e..2040a14f9 100644 --- a/tests/auto/api/tst_api.cpp +++ b/tests/auto/api/tst_api.cpp @@ -35,16 +35,18 @@ #include <api/project.h> #include <api/projectdata.h> #include <logging/ilogsink.h> +#include <tools/buildoptions.h> #include <tools/fileinfo.h> #include <tools/hostosinfo.h> -#include <tools/buildoptions.h> #include <tools/installoptions.h> #include <tools/preferences.h> +#include <tools/profile.h> #include <tools/setupprojectparameters.h> #include <QCoreApplication> #include <QDir> #include <QEventLoop> +#include <QFile> #include <QFileInfo> #include <QScopedPointer> #include <QStringList> @@ -585,6 +587,94 @@ void TestApi::listBuildSystemFiles() + QLatin1String("/subproject2/subproject3/subproject3.qbs"))); } +void TestApi::multiArch() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters(); + setupParams.setDryRun(false); + const QString projectDir + = QDir::cleanPath(m_workingDataDir + "/multi-arch"); + const QString topLevelProjectFile = projectDir + QLatin1String("/project.qbs"); + setupParams.setBuildRoot(projectDir); + setupParams.setProjectFilePath(topLevelProjectFile); + SettingsPtr settings = qbsSettings(QString()); + qbs::Internal::TemporaryProfile tph("host", settings.data()); + qbs::Profile hostProfile = tph.p; + hostProfile.setValue("qbs.architecture", "host-arch"); + qbs::Internal::TemporaryProfile tpt("target", settings.data()); + qbs::Profile targetProfile = tpt.p; + targetProfile.setValue("qbs.architecture", "target-arch"); + QVariantMap overriddenValues; + overriddenValues.insert("project.hostProfile", hostProfile.name()); + overriddenValues.insert("project.targetProfile", targetProfile.name()); + overriddenValues.insert("qbs.endianness", "little"); // TODO: Why does the qbs module require this? + setupParams.setOverriddenValues(overriddenValues); + QScopedPointer<qbs::SetupProjectJob> setupJob(qbs::Project::setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.data()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const qbs::Project &project = setupJob->project(); + QCOMPARE(project.profile(), QLatin1String("qbs_autotests")); + const QList<qbs::ProductData> &products = project.projectData().products(); + QCOMPARE(products.count(), 3); + QList<qbs::ProductData> hostProducts; + QList<qbs::ProductData> targetProducts; + foreach (const qbs::ProductData &p, products) { + QVERIFY2(p.profile() == hostProfile.name() || p.profile() == targetProfile.name(), + qPrintable(p.profile())); + if (p.profile() == hostProfile.name()) + hostProducts << p; + else + targetProducts << p; + } + QCOMPARE(hostProducts.count(), 2); + QCOMPARE(targetProducts.count(), 1); + QCOMPARE(targetProducts.first().name(), QLatin1String("p1")); + QStringList hostProductNames + = QStringList() << hostProducts.first().name() << hostProducts.last().name(); + QCOMPARE(hostProductNames.count("p1"), 1); + QCOMPARE(hostProductNames.count("p2"), 1); + + QScopedPointer<qbs::BuildJob> buildJob(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.data()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + const QString outputBaseDir(setupParams.buildRoot() + "/qbs_autotests-debug"); + QFile p1HostArtifact(outputBaseDir + "/p1.host/host+target.output"); + QVERIFY2(p1HostArtifact.exists(), qPrintable(p1HostArtifact.fileName())); + QVERIFY2(p1HostArtifact.open(QIODevice::ReadOnly), qPrintable(p1HostArtifact.errorString())); + QCOMPARE(p1HostArtifact.readAll().constData(), "host-arch"); + QFile p1TargetArtifact(outputBaseDir + "/p1.target/host+target.output"); + QVERIFY2(p1TargetArtifact.exists(), qPrintable(p1TargetArtifact.fileName())); + QVERIFY2(p1TargetArtifact.open(QIODevice::ReadOnly), qPrintable(p1TargetArtifact.errorString())); + QCOMPARE(p1TargetArtifact.readAll().constData(), "target-arch"); + QFile p2Artifact(outputBaseDir + "/p2.host/host-tool.output"); + QVERIFY2(p2Artifact.exists(), qPrintable(p2Artifact.fileName())); + QVERIFY2(p2Artifact.open(QIODevice::ReadOnly), qPrintable(p2Artifact.errorString())); + QCOMPARE(p2Artifact.readAll().constData(), "host-arch"); + + // Error check: Try to build for the same profile twice. + overriddenValues.insert("project.targetProfile", hostProfile.name()); + setupParams.setOverriddenValues(overriddenValues); + setupJob.reset(qbs::Project::setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.data()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains(hostProfile.name()) + && setupJob->error().toString().contains("not allowed"), + qPrintable(setupJob->error().toString())); + + // Error check: Try to build for the same profile twice, this time attaching + // the properties via the product name. + overriddenValues.clear(); + overriddenValues.insert("p1.profiles", targetProfile.name() + ',' + targetProfile.name()); + overriddenValues.insert("qbs.endianness", "little"); // TODO: Meh. + setupParams.setOverriddenValues(overriddenValues); + setupJob.reset(qbs::Project::setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.data()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains(targetProfile.name()) + && setupJob->error().toString().contains("not allowed"), + qPrintable(setupJob->error().toString())); +} + void TestApi::nonexistingProjectPropertyFromProduct() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); @@ -622,7 +712,7 @@ qbs::SetupProjectParameters TestApi::defaultSetupParameters() const { qbs::SetupProjectParameters setupParams; setupParams.setDryRun(true); // So no build graph gets created. - setupParams.setBuildRoot(QLatin1String("/blubb")); // Must be set and be absolute. + setupParams.setBuildRoot(m_workingDataDir); setupParams.setRestoreBehavior(qbs::SetupProjectParameters::ResolveOnly); // No restoring. const QString qbsRootPath = QDir::cleanPath(QCoreApplication::applicationDirPath() @@ -679,6 +769,7 @@ void TestApi::sourceFileInBuildDir() const qbs::ProjectData projectData = job->project().projectData(); QCOMPARE(projectData.allProducts().count(), 1); const qbs::ProductData product = projectData.allProducts().first(); + QCOMPARE(product.profile(), QLatin1String("qbs_autotests")); QCOMPARE(product.groups().count(), 1); const qbs::GroupData group = product.groups().first(); QCOMPARE(group.allFilePaths().count(), 1); diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h index 0ad65c5e5..7d1157512 100644 --- a/tests/auto/api/tst_api.h +++ b/tests/auto/api/tst_api.h @@ -57,6 +57,7 @@ private slots: void installableFiles(); void isRunnable(); void listBuildSystemFiles(); + void multiArch(); void nonexistingProjectPropertyFromProduct(); void nonexistingProjectPropertyFromCommandLine(); void references(); diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 148066009..d7d6129d1 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -776,8 +776,8 @@ void TestBlackbox::track_qobject_change() QCOMPARE(runQbs(), 0); const QString productFilePath = executableFilePath("i"); QVERIFY2(regularFileExists(productFilePath), qPrintable(productFilePath)); - QString moc_bla_objectFileName - = buildDir + "/i/.obj/GeneratedFiles/moc_bla.cpp" QTC_HOST_OBJECT_SUFFIX; + QString moc_bla_objectFileName = productBuildDir("i") + + "/.obj/GeneratedFiles/moc_bla.cpp" QTC_HOST_OBJECT_SUFFIX; QVERIFY2(regularFileExists(moc_bla_objectFileName), qPrintable(moc_bla_objectFileName)); QTest::qSleep(1000); @@ -992,7 +992,8 @@ void TestBlackbox::trackRemoveFileTag() QCOMPARE(runQbs(), 0); // check if the artifacts are here that will become stale in the 2nd step - QVERIFY(regularFileExists(buildDir + "/someapp/.obj/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX)); + QVERIFY(regularFileExists(productBuildDir("someapp") + + "/.obj/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX)); QVERIFY(regularFileExists(productBuildDir("someapp") + "/main_foo.cpp")); QVERIFY(regularFileExists(productBuildDir("someapp") + "/main.foo")); @@ -2147,8 +2148,8 @@ void TestBlackbox::testWiX() QVERIFY(m_qbsStdout.contains("compiling QbsBootstrapper.wxs")); QVERIFY(m_qbsStdout.contains("linking qbs-" + arch + ".msi")); QVERIFY(m_qbsStdout.contains("linking qbs-setup-" + arch + ".exe")); - QVERIFY(regularFileExists(buildDir + "/QbsSetup/qbs-" + arch + ".msi")); - QVERIFY(regularFileExists(buildDir + "/QbsBootstrapper/qbs-setup-" + arch + ".exe")); + QVERIFY(regularFileExists(productBuildDir("QbsSetup") + "/qbs-" + arch + ".msi")); + QVERIFY(regularFileExists(productBuildDir("QbsBootstrapper") + "/qbs-setup-" + arch + ".exe")); } static QString findExecutable(const QStringList &fileNames) @@ -2188,7 +2189,7 @@ void TestBlackbox::testNodeJs() params.command = QLatin1String("run"); QCOMPARE(runQbs(params), 0); QVERIFY((bool)m_qbsStdout.contains("hello world")); - QVERIFY(regularFileExists(buildDir + "/hello/hello.js")); + QVERIFY(regularFileExists(productBuildDir("hello") + "/hello.js")); } void TestBlackbox::testTypeScript() @@ -2205,14 +2206,19 @@ void TestBlackbox::testTypeScript() params.arguments = QStringList() << "-p" << "animals"; QCOMPARE(runQbs(params), 0); - QVERIFY(regularFileExists(buildDir + "/animals/animals.js")); - QVERIFY(regularFileExists(buildDir + "/animals/extra.js")); - QVERIFY(regularFileExists(buildDir + "/animals/main.js")); + QVERIFY(regularFileExists(productBuildDir("animals") + "/animals.js")); + QVERIFY(regularFileExists(productBuildDir("animals") + "/extra.js")); + QVERIFY(regularFileExists(productBuildDir("animals") + "/main.js")); +} + +QString TestBlackbox::uniqueProductName(const QString &productName) const +{ + return productName + '.' + buildProfileName; } QString TestBlackbox::productBuildDir(const QString &productName) const { - return buildDir + '/' + productName; + return buildDir + '/' + uniqueProductName(productName); } QString TestBlackbox::executableFilePath(const QString &productName) const diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 568d1c08e..6076e4fda 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -183,6 +183,7 @@ private slots: void testTypeScript(); private: + QString uniqueProductName(const QString &productName) const; QString productBuildDir(const QString &productName) const; QString executableFilePath(const QString &productName) const; |