diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2018-03-06 10:46:26 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2018-03-14 08:53:13 +0000 |
commit | 2acaba8ea211e1ba00c2e2844aa00ca16c7a04f4 (patch) | |
tree | 6be71fac638be27609fb6b196ce73d058780c67f /share | |
parent | 149e20aca1e401ba18bbae602df2caa7dc68c493 (diff) |
Add module Exporter.qbs
This module generates qbs modules from products, providing an interface
to them for use by external projects.
[ChangeLog] Added new module "Exporter.qbs" for creating qbs modules
from products.
Task-number: QBS-1231
Change-Id: I9f0cf04b441aaf279cf19a84fd94d97a8cea9de8
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'share')
-rw-r--r-- | share/qbs/modules/Exporter/qbs/qbsexporter.js | 268 | ||||
-rw-r--r-- | share/qbs/modules/Exporter/qbs/qbsexporter.qbs | 78 |
2 files changed, 346 insertions, 0 deletions
diff --git a/share/qbs/modules/Exporter/qbs/qbsexporter.js b/share/qbs/modules/Exporter/qbs/qbsexporter.js new file mode 100644 index 000000000..e71622607 --- /dev/null +++ b/share/qbs/modules/Exporter/qbs/qbsexporter.js @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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. +** +****************************************************************************/ + +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); + +function tagListToString(tagList) +{ + return JSON.stringify(tagList); +} + +function stringToTagList(tagListString) +{ + return JSON.parse(tagListString); +} + +function writeTargetArtifactGroup(output, tagList, artifactList, moduleInstallDir, moduleFile) +{ + // Do not add our qbs module file itself. + if (tagListToString(tagList) === tagListToString(output.fileTags)) + return; + + moduleFile.writeLine(" Group {"); + moduleFile.writeLine(" filesAreTargets: true"); + var filteredTagList = tagList.filter(function(t) { return t !== "installable"; }); + moduleFile.writeLine(" fileTags: " + JSON.stringify(filteredTagList)); + moduleFile.writeLine(" files: ["); + for (i = 0; i < artifactList.length; ++i) { + var artifact = artifactList[i]; + var installedArtifactFilePath = ModUtils.artifactInstalledFilePath(artifact); + + // Use relative file paths for relocatability. + var relativeInstalledArtifactFilePath = FileInfo.relativePath(moduleInstallDir, + installedArtifactFilePath); + moduleFile.writeLine(" " + JSON.stringify(relativeInstalledArtifactFilePath) + + ","); + } + moduleFile.writeLine(" ]"); + moduleFile.writeLine(" }"); + +} + +function writeTargetArtifactGroups(product, output, moduleFile) +{ + var relevantArtifacts = []; + for (var i = 0; i < (product.Exporter.qbs._artifactTypes || []).length; ++i) { + var tag = product.Exporter.qbs._artifactTypes[i]; + var artifactsForTag = product.artifacts[tag] || []; + for (var j = 0; j < artifactsForTag.length; ++j) { + if (!relevantArtifacts.contains(artifactsForTag[j])) + relevantArtifacts.push(artifactsForTag[j]); + } + } + var artifactsByTags = {}; + var artifactCount = relevantArtifacts ? relevantArtifacts.length : 0; + for (i = 0; i < artifactCount; ++i) { + var artifact = relevantArtifacts[i]; + if (!artifact.fileTags.contains("installable")) + continue; + + // Put all artifacts with the same set of file tags into the same group, so we don't + // create more groups than necessary. + var key = tagListToString(artifact.fileTags); + var currentList = artifactsByTags[key]; + if (currentList) + currentList.push(artifact); + else + currentList = [artifact]; + artifactsByTags[key] = currentList; + } + var moduleInstallDir = FileInfo.path(ModUtils.artifactInstalledFilePath(output)); + for (var tagListKey in artifactsByTags) { + writeTargetArtifactGroup(output, stringToTagList(tagListKey), artifactsByTags[tagListKey], + moduleInstallDir, moduleFile); + } +} + +function checkValuePrefix(name, value, forbiddenPrefix, prefixDescription) +{ + if (value.startsWith(forbiddenPrefix)) { + throw "Value '" + value + "' for exported property '" + name + "' in product '" + + product.name + "' points into " + prefixDescription + ".\n" + + "Did you forget to set the prefixMapping property in an Export item?"; + } +} + +function stringifyValue(project, product, moduleInstallDir, name, value) +{ + if (Array.isArray(value)) { + var repr = "["; + for (var i = 0; i < value.length; ++i) { + repr += stringifyValue(project, product, moduleInstallDir, name, value[i]) + ", "; + } + repr += "]"; + return repr; + } + if (typeof(value) !== "string") + return JSON.stringify(value); + + // Catch user oversights: Paths that point into the project source or build directories + // make no sense in the module. + if (!value.startsWith(product.qbs.installRoot)) { + checkValuePrefix(name, value, project.buildDirectory, "project build directory"); + checkValuePrefix(name, value, project.sourceDirectory, "project source directory"); + } + + // Adapt file paths pointing into the install dir, that is, make them relative to the + // module file for relocatability. We accept them with or without the install root. + // The latter form will typically be a result of applying the prefixMapping property, + // while the first one could be an untransformed path, for instance if the project + // file is written in such a way that include paths are picked up from the installed + // location rather than the source directory. + var valuePrefixToStrip; + var fullInstallPrefix = FileInfo.joinPaths(product.qbs.installRoot, product.qbs.installPrefix); + if (fullInstallPrefix.length > 1 && value.startsWith(fullInstallPrefix)) { + valuePrefixToStrip = fullInstallPrefix; + } else { + var installPrefix = FileInfo.joinPaths("/", product.qbs.installPrefix); + if (installPrefix.length > 1 && value.startsWith(installPrefix)) + valuePrefixToStrip = installPrefix; + } + if (valuePrefixToStrip) { + var deployedModuleInstallDir = moduleInstallDir.slice(fullInstallPrefix.length); + return "FileInfo.cleanPath(FileInfo.joinPaths(path, FileInfo.relativePath(" + + JSON.stringify(deployedModuleInstallDir) + ", " + + JSON.stringify(value.slice(valuePrefixToStrip.length)) + ")))"; + } + + return JSON.stringify(value); +} + +function writeProperty(project, product, moduleInstallDir, prop, indentation, considerValue, + moduleFile) +{ + var line = indentation; + var separatorIndex = prop.name.lastIndexOf("."); + var isModuleProperty = separatorIndex !== -1; + var needsDeclaration = !prop.isBuiltin && !isModuleProperty; + if (needsDeclaration) + line += "property " + prop.type + " "; + var moduleName; + if (isModuleProperty) { + moduleName = prop.name.slice(0, separatorIndex); + if ((product.Exporter.qbs.excludedDependencies || []).contains(moduleName)) + return; + } + line += prop.name + ": "; + + // We emit the literal value, unless the source code clearly refers to values from inside the + // original project, in which case the evaluated value is used. + if (considerValue && /(project|product)\./.test(prop.sourceCode)) { + var value; + if (isModuleProperty) { + var propertyName = prop.name.slice(separatorIndex + 1); + value = product.exports[moduleName][propertyName]; + } else { + value = product.exports[prop.name]; + } + line += stringifyValue(project, product, moduleInstallDir, prop.name, value); + } else { + line += prop.sourceCode.replace(/importingProduct\./g, "product."); + } + moduleFile.writeLine(line); +} + +function writeProperties(project, product, moduleInstallDir, list, indentation, considerValue, + moduleFile) +{ + for (var i = 0; i < list.length; ++i) { + writeProperty(project, product, moduleInstallDir, list[i], indentation, considerValue, + moduleFile); + } +} + +// This writes properties set on other modules in the Export item, i.e. property assignments +// like "cpp.includePaths: '...'". +function writeModuleProperties(project, product, output, moduleFile) +{ + var moduleInstallDir = FileInfo.path(ModUtils.artifactInstalledFilePath(output)); + var filteredProps = product.exports.properties.filter(function(p) { + return p.name !== "name"; + }); + + // The right-hand side can refer to values from the exporting product, in which case + // the evaluated value, rather than the source code, needs to go into the module file. + var considerValues = true; + writeProperties(project, product, moduleInstallDir, filteredProps, " ", considerValues, + moduleFile); +} + +function writeItem(product, item, indentation, moduleFile) +{ + moduleFile.writeLine(indentation + item.name + " {"); + var newIndentation = indentation + " "; + + // These are sub-items of the Export item, whose properties entirely live in the context + // of the importing product. Therefore, they must never use pre-evaluated values. + var considerValues = false; + writeProperties(undefined, product, undefined, item.properties, newIndentation, considerValues, + moduleFile) + + for (var i = 0; i < item.childItems.length; ++i) + writeItem(product, item.childItems[i], newIndentation, moduleFile); + moduleFile.writeLine(indentation + "}"); +} + +function isExcludedDependency(product, childItem) +{ + if ((product.Exporter.qbs.excludedDependencies || []).length === 0) + return false; + if (childItem.name !== "Depends") + return false; + for (var i = 0; i < childItem.properties.length; ++i) { + var prop = childItem.properties[i]; + var unquotedRhs = prop.sourceCode.slice(1, -1); + if (prop.name === "name" && product.Exporter.qbs.excludedDependencies.contains(unquotedRhs)) + return true; + } + return false; +} + +function writeChildItems(product, moduleFile) +{ + for (var i = 0; i < product.exports.childItems.length; ++i) { + var item = product.exports.childItems[i]; + if (!isExcludedDependency(product, item)) + writeItem(product, item, " ", moduleFile); + } +} + +function writeImportStatements(product, moduleFile) +{ + var imports = product.exports.imports; + + // We potentially use FileInfo ourselves when transforming paths in stringifyValue(). + if (!imports.contains("import qbs.FileInfo")) + imports.push("import qbs.FileInfo"); + + for (var i = 0; i < product.exports.imports.length; ++i) + moduleFile.writeLine(product.exports.imports[i]); +} diff --git a/share/qbs/modules/Exporter/qbs/qbsexporter.qbs b/share/qbs/modules/Exporter/qbs/qbsexporter.qbs new file mode 100644 index 000000000..6cdc55891 --- /dev/null +++ b/share/qbs/modules/Exporter/qbs/qbsexporter.qbs @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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. +** +****************************************************************************/ + +import qbs +import qbs.FileInfo +import qbs.TextFile + +import "qbsexporter.js" as HelperFunctions + +Module { + property stringList artifactTypes + property string fileName: product.targetName + ".qbs" + property stringList excludedDependencies + property string additionalContent + + property stringList _artifactTypes: artifactTypes ? artifactTypes : ["installable"] + + additionalProductTypes: ["Exporter.qbs.module"] + + Rule { + multiplex: true + requiresInputs: false + + // Make sure we only run when all other artifacts are already present. + inputs: product.type.filter(function(t) { return t !== "Exporter.qbs.module"; }) + + Artifact { + filePath: product.Exporter.qbs.fileName + fileTags: ["Exporter.qbs.module"] + qbs.install: true + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("import qbs"); + HelperFunctions.writeImportStatements(product, f); + f.writeLine("\nModule {"); + HelperFunctions.writeModuleProperties(project, product, output, f); + HelperFunctions.writeTargetArtifactGroups(product, output, f); + HelperFunctions.writeChildItems(product, f); + if (product.Exporter.qbs.additionalContent) + f.writeLine(product.Exporter.qbs.additionalContent); + f.writeLine("}"); + f.close(); + }; + return [cmd]; + } + } +} |