diff options
| author | Christian Kandeler <christian.kandeler@digia.com> | 2013-11-14 11:14:26 +0100 |
|---|---|---|
| committer | Christian Kandeler <christian.kandeler@digia.com> | 2013-11-20 14:54:32 +0100 |
| commit | 4bdfb786b4120165615cde287e917a802b32d31d (patch) | |
| tree | dc74ed7b704971145e07b411f6309dcfccb8df18 | |
| parent | 9d0a5b9ac770ca69fcb198ceaf02a39f3c72f6a2 (diff) | |
| download | qbs-4bdfb786b4120165615cde287e917a802b32d31d.tar.gz | |
Add API for adding groups and files.
This is a necessary feature for proper IDE support.
Change-Id: I296beb5beae514f70babf3da1b5544726e9df82e
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
23 files changed, 932 insertions, 44 deletions
diff --git a/src/lib/api/api.pri b/src/lib/api/api.pri index 917a54bbe..8a0c93237 100644 --- a/src/lib/api/api.pri +++ b/src/lib/api/api.pri @@ -2,6 +2,7 @@ HEADERS += \ $$PWD/changeset.h \ $$PWD/internaljobs.h \ $$PWD/projectdata.h \ + $$PWD/projectfileupdater.h \ $$PWD/runenvironment.h \ $$PWD/jobs.h \ $$PWD/project.h \ @@ -12,6 +13,7 @@ HEADERS += \ SOURCES += \ $$PWD/changeset.cpp \ $$PWD/internaljobs.cpp \ + $$PWD/projectfileupdater.cpp \ $$PWD/runenvironment.cpp \ $$PWD/projectdata.cpp \ $$PWD/jobs.cpp \ diff --git a/src/lib/api/project.cpp b/src/lib/api/project.cpp index 41d4db29c..39ed4dc27 100644 --- a/src/lib/api/project.cpp +++ b/src/lib/api/project.cpp @@ -32,13 +32,18 @@ #include "jobs.h" #include "projectdata.h" #include "projectdata_p.h" +#include "projectfileupdater.h" #include "propertymap_p.h" #include "runenvironment.h" #include <buildgraph/artifact.h> +#include <buildgraph/buildgraph.h> #include <buildgraph/productbuilddata.h> #include <buildgraph/productinstaller.h> +#include <buildgraph/projectbuilddata.h> +#include <buildgraph/rulesevaluationcontext.h> #include <buildgraph/timestampsupdater.h> #include <language/language.h> +#include <language/projectresolver.h> #include <logging/logger.h> #include <logging/translator.h> #include <tools/cleanoptions.h> @@ -108,6 +113,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; + GroupData findGroupData(const ProductData &product, const QString &groupName) const; + + GroupData createGroupDataFromGroup(const GroupPtr &resolvedGroup); + + void addGroup(const ProductData &product, const QString &groupName); + void addFiles(const ProductData &product, const GroupData &group, const QStringList &filePaths); + void updateInternalCodeLocations(const ResolvedProjectPtr &project, + const CodeLocation &changeLocation, int lineOffset); + void updateExternalCodeLocations(const ProjectData &project, + const CodeLocation &changeLocation, int lineOffset); const TopLevelProjectPtr internalProject; Logger logger; @@ -220,6 +236,235 @@ ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) c return internalProductForProject(internalProject, product); } +ProductData ProjectPrivate::findProductData(const QString &productName) const +{ + foreach (const ProductData &p, m_projectData.allProducts()) { + if (p.name() == productName) + return p; + } + return ProductData(); +} + +GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const +{ + foreach (const GroupData &g, product.groups()) { + if (g.name() == groupName) + return g; + } + return GroupData(); +} + +GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup) +{ + GroupData group; + group.d->name = resolvedGroup->name; + group.d->location = resolvedGroup->location; + foreach (const SourceArtifactConstPtr &sa, resolvedGroup->files) + group.d->filePaths << sa->absoluteFilePath; + if (resolvedGroup->wildcards) { + foreach (const SourceArtifactConstPtr &sa, resolvedGroup->wildcards->files) + group.d->expandedWildcards << sa->absoluteFilePath; + } + qSort(group.d->filePaths); + qSort(group.d->expandedWildcards); + group.d->properties.d->m_map = resolvedGroup->properties; + group.d->isEnabled = resolvedGroup->enabled; + group.d->isValid = true; + return group; +} + +void ProjectPrivate::addGroup(const ProductData &product, const QString &groupName) +{ + if (groupName.isEmpty()) + 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) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + + // Guard against calls with outdated product data. + const ProductData currentProduct= findProductData(product.name()); + QBS_CHECK(currentProduct.isValid()); + + foreach (const GroupPtr &resolvedGroup, resolvedProduct->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); + groupInserter.apply(); + + m_projectData.d.detach(); // The data we already gave out must stay as it is. + + updateInternalCodeLocations(internalProject, groupInserter.itemPosition(), + groupInserter.lineOffset()); + updateExternalCodeLocations(m_projectData, groupInserter.itemPosition(), + groupInserter.lineOffset()); + + GroupPtr resolvedGroup = ResolvedGroup::create(); + resolvedGroup->location = groupInserter.itemPosition(); + resolvedGroup->enabled = true; + resolvedGroup->name = groupName; + resolvedGroup->properties = resolvedProduct->properties; + 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; + } + } +} + +void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + if (!product.isValid()) + throw ErrorInfo(Tr::tr("Product is invalid.")); + const ResolvedProductPtr resolvedProduct = internalProduct(product); + if (!resolvedProduct) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + + const ProductData currentProduct = findProductData(product.name()); + QBS_CHECK(currentProduct.isValid()); + + GroupPtr resolvedGroup; + GroupData currentGroup; + const QString groupName = group.isValid() ? group.name() : product.name(); + foreach (const GroupPtr &g, resolvedProduct->groups) { + if (g->name == groupName) { + resolvedGroup = g; + break; + } + } + if (!resolvedGroup) + throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName)); + currentGroup = findGroupData(currentProduct, groupName); + QBS_CHECK(currentGroup.isValid()); + + if (filePaths.isEmpty()) + return; // "No-op". Could also be defined to be an error, I guess. + + if (!resolvedGroup->prefix.isEmpty()) + throw ErrorInfo(Tr::tr("Cannot add files to a group with a prefix.")); + QStringList absoluteFilePaths; + QString projectFileDir = QFileInfo(product.location().fileName()).dir().absolutePath(); + foreach (const QString &filePath, filePaths) { + const QString absPath = QDir::cleanPath(FileInfo::resolvePath(projectFileDir, + filePath)); + if (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; + } + + // 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) { + if (sa->absoluteFilePath == filePath) { + throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") + .arg(filePath, group.name())); + } + } + } + + ProjectFileFilesAdder adder(currentProduct, group.isValid() ? currentGroup : GroupData(), + absoluteFilePaths); + adder.apply(); + + m_projectData.d.detach(); + updateInternalCodeLocations(internalProject, adder.itemPosition(), adder.lineOffset()); + updateExternalCodeLocations(m_projectData, adder.itemPosition(), adder.lineOffset()); + + QList<SourceArtifactPtr> addedSourceArtifacts; + foreach (const QString &file, 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) { + ArtifactsPerFileTagMap artifactsPerFileTag; + foreach (const SourceArtifactConstPtr &sa, addedSourceArtifacts) { + Artifact * const artifact = createArtifact(resolvedProduct, sa, logger); + foreach (const FileTag &ft, artifact->fileTags) + artifactsPerFileTag[ft] += artifact; + } + RulesEvaluationContextPtr &evalContext + = resolvedProduct->topLevelProject()->buildData->evaluationContext; + evalContext.reset(new RulesEvaluationContext(logger)); + RulesApplicator(resolvedProduct, artifactsPerFileTag, logger).applyAllRules(); + addTargetArtifacts(resolvedProduct, artifactsPerFileTag, logger); + evalContext.clear(); + } + doSanityChecks(internalProject, logger); + currentGroup.d->filePaths << absoluteFilePaths; + qSort(currentGroup.d->filePaths); +} + +static void updateLocationIfNecessary(CodeLocation &location, const CodeLocation &changeLocation, + int lineOffset) +{ + if (location.fileName() == changeLocation.fileName() + && location.line() >= changeLocation.line()) { + location = CodeLocation(location.fileName(), location.line() + lineOffset, + location.column()); + } +} + +void ProjectPrivate::updateInternalCodeLocations(const ResolvedProjectPtr &project, + const CodeLocation &changeLocation, int lineOffset) +{ + updateLocationIfNecessary(project->location, changeLocation, lineOffset); + foreach (const ResolvedProjectPtr &subProject, project->subProjects) + updateInternalCodeLocations(subProject, changeLocation, lineOffset); + foreach (const ResolvedProductPtr &product, project->products) { + updateLocationIfNecessary(product->location, changeLocation, lineOffset); + foreach (const GroupPtr &group, product->groups) + updateLocationIfNecessary(group->location, changeLocation, lineOffset); + foreach (const RulePtr &rule, product->rules) { + updateLocationIfNecessary(rule->script->location, changeLocation, lineOffset); + foreach (const RuleArtifactPtr &artifact, rule->artifacts) { + for (int i = 0; i < artifact->bindings.count(); ++i) { + updateLocationIfNecessary(artifact->bindings[i].location, changeLocation, + lineOffset); + } + } + } + foreach (const ResolvedTransformerConstPtr &transformer, product->transformers) + updateLocationIfNecessary(transformer->transform->location, changeLocation, lineOffset); + foreach (const ResolvedModuleConstPtr &module, product->modules) { + updateLocationIfNecessary(module->setupBuildEnvironmentScript->location, + changeLocation, lineOffset); + updateLocationIfNecessary(module->setupRunEnvironmentScript->location, + changeLocation, lineOffset); + } + } +} + +void ProjectPrivate::updateExternalCodeLocations(const ProjectData &project, + const CodeLocation &changeLocation, int lineOffset) +{ + updateLocationIfNecessary(project.d->location, changeLocation, lineOffset); + foreach (const ProjectData &subProject, project.subProjects()) + updateExternalCodeLocations(subProject, changeLocation, lineOffset); + foreach (const ProductData &product, project.products()) { + updateLocationIfNecessary(product.d->location, changeLocation, lineOffset); + foreach (const GroupData &group, product.groups()) + updateLocationIfNecessary(group.d->location, changeLocation, lineOffset); + } +} + void ProjectPrivate::retrieveProjectData(ProjectData &projectData, const ResolvedProjectConstPtr &internalProject) { @@ -231,23 +476,8 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, product.d->name = resolvedProduct->name; product.d->location = resolvedProduct->location; product.d->isEnabled = resolvedProduct->enabled; - foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) { - GroupData group; - group.d->name = resolvedGroup->name; - group.d->location = resolvedGroup->location; - foreach (const SourceArtifactConstPtr &sa, resolvedGroup->files) - group.d->filePaths << sa->absoluteFilePath; - if (resolvedGroup->wildcards) { - foreach (const SourceArtifactConstPtr &sa, resolvedGroup->wildcards->files) - group.d->expandedWildcards << sa->absoluteFilePath; - } - qSort(group.d->filePaths); - qSort(group.d->expandedWildcards); - group.d->properties.d->m_map = resolvedGroup->properties; - group.d->isEnabled = resolvedGroup->enabled; - group.d->isValid = true; - product.d->groups << group; - } + foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) + product.d->groups << createGroupDataFromGroup(resolvedGroup); if (resolvedProduct->enabled) { QBS_CHECK(resolvedProduct->buildData); foreach (const Artifact * const a, resolvedProduct->buildData->targetArtifacts) { @@ -577,4 +807,45 @@ QSet<QString> Project::buildSystemFiles() const return d->internalProject->buildSystemFiles; } +/*! + * \brief Adds a new empty group to the given product. + * Returns an \c ErrorInfo object for which \c hasError() is false in case of a success + * and true otherwise. In the latter case, the object will have a sensible description. + * 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::addGroup(const ProductData &product, const QString &groupName) +{ + try { + d->addGroup(product, groupName); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure adding group to product.")); + return errorInfo; + } +} + +/*! + * \brief Adds the given files to the given product. + * If \c group is a default-constructed object, the files will be added to the product's + * "files" property, otherwise to the one of \c group. + * The file paths can be absolute or relative to the location of \c product. The project file + * will always contain relative paths. + * 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::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + try { + d->addFiles(product, group, filePaths); + return ErrorInfo(); + } catch (ErrorInfo errorInfo) { + errorInfo.prepend(Tr::tr("Failure adding files to product.")); + return errorInfo; + } +} + } // namespace qbs diff --git a/src/lib/api/project.h b/src/lib/api/project.h index 9e85bc417..11df00a08 100644 --- a/src/lib/api/project.h +++ b/src/lib/api/project.h @@ -49,6 +49,8 @@ class BuildJob; class BuildOptions; class CleanJob; class CleanOptions; +class ErrorInfo; +class GroupData; class ILogSink; class InstallableFile; class InstallJob; @@ -120,6 +122,10 @@ public: QSet<QString> buildSystemFiles() const; + ErrorInfo addGroup(const ProductData &product, const QString &groupName); + ErrorInfo addFiles(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 new file mode 100644 index 000000000..d4c60abad --- /dev/null +++ b/src/lib/api/projectfileupdater.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "projectfileupdater.h" + +#include "projectdata.h" +#include "qmljsrewriter.h" + +#include <language/asttools.h> +#include <logging/translator.h> +#include <parser/qmljsast_p.h> +#include <parser/qmljsastvisitor_p.h> +#include <parser/qmljsengine_p.h> +#include <parser/qmljslexer_p.h> +#include <parser/qmljsparser_p.h> +#include <tools/qbsassert.h> + +#include <QDir> +#include <QFile> +#include <QFileInfo> + +using namespace QbsQmlJS; +using namespace AST; + +namespace qbs { +namespace Internal { + +class ItemFinder : public Visitor +{ +public: + ItemFinder(const CodeLocation &cl) : m_cl(cl), m_item(0) { } + + UiObjectDefinition *item() const { return m_item; } + +private: + bool visit(UiObjectDefinition *ast) + { + if (toCodeLocation(m_cl.fileName(), ast->firstSourceLocation()) == m_cl) { + m_item = ast; + return false; + } + return true; + } + + const CodeLocation m_cl; + UiObjectDefinition *m_item; +}; + +class FilesBindingFinder : public Visitor +{ +public: + FilesBindingFinder(const UiObjectDefinition *startItem) + : m_startItem(startItem), m_binding(0) + { + } + + UiScriptBinding *binding() const { return m_binding; } + +private: + bool visit(UiObjectDefinition *ast) + { + // We start with the direct parent of the binding, so do not descend into any + // other item. + return ast == m_startItem; + } + + bool visit(UiScriptBinding *ast) + { + if (ast->qualifiedId->name.toString() != QLatin1String("files")) + return true; + m_binding = ast; + return false; + } + + const UiObjectDefinition * const m_startItem; + UiScriptBinding *m_binding; +}; + + +ProjectFileUpdater::ProjectFileUpdater(const QString &projectFile) : m_projectFile(projectFile) +{ +} + +void ProjectFileUpdater::apply() +{ + QFile file(m_projectFile); + if (!file.open(QFile::ReadOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for reading: %2") + .arg(m_projectFile, file.errorString())); + } + QString content = QString::fromLocal8Bit(file.readAll()); + file.close(); + Engine engine; + Lexer lexer(&engine); + lexer.setCode(content, 1); + Parser parser(&engine); + if (!parser.parse()) { + QList<DiagnosticMessage> parserMessages = parser.diagnosticMessages(); + if (!parserMessages.isEmpty()) { + ErrorInfo errorInfo; + errorInfo.append(Tr::tr("Failure parsing project file.")); + foreach (const DiagnosticMessage &msg, parserMessages) + errorInfo.append(msg.message, toCodeLocation(file.fileName(), msg.loc)); + throw errorInfo; + } + } + + doApply(content, parser.ast()); + + if (!file.open(QFile::WriteOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for writing: %2") + .arg(m_projectFile, file.errorString())); + } + file.resize(0); + file.write(content.toLocal8Bit()); +} + + +ProjectFileGroupInserter::ProjectFileGroupInserter(const ProductData &product, + const QString &groupName) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_groupName(groupName) +{ +} + +void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) +{ + ItemFinder itemFinder(m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the product item."), + CodeLocation(projectFile())); + } + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + QString groupItemString; + const int productItemIndentation + = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + const int groupItemIndentation = productItemIndentation + 4; + const QString groupItemIndentationString = QString(groupItemIndentation, QLatin1Char(' ')); + groupItemString += groupItemIndentationString + QLatin1String("Group {\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("name: \"") + m_groupName + QLatin1String("\"\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("files: []\n"); + groupItemString += groupItemIndentationString + QLatin1Char('}'); + rewriter.addObject(itemFinder.item()->initializer, groupItemString); + + int lineOffset = 3 + 1; // Our text + a leading newline that is always added by the rewriter. + const QList<ChangeSet::EditOp> &editOps = changeSet.operationList(); + QBS_CHECK(editOps.count() == 1); + const ChangeSet::EditOp &insertOp = editOps.first(); + setLineOffset(lineOffset); + + int insertionLine = fileContent.left(insertOp.pos1).count(QLatin1Char('\n')); + for (int i = 0; i < insertOp.text.count() && insertOp.text.at(i) == QLatin1Char('\n'); ++i) + ++insertionLine; // To account for newlines prepended by the rewriter. + ++insertionLine; // To account for zero-based indexing. + setItemPosition(CodeLocation(projectFile(), insertionLine, + groupItemIndentation + 1)); + changeSet.apply(&fileContent); +} + +ProjectFileFilesAdder::ProjectFileFilesAdder(const ProductData &product, const GroupData &group, + const QStringList &files) + : ProjectFileUpdater(product.location().fileName()) + , m_product(product) + , m_group(group) + , m_files(files) +{ +} + +void ProjectFileFilesAdder::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())); + } + + const int itemIndentation + = itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1; + const int bindingIndentation = itemIndentation + 4; + const int arrayElemIndentation = bindingIndentation + 4; + QString newFilesString; + const QDir projectFileDir = QFileInfo(projectFile()).dir(); + foreach (const QString &absFilePath, m_files) { + newFilesString += QString(arrayElemIndentation, QLatin1Char(' ')); + newFilesString += QLatin1Char('"'); + newFilesString += projectFileDir.relativeFilePath(absFilePath); + newFilesString += QLatin1Char('"'); + newFilesString += QLatin1String(",\n"); + } + newFilesString.chop(2); // Trailing comma and newline. + + // Now get the binding itself. + FilesBindingFinder bindingFinder(itemFinder.item()); + itemFinder.item()->accept(&bindingFinder); + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + + UiScriptBinding * const filesBinding = bindingFinder.binding(); + if (filesBinding) { + if (filesBinding->statement->kind != Node::Kind_ExpressionStatement) + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex.")); // TODO: rename, add new and concat. + const ExpressionStatement * const exprStatement + = static_cast<ExpressionStatement *>(filesBinding->statement); + switch (exprStatement->expression->kind) { + case Node::Kind_ArrayLiteral: { + QString filesString = QLatin1String("[\n"); + const ElementList *elem + = static_cast<ArrayLiteral *>(exprStatement->expression)->elements; + while (elem) { + const quint32 start = elem->expression->firstSourceLocation().offset; + const quint32 end = elem->expression->lastSourceLocation().end(); + const QString elemRepr = fileContent.mid(start, end - start); + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += elemRepr; + filesString += QLatin1String(",\n"); + elem = elem->next; + } + filesString += newFilesString; + filesString += QLatin1Char('\n'); + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + break; + } + case Node::Kind_StringLiteral: { + const QString existingElement + = static_cast<StringLiteral *>(exprStatement->expression)->value.toString(); + QString filesString = QLatin1String("[\n"); + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += QLatin1Char('"') + existingElement + QLatin1Char('"'); + filesString += QLatin1String(",\n"); + filesString += newFilesString; + filesString += QLatin1Char('\n'); + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, QLatin1String("files"), + filesString, Rewriter::ScriptBinding); + break; + } + default: + qDebug("unsupported kind: %d", exprStatement->expression->kind); + // TODO: If it's an identifier, call "concat()". + throw ErrorInfo(Tr::tr("Javascript construct in source file is too complex.")); + } + } else { // Can happen for the product itself, for which the "files" binding is not mandatory. + newFilesString.prepend(QLatin1String("[\n")); + newFilesString += QLatin1Char('\n'); + newFilesString += QString(bindingIndentation, QLatin1Char(' ')); + newFilesString += QLatin1Char(']'); + const QString bindingString = QString(bindingIndentation, QLatin1Char(' ')) + + QLatin1String("files"); + rewriter.addBinding(itemFinder.item()->initializer, bindingString, newFilesString, + 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; + const int insertionColumn = (filesBinding ? arrayElemIndentation : bindingIndentation) + 1; + setItemPosition(CodeLocation(projectFile(), insertionLine, insertionColumn)); + changeSet.apply(&fileContent); +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/api/projectfileupdater.h b/src/lib/api/projectfileupdater.h new file mode 100644 index 000000000..a7ce351a5 --- /dev/null +++ b/src/lib/api/projectfileupdater.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ +#ifndef QBS_PROJECTFILEUPDATER_H +#define QBS_PROJECTFILEUPDATER_H + +#include "projectdata.h" + +#include <tools/error.h> +#include <tools/codelocation.h> + +#include <QStringList> + +namespace QbsQmlJS { namespace AST { class UiProgram; } } + +namespace qbs { +namespace Internal { + +class ProjectFileUpdater +{ +public: + void apply(); + + CodeLocation itemPosition() const { return m_itemPosition; } + int lineOffset() const { return m_lineOffset; } + +protected: + ProjectFileUpdater(const QString &projectFile); + + QString projectFile() const { return m_projectFile; } + + void setLineOffset(int offset) { m_lineOffset = offset; } + void setItemPosition(const CodeLocation &cl) { m_itemPosition = cl; } + +private: + virtual void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) = 0; + + const QString m_projectFile; + CodeLocation m_itemPosition; + int m_lineOffset; +}; + + +class ProjectFileGroupInserter : public ProjectFileUpdater +{ +public: + ProjectFileGroupInserter(const ProductData &product, const QString &groupName); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast); + + const ProductData m_product; + const QString m_groupName; +}; + + +class ProjectFileFilesAdder : public ProjectFileUpdater +{ +public: + ProjectFileFilesAdder(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 + + +#endif // Include guard. diff --git a/src/lib/language/language.h b/src/lib/language/language.h index 334400035..f828b7b33 100644 --- a/src/lib/language/language.h +++ b/src/lib/language/language.h @@ -252,8 +252,8 @@ public: QString name; QStringList moduleDependencies; - ScriptFunctionConstPtr setupBuildEnvironmentScript; - ScriptFunctionConstPtr setupRunEnvironmentScript; + ScriptFunctionPtr setupBuildEnvironmentScript; + ScriptFunctionPtr setupRunEnvironmentScript; private: ResolvedModule() {} @@ -279,13 +279,13 @@ public: static RulePtr create() { return RulePtr(new Rule); } ResolvedModuleConstPtr module; - ScriptFunctionConstPtr script; + ScriptFunctionPtr script; FileTags inputs; FileTags auxiliaryInputs; FileTags usings; FileTags explicitlyDependsOn; bool multiplex; - QList<RuleArtifactConstPtr> artifacts; + QList<RuleArtifactPtr> artifacts; // members that we don't need to save int ruleGraphId; @@ -314,7 +314,7 @@ public: ResolvedModuleConstPtr module; QStringList inputs; QList<SourceArtifactPtr> outputs; - ScriptFunctionConstPtr transform; + ScriptFunctionPtr transform; FileTags explicitlyDependsOn; private: diff --git a/src/lib/language/projectresolver.cpp b/src/lib/language/projectresolver.cpp index 65a874d70..7de84608c 100644 --- a/src/lib/language/projectresolver.cpp +++ b/src/lib/language/projectresolver.cpp @@ -831,11 +831,11 @@ void ProjectResolver::postProcess(const ResolvedProductPtr &product, void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const { foreach (const SourceArtifactPtr &artifact, product->allEnabledFiles()) - applyFileTaggers(artifact, product); + applyFileTaggers(artifact, product, m_logger); } void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product) const + const ResolvedProductConstPtr &product, const Logger &logger) { if (!artifact->overrideFileTags || artifact->fileTags.isEmpty()) { const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); @@ -843,8 +843,8 @@ void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, artifact->fileTags.unite(fileTags); if (artifact->fileTags.isEmpty()) artifact->fileTags.insert(unknownFileTag()); - if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[PR] adding file tags " << artifact->fileTags + if (logger.traceEnabled()) + logger.qbsTrace() << "[PR] adding file tags " << artifact->fileTags << " to " << fileName; } } diff --git a/src/lib/language/projectresolver.h b/src/lib/language/projectresolver.h index 645aeba77..b23f19341 100644 --- a/src/lib/language/projectresolver.h +++ b/src/lib/language/projectresolver.h @@ -61,6 +61,9 @@ public: TopLevelProjectPtr resolve(ModuleLoaderResult &loadResult, const SetupProjectParameters &setupParameters); + static void applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product, const Logger &logger); + private: struct ProjectContext { @@ -105,8 +108,6 @@ private: void resolveProductDependencies(ProjectContext *projectContext); void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; void applyFileTaggers(const ResolvedProductPtr &product) const; - void applyFileTaggers(const SourceArtifactPtr &artifact, - const ResolvedProductConstPtr &product) const; QVariantMap evaluateModuleValues(Item *item) const; void evaluateModuleValues(Item *item, QVariantMap *modulesMap) const; QVariantMap evaluateProperties(Item *item) const; diff --git a/src/lib/lib.qbs b/src/lib/lib.qbs index c45997744..f5d08380d 100644 --- a/src/lib/lib.qbs +++ b/src/lib/lib.qbs @@ -49,6 +49,8 @@ Product { "project.cpp", "projectdata.cpp", "projectdata_p.h", + "projectfileupdater.cpp", + "projectfileupdater.h", "qmljsrewriter.cpp", "qmljsrewriter.h", "propertymap_p.h", diff --git a/src/lib/tools/fileinfo.h b/src/lib/tools/fileinfo.h index f7de2d2a0..1f988c867 100644 --- a/src/lib/tools/fileinfo.h +++ b/src/lib/tools/fileinfo.h @@ -83,11 +83,11 @@ private: }; bool removeFileRecursion(const QFileInfo &f, QString *errorMessage); -bool copyFileRecursion(const QString &sourcePath, const QString &targetPath, bool preserveSymLinks, - QString *errorMessage); -// FIXME: Used by tst_blackbox +// FIXME: Used by tests. bool QBS_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage); +bool QBS_EXPORT copyFileRecursion(const QString &sourcePath, const QString &targetPath, + bool preserveSymLinks, QString *errorMessage); } // namespace Internal } // namespace qbs diff --git a/tests/auto/api/testdata/project-editing/existingfile1.txt b/tests/auto/api/testdata/project-editing/existingfile1.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/existingfile1.txt diff --git a/tests/auto/api/testdata/project-editing/existingfile2.txt b/tests/auto/api/testdata/project-editing/existingfile2.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/existingfile2.txt diff --git a/tests/auto/api/testdata/project-editing/existingfile3.txt b/tests/auto/api/testdata/project-editing/existingfile3.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/existingfile3.txt diff --git a/tests/auto/api/testdata/project-editing/file.cpp b/tests/auto/api/testdata/project-editing/file.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/file.cpp diff --git a/tests/auto/api/testdata/project-editing/file.h b/tests/auto/api/testdata/project-editing/file.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/file.h diff --git a/tests/auto/api/testdata/project-editing/main.cpp b/tests/auto/api/testdata/project-editing/main.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/main.cpp diff --git a/tests/auto/api/testdata/project-editing/newfile1.txt b/tests/auto/api/testdata/project-editing/newfile1.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/newfile1.txt diff --git a/tests/auto/api/testdata/project-editing/newfile2.txt b/tests/auto/api/testdata/project-editing/newfile2.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/newfile2.txt diff --git a/tests/auto/api/testdata/project-editing/newfile3.txt b/tests/auto/api/testdata/project-editing/newfile3.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/newfile3.txt diff --git a/tests/auto/api/testdata/project-editing/newfile4.txt b/tests/auto/api/testdata/project-editing/newfile4.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/api/testdata/project-editing/newfile4.txt diff --git a/tests/auto/api/testdata/project-editing/project.qbs b/tests/auto/api/testdata/project-editing/project.qbs new file mode 100644 index 000000000..1d7704fe6 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/project.qbs @@ -0,0 +1,21 @@ +import qbs + +CppApplication { + Group { + name: "Existing Group 1" + files: ["existingfile1.txt"] + } + property string aFile: "existingfile2.txt" + Group { + name: "Existing Group 2" + files: product.aFile + } + Group { + name: "Existing Group 3" + files: { + var file = "existingfile3.txt"; + return file; + } + } + files: "main.cpp" +} diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp index a60d4795e..1e1e883d3 100644 --- a/tests/auto/api/tst_api.cpp +++ b/tests/auto/api/tst_api.cpp @@ -35,7 +35,9 @@ #include <api/project.h> #include <api/projectdata.h> #include <logging/ilogsink.h> +#include <tools/fileinfo.h> #include <tools/hostosinfo.h> +#include <tools/buildoptions.h> #include <tools/installoptions.h> #include <tools/preferences.h> #include <tools/setupprojectparameters.h> @@ -44,6 +46,7 @@ #include <QDir> #include <QEventLoop> #include <QScopedPointer> +#include <QStringList> #include <QTest> class LogSink: public qbs::ILogSink @@ -54,7 +57,22 @@ class LogSink: public qbs::ILogSink void doPrintMessage(qbs::LoggerLevel, const QString &, const QString &) { } }; -TestApi::TestApi() : m_logSink(new LogSink) +class BuildDescriptionReveiver : public QObject +{ + Q_OBJECT +public: + QString descriptions; + +private slots: + void handleDescription(const QString &, const QString &description) { + descriptions += description; + } +}; + +TestApi::TestApi() + : m_logSink(new LogSink) + , m_sourceDataDir(QDir::cleanPath(SRCDIR "/testdata")) + , m_workingDataDir(QCoreApplication::applicationDirPath() + "/../tests/auto/api/testWorkDir") { } @@ -63,6 +81,14 @@ TestApi::~TestApi() delete m_logSink; } +void TestApi::initTestCase() +{ + QString errorMessage; + qbs::Internal::removeDirectoryWithContents(m_workingDataDir, &errorMessage); + QVERIFY2(qbs::Internal::copyFileRecursion(m_sourceDataDir, + m_workingDataDir, false, &errorMessage), qPrintable(errorMessage)); +} + static void waitForFinished(qbs::AbstractJob *job) { QEventLoop loop; @@ -70,11 +96,160 @@ static void waitForFinished(qbs::AbstractJob *job) loop.exec(); } + +void printProjectData(const qbs::ProjectData &project) +{ + foreach (const qbs::ProductData &p, project.products()) { + qDebug(" Product '%s' at %s", qPrintable(p.name()), qPrintable(p.location().toString())); + foreach (const qbs::GroupData &g, p.groups()) { + qDebug(" Group '%s' at %s", qPrintable(g.name()), qPrintable(g.location().toString())); + qDebug(" Files: %s", qPrintable(g.filePaths().join(QLatin1String(", ")))); + } + } +} + +qbs::GroupData findGroup(const qbs::ProductData &product, const QString &name) +{ + foreach (const qbs::GroupData &g, product.groups()) { + if (g.name() == name) + return g; + } + return qbs::GroupData(); +} + +void TestApi::changeContent() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters(); + setupParams.setProjectFilePath(QDir::cleanPath(m_workingDataDir + + "/project-editing/project.qbs")); + QScopedPointer<qbs::SetupProjectJob> job(qbs::Project::setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.data()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::Project project = job->project(); + qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.allProducts().count(), 1); + qbs::ProductData product = projectData.allProducts().first(); + QCOMPARE(product.groups().count(), 4); + + // Error handling: Invalid product. + qbs::ErrorInfo errorInfo = project.addGroup(qbs::ProductData(), "blubb"); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("invalid")); + + // Error handling: Empty group name. + errorInfo = project.addGroup(product, QString()); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("empty")); + + errorInfo = project.addGroup(product, "New Group 1"); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + + errorInfo = project.addGroup(product, "New Group 2"); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + + // Error handling: Group already inserted. + errorInfo = project.addGroup(product, "New Group 1"); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("already")); + + // Error handling: Add list of files with double entries. + errorInfo = project.addFiles(product, qbs::GroupData(), QStringList() << "file.cpp" + << "file.cpp"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("more than once"), qPrintable(errorInfo.toString())); + + // Add files to empty array literal. + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + QCOMPARE(product.groups().count(), 6); + qbs::GroupData group = findGroup(product, "New Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "file.h" << "file.cpp"); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + + // Error handling: Add the same file again. + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + QCOMPARE(product.groups().count(), 6); + group = findGroup(product, "New Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "file.cpp"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("already"), qPrintable(errorInfo.toString())); + + // Add file to non-empty array literal. + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + group = findGroup(product, "Existing Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile1.txt"); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + + // Add files to list represented as a single string. + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + errorInfo = project.addFiles(product, qbs::GroupData(), QStringList() << "newfile2.txt"); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + + // Add files to list represented as an identifier (not yet implmented). + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + group = findGroup(product, "Existing Group 2"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile3.txt"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("complex"), qPrintable(errorInfo.toString())); + + // Add files to list represented as a block of code (not yet implemented). + projectData = project.projectData(); + QVERIFY(projectData.products().count() == 1); + product = projectData.products().first(); + group = findGroup(product, "Existing Group 3"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile4.txt"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("complex"), qPrintable(errorInfo.toString())); + + // Check whether building will take the newly added cpp file 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)); + 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")); + + // Now check whether the data updates were done correctly. + projectData = project.projectData(); + job.reset(qbs::Project::setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.data()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::ProjectData newProjectData = job->project().projectData(); + const bool projectDataMatches = newProjectData == projectData; + if (!projectDataMatches) { + qDebug("This is the assumed project:"); + printProjectData(projectData); + qDebug("This is the actual project:"); + printProjectData(newProjectData); + } + QVERIFY(projectDataMatches); // Will fail if e.g. code locations don't match. +} + void TestApi::disabledInstallGroup() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); - setupParams.setProjectFilePath(QDir::cleanPath(QLatin1String(SRCDIR "/testdata" - "/disabled_install_group/project.qbs"))); + setupParams.setProjectFilePath(QDir::cleanPath(m_workingDataDir + + "/disabled_install_group/project.qbs")); QScopedPointer<qbs::SetupProjectJob> job(qbs::Project::setupProject(setupParams, m_logSink, 0)); waitForFinished(job.data()); @@ -95,8 +270,8 @@ void TestApi::disabledInstallGroup() void TestApi::fileTagsFilterOverride() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); - setupParams.setProjectFilePath(QDir::cleanPath(QLatin1String(SRCDIR "/testdata" - "/filetagsfilter_override/project.qbs"))); + setupParams.setProjectFilePath(QDir::cleanPath(m_workingDataDir + + "/filetagsfilter_override/project.qbs")); QScopedPointer<qbs::SetupProjectJob> job(qbs::Project::setupProject(setupParams, m_logSink, 0)); waitForFinished(job.data()); @@ -176,7 +351,7 @@ void TestApi::nonexistingProjectPropertyFromProduct() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); const QString projectDir - = QDir::cleanPath(QLatin1String(SRCDIR "/testdata/nonexistingprojectproperties")); + = QDir::cleanPath(m_workingDataDir + "/nonexistingprojectproperties"); const QString topLevelProjectFile = projectDir + QLatin1String("/invalidaccessfromproduct.qbs"); setupParams.setProjectFilePath(topLevelProjectFile); QScopedPointer<qbs::SetupProjectJob> job(qbs::Project::setupProject(setupParams, @@ -191,8 +366,7 @@ void TestApi::nonexistingProjectPropertyFromProduct() void TestApi::nonexistingProjectPropertyFromCommandLine() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); - const QString projectDir - = QDir::cleanPath(QLatin1String(SRCDIR "/testdata/nonexistingprojectproperties")); + const QString projectDir = QDir::cleanPath(m_workingDataDir + "/nonexistingprojectproperties"); const QString topLevelProjectFile = projectDir + QLatin1String("/project.qbs"); setupParams.setProjectFilePath(topLevelProjectFile); QVariantMap projectProperties; @@ -227,3 +401,5 @@ qbs::SetupProjectParameters TestApi::defaultSetupParameters() const } QTEST_MAIN(TestApi) + +#include "tst_api.moc" diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h index 707ccf85e..ffeff6922 100644 --- a/tests/auto/api/tst_api.h +++ b/tests/auto/api/tst_api.h @@ -32,10 +32,9 @@ #include <QObject> -namespace qbs { -class ILogSink; -class SetupProjectParameters; -} +namespace qbs { class SetupProjectParameters; } + +class LogSink; class TestApi : public QObject { @@ -46,6 +45,9 @@ public: ~TestApi(); private slots: + void initTestCase(); + + void changeContent(); void disabledInstallGroup(); void fileTagsFilterOverride(); void installableFiles(); @@ -56,7 +58,9 @@ private slots: private: qbs::SetupProjectParameters defaultSetupParameters() const; - qbs::ILogSink * const m_logSink; + LogSink * const m_logSink; + const QString m_sourceDataDir; + const QString m_workingDataDir; }; #endif // Include guard. |
