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
40 #include "language.h"
41
42 #include "artifactproperties.h"
43 #include "builtindeclarations.h"
44 #include "propertymapinternal.h"
45 #include "scriptengine.h"
46
47 #include <buildgraph/artifact.h>
48 #include <buildgraph/buildgraph.h>
49 #include <buildgraph/productbuilddata.h>
50 #include <buildgraph/projectbuilddata.h>
51 #include <buildgraph/rulegraph.h> // TODO: Move to language?
52 #include <buildgraph/transformer.h>
53 #include <jsextensions/jsextensions.h>
54 #include <logging/categories.h>
55 #include <logging/translator.h>
56 #include <tools/buildgraphlocker.h>
57 #include <tools/hostosinfo.h>
58 #include <tools/error.h>
59 #include <tools/fileinfo.h>
60 #include <tools/scripttools.h>
61 #include <tools/qbsassert.h>
62 #include <tools/qttools.h>
63 #include <tools/stringconstants.h>
64
65 #include <QtCore/qcryptographichash.h>
66 #include <QtCore/qdir.h>
67 #include <QtCore/qdiriterator.h>
68 #include <QtCore/qmap.h>
69
70 #include <QtScript/qscriptvalue.h>
71
72 #include <algorithm>
73 #include <memory>
74 #include <mutex>
75
76 namespace qbs {
77 namespace Internal {
78
equals(const T * v1,const T * v2)79 template<typename T> bool equals(const T *v1, const T *v2)
80 {
81 if (v1 == v2)
82 return true;
83 if (!v1 != !v2)
84 return false;
85 return *v1 == *v2;
86 }
87
88
89 /*!
90 * \class FileTagger
91 * \brief The \c FileTagger class maps 1:1 to the respective item in a qbs source file.
92 */
93
FileTagger(const QStringList & patterns,FileTags fileTags,int priority)94 FileTagger::FileTagger(const QStringList &patterns, FileTags fileTags, int priority)
95 : m_fileTags(std::move(fileTags)), m_priority(priority)
96 {
97 setPatterns(patterns);
98 }
99
setPatterns(const QStringList & patterns)100 void FileTagger::setPatterns(const QStringList &patterns)
101 {
102 m_patterns.clear();
103 for (const QString &pattern : patterns) {
104 QBS_CHECK(!pattern.isEmpty());
105 m_patterns << QRegularExpression(QRegularExpression::wildcardToRegularExpression(pattern));
106 }
107 }
108
109
needsReconfigure(const FileTime & referenceTime) const110 bool Probe::needsReconfigure(const FileTime &referenceTime) const
111 {
112 const auto criterion = [referenceTime](const QString &filePath) {
113 FileInfo fi(filePath);
114 return !fi.exists() || fi.lastModified() > referenceTime;
115 };
116 return std::any_of(m_importedFilesUsed.cbegin(), m_importedFilesUsed.cend(), criterion);
117 }
118
119
120 /*!
121 * \class SourceArtifact
122 * \brief The \c SourceArtifact class represents a source file.
123 * Everything except the file path is inherited from the surrounding \c ResolvedGroup.
124 * (TODO: Not quite true. Artifacts in transformers will be generated by the transformer, but are
125 * still represented as source artifacts. We may or may not want to change this; if we do,
126 * SourceArtifact could simply have a back pointer to the group in addition to the file path.)
127 * \sa ResolvedGroup
128 */
129
130
131 /*!
132 * \class ResolvedGroup
133 * \brief The \c ResolvedGroup class corresponds to the Group item in a qbs source file.
134 */
135
136 /*!
137 * \variable ResolvedGroup::files
138 * \brief The files listed in the group item's "files" binding.
139 * Note that these do not include expanded wildcards.
140 */
141
142 /*!
143 * \variable ResolvedGroup::wildcards
144 * \brief Represents the wildcard elements in this group's "files" binding.
145 * If no wildcards are specified there, this variable is null.
146 * \sa SourceWildCards
147 */
148
149 /*!
150 * \brief Returns all files specified in the group item as source artifacts.
151 * This includes the expanded list of wildcards.
152 */
allFiles() const153 std::vector<SourceArtifactPtr> ResolvedGroup::allFiles() const
154 {
155 std::vector<SourceArtifactPtr> lst = files;
156 if (wildcards)
157 lst << wildcards->files;
158 return lst;
159 }
160
load(PersistentPool & pool)161 void ResolvedGroup::load(PersistentPool &pool)
162 {
163 serializationOp<PersistentPool::Load>(pool);
164 if (wildcards)
165 wildcards->group = this;
166 }
167
store(PersistentPool & pool)168 void ResolvedGroup::store(PersistentPool &pool)
169 {
170 serializationOp<PersistentPool::Store>(pool);
171 }
172
173 /*!
174 * \class RuleArtifact
175 * \brief The \c RuleArtifact class represents an Artifact item encountered in the context
176 * of a Rule item.
177 * When applying the rule, one \c Artifact object will be constructed from each \c RuleArtifact
178 * object. During that process, the \c RuleArtifact's bindings are evaluated and the results
179 * are inserted into the corresponding \c Artifact's properties.
180 * \sa Rule
181 */
182
183
184 /*!
185 * \class ScriptFunction
186 * \brief The \c ScriptFunction class represents the JavaScript code found in the "prepare" binding
187 * of a \c Rule item in a qbs file.
188 * \sa Rule
189 */
190
191 ScriptFunction::ScriptFunction() = default;
192
193 ScriptFunction::~ScriptFunction() = default;
194
195 /*!
196 * \variable ScriptFunction::script
197 * \brief The actual Javascript code, taken verbatim from the qbs source file.
198 */
199
200 /*!
201 * \variable ScriptFunction::location
202 * \brief The exact location of the script in the qbs source file.
203 * This is mostly needed for diagnostics.
204 */
205
isValid() const206 bool ScriptFunction::isValid() const
207 {
208 return location.line() != -1;
209 }
210
operator ==(const ScriptFunction & a,const ScriptFunction & b)211 bool operator==(const ScriptFunction &a, const ScriptFunction &b)
212 {
213 return a.sourceCode == b.sourceCode
214 && a.location == b.location
215 && equals(a.fileContext.get(), b.fileContext.get());
216 }
217
argumentNamesForSetupBuildEnv()218 QStringList ResolvedModule::argumentNamesForSetupBuildEnv()
219 {
220 static const QStringList argNames = BuiltinDeclarations::instance()
221 .argumentNamesForScriptFunction(ItemType::Module,
222 StringConstants::setupBuildEnvironmentProperty());
223 return argNames;
224 }
225
argumentNamesForSetupRunEnv()226 QStringList ResolvedModule::argumentNamesForSetupRunEnv()
227 {
228 static const QStringList argNames = BuiltinDeclarations::instance()
229 .argumentNamesForScriptFunction(ItemType::Module,
230 StringConstants::setupRunEnvironmentProperty());
231 return argNames;
232 }
233
operator ==(const ResolvedModule & m1,const ResolvedModule & m2)234 bool operator==(const ResolvedModule &m1, const ResolvedModule &m2)
235 {
236 return m1.name == m2.name
237 && m1.isProduct == m2.isProduct
238 && toSet(m1.moduleDependencies) == toSet(m2.moduleDependencies)
239 && m1.setupBuildEnvironmentScript == m2.setupBuildEnvironmentScript
240 && m1.setupRunEnvironmentScript == m2.setupRunEnvironmentScript;
241 }
242
clone() const243 RulePtr Rule::clone() const
244 {
245 return std::make_shared<Rule>(*this);
246 }
247
argumentNamesForOutputArtifacts()248 QStringList Rule::argumentNamesForOutputArtifacts()
249 {
250 static const QStringList argNames = BuiltinDeclarations::instance()
251 .argumentNamesForScriptFunction(ItemType::Rule,
252 StringConstants::outputArtifactsProperty());
253 return argNames;
254 }
255
argumentNamesForPrepare()256 QStringList Rule::argumentNamesForPrepare()
257 {
258 static const QStringList argNames = BuiltinDeclarations::instance()
259 .argumentNamesForScriptFunction(ItemType::Rule, StringConstants::prepareProperty());
260 return argNames;
261 }
262
toString() const263 QString Rule::toString() const
264 {
265 QStringList outputTagsSorted = collectedOutputFileTags().toStringList();
266 outputTagsSorted.sort();
267 FileTags inputTags = inputs;
268 inputTags.unite(inputsFromDependencies);
269 QStringList inputTagsSorted = inputTags.toStringList();
270 inputTagsSorted.sort();
271 return QLatin1Char('[') + outputTagsSorted.join(QLatin1Char(','))
272 + QLatin1String("][")
273 + inputTagsSorted.join(QLatin1Char(',')) + QLatin1Char(']');
274 }
275
staticOutputFileTags() const276 FileTags Rule::staticOutputFileTags() const
277 {
278 FileTags result;
279 for (const auto &artifact : artifacts)
280 result.unite(artifact->fileTags);
281 return result;
282 }
283
collectedOutputFileTags() const284 FileTags Rule::collectedOutputFileTags() const
285 {
286 FileTags result = outputFileTags.empty() ? staticOutputFileTags() : outputFileTags;
287 for (const auto &ap : product->artifactProperties) {
288 if (ap->fileTagsFilter().intersects(result))
289 result += ap->extraFileTags();
290 }
291 return result;
292 }
293
isDynamic() const294 bool Rule::isDynamic() const
295 {
296 return outputArtifactsScript.isValid();
297 }
298
declaresInputs() const299 bool Rule::declaresInputs() const
300 {
301 return !inputs.empty() || !inputsFromDependencies.empty();
302 }
303
ResolvedProduct()304 ResolvedProduct::ResolvedProduct()
305 : enabled(true)
306 {
307 }
308
309 ResolvedProduct::~ResolvedProduct() = default;
310
accept(BuildGraphVisitor * visitor) const311 void ResolvedProduct::accept(BuildGraphVisitor *visitor) const
312 {
313 if (!buildData)
314 return;
315 for (BuildGraphNode * const node : qAsConst(buildData->rootNodes()))
316 node->accept(visitor);
317 }
318
319 /*!
320 * \brief Returns all files of all groups as source artifacts.
321 * This includes the expanded list of wildcards.
322 */
allFiles() const323 std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const
324 {
325 std::vector<SourceArtifactPtr> lst;
326 for (const auto &group : groups)
327 lst << group->allFiles();
328 return lst;
329 }
330
331 /*!
332 * \brief Returns all files of all enabled groups as source artifacts.
333 * \sa ResolvedProduct::allFiles()
334 */
allEnabledFiles() const335 std::vector<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const
336 {
337 std::vector<SourceArtifactPtr> lst;
338 for (const auto &group : groups) {
339 if (group->enabled)
340 lst << group->allFiles();
341 }
342 return lst;
343 }
344
fileTagsForFileName(const QString & fileName) const345 FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const
346 {
347 FileTags result;
348 std::unique_ptr<int> priority;
349 for (const FileTaggerConstPtr &tagger : qAsConst(fileTaggers)) {
350 for (const QRegularExpression &pattern : tagger->patterns()) {
351 if (pattern.match(fileName).hasMatch()) {
352 if (priority) {
353 if (*priority != tagger->priority()) {
354 // The taggers are expected to be sorted by priority.
355 QBS_ASSERT(*priority > tagger->priority(), return result);
356 return result;
357 }
358 } else {
359 priority = std::make_unique<int>(tagger->priority());
360 }
361 result.unite(tagger->fileTags());
362 break;
363 }
364 }
365 }
366 return result;
367 }
368
load(PersistentPool & pool)369 void ResolvedProduct::load(PersistentPool &pool)
370 {
371 serializationOp<PersistentPool::Load>(pool);
372 for (const RulePtr &rule : rules)
373 rule->product = this;
374 for (const ResolvedModulePtr &module : modules)
375 module->product = this;
376 }
377
store(PersistentPool & pool)378 void ResolvedProduct::store(PersistentPool &pool)
379 {
380 serializationOp<PersistentPool::Store>(pool);
381 }
382
lookupArtifactsByFileTag(const FileTag & tag) const383 ArtifactSet ResolvedProduct::lookupArtifactsByFileTag(const FileTag &tag) const
384 {
385 QBS_CHECK(buildData);
386 return buildData->artifactsByFileTag().value(tag);
387 }
388
lookupArtifactsByFileTags(const FileTags & tags) const389 ArtifactSet ResolvedProduct::lookupArtifactsByFileTags(const FileTags &tags) const
390 {
391 QBS_CHECK(buildData);
392 ArtifactSet set;
393 for (const FileTag &tag : tags)
394 set = set.unite(buildData->artifactsByFileTag().value(tag));
395 return set;
396 }
397
targetArtifacts() const398 ArtifactSet ResolvedProduct::targetArtifacts() const
399 {
400 QBS_CHECK(buildData);
401 ArtifactSet taSet;
402 for (Artifact * const a : buildData->rootArtifacts()) {
403 QBS_CHECK(a->fileTags().intersects(fileTags));
404 taSet << a;
405 }
406 return taSet;
407 }
408
topLevelProject() const409 TopLevelProject *ResolvedProduct::topLevelProject() const
410 {
411 return project->topLevelProject();
412 }
413
uniqueName(const QString & name,const QString & multiplexConfigurationId)414 QString ResolvedProduct::uniqueName(const QString &name, const QString &multiplexConfigurationId)
415 {
416 QString result = name;
417 if (!multiplexConfigurationId.isEmpty())
418 result.append(QLatin1Char('.')).append(multiplexConfigurationId);
419 return result;
420 }
421
uniqueName() const422 QString ResolvedProduct::uniqueName() const
423 {
424 return uniqueName(name, multiplexConfigurationId);
425 }
426
fullDisplayName(const QString & name,const QString & multiplexConfigurationId)427 QString ResolvedProduct::fullDisplayName(const QString &name,
428 const QString &multiplexConfigurationId)
429 {
430 QString result = name;
431 if (!multiplexConfigurationId.isEmpty())
432 result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId));
433 return result;
434 }
435
fullDisplayName() const436 QString ResolvedProduct::fullDisplayName() const
437 {
438 return fullDisplayName(name, multiplexConfigurationId);
439 }
440
profile() const441 QString ResolvedProduct::profile() const
442 {
443 return moduleProperties->qbsPropertyValue(StringConstants::profileProperty()).toString();
444 }
445
findGeneratedFiles(const Artifact * base,bool recursive,const FileTags & tags)446 static QStringList findGeneratedFiles(const Artifact *base, bool recursive, const FileTags &tags)
447 {
448 QStringList result;
449 for (const Artifact *parent : base->parentArtifacts()) {
450 if (tags.empty() || parent->fileTags().intersects(tags))
451 result << parent->filePath();
452 if (recursive)
453 result << findGeneratedFiles(parent, true, tags);
454 }
455 return result;
456 }
457
generatedFiles(const QString & baseFile,bool recursive,const FileTags & tags) const458 QStringList ResolvedProduct::generatedFiles(const QString &baseFile, bool recursive,
459 const FileTags &tags) const
460 {
461 ProductBuildData *data = buildData.get();
462 if (!data)
463 return {};
464
465 for (const Artifact *art : filterByType<Artifact>(data->allNodes())) {
466 if (art->filePath() == baseFile)
467 return findGeneratedFiles(art, recursive, tags);
468 }
469 return {};
470 }
471
deriveBuildDirectoryName(const QString & name,const QString & multiplexConfigurationId)472 QString ResolvedProduct::deriveBuildDirectoryName(const QString &name,
473 const QString &multiplexConfigurationId)
474 {
475 QString dirName = uniqueName(name, multiplexConfigurationId);
476 const QByteArray hash = QCryptographicHash::hash(dirName.toUtf8(), QCryptographicHash::Sha1);
477 return HostOsInfo::rfc1034Identifier(dirName)
478 .append(QLatin1Char('.'))
479 .append(QString::fromLatin1(hash.toHex().left(8)));
480 }
481
buildDirectory() const482 QString ResolvedProduct::buildDirectory() const
483 {
484 return productProperties.value(StringConstants::buildDirectoryProperty()).toString();
485 }
486
isInParentProject(const ResolvedProductConstPtr & other) const487 bool ResolvedProduct::isInParentProject(const ResolvedProductConstPtr &other) const
488 {
489 for (const ResolvedProject *otherParent = other->project.get(); otherParent;
490 otherParent = otherParent->parentProject.get()) {
491 if (otherParent == project.get())
492 return true;
493 }
494 return false;
495 }
496
builtByDefault() const497 bool ResolvedProduct::builtByDefault() const
498 {
499 return productProperties.value(StringConstants::builtByDefaultProperty(), true).toBool();
500 }
501
cacheExecutablePath(const QString & origFilePath,const QString & fullFilePath)502 void ResolvedProduct::cacheExecutablePath(const QString &origFilePath, const QString &fullFilePath)
503 {
504 std::lock_guard<std::mutex> locker(m_executablePathCacheLock);
505 m_executablePathCache.insert(origFilePath, fullFilePath);
506 }
507
cachedExecutablePath(const QString & origFilePath) const508 QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const
509 {
510 std::lock_guard<std::mutex> locker(m_executablePathCacheLock);
511 return m_executablePathCache.value(origFilePath);
512 }
513
514
ResolvedProject()515 ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(nullptr)
516 {
517 }
518
519 ResolvedProject::~ResolvedProject() = default;
520
accept(BuildGraphVisitor * visitor) const521 void ResolvedProject::accept(BuildGraphVisitor *visitor) const
522 {
523 for (const ResolvedProductPtr &product : products)
524 product->accept(visitor);
525 for (const ResolvedProjectPtr &subProject : qAsConst(subProjects))
526 subProject->accept(visitor);
527 }
528
topLevelProject()529 TopLevelProject *ResolvedProject::topLevelProject()
530 {
531 if (m_topLevelProject)
532 return m_topLevelProject;
533 if (parentProject.expired()) {
534 m_topLevelProject = static_cast<TopLevelProject *>(this);
535 return m_topLevelProject;
536 }
537 m_topLevelProject = parentProject->topLevelProject();
538 return m_topLevelProject;
539 }
540
allSubProjects() const541 std::vector<ResolvedProjectPtr> ResolvedProject::allSubProjects() const
542 {
543 std::vector<ResolvedProjectPtr> projectList = subProjects;
544 for (const auto &subProject : subProjects)
545 projectList << subProject->allSubProjects();
546 return projectList;
547 }
548
allProducts() const549 std::vector<ResolvedProductPtr> ResolvedProject::allProducts() const
550 {
551 std::vector<ResolvedProductPtr> productList = products;
552 for (const auto &subProject : qAsConst(subProjects))
553 productList << subProject->allProducts();
554 return productList;
555 }
556
load(PersistentPool & pool)557 void ResolvedProject::load(PersistentPool &pool)
558 {
559 serializationOp<PersistentPool::Load>(pool);
560 std::for_each(products.cbegin(), products.cend(),
561 [](const ResolvedProductPtr &p) {
562 if (!p->buildData)
563 return;
564 for (BuildGraphNode * const node : qAsConst(p->buildData->allNodes())) {
565 node->product = p;
566
567 // restore parent links
568 for (BuildGraphNode * const child : qAsConst(node->children))
569 child->parents.insert(node);
570 }
571 });
572 }
573
store(PersistentPool & pool)574 void ResolvedProject::store(PersistentPool &pool)
575 {
576 serializationOp<PersistentPool::Store>(pool);
577 }
578
579
TopLevelProject()580 TopLevelProject::TopLevelProject()
581 : bgLocker(nullptr), locked(false), lastStartResolveTime(FileTime::oldestTime())
582 {
583 }
584
~TopLevelProject()585 TopLevelProject::~TopLevelProject()
586 {
587 cleanupModuleProviderOutput();
588 delete bgLocker;
589 }
590
deriveId(const QVariantMap & config)591 QString TopLevelProject::deriveId(const QVariantMap &config)
592 {
593 const QVariantMap qbsProperties = config.value(StringConstants::qbsModule()).toMap();
594 const QString configurationName = qbsProperties.value(
595 StringConstants::configurationNameProperty()).toString();
596 return configurationName;
597 }
598
deriveBuildDirectory(const QString & buildRoot,const QString & id)599 QString TopLevelProject::deriveBuildDirectory(const QString &buildRoot, const QString &id)
600 {
601 return buildRoot + QLatin1Char('/') + id;
602 }
603
setBuildConfiguration(const QVariantMap & config)604 void TopLevelProject::setBuildConfiguration(const QVariantMap &config)
605 {
606 m_buildConfiguration = config;
607 m_id = deriveId(config);
608 }
609
profile() const610 QString TopLevelProject::profile() const
611 {
612 return projectProperties().value(StringConstants::profileProperty()).toString();
613 }
614
makeModuleProvidersNonTransient()615 void TopLevelProject::makeModuleProvidersNonTransient()
616 {
617 for (ModuleProviderInfo &m : moduleProviderInfo.providers)
618 m.transientOutput = false;
619 }
620
buildGraphFilePath() const621 QString TopLevelProject::buildGraphFilePath() const
622 {
623 return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id());
624 }
625
store(Logger logger)626 void TopLevelProject::store(Logger logger)
627 {
628 // TODO: Use progress observer here.
629
630 if (!buildData)
631 return;
632 if (!buildData->isDirty()) {
633 qCDebug(lcBuildGraph) << "build graph is unchanged in project" << id();
634 return;
635 }
636
637 makeModuleProvidersNonTransient();
638
639 const QString fileName = buildGraphFilePath();
640 qCDebug(lcBuildGraph) << "storing:" << fileName;
641 PersistentPool pool(logger);
642 PersistentPool::HeadData headData;
643 headData.projectConfig = buildConfiguration();
644 pool.setHeadData(headData);
645 pool.setupWriteStream(fileName);
646 store(pool);
647 pool.finalizeWriteStream();
648 buildData->setClean();
649 }
650
load(PersistentPool & pool)651 void TopLevelProject::load(PersistentPool &pool)
652 {
653 ResolvedProject::load(pool);
654 serializationOp<PersistentPool::Load>(pool);
655 QBS_CHECK(buildData);
656 }
657
store(PersistentPool & pool)658 void TopLevelProject::store(PersistentPool &pool)
659 {
660 ResolvedProject::store(pool);
661 serializationOp<PersistentPool::Store>(pool);
662 }
663
cleanupModuleProviderOutput()664 void TopLevelProject::cleanupModuleProviderOutput()
665 {
666 QString error;
667 for (const ModuleProviderInfo &m : moduleProviderInfo.providers) {
668 if (m.transientOutput) {
669 if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error))
670 qCWarning(lcBuildGraph) << "Error removing module provider output:" << error;
671 }
672 }
673 QDir moduleProviderBaseDir(buildDirectory + QLatin1Char('/')
674 + ModuleProviderInfo::outputBaseDirName());
675 if (moduleProviderBaseDir.exists() && moduleProviderBaseDir.isEmpty()
676 && !removeDirectoryWithContents(moduleProviderBaseDir.path(), &error)) {
677 qCWarning(lcBuildGraph) << "Error removing module provider output:" << error;
678 }
679 }
680
681 /*!
682 * \class SourceWildCards
683 * \brief Objects of the \c SourceWildCards class result from giving wildcards in a
684 * \c ResolvedGroup's "files" binding.
685 * \sa ResolvedGroup
686 */
687
688 /*!
689 * \variable SourceWildCards::prefix
690 * \brief Inherited from the \c ResolvedGroup
691 * \sa ResolvedGroup
692 */
693
694 /*!
695 * \variable SourceWildCards::patterns
696 * \brief All elements of the \c ResolvedGroup's "files" binding that contain wildcards.
697 * \sa ResolvedGroup
698 */
699
700 /*!
701 * \variable SourceWildCards::excludePatterns
702 * \brief Corresponds to the \c ResolvedGroup's "excludeFiles" binding.
703 * \sa ResolvedGroup
704 */
705
706 /*!
707 * \variable SourceWildCards::files
708 * \brief The \c SourceArtifacts resulting from the expanded list of matching files.
709 */
710
expandPatterns(const GroupConstPtr & group,const QString & baseDir,const QString & buildDir)711 Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
712 const QString &baseDir, const QString &buildDir)
713 {
714 Set<QString> files = expandPatterns(group, patterns, baseDir, buildDir);
715 files -= expandPatterns(group, excludePatterns, baseDir, buildDir);
716 return files;
717 }
718
expandPatterns(const GroupConstPtr & group,const QStringList & patterns,const QString & baseDir,const QString & buildDir)719 Set<QString> SourceWildCards::expandPatterns(const GroupConstPtr &group,
720 const QStringList &patterns, const QString &baseDir, const QString &buildDir)
721 {
722 Set<QString> files;
723 QString expandedPrefix = group->prefix;
724 if (expandedPrefix.startsWith(StringConstants::tildeSlash()))
725 expandedPrefix.replace(0, 1, QDir::homePath());
726 for (QString pattern : patterns) {
727 pattern.prepend(expandedPrefix);
728 pattern.replace(QLatin1Char('\\'), QLatin1Char('/'));
729 QStringList parts = pattern.split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS);
730 if (FileInfo::isAbsolute(pattern)) {
731 QString rootDir;
732 if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) {
733 rootDir = parts.takeFirst();
734 if (!rootDir.endsWith(QLatin1Char('/')))
735 rootDir.append(QLatin1Char('/'));
736 } else {
737 rootDir = QLatin1Char('/');
738 }
739 expandPatterns(files, group, parts, rootDir, buildDir);
740 } else {
741 expandPatterns(files, group, parts, baseDir, buildDir);
742 }
743 }
744
745 return files;
746 }
747
expandPatterns(Set<QString> & result,const GroupConstPtr & group,const QStringList & parts,const QString & baseDir,const QString & buildDir)748 void SourceWildCards::expandPatterns(Set<QString> &result, const GroupConstPtr &group,
749 const QStringList &parts,
750 const QString &baseDir, const QString &buildDir)
751 {
752 // People might build directly in the project source directory. This is okay, since
753 // we keep the build data in a "container" directory. However, we must make sure we don't
754 // match any generated files therein as source files.
755 if (baseDir.startsWith(buildDir))
756 return;
757
758 dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified());
759
760 QStringList changed_parts = parts;
761 bool recursive = false;
762 QString part = changed_parts.takeFirst();
763
764 while (part == QStringLiteral("**")) {
765 recursive = true;
766
767 if (changed_parts.empty()) {
768 part = StringConstants::star();
769 break;
770 }
771
772 part = changed_parts.takeFirst();
773 }
774
775 const bool isDir = !changed_parts.empty();
776
777 const QString &filePattern = part;
778 const QDirIterator::IteratorFlags itFlags = recursive
779 ? QDirIterator::Subdirectories
780 : QDirIterator::NoIteratorFlags;
781 QDir::Filters itFilters = isDir
782 ? QDir::Dirs
783 : QDir::Files | QDir::System
784 | QDir::Dirs; // This one is needed to get symbolic links to directories
785
786 if (isDir && !FileInfo::isPattern(filePattern))
787 itFilters |= QDir::Hidden;
788 if (filePattern != StringConstants::dotDot() && filePattern != StringConstants::dot())
789 itFilters |= QDir::NoDotAndDotDot;
790
791 QDirIterator it(baseDir, QStringList(filePattern), itFilters, itFlags);
792 while (it.hasNext()) {
793 const QString filePath = it.next();
794 const QString parentDir = it.fileInfo().dir().path();
795 if (parentDir.startsWith(buildDir))
796 continue; // See above.
797 if (!isDir && it.fileInfo().isDir() && !it.fileInfo().isSymLink())
798 continue;
799 if (isDir) {
800 expandPatterns(result, group, changed_parts, filePath, buildDir);
801 } else {
802 if (parentDir != baseDir)
803 dirTimeStamps.emplace_back(parentDir, FileInfo(baseDir).lastModified());
804 result += QDir::cleanPath(filePath);
805 }
806 }
807 }
808
809 template<typename L>
listToMap(const L & list)810 QMap<QString, typename L::value_type> listToMap(const L &list)
811 {
812 using V = typename L::value_type;
813 QMap<QString, V> map;
814 for (const V &elem : list)
815 map.insert(keyFromElem(elem), elem);
816 return map;
817 }
818
819 template<typename L>
listsAreEqual(const L & l1,const L & l2)820 bool listsAreEqual(const L &l1, const L &l2)
821 {
822 if (l1.size() != l2.size())
823 return false;
824 using V = typename L::value_type;
825 const QMap<QString, V> map1 = listToMap(l1);
826 const QMap<QString, V> map2 = listToMap(l2);
827 for (const QString &key : map1.keys()) {
828 const V &value2 = map2.value(key);
829 if (!value2)
830 return false;
831 if (!equals(map1.value(key).get(), value2.get()))
832 return false;
833 }
834 return true;
835 }
836
keyFromElem(const SourceArtifactPtr & sa)837 QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; }
keyFromElem(const RulePtr & r)838 QString keyFromElem(const RulePtr &r) {
839 QString key = r->toString() + r->prepareScript.sourceCode();
840 if (r->outputArtifactsScript.isValid())
841 key += r->outputArtifactsScript.sourceCode();
842 for (const auto &a : r->artifacts)
843 key += a->filePath;
844 return key;
845 }
846
keyFromElem(const ArtifactPropertiesPtr & ap)847 QString keyFromElem(const ArtifactPropertiesPtr &ap)
848 {
849 QStringList lst = ap->fileTagsFilter().toStringList();
850 lst.sort();
851 return lst.join(QLatin1Char(','));
852 }
853
operator ==(const SourceArtifactInternal & sa1,const SourceArtifactInternal & sa2)854 bool operator==(const SourceArtifactInternal &sa1, const SourceArtifactInternal &sa2)
855 {
856 return sa1.absoluteFilePath == sa2.absoluteFilePath
857 && sa1.fileTags == sa2.fileTags
858 && sa1.overrideFileTags == sa2.overrideFileTags
859 && sa1.targetOfModule == sa2.targetOfModule
860 && !sa1.properties == !sa2.properties
861 && *sa1.properties == *sa2.properties;
862 }
863
operator ==(const Rule & r1,const Rule & r2)864 bool operator==(const Rule &r1, const Rule &r2)
865 {
866 if (r1.artifacts.size() != r2.artifacts.size())
867 return false;
868 for (size_t i = 0; i < r1.artifacts.size(); ++i) {
869 if (!equals(r1.artifacts.at(i).get(), r2.artifacts.at(i).get()))
870 return false;
871 }
872
873 return r1.module->name == r2.module->name
874 && r1.prepareScript == r2.prepareScript
875 && r1.outputArtifactsScript == r2.outputArtifactsScript
876 && r1.inputs == r2.inputs
877 && r1.outputFileTags == r2.outputFileTags
878 && r1.auxiliaryInputs == r2.auxiliaryInputs
879 && r1.excludedInputs == r2.excludedInputs
880 && r1.inputsFromDependencies == r2.inputsFromDependencies
881 && r1.explicitlyDependsOn == r2.explicitlyDependsOn
882 && r1.explicitlyDependsOnFromDependencies == r2.explicitlyDependsOnFromDependencies
883 && r1.multiplex == r2.multiplex
884 && r1.requiresInputs == r2.requiresInputs
885 && r1.alwaysRun == r2.alwaysRun;
886 }
887
ruleListsAreEqual(const std::vector<RulePtr> & l1,const std::vector<RulePtr> & l2)888 bool ruleListsAreEqual(const std::vector<RulePtr> &l1, const std::vector<RulePtr> &l2)
889 {
890 return listsAreEqual(l1, l2);
891 }
892
operator ==(const RuleArtifact & a1,const RuleArtifact & a2)893 bool operator==(const RuleArtifact &a1, const RuleArtifact &a2)
894 {
895 return a1.filePath == a2.filePath
896 && a1.fileTags == a2.fileTags
897 && a1.alwaysUpdated == a2.alwaysUpdated
898 && rangeTo<Set<RuleArtifact::Binding>>(a1.bindings) ==
899 rangeTo<Set<RuleArtifact::Binding>>(a2.bindings);
900 }
901
operator ==(const RuleArtifact::Binding & b1,const RuleArtifact::Binding & b2)902 bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2)
903 {
904 return b1.code == b2.code && b1.name == b2.name;
905 }
906
qHash(const RuleArtifact::Binding & b)907 QHashValueType qHash(const RuleArtifact::Binding &b)
908 {
909 return qHash(std::make_pair(b.code, b.name.join(QLatin1Char(','))));
910 }
911
artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> & l1,const std::vector<ArtifactPropertiesPtr> & l2)912 bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1,
913 const std::vector<ArtifactPropertiesPtr> &l2)
914 {
915 return listsAreEqual(l1, l2);
916 }
917
multiplexIdToString(const QString & id)918 QString multiplexIdToString(const QString &id)
919 {
920 return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
921 }
922
operator ==(const PrivateScriptFunction & a,const PrivateScriptFunction & b)923 bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b)
924 {
925 return equals(a.m_sharedData.get(), b.m_sharedData.get());
926 }
927
operator ==(const ExportedProperty & p1,const ExportedProperty & p2)928 bool operator==(const ExportedProperty &p1, const ExportedProperty &p2)
929 {
930 return p1.fullName == p2.fullName
931 && p1.type == p2.type
932 && p1.sourceCode == p2.sourceCode
933 && p1.isBuiltin == p2.isBuiltin;
934 }
935
operator ==(const ExportedModuleDependency & d1,const ExportedModuleDependency & d2)936 bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2)
937 {
938 return d1.name == d2.name && d1.moduleProperties == d2.moduleProperties;
939 }
940
equals(const std::vector<ExportedItemPtr> & l1,const std::vector<ExportedItemPtr> & l2)941 bool equals(const std::vector<ExportedItemPtr> &l1, const std::vector<ExportedItemPtr> &l2)
942 {
943 static const auto cmp = [](const ExportedItemPtr &p1, const ExportedItemPtr &p2) {
944 return *p1 == *p2;
945 };
946 return l1.size() == l2.size() && std::equal(l1.cbegin(), l1.cend(), l2.cbegin(), cmp);
947 }
948
operator ==(const ExportedItem & i1,const ExportedItem & i2)949 bool operator==(const ExportedItem &i1, const ExportedItem &i2)
950 {
951 return i1.name == i2.name
952 && i1.properties == i2.properties
953 && equals(i1.children, i2.children);
954 }
955
operator ==(const ExportedModule & m1,const ExportedModule & m2)956 bool operator==(const ExportedModule &m1, const ExportedModule &m2)
957 {
958 static const auto depMapsEqual = [](const QMap<ResolvedProductConstPtr, QVariantMap> &m1,
959 const QMap<ResolvedProductConstPtr, QVariantMap> &m2) {
960 if (m1.size() != m2.size())
961 return false;
962 for (auto it1 = m1.cbegin(), it2 = m2.cbegin(); it1 != m1.cend(); ++it1, ++it2) {
963 if (it1.key()->name != it2.key()->name)
964 return false;
965 if (it1.value() != it2.value())
966 return false;
967 }
968 return true;
969 };
970
971 return m1.propertyValues == m2.propertyValues
972 && m1.modulePropertyValues == m2.modulePropertyValues
973 && equals(m1.children, m2.children)
974 && m1.m_properties == m2.m_properties
975 && m1.importStatements == m2.importStatements
976 && m1.productDependencies.size() == m2.productDependencies.size()
977 && m1.productDependencies == m2.productDependencies
978 && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters);
979 }
980
981 } // namespace Internal
982 } // namespace qbs
983