/************************************************************************** ** ** This file is part of the Qt Build Suite ** ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (info@qt.nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. ** Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** **************************************************************************/ #include "buildgraph.h" #include "artifact.h" #include "command.h" #include "rulegraph.h" #include "transformer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace qbs { BuildProduct::BuildProduct() : project(0) { } BuildProduct::~BuildProduct() { qDeleteAll(artifacts); } const QList &BuildProduct::topSortedRules() const { if (m_topSortedRules.isEmpty()) { RuleGraph ruleGraph; ruleGraph.build(rProduct->rules, rProduct->fileTags); // ruleGraph.dump(); m_topSortedRules = ruleGraph.topSorted(); // int i=0; // foreach (Rule::Ptr r, m_topSortedRules) // qDebug() << ++i << r->toString() << (void*)r.data(); } return m_topSortedRules; } BuildGraph::BuildGraph() { ProcessCommand::setupForJavaScript(&m_scriptEngine); JavaScriptCommand::setupForJavaScript(&m_scriptEngine); } BuildGraph::~BuildGraph() { } static void internalDump(BuildProduct *product, Artifact *n, QByteArray indent) { Artifact *artifactInProduct = product->artifacts.value(n->fileName); if (artifactInProduct && artifactInProduct != n) { fprintf(stderr,"\ntree corrupted. %p ('%s') resolves to %p ('%s')\n", n, qPrintable(n->fileName), product->artifacts.value(n->fileName), qPrintable(product->artifacts.value(n->fileName)->fileName)); abort(); } printf("%s", indent.constData()); printf("Artifact (%p) ", n); printf("%s%s %s [%s]", qPrintable(QString(toString(n->buildState).at(0))), artifactInProduct ? "" : " SBS", // SBS == side-by-side artifact from other product qPrintable(n->fileName), qPrintable(QStringList(n->fileTags.toList()).join(","))); printf("\n"); indent.append(" "); foreach (Artifact *child, n->children) { internalDump(product, child, indent); } } void BuildGraph::dump(BuildProduct::Ptr product) const { Q_ASSERT(product->artifacts.uniqueKeys() == product->artifacts.keys()); foreach (Artifact *n, product->artifacts) if (n->parents.isEmpty()) internalDump(product.data(), n, QByteArray()); } void BuildGraph::insert(BuildProduct::Ptr product, Artifact *n) const { insert(product.data(), n); } void BuildGraph::insert(BuildProduct *product, Artifact *n) const { Q_ASSERT(n->product == 0); Q_ASSERT(!n->fileName.isEmpty()); Q_ASSERT(!product->artifacts.contains(n->fileName)); #ifdef QT_DEBUG foreach (BuildProduct::Ptr otherProduct, product->project->buildProducts()) { if (otherProduct->artifacts.contains(n->fileName)) { if (n->artifactType == Artifact::Generated) { QString pl; pl.append(QString(" - %1 \n").arg(product->rProduct->name)); foreach (BuildProduct::Ptr p, product->project->buildProducts()) { if (p->artifacts.contains(n->fileName)) { pl.append(QString(" - %1 \n").arg(p->rProduct->name)); } } throw Error(QString ("BUG: already inserted in this project: %1\n%2" ) .arg(n->fileName) .arg(pl) ); } } } #endif product->artifacts.insert(n->fileName, n); n->product = product; product->project->markDirty(); if (qbsLogLevel(LoggerTrace)) qbsTrace("[BG] insert artifact '%s'", qPrintable(n->fileName)); } void BuildGraph::setupScriptEngineForProduct(QScriptEngine *scriptEngine, ResolvedProduct::Ptr product, Rule::Ptr rule, BuildGraph *bg) { ResolvedProduct *lastSetupProduct = (ResolvedProduct *)scriptEngine->property("lastSetupProduct").toULongLong(); QScriptValue productScriptValue; if (lastSetupProduct != product.data()) { scriptEngine->setProperty("lastSetupProduct", QVariant((qulonglong)product.data())); productScriptValue = scriptEngine->toScriptValue(product->configuration->value()); productScriptValue.setProperty("name", product->name); QString destinationDirectory = product->destinationDirectory; if (destinationDirectory.isEmpty()) destinationDirectory = "."; productScriptValue.setProperty("destinationDirectory", destinationDirectory); scriptEngine->globalObject().setProperty("product", productScriptValue, QScriptValue::ReadOnly); } else { productScriptValue = scriptEngine->globalObject().property("product"); } // If the Rule is in a Module, set up the 'module' property if (!rule->module->name.isEmpty()) productScriptValue.setProperty("module", productScriptValue.property("modules").property(rule->module->name)); if (rule) { for (JsImports::const_iterator it = rule->jsImports.begin(); it != rule->jsImports.end(); ++it) { foreach (const QString &fileName, it.value()) { QScriptValue jsImportValue; if (bg) jsImportValue = bg->m_jsImportCache.value(fileName, scriptEngine->undefinedValue()); if (jsImportValue.isUndefined()) { // qDebug() << "CACHE MISS" << fileName; QFile file(fileName); if (!file.open(QFile::ReadOnly)) throw Error(QString("Cannot open '%1'.").arg(fileName)); const QString sourceCode = QTextStream(&file).readAll(); QScriptProgram program(sourceCode, fileName); addJSImport(scriptEngine, program, jsImportValue); addJSImport(scriptEngine, jsImportValue, it.key()); if (bg) bg->m_jsImportCache.insert(fileName, jsImportValue); } else { // qDebug() << "CACHE HIT" << fileName; addJSImport(scriptEngine, jsImportValue, it.key()); } } } } else { // ### TODO remove the imports we added before } } void BuildGraph::setupScriptEngineForArtifact(BuildProduct *product, Artifact *artifact) { QString inFileName = FileInfo::fileName(artifact->fileName); QString inBaseName = FileInfo::baseName(artifact->fileName); QString inCompleteBaseName = FileInfo::completeBaseName(artifact->fileName); QString basedir; if (artifact->artifactType == Artifact::SourceFile) { QDir sourceDir(product->rProduct->sourceDirectory); basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->fileName)); } else { QDir buildDir(product->project->buildGraph()->buildDirectoryRoot() + product->project->resolvedProject()->id); basedir = FileInfo::path(buildDir.relativeFilePath(artifact->fileName)); } QScriptValue modulesScriptValue = artifact->configuration->cachedScriptValue(&m_scriptEngine); if (!modulesScriptValue.isValid()) { modulesScriptValue = m_scriptEngine.toScriptValue(artifact->configuration->value()); artifact->configuration->cacheScriptValue(&m_scriptEngine, modulesScriptValue); } modulesScriptValue = modulesScriptValue.property("modules"); // expose per file properties we want to use in an Artifact within a Rule QScriptValue scriptValue = m_scriptEngine.newObject(); scriptValue.setProperty("fileName", inFileName); scriptValue.setProperty("baseName", inBaseName); scriptValue.setProperty("completeBaseName", inCompleteBaseName); scriptValue.setProperty("baseDir", basedir); scriptValue.setProperty("modules", modulesScriptValue); QScriptValue globalObj = m_scriptEngine.globalObject(); globalObj.setProperty("input", scriptValue); } void BuildGraph::applyRules(BuildProduct *product, QMap > &artifactsPerFileTag) { foreach (Rule::Ptr rule, product->topSortedRules()) applyRule(product, artifactsPerFileTag, rule); } /*! * Runs a cycle detection on the BG and throws an exception if there is one. */ void BuildGraph::detectCycle(BuildProject *project) { QElapsedTimer *t = 0; if (qbsLogLevel(LoggerTrace)) { t = new QElapsedTimer; qbsTrace() << "[BG] running cycle detection on project '" + project->resolvedProject()->id + "'"; } foreach (BuildProduct::Ptr product, project->buildProducts()) foreach (Artifact *artifact, product->targetArtifacts) detectCycle(artifact); if (qbsLogLevel(LoggerTrace)) { qint64 elapsed = t->elapsed(); qbsTrace() << "[BG] cycle detection for project '" + project->resolvedProject()->id + "' took " << elapsed << " ms"; delete t; } } void BuildGraph::detectCycle(Artifact *a) { QSet done, currentBranch; detectCycle(a, done, currentBranch); } void BuildGraph::detectCycle(Artifact *v, QSet &done, QSet ¤tBranch) { currentBranch += v; for (ArtifactList::const_iterator it = v->children.begin(); it != v->children.end(); ++it) { Artifact *u = *it; if (currentBranch.contains(u)) throw Error("Cycle in build graph detected."); if (!done.contains(u)) detectCycle(u, done, currentBranch); } currentBranch -= v; done += v; } static AbstractCommand *createCommandFromScriptValue(const QScriptValue &scriptValue) { if (scriptValue.isUndefined() || !scriptValue.isValid()) return 0; AbstractCommand *cmdBase = 0; QString className = scriptValue.property("className").toString(); if (className == "Command") cmdBase = new ProcessCommand; else if (className == "JavaScriptCommand") cmdBase = new JavaScriptCommand; if (cmdBase) cmdBase->fillFromScriptValue(&scriptValue); return cmdBase; } void BuildGraph::applyRule(BuildProduct *product, QMap > &artifactsPerFileTag, Rule::Ptr rule) { setupScriptEngineForProduct(&m_scriptEngine, product->rProduct, rule, this); if (rule->isMultiplexRule()) { // apply the rule once for a set of inputs QSet inputArtifacts; foreach (const QString &fileTag, rule->inputs) inputArtifacts.unite(artifactsPerFileTag.value(fileTag)); if (!inputArtifacts.isEmpty()) applyRule(product, artifactsPerFileTag, rule, inputArtifacts); } else { // apply the rule once for each input QSet inputArtifacts; foreach (const QString &fileTag, rule->inputs) { foreach (Artifact *inputArtifact, artifactsPerFileTag.value(fileTag)) { inputArtifacts.insert(inputArtifact); applyRule(product, artifactsPerFileTag, rule, inputArtifacts); inputArtifacts.clear(); } } } } void BuildGraph::createOutputArtifact( BuildProduct *product, const Rule::Ptr &rule, const RuleArtifact::Ptr &ruleArtifact, const QSet &inputArtifacts, QList< QPair > *ruleArtifactArtifactMap, QList *outputArtifacts, QSharedPointer &transformer) { QScriptValue scriptValue = m_scriptEngine.evaluate(ruleArtifact->fileScript); if (scriptValue.isError() || m_scriptEngine.hasUncaughtException()) throw Error("Error in Rule.Artifact fileName: " + scriptValue.toString()); QString outputPath = scriptValue.toString(); outputPath.replace("..", "dotdot"); // don't let the output artifact "escape" its build dir outputPath = resolveOutPath(outputPath, product); Artifact *outputArtifact = product->artifacts.value(outputPath); if (outputArtifact) { if (outputArtifact->transformer && outputArtifact->transformer != transformer) { // This can happen when applying rules after scanning for additional file tags. // We just regenerate the transformer. if (qbsLogLevel(LoggerTrace)) qbsTrace("[BG] regenerating transformer for '%s'", qPrintable(fileName(outputArtifact))); transformer = outputArtifact->transformer; transformer->inputs += inputArtifacts; if (transformer->inputs.count() > 1 && !rule->isMultiplexRule()) { QString th = "[" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]"; QString e = tr("Conflicting rules for producing %1 %2 \n").arg(outputArtifact->fileName, th); th = "[" + rule->inputs.join(", ") + "] -> [" + QStringList(outputArtifact->fileTags.toList()).join(", ") + "]"; e += QString(" while trying to apply: %1:%2:%3 %4\n") .arg(rule->script->location.fileName) .arg(rule->script->location.line) .arg(rule->script->location.column) .arg(th); e += QString(" was already defined in: %1:%2:%3 %4\n") .arg(outputArtifact->transformer->rule->script->location.fileName) .arg(outputArtifact->transformer->rule->script->location.line) .arg(outputArtifact->transformer->rule->script->location.column) .arg(th); throw Error(e); } } outputArtifact->fileTags += ruleArtifact->fileTags.toSet(); } else { outputArtifact = new Artifact(product->project); outputArtifact->artifactType = Artifact::Generated; outputArtifact->fileName = outputPath; outputArtifact->fileTags = ruleArtifact->fileTags.toSet(); insert(product, outputArtifact); } if (rule->isMultiplexRule()) outputArtifact->configuration = product->rProduct->configuration; else outputArtifact->configuration = (*inputArtifacts.constBegin())->configuration; foreach (Artifact *inputArtifact, inputArtifacts) { Q_ASSERT(outputArtifact != inputArtifact); loggedConnect(outputArtifact, inputArtifact); } ruleArtifactArtifactMap->append(qMakePair(ruleArtifact.data(), outputArtifact)); outputArtifacts->append(outputArtifact); // create transformer if not already done so if (!transformer) { transformer = QSharedPointer(new Transformer); transformer->rule = rule; transformer->inputs = inputArtifacts; } outputArtifact->transformer = transformer; } void BuildGraph::applyRule(BuildProduct *product, QMap > &artifactsPerFileTag, Rule::Ptr rule, const QSet &inputArtifacts) { if (qbsLogLevel(LoggerDebug)) qbsDebug() << "[BG] apply rule " << rule->toString() << " " << toStringList(inputArtifacts).join(",\n "); QList< QPair > ruleArtifactArtifactMap; QList outputArtifacts; QSet usingArtifacts; foreach (BuildProduct *dep, product->usings) { foreach (Artifact *targetArtifact, dep->targetArtifacts) { ArtifactList sbsArtifacts = targetArtifact->sideBySideArtifacts; sbsArtifacts.insert(targetArtifact); foreach (Artifact *artifact, sbsArtifacts) { QString matchingTag; foreach (const QString &tag, rule->usings) { if (artifact->fileTags.contains(tag)) { matchingTag = tag; break; } } if (matchingTag.isEmpty()) continue; usingArtifacts.insert(artifact); } } } // create the output artifacts from the set of input artifacts QSharedPointer transformer; foreach (RuleArtifact::Ptr ruleArtifact, rule->artifacts) { if (!rule->isMultiplexRule()) { foreach (Artifact *inputArtifact, inputArtifacts) { setupScriptEngineForArtifact(product, inputArtifact); QSet oneInputArtifact; oneInputArtifact.insert(inputArtifact); createOutputArtifact(product, rule, ruleArtifact, oneInputArtifact, &ruleArtifactArtifactMap, &outputArtifacts, transformer); } } else { createOutputArtifact(product, rule, ruleArtifact, inputArtifacts, &ruleArtifactArtifactMap, &outputArtifacts, transformer); } } foreach (Artifact *outputArtifact, outputArtifacts) { // insert the output artifacts into the pool of artifacts foreach (const QString &fileTag, outputArtifact->fileTags) artifactsPerFileTag[fileTag].insert(outputArtifact); // connect artifacts that match the file tags in explicitlyDependsOn foreach (const QString &fileTag, rule->explicitlyDependsOn) foreach (Artifact *dependency, artifactsPerFileTag.value(fileTag)) loggedConnect(outputArtifact, dependency); // Transformer setup transformer->outputs.insert(outputArtifact); for (QSet::const_iterator it = usingArtifacts.constBegin(); it != usingArtifacts.constEnd(); ++it) { Artifact *dep = *it; loggedConnect(outputArtifact, dep); transformer->inputs.insert(dep); foreach (Artifact *sideBySideDep, dep->sideBySideArtifacts) { loggedConnect(outputArtifact, sideBySideDep); transformer->inputs.insert(sideBySideDep); } } m_artifactsThatMustGetNewTransformers -= outputArtifact; } // setup side-by-side artifacts if (outputArtifacts.count() > 1) foreach (Artifact *sbs1, outputArtifacts) foreach (Artifact *sbs2, outputArtifacts) if (sbs1 != sbs2) sbs1->sideBySideArtifacts.insert(sbs2); transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject()); // change the transformer outputs according to the bindings in Artifact QScriptValue scriptValue; for (int i=ruleArtifactArtifactMap.count(); --i >= 0;) { RuleArtifact *ra = ruleArtifactArtifactMap.at(i).first; if (ra->bindings.isEmpty()) continue; // expose attributes of this artifact Artifact *outputArtifact = ruleArtifactArtifactMap.at(i).second; outputArtifact->configuration = Configuration::Ptr(new Configuration(*outputArtifact->configuration)); // ### clean m_scriptEngine first? m_scriptEngine.globalObject().setProperty("fileName", m_scriptEngine.toScriptValue(outputArtifact->fileName), QScriptValue::ReadOnly); m_scriptEngine.globalObject().setProperty("fileTags", toScriptValue(&m_scriptEngine, outputArtifact->fileTags), QScriptValue::ReadOnly); QVariantMap artifactModulesCfg = outputArtifact->configuration->value().value("modules").toMap(); for (int i=0; i < ra->bindings.count(); ++i) { const QStringList &name = ra->bindings.at(i).first; const QString &code = ra->bindings.at(i).second; scriptValue = m_scriptEngine.evaluate(code); if (scriptValue.isError()) throw Error(QLatin1String("evaluating rule bindings: ") + scriptValue.toString()); setConfigProperty(artifactModulesCfg, name, scriptValue.toVariant()); } QVariantMap outputArtifactConfiguration = outputArtifact->configuration->value(); outputArtifactConfiguration.insert("modules", artifactModulesCfg); outputArtifact->configuration->setValue(outputArtifactConfiguration); } transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject()); // setup transform properties { const QVariantMap overriddenTransformProperties = product->rProduct->configuration->value().value("modules").toMap().value(rule->module->name).toMap().value(rule->objectId).toMap(); /* overriddenTransformProperties contains the rule's transform properties that have been overridden in the project file. For example, if you set cpp.compiler.defines in your project file, that property appears here. */ QMap::const_iterator it = rule->transformProperties.begin(); for (; it != rule->transformProperties.end(); ++it) { const QString &propertyName = it.key(); QScriptValue sv; if (overriddenTransformProperties.contains(propertyName)) { sv = m_scriptEngine.toScriptValue(overriddenTransformProperties.value(propertyName)); } else { const QScriptProgram &myProgram = it.value(); sv = m_scriptEngine.evaluate(myProgram); if (m_scriptEngine.hasUncaughtException()) { CodeLocation errorLocation; errorLocation.fileName = m_scriptEngine.uncaughtExceptionBacktrace().join("\n"); errorLocation.line = m_scriptEngine.uncaughtExceptionLineNumber(); throw Error(QLatin1String("transform property evaluation: ") + m_scriptEngine.uncaughtException().toString(), errorLocation); } else if (sv.isError()) { CodeLocation errorLocation(myProgram.fileName(), myProgram.firstLineNumber()); throw Error(QLatin1String("transform property evaluation: ") + sv.toString(), errorLocation); } } m_scriptEngine.globalObject().setProperty(propertyName, sv); } } createTransformerCommands(rule->script, transformer.data()); if (transformer->commands.isEmpty()) throw Error(QString("There's a rule without commands: %1.").arg(rule->toString()), rule->script->location); } void BuildGraph::createTransformerCommands(RuleScript::Ptr script, Transformer *transformer) { QScriptProgram &scriptProgram = m_scriptProgramCache[script->script]; if (scriptProgram.isNull()) scriptProgram = QScriptProgram(script->script); QScriptValue scriptValue = m_scriptEngine.evaluate(scriptProgram); if (m_scriptEngine.hasUncaughtException()) throw Error("evaluating prepare script: " + m_scriptEngine.uncaughtException().toString(), script->location); QList commands; if (scriptValue.isArray()) { const int count = scriptValue.property("length").toInt32(); for (qint32 i=0; i < count; ++i) { QScriptValue item = scriptValue.property(i); if (item.isValid() && !item.isUndefined()) { AbstractCommand *cmd = createCommandFromScriptValue(item); if (cmd) commands += cmd; } } } else { AbstractCommand *cmd = createCommandFromScriptValue(scriptValue); if (cmd) commands += cmd; } transformer->commands = commands; } QString BuildGraph::buildDirectoryRoot() const { Q_ASSERT(!m_outputDirectoryRoot.isEmpty()); QString path = FileInfo::resolvePath(m_outputDirectoryRoot, QLatin1String("build")); if (!path.endsWith('/')) path.append(QLatin1Char('/')); return path; } /* * c must be built before p * p ----> c * p.children = c * c.parents = p * * also: children means i depend on or i am produced by * parent means "produced by me" or "depends on me" */ void BuildGraph::connect(Artifact *p, Artifact *c) { Q_ASSERT(p != c); p->children.insert(c); c->parents.insert(p); p->project->markDirty(); } void BuildGraph::loggedConnect(Artifact *u, Artifact *v) { Q_ASSERT(u != v); if (qbsLogLevel(LoggerTrace)) qbsTrace("[BG] connect '%s' -> '%s'", qPrintable(fileName(u)), qPrintable(fileName(v))); connect(u, v); } static bool findPath(Artifact *u, Artifact *v, QList &path) { if (u == v) { path.append(v); return true; } for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) { if (findPath(*it, v, path)) { path.prepend(u); return true; } } return false; } static bool existsPath(Artifact *u, Artifact *v) { if (u == v) return true; for (ArtifactList::const_iterator it = u->children.begin(); it != u->children.end(); ++it) if (existsPath(*it, v)) return true; return false; } bool BuildGraph::safeConnect(Artifact *u, Artifact *v) { Q_ASSERT(u != v); if (qbsLogLevel(LoggerTrace)) qbsTrace("[BG] safeConnect: '%s' '%s'", qPrintable(fileName(u)), qPrintable(fileName(v))); if (existsPath(v, u)) { QList circle; findPath(v, u, circle); qbsTrace() << "[BG] safeConnect: circle detected " << toStringList(circle); return false; } connect(u, v); return true; } void BuildGraph::disconnect(Artifact *u, Artifact *v) { u->children.remove(v); v->parents.remove(u); } QSet BuildGraph::disconnect(Artifact *n) const { QSet r; if (n->children.count() == 1) { Artifact * c = *(n->children.begin()); c->parents.remove(n); n->children.clear(); r += n; foreach (Artifact * p, n->parents) { r += disconnect(p); p->children.remove(n); if (p->transformer) p->transformer->inputs.remove(n); } } return r; } void BuildGraph::remove(Artifact *artifact) const { if (qbsLogLevel(LoggerTrace)) qbsTrace() << "[BG] remove artifact " << fileName(artifact); if (artifact->artifactType == Artifact::Generated) QFile::remove(artifact->fileName); artifact->product->artifacts.remove(artifact->fileName); artifact->product->targetArtifacts.remove(artifact); foreach (Artifact *parent, artifact->parents) { parent->children.remove(artifact); if (parent->transformer) { parent->transformer->inputs.remove(artifact); m_artifactsThatMustGetNewTransformers += parent; } } foreach (Artifact *child, artifact->children) { child->parents.remove(artifact); } artifact->children.clear(); artifact->parents.clear(); artifact->project->markDirty(); } /** * Removes the artifact and all the artifacts that depend exclusively on it. * Example: if you remove a cpp artifact then the obj artifact is removed but * not the resulting application (if there's more then one cpp artifact). */ void BuildGraph::removeArtifactAndExclusiveDependents(Artifact *artifact, QList *removedArtifacts) { if (removedArtifacts) removedArtifacts->append(artifact); foreach (Artifact *parent, artifact->parents) { if (parent->children.count() == 1) removeArtifactAndExclusiveDependents(parent, removedArtifacts); } remove(artifact); } BuildProject::Ptr BuildGraph::resolveProject(ResolvedProject::Ptr rProject, QFutureInterface &futureInterface) { BuildProject::Ptr project = BuildProject::Ptr(new BuildProject(this)); project->setResolvedProject(rProject); foreach (ResolvedProduct::Ptr rProduct, rProject->products) { resolveProduct(project.data(), rProduct, futureInterface); } detectCycle(project.data()); return project; } BuildProduct::Ptr BuildGraph::resolveProduct(BuildProject *project, ResolvedProduct::Ptr rProduct, QFutureInterface &futureInterface) { BuildProduct::Ptr product = m_productCache.value(rProduct); if (product) return product; futureInterface.setProgressValue(futureInterface.progressValue() + 1); product = BuildProduct::Ptr(new BuildProduct); m_productCache.insert(rProduct, product); product->project = project; product->rProduct = rProduct; QMap > artifactsPerFileTag; foreach (ResolvedProduct::Ptr t2, rProduct->uses) { if (t2 == rProduct) { throw Error(tr("circular using")); } BuildProduct::Ptr referencedProduct = resolveProduct(project, t2, futureInterface); product->usings.append(referencedProduct.data()); } //add qbsFile artifact Artifact *qbsFileArtifact = product->artifacts.value(rProduct->qbsFile); if (!qbsFileArtifact) { qbsFileArtifact = new Artifact(project); qbsFileArtifact->artifactType = Artifact::SourceFile; qbsFileArtifact->fileName = rProduct->qbsFile; qbsFileArtifact->configuration = rProduct->configuration; insert(product, qbsFileArtifact); } qbsFileArtifact->fileTags.insert("qbs"); artifactsPerFileTag["qbs"].insert(qbsFileArtifact); // read sources foreach (SourceArtifact::Ptr sourceArtifact, rProduct->sources) { QString filePath = sourceArtifact->absoluteFilePath; if (product->artifacts.contains(filePath)) { // ignore duplicate artifacts continue; } Artifact *artifact = createArtifact(product, sourceArtifact); foreach (const QString &fileTag, artifact->fileTags) artifactsPerFileTag[fileTag].insert(artifact); } // read manually added transformers QList transformerOutputs; foreach (const ResolvedTransformer::Ptr rtrafo, rProduct->transformers) { QList inputArtifacts; foreach (const QString &inputFileName, rtrafo->inputs) { Artifact *artifact = product->artifacts.value(inputFileName); if (!artifact) throw Error(QString("Can't find artifact '%0' in the list of source files.").arg(inputFileName)); if (artifact->fileTags.isEmpty()) artifact->fileTags += "unknown"; inputArtifacts += artifact; } QSharedPointer transformer(new Transformer); transformer->inputs = inputArtifacts.toSet(); transformer->rule = Rule::Ptr(new Rule); transformer->rule->inputs = rtrafo->inputs; transformer->rule->jsImports = rtrafo->jsImports; transformer->rule->module = ResolvedModule::Ptr(new ResolvedModule); transformer->rule->module->name = rtrafo->module->name; transformer->rule->script = rtrafo->transform; foreach (SourceArtifact::Ptr sourceArtifact, rtrafo->outputs) { Artifact *outputArtifact = createArtifact(product, sourceArtifact); outputArtifact->artifactType = Artifact::Generated; outputArtifact->transformer = transformer; transformer->outputs += outputArtifact; transformerOutputs += outputArtifact; foreach (Artifact *inputArtifact, inputArtifacts) safeConnect(outputArtifact, inputArtifact); foreach (const QString &fileTag, outputArtifact->fileTags) artifactsPerFileTag[fileTag].insert(outputArtifact); RuleArtifact::Ptr ruleArtifact(new RuleArtifact); ruleArtifact->fileScript = outputArtifact->fileName; ruleArtifact->fileTags = outputArtifact->fileTags.toList(); transformer->rule->artifacts += ruleArtifact; } setupScriptEngineForProduct(&m_scriptEngine, rProduct, transformer->rule, this); transformer->setupInputs(&m_scriptEngine, m_scriptEngine.globalObject()); transformer->setupOutputs(&m_scriptEngine, m_scriptEngine.globalObject()); createTransformerCommands(rtrafo->transform, transformer.data()); if (transformer->commands.isEmpty()) throw Error(QString("There's a transformer without commands."), rtrafo->transform->location); } applyRules(product.data(), artifactsPerFileTag); QSet productArtifactCandidates; for (int i=0; i < product->rProduct->fileTags.count(); ++i) foreach (Artifact *artifact, artifactsPerFileTag.value(product->rProduct->fileTags.at(i))) if (artifact->artifactType == Artifact::Generated) productArtifactCandidates += artifact; if (productArtifactCandidates.isEmpty()) { // this should already be catched in the rule graph throw Error("The impossible happenend! The rules generate no product."); } foreach (Artifact *productArtifact, productArtifactCandidates) { product->targetArtifacts.insert(productArtifact); project->addBuildProduct(product); foreach (Artifact *trafoOutputArtifact, transformerOutputs) if (productArtifact != trafoOutputArtifact) loggedConnect(productArtifact, trafoOutputArtifact); } return product; } void BuildGraph::onProductChanged(BuildProduct::Ptr product, ResolvedProduct::Ptr changedProduct) { qbsDebug() << "[BG] product '" << product->rProduct->name << "' changed."; QMap > artifactsPerFileTag; QList addedArtifacts, artifactsToRemove; QHash oldArtifacts, newArtifacts; foreach (SourceArtifact::Ptr a, product->rProduct->sources) oldArtifacts.insert(a->absoluteFilePath, a); foreach (SourceArtifact::Ptr a, changedProduct->sources) { newArtifacts.insert(a->absoluteFilePath, a); if (!oldArtifacts.contains(a->absoluteFilePath)) { // artifact added qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' added to product " << product->rProduct->name; product->rProduct->sources.insert(a); addedArtifacts += createArtifact(product, a); } } foreach (SourceArtifact::Ptr a, product->rProduct->sources) { SourceArtifact::Ptr changedArtifact = newArtifacts.value(a->absoluteFilePath); if (!changedArtifact) { // artifact removed qbsDebug() << "[BG] artifact '" << a->absoluteFilePath << "' removed from product " << product->rProduct->name; Artifact *artifact = product->artifacts.value(a->absoluteFilePath); Q_ASSERT(artifact); removeArtifactAndExclusiveDependents(artifact, &artifactsToRemove); continue; } if (changedArtifact->fileTags != a->fileTags) { // artifact's filetags have changed qbsDebug() << "[BG] filetags have changed for artifact '" << a->absoluteFilePath << "' from " << a->fileTags << " to " << changedArtifact->fileTags; Artifact *artifact = product->artifacts.value(a->absoluteFilePath); Q_ASSERT(artifact); // handle added filetags foreach (const QString &addedFileTag, changedArtifact->fileTags - a->fileTags) artifactsPerFileTag[addedFileTag] += artifact; // handle removed filetags foreach (const QString &removedFileTag, a->fileTags - changedArtifact->fileTags) { artifact->fileTags -= removedFileTag; foreach (Artifact *parent, artifact->parents) { if (parent->transformer && parent->transformer->rule->inputs.contains(removedFileTag)) { // this parent has been created because of the removed filetag removeArtifactAndExclusiveDependents(parent, &artifactsToRemove); } } } } if (changedArtifact->configuration->value() != a->configuration->value()) // ### TODO { qWarning("Some properties changed. Consider rebuild or fix QBS-7. File name: %s", qPrintable(changedArtifact->absoluteFilePath)); QVariantMap m = a->configuration->value(); for (QVariantMap::iterator it = m.begin(); it != m.end(); ++it) { if (it.value() != changedArtifact->configuration->value().value(it.key())) { qDebug() << " old:" << it.value(); qDebug() << " new:" << changedArtifact->configuration->value().value(it.key()); } } } } // apply rules for new artifacts foreach (Artifact *artifact, addedArtifacts) foreach (const QString &ft, artifact->fileTags) artifactsPerFileTag[ft] += artifact; applyRules(product.data(), artifactsPerFileTag); // parents of removed artifacts must update their transformers foreach (Artifact *removedArtifact, artifactsToRemove) foreach (Artifact *parent, removedArtifact->parents) m_artifactsThatMustGetNewTransformers += parent; updateNodesThatMustGetNewTransformer(); // delete all removed artifacts physically from the disk foreach (Artifact *artifact, artifactsToRemove) { if (artifact->artifactType == Artifact::Generated) { qbsDebug() << "[BG] deleting stale artifact " << artifact->fileName; QFile::remove(artifact->fileName); } delete artifact; } } void BuildGraph::updateNodesThatMustGetNewTransformer() { foreach (Artifact *artifact, m_artifactsThatMustGetNewTransformers) updateNodeThatMustGetNewTransformer(artifact); m_artifactsThatMustGetNewTransformers.clear(); } void BuildGraph::updateNodeThatMustGetNewTransformer(Artifact *artifact) { Q_CHECK_PTR(artifact->transformer); if (qbsLogLevel(LoggerDebug)) qbsDebug() << "[BG] updating transformer for " << fileName(artifact); Rule::Ptr rule = artifact->transformer->rule; artifact->product->project->markDirty(); artifact->transformer = QSharedPointer(); QMap > artifactsPerFileTag; foreach (Artifact *input, artifact->children) foreach (const QString &fileTag, input->fileTags) artifactsPerFileTag[fileTag] += input; applyRule(artifact->product, artifactsPerFileTag, rule); } Artifact *BuildGraph::createArtifact(BuildProduct::Ptr product, SourceArtifact::Ptr sourceArtifact) { Artifact *artifact = new Artifact(product->project); artifact->artifactType = Artifact::SourceFile; artifact->fileName = sourceArtifact->absoluteFilePath; artifact->fileTags = sourceArtifact->fileTags; artifact->configuration = sourceArtifact->configuration; insert(product, artifact); return artifact; } QString BuildGraph::resolveOutPath(const QString &path, BuildProduct *product) const { QString result; QString buildDir = product->rProduct->buildDirectory; result = FileInfo::resolvePath(buildDir, path); Q_ASSERT(result.startsWith(buildDir)); result = QDir::cleanPath(result); return result; } void Transformer::load(PersistentPool &pool, PersistentObjectData &data) { QDataStream s(data); rule = pool.idLoadS(s); loadContainer(inputs, s, pool); loadContainer(outputs, s, pool); int count, cmdType; s >> count; commands.reserve(count); while (--count >= 0) { s >> cmdType; AbstractCommand *cmd = AbstractCommand::createByType(static_cast(cmdType)); cmd->load(s); commands += cmd; } } void Transformer::store(PersistentPool &pool, PersistentObjectData &data) const { QDataStream s(&data, QIODevice::WriteOnly); s << pool.store(rule); storeContainer(inputs, s, pool); storeContainer(outputs, s, pool); s << commands.count(); foreach (AbstractCommand *cmd, commands) { s << int(cmd->type()); cmd->store(s); } } void BuildProduct::load(PersistentPool &pool, PersistentObjectData &data) { QDataStream s(data); int i, count; // artifacts artifacts.clear(); s >> count; for (i = count; --i >= 0;) { QString key; s >> key; artifacts.insert(key, pool.idLoad(s)); } // edges for (i = count; --i >= 0;) { Artifact *artifact = pool.idLoad(s); int count2, j; s >> count2; artifact->parents.clear(); artifact->parents.reserve(count2); for (j = count2; --j >= 0;) artifact->parents.insert(pool.idLoad(s)); s >> count2; artifact->children.clear(); artifact->children.reserve(count2); for (j = count2; --j >= 0;) artifact->children.insert(pool.idLoad(s)); s >> count2; artifact->fileDependencies.clear(); artifact->fileDependencies.reserve(count2); for (j = count2; --j >= 0;) artifact->fileDependencies.insert(pool.idLoad(s)); s >> count2; artifact->sideBySideArtifacts.clear(); artifact->sideBySideArtifacts.reserve(count2); for (j = count2; --j >= 0;) artifact->sideBySideArtifacts.insert(pool.idLoad(s)); } // other data rProduct = pool.idLoadS(s); loadContainer(targetArtifacts, s, pool); loadContainer(usings, s, pool); } void BuildProduct::store(PersistentPool &pool, PersistentObjectData &data) const { QDataStream s(&data, QIODevice::WriteOnly); s << artifacts.count(); //artifacts for (QHash::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) { s << i.key(); PersistentObjectId artifactId = pool.store(i.value()); s << artifactId; } // edges for (QHash::const_iterator i = artifacts.constBegin(); i != artifacts.constEnd(); i++) { Artifact * artifact = i.value(); s << pool.store(artifact); s << artifact->parents.count(); foreach (Artifact * n, artifact->parents) s << pool.store(n); s << artifact->children.count(); foreach (Artifact * n, artifact->children) s << pool.store(n); s << artifact->fileDependencies.count(); foreach (Artifact * n, artifact->fileDependencies) s << pool.store(n); s << artifact->sideBySideArtifacts.count(); foreach (Artifact *n, artifact->sideBySideArtifacts) s << pool.store(n); } // other data s << pool.store(rProduct); storeContainer(targetArtifacts, s, pool); storeContainer(usings, s, pool); } BuildProject::BuildProject(BuildGraph *bg) : m_buildGraph(bg) , m_dirty(false) { } BuildProject::~BuildProject() { qDeleteAll(m_dependencyArtifacts); } static bool isConfigCompatible(const QVariantMap &userCfg, const QVariantMap &projectCfg) { QVariantMap::const_iterator it = userCfg.begin(); for (; it != userCfg.end(); ++it) { if (it.value().type() == QVariant::Map) { if (!isConfigCompatible(it.value().toMap(), projectCfg.value(it.key()).toMap())) return false; } else { QVariant value = projectCfg.value(it.key()); if (!value.isNull() && value != it.value()) { return false; } } } return true; } BuildProject::Ptr BuildProject::load(BuildGraph *bg, const FileTime &minTimeStamp, Configuration::Ptr cfg, const QStringList &loaderSearchPaths) { PersistentPool pool; QString fileName; QStringList bgFiles = storedProjectFiles(bg); foreach (const QString &fn, bgFiles) { if (!pool.load(fn, PersistentPool::LoadHeadData)) continue; PersistentPool::HeadData headData = pool.headData(); if (isConfigCompatible(cfg->value(), headData.projectConfig)) { fileName = fn; break; } } if (fileName.isNull()) { qbsDebug() << "[BG] No stored build graph found that's compatible to the desired build configuration."; return BuildProject::Ptr(); } BuildProject::Ptr project; qbsDebug() << "[BG] trying to load: " << fileName; FileInfo bgfi(fileName); if (!bgfi.exists()) { qbsDebug() << "[BG] stored build graph file does not exist"; return project; } if (!pool.load(fileName)) throw Error("Cannot load stored build graph."); project = BuildProject::Ptr(new BuildProject(bg)); PersistentObjectData data = pool.getData(0); project->load(pool, data); project->resolvedProject()->configuration = Configuration::Ptr(new Configuration); project->resolvedProject()->configuration->setValue(pool.headData().projectConfig); qbsDebug() << "[BG] stored project loaded."; bool projectFileChanged = false; if (bgfi.lastModified() < minTimeStamp) { projectFileChanged = true; } QList changedProducts; foreach (BuildProduct::Ptr product, project->buildProducts()) { FileInfo pfi(product->rProduct->qbsFile); if (!pfi.exists()) throw Error(QString("The product file '%1' is gone.").arg(product->rProduct->qbsFile)); if (bgfi.lastModified() < pfi.lastModified()) changedProducts += product; } if (projectFileChanged || !changedProducts.isEmpty()) { Loader ldr; ldr.setSearchPaths(loaderSearchPaths); ldr.loadProject(project->resolvedProject()->qbsFile); QFutureInterface dummyFutureInterface; ResolvedProject::Ptr changedProject = ldr.resolveProject(bg->buildDirectoryRoot(), cfg, dummyFutureInterface); if (!changedProject) { QString msg("Trying to load '%1' failed."); throw Error(msg.arg(project->resolvedProject()->qbsFile)); } if (projectFileChanged) { qWarning("[BG] project file changed: %s", qPrintable(project->resolvedProject()->qbsFile)); qWarning("[BG] ### HANDLING THAT PROPERLY IS NOT YET IMPLEMENTED"); qWarning("[BG] ### CONSIDER DELETING THE STORED BUILD GRAPH"); } QMap changedProductsMap; foreach (BuildProduct::Ptr product, changedProducts) { if (changedProductsMap.isEmpty()) foreach (ResolvedProduct::Ptr cp, changedProject->products) changedProductsMap.insert(cp->name, cp); bg->onProductChanged(product, changedProductsMap.value(product->rProduct->name)); } BuildGraph::detectCycle(project.data()); } return project; } void BuildProject::store() { if (!dirty()) { qbsDebug() << "[BG] build graph is unchanged in project " << resolvedProject()->id << "."; return; } const QString fileName = storedProjectFilePath(buildGraph(), resolvedProject()->id); qbsDebug() << "[BG] storing: " << fileName; PersistentPool pool; PersistentPool::HeadData headData; headData.projectConfig = resolvedProject()->configuration->value(); pool.setHeadData(headData); PersistentObjectData data; store(pool, data); pool.setData(0, data); pool.store(fileName); } QString BuildProject::storedProjectFilePath(BuildGraph *bg, const QString &projectId) { return bg->buildDirectoryRoot() + projectId + ".bg"; } QStringList BuildProject::storedProjectFiles(BuildGraph *bg) { QStringList result; QDirIterator dirit(bg->buildDirectoryRoot(), QStringList() << "*.bg", QDir::Files); while (dirit.hasNext()) result += dirit.next(); return result; } void BuildProject::load(PersistentPool &pool, PersistentObjectData &data) { QDataStream s(data); setResolvedProject(pool.idLoadS(s)); int count, i; s >> count; for (i = count; --i >= 0;) { BuildProduct::Ptr product = pool.idLoadS(s); product->project = this; foreach (Artifact *artifact, product->artifacts) artifact->project = this; addBuildProduct(product); } s >> count; m_dependencyArtifacts.clear(); m_dependencyArtifacts.reserve(count); for (i = count; --i >= 0;) { Artifact *artifact = pool.idLoad(s); artifact->project = this; m_dependencyArtifacts.insert(artifact->fileName, artifact); } } void BuildProject::store(PersistentPool &pool, PersistentObjectData &data) const { QDataStream s(&data, QIODevice::WriteOnly); s << pool.store(resolvedProject()); storeContainer(m_buildProducts, s, pool); storeHashContainer(m_dependencyArtifacts, s, pool); } char **createCFileTags(const QSet &fileTags) { if (fileTags.isEmpty()) return 0; char **buf = new char*[fileTags.count()]; size_t i = 0; foreach (const QString &fileTag, fileTags) { buf[i] = qstrdup(fileTag.toLocal8Bit().data()); ++i; } return buf; } void freeCFileTags(char **cFileTags, int numFileTags) { if (!cFileTags) return; for (int i = numFileTags; --i >= 0;) delete[] cFileTags[i]; delete[] cFileTags; } BuildGraph * BuildProject::buildGraph() const { return m_buildGraph; } ResolvedProject::Ptr BuildProject::resolvedProject() const { return m_resolvedProject; } QSet BuildProject::buildProducts() const { return m_buildProducts; } QHash &BuildProject::dependencyArtifacts() { return m_dependencyArtifacts; } bool BuildProject::dirty() const { return m_dirty; } Artifact *BuildProject::findArtifact(const QString &filePath) const { Artifact *artifact = m_dependencyArtifacts.value(filePath); if (!artifact) { foreach (const BuildProduct::Ptr &product, m_buildProducts) { artifact = product->artifacts.value(filePath); if (artifact) break; } } return artifact; } void BuildProject::markDirty() { m_dirty = true; } void BuildProject::addBuildProduct(const BuildProduct::Ptr &product) { m_buildProducts.insert(product); } void BuildProject::setResolvedProject(const ResolvedProject::Ptr &resolvedProject) { m_resolvedProject = resolvedProject; } QString fileName(Artifact *n) { class BuildGraph *bg = n->project->buildGraph(); QString str = n->fileName; if (str.startsWith(bg->outputDirectoryRoot())) str.remove(0, bg->outputDirectoryRoot().count()); if (str.startsWith('/')) str.remove(0, 1); return str; } } // namespace qbs