diff options
| author | Christian Kandeler <christian.kandeler@digia.com> | 2013-11-20 18:16:34 +0100 |
|---|---|---|
| committer | Christian Kandeler <christian.kandeler@digia.com> | 2013-11-25 12:15:02 +0100 |
| commit | 9ad4bc2401dd3d3be8b6c4407666b9ea1973dbb5 (patch) | |
| tree | 84477d04987b7ac48541dbd2b9ed0bdb3ab90037 | |
| parent | 8e1cba759fa91d2537bed76eb604c6a26df415b2 (diff) | |
| download | qbs-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.cpp | 171 | ||||
| -rw-r--r-- | src/lib/api/project.h | 2 | ||||
| -rw-r--r-- | src/lib/api/projectfileupdater.cpp | 142 | ||||
| -rw-r--r-- | src/lib/api/projectfileupdater.h | 14 | ||||
| -rw-r--r-- | src/lib/buildgraph/buildgraphloader.cpp | 35 | ||||
| -rw-r--r-- | src/lib/buildgraph/projectbuilddata.cpp | 30 | ||||
| -rw-r--r-- | src/lib/buildgraph/projectbuilddata.h | 2 | ||||
| -rw-r--r-- | tests/auto/api/tst_api.cpp | 41 |
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() |
