aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2014-05-22 12:02:12 +0200
committerChristian Kandeler <christian.kandeler@digia.com>2014-07-02 16:46:11 +0200
commite264281c4810bb0afb07e8f590fc31e504d5e4d0 (patch)
tree1bf8a9f0aaf1f6734e4acf53003a681048156efd
parent9297ea217284e2279f2d4471b3f8fc754dc5fe71 (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>
-rw-r--r--doc/reference/items/depends.qdoc7
-rw-r--r--doc/reference/items/product.qdoc14
-rw-r--r--doc/reference/items/project.qdoc6
-rw-r--r--src/lib/corelib/api/project.cpp209
-rw-r--r--src/lib/corelib/api/projectdata.cpp16
-rw-r--r--src/lib/corelib/api/projectdata.h1
-rw-r--r--src/lib/corelib/api/projectdata_p.h1
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp12
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp45
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp20
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp2
-rw-r--r--src/lib/corelib/buildgraph/projectbuilddata.cpp2
-rw-r--r--src/lib/corelib/buildgraph/rescuableartifactdata.cpp2
-rw-r--r--src/lib/corelib/buildgraph/rescuableartifactdata.h7
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp7
-rw-r--r--src/lib/corelib/language/language.cpp15
-rw-r--r--src/lib/corelib/language/language.h4
-rw-r--r--src/lib/corelib/language/moduleloader.cpp130
-rw-r--r--src/lib/corelib/language/moduleloader.h16
-rw-r--r--src/lib/corelib/language/projectresolver.cpp31
-rw-r--r--src/lib/corelib/language/tst_language.cpp8
-rw-r--r--src/lib/corelib/tools/persistence.cpp2
-rw-r--r--src/lib/corelib/tools/profile.h3
-rw-r--r--tests/auto/api/testdata/multi-arch/host+target.input0
-rw-r--r--tests/auto/api/testdata/multi-arch/host-tool.input0
-rw-r--r--tests/auto/api/testdata/multi-arch/project.qbs44
-rw-r--r--tests/auto/api/tst_api.cpp95
-rw-r--r--tests/auto/api/tst_api.h1
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp26
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
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 &paramet
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 &paramet
// 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 &paramet
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 &paramet
// 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 &parameters);
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;