summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@digia.com>2013-11-20 18:16:34 +0100
committerChristian Kandeler <christian.kandeler@digia.com>2013-11-25 12:15:02 +0100
commit9ad4bc2401dd3d3be8b6c4407666b9ea1973dbb5 (patch)
tree84477d04987b7ac48541dbd2b9ed0bdb3ab90037
parent8e1cba759fa91d2537bed76eb604c6a26df415b2 (diff)
downloadqbs-9ad4bc2401dd3d3be8b6c4407666b9ea1973dbb5.tar.gz
API: Allow to remove files from a product.
Task-number: QBS-20 Change-Id: Ia8b8c4745e8e68ec219e16876fb630d02453425b Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
-rw-r--r--src/lib/api/project.cpp171
-rw-r--r--src/lib/api/project.h2
-rw-r--r--src/lib/api/projectfileupdater.cpp142
-rw-r--r--src/lib/api/projectfileupdater.h14
-rw-r--r--src/lib/buildgraph/buildgraphloader.cpp35
-rw-r--r--src/lib/buildgraph/projectbuilddata.cpp30
-rw-r--r--src/lib/buildgraph/projectbuilddata.h2
-rw-r--r--tests/auto/api/tst_api.cpp41
8 files changed, 353 insertions, 84 deletions
diff --git a/src/lib/api/project.cpp b/src/lib/api/project.cpp
index affe2c9f4..687488bfe 100644
--- a/src/lib/api/project.cpp
+++ b/src/lib/api/project.cpp
@@ -119,8 +119,21 @@ public:
GroupData createGroupDataFromGroup(const GroupPtr &resolvedGroup);
+ struct FileListUpdateContext {
+ ResolvedProductPtr resolvedProduct;
+ GroupPtr resolvedGroup;
+ ProductData currentProduct;
+ GroupData currentGroup;
+ QStringList absoluteFilePaths;
+ QStringList relativeFilePaths;
+ };
+ FileListUpdateContext getFileListContext(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths);
+
void addGroup(const ProductData &product, const QString &groupName);
void addFiles(const ProductData &product, const GroupData &group, const QStringList &filePaths);
+ void removeFiles(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths);
void updateInternalCodeLocations(const ResolvedProjectPtr &project,
const CodeLocation &changeLocation, int lineOffset);
void updateExternalCodeLocations(const ProjectData &project,
@@ -321,56 +334,63 @@ void ProjectPrivate::addGroup(const ProductData &product, const QString &groupNa
}
}
-void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group,
- const QStringList &filePaths)
+ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const ProductData &product,
+ const GroupData &group, const QStringList &filePaths)
{
+ FileListUpdateContext context;
if (!product.isValid())
throw ErrorInfo(Tr::tr("Product is invalid."));
- const ResolvedProductPtr resolvedProduct = internalProduct(product);
- if (!resolvedProduct)
+ context.resolvedProduct = internalProduct(product);
+ if (!context.resolvedProduct)
throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name()));
- const ProductData currentProduct = findProductData(product.name());
- QBS_CHECK(currentProduct.isValid());
+ context.currentProduct = findProductData(product.name());
+ QBS_CHECK(context.currentProduct.isValid());
- GroupPtr resolvedGroup;
- GroupData currentGroup;
const QString groupName = group.isValid() ? group.name() : product.name();
- foreach (const GroupPtr &g, resolvedProduct->groups) {
+ foreach (const GroupPtr &g, context.resolvedProduct->groups) {
if (g->name == groupName) {
- resolvedGroup = g;
+ context.resolvedGroup = g;
break;
}
}
- if (!resolvedGroup)
+ if (!context.resolvedGroup)
throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName));
- currentGroup = findGroupData(currentProduct, groupName);
- QBS_CHECK(currentGroup.isValid());
+ context.currentGroup = findGroupData(context.currentProduct, groupName);
+ QBS_CHECK(context.currentGroup.isValid());
if (filePaths.isEmpty())
- return; // "No-op". Could also be defined to be an error, I guess.
+ throw ErrorInfo(Tr::tr("No files supplied."));
- if (!resolvedGroup->prefix.isEmpty() && !resolvedGroup->prefix.endsWith(QLatin1Char('/')))
- throw ErrorInfo(Tr::tr("Cannot add files to a group with a non-directory prefix."));
- QStringList absoluteFilePaths;
- QStringList relativeFilePaths;
+ if (!context.resolvedGroup->prefix.isEmpty() &&
+ !context.resolvedGroup->prefix.endsWith(QLatin1Char('/'))) {
+ throw ErrorInfo(Tr::tr("Group has non-directory prefix."));
+ }
QString baseDirPath = QFileInfo(product.location().fileName()).dir().absolutePath()
- + QLatin1Char('/') + resolvedGroup->prefix;
+ + QLatin1Char('/') + context.resolvedGroup->prefix;
QDir baseDir(baseDirPath);
foreach (const QString &filePath, filePaths) {
const QString absPath = QDir::cleanPath(FileInfo::resolvePath(baseDirPath, filePath));
- if (absoluteFilePaths.contains(absPath))
+ if (context.absoluteFilePaths.contains(absPath))
throw ErrorInfo(Tr::tr("File '%1' appears more than once.").arg(absPath));
if (!FileInfo(absPath).exists())
throw ErrorInfo(Tr::tr("File '%1' does not exist.").arg(absPath));
- absoluteFilePaths << absPath;
- relativeFilePaths << baseDir.relativeFilePath(absPath);
+ context.absoluteFilePaths << absPath;
+ context.relativeFilePaths << baseDir.relativeFilePath(absPath);
}
+ return context;
+}
+
+void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths)
+{
+ FileListUpdateContext context = getFileListContext(product, group, filePaths);
+
// We do not check for entries in other groups, because such doublettes might be legitimate
// due to conditions.
- foreach (const QString &filePath, absoluteFilePaths) {
- foreach (const SourceArtifactConstPtr &sa, resolvedGroup->files) {
+ foreach (const QString &filePath, context.absoluteFilePaths) {
+ foreach (const SourceArtifactConstPtr &sa, context.resolvedGroup->files) {
if (sa->absoluteFilePath == filePath) {
throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.")
.arg(filePath, group.name()));
@@ -378,8 +398,8 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group
}
}
- ProjectFileFilesAdder adder(currentProduct, group.isValid() ? currentGroup : GroupData(),
- relativeFilePaths);
+ ProjectFileFilesAdder adder(context.currentProduct,
+ group.isValid() ? context.currentGroup : GroupData(), context.relativeFilePaths);
adder.apply();
m_projectData.d.detach();
@@ -387,33 +407,80 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group
updateExternalCodeLocations(m_projectData, adder.itemPosition(), adder.lineOffset());
QList<SourceArtifactPtr> addedSourceArtifacts;
- foreach (const QString &file, absoluteFilePaths) {
+ foreach (const QString &file, context.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);
+ artifact->properties = context.resolvedGroup->properties;
+ artifact->fileTags = context.resolvedGroup->fileTags;
+ artifact->overrideFileTags = context.resolvedGroup->overrideTags;
+ ProjectResolver::applyFileTaggers(artifact, context.resolvedProduct, logger);
addedSourceArtifacts << artifact;
- resolvedGroup->files << artifact;
+ context.resolvedGroup->files << artifact;
}
- if (resolvedProduct->enabled) {
+ if (context.resolvedProduct->enabled) {
ArtifactsPerFileTagMap artifactsPerFileTag;
foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) {
- Artifact * const artifact = createArtifact(resolvedProduct, sa, logger);
+ Artifact * const artifact = createArtifact(context.resolvedProduct, sa, logger);
foreach (const FileTag &ft, artifact->fileTags)
artifactsPerFileTag[ft] += artifact;
}
RulesEvaluationContextPtr &evalContext
- = resolvedProduct->topLevelProject()->buildData->evaluationContext;
+ = context.resolvedProduct->topLevelProject()->buildData->evaluationContext;
evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger));
- RulesApplicator(resolvedProduct, artifactsPerFileTag, logger).applyAllRules();
- addTargetArtifacts(resolvedProduct, artifactsPerFileTag, logger);
+ RulesApplicator(context.resolvedProduct, artifactsPerFileTag, logger).applyAllRules();
+ addTargetArtifacts(context.resolvedProduct, artifactsPerFileTag, logger);
evalContext.clear();
}
doSanityChecks(internalProject, logger);
- currentGroup.d->filePaths << absoluteFilePaths;
- qSort(currentGroup.d->filePaths);
+ context.currentGroup.d->filePaths << context.absoluteFilePaths;
+ qSort(context.currentGroup.d->filePaths);
+}
+
+void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths)
+{
+ FileListUpdateContext context = getFileListContext(product, group, filePaths);
+
+ QStringList filesNotFound = context.absoluteFilePaths;
+ QList<SourceArtifactPtr> sourceArtifacts;
+ foreach (const SourceArtifactPtr &sa, context.resolvedGroup->files) {
+ if (filesNotFound.removeOne(sa->absoluteFilePath))
+ sourceArtifacts << sa;
+ }
+ if (!filesNotFound.isEmpty()) {
+ throw ErrorInfo(Tr::tr("The following files are not known to qbs: %1")
+ .arg(filesNotFound.join(QLatin1String(", "))));
+ }
+
+ ProjectFileFilesRemover remover(context.currentProduct,
+ group.isValid() ? context.currentGroup : GroupData(), context.relativeFilePaths);
+ remover.apply();
+
+ foreach (const SourceArtifactPtr &sa, sourceArtifacts) {
+ if (context.resolvedProduct->enabled) {
+ QBS_CHECK(internalProject->buildData);
+ Artifact * const artifact = lookupArtifact(context.resolvedProduct,
+ sa->absoluteFilePath);
+ QBS_CHECK(artifact);
+ internalProject->buildData->removeArtifactAndExclusiveDependents(artifact, logger);
+ }
+ context.resolvedGroup->files.removeOne(sa);
+ }
+ if (context.resolvedProduct->enabled) {
+ RulesEvaluationContextPtr &evalContext
+ = context.resolvedProduct->topLevelProject()->buildData->evaluationContext;
+ evalContext = QSharedPointer<RulesEvaluationContext>(new RulesEvaluationContext(logger));
+ internalProject->buildData->updateNodesThatMustGetNewTransformer(logger);
+ evalContext.clear();
+ }
+ doSanityChecks(internalProject, logger);
+
+ m_projectData.d.detach();
+ updateInternalCodeLocations(internalProject, remover.itemPosition(), remover.lineOffset());
+ updateExternalCodeLocations(m_projectData, remover.itemPosition(), remover.lineOffset());
+ foreach (const QString &filePath, context.absoluteFilePaths)
+ context.currentGroup.d->filePaths.removeOne(filePath);
+ qSort(context.currentGroup.d->filePaths);
}
static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation &changeLocation,
@@ -429,6 +496,8 @@ static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation
void ProjectPrivate::updateInternalCodeLocations(const ResolvedProjectPtr &project,
const CodeLocation &changeLocation, int lineOffset)
{
+ if (lineOffset == 0)
+ return;
updateLocationIfNecessary(project->location, changeLocation, lineOffset);
foreach (const ResolvedProjectPtr &subProject, project->subProjects)
updateInternalCodeLocations(subProject, changeLocation, lineOffset);
@@ -459,6 +528,8 @@ void ProjectPrivate::updateInternalCodeLocations(const ResolvedProjectPtr &proje
void ProjectPrivate::updateExternalCodeLocations(const ProjectData &project,
const CodeLocation &changeLocation, int lineOffset)
{
+ if (lineOffset == 0)
+ return;
updateLocationIfNecessary(project.d->location, changeLocation, lineOffset);
foreach (const ProjectData &subProject, project.subProjects())
updateExternalCodeLocations(subProject, changeLocation, lineOffset);
@@ -852,4 +923,26 @@ ErrorInfo Project::addFiles(const ProductData &product, const GroupData &group,
}
}
+/*!
+ * \brief Removes the given files from the given product.
+ * If \c group is a default-constructed object, the files will be removed from the product's
+ * "files" property, otherwise from the one of \c group.
+ * The file paths can be absolute or relative to the location of \c product (including a possible
+ * prefix in the group).
+ * After calling this function, it is recommended to re-fetch the project data, as other
+ * items can be affected.
+ * \sa qbs::Project::projectData()
+ */
+ErrorInfo Project::removeFiles(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths)
+{
+ try {
+ d->removeFiles(product, group, filePaths);
+ return ErrorInfo();
+ } catch (ErrorInfo errorInfo) {
+ errorInfo.prepend(Tr::tr("Failure removing files from product."));
+ return errorInfo;
+ }
+}
+
} // namespace qbs
diff --git a/src/lib/api/project.h b/src/lib/api/project.h
index 11df00a08..883400e6a 100644
--- a/src/lib/api/project.h
+++ b/src/lib/api/project.h
@@ -125,6 +125,8 @@ public:
ErrorInfo addGroup(const ProductData &product, const QString &groupName);
ErrorInfo addFiles(const ProductData &product, const GroupData &group,
const QStringList &filePaths);
+ ErrorInfo removeFiles(const ProductData &product, const GroupData &group,
+ const QStringList &filePaths);
private:
Project(const Internal::TopLevelProjectPtr &internalProject, const Internal::Logger &logger);
diff --git a/src/lib/api/projectfileupdater.cpp b/src/lib/api/projectfileupdater.cpp
index 3c35af161..97d57fd86 100644
--- a/src/lib/api/projectfileupdater.cpp
+++ b/src/lib/api/projectfileupdater.cpp
@@ -187,6 +187,31 @@ void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast)
changeSet.apply(&fileContent);
}
+static QString getNodeRepresentation(const QString &fileContent, const Node *node)
+{
+ const quint32 start = node->firstSourceLocation().offset;
+ const quint32 end = node->lastSourceLocation().end();
+ return fileContent.mid(start, end - start);
+}
+
+static const ChangeSet::EditOp &getEditOp(const ChangeSet &changeSet)
+{
+ const QList<ChangeSet::EditOp> &editOps = changeSet.operationList();
+ QBS_CHECK(editOps.count() == 1);
+ return editOps.first();
+}
+
+static int getLineOffsetForChangedBinding(const ChangeSet &changeSet, const QString &oldRhs)
+{
+ return getEditOp(changeSet).text.count(QLatin1Char('\n')) - oldRhs.count(QLatin1Char('\n'));
+}
+
+static int getBindingLine(const ChangeSet &changeSet, const QString &fileContent)
+{
+ return fileContent.left(getEditOp(changeSet).pos1 + 1).count(QLatin1Char('\n')) + 1;
+}
+
+
ProjectFileFilesAdder::ProjectFileFilesAdder(const ProductData &product, const GroupData &group,
const QStringList &files)
: ProjectFileUpdater(product.location().fileName())
@@ -196,13 +221,6 @@ ProjectFileFilesAdder::ProjectFileFilesAdder(const ProductData &product, const G
{
}
-static QString getNodeRepresentation(const QString &fileContent, const Node *node)
-{
- const quint32 start = node->firstSourceLocation().offset;
- const quint32 end = node->lastSourceLocation().end();
- return fileContent.mid(start, end - start);
-}
-
void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast)
{
// Find the item containing the "files" binding.
@@ -305,17 +323,115 @@ void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast)
Rewriter::ScriptBinding);
}
- const QList<ChangeSet::EditOp> &editOps = changeSet.operationList();
- QBS_CHECK(editOps.count() == 1);
- const ChangeSet::EditOp &insertOp = editOps.first();
- setLineOffset(insertOp.text.count(QLatin1Char('\n')));
-
- const int insertionLine = fileContent.left(insertOp.pos1 + 1).count(QLatin1Char('\n')) + 2;
+ setLineOffset(getLineOffsetForChangedBinding(changeSet, getNodeRepresentation(fileContent,
+ filesBinding->statement)));
+ const int insertionLine = getBindingLine(changeSet, fileContent) + 1;
const int insertionColumn = (filesBinding ? arrayElemIndentation : bindingIndentation) + 1;
setItemPosition(CodeLocation(projectFile(), insertionLine, insertionColumn));
changeSet.apply(&fileContent);
}
+ProjectFileFilesRemover::ProjectFileFilesRemover(const ProductData &product, const GroupData &group,
+ const QStringList &files)
+ : ProjectFileUpdater(product.location().fileName())
+ , m_product(product)
+ , m_group(group)
+ , m_files(files)
+{
+}
+
+void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast)
+{
+ // Find the item containing the "files" binding.
+ ItemFinder itemFinder(m_group.isValid() ? m_group.location() : m_product.location());
+ ast->accept(&itemFinder);
+ if (!itemFinder.item()) {
+ throw ErrorInfo(Tr::tr("The project file parser failed to find the item."),
+ CodeLocation(projectFile()));
+ }
+
+ // Now get the binding itself.
+ FilesBindingFinder bindingFinder(itemFinder.item());
+ itemFinder.item()->accept(&bindingFinder);
+ if (!bindingFinder.binding()) {
+ throw ErrorInfo(Tr::tr("Could not find the 'files' binding in the project file."),
+ m_product.location());
+ }
+
+ if (bindingFinder.binding()->statement->kind != Node::Kind_ExpressionStatement)
+ throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."));
+ const CodeLocation bindingLocation
+ = toCodeLocation(projectFile(), bindingFinder.binding()->firstSourceLocation());
+
+ ChangeSet changeSet;
+ Rewriter rewriter(fileContent, &changeSet, QStringList());
+
+ const int itemIndentation
+ = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1;
+ const int bindingIndentation = itemIndentation + 4;
+ const int arrayElemIndentation = bindingIndentation + 4;
+
+ const ExpressionStatement * const exprStatement
+ = static_cast<ExpressionStatement *>(bindingFinder.binding()->statement);
+ switch (exprStatement->expression->kind) {
+ case Node::Kind_ArrayLiteral: {
+ QStringList filesToRemove = m_files;
+ QStringList newFilesList;
+ const ElementList *elem = static_cast<ArrayLiteral *>(exprStatement->expression)->elements;
+ while (elem) {
+ if (elem->expression->kind != Node::Kind_StringLiteral) {
+ throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."),
+ bindingLocation);
+ }
+ const QString existingFile
+ = static_cast<StringLiteral *>(elem->expression)->value.toString();
+ if (!filesToRemove.removeOne(existingFile))
+ newFilesList << existingFile;
+ elem = elem->next;
+ }
+ if (!filesToRemove.isEmpty()) {
+ throw ErrorInfo(Tr::tr("The following files were not found in the 'files' list: %1")
+ .arg(filesToRemove.join(QLatin1String(", "))), bindingLocation);
+ }
+ QString filesString = QLatin1String("[\n");
+ foreach (const QString &file, newFilesList) {
+ filesString += QString(arrayElemIndentation, QLatin1Char(' '));
+ filesString += QString::fromLocal8Bit("\"%1\",\n").arg(file);
+ }
+ filesString += QString(bindingIndentation, QLatin1Char(' '));
+ filesString += QLatin1Char(']');
+ rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"),
+ filesString, Rewriter::ScriptBinding);
+ break;
+ }
+ case Node::Kind_StringLiteral: {
+ if (m_files.count() != 1) {
+ throw ErrorInfo(Tr::tr("Was requested to remove %1 files, but there is only "
+ "one in the list.").arg(m_files.count()), bindingLocation);
+ }
+ const QString existingFile
+ = static_cast<StringLiteral *>(exprStatement->expression)->value.toString();
+ if (existingFile != m_files.first()) {
+ throw ErrorInfo(Tr::tr("File '1' could not be found in the 'files' list."),
+ bindingLocation);
+ }
+ rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"),
+ QLatin1String("[]"), Rewriter::ScriptBinding);
+ break;
+ }
+ default:
+ throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."),
+ bindingLocation);
+ }
+
+ setLineOffset(getLineOffsetForChangedBinding(changeSet,
+ getNodeRepresentation(fileContent, exprStatement->expression)));
+ const int bindingLine = getBindingLine(changeSet, fileContent);
+ const int bindingColumn = (bindingFinder.binding()
+ ? arrayElemIndentation : bindingIndentation) + 1;
+ setItemPosition(CodeLocation(projectFile(), bindingLine, bindingColumn));
+ changeSet.apply(&fileContent);
+}
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/api/projectfileupdater.h b/src/lib/api/projectfileupdater.h
index a7ce351a5..5709375b3 100644
--- a/src/lib/api/projectfileupdater.h
+++ b/src/lib/api/projectfileupdater.h
@@ -93,6 +93,20 @@ private:
const QStringList m_files;
};
+class ProjectFileFilesRemover : public ProjectFileUpdater
+{
+public:
+ ProjectFileFilesRemover(const ProductData &product, const GroupData &group,
+ const QStringList &files);
+
+private:
+ void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast);
+
+ const ProductData m_product;
+ const GroupData m_group;
+ const QStringList m_files;
+};
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/buildgraph/buildgraphloader.cpp b/src/lib/buildgraph/buildgraphloader.cpp
index 94ce07f57..8c3639104 100644
--- a/src/lib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/buildgraph/buildgraphloader.cpp
@@ -548,7 +548,9 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor
Artifact *artifact
= lookupArtifact(restoredProduct, oldBuildData, a->absoluteFilePath, true);
QBS_CHECK(artifact);
- removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove);
+ newlyResolvedProduct->topLevelProject()->buildData
+ ->removeArtifactAndExclusiveDependents(artifact, m_logger, true,
+ &artifactsToRemove);
continue;
}
@@ -574,7 +576,9 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor
foreach (Artifact *parent, artifact->parents) {
if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) {
// this parent has been created because of the removed filetag
- removeArtifactAndExclusiveDependents(parent, &artifactsToRemove);
+ newlyResolvedProduct->topLevelProject()->buildData
+ ->removeArtifactAndExclusiveDependents(parent, m_logger, true,
+ &artifactsToRemove);
}
}
}
@@ -602,33 +606,6 @@ void BuildGraphLoader::onProductFileListChanged(const ResolvedProductPtr &restor
}
}
-/**
- * Removes the artifact and all the artifacts that depend exclusively on it.
- * Example: if you remove a cpp artifact then the obj artifact is removed but
- * not the resulting application (if there's more then one cpp artifact).
- */
-void BuildGraphLoader::removeArtifactAndExclusiveDependents(Artifact *artifact,
- ArtifactList *removedArtifacts)
-{
- if (removedArtifacts)
- removedArtifacts->insert(artifact);
- TopLevelProject * const project = artifact->product->topLevelProject();
- foreach (Artifact *parent, artifact->parents) {
- bool removeParent = false;
- disconnect(parent, artifact, m_logger);
- if (parent->children.isEmpty()) {
- removeParent = true;
- } else if (parent->transformer) {
- project->buildData->artifactsThatMustGetNewTransformers += parent;
- parent->transformer->inputs.remove(artifact);
- removeParent = parent->transformer->inputs.isEmpty();
- }
- if (removeParent)
- removeArtifactAndExclusiveDependents(parent, removedArtifacts);
- }
- project->buildData->removeArtifact(artifact, m_logger);
-}
-
static SourceArtifactConstPtr findSourceArtifact(const ResolvedProductConstPtr &product,
const QString &artifactFilePath, QMap<QString, SourceArtifactConstPtr> &artifactMap)
{
diff --git a/src/lib/buildgraph/projectbuilddata.cpp b/src/lib/buildgraph/projectbuilddata.cpp
index bec6585bc..57d7ea6e0 100644
--- a/src/lib/buildgraph/projectbuilddata.cpp
+++ b/src/lib/buildgraph/projectbuilddata.cpp
@@ -142,6 +142,36 @@ static void disconnectArtifact(Artifact *artifact, ProjectBuildData *projectBuil
disconnectArtifactParents(artifact, projectBuildData, logger);
}
+/*!
+ * Removes the artifact and all the artifacts that depend exclusively on it.
+ * Example: if you remove a cpp artifact then the obj artifact is removed but
+ * not the resulting application (if there's more then one cpp artifact).
+ */
+void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact,
+ const Logger &logger, bool removeFromProduct,
+ ArtifactList *removedArtifacts)
+{
+ if (removedArtifacts)
+ removedArtifacts->insert(artifact);
+ foreach (Artifact *parent, artifact->parents) {
+ bool removeParent = false;
+ disconnect(parent, artifact, logger);
+ if (parent->children.isEmpty()) {
+ removeParent = true;
+ } else if (parent->transformer) {
+ artifactsThatMustGetNewTransformers += parent;
+ parent->transformer->inputs.remove(artifact);
+ removeParent = parent->transformer->inputs.isEmpty();
+ }
+ if (removeParent) {
+ removeArtifactAndExclusiveDependents(parent, logger, removeFromProduct,
+ removedArtifacts);
+ }
+ }
+ const bool removeFromDisk = artifact->artifactType == Artifact::Generated;
+ removeArtifact(artifact, logger, removeFromDisk, removeFromProduct);
+}
+
void ProjectBuildData::removeArtifact(Artifact *artifact,
const Logger &logger, bool removeFromDisk, bool removeFromProduct)
{
diff --git a/src/lib/buildgraph/projectbuilddata.h b/src/lib/buildgraph/projectbuilddata.h
index 346291dc6..2268d67f0 100644
--- a/src/lib/buildgraph/projectbuilddata.h
+++ b/src/lib/buildgraph/projectbuilddata.h
@@ -63,6 +63,8 @@ public:
QList<FileResourceBase *> lookupFiles(const Artifact *artifact) const;
void insertFileDependency(FileDependency *dependency);
void updateNodesThatMustGetNewTransformer(const Logger &logger);
+ void removeArtifactAndExclusiveDependents(Artifact *artifact, const Logger &logger,
+ bool removeFromProduct = true, ArtifactList *removedArtifacts = 0);
void removeArtifact(Artifact *artifact, const Logger &logger, bool removeFromDisk = true,
bool removeFromProduct = true);
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 536603d7d..94c94e656 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -180,6 +180,32 @@ void TestApi::changeContent()
QVERIFY(errorInfo.hasError());
QVERIFY2(errorInfo.toString().contains("already"), qPrintable(errorInfo.toString()));
+ // Remove one of the newly added files again.
+ errorInfo = project.removeFiles(product, group, QStringList("file.h"));
+ QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString()));
+
+ // Error handling: Try to remove the same file again.
+ projectData = project.projectData();
+ QVERIFY(projectData.products().count() == 1);
+ product = projectData.products().first();
+ QCOMPARE(product.groups().count(), 8);
+ group = findGroup(product, "New Group 1");
+ QVERIFY(group.isValid());
+ errorInfo = project.removeFiles(product, group, QStringList() << "file.h");
+ QVERIFY(errorInfo.hasError());
+ QVERIFY2(errorInfo.toString().contains("not known"), qPrintable(errorInfo.toString()));
+
+ // Error handling: Try to remove a file from a complex list.
+ group = findGroup(product, "Existing Group 2");
+ QVERIFY(group.isValid());
+ errorInfo = project.removeFiles(product, group, QStringList() << "existingfile2.txt");
+ QVERIFY(errorInfo.hasError());
+ QVERIFY2(errorInfo.toString().contains("complex"), qPrintable(errorInfo.toString()));
+
+ // Remove file from product's 'files' binding.
+ errorInfo = project.removeFiles(product, qbs::GroupData(), QStringList("main.cpp"));
+ QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString()));
+
// Add file to non-empty array literal.
projectData = project.projectData();
QVERIFY(projectData.products().count() == 1);
@@ -234,18 +260,18 @@ void TestApi::changeContent()
QVERIFY(errorInfo.hasError());
QVERIFY2(errorInfo.toString().contains("prefix"), qPrintable(errorInfo.toString()));
- // Check whether building will take the newly added cpp file into account.
+ // Check whether building will take the added and removed cpp files into account.
// This must not be moved below the re-resolving test!!!
qbs::BuildOptions buildOptions;
buildOptions.setDryRun(true);
- m_logSink->setLogLevel(qbs::LoggerMaxLevel);
BuildDescriptionReveiver rcvr;
- const QScopedPointer<qbs::BuildJob> buildJob(project.buildAllProducts(buildOptions, this));
+ QScopedPointer<qbs::BuildJob> buildJob(project.buildAllProducts(buildOptions, this));
connect(buildJob.data(), SIGNAL(reportCommandDescription(QString, QString)), &rcvr,
SLOT(handleDescription(QString,QString)));
waitForFinished(buildJob.data());
QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString()));
QVERIFY(rcvr.descriptions.contains("compiling file.cpp"));
+ QVERIFY(!rcvr.descriptions.contains("compiling main.cpp"));
// Now check whether the data updates were done correctly.
projectData = project.projectData();
@@ -261,6 +287,15 @@ void TestApi::changeContent()
printProjectData(newProjectData);
}
QVERIFY(projectDataMatches); // Will fail if e.g. code locations don't match.
+
+ // Now try building again and check if the newly resolved product behaves the same way.
+ buildJob.reset(job->project().buildAllProducts(buildOptions, this));
+ connect(buildJob.data(), SIGNAL(reportCommandDescription(QString, QString)), &rcvr,
+ SLOT(handleDescription(QString,QString)));
+ waitForFinished(buildJob.data());
+ QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString()));
+ QVERIFY(rcvr.descriptions.contains("compiling file.cpp"));
+ QVERIFY(!rcvr.descriptions.contains("compiling main.cpp"));
}
void TestApi::disabledInstallGroup()