1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 #include "project.h"
40 #include "project_p.h"
41 
42 #include "internaljobs.h"
43 #include "jobs.h"
44 #include "projectdata_p.h"
45 #include "projectfileupdater.h"
46 #include "propertymap_p.h"
47 #include "rulecommand_p.h"
48 #include "runenvironment.h"
49 #include "transformerdata_p.h"
50 #include <buildgraph/artifact.h>
51 #include <buildgraph/buildgraph.h>
52 #include <buildgraph/buildgraphloader.h>
53 #include <buildgraph/emptydirectoriesremover.h>
54 #include <buildgraph/nodetreedumper.h>
55 #include <buildgraph/productbuilddata.h>
56 #include <buildgraph/productinstaller.h>
57 #include <buildgraph/projectbuilddata.h>
58 #include <buildgraph/rulecommands.h>
59 #include <buildgraph/rulesevaluationcontext.h>
60 #include <buildgraph/rulesapplicator.h>
61 #include <buildgraph/timestampsupdater.h>
62 #include <buildgraph/transformer.h>
63 #include <language/language.h>
64 #include <language/projectresolver.h>
65 #include <language/propertymapinternal.h>
66 #include <logging/logger.h>
67 #include <logging/translator.h>
68 #include <tools/cleanoptions.h>
69 #include <tools/error.h>
70 #include <tools/fileinfo.h>
71 #include <tools/installoptions.h>
72 #include <tools/preferences.h>
73 #include <tools/processresult.h>
74 #include <tools/qbspluginmanager.h>
75 #include <tools/scripttools.h>
76 #include <tools/setupprojectparameters.h>
77 #include <tools/stringconstants.h>
78 #include <tools/qbsassert.h>
79 #include <tools/qttools.h>
80 
81 #include <QtCore/qdir.h>
82 #include <QtCore/qregularexpression.h>
83 #include <QtCore/qshareddata.h>
84 
85 #include <mutex>
86 #include <utility>
87 #include <vector>
88 
89 namespace qbs {
90 namespace Internal {
91 
92 static bool pluginsLoaded = false;
93 static std::mutex pluginsLoadedMutex;
94 
loadPlugins(const QStringList & _pluginPaths,const Logger & logger)95 static void loadPlugins(const QStringList &_pluginPaths, const Logger &logger)
96 {
97     std::lock_guard<std::mutex> locker(pluginsLoadedMutex);
98     if (pluginsLoaded)
99         return;
100 
101     std::vector<std::string> pluginPaths;
102     for (const QString &pluginPath : _pluginPaths) {
103         if (!FileInfo::exists(pluginPath)) {
104 #ifndef QBS_STATIC_LIB
105             logger.qbsWarning() << Tr::tr("Plugin path '%1' does not exist.")
106                                     .arg(QDir::toNativeSeparators(pluginPath));
107 #endif
108         } else {
109             pluginPaths.push_back(pluginPath.toStdString());
110         }
111     }
112     auto pluginManager = QbsPluginManager::instance();
113     pluginManager->loadStaticPlugins();
114     pluginManager->loadPlugins(pluginPaths, logger);
115 
116     qRegisterMetaType<ErrorInfo>("qbs::ErrorInfo");
117     qRegisterMetaType<ProcessResult>("qbs::ProcessResult");
118     qRegisterMetaType<InternalJob *>("Internal::InternalJob *");
119     qRegisterMetaType<AbstractJob *>("qbs::AbstractJob *");
120     pluginsLoaded = true;
121 }
122 
projectData()123 ProjectData ProjectPrivate::projectData()
124 {
125     m_projectData = ProjectData();
126     retrieveProjectData(m_projectData, internalProject);
127     m_projectData.d->buildDir = internalProject->buildDirectory;
128     return m_projectData;
129 }
130 
addDependencies(QVector<ResolvedProductPtr> & products)131 static void addDependencies(QVector<ResolvedProductPtr> &products)
132 {
133     for (int i = 0; i < products.size(); ++i) {
134         const ResolvedProductPtr &product = products.at(i);
135         for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) {
136             if (!products.contains(dependency))
137                 products.push_back(dependency);
138         }
139     }
140 }
141 
buildProducts(const QVector<ResolvedProductPtr> & products,const BuildOptions & options,bool needsDepencencyResolving,QObject * jobOwner)142 BuildJob *ProjectPrivate::buildProducts(const QVector<ResolvedProductPtr> &products,
143                                         const BuildOptions &options, bool needsDepencencyResolving,
144                                         QObject *jobOwner)
145 {
146     QVector<ResolvedProductPtr> productsToBuild = products;
147     if (needsDepencencyResolving)
148         addDependencies(productsToBuild);
149 
150     const auto job = new BuildJob(logger, jobOwner);
151     job->build(internalProject, productsToBuild, options);
152     QBS_ASSERT(job->state() == AbstractJob::StateRunning,);
153     return job;
154 }
155 
cleanProducts(const QVector<ResolvedProductPtr> & products,const CleanOptions & options,QObject * jobOwner)156 CleanJob *ProjectPrivate::cleanProducts(const QVector<ResolvedProductPtr> &products,
157         const CleanOptions &options, QObject *jobOwner)
158 {
159     const auto job = new CleanJob(logger, jobOwner);
160     job->clean(internalProject, products, options);
161     QBS_ASSERT(job->state() == AbstractJob::StateRunning,);
162     return job;
163 }
164 
installProducts(const QVector<ResolvedProductPtr> & products,const InstallOptions & options,bool needsDepencencyResolving,QObject * jobOwner)165 InstallJob *ProjectPrivate::installProducts(const QVector<ResolvedProductPtr> &products,
166         const InstallOptions &options, bool needsDepencencyResolving, QObject *jobOwner)
167 {
168     QVector<ResolvedProductPtr> productsToInstall = products;
169     if (needsDepencencyResolving)
170         addDependencies(productsToInstall);
171     const auto job = new InstallJob(logger, jobOwner);
172     job->install(internalProject, productsToInstall, options);
173     QBS_ASSERT(job->state() == AbstractJob::StateRunning,);
174     return job;
175 }
176 
internalProducts(const QList<ProductData> & products) const177 QVector<ResolvedProductPtr> ProjectPrivate::internalProducts(const QList<ProductData> &products) const
178 {
179     QVector<ResolvedProductPtr> internalProducts;
180     for (const ProductData &product : products) {
181         if (product.isEnabled())
182             internalProducts.push_back(internalProduct(product));
183     }
184     return internalProducts;
185 }
186 
enabledInternalProducts(const ResolvedProjectConstPtr & project,bool includingNonDefault)187 static QVector<ResolvedProductPtr> enabledInternalProducts(const ResolvedProjectConstPtr &project,
188                                                          bool includingNonDefault)
189 {
190     QVector<ResolvedProductPtr> products;
191     for (const ResolvedProductPtr &p : project->products) {
192         if (p->enabled && (includingNonDefault || p->builtByDefault()))
193             products.push_back(p);
194     }
195     for (const auto &subProject : qAsConst(project->subProjects))
196         products << enabledInternalProducts(subProject, includingNonDefault);
197     return products;
198 }
199 
allEnabledInternalProducts(bool includingNonDefault) const200 QVector<ResolvedProductPtr> ProjectPrivate::allEnabledInternalProducts(
201         bool includingNonDefault) const
202 {
203     return enabledInternalProducts(internalProject, includingNonDefault);
204 }
205 
matches(const ProductData & product,const ResolvedProductConstPtr & rproduct)206 static bool matches(const ProductData &product, const ResolvedProductConstPtr &rproduct)
207 {
208     return product.name() == rproduct->name
209             && product.multiplexConfigurationId() == rproduct->multiplexConfigurationId;
210 }
211 
internalProductForProject(const ResolvedProjectConstPtr & project,const ProductData & product)212 static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPtr &project,
213                                                     const ProductData &product)
214 {
215     for (const ResolvedProductPtr &resolvedProduct : project->products) {
216         if (matches(product, resolvedProduct))
217             return resolvedProduct;
218     }
219     for (const auto &subProject : qAsConst(project->subProjects)) {
220         const ResolvedProductPtr &p = internalProductForProject(subProject, product);
221         if (p)
222             return p;
223     }
224     return {};
225 }
226 
internalProduct(const ProductData & product) const227 ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) const
228 {
229     return internalProductForProject(internalProject, product);
230 }
231 
findProductData(const ProductData & product) const232 ProductData ProjectPrivate::findProductData(const ProductData &product) const
233 {
234     for (const ProductData &p : m_projectData.allProducts()) {
235         if (p.name() == product.name()
236                 && p.profile() == product.profile()
237                 && p.multiplexConfigurationId() == product.multiplexConfigurationId()) {
238             return p;
239         }
240     }
241     return {};
242 }
243 
findProductsByName(const QString & name) const244 QList<ProductData> ProjectPrivate::findProductsByName(const QString &name) const
245 {
246     QList<ProductData> list;
247     for (const ProductData &p : m_projectData.allProducts()) {
248         if (p.name() == name)
249             list.push_back(p);
250     }
251     return list;
252 }
253 
findGroupData(const ProductData & product,const QString & groupName) const254 GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const
255 {
256     for (const GroupData &g : product.groups()) {
257         if (g.name() == groupName)
258             return g;
259     }
260     return {};
261 }
262 
createGroupDataFromGroup(const GroupPtr & resolvedGroup,const ResolvedProductConstPtr & product)263 GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup,
264                                                    const ResolvedProductConstPtr &product)
265 {
266     GroupData group;
267     group.d->name = resolvedGroup->name;
268     group.d->prefix = resolvedGroup->prefix;
269     group.d->location = resolvedGroup->location;
270     for (const auto &sa : resolvedGroup->files) {
271         ArtifactData artifact = createApiSourceArtifact(sa);
272         setupInstallData(artifact, product);
273         group.d->sourceArtifacts.push_back(artifact);
274     }
275     if (resolvedGroup->wildcards) {
276         for (const auto &sa : resolvedGroup->wildcards->files) {
277             ArtifactData artifact = createApiSourceArtifact(sa);
278             setupInstallData(artifact, product);
279             group.d->sourceArtifactsFromWildcards.push_back(artifact);
280         }
281     }
282     std::sort(group.d->sourceArtifacts.begin(),
283               group.d->sourceArtifacts.end());
284     std::sort(group.d->sourceArtifactsFromWildcards.begin(),
285               group.d->sourceArtifactsFromWildcards.end());
286     group.d->properties.d->m_map = resolvedGroup->properties;
287     group.d->isEnabled = resolvedGroup->enabled;
288     group.d->isValid = true;
289     return group;
290 }
291 
createApiSourceArtifact(const SourceArtifactConstPtr & sa)292 ArtifactData ProjectPrivate::createApiSourceArtifact(const SourceArtifactConstPtr &sa)
293 {
294     ArtifactData saApi;
295     saApi.d->isValid = true;
296     saApi.d->filePath = sa->absoluteFilePath;
297     saApi.d->fileTags = sa->fileTags.toStringList();
298     saApi.d->isGenerated = false;
299     saApi.d->isTargetArtifact = false;
300     saApi.d->properties.d->m_map = sa->properties;
301     return saApi;
302 }
303 
createArtifactData(const Artifact * artifact,const ResolvedProductConstPtr & product,const ArtifactSet & targetArtifacts)304 ArtifactData ProjectPrivate::createArtifactData(const Artifact *artifact,
305         const ResolvedProductConstPtr &product, const ArtifactSet &targetArtifacts)
306 {
307     ArtifactData ta;
308     ta.d->filePath = artifact->filePath();
309     ta.d->fileTags = artifact->fileTags().toStringList();
310     ta.d->properties.d->m_map = artifact->properties;
311     ta.d->isGenerated = artifact->artifactType == Artifact::Generated;
312     ta.d->isTargetArtifact = targetArtifacts.contains(const_cast<Artifact *>(artifact));
313     ta.d->isValid = true;
314     setupInstallData(ta, product);
315     return ta;
316 }
317 
setupInstallData(ArtifactData & artifact,const ResolvedProductConstPtr & product)318 void ProjectPrivate::setupInstallData(ArtifactData &artifact,
319                                       const ResolvedProductConstPtr &product)
320 {
321     artifact.d->installData.d->isValid = true;
322     artifact.d->installData.d->isInstallable = artifact.properties().getModuleProperty(
323                 StringConstants::qbsModule(), StringConstants::installProperty()).toBool();
324     if (!artifact.d->installData.d->isInstallable)
325         return;
326     const QString installRoot = artifact.properties().getModuleProperty(
327                 StringConstants::qbsModule(), StringConstants::installRootProperty()).toString();
328     InstallOptions options;
329     options.setInstallRoot(installRoot);
330     artifact.d->installData.d->installRoot = installRoot;
331     try {
332         QString installFilePath = ProductInstaller::targetFilePath(product->topLevelProject(),
333                 product->sourceDirectory, artifact.filePath(), artifact.properties().d->m_map,
334                 options);
335         if (!installRoot.isEmpty())
336             installFilePath.remove(0, installRoot.size());
337         artifact.d->installData.d->installFilePath = installFilePath;
338     } catch (const ErrorInfo &e) {
339         logger.printWarning(e);
340     }
341 }
342 
addGroup(const ProductData & product,const QString & groupName)343 void ProjectPrivate::addGroup(const ProductData &product, const QString &groupName)
344 {
345     if (groupName.isEmpty())
346         throw ErrorInfo(Tr::tr("Group has an empty name."));
347     if (!product.isValid())
348         throw ErrorInfo(Tr::tr("Product is invalid."));
349     QList<ProductData> products = findProductsByName(product.name());
350     if (products.empty())
351         throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name()));
352     const auto resolvedProducts = internalProducts(products);
353     QBS_CHECK(products.size() == resolvedProducts.size());
354 
355     for (const GroupPtr &resolvedGroup : resolvedProducts.front()->groups) {
356         if (resolvedGroup->name == groupName) {
357             throw ErrorInfo(Tr::tr("Group '%1' already exists in product '%2'.")
358                             .arg(groupName, product.name()), resolvedGroup->location);
359         }
360     }
361 
362     ProjectFileGroupInserter groupInserter(products.front(), groupName);
363     groupInserter.apply();
364 }
365 
getGroupContext(const ProductData & product,const GroupData & group)366 ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const ProductData &product,
367                                                                    const GroupData &group)
368 {
369     GroupUpdateContext context;
370     if (!product.isValid())
371         throw ErrorInfo(Tr::tr("Product is invalid."));
372     context.products = findProductsByName(product.name());
373     if (context.products.empty())
374         throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name()));
375     context.resolvedProducts = internalProducts(context.products);
376 
377     const QString groupName = group.isValid() ? group.name() : product.name();
378     for (const ResolvedProductPtr &p : qAsConst(context.resolvedProducts)) {
379         for (const GroupPtr &g : p->groups) {
380             if (g->name == groupName) {
381                 context.resolvedGroups << g;
382                 break;
383             }
384         }
385     }
386     if (context.resolvedGroups.empty())
387         throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName));
388     for (const ProductData &p : qAsConst(context.products)) {
389         const GroupData &g = findGroupData(p, groupName);
390         QBS_CHECK(p.isValid());
391         context.groups << g;
392     }
393     QBS_CHECK(context.resolvedProducts.size() == context.products.size());
394     QBS_CHECK(context.resolvedProducts.size() == context.resolvedGroups.size());
395     QBS_CHECK(context.products.size() == context.groups.size());
396     return context;
397 }
398 
matchesWildcard(const QString & filePath,const GroupConstPtr & group)399 static bool matchesWildcard(const QString &filePath, const GroupConstPtr &group)
400 {
401     if (!group->wildcards)
402         return false;
403     for (const QString &pattern : qAsConst(group->wildcards->patterns)) {
404         QString fullPattern;
405         if (QFileInfo(group->prefix).isAbsolute()) {
406             fullPattern = group->prefix;
407         } else {
408             fullPattern = QFileInfo(group->location.filePath()).absolutePath()
409                     + QLatin1Char('/') + group->prefix;
410         }
411         fullPattern.append(QLatin1Char('/')).append(pattern);
412         fullPattern = QDir::cleanPath(fullPattern);
413         if (QRegularExpression(QRegularExpression::wildcardToRegularExpression(fullPattern)).match(filePath).hasMatch())
414             return true;
415     }
416     return false;
417 }
418 
getFileListContext(const ProductData & product,const GroupData & group,const QStringList & filePaths,bool forAdding)419 ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const ProductData &product,
420         const GroupData &group, const QStringList &filePaths, bool forAdding)
421 {
422     FileListUpdateContext filesContext;
423     GroupUpdateContext &groupContext = filesContext.groupContext;
424     groupContext = getGroupContext(product, group);
425 
426     if (filePaths.empty())
427         throw ErrorInfo(Tr::tr("No files supplied."));
428 
429     QString prefix;
430     for (int i = 0; i < groupContext.resolvedGroups.size(); ++i) {
431         const GroupPtr &g = groupContext.resolvedGroups.at(i);
432         if (!g->prefix.isEmpty() && !g->prefix.endsWith(QLatin1Char('/')))
433             throw ErrorInfo(Tr::tr("Group has non-directory prefix."));
434         if (i == 0)
435             prefix = g->prefix;
436         else if (prefix != g->prefix)
437             throw ErrorInfo(Tr::tr("Cannot update: Group prefix depends on properties."));
438     }
439     QString baseDirPath = QFileInfo(product.location().filePath()).dir().absolutePath()
440             + QLatin1Char('/') + prefix;
441     QDir baseDir(baseDirPath);
442     for (const QString &filePath : filePaths) {
443         const QString absPath = QDir::cleanPath(FileInfo::resolvePath(baseDirPath, filePath));
444         if (filesContext.absoluteFilePaths.contains(absPath))
445             throw ErrorInfo(Tr::tr("File '%1' appears more than once.").arg(absPath));
446         if (forAdding && !FileInfo(absPath).exists())
447             throw ErrorInfo(Tr::tr("File '%1' does not exist.").arg(absPath));
448         if (matchesWildcard(absPath, groupContext.resolvedGroups.front())) {
449             filesContext.absoluteFilePathsFromWildcards << absPath;
450         } else {
451             filesContext.absoluteFilePaths << absPath;
452             filesContext.relativeFilePaths << baseDir.relativeFilePath(absPath);
453         }
454     }
455 
456     return filesContext;
457 }
458 
addFiles(const ProductData & product,const GroupData & group,const QStringList & filePaths)459 void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group,
460                               const QStringList &filePaths)
461 {
462     FileListUpdateContext filesContext = getFileListContext(product, group, filePaths, true);
463     GroupUpdateContext &groupContext = filesContext.groupContext;
464 
465     // We do not check for entries in other groups, because such doublettes might be legitimate
466     // due to conditions.
467     for (const GroupPtr &group : qAsConst(groupContext.resolvedGroups)) {
468         for (const QString &filePath : qAsConst(filesContext.absoluteFilePaths)) {
469             for (const auto &sa : group->files) {
470                 if (sa->absoluteFilePath == filePath) {
471                     throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.")
472                                     .arg(filePath, group->name));
473                 }
474             }
475         }
476     }
477 
478     ProjectFileFilesAdder adder(groupContext.products.front(),
479             group.isValid() ? groupContext.groups.front() : GroupData(),
480             filesContext.relativeFilePaths);
481     adder.apply();
482 }
483 
removeFiles(const ProductData & product,const GroupData & group,const QStringList & filePaths)484 void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group,
485                                  const QStringList &filePaths)
486 {
487     FileListUpdateContext filesContext = getFileListContext(product, group, filePaths, false);
488     GroupUpdateContext &groupContext = filesContext.groupContext;
489 
490     if (!filesContext.absoluteFilePathsFromWildcards.empty()) {
491         throw ErrorInfo(Tr::tr("The following files cannot be removed from the project file, "
492                                "because they match wildcard patterns: %1")
493                 .arg(filesContext.absoluteFilePathsFromWildcards.join(QLatin1String(", "))));
494     }
495     QStringList filesNotFound = filesContext.absoluteFilePaths;
496     std::vector<SourceArtifactPtr> sourceArtifacts;
497     for (const SourceArtifactPtr &sa : groupContext.resolvedGroups.front()->files) {
498         if (filesNotFound.removeOne(sa->absoluteFilePath))
499             sourceArtifacts << sa;
500     }
501     if (!filesNotFound.empty()) {
502         throw ErrorInfo(Tr::tr("The following files are not known to qbs: %1")
503                         .arg(filesNotFound.join(QLatin1String(", "))));
504     }
505 
506     ProjectFileFilesRemover remover(groupContext.products.front(),
507             group.isValid() ? groupContext.groups.front() : GroupData(),
508             filesContext.relativeFilePaths);
509     remover.apply();
510 }
511 
removeGroup(const ProductData & product,const GroupData & group)512 void ProjectPrivate::removeGroup(const ProductData &product, const GroupData &group)
513 {
514     GroupUpdateContext context = getGroupContext(product, group);
515     ProjectFileGroupRemover remover(context.products.front(), context.groups.front());
516     remover.apply();
517 
518 }
519 
prepareChangeToProject()520 void ProjectPrivate::prepareChangeToProject()
521 {
522     if (internalProject->locked)
523         throw ErrorInfo(Tr::tr("A job is currently in progress."));
524     if (!m_projectData.isValid())
525         retrieveProjectData(m_projectData, internalProject);
526 }
527 
ruleCommandListForTransformer(const Transformer * transformer)528 RuleCommandList ProjectPrivate::ruleCommandListForTransformer(const Transformer *transformer)
529 {
530     RuleCommandList list;
531     for (const AbstractCommandPtr &internalCommand : qAsConst(transformer->commands.commands())) {
532         RuleCommand externalCommand;
533         externalCommand.d->description = internalCommand->description();
534         externalCommand.d->extendedDescription = internalCommand->extendedDescription();
535         switch (internalCommand->type()) {
536         case AbstractCommand::JavaScriptCommandType: {
537             externalCommand.d->type = RuleCommand::JavaScriptCommandType;
538             const JavaScriptCommandPtr &jsCmd
539                     = std::static_pointer_cast<JavaScriptCommand>(internalCommand);
540             externalCommand.d->sourceCode = jsCmd->sourceCode();
541             break;
542         }
543         case AbstractCommand::ProcessCommandType: {
544             externalCommand.d->type = RuleCommand::ProcessCommandType;
545             const ProcessCommandPtr &procCmd
546                     = std::static_pointer_cast<ProcessCommand>(internalCommand);
547             externalCommand.d->executable = procCmd->program();
548             externalCommand.d->arguments = procCmd->arguments();
549             externalCommand.d->workingDir = procCmd->workingDir();
550             externalCommand.d->environment = procCmd->environment();
551             break;
552         }
553         }
554         list << externalCommand;
555     }
556     return list;
557 }
558 
ruleCommands(const ProductData & product,const QString & inputFilePath,const QString & outputFileTag)559 RuleCommandList ProjectPrivate::ruleCommands(const ProductData &product,
560         const QString &inputFilePath, const QString &outputFileTag)
561 {
562     if (internalProject->locked)
563         throw ErrorInfo(Tr::tr("A job is currently in progress."));
564     const ResolvedProductConstPtr resolvedProduct = internalProduct(product);
565     if (!resolvedProduct)
566         throw ErrorInfo(Tr::tr("No such product '%1'.").arg(product.name()));
567     if (!resolvedProduct->enabled)
568         throw ErrorInfo(Tr::tr("Product '%1' is disabled.").arg(product.name()));
569     QBS_CHECK(resolvedProduct->buildData);
570     const ArtifactSet &outputArtifacts = resolvedProduct->buildData->artifactsByFileTag()
571             .value(FileTag(outputFileTag.toLocal8Bit()));
572     for (const Artifact * const outputArtifact : qAsConst(outputArtifacts)) {
573         const TransformerConstPtr transformer = outputArtifact->transformer;
574         if (!transformer)
575             continue;
576         for (const Artifact * const inputArtifact : qAsConst(transformer->inputs)) {
577             if (inputArtifact->filePath() == inputFilePath)
578                 return ruleCommandListForTransformer(transformer.get());
579         }
580     }
581 
582     throw ErrorInfo(Tr::tr("No rule was found that produces an artifact tagged '%1' "
583                            "from input file '%2'.").arg(outputFileTag, inputFilePath));
584 }
585 
transformerData()586 ProjectTransformerData ProjectPrivate::transformerData()
587 {
588     if (!m_projectData.isValid())
589         retrieveProjectData(m_projectData, internalProject);
590     ProjectTransformerData projectTransformerData;
591     for (const ProductData &productData : m_projectData.allProducts()) {
592         if (!productData.isEnabled())
593             continue;
594         const ResolvedProductConstPtr product = internalProduct(productData);
595         QBS_ASSERT(!!product, continue);
596         QBS_ASSERT(!!product->buildData, continue);
597         const ArtifactSet targetArtifacts = product->targetArtifacts();
598         Set<const Transformer *> allTransformers;
599         for (const Artifact * const a : TypeFilter<Artifact>(product->buildData->allNodes())) {
600             if (a->artifactType == Artifact::Generated)
601                 allTransformers.insert(a->transformer.get());
602         }
603         if (allTransformers.empty())
604             continue;
605         ProductTransformerData productTransformerData;
606         for (const Transformer * const t : allTransformers) {
607             TransformerData tData;
608             Set<const Artifact *> allInputs;
609             for (Artifact * const a : t->outputs) {
610                 tData.d->outputs << createArtifactData(a, product, targetArtifacts);
611                 for (const Artifact * const child : filterByType<Artifact>(a->children))
612                     allInputs << child;
613                 for (Artifact * const a
614                      : RulesApplicator::collectAuxiliaryInputs(t->rule.get(), product.get())) {
615                     if (a->artifactType == Artifact::Generated)
616                         tData.d->inputs << createArtifactData(a, product, targetArtifacts);
617                 }
618             }
619             for (const Artifact * const input : allInputs)
620                 tData.d->inputs << createArtifactData(input, product, targetArtifacts);
621             tData.d->commands = ruleCommandListForTransformer(t);
622             productTransformerData << tData;
623         }
624         projectTransformerData << qMakePair(productData, productTransformerData);
625     }
626     return projectTransformerData;
627 }
628 
productIsRunnable(const ResolvedProductConstPtr & product)629 static bool productIsRunnable(const ResolvedProductConstPtr &product)
630 {
631     const bool isBundle = product->moduleProperties->moduleProperty(
632                 QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool();
633     const QString androidSdkPackageType = product->moduleProperties->moduleProperty(
634                 QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString();
635     const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk");
636     return isRunnableArtifact(product->fileTags, isBundle, isAndroidApk);
637 }
638 
productIsMultiplexed(const ResolvedProductConstPtr & product)639 static bool productIsMultiplexed(const ResolvedProductConstPtr &product)
640 {
641     return product->productProperties.value(StringConstants::multiplexedProperty()).toBool();
642 }
643 
retrieveProjectData(ProjectData & projectData,const ResolvedProjectConstPtr & internalProject)644 void ProjectPrivate::retrieveProjectData(ProjectData &projectData,
645                                          const ResolvedProjectConstPtr &internalProject)
646 {
647     projectData.d->name = internalProject->name;
648     projectData.d->location = internalProject->location;
649     projectData.d->enabled = internalProject->enabled;
650     for (const auto &resolvedProduct : internalProject->products) {
651         ProductData product;
652         product.d->type = resolvedProduct->fileTags.toStringList();
653         product.d->name = resolvedProduct->name;
654         product.d->targetName = resolvedProduct->targetName;
655         product.d->version = resolvedProduct
656                 ->productProperties.value(StringConstants::versionProperty()).toString();
657         product.d->multiplexConfigurationId = resolvedProduct->multiplexConfigurationId;
658         product.d->location = resolvedProduct->location;
659         product.d->buildDirectory = resolvedProduct->buildDirectory();
660         product.d->isEnabled = resolvedProduct->enabled;
661         product.d->isRunnable = productIsRunnable(resolvedProduct);
662         product.d->isMultiplexed = productIsMultiplexed(resolvedProduct);
663         product.d->properties = resolvedProduct->productProperties;
664         product.d->moduleProperties.d->m_map = resolvedProduct->moduleProperties;
665         for (const GroupPtr &resolvedGroup : resolvedProduct->groups) {
666             if (resolvedGroup->targetOfModule.isEmpty())
667                 product.d->groups << createGroupDataFromGroup(resolvedGroup, resolvedProduct);
668         }
669         if (resolvedProduct->enabled) {
670             QBS_CHECK(resolvedProduct->buildData);
671             const ArtifactSet targetArtifacts = resolvedProduct->targetArtifacts();
672             for (Artifact * const a
673                  : filterByType<Artifact>(resolvedProduct->buildData->allNodes())) {
674                 if (a->artifactType != Artifact::Generated)
675                     continue;
676                 product.d->generatedArtifacts << createArtifactData(a, resolvedProduct,
677                                                                     targetArtifacts);
678             }
679             const AllRescuableArtifactData &rad
680                     = resolvedProduct->buildData->rescuableArtifactData();
681             for (auto it = rad.begin(); it != rad.end(); ++it) {
682                 ArtifactData ta;
683                 ta.d->filePath = it.key();
684                 ta.d->fileTags = it.value().fileTags.toStringList();
685                 ta.d->properties.d->m_map = it.value().properties;
686                 ta.d->isGenerated = true;
687                 ta.d->isTargetArtifact = resolvedProduct->fileTags.intersects(it.value().fileTags);
688                 ta.d->isValid = true;
689                 setupInstallData(ta, resolvedProduct);
690                 product.d->generatedArtifacts << ta;
691             }
692         }
693         for (const ResolvedProductPtr &resolvedDependentProduct
694              : qAsConst(resolvedProduct->dependencies)) {
695             product.d->dependencies << resolvedDependentProduct->fullDisplayName();
696         }
697         std::sort(product.d->type.begin(), product.d->type.end());
698         std::sort(product.d->groups.begin(), product.d->groups.end());
699         std::sort(product.d->generatedArtifacts.begin(), product.d->generatedArtifacts.end());
700         product.d->isValid = true;
701         projectData.d->products << product;
702     }
703     for (const auto &internalSubProject : qAsConst(internalProject->subProjects)) {
704         if (!internalSubProject->enabled)
705             continue;
706         ProjectData subProject;
707         retrieveProjectData(subProject, internalSubProject);
708         projectData.d->subProjects << subProject;
709     }
710     projectData.d->isValid = true;
711     std::sort(projectData.d->products.begin(), projectData.d->products.end());
712     std::sort(projectData.d->subProjects.begin(), projectData.d->subProjects.end());
713 }
714 
715 } // namespace Internal
716 
717 using namespace Internal;
718 
719  /*!
720   * \class Project
721   * \brief The \c Project class provides services related to a qbs project.
722   */
723 
Project(const TopLevelProjectPtr & internalProject,const Logger & logger)724 Project::Project(const TopLevelProjectPtr &internalProject, const Logger &logger)
725     : d(new ProjectPrivate(internalProject, logger))
726 {
727 }
728 
729 Project::Project(const Project &other) = default;
730 
731 Project::~Project() = default;
732 
733 /*!
734  * \brief Returns true if and only if this object was retrieved from a successful \c SetupProjectJob.
735  * \sa SetupProjectJob
736  */
isValid() const737 bool Project::isValid() const
738 {
739     return d && d->internalProject;
740 }
741 
742 /*!
743  * \brief The top-level profile for building this project.
744  */
profile() const745 QString Project::profile() const
746 {
747     QBS_ASSERT(isValid(), return {});
748     return d->internalProject->profile();
749 }
750 
751 Project &Project::operator=(const Project &other) = default;
752 
753 /*!
754  * \brief Sets up a \c Project from a source file, possibly re-using previously stored information.
755  * The function will finish immediately, returning a \c SetupProjectJob which can be used to
756  * track the results of the operation.
757  * If the function is called on a valid \c Project object, the build graph will not be loaded
758  * from a file, but will be taken from the existing project. In that case, if resolving
759  * finishes successfully, the existing project will be invalidated. If resolving fails, qbs will
760  * try to keep the existing project valid. However, under certain circumstances, resolving the new
761  * project will fail at a time where existing project data has already been touched, in which case
762  * the existing project has to be invalidated (this could be avoided, but it would hurt performance).
763  * So after an unsuccessful re-resolve job, the existing project may or may not be valid anymore.
764  * \note The qbs plugins will only be loaded once. As a result, the value of
765  *       \c parameters.pluginPaths will only have an effect the first time this function is called.
766  *       Similarly, the value of \c parameters.searchPaths will not have an effect if
767  *       a stored build graph is available.
768  */
setupProject(const SetupProjectParameters & parameters,ILogSink * logSink,QObject * jobOwner)769 SetupProjectJob *Project::setupProject(const SetupProjectParameters &parameters,
770                                        ILogSink *logSink, QObject *jobOwner)
771 {
772     Logger logger(logSink);
773     const auto job = new SetupProjectJob(logger, jobOwner);
774     try {
775         loadPlugins(parameters.pluginPaths(), logger);
776         job->resolve(*this, parameters);
777         QBS_ASSERT(job->state() == AbstractJob::StateRunning,);
778     } catch (const ErrorInfo &error) {
779         // Throwing from here would complicate the API, so let's report the error the same way
780         // as all others, via AbstractJob::error().
781         job->reportError(error);
782     }
783     return job;
784 }
785 
786 Project::Project() = default;
787 
788 /*!
789  * \brief Retrieves information for this project.
790  * Call this function if you need insight into the project structure, e.g. because you want to know
791  * which products or files are in it.
792  */
projectData() const793 ProjectData Project::projectData() const
794 {
795     QBS_ASSERT(isValid(), return {});
796     return d->projectData();
797 }
798 
getRunEnvironment(const ProductData & product,const InstallOptions & installOptions,const QProcessEnvironment & environment,const QStringList & setupRunEnvConfig,Settings * settings) const799 RunEnvironment Project::getRunEnvironment(const ProductData &product,
800         const InstallOptions &installOptions,
801         const QProcessEnvironment &environment,
802         const QStringList &setupRunEnvConfig, Settings *settings) const
803 {
804     const ResolvedProductPtr resolvedProduct = d->internalProduct(product);
805     return RunEnvironment(resolvedProduct, d->internalProject, installOptions, environment,
806                           setupRunEnvConfig, settings, d->logger);
807 }
808 
809 /*!
810  * \enum Project::ProductSelection
811  * This enum type specifies which products to include if "all" products are to be built.
812  * \value Project::ProdProductSelectionDefaultOnly Indicates that only those products should be
813  *                                                 built whose \c builtByDefault property
814  *                                                 is \c true.
815  * \value Project::ProdProductSelectionWithNonDefault Indicates that products whose
816  *                                                    \c builtByDefault property is \c false should
817  *                                                    also be built.
818  */
819 
820 /*!
821  * \brief Causes all products of this project to be built, if necessary.
822  * If and only if \c producSelection is \c Project::ProductSelectionWithNonDefault, products with
823  * the \c builtByDefault property set to \c false will be built too.
824  * The function will finish immediately, returning a \c BuildJob identifiying the operation.
825  */
buildAllProducts(const BuildOptions & options,ProductSelection productSelection,QObject * jobOwner) const826 BuildJob *Project::buildAllProducts(const BuildOptions &options, ProductSelection productSelection,
827                                     QObject *jobOwner) const
828 {
829     QBS_ASSERT(isValid(), return nullptr);
830     const bool includingNonDefault = productSelection == ProductSelectionWithNonDefault;
831     return d->buildProducts(d->allEnabledInternalProducts(includingNonDefault), options,
832                             !includingNonDefault, jobOwner);
833 }
834 
835 /*!
836  * \brief Causes the specified list of products to be built.
837  * Use this function if you only want to build some products, not the whole project. If any of
838  * the products in \a products depend on other products, those will also be built.
839  * The function will finish immediately, returning a \c BuildJob identifiying the operation.
840  */
buildSomeProducts(const QList<ProductData> & products,const BuildOptions & options,QObject * jobOwner) const841 BuildJob *Project::buildSomeProducts(const QList<ProductData> &products,
842                                      const BuildOptions &options, QObject *jobOwner) const
843 {
844     QBS_ASSERT(isValid(), return nullptr);
845     return d->buildProducts(d->internalProducts(products), options, true, jobOwner);
846 }
847 
848 /*!
849  * \brief Convenience function for \c buildSomeProducts().
850  * \sa Project::buildSomeProducts().
851  */
buildOneProduct(const ProductData & product,const BuildOptions & options,QObject * jobOwner) const852 BuildJob *Project::buildOneProduct(const ProductData &product, const BuildOptions &options,
853                                    QObject *jobOwner) const
854 {
855     return buildSomeProducts(QList<ProductData>() << product, options, jobOwner);
856 }
857 
858 /*!
859  * \brief Removes the build artifacts of all products in the project.
860  * The function will finish immediately, returning a \c CleanJob identifiying this operation.
861  * \sa Project::cleanSomeProducts()
862  */
cleanAllProducts(const CleanOptions & options,QObject * jobOwner) const863 CleanJob *Project::cleanAllProducts(const CleanOptions &options, QObject *jobOwner) const
864 {
865     QBS_ASSERT(isValid(), return nullptr);
866     return d->cleanProducts(d->allEnabledInternalProducts(true), options, jobOwner);
867 }
868 
869 /*!
870  * \brief Removes the build artifacts of the given products.
871  * The function will finish immediately, returning a \c CleanJob identifiying this operation.
872  */
cleanSomeProducts(const QList<ProductData> & products,const CleanOptions & options,QObject * jobOwner) const873 CleanJob *Project::cleanSomeProducts(const QList<ProductData> &products,
874         const CleanOptions &options, QObject *jobOwner) const
875 {
876     QBS_ASSERT(isValid(), return nullptr);
877     return d->cleanProducts(d->internalProducts(products), options, jobOwner);
878 }
879 
880 /*!
881  * \brief Convenience function for \c cleanSomeProducts().
882  * \sa Project::cleanSomeProducts().
883  */
cleanOneProduct(const ProductData & product,const CleanOptions & options,QObject * jobOwner) const884 CleanJob *Project::cleanOneProduct(const ProductData &product, const CleanOptions &options,
885                                    QObject *jobOwner) const
886 {
887     return cleanSomeProducts(QList<ProductData>() << product, options, jobOwner);
888 }
889 
890 /*!
891  * \brief Installs the installable files of all products in the project.
892  * If and only if \c producSelection is \c Project::ProductSelectionWithNonDefault, products with
893  * the \c builtByDefault property set to \c false will be installed too.
894  * The function will finish immediately, returning an \c InstallJob identifiying this operation.
895  */
installAllProducts(const InstallOptions & options,ProductSelection productSelection,QObject * jobOwner) const896 InstallJob *Project::installAllProducts(const InstallOptions &options,
897                                         ProductSelection productSelection, QObject *jobOwner) const
898 {
899     QBS_ASSERT(isValid(), return nullptr);
900     const bool includingNonDefault = productSelection == ProductSelectionWithNonDefault;
901     return d->installProducts(d->allEnabledInternalProducts(includingNonDefault), options,
902                               !includingNonDefault, jobOwner);
903 }
904 
905 /*!
906  * \brief Installs the installable files of the given products.
907  * The function will finish immediately, returning an \c InstallJob identifiying this operation.
908  */
installSomeProducts(const QList<ProductData> & products,const InstallOptions & options,QObject * jobOwner) const909 InstallJob *Project::installSomeProducts(const QList<ProductData> &products,
910                                          const InstallOptions &options, QObject *jobOwner) const
911 {
912     QBS_ASSERT(isValid(), return nullptr);
913     return d->installProducts(d->internalProducts(products), options, true, jobOwner);
914 }
915 
916 /*!
917  * \brief Convenience function for \c installSomeProducts().
918  * \sa Project::installSomeProducts().
919  */
installOneProduct(const ProductData & product,const InstallOptions & options,QObject * jobOwner) const920 InstallJob *Project::installOneProduct(const ProductData &product, const InstallOptions &options,
921                                        QObject *jobOwner) const
922 {
923     return installSomeProducts(QList<ProductData>() << product, options, jobOwner);
924 }
925 
926 /*!
927  * \brief Updates the timestamps of all build artifacts in the given products.
928  * Afterwards, the build graph will have the same state as if a successful build had been done.
929  */
updateTimestamps(const QList<ProductData> & products)930 void Project::updateTimestamps(const QList<ProductData> &products)
931 {
932     QBS_ASSERT(isValid(), return);
933     TimestampsUpdater().updateTimestamps(d->internalProject, d->internalProducts(products),
934                                          d->logger);
935 }
936 
937 /*!
938  * \brief Finds files generated from the given file in the given product.
939  * If \a recursive is \c false, only files generated directly from \a file will be considered,
940  * otherwise the generated files are collected recursively.
941  * If \a tags is not empty, only generated files matching at least one of these tags will
942  * be considered.
943  */
generatedFiles(const ProductData & product,const QString & file,bool recursive,const QStringList & tags) const944 QStringList Project::generatedFiles(const ProductData &product, const QString &file,
945                                     bool recursive, const QStringList &tags) const
946 {
947     QBS_ASSERT(isValid(), return {});
948     const ResolvedProductConstPtr internalProduct = d->internalProduct(product);
949     return internalProduct->generatedFiles(file, recursive, FileTags::fromStringList(tags));
950 }
951 
projectConfiguration() const952 QVariantMap Project::projectConfiguration() const
953 {
954     QBS_ASSERT(isValid(), return {});
955     return d->internalProject->buildConfiguration();
956 }
957 
buildSystemFiles() const958 std::set<QString> Project::buildSystemFiles() const
959 {
960     QBS_ASSERT(isValid(), return {});
961     return rangeTo<std::set<QString>>(d->internalProject->buildSystemFiles);
962 }
963 
ruleCommands(const ProductData & product,const QString & inputFilePath,const QString & outputFileTag,ErrorInfo * error) const964 RuleCommandList Project::ruleCommands(const ProductData &product,
965         const QString &inputFilePath, const QString &outputFileTag, ErrorInfo *error) const
966 {
967     QBS_ASSERT(isValid(), return {});
968     QBS_ASSERT(product.isValid(), return {});
969 
970     try {
971         return d->ruleCommands(product, inputFilePath, outputFileTag);
972     } catch (const ErrorInfo &e) {
973         if (error)
974             *error = e;
975         return {};
976     }
977 }
978 
transformerData(ErrorInfo * error) const979 ProjectTransformerData Project::transformerData(ErrorInfo *error) const
980 {
981     QBS_ASSERT(isValid(), return {});
982     try {
983         return d->transformerData();
984     } catch (const ErrorInfo &e) {
985         if (error)
986             *error = e;
987         return {};
988     }
989 }
990 
dumpNodesTree(QIODevice & outDevice,const QList<ProductData> & products)991 ErrorInfo Project::dumpNodesTree(QIODevice &outDevice, const QList<ProductData> &products)
992 {
993     try {
994         NodeTreeDumper(outDevice).start(d->internalProducts(products));
995     } catch (const ErrorInfo &e) {
996         return e;
997     }
998     return {};
999 }
1000 
getBuildGraphInfo(const QString & bgFilePath,const QStringList & requestedProperties)1001 Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath,
1002                                                    const QStringList &requestedProperties)
1003 {
1004     BuildGraphInfo info;
1005     try {
1006         const Internal::TopLevelProjectConstPtr project = BuildGraphLoader::loadProject(bgFilePath);
1007         info.bgFilePath = bgFilePath;
1008         info.overriddenProperties = project->overriddenValues;
1009         info.profileData = project->profileConfigs;
1010         std::vector<std::pair<QString, QString>> props;
1011         for (const QString &prop : requestedProperties) {
1012             QStringList components = prop.split(QLatin1Char('.'));
1013             const QString propName = components.takeLast();
1014             props.emplace_back(components.join(QLatin1Char('.')), propName);
1015         }
1016         for (const auto &product : project->allProducts()) {
1017             if (props.empty())
1018                 break;
1019             if (product->profile() != project->profile())
1020                 continue;
1021             for (auto it = props.begin(); it != props.end();) {
1022                 const QVariant value
1023                         = product->moduleProperties->moduleProperty(it->first, it->second);
1024                 if (value.isValid()) {
1025                     info.requestedProperties.insert(it->first + QLatin1Char('.') + it->second,
1026                                                     value);
1027                     it = props.erase(it);
1028                 } else {
1029                     ++it;
1030                 }
1031             }
1032         }
1033     } catch (const ErrorInfo &e) {
1034         info.error = e;
1035     }
1036     return info;
1037 }
1038 
getBuildGraphInfo() const1039 Project::BuildGraphInfo Project::getBuildGraphInfo() const
1040 {
1041     QBS_ASSERT(isValid(), return {});
1042     BuildGraphInfo info;
1043     try {
1044         if (d->internalProject->locked)
1045             throw ErrorInfo(Tr::tr("A job is currently in progress."));
1046         info.bgFilePath = d->internalProject->buildGraphFilePath();
1047         info.overriddenProperties = d->internalProject->overriddenValues;
1048         info.profileData = d->internalProject->profileConfigs;
1049     } catch (const ErrorInfo &e) {
1050         info.error = e;
1051     }
1052     return info;
1053 }
1054 
1055 /*!
1056  * \brief Adds a new empty group to the given product.
1057  * Returns an \c ErrorInfo object for which \c hasError() is false in case of a success
1058  * and true otherwise. In the latter case, the object will have a sensible description.
1059  * After calling this function, it is recommended to re-fetch the project data, as other
1060  * items can be affected.
1061  * \sa qbs::Project::projectData()
1062  */
addGroup(const ProductData & product,const QString & groupName)1063 ErrorInfo Project::addGroup(const ProductData &product, const QString &groupName)
1064 {
1065     try {
1066         QBS_CHECK(isValid());
1067         d->prepareChangeToProject();
1068         d->addGroup(product, groupName);
1069         d->internalProject->store(d->logger);
1070         return {};
1071     } catch (const ErrorInfo &exception) {
1072         auto errorInfo = exception;
1073         errorInfo.prepend(Tr::tr("Failure adding group '%1' to product '%2'.")
1074                           .arg(groupName, product.name()));
1075         return errorInfo;
1076     }
1077 }
1078 
1079 /*!
1080  * \brief Adds the given files to the given product.
1081  * If \c group is a default-constructed object, the files will be added to the product's
1082  * "files" property, otherwise to the one of \c group.
1083  * The file paths can be absolute or relative to the location of \c product (including a possible
1084  * prefix in the group). The project file will always contain relative paths.
1085  * After calling this function, it is recommended to re-fetch the project data, as other
1086  * items can be affected.
1087  * \sa qbs::Project::projectData()
1088  */
addFiles(const ProductData & product,const GroupData & group,const QStringList & filePaths)1089 ErrorInfo Project::addFiles(const ProductData &product, const GroupData &group,
1090                             const QStringList &filePaths)
1091 {
1092     try {
1093         QBS_CHECK(isValid());
1094         d->prepareChangeToProject();
1095         d->addFiles(product, group, filePaths);
1096         d->internalProject->store(d->logger);
1097         return {};
1098     } catch (const ErrorInfo &exception) {
1099         auto errorInfo = exception;
1100         errorInfo.prepend(Tr::tr("Failure adding files to product."));
1101         return errorInfo;
1102     }
1103 }
1104 
1105 /*!
1106  * \brief Removes the given files from the given product.
1107  * If \c group is a default-constructed object, the files will be removed from the product's
1108  * "files" property, otherwise from the one of \c group.
1109  * The file paths can be absolute or relative to the location of \c product (including a possible
1110  * prefix in the group).
1111  * After calling this function, it is recommended to re-fetch the project data, as other
1112  * items can be affected.
1113  * \sa qbs::Project::projectData()
1114  */
removeFiles(const ProductData & product,const GroupData & group,const QStringList & filePaths)1115 ErrorInfo Project::removeFiles(const ProductData &product, const GroupData &group,
1116                                const QStringList &filePaths)
1117 {
1118     try {
1119         QBS_CHECK(isValid());
1120         d->prepareChangeToProject();
1121         d->removeFiles(product, group, filePaths);
1122         d->internalProject->store(d->logger);
1123         return {};
1124     } catch (const ErrorInfo &exception) {
1125         auto errorInfo = exception;
1126         errorInfo.prepend(Tr::tr("Failure removing files from product '%1'.").arg(product.name()));
1127         return errorInfo;
1128     }
1129 }
1130 
1131 /*!
1132  * \brief Removes the given group from the given product.
1133  * After calling this function, it is recommended to re-fetch the project data, as other
1134  * items can be affected.
1135  * \sa qbs::Project::projectData()
1136  */
removeGroup(const ProductData & product,const GroupData & group)1137 ErrorInfo Project::removeGroup(const ProductData &product, const GroupData &group)
1138 {
1139     try {
1140         QBS_CHECK(isValid());
1141         d->prepareChangeToProject();
1142         d->removeGroup(product, group);
1143         d->internalProject->store(d->logger);
1144         return {};
1145     } catch (const ErrorInfo &exception) {
1146         auto errorInfo = exception;
1147         errorInfo.prepend(Tr::tr("Failure removing group '%1' from product '%2'.")
1148                           .arg(group.name(), product.name()));
1149         return errorInfo;
1150     }
1151 }
1152 
1153 } // namespace qbs
1154