/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qbs. ** ** 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 The Qt Company. For licensing terms and ** conditions see http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/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 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "language.h" #include "artifactproperties.h" #include "propertymapinternal.h" #include "resolvedfilecontext.h" #include "scriptengine.h" #include #include #include #include #include // TODO: Move to language? #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace qbs { namespace Internal { template bool equals(const T *v1, const T *v2) { if (v1 == v2) return true; if (!v1 != !v2) return false; return *v1 == *v2; } FileTagger::FileTagger(const QStringList &patterns, const FileTags &fileTags) : m_fileTags(fileTags) { setPatterns(patterns); } void FileTagger::setPatterns(const QStringList &patterns) { m_patterns.clear(); foreach (const QString &pattern, patterns) { QBS_CHECK(!pattern.isEmpty()); m_patterns << QRegExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); } } /*! * \class FileTagger * \brief The \c FileTagger class maps 1:1 to the respective item in a qbs source file. */ void FileTagger::load(PersistentPool &pool) { setPatterns(pool.idLoadStringList()); pool.stream() >> m_fileTags; } void FileTagger::store(PersistentPool &pool) const { QStringList patterns; foreach (const QRegExp ®Exp, m_patterns) patterns << regExp.pattern(); pool.storeStringList(patterns); pool.stream() << m_fileTags; } /*! * \class SourceArtifact * \brief The \c SourceArtifact class represents a source file. * Everything except the file path is inherited from the surrounding \c ResolvedGroup. * (TODO: Not quite true. Artifacts in transformers will be generated by the transformer, but are * still represented as source artifacts. We may or may not want to change this; if we do, * SourceArtifact could simply have a back pointer to the group in addition to the file path.) * \sa ResolvedGroup */ void SourceArtifactInternal::load(PersistentPool &pool) { absoluteFilePath = pool.idLoadString(); pool.stream() >> fileTags; pool.stream() >> overrideFileTags; properties = pool.idLoadS(); } void SourceArtifactInternal::store(PersistentPool &pool) const { pool.storeString(absoluteFilePath); pool.stream() << fileTags; pool.stream() << overrideFileTags; pool.store(properties); } void SourceWildCards::load(PersistentPool &pool) { prefix = pool.idLoadString(); patterns = pool.idLoadStringList(); excludePatterns = pool.idLoadStringList(); pool.loadContainerS(files); } void SourceWildCards::store(PersistentPool &pool) const { pool.storeString(prefix); pool.storeStringList(patterns); pool.storeStringList(excludePatterns); pool.storeContainer(files); } /*! * \class ResolvedGroup * \brief The \c ResolvedGroup class corresponds to the Group item in a qbs source file. */ /*! * \variable ResolvedGroup::files * \brief The files listed in the group item's "files" binding. * Note that these do not include expanded wildcards. */ /*! * \variable ResolvedGroup::wildcards * \brief Represents the wildcard elements in this group's "files" binding. * If no wildcards are specified there, this variable is null. * \sa SourceWildCards */ /*! * \brief Returns all files specified in the group item as source artifacts. * This includes the expanded list of wildcards. */ QList ResolvedGroup::allFiles() const { QList lst = files; if (wildcards) lst.append(wildcards->files); return lst; } void ResolvedGroup::load(PersistentPool &pool) { name = pool.idLoadString(); pool.stream() >> enabled; location.load(pool); prefix = pool.idLoadString(); pool.loadContainerS(files); wildcards = pool.idLoadS(); properties = pool.idLoadS(); pool.stream() >> fileTags >> overrideTags; } void ResolvedGroup::store(PersistentPool &pool) const { pool.storeString(name); pool.stream() << enabled; location.store(pool); pool.storeString(prefix); pool.storeContainer(files); pool.store(wildcards); pool.store(properties); pool.stream() << fileTags << overrideTags; } /*! * \class RuleArtifact * \brief The \c RuleArtifact class represents an Artifact item encountered in the context * of a Rule item. * When applying the rule, one \c Artifact object will be constructed from each \c RuleArtifact * object. During that process, the \c RuleArtifact's bindings are evaluated and the results * are inserted into the corresponding \c Artifact's properties. * \sa Rule */ void RuleArtifact::load(PersistentPool &pool) { pool.stream() >> filePath >> fileTags >> alwaysUpdated; location.load(pool); filePathLocation.load(pool); int i; pool.stream() >> i; bindings.clear(); bindings.reserve(i); Binding binding; for (; --i >= 0;) { binding.name = pool.idLoadStringList(); binding.code = pool.idLoadString(); binding.location.load(pool); bindings += binding; } } void RuleArtifact::store(PersistentPool &pool) const { pool.stream() << filePath << fileTags << alwaysUpdated; location.store(pool); filePathLocation.store(pool); pool.stream() << bindings.count(); for (int i = bindings.count(); --i >= 0;) { const Binding &binding = bindings.at(i); pool.storeStringList(binding.name); pool.storeString(binding.code); binding.location.store(pool); } } /*! * \class ScriptFunction * \brief The \c ScriptFunction class represents the JavaScript code found in the "prepare" binding * of a \c Rule or \c Transformer item in a qbs file. * \sa Rule * \sa ResolvedTransformer */ ScriptFunction::ScriptFunction() { } ScriptFunction::~ScriptFunction() { } /*! * \variable ScriptFunction::script * \brief The actual Javascript code, taken verbatim from the qbs source file. */ /*! * \variable ScriptFunction::location * \brief The exact location of the script in the qbs source file. * This is mostly needed for diagnostics. */ bool ScriptFunction::isValid() const { return location.line() != -1; } void ScriptFunction::load(PersistentPool &pool) { sourceCode = pool.idLoadString(); argumentNames = pool.idLoadStringList(); location.load(pool); fileContext = pool.idLoadS(); } void ScriptFunction::store(PersistentPool &pool) const { pool.storeString(sourceCode); pool.storeStringList(argumentNames); location.store(pool); pool.store(fileContext); } bool operator==(const ScriptFunction &a, const ScriptFunction &b) { return a.sourceCode == b.sourceCode && a.location == b.location && a.argumentNames == b.argumentNames && equals(a.fileContext.data(), b.fileContext.data()); } void ResolvedModule::load(PersistentPool &pool) { name = pool.idLoadString(); moduleDependencies = pool.idLoadStringList(); setupBuildEnvironmentScript = pool.idLoadS(); setupRunEnvironmentScript = pool.idLoadS(); } void ResolvedModule::store(PersistentPool &pool) const { pool.storeString(name); pool.storeStringList(moduleDependencies); pool.store(setupBuildEnvironmentScript); pool.store(setupRunEnvironmentScript); } bool operator==(const ResolvedModule &m1, const ResolvedModule &m2) { return m1.name == m2.name && m1.moduleDependencies.toSet() == m2.moduleDependencies.toSet() && equals(m1.setupBuildEnvironmentScript.data(), m2.setupBuildEnvironmentScript.data()) && equals(m1.setupRunEnvironmentScript.data(), m2.setupRunEnvironmentScript.data()); } QString Rule::toString() const { QStringList outputTagsSorted = collectedOutputFileTags().toStringList(); outputTagsSorted.sort(); FileTags inputTags = inputs; inputTags.unite(inputsFromDependencies); QStringList inputTagsSorted = inputTags.toStringList(); inputTagsSorted.sort(); return QLatin1Char('[') + outputTagsSorted.join(QLatin1Char(',')) + QLatin1String("][") + inputTagsSorted.join(QLatin1Char(',')) + QLatin1Char(']'); } bool Rule::acceptsAsInput(Artifact *artifact) const { return artifact->fileTags().matches(inputs); } FileTags Rule::staticOutputFileTags() const { FileTags result; foreach (const RuleArtifactConstPtr &artifact, artifacts) result.unite(artifact->fileTags); return result; } FileTags Rule::collectedOutputFileTags() const { return outputFileTags.isEmpty() ? staticOutputFileTags() : outputFileTags; } bool Rule::isDynamic() const { return outputArtifactsScript->isValid(); } void Rule::load(PersistentPool &pool) { name = pool.idLoadString(); prepareScript = pool.idLoadS(); outputArtifactsScript = pool.idLoadS(); module = pool.idLoadS(); pool.stream() >> inputs >> outputFileTags >> auxiliaryInputs >> excludedAuxiliaryInputs >> inputsFromDependencies >> explicitlyDependsOn >> multiplex >> alwaysRun; pool.loadContainerS(artifacts); } void Rule::store(PersistentPool &pool) const { pool.storeString(name); pool.store(prepareScript); pool.store(outputArtifactsScript); pool.store(module); pool.stream() << inputs << outputFileTags << auxiliaryInputs << excludedAuxiliaryInputs << inputsFromDependencies << explicitlyDependsOn << multiplex << alwaysRun; pool.storeContainer(artifacts); } ResolvedProduct::ResolvedProduct() : enabled(true) { } ResolvedProduct::~ResolvedProduct() { } void ResolvedProduct::accept(BuildGraphVisitor *visitor) const { if (!buildData) return; foreach (BuildGraphNode * const node, buildData->roots) node->accept(visitor); } /*! * \brief Returns all files of all groups as source artifacts. * This includes the expanded list of wildcards. */ QList ResolvedProduct::allFiles() const { QList lst; foreach (const GroupConstPtr &group, groups) lst += group->allFiles(); return lst; } /*! * \brief Returns all files of all enabled groups as source artifacts. * \sa ResolvedProduct::allFiles() */ QList ResolvedProduct::allEnabledFiles() const { QList lst; foreach (const GroupConstPtr &group, groups) { if (group->enabled) lst += group->allFiles(); } return lst; } FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const { FileTags result; foreach (FileTaggerConstPtr tagger, fileTaggers) { foreach (const QRegExp &pattern, tagger->patterns()) { if (FileInfo::globMatches(pattern, fileName)) { result.unite(tagger->fileTags()); break; } } } return result; } void ResolvedProduct::load(PersistentPool &pool) { pool.stream() >> enabled >> fileTags; name = pool.idLoadString(); profile = pool.idLoadString(); targetName = pool.idLoadString(); sourceDirectory = pool.idLoadString(); destinationDirectory = pool.idLoadString(); location.load(pool); productProperties = pool.loadVariantMap(); moduleProperties = pool.idLoadS(); pool.loadContainerS(rules); pool.loadContainerS(dependencies); pool.loadContainerS(fileTaggers); pool.loadContainerS(modules); pool.loadContainerS(transformers); pool.loadContainerS(scanners); pool.loadContainerS(groups); pool.loadContainerS(artifactProperties); buildData.reset(pool.idLoad()); } void ResolvedProduct::store(PersistentPool &pool) const { pool.stream() << enabled << fileTags; pool.storeString(name); pool.storeString(profile); pool.storeString(targetName); pool.storeString(sourceDirectory); pool.storeString(destinationDirectory); location.store(pool); pool.store(productProperties); pool.store(moduleProperties); pool.storeContainer(rules); pool.storeContainer(dependencies); pool.storeContainer(fileTaggers); pool.storeContainer(modules); pool.storeContainer(transformers); pool.storeContainer(scanners); pool.storeContainer(groups); pool.storeContainer(artifactProperties); pool.store(buildData.data()); } QList topSortModules(const QHash > &moduleChildren, const QList &modules, QSet &seenModuleNames) { QList result; foreach (const ResolvedModule *m, modules) { if (m->name.isNull()) continue; result.append(topSortModules(moduleChildren, moduleChildren.value(m), seenModuleNames)); if (!seenModuleNames.contains(m->name)) { seenModuleNames.insert(m->name); result.append(m); } } return result; } enum EnvType { BuildEnv, RunEnv }; static bool findModuleMapRecursively_impl(const QVariantMap &cfg, const QString &moduleName, QVariantMap *result) { for (QVariantMap::const_iterator it = cfg.constBegin(); it != cfg.constEnd(); ++it) { if (it.key() == moduleName) { *result = it.value().toMap(); return true; } if (findModuleMapRecursively_impl(it.value().toMap().value(QStringLiteral("modules")).toMap(), moduleName, result)) { return true; } } return false; } static QVariantMap findModuleMapRecursively(const QVariantMap &cfg, const QString &moduleName) { QVariantMap result; findModuleMapRecursively_impl(cfg, moduleName, &result); return result; } static QProcessEnvironment getProcessEnvironment(ScriptEngine *engine, EnvType envType, const QList &modules, const PropertyMapConstPtr &productConfiguration, const QProcessEnvironment &env) { QMap moduleMap; foreach (const ResolvedModuleConstPtr &module, modules) moduleMap.insert(module->name, module.data()); QHash > moduleParents; QHash > moduleChildren; foreach (ResolvedModuleConstPtr module, modules) { foreach (const QString &moduleName, module->moduleDependencies) { const ResolvedModule * const depmod = moduleMap.value(moduleName); QBS_ASSERT(depmod, return env); moduleParents[depmod].append(module.data()); moduleChildren[module.data()].append(depmod); } } QList rootModules; foreach (ResolvedModuleConstPtr module, modules) { if (moduleParents.value(module.data()).isEmpty()) { QBS_ASSERT(module, return env); rootModules.append(module.data()); } } QProcessEnvironment procenv = env; { QVariant v; v.setValue(&procenv); engine->setProperty("_qbs_procenv", v); } QScriptValue scope = engine->newObject(); QSet seenModuleNames; QList topSortedModules = topSortModules(moduleChildren, rootModules, seenModuleNames); foreach (const ResolvedModule *module, topSortedModules) { if ((envType == BuildEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty()) || (envType == RunEnv && module->setupBuildEnvironmentScript->sourceCode.isEmpty() && module->setupRunEnvironmentScript->sourceCode.isEmpty())) continue; ScriptFunctionConstPtr setupScript; if (envType == BuildEnv) { setupScript = module->setupBuildEnvironmentScript; } else { if (!module->setupRunEnvironmentScript) setupScript = module->setupBuildEnvironmentScript; else setupScript = module->setupRunEnvironmentScript; } // handle imports engine->import(setupScript->fileContext, scope, scope); JsExtensions::setupExtensions(setupScript->fileContext->jsExtensions(), scope); // expose properties of direct module dependencies QScriptValue scriptValue; QVariantMap productModules = productConfiguration->value() .value(QLatin1String("modules")).toMap(); foreach (const ResolvedModule * const depmod, moduleChildren.value(module)) { scriptValue = engine->newObject(); QVariantMap moduleCfg = productModules.value(depmod->name).toMap(); for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) scriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); scope.setProperty(depmod->name, scriptValue); } // expose the module's properties QVariantMap moduleCfg = findModuleMapRecursively(productModules, module->name); for (QVariantMap::const_iterator it = moduleCfg.constBegin(); it != moduleCfg.constEnd(); ++it) scope.setProperty(it.key(), engine->toScriptValue(it.value())); QScriptContext *ctx = engine->currentContext(); ctx->pushScope(scope); scriptValue = engine->evaluate(setupScript->sourceCode + QLatin1String("()")); ctx->popScope(); if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) { QString envTypeStr = (envType == BuildEnv ? QLatin1String("build") : QLatin1String("run")); throw ErrorInfo(Tr::tr("Error while setting up %1 environment: %2") .arg(envTypeStr, engine->lastErrorString(scriptValue)), setupScript->location); } } engine->setProperty("_qbs_procenv", QVariant()); return procenv; } void ResolvedProduct::setupBuildEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const { if (!buildEnvironment.isEmpty()) return; buildEnvironment = getProcessEnvironment(engine, BuildEnv, modules, moduleProperties, env); } void ResolvedProduct::setupRunEnvironment(ScriptEngine *engine, const QProcessEnvironment &env) const { if (!runEnvironment.isEmpty()) return; runEnvironment = getProcessEnvironment(engine, RunEnv, modules, moduleProperties, env); } void ResolvedProduct::registerArtifactWithChangedInputs(Artifact *artifact) { QBS_CHECK(buildData); QBS_CHECK(artifact->product == this); QBS_CHECK(artifact->transformer); if (artifact->transformer->rule->multiplex) { // Reapplication of rules only makes sense for multiplex rules (e.g. linker). buildData->artifactsWithChangedInputsPerRule[artifact->transformer->rule] += artifact; } } void ResolvedProduct::unregisterArtifactWithChangedInputs(Artifact *artifact) { QBS_CHECK(buildData); QBS_CHECK(artifact->product == this); QBS_CHECK(artifact->transformer); buildData->artifactsWithChangedInputsPerRule[artifact->transformer->rule] -= artifact; } void ResolvedProduct::unmarkForReapplication(const RuleConstPtr &rule) { QBS_CHECK(buildData); buildData->artifactsWithChangedInputsPerRule.remove(rule); } bool ResolvedProduct::isMarkedForReapplication(const RuleConstPtr &rule) const { return !buildData->artifactsWithChangedInputsPerRule.value(rule).isEmpty(); } ArtifactSet ResolvedProduct::lookupArtifactsByFileTag(const FileTag &tag) const { QBS_CHECK(buildData); return buildData->artifactsByFileTag.value(tag); } ArtifactSet ResolvedProduct::lookupArtifactsByFileTags(const FileTags &tags) const { QBS_CHECK(buildData); ArtifactSet set; for (const FileTag &tag : tags) set = set.unite(buildData->artifactsByFileTag.value(tag)); return set; } ArtifactSet ResolvedProduct::targetArtifacts() const { QBS_CHECK(buildData); ArtifactSet taSet; foreach (Artifact * const a, buildData->rootArtifacts()) { if (a->fileTags().matches(fileTags)) taSet << a; } return taSet; } TopLevelProject *ResolvedProduct::topLevelProject() const { return project->topLevelProject(); } QString ResolvedProduct::uniqueName(const QString &name, const QString &profile) { QBS_CHECK(!profile.isEmpty()); return name + QLatin1Char('.') + profile; } QString ResolvedProduct::uniqueName() const { return uniqueName(name, profile); } static QStringList findGeneratedFiles(const Artifact *base, bool recursive, const FileTags &tags) { QStringList result; foreach (const Artifact *parent, base->parentArtifacts()) { if (tags.isEmpty() || parent->fileTags().matches(tags)) result << parent->filePath(); if (recursive) result << findGeneratedFiles(parent, true, tags); } return result; } QStringList ResolvedProduct::generatedFiles(const QString &baseFile, bool recursive, const FileTags &tags) const { ProductBuildData *data = buildData.data(); if (!data) return QStringList(); foreach (const Artifact *art, ArtifactSet::fromNodeSet(data->nodes)) { if (art->filePath() == baseFile) return findGeneratedFiles(art, recursive, tags); } return QStringList(); } QString ResolvedProduct::deriveBuildDirectoryName(const QString &name, const QString &profile) { QString dirName = uniqueName(name, profile); const QByteArray hash = QCryptographicHash::hash(dirName.toUtf8(), QCryptographicHash::Sha1); return HostOsInfo::rfc1034Identifier(dirName) .append(QLatin1Char('.')) .append(QString::fromLatin1(hash.toHex().left(8))); } QString ResolvedProduct::buildDirectory() const { const QString result = productProperties.value(QLatin1String("buildDirectory")).toString(); QBS_CHECK(!result.isEmpty()); return result; } bool ResolvedProduct::isInParentProject(const ResolvedProductConstPtr &other) const { for (const ResolvedProject *otherParent = other->project.data(); otherParent; otherParent = otherParent->parentProject.data()) { if (otherParent == project.data()) return true; } return false; } bool ResolvedProduct::builtByDefault() const { return productProperties.value(QLatin1String("builtByDefault"), true).toBool(); } void ResolvedProduct::cacheExecutablePath(const QString &origFilePath, const QString &fullFilePath) { QMutexLocker locker(&m_executablePathCacheLock); m_executablePathCache.insert(origFilePath, fullFilePath); } QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const { QMutexLocker locker(&m_executablePathCacheLock); return m_executablePathCache.value(origFilePath); } ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(0) { } void ResolvedProject::accept(BuildGraphVisitor *visitor) const { foreach (const ResolvedProductPtr &product, products) product->accept(visitor); foreach (const ResolvedProjectPtr &subProject, subProjects) subProject->accept(visitor); } TopLevelProject *ResolvedProject::topLevelProject() { if (m_topLevelProject) return m_topLevelProject; TopLevelProject *tlp = dynamic_cast(this); if (tlp) { m_topLevelProject = tlp; return m_topLevelProject; } QBS_CHECK(!parentProject.isNull()); m_topLevelProject = parentProject->topLevelProject(); return m_topLevelProject; } QList ResolvedProject::allSubProjects() const { QList projectList = subProjects; foreach (const ResolvedProjectConstPtr &subProject, subProjects) projectList << subProject->allSubProjects(); return projectList; } QList ResolvedProject::allProducts() const { QList productList = products; foreach (const ResolvedProjectConstPtr &subProject, subProjects) productList << subProject->allProducts(); return productList; } void ResolvedProject::load(PersistentPool &pool) { name = pool.idLoadString(); location.load(pool); int count; pool.stream() >> enabled >> count; products.clear(); products.reserve(count); for (; --count >= 0;) { ResolvedProductPtr rProduct = pool.idLoadS(); if (rProduct->buildData) { foreach (BuildGraphNode * const node, rProduct->buildData->nodes) { node->product = rProduct; // restore parent links foreach (BuildGraphNode *child, node->children) child->parents.insert(node); } } products.append(rProduct); } pool.stream() >> count; subProjects.clear(); subProjects.reserve(count); for (; --count >= 0;) { ResolvedProjectPtr p = pool.idLoadS(); subProjects.append(p); } m_projectProperties = pool.loadVariantMap(); } void ResolvedProject::store(PersistentPool &pool) const { pool.storeString(name); location.store(pool); pool.stream() << enabled << products.count(); foreach (const ResolvedProductConstPtr &product, products) pool.store(product); pool.stream() << subProjects.count(); foreach (const ResolvedProjectConstPtr &project, subProjects) pool.store(project); pool.store(m_projectProperties); } TopLevelProject::TopLevelProject() : bgLocker(0), locked(false), lastResolveTime(FileTime::oldestTime()) { } TopLevelProject::~TopLevelProject() { delete bgLocker; } QString TopLevelProject::deriveId(const QString &profile, const QVariantMap &config) { const QVariantMap qbsProperties = config.value(QLatin1String("qbs")).toMap(); const QString buildVariant = qbsProperties.value(QLatin1String("buildVariant")).toString(); QString prefix = profile; if (prefix.isEmpty()) prefix = QLatin1String("no-profile"); return prefix + QLatin1Char('-') + buildVariant; } QString TopLevelProject::deriveBuildDirectory(const QString &buildRoot, const QString &id) { return buildRoot + QLatin1Char('/') + id; } void TopLevelProject::setBuildConfiguration(const QVariantMap &config) { m_buildConfiguration = config; m_id = deriveId(profile(), config); } QString TopLevelProject::profile() const { return projectProperties().value(QLatin1String("profile")).toString(); } QString TopLevelProject::buildGraphFilePath() const { return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); } void TopLevelProject::store(const Logger &logger) const { // TODO: Use progress observer here. if (!buildData) return; if (!buildData->isDirty) { logger.qbsDebug() << "[BG] build graph is unchanged in project " << id() << "."; return; } const QString fileName = buildGraphFilePath(); logger.qbsDebug() << "[BG] storing: " << fileName; PersistentPool pool(logger); PersistentPool::HeadData headData; headData.projectConfig = buildConfiguration(); pool.setHeadData(headData); pool.setupWriteStream(fileName); store(pool); pool.finalizeWriteStream(); buildData->isDirty = false; } void TopLevelProject::load(PersistentPool &pool) { ResolvedProject::load(pool); m_id = pool.idLoadString(); pool.stream() >> usedEnvironment; pool.stream() >> canonicalFilePathResults; pool.stream() >> fileExistsResults; pool.stream() >> directoryEntriesResults; pool.stream() >> fileLastModifiedResults; QHash envHash; pool.stream() >> envHash; for (QHash::const_iterator i = envHash.begin(); i != envHash.end(); ++i) environment.insert(i.key(), i.value()); pool.stream() >> profileConfigs; pool.stream() >> buildSystemFiles; pool.stream() >> lastResolveTime; buildData.reset(pool.idLoad()); QBS_CHECK(buildData); buildData->isDirty = false; } void TopLevelProject::store(PersistentPool &pool) const { ResolvedProject::store(pool); pool.storeString(m_id); pool.stream() << usedEnvironment << canonicalFilePathResults << fileExistsResults << directoryEntriesResults << fileLastModifiedResults; QHash envHash; foreach (const QString &key, environment.keys()) envHash.insert(key, environment.value(key)); pool.stream() << envHash; pool.stream() << profileConfigs; pool.stream() << buildSystemFiles; pool.stream() << lastResolveTime; pool.store(buildData.data()); } /*! * \class SourceWildCards * \brief Objects of the \c SourceWildCards class result from giving wildcards in a * \c ResolvedGroup's "files" binding. * \sa ResolvedGroup */ /*! * \variable SourceWildCards::prefix * \brief Inherited from the \c ResolvedGroup * \sa ResolvedGroup */ /*! * \variable SourceWildCards::patterns * \brief All elements of the \c ResolvedGroup's "files" binding that contain wildcards. * \sa ResolvedGroup */ /*! * \variable SourceWildCards::excludePatterns * \brief Corresponds to the \c ResolvedGroup's "excludeFiles" binding. * \sa ResolvedGroup */ /*! * \variable SourceWildCards::files * \brief The \c SourceArtifacts resulting from the expanded list of matching files. */ QSet SourceWildCards::expandPatterns(const GroupConstPtr &group, const QString &baseDir) const { QSet files = expandPatterns(group, patterns, baseDir); files -= expandPatterns(group, excludePatterns, baseDir); return files; } QSet SourceWildCards::expandPatterns(const GroupConstPtr &group, const QStringList &patterns, const QString &baseDir) const { QSet files; QString expandedPrefix = prefix; if (expandedPrefix.startsWith(QLatin1String("~/"))) expandedPrefix.replace(0, 1, QDir::homePath()); foreach (QString pattern, patterns) { pattern.prepend(expandedPrefix); pattern.replace(QLatin1Char('\\'), QLatin1Char('/')); QStringList parts = pattern.split(QLatin1Char('/'), QString::SkipEmptyParts); if (FileInfo::isAbsolute(pattern)) { QString rootDir; if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) { rootDir = parts.takeFirst(); if (!rootDir.endsWith(QLatin1Char('/'))) rootDir.append(QLatin1Char('/')); } else { rootDir = QLatin1Char('/'); } expandPatterns(files, group, parts, rootDir); } else { expandPatterns(files, group, parts, baseDir); } } return files; } static bool isQbsBuildDir(const QDir &dir) { return dir.exists(dir.dirName() + QLatin1String(".bg")); } void SourceWildCards::expandPatterns(QSet &result, const GroupConstPtr &group, const QStringList &parts, const QString &baseDir) const { // People might build directly in the project source directory. This is okay, since // we keep the build data in a "container" directory. However, we must make sure we don't // match any generated files therein as source files. if (isQbsBuildDir(baseDir)) return; QStringList changed_parts = parts; bool recursive = false; QString part = changed_parts.takeFirst(); while (part == QLatin1String("**")) { recursive = true; if (changed_parts.isEmpty()) { part = QLatin1String("*"); break; } part = changed_parts.takeFirst(); } const bool isDir = !changed_parts.isEmpty(); const QString &filePattern = part; const QDirIterator::IteratorFlags itFlags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; QDir::Filters itFilters = isDir ? QDir::Dirs : QDir::Files; if (isDir && !FileInfo::isPattern(filePattern)) itFilters |= QDir::Hidden; if (filePattern != QLatin1String("..") && filePattern != QLatin1String(".")) itFilters |= QDir::NoDotAndDotDot; QDirIterator it(baseDir, QStringList(filePattern), itFilters, itFlags); while (it.hasNext()) { const QString filePath = it.next(); if (isQbsBuildDir(it.fileInfo().dir())) continue; // See above. QBS_ASSERT(FileInfo(filePath).isDir() == isDir, break); if (isDir) expandPatterns(result, group, changed_parts, filePath); else result += QDir::cleanPath(filePath); } } void ResolvedTransformer::load(PersistentPool &pool) { module = pool.idLoadS(); pool.stream() >> inputs; pool.loadContainerS(outputs); transform = pool.idLoadS(); pool.stream() >> explicitlyDependsOn; pool.stream() >> alwaysRun; } void ResolvedTransformer::store(PersistentPool &pool) const { pool.store(module); pool.stream() << inputs; pool.storeContainer(outputs); pool.store(transform); pool.stream() << explicitlyDependsOn; pool.stream() << alwaysRun; } template QMap listToMap(const QList &list) { QMap map; foreach (const T &elem, list) map.insert(keyFromElem(elem), elem); return map; } template bool listsAreEqual(const QList &l1, const QList &l2) { if (l1.count() != l2.count()) return false; const QMap map1 = listToMap(l1); const QMap map2 = listToMap(l2); foreach (const QString &key, map1.keys()) { const T value2 = map2.value(key); if (!value2) return false; if (!equals(map1.value(key).data(), value2.data())) return false; } return true; } QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; } QString keyFromElem(const ResolvedTransformerPtr &t) { return t->transform->sourceCode; } QString keyFromElem(const RulePtr &r) { return r->toString(); } QString keyFromElem(const ArtifactPropertiesPtr &ap) { QStringList lst = ap->fileTagsFilter().toStringList(); lst.sort(); return lst.join(QLatin1Char(',')); } bool operator==(const SourceArtifactInternal &sa1, const SourceArtifactInternal &sa2) { return sa1.absoluteFilePath == sa2.absoluteFilePath && sa1.fileTags == sa2.fileTags && sa1.overrideFileTags == sa2.overrideFileTags && sa1.properties->value() == sa2.properties->value(); } bool sourceArtifactSetsAreEqual(const QList &l1, const QList &l2) { return listsAreEqual(l1, l2); } bool operator==(const ResolvedTransformer &t1, const ResolvedTransformer &t2) { return equals(t1.module.data(), t2.module.data()) && t1.inputs.toSet() == t2.inputs.toSet() && sourceArtifactSetsAreEqual(t1.outputs, t2.outputs) && equals(t1.transform.data(), t2.transform.data()) && t1.explicitlyDependsOn == t2.explicitlyDependsOn && t1.alwaysRun == t2.alwaysRun; } bool transformerListsAreEqual(const QList &l1, const QList &l2) { return listsAreEqual(l1, l2); } bool operator==(const Rule &r1, const Rule &r2) { if (r1.artifacts.count() != r2.artifacts.count()) return false; for (int i = 0; i < r1.artifacts.count(); ++i) { if (!equals(r1.artifacts.at(i).data(), r2.artifacts.at(i).data())) return false; } return r1.module->name == r2.module->name && equals(r1.prepareScript.data(), r2.prepareScript.data()) && equals(r1.outputArtifactsScript.data(), r2.outputArtifactsScript.data()) && r1.inputs == r2.inputs && r1.outputFileTags == r2.outputFileTags && r1.auxiliaryInputs == r2.auxiliaryInputs && r1.excludedAuxiliaryInputs == r2.excludedAuxiliaryInputs && r1.inputsFromDependencies == r2.inputsFromDependencies && r1.explicitlyDependsOn == r2.explicitlyDependsOn && r1.multiplex == r2.multiplex && r1.alwaysRun == r2.alwaysRun; } bool ruleListsAreEqual(const QList &l1, const QList &l2) { return listsAreEqual(l1, l2); } bool operator==(const RuleArtifact &a1, const RuleArtifact &a2) { return a1.filePath == a2.filePath && a1.fileTags == a2.fileTags && a1.alwaysUpdated == a2.alwaysUpdated && a1.bindings.toList().toSet() == a2.bindings.toList().toSet(); } bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) { return b1.code == b2.code && b1.name == b2.name; } uint qHash(const RuleArtifact::Binding &b) { return qHash(qMakePair(b.code, b.name.join(QLatin1Char(',')))); } bool artifactPropertyListsAreEqual(const QList &l1, const QList &l2) { return listsAreEqual(l1, l2); } void ResolvedScanner::load(PersistentPool &pool) { module = pool.idLoadS(); pool.stream() >> inputs >> recursive; searchPathsScript = pool.idLoadS(); scanScript = pool.idLoadS(); } void ResolvedScanner::store(PersistentPool &pool) const { pool.store(module); pool.stream() << inputs << recursive; pool.store(searchPathsScript); pool.store(scanScript); } } // namespace Internal } // namespace qbs