diff options
Diffstat (limited to 'share/qbs/module-providers/Qt')
20 files changed, 3622 insertions, 0 deletions
diff --git a/share/qbs/module-providers/Qt/provider.qbs b/share/qbs/module-providers/Qt/provider.qbs new file mode 100644 index 000000000..33083c51d --- /dev/null +++ b/share/qbs/module-providers/Qt/provider.qbs @@ -0,0 +1,6 @@ +import "setup-qt.js" as SetupQt + +ModuleProvider { + property stringList qmakeFilePaths + relativeSearchPaths: SetupQt.doSetup(qmakeFilePaths, outputBaseDir, path, qbs) +} diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js new file mode 100644 index 000000000..0f0755409 --- /dev/null +++ b/share/qbs/module-providers/Qt/setup-qt.js @@ -0,0 +1,1504 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +var Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function splitNonEmpty(s, c) { return s.split(c).filter(function(e) { return e; }); } +function toNative(p) { return FileInfo.toNativeSeparators(p); } +function exeSuffix(qbs) { return qbs.hostOS.contains("windows") ? ".exe" : ""; } + +function getQmakeFilePaths(qmakeFilePaths, qbs) { + if (qmakeFilePaths && qmakeFilePaths.length > 0) + return qmakeFilePaths; + console.info("Detecting Qt installations..."); + var pathValue = Environment.getEnv("PATH"); + if (!pathValue) + return []; + var dirs = splitNonEmpty(pathValue, qbs.pathListSeparator); + var suffix = exeSuffix(qbs); + var filePaths = []; + for (var i = 0; i < dirs.length; ++i) { + var candidate = FileInfo.canonicalPath(FileInfo.joinPaths(dirs[i], "qmake" + suffix)); + if (candidate && File.exists(candidate) && !filePaths.contains(candidate)) { + console.info("Found Qt at '" + toNative(candidate) + "'."); + filePaths.push(candidate); + } + } + return filePaths; +} + +function queryQmake(qmakeFilePath) { + var qmakeProcess = new Process; + qmakeProcess.exec(qmakeFilePath, ["-query"]); + var queryResult = {}; + while (!qmakeProcess.atEnd()) { + var line = qmakeProcess.readLine(); + var index = (line || "").indexOf(":"); + if (index !== -1) + queryResult[line.slice(0, index)] = line.slice(index + 1).trim(); + } + return queryResult; +} + +function pathQueryValue(queryResult, key) { + var p = queryResult[key]; + if (p) + return FileInfo.fromNativeSeparators(p); +} + +function readFileContent(filePath) { + var f = new TextFile(filePath, TextFile.ReadOnly); + var content = f.readAll(); + f.close(); + return content; +} + +// TODO: Don't do the split every time... +function configVariable(configContent, key) { + var configContentLines = configContent.split('\n'); + var regexp = new RegExp("\\s*" + key + "\\s*\\+{0,1}=(.*)"); + for (var i = 0; i < configContentLines.length; ++i) { + var line = configContentLines[i]; + var match = regexp.exec(line); + if (match) + return match[1].trim(); + } +} + +function configVariableItems(configContent, key) { + return splitNonEmpty(configVariable(configContent, key), ' '); +} + +function msvcPrefix() { return "win32-msvc"; } + +function isMsvcQt(qtProps) { return qtProps.mkspecName.startsWith(msvcPrefix()); } + +function msvcCompilerVersionForYear(year) { + var mapping = { + "2005": "14", "2008": "15", "2010": "16", "2012": "17", "2013": "18", "2015": "19", + "2017": "19.1" + }; + return mapping[year]; +} + +function msvcCompilerVersionFromMkspecName(mkspecName) { + return msvcCompilerVersionForYear(mkspecName.slice(msvcPrefix().length)); +} + +function addQtBuildVariant(qtProps, buildVariantName) { + if (qtProps.qtConfigItems.contains(buildVariantName)) + qtProps.buildVariant.push(buildVariantName); +} + +function checkForStaticBuild(qtProps) { + if (qtProps.qtMajorVersion >= 5) + return qtProps.qtConfigItems.contains("static"); + if (qtProps.frameworkBuild) + return false; // there are no Qt4 static frameworks + var isWin = qtProps.mkspecName.startsWith("win"); + var libDir = isWin ? qtProps.binaryPath : qtProps.libraryPath; + var coreLibFiles = File.directoryEntries(libDir, File.Files) + .filter(function(fp) { return fp.contains("Core"); }); + if (coreLibFiles.length === 0) + throw "Could not determine whether Qt is a static build."; + for (var i = 0; i < coreLibFiles.length; ++i) { + if (Utilities.isSharedLibrary(coreLibFiles[i])) + return false; + } + return true; +} + +function isForMinGw(qtProps) { + return qtProps.mkspecName.startsWith("win32-g++") || qtProps.mkspecName.startsWith("mingw"); +} + +function targetsDesktopWindows(qtProps) { + return qtProps.mkspecName.startsWith("win32-") || isForMinGw(qtProps); +} + +function guessMinimumWindowsVersion(qtProps) { + if (qtProps.mkspecName.startsWith("winrt-")) + return "10.0"; + if (!targetsDesktopWindows(qtProps)) + return ""; + if (qtProps.architecture === "x86_64" || qtProps.architecture === "ia64") + return "5.2" + var match = qtProps.mkspecName.match(/^win32-msvc(\d+)$/); + if (match) { + var msvcVersion = match[1]; + if (msvcVersion < 2012) + return "5.0"; + return "5.1"; + } + return qtProps.qtMajorVersion < 5 ? "5.0" : "5.1"; +} + +function fillEntryPointLibs(qtProps, debug) { + result = []; + var isMinGW = isForMinGw(qtProps); + + // Some Linux distributions rename the qtmain library. + var qtMainCandidates = ["qtmain"]; + if (isMinGW && qtProps.qtMajorVersion === 5) + qtMainCandidates.push("qt5main"); + + for (var i = 0; i < qtMainCandidates.length; ++i) { + var baseNameCandidate = qtMainCandidates[i]; + var qtmain = qtProps.libraryPath + '/'; + if (isMinGW) + qtmain += "lib"; + qtmain += baseNameCandidate + qtProps.qtLibInfix; + if (debug) + qtmain += 'd'; + if (isMinGW) { + qtmain += ".a"; + } else { + qtmain += ".lib"; + if (Utilities.versionCompare(qtProps.qtVersion, "5.4.0") >= 0) + result.push("Shell32.lib"); + } + if (File.exists(qtmain)) { + result.push(qtmain); + break; + } + } + if (result.length === 0) { + console.warn("Could not find the qtmain library at '" + toNative(qtProps.libraryPath) + + "'. You will not be able to link Qt applications."); + } + return result; +} + +function getQtProperties(qmakeFilePath, qbs) { + var queryResult = queryQmake(qmakeFilePath); + var qtProps = {}; + qtProps.installPrefixPath = pathQueryValue(queryResult, "QT_INSTALL_PREFIX"); + qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS"); + qtProps.includePath = pathQueryValue(queryResult, "QT_INSTALL_HEADERS"); + qtProps.libraryPath = pathQueryValue(queryResult, "QT_INSTALL_LIBS"); + qtProps.binaryPath = pathQueryValue(queryResult, "QT_HOST_BINS") + || pathQueryValue(queryResult, "QT_INSTALL_BINS"); + qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS"); + qtProps.pluginPath = pathQueryValue(queryResult, "QT_INSTALL_PLUGINS"); + qtProps.qmlPath = pathQueryValue(queryResult, "QT_INSTALL_QML"); + qtProps.qmlImportPath = pathQueryValue(queryResult, "QT_INSTALL_IMPORTS"); + qtProps.qtVersion = queryResult.QT_VERSION; + + var mkspecsBaseSrcPath; + if (Utilities.versionCompare(qtProps.qtVersion, "5") >= 0) { + qtProps.mkspecBasePath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA"), + "mkspecs"); + mkspecsBaseSrcPath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA/src"), + "mkspecs"); + } else { + qtProps.mkspecBasePath = FileInfo.joinPaths + (pathQueryValue(queryResult, "QT_INSTALL_DATA"), "mkspecs"); + } + if (!File.exists(qtProps.mkspecBasePath)) + throw "Cannot extract the mkspecs directory."; + + var qconfigContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath, + "qconfig.pri")); + qtProps.qtMajorVersion = parseInt(configVariable(qconfigContent, "QT_MAJOR_VERSION")); + qtProps.qtMinorVersion = parseInt(configVariable(qconfigContent, "QT_MINOR_VERSION")); + qtProps.qtPatchVersion = parseInt(configVariable(qconfigContent, "QT_PATCH_VERSION")); + qtProps.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE"); + qtProps.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX") || ""; + qtProps.architecture = configVariable(qconfigContent, "QT_TARGET_ARCH") + || configVariable(qconfigContent, "QT_ARCH") || "x86"; + qtProps.configItems = configVariableItems(qconfigContent, "CONFIG"); + qtProps.qtConfigItems = configVariableItems(qconfigContent, "QT_CONFIG"); + + // retrieve the mkspec + if (qtProps.qtMajorVersion >= 5) { + qtProps.mkspecName = queryResult.QMAKE_XSPEC; + qtProps.mkspecPath = FileInfo.joinPaths(qtProps.mkspecBasePath, qtProps.mkspecName); + if (mkspecsBaseSrcPath && !File.exists(qtProps.mkspecPath)) + qtProps.mkspecPath = FileInfo.joinPaths(mkspecsBaseSrcPath, qtProps.mkspecName); + } else { + if (qbs.hostOS.contains("windows")) { + var baseDirPath = FileInfo.joinPaths(qtProps.mkspecBasePath, "default"); + var fileContent = readFileContent(FileInfo.joinPaths(baseDirPath, "qmake.conf")); + qtProps.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL"); + if (!File.exists(qtProps.mkspecPath)) { + // Work around QTBUG-28792. + // The value of QMAKESPEC_ORIGINAL is wrong for MinGW packages. Y u h8 me? + var match = fileContent.exec(/\binclude\(([^)]+)\/qmake\.conf\)/m); + if (match) { + qtProps.mkspecPath = FileInfo.cleanPath(FileInfo.joinPaths( + baseDirPath, match[1])); + } + } + } else { + qtProps.mkspecPath = FileInfo.canonicalPath( + FileInfo.joinPaths(qtProps.mkspecBasePath, "default")); + } + + // E.g. in qmake.conf for Qt 4.8/mingw we find this gem: + // QMAKESPEC_ORIGINAL=C:\\Qt\\Qt\\4.8\\mingw482\\mkspecs\\win32-g++ + qtProps.mkspecPath = FileInfo.cleanPath(qtProps.mkspecPath); + + qtProps.mkspecName = qtProps.mkspecPath; + var idx = qtProps.mkspecName.lastIndexOf('/'); + if (idx !== -1) + qtProps.mkspecName = qtProps.mkspecName.slice(idx + 1); + } + if (!File.exists(qtProps.mkspecPath)) + throw "mkspec '" + toNative(qtProps.mkspecPath) + "' does not exist"; + + // determine MSVC version + if (isMsvcQt(qtProps)) { + var msvcMajor = configVariable(qconfigContent, "QT_MSVC_MAJOR_VERSION"); + var msvcMinor = configVariable(qconfigContent, "QT_MSVC_MINOR_VERSION"); + var msvcPatch = configVariable(qconfigContent, "QT_MSVC_PATCH_VERSION"); + if (msvcMajor && msvcMinor && msvcPatch) + qtProps.msvcVersion = msvcMajor + "." + msvcMinor + "." + msvcPatch; + else + qtProps.msvcVersion = msvcCompilerVersionFromMkspecName(qtProps.mkspecName); + } + + // determine whether we have a framework build + qtProps.frameworkBuild = qtProps.mkspecPath.contains("macx") + && qtProps.configItems.contains("qt_framework"); + + // determine whether Qt is built with debug, release or both + qtProps.buildVariant = []; + addQtBuildVariant(qtProps, "debug"); + addQtBuildVariant(qtProps, "release"); + + qtProps.staticBuild = checkForStaticBuild(qtProps); + + // determine whether user apps require C++11 + if (qtProps.qtConfigItems.contains("c++11") && qtProps.staticBuild) + qtProps.configItems.push("c++11"); + + // Set the minimum operating system versions appropriate for this Qt version + qtProps.windowsVersion = guessMinimumWindowsVersion(qtProps); + if (qtProps.windowsVersion) { // Is target OS Windows? + if (qtProps.buildVariant.contains("debug")) + qtProps.entryPointLibsDebug = fillEntryPointLibs(qtProps, true); + if (qtProps.buildVariant.contains("release")) + qtProps.entryPointLibsRelease = fillEntryPointLibs(qtProps, false); + } else if (qtProps.mkspecPath.contains("macx")) { + if (qtProps.qtMajorVersion >= 5) { + try { + var qmakeConf = new TextFile(FileInfo.joinPaths(qtProps.mkspecPath, "qmake.conf"), + TextFile.ReadOnly); + while (!qmakeConf.atEof()) { + var line = qmakeConf.readLine().trim(); + match = line.match + (/^QMAKE_(MACOSX|IOS|TVOS|WATCHOS)_DEPLOYMENT_TARGET\s*=\s*(.*)\s*$/); + if (match) { + var platform = match[1]; + var version = match[2]; + if (platform === "MACOSX") + qtProps.macosVersion = version; + else if (platform === "IOS") + qtProps.iosVersion = version; + else if (platform === "TVOS") + qtProps.tvosVersion = version; + else if (platform === "WATCHOS") + qtProps.watchosVersion = version; + } + } + } + catch (e) {} + finally { + if (qmakeConf) + qmakeConf.close(); + } + var isMac = qtProps.mkspecName !== "macx-ios-clang" + && qtProps.mkspecName !== "macx-tvos-clang" + && qtProps.mkspecName !== "macx-watchos-clang"; + if (isMac) { + // Qt 5.0.x placed the minimum version in a different file + if (!qtProps.macosVersion) + qtProps.macosVersion = "10.6"; + + // If we're using C++11 with libc++, make sure the deployment target is >= 10.7 + if (Utilities.versionCompare(qtProps.macosVersion, "10, 7") < 0 + && qtProps.qtConfigItems.contains("c++11")) { + qtProps.macosVersion = "10.7"; + } + } + } else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 6) { + var qconfigDir = qtProps.frameworkBuild + ? FileInfo.joinPaths(qtProps.libraryPath, "QtCore.framework", "Headers") + : FileInfo.joinPaths(qtProps.includePath, "Qt"); + try { + var qconfig = new TextFile(FileInfo.joinPaths(qconfigDir, "qconfig.h"), + TextFile.ReadOnly); + var qtCocoaBuild = false; + var ok = true; + do { + line = qconfig.readLine(); + if (line.match(/\s*#define\s+QT_MAC_USE_COCOA\s+1\s*/)) { + qtCocoaBuild = true; + break; + } + } while (!qconfig.atEof()); + qtProps.macosVersion = qtCocoaBuild ? "10.5" : "10.4"; + } + catch (e) {} + finally { + if (qconfig) + qconfig.close(); + } + if (!qtProps.macosVersion) { + throw "Could not determine whether Qt is using Cocoa or Carbon from '" + + toNative(qconfig.filePath()) + "'."; + } + } + } else if (qtProps.mkspecPath.contains("android")) { + if (qtProps.qtMajorVersion >= 5) + qtProps.androidVersion = "2.3"; + else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 8) + qtProps.androidVersion = "1.6"; // Necessitas + } + return qtProps; +} + +function makePluginData() { + var pluginData = {}; + pluginData.type = undefined; + pluginData.className = undefined; + pluginData.autoLoad = true; + pluginData["extends"] = []; + return pluginData; +} + +function makeQtModuleInfo(name, qbsName, deps) { + var moduleInfo = {}; + moduleInfo.name = name; // As in the path to the headers and ".name" in the pri files. + if (moduleInfo.name === undefined) + moduleInfo.name = ""; + moduleInfo.qbsName = qbsName; // Lower-case version without "qt" prefix. + moduleInfo.dependencies = deps || []; // qbs names. + if (moduleInfo.qbsName !== "core" && !moduleInfo.dependencies.contains("core")) + moduleInfo.dependencies.unshift("core"); + moduleInfo.isPrivate = qbsName && qbsName.endsWith("-private"); + moduleInfo.hasLibrary = !moduleInfo.isPrivate; + moduleInfo.isStaticLibrary = false; + moduleInfo.isPlugin = false; + moduleInfo.mustExist = true; + moduleInfo.modulePrefix = ""; // empty value means "Qt". + moduleInfo.version = undefined; + moduleInfo.includePaths = []; + moduleInfo.compilerDefines = []; + moduleInfo.staticLibrariesDebug = []; + moduleInfo.staticLibrariesRelease = []; + moduleInfo.dynamicLibrariesDebug = []; + moduleInfo.dynamicLibrariesRelease = []; + moduleInfo.linkerFlagsDebug = []; + moduleInfo.linkerFlagsRelease = []; + moduleInfo.libFilePathDebug = undefined; + moduleInfo.libFilePathRelease = undefined; + moduleInfo.frameworksDebug = []; + moduleInfo.frameworksRelease = []; + moduleInfo.frameworkPathsDebug = []; + moduleInfo.frameworkPathsRelease = []; + moduleInfo.libraryPaths = []; + moduleInfo.config = []; + moduleInfo.supportedPluginTypes = []; + moduleInfo.pluginData = makePluginData(); + return moduleInfo; +} + +function frameworkHeadersPath(qtModuleInfo, qtProps) { + return FileInfo.joinPaths(qtProps.libraryPath, qtModuleInfo.name + ".framework", "Headers"); +} + +function qt4ModuleIncludePaths(qtModuleInfo, qtProps) { + var paths = []; + if (isFramework(qtModuleInfo, qtProps)) + paths.push(frameworkHeadersPath(qtModuleInfo, qtProps)); + else + paths.push(qtProps.includePath, FileInfo.joinPaths(qtProps.includePath, qtModuleInfo.name)); + return paths; +} + +// We erroneously called the "testlib" module "test" for quite a while. Let's not punish users +// for that. +function addTestModule(modules) { + var testModule = makeQtModuleInfo("QtTest", "test", ["testlib"]); + testModule.hasLibrary = false; + modules.push(testModule); +} + +// See above. +function addDesignerComponentsModule(modules) { + var module = makeQtModuleInfo("QtDesignerComponents", "designercomponents", + ["designercomponents-private"]); + module.hasLibrary = false; + modules.push(module); +} + +function isFramework(modInfo, qtProps) { + if (!qtProps.frameworkBuild || modInfo.isStaticLibrary) + return false; + var modulesNeverBuiltAsFrameworks = [ + "bootstrap", "openglextensions", "platformsupport", "qmldevtools", "uitools", "harfbuzzng" + ]; + return !modulesNeverBuiltAsFrameworks.contains(modInfo.qbsName); +} + +function libBaseName(modInfo, libName, debugBuild, qtProps) { + var name = libName; + if (qtProps.mkspecName.startsWith("win")) { + if (debugBuild) + name += 'd'; + if (!modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5) + name += qtProps.qtMajorVersion; + } + if (qtProps.mkspecName.contains("macx") + || qtProps.mkspecName.contains("ios") + || qtProps.mkspecName.contains("darwin")) { + if (!isFramework(modInfo, qtProps) + && qtProps.buildVariant.contains("debug") + && (!qtProps.buildVariant.contains("release") || debugBuild)) { + name += "_debug"; + } + } + return name; +} + +function moduleNameWithoutPrefix(modInfo) { + if (modInfo.name === "Phonon") + return "phonon"; + if (!modInfo.modulePrefix && modInfo.name.startsWith("Qt")) + return modInfo.name.slice(2); // Strip off "Qt". + if (modInfo.name.startsWith(modInfo.modulePrefix)) + return modInfo.name.slice(modInfo.modulePrefix.length); + return modInfo.name; +} + +function libraryBaseName(modInfo, qtProps, debugBuild) { + if (modInfo.isPlugin) + return libBaseName(modInfo, name, debugBuild, qtProps); + + // Some modules use a different naming scheme, so it doesn't get boring. + var libNameBroken = modInfo.name === "Enginio" + || modInfo.name === "DataVisualization" + || modInfo.name === "Phonon"; + + var libName = !modInfo.modulePrefix && !libNameBroken ? "Qt" : modInfo.modulePrefix; + if (qtProps.qtMajorVersion >= 5 && !isFramework(modInfo, qtProps) && !libNameBroken) + libName += qtProps.qtMajorVersion; + libName += moduleNameWithoutPrefix(modInfo); + libName += qtProps.qtLibInfix; + return libBaseName(modInfo, libName, debugBuild, qtProps); +} + +function libNameForLinker(modInfo, qtProps, debugBuild) { + if (!modInfo.hasLibrary) + return undefined; + var libName = libraryBaseName(modInfo, qtProps, debugBuild); + if (qtProps.mkspecName.contains("msvc")) + libName += ".lib"; + return libName; +} + +function guessLibraryFilePath(prlFilePath, libDir, qtProps) { + var baseName = FileInfo.baseName(prlFilePath); + var prefixCandidates = ["", "lib"]; + var suffixCandidates = ["so." + qtProps.qtVersion, "so", "a", "lib", "dll.a"]; + for (var i = 0; i < prefixCandidates.length; ++i) { + var prefix = prefixCandidates[i]; + for (var j = 0; j < suffixCandidates.length; ++j) { + var suffix = suffixCandidates[j]; + var candidate = FileInfo.joinPaths(libDir, prefix + baseName + '.' + suffix); + if (File.exists(candidate)) + return candidate; + } + } +} + +function doReplaceQtLibNamesWithFilePath(namePathMap, libList) { + for (var i = 0; i < libList.length; ++i) { + var lib = libList[i]; + var path = namePathMap[lib]; + if (path) + libList[i] = path; + } +} + +function replaceQtLibNamesWithFilePath(modules, qtProps) { + // We don't want to add the libraries for Qt modules via "-l", because of the + // danger that a wrong one will be picked up, e.g. from /usr/lib. Instead, + // we pull them in using the full file path. + var linkerNamesToFilePathsDebug = {}; + var linkerNamesToFilePathsRelease = {}; + for (var i = 0; i < modules.length; ++i) { + var m = modules[i]; + linkerNamesToFilePathsDebug[libNameForLinker(m, qtProps, true)] = m.libFilePathDebug; + linkerNamesToFilePathsRelease[libNameForLinker(m, qtProps, false)] = m.libFilePathRelease; + } + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.dynamicLibrariesDebug); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.staticLibrariesDebug); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease, + module.dynamicLibrariesRelease); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease, + module.staticLibrariesRelease); + } +} + +function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles) { + if (!modInfo.hasLibrary) + return; // Can happen for Qt4 convenience modules, like "widgets". + + if (debugBuild) { + if (!qtProps.buildVariant.contains("debug")) + return; + var modulesNeverBuiltAsDebug = ["bootstrap", "qmldevtools"]; + for (var i = 0; i < modulesNeverBuiltAsDebug.length; ++i) { + var m = modulesNeverBuiltAsDebug[i]; + if (modInfo.qbsName === m || modInfo.qbsName === m + "-private") + return; + } + } else if (!qtProps.buildVariant.contains("release")) { + return; + } + + var libs = modInfo.isStaticLibrary + ? (debugBuild ? modInfo.staticLibrariesDebug : modInfo.staticLibrariesRelease) + : (debugBuild ? modInfo.dynamicLibrariesDebug : modInfo.dynamicLibrariesRelease); + var frameworks = debugBuild ? modInfo.frameworksDebug : modInfo.frameworksRelease; + var frameworkPaths = debugBuild ? modInfo.frameworkPathsDebug : modInfo.frameworkPathsRelease; + var flags = debugBuild ? modInfo.linkerFlagsDebug : modInfo.linkerFlagsRelease; + var libFilePath; + + if (qtProps.mkspecName.contains("ios") && modInfo.isStaticLibrary) { + libs.push("z", "m"); + if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) { + var platformSupportModule = makeQtModuleInfo("QtPlatformSupport", "platformsupport"); + libs.push(libNameForLinker(platformSupportModule, qtProps, debugBuild)); + } + if (modInfo.name === "qios") { + flags.push("-force_load", FileInfo.joinPaths( + qtProps.pluginPath, "platforms", + libBaseName(modInfo, "libqios", debugBuild, qtProps) + ".a")); + } + } + var prlFilePath = modInfo.isPlugin + ? FileInfo.joinPaths(qtProps.pluginPath, modInfo.pluginData.type) + : qtProps.libraryPath; + if (isFramework(modInfo, qtProps)) { + prlFilePath = FileInfo.joinPaths(prlFilePath, + libraryBaseName(modInfo, qtProps, false) + ".framework"); + } + var libDir = prlFilePath; + var baseName = libraryBaseName(modInfo, qtProps, debugBuild); + if (!qtProps.mkspecName.startsWith("win") && !isFramework(modInfo, qtProps)) + baseName = "lib" + baseName; + prlFilePath = FileInfo.joinPaths(prlFilePath, baseName); + var isNonStaticQt4OnWindows = qtProps.mkspecName.startsWith("win") + && !modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5; + if (isNonStaticQt4OnWindows) + prlFilePath = prlFilePath.slice(0, prlFilePath.length - 1); // The prl file base name does *not* contain the version number... + prlFilePath += ".prl"; + try { + var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly); + while (!prlFile.atEof()) { + var line = prlFile.readLine().trim(); + var equalsOffset = line.indexOf('='); + if (equalsOffset === -1) + continue; + if (line.startsWith("QMAKE_PRL_TARGET")) { + var isMingw = qtProps.mkspecName.startsWith("win") + && qtProps.mkspecName.contains("g++"); + var isQtVersionBefore56 = qtProps.qtMajorVersion < 5 + || (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 6); + + // QMAKE_PRL_TARGET has a "lib" prefix, except for mingw. + // Of course, the exception has an exception too: For static libs, mingw *does* + // have the "lib" prefix. + var libFileName = ""; + if (isQtVersionBefore56 && qtProps.qtMajorVersion === 5 && isMingw + && !modInfo.isStaticLibrary) { + libFileName += "lib"; + } + + libFileName += line.slice(equalsOffset + 1).trim(); + if (isNonStaticQt4OnWindows) + libFileName += 4; // This is *not* part of QMAKE_PRL_TARGET... + if (isQtVersionBefore56) { + if (qtProps.mkspecName.contains("msvc")) { + libFileName += ".lib"; + } else if (isMingw) { + libFileName += ".a"; + if (!File.exists(FileInfo.joinPaths(libDir, libFileName))) + libFileName = libFileName.slice(0, -2) + ".dll"; + } + } + libFilePath = FileInfo.joinPaths(libDir, libFileName); + continue; + } + if (line.startsWith("QMAKE_PRL_CONFIG")) { + modInfo.config = splitNonEmpty(line.slice(equalsOffset + 1).trim(), ' '); + continue; + } + if (!line.startsWith("QMAKE_PRL_LIBS")) + continue; + + // Assuming lib names and directories without spaces here. + var parts = splitNonEmpty(line.slice(equalsOffset + 1).trim(), ' '); + for (i = 0; i < parts.length; ++i) { + var part = parts[i]; + part = part.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath); + if (part.startsWith("-l")) { + libs.push(part.slice(2)); + } else if (part.startsWith("-L")) { + modInfo.libraryPaths.push(part.slice(2)); + } else if (part.startsWith("-F")) { + frameworkPaths.push(part.slice(2)); + } else if (part === "-framework") { + if (++i < parts.length) + frameworks.push(parts[i]); + } else if (part === "-pthread") { + libs.push("pthread"); + } else if (part.startsWith('-')) { // Some other option + console.debug("QMAKE_PRL_LIBS contains non-library option '" + part + + "' in file '" + prlFilePath + "'"); + flags.push(part); + } else if (part.startsWith("/LIBPATH:")) { + libraryPaths.push(part.slice(9).replace(/\\/g, '/')); + } else { // Assume it's a file path/name. + libs.push(part.replace(/\\/g, '/')); + } + } + } + } catch (e) { + libFilePath = guessLibraryFilePath(prlFilePath, libDir, qtProps); + if (nonExistingPrlFiles.contains(prlFilePath)) + return; + nonExistingPrlFiles.push(prlFilePath); + if (!libFilePath && modInfo.mustExist) { + console.warn("Could not open prl file '" + toNative(prlFilePath) + "' for module '" + + modInfo.name + + "' (" + e + "), and failed to deduce the library file path. " + + " This module will likely not be usable by qbs."); + } + } + finally { + if (prlFile) + prlFile.close(); + } + + if (debugBuild) + modInfo.libFilePathDebug = libFilePath; + else + modInfo.libFilePathRelease = libFilePath; +} + +function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles) { + doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles); + doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles); +} + +function allQt4Modules(qtProps) { + // as per http://doc.qt.io/qt-4.8/modules.html + private stuff. + var modules = []; + + var core = makeQtModuleInfo("QtCore", "core"); + core.compilerDefines.push("QT_CORE_LIB"); + if (qtProps.qtNameSpace) + core.compilerDefines.push("QT_NAMESPACE=" + qtProps.qtNameSpace); + modules.push(core, + makeQtModuleInfo("QtCore", "core-private", ["core"]), + makeQtModuleInfo("QtGui", "gui"), + makeQtModuleInfo("QtGui", "gui-private", ["gui"]), + makeQtModuleInfo("QtMultimedia", "multimedia", ["gui", "network"]), + makeQtModuleInfo("QtMultimedia", "multimedia-private", ["multimedia"]), + makeQtModuleInfo("QtNetwork", "network"), + makeQtModuleInfo("QtNetwork", "network-private", ["network"]), + makeQtModuleInfo("QtOpenGL", "opengl", ["gui"]), + makeQtModuleInfo("QtOpenGL", "opengl-private", ["opengl"]), + makeQtModuleInfo("QtOpenVG", "openvg", ["gui"]), + makeQtModuleInfo("QtScript", "script"), + makeQtModuleInfo("QtScript", "script-private", ["script"]), + makeQtModuleInfo("QtScriptTools", "scripttools", ["script", "gui"]), + makeQtModuleInfo("QtScriptTools", "scripttools-private", ["scripttools"]), + makeQtModuleInfo("QtSql", "sql"), + makeQtModuleInfo("QtSql", "sql-private", ["sql"]), + makeQtModuleInfo("QtSvg", "svg", ["gui"]), + makeQtModuleInfo("QtSvg", "svg-private", ["svg"]), + makeQtModuleInfo("QtWebKit", "webkit", ["gui", "network"]), + makeQtModuleInfo("QtWebKit", "webkit-private", ["webkit"]), + makeQtModuleInfo("QtXml", "xml"), + makeQtModuleInfo("QtXml", "xml-private", ["xml"]), + makeQtModuleInfo("QtXmlPatterns", "xmlpatterns", ["network"]), + makeQtModuleInfo("QtXmlPatterns", "xmlpatterns-private", ["xmlpatterns"]), + makeQtModuleInfo("QtDeclarative", "declarative", ["gui", "script"]), + makeQtModuleInfo("QtDeclarative", "declarative-private", ["declarative"]), + makeQtModuleInfo("QtDesigner", "designer", ["gui", "xml"]), + makeQtModuleInfo("QtDesigner", "designer-private", ["designer"]), + makeQtModuleInfo("QtUiTools", "uitools"), + makeQtModuleInfo("QtUiTools", "uitools-private", ["uitools"]), + makeQtModuleInfo("QtHelp", "help", ["network", "sql"]), + makeQtModuleInfo("QtHelp", "help-private", ["help"]), + makeQtModuleInfo("QtTest", "testlib"), + makeQtModuleInfo("QtTest", "testlib-private", ["testlib"])); + if (qtProps.mkspecName.startsWith("win")) { + var axcontainer = makeQtModuleInfo("QAxContainer", "axcontainer"); + axcontainer.modulePrefix = "Q"; + axcontainer.isStaticLibrary = true; + axcontainer.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt")); + modules.push(axcontainer); + + var axserver = makeQtModuleInfo("QAxServer", "axserver"); + axserver.modulePrefix = "Q"; + axserver.isStaticLibrary = true; + axserver.compilerDefines.push("QAXSERVER"); + axserver.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt")); + modules.push(axserver); + } else { + modules.push(makeQtModuleInfo("QtDBus", "dbus")); + modules.push(makeQtModuleInfo("QtDBus", "dbus-private", ["dbus"])); + } + + var designerComponentsPrivate = makeQtModuleInfo( + "QtDesignerComponents", "designercomponents-private", + ["gui-private", "designer-private"]); + designerComponentsPrivate.hasLibrary = true; + modules.push(designerComponentsPrivate); + + var phonon = makeQtModuleInfo("Phonon", "phonon"); + phonon.includePaths = qt4ModuleIncludePaths(phonon, qtProps); + modules.push(phonon); + + // Set up include paths that haven't been set up before this point. + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + if (module.includePaths.length > 0) + continue; + module.includePaths = qt4ModuleIncludePaths(module, qtProps); + } + + // Set up compiler defines haven't been set up before this point. + for (i = 0; i < modules.length; ++i) { + module = modules[i]; + if (module.compilerDefines.length > 0) + continue; + module.compilerDefines.push("QT_" + module.qbsName.toUpperCase() + "_LIB"); + } + + // These are for the convenience of project file authors. It allows them + // to add a dependency to e.g. "Qt.widgets" without a version check. + var virtualModule = makeQtModuleInfo(undefined, "widgets", ["core", "gui"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "quick", ["declarative"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "concurrent"); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "printsupport", ["core", "gui"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + + addTestModule(modules); + addDesignerComponentsModule(modules); + + var modulesThatCanBeDisabled = [ + "xmlpatterns", "multimedia", "phonon", "svg", "webkit", "script", "scripttools", + "declarative", "gui", "dbus", "opengl", "openvg"]; + var nonExistingPrlFiles = []; + for (i = 0; i < modules.length; ++i) { + module = modules[i]; + var name = module.qbsName; + var privateIndex = name.indexOf("-private"); + if (privateIndex !== -1) + name = name.slice(0, privateIndex); + if (modulesThatCanBeDisabled.contains(name)) + module.mustExist = false; + if (qtProps.staticBuild) + module.isStaticLibrary = true; + setupLibraries(module, qtProps, nonExistingPrlFiles); + } + replaceQtLibNamesWithFilePath(modules, qtProps); + + return modules; +} + +function getPriFileContentsRecursively(priFilePath) { + var priFile = new TextFile(priFilePath, TextFile.ReadOnly); + var lines = splitNonEmpty(priFile.readAll(), '\n'); + for (var i = 0; i < lines.length; ++i) { + var includeString = "include("; + var line = lines[i].trim(); + if (!line.startsWith(includeString)) + continue; + var offset = includeString.length; + var closingParenPos = line.indexOf(')', offset); + if (closingParenPos === -1) { + console.warn("Invalid include statement in '" + toNative(priFilePath) + "'"); + continue; + } + var includedFilePath = line.slice(offset, closingParenPos - offset); + var includedContents = getPriFileContentsRecursively(includedFilePath); + var j = i; + for (var k = 0; k < includedContents.length; ++k) + lines.splice(++j, 0, includedContents[k]); + lines.splice(i--, 1); + } + priFile.close(); + return lines; +} + +function extractPaths(rhs, filePath) { + var paths = []; + var startIndex = 0; + for (;;) { + while (startIndex < rhs.length && rhs.charAt(startIndex) === ' ') + ++startIndex; + if (startIndex >= rhs.length) + break; + var endIndex; + if (rhs.charAt(startIndex) === '"') { + ++startIndex; + endIndex = rhs.indexOf('"', startIndex); + if (endIndex === -1) { + console.warn("Unmatched quote in file '" + toNative(filePath) + "'"); + break; + } + } else { + endIndex = rhs.indexOf(' ', startIndex + 1); + if (endIndex === -1) + endIndex = rhs.length; + } + paths.push(rhs.slice(startIndex, endIndex)); + startIndex = endIndex + 1; + } + return paths; +} + +function removeDuplicatedDependencyLibs(modules) { + var revDeps = {}; + var currentPath; + var getLibraries; + var getLibFilePath; + + function setupReverseDependencies(modules) { + var moduleByName = {}; + for (var i = 0; i < modules.length; ++i) + moduleByName[modules[i].qbsName] = modules[i]; + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + for (var j = 0; j < module.dependencies.length; ++j) { + var depmod = moduleByName[module.dependencies[j]]; + if (!depmod) + continue; + if (!revDeps[depmod]) + revDeps[depmod] = []; + revDeps[depmod].push(module); + } + } + } + + function roots(modules) { + var result = []; + for (i = 0; i < modules.length; ++i) { + var module = modules[i] + if (module.dependencies.lenegth === 0) + result.push(module); + } + return result; + } + + function traverse(module, libs) { + if (currentPath.contains(module)) + return; + currentPath.push(module); + + var moduleLibraryLists = getLibraries(module); + for (var i = 0; i < moduleLibraryLists.length; ++i) { + var modLibList = moduleLibraryLists[i]; + for (j = modLibList.length - 1; j >= 0; --j) { + if (libs.contains(modLibList[j])) + modLibList.splice(j, 1); + } + } + + var libFilePath = getLibFilePath(module); + if (libFilePath) + libs.push(libFilePath); + for (i = 0; i < moduleLibraryLists.length; ++i) + libs = libs.concat(moduleLibraryLists[i]); + libs.sort(); + + for (i = 0; i < (revDeps[module] || []).length; ++i) + traverse(revDeps[module][i], libs); + + m_currentPath.pop(); + } + + setupReverseDependencies(modules); + + // Traverse the debug variants of modules. + getLibraries = function(module) { + return [module.dynamicLibrariesDebug, module.staticLibrariesDebug]; + }; + getLibFilePath = function(module) { return module.libFilePathDebug; }; + var rootModules = roots(modules); + for (var i = 0; i < rootModules.length; ++i) + traverse(rootModules[i], []); + + // Traverse the release variants of modules. + getLibraries = function(module) { + return [module.dynamicLibrariesRelease, module.staticLibrariesRelease]; + }; + getLibFilePath = function(module) { return module.libFilePathRelease; }; + for (i = 0; i < rootModules.length; ++i) + traverse(rootModules[i], []); +} + +function allQt5Modules(qtProps) { + var nonExistingPrlFiles = []; + var modules = []; + var modulesDir = FileInfo.joinPaths(qtProps.mkspecBasePath, "modules"); + var modulePriFiles = File.directoryEntries(modulesDir, File.Files); + for (var i = 0; i < modulePriFiles.length; ++i) { + var priFileName = modulePriFiles[i]; + var priFilePath = FileInfo.joinPaths(modulesDir, priFileName); + var moduleFileNamePrefix = "qt_lib_"; + var pluginFileNamePrefix = "qt_plugin_"; + var moduleFileNameSuffix = ".pri"; + var fileHasPluginPrefix = priFileName.startsWith(pluginFileNamePrefix); + if (!fileHasPluginPrefix && (!priFileName.startsWith(moduleFileNamePrefix)) + || !priFileName.endsWith(moduleFileNameSuffix)) { + continue; + } + var moduleInfo = makeQtModuleInfo(); + moduleInfo.isPlugin = fileHasPluginPrefix; + var fileNamePrefix = moduleInfo.isPlugin ? pluginFileNamePrefix : moduleFileNamePrefix; + moduleInfo.qbsName = priFileName.slice(fileNamePrefix.length, -moduleFileNameSuffix.length); + if (moduleInfo.isPlugin) { + moduleInfo.name = moduleInfo.qbsName; + moduleInfo.isStaticLibrary = true; + } + var moduleKeyPrefix = (moduleInfo.isPlugin ? "QT_PLUGIN" : "QT") + + '.' + moduleInfo.qbsName + '.'; + moduleInfo.qbsName = moduleInfo.qbsName.replace("_private", "-private"); + var hasV2 = false; + var hasModuleEntry = false; + var lines = getPriFileContentsRecursively(priFilePath); + for (var j = 0; j < lines.length; ++j) { + var line = lines[j].trim(); + var firstEqualsOffset = line.indexOf('='); + if (firstEqualsOffset === -1) + continue; + var key = line.slice(0, firstEqualsOffset).trim(); + var value = line.slice(firstEqualsOffset + 1).trim(); + if (!key.startsWith(moduleKeyPrefix) || !value) + continue; + if (key.endsWith(".name")) { + moduleInfo.name = value; + } else if (key.endsWith(".module")) { + hasModuleEntry = true; + } else if (key.endsWith(".depends")) { + moduleInfo.dependencies = splitNonEmpty(value, ' '); + for (var k = 0; k < moduleInfo.dependencies.length; ++k) { + moduleInfo.dependencies[k] + = moduleInfo.dependencies[k].replace("_private", "-private"); + } + } else if (key.endsWith(".module_config")) { + var elems = splitNonEmpty(value, ' '); + for (k = 0; k < elems.length; ++k) { + var elem = elems[k]; + if (elem === "no_link") + moduleInfo.hasLibrary = false; + else if (elem === "staticlib") + moduleInfo.isStaticLibrary = true; + else if (elem === "internal_module") + moduleInfo.isPrivate = true; + else if (elem === "v2") + hasV2 = true; + } + } else if (key.endsWith(".includes")) { + moduleInfo.includePaths = extractPaths(value, priFilePath); + for (k = 0; k < moduleInfo.includePaths.length; ++k) { + moduleInfo.includePaths[k] = moduleInfo.includePaths[k] + .replace("$$QT_MODULE_INCLUDE_BASE", qtProps.includePath) + .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath); + } + } else if (key.endsWith(".DEFINES")) { + moduleInfo.compilerDefines = splitNonEmpty(value, ' '); + } else if (key.endsWith(".VERSION")) { + moduleInfo.version = value; + } else if (key.endsWith(".plugin_types")) { + moduleInfo.supportedPluginTypes = splitNonEmpty(value, ' '); + } else if (key.endsWith(".TYPE")) { + moduleInfo.pluginData.type = value; + } else if (key.endsWith(".EXTENDS")) { + moduleInfo.pluginData["extends"] = splitNonEmpty(value, ' '); + for (k = 0; k < moduleInfo.pluginData["extends"].length; ++k) { + if (moduleInfo.pluginData["extends"][k] === "-") { + moduleInfo.pluginData.splice(k, 1); + moduleInfo.pluginData.autoLoad = false; + break; + } + } + } else if (key.endsWith(".CLASS_NAME")) { + moduleInfo.pluginData.className = value; + } + } + if (hasV2 && !hasModuleEntry) + moduleInfo.hasLibrary = false; + + // Fix include paths for Apple frameworks. + // The qt_lib_XXX.pri files contain wrong values for versions < 5.6. + if (!hasV2 && isFramework(moduleInfo, qtProps)) { + moduleInfo.includePaths = []; + var baseIncDir = frameworkHeadersPath(moduleInfo, qtProps); + if (moduleInfo.isPrivate) { + baseIncDir = FileInfo.joinPaths(baseIncDir, moduleInfo.version); + moduleInfo.includePaths.push(baseIncDir, + FileInfo.joinPaths(baseIncDir, moduleInfo.name)); + } else { + moduleInfo.includePaths.push(baseIncDir); + } + } + + setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles); + + modules.push(moduleInfo); + if (moduleInfo.qbsName === "testlib") + addTestModule(modules); + if (moduleInfo.qbsName === "designercomponents-private") + addDesignerComponentsModule(modules); + } + + replaceQtLibNamesWithFilePath(modules, qtProps); + removeDuplicatedDependencyLibs(modules); + return modules; +} + +function extractQbsArchs(module, qtProps) { + if (qtProps.mkspecName.startsWith("macx-")) { + var archs = []; + if (module.libFilePathRelease) + archs = Utilities.getArchitecturesFromBinary(module.libFilePathRelease); + return archs; + } + var qbsArch = Utilities.canonicalArchitecture(qtProps.architecture); + if (qbsArch === "arm" && qtProps.mkspecPath.contains("android")) + qbsArch = "armv7a"; + + // Qt4 has "QT_ARCH = windows" in qconfig.pri for both MSVC and mingw. + if (qbsArch === "windows") + return [] + + return [qbsArch]; +} + +function qbsTargetPlatformFromQtMkspec(qtProps) { + var mkspec = qtProps.mkspecName; + var idx = mkspec.lastIndexOf('/'); + if (idx !== -1) + mkspec = mkspec.slice(idx + 1); + if (mkspec.startsWith("aix-")) + return "aix"; + if (mkspec.startsWith("android-")) + return "android"; + if (mkspec.startsWith("cygwin-")) + return "windows"; + if (mkspec.startsWith("darwin-")) + return "macos"; + if (mkspec.startsWith("freebsd-")) + return "freebsd"; + if (mkspec.startsWith("haiku-")) + return "haiku"; + if (mkspec.startsWith(("hpux-")) || mkspec.startsWith(("hpuxi-"))) + return "hpux"; + if (mkspec.startsWith("hurd-")) + return "hurd"; + if (mkspec.startsWith("integrity-")) + return "integrity"; + if (mkspec.startsWith("linux-")) + return "linux"; + if (mkspec.startsWith("macx-")) { + if (mkspec.startsWith("macx-ios-")) + return "ios"; + if (mkspec.startsWith("macx-tvos-")) + return "tvos"; + if (mkspec.startsWith("macx-watchos-")) + return "watchos"; + return "macos"; + } + if (mkspec.startsWith("netbsd-")) + return "netbsd"; + if (mkspec.startsWith("openbsd-")) + return "openbsd"; + if (mkspec.startsWith("qnx-")) + return "qnx"; + if (mkspec.startsWith("solaris-")) + return "solaris"; + if (mkspec.startsWith("vxworks-")) + return "vxworks"; + if (targetsDesktopWindows(qtProps) || mkspec.startsWith("winrt-")) + return "windows"; +} + +function pathToJSLiteral(path) { return JSON.stringify(FileInfo.fromNativeSeparators(path)); } + +function defaultQpaPlugin(module, qtProps) { + if (qtProps.qtMajorVersion < 5) + return undefined; + if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) { + var qConfigPri = new TextFile(FileInfo.joinPaths(qtProps.mkspecBasePath, "qconfig.pri")); + var magicString = "QT_DEFAULT_QPA_PLUGIN ="; + while (!qConfigPri.atEof()) { + var line = qConfigPri.readLine().trim(); + if (line.startsWith(magicString)) + return line.slice(magicString.length).trim(); + } + qConfigPri.close(); + } else { + var gtGuiHeadersPath = qtProps.frameworkBuild + ? FileInfo.joinPaths(qtProps.libraryPath, "QtGui.framework", "Headers") + : FileInfo.joinPaths(qtProps.includePath, "QtGui"); + var qtGuiConfigHeader = FileInfo.joinPaths(gtGuiHeadersPath, "qtgui-config.h"); + var headerFiles = []; + headerFiles.push(qtGuiConfigHeader); + while (headerFiles.length > 0) { + var filePath = headerFiles.shift(); + var headerFile = new TextFile(filePath, TextFile.ReadOnly); + var regexp = /^#define QT_QPA_DEFAULT_PLATFORM_NAME "(.+)".*$/; + var includeRegexp = /^#include "(.+)".*$/; + while (!headerFile.atEof()) { + line = headerFile.readLine().trim(); + var match = line.match(regexp); + if (match) + return 'q' + match[1]; + match = line.match(includeRegexp); + if (match) { + var includedFile = match[1]; + if (!FileInfo.isAbsolute(includedFile)) { + includedFile = FileInfo.cleanPath( + FileInfo.joinPaths(FileInfo.path(filePath), includedFile)); + } + headerFiles.push(includedFile); + } + } + headerFile.close(); + } + } + + if (module.isStaticLibrary) + console.warn("Could not determine default QPA plugin for static Qt."); +} + +function libraryFileTag(module, qtProps) { + if (module.isStaticLibrary) + return "staticlibrary"; + return isMsvcQt(qtProps) || qtProps.mkspecName.startsWith("win32-g++") + ? "dynamiclibrary_import" : "dynamiclibrary"; +} + +function findVariable(content, start) { + var result = [-1, -1]; + result[0] = content.indexOf('@', start); + if (result[0] === -1) + return result; + result[1] = content.indexOf('@', result[0] + 1); + if (result[1] === -1) { + result[0] = -1; + return result; + } + var forbiddenChars = [' ', '\n']; + for (var i = 0; i < forbiddenChars.length; ++i) { + var forbidden = forbiddenChars[i]; + var k = content.indexOf(forbidden, result[0] + 1); + if (k !== -1 && k < result[1]) + return findVariable(content, result[0] + 1); + } + return result; +} + +function toJSLiteral(v) { + if (v === undefined) + return "undefined"; + return JSON.stringify(v); +} + +function minVersionJsString(minVersion) { + return !minVersion ? "original" : toJSLiteral(minVersion); +} + +function replaceSpecialValues(content, module, qtProps) { + var dict = { + archs: toJSLiteral(extractQbsArchs(module, qtProps)), + targetPlatform: toJSLiteral(qbsTargetPlatformFromQtMkspec(qtProps)), + config: toJSLiteral(qtProps.configItems), + qtConfig: toJSLiteral(qtProps.qtConfigItems), + binPath: toJSLiteral(qtProps.binaryPath), + libPath: toJSLiteral(qtProps.libraryPath), + pluginPath: toJSLiteral(qtProps.pluginPath), + incPath: toJSLiteral(qtProps.includePath), + docPath: toJSLiteral(qtProps.documentationPath), + mkspecName: toJSLiteral(qtProps.mkspecName), + mkspecPath: toJSLiteral(qtProps.mkspecPath), + version: toJSLiteral(qtProps.qtVersion), + libInfix: toJSLiteral(qtProps.qtLibInfix), + availableBuildVariants: toJSLiteral(qtProps.buildVariant), + staticBuild: toJSLiteral(qtProps.staticBuild), + frameworkBuild: toJSLiteral(qtProps.frameworkBuild), + name: toJSLiteral(moduleNameWithoutPrefix(module)), + has_library: toJSLiteral(module.hasLibrary), + dependencies: toJSLiteral(module.dependencies), + includes: toJSLiteral(module.includePaths), + staticLibsDebug: toJSLiteral(module.staticLibrariesDebug), + staticLibsRelease: toJSLiteral(module.staticLibrariesRelease), + dynamicLibsDebug: toJSLiteral(module.dynamicLibrariesDebug), + dynamicLibsRelease: toJSLiteral(module.dynamicLibrariesRelease), + linkerFlagsDebug: toJSLiteral(module.linkerFlagsDebug), + linkerFlagsRelease: toJSLiteral(module.linkerFlagsRelease), + libraryPaths: toJSLiteral(module.libraryPaths), + frameworkPathsDebug: toJSLiteral(module.frameworkPathsDebug), + frameworkPathsRelease: toJSLiteral(module.frameworkPathsRelease), + frameworksDebug: toJSLiteral(module.frameworksDebug), + frameworksRelease: toJSLiteral(module.frameworksRelease), + libFilePathDebug: toJSLiteral(module.libFilePathDebug), + libFilePathRelease: toJSLiteral(module.libFilePathRelease), + libNameForLinkerDebug: toJSLiteral(libNameForLinker(module, qtProps, true)), + pluginTypes: toJSLiteral(module.supportedPluginTypes), + moduleConfig: toJSLiteral(module.config), + libNameForLinkerRelease: toJSLiteral(libNameForLinker(module, qtProps, false)), + entryPointLibsDebug: toJSLiteral(qtProps.entryPointLibsDebug), + entryPointLibsRelease: toJSLiteral(qtProps.entryPointLibsRelease), + minWinVersion: minVersionJsString(qtProps.windowsVersion), + minMacVersion: minVersionJsString(qtProps.macosVersion), + minIosVersion: minVersionJsString(qtProps.iosVersion), + minTvosVersion: minVersionJsString(qtProps.tvosVersion), + minWatchosVersion: minVersionJsString(qtProps.watchosVersion), + minAndroidVersion: minVersionJsString(qtProps.androidVersion), + }; + + var additionalContent = ""; + var compilerDefines = toJSLiteral(module.compilerDefines); + if (module.qbsName === "declarative" || module.qbsName === "quick") { + var debugMacro = module.qbsName === "declarative" || qtProps.qtMajorVersion < 5 + ? "QT_DECLARATIVE_DEBUG" : "QT_QML_DEBUG"; + var indent = " "; + additionalContent = "property bool qmlDebugging: false\n" + + indent + "property string qmlPath"; + if (qtProps.qmlPath) + additionalContent += ": " + pathToJSLiteral(qtProps.qmlPath) + '\n'; + else + additionalContent += '\n'; + + additionalContent += indent + "property string qmlImportsPath: " + + pathToJSLiteral(qtProps.qmlImportPath); + + compilerDefines = "{\n" + + indent + indent + "var result = " + compilerDefines + ";\n" + + indent + indent + "if (qmlDebugging)\n" + + indent + indent + indent + "result.push(\"" + debugMacro + "\");\n" + + indent + indent + "return result;\n" + + indent + "}"; + } + dict.defines = compilerDefines; + if (module.qbsName === "gui") + dict.defaultQpaPlugin = toJSLiteral(defaultQpaPlugin(module, qtProps)); + if (module.qbsName === "qml") + dict.qmlPath = pathToJSLiteral(qtProps.qmlPath); + if (module.isStaticLibrary && module.qbsName !== "core") { + if (additionalContent) + additionalContent += "\n "; + additionalContent += "isStaticLibrary: true"; + } + if (module.isPlugin) { + dict.className = toJSLiteral(module.pluginData.className); + dict["extends"] = toJSLiteral(module.pluginData["extends"]); + } + if (module.hasLibrary && !isFramework(module, qtProps)) { + if (additionalContent) + additionalContent += "\n"; + indent = " "; + additionalContent += "Group {\n"; + if (module.isPlugin) { + additionalContent += indent + indent + + "condition: Qt[\"" + module.qbsName + "\"].enableLinking\n"; + } + additionalContent += indent + indent + "files: [Qt[\"" + module.qbsName + "\"]" + + ".libFilePath]\n" + + indent + indent + "filesAreTargets: true\n" + + indent + indent + "fileTags: [\"" + libraryFileTag(module, qtProps) + + "\"]\n" + + indent + "}"; + } + dict.additionalContent = additionalContent; + + for (var pos = findVariable(content, 0); pos[0] !== -1; + pos = findVariable(content, pos[0])) { + var replacement = dict[content.slice(pos[0] + 1, pos[1])] || ""; + content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1); + pos[0] += replacement.length; + } + return content; +} + +function copyTemplateFile(fileName, targetDirectory, qtProps, location, allFiles, module, pluginMap, + nonEssentialPlugins) +{ + if (!File.makePath(targetDirectory)) { + throw "Cannot create directory '" + toNative(targetDirectory) + "'."; + } + var sourceFile = new TextFile(FileInfo.joinPaths(location, "templates", fileName), + TextFile.ReadOnly); + var newContent = sourceFile.readAll(); + if (module) { + newContent = replaceSpecialValues(newContent, module, qtProps); + } else { + newContent = newContent.replace("@allPluginsByType@", + '(' + toJSLiteral(pluginMap) + ')'); + newContent = newContent.replace("@nonEssentialPlugins@", + toJSLiteral(nonEssentialPlugins)); + } + sourceFile.close(); + var targetPath = FileInfo.joinPaths(targetDirectory, fileName); + allFiles.push(targetPath); + var targetFile = new TextFile(targetPath, TextFile.WriteOnly); + targetFile.write(newContent); + targetFile.close(); +} + +function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) { + if (!File.exists(qmakeFilePath)) + throw "The specified qmake file path '" + toNative(qmakeFilePath) + "' does not exist."; + var qtProps = getQtProperties(qmakeFilePath, qbs); + var modules = qtProps.qtMajorVersion < 5 ? allQt4Modules(qtProps) : allQt5Modules(qtProps); + var pluginsByType = []; + var nonEssentialPlugins = []; + for (var i = 0; i < modules.length; ++i) { + var m = modules[i]; + if (m.isPlugin) { + if (!pluginsByType[m.pluginData.type]) + pluginsByType[m.pluginData.type] = []; + pluginsByType[m.pluginData.type].push(m.name); + if (!m.pluginData.autoLoad) + nonEssentialPlugins.push(m.name); + } + } + + var relativeSearchPath = uniquify ? Utilities.getHash(qmakeFilePath) : ""; + var qbsQtModuleBaseDir = FileInfo.joinPaths(outputBaseDir, relativeSearchPath, "modules", "Qt"); + if (File.exists(qbsQtModuleBaseDir)) + File.remove(qbsQtModuleBaseDir); + + var allFiles = []; + copyTemplateFile("QtModule.qbs", qbsQtModuleBaseDir, qtProps, location, allFiles); + copyTemplateFile("QtPlugin.qbs", qbsQtModuleBaseDir, qtProps, location, allFiles); + copyTemplateFile("plugin_support.qbs", FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), + qtProps, location, allFiles, undefined, pluginsByType, nonEssentialPlugins); + + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + var qbsQtModuleDir = FileInfo.joinPaths(qbsQtModuleBaseDir, module.qbsName); + var moduleTemplateFileName; + if (module.qbsName === "core") { + moduleTemplateFileName = "core.qbs"; + copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, location, allFiles); + copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, location, allFiles); + } else if (module.qbsName === "gui") { + moduleTemplateFileName = "gui.qbs"; + } else if (module.qbsName === "scxml") { + moduleTemplateFileName = "scxml.qbs"; + } else if (module.qbsName === "dbus") { + moduleTemplateFileName = "dbus.qbs"; + copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, location, allFiles); + } else if (module.qbsName === "qml") { + moduleTemplateFileName = "qml.qbs"; + copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, location, allFiles); + var qmlcacheStr = "qmlcache"; + if (File.exists(FileInfo.joinPaths(qtProps.binaryPath, + "qmlcachegen" + exeSuffix(qbs)))) { + copyTemplateFile(qmlcacheStr + ".qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps, + location, allFiles); + } + } else if (module.qbsName === "quick") { + moduleTemplateFileName = "quick.qbs"; + copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, location, allFiles); + } else if (module.isPlugin) { + moduleTemplateFileName = "plugin.qbs"; + } else { + moduleTemplateFileName = "module.qbs"; + } + copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, location, allFiles, + module); + } + + // Note that it's not strictly necessary to copy this one, as it has no variable content. + // But we do it anyway for consistency. + copyTemplateFile("android_support.qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"), + qtProps, location, allFiles); + return relativeSearchPath; +} + +function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) { + qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths, qbs); + if (!qmakeFilePaths || qmakeFilePaths.length === 0) + return []; + var uniquifySearchPath = qmakeFilePaths.length > 1; + var searchPaths = []; + for (var i = 0; i < qmakeFilePaths.length; ++i) { + try { + console.info("Setting up Qt at '" + toNative(qmakeFilePaths[i]) + "'..."); + var searchPath = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath, + location, qbs); + if (searchPath !== undefined) { + searchPaths.push(searchPath); + console.info("Qt was set up successfully."); + } + } catch (e) { + console.warn("Error setting up Qt for '" + toNative(qmakeFilePaths[i]) + "': " + e); + } + } + return searchPaths; +} diff --git a/share/qbs/module-providers/Qt/templates/QtModule.qbs b/share/qbs/module-providers/Qt/templates/QtModule.qbs new file mode 100644 index 000000000..aa7c1d15a --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/QtModule.qbs @@ -0,0 +1,86 @@ +import qbs.FileInfo + +Module { + condition: (qbs.targetPlatform === targetPlatform || isCombinedUIKitBuild) + && (!qbs.architecture + || architectures.length === 0 + || architectures.contains(qbs.architecture)) + + readonly property bool isCombinedUIKitBuild: ["ios", "tvos", "watchos"].contains(targetPlatform) + && ["x86", "x86_64"].contains(qbs.architecture) + && qbs.targetPlatform === targetPlatform + "-simulator" + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + + Depends { name: "Qt.plugin_support" } + property stringList pluginTypes + Qt.plugin_support.pluginTypes: pluginTypes + Depends { + condition: Qt.core.staticBuild && !isPlugin + name: "Qt"; + submodules: { + // We have to pull in all plugins here, because dependency resolving happens + // before module merging, and we don't know yet if someone set + // Qt.pluginSupport.pluginsByType in the product. + // The real filtering is done later by the plugin module files themselves. + var list = []; + var allPlugins = Qt.plugin_support.allPluginsByType; + for (var i = 0; i < (pluginTypes || []).length; ++i) + Array.prototype.push.apply(list, allPlugins[pluginTypes[i]]) + return list; + } + } + + property string qtModuleName + property path binPath: Qt.core.binPath + property path incPath: Qt.core.incPath + property path libPath: Qt.core.libPath + property string qtLibInfix: Qt.core.libInfix + property string libNameForLinkerDebug + property string libNameForLinkerRelease + property string libNameForLinker: Qt.core.qtBuildVariant === "debug" + ? libNameForLinkerDebug : libNameForLinkerRelease + property string libFilePathDebug + property string libFilePathRelease + property string libFilePath: Qt.core.qtBuildVariant === "debug" + ? libFilePathDebug : libFilePathRelease + version: Qt.core.version + property bool hasLibrary: true + property bool isStaticLibrary: false + property bool isPlugin: false + + property stringList architectures + property string targetPlatform + property stringList staticLibsDebug + property stringList staticLibsRelease + property stringList dynamicLibsDebug + property stringList dynamicLibsRelease + property stringList linkerFlagsDebug + property stringList linkerFlagsRelease + property stringList staticLibs: Qt.core.qtBuildVariant === "debug" + ? staticLibsDebug : staticLibsRelease + property stringList dynamicLibs: Qt.core.qtBuildVariant === "debug" + ? dynamicLibsDebug : dynamicLibsRelease + property stringList frameworksDebug + property stringList frameworksRelease + property stringList frameworkPathsDebug + property stringList frameworkPathsRelease + property stringList mFrameworks: Qt.core.qtBuildVariant === "debug" + ? frameworksDebug : frameworksRelease + property stringList mFrameworkPaths: Qt.core.qtBuildVariant === "debug" + ? frameworkPathsDebug: frameworkPathsRelease + cpp.linkerFlags: Qt.core.qtBuildVariant === "debug" + ? linkerFlagsDebug : linkerFlagsRelease + property bool enableLinking: qtModuleName != undefined && hasLibrary + property stringList moduleConfig + + Properties { + condition: enableLinking + cpp.staticLibraries: staticLibs + cpp.dynamicLibraries: dynamicLibs + cpp.frameworks: mFrameworks.concat(!isStaticLibrary && Qt.core.frameworkBuild + ? [libNameForLinker] : []) + cpp.frameworkPaths: mFrameworkPaths + } +} diff --git a/share/qbs/module-providers/Qt/templates/QtPlugin.qbs b/share/qbs/module-providers/Qt/templates/QtPlugin.qbs new file mode 100644 index 000000000..23a6795f3 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/QtPlugin.qbs @@ -0,0 +1,49 @@ +import qbs.FileInfo +import qbs.TextFile + +QtModule { + isPlugin: true + + property string className + property stringList extendsModules + + enableLinking: { + if (!base) + return false; + if (!isStaticLibrary) + return false; + if (!(Qt.plugin_support.enabledPlugins || []).contains(qtModuleName)) + return false; + if (!extendsModules || extendsModules.length === 0) + return true; + for (var i = 0; i < extendsModules.length; ++i) { + var moduleName = extendsModules[i]; + if (product.Qt[moduleName] && product.Qt[moduleName].present) + return true; + } + return false; + } + + Rule { + condition: enableLinking + multiplex: true + Artifact { + filePath: product.targetName + "_qt_plugin_import_" + + product.moduleProperty(product.moduleName, "qtModuleName") + ".cpp" + fileTags: "cpp" + } + + prepare: { + var cmd = new JavaScriptCommand(); + var pluginName = product.moduleProperty(product.moduleName, "qtModuleName"); + cmd.description = "Creating static import for plugin '" + pluginName + "'."; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + var className = product.moduleProperty(product.moduleName, "className"); + f.writeLine("#include <QtPlugin>\n\nQ_IMPORT_PLUGIN(" + className + ")"); + f.close(); + }; + return cmd; + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs new file mode 100644 index 000000000..79276a494 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/android_support.qbs @@ -0,0 +1,291 @@ +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile +import qbs.Utilities + +Module { + property bool useMinistro: false + property string qmlRootDir: product.sourceDirectory + property stringList extraPrefixDirs + property stringList deploymentDependencies // qmake: ANDROID_DEPLOYMENT_DEPENDENCIES + property stringList extraPlugins // qmake: ANDROID_EXTRA_PLUGINS + property bool verboseAndroidDeployQt: false + + property string _androidDeployQtFilePath: FileInfo.joinPaths(_qtInstallDir, "bin", + "androiddeployqt") + property string _qtInstallDir + property bool _enableSdkSupport: product.type && product.type.contains("android.apk") + && !consoleApplication + property bool _enableNdkSupport: !product.aggregate || product.multiplexConfigurationId + property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android") + property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out") + + Depends { name: "Android.sdk"; condition: _enableSdkSupport } + Depends { name: "Android.ndk"; condition: _enableNdkSupport } + Depends { name: "java"; condition: _enableSdkSupport } + + Properties { + condition: _enableNdkSupport && qbs.toolchain.contains("clang") + Android.ndk.appStl: "c++_shared" + } + Properties { + condition: _enableNdkSupport && !qbs.toolchain.contains("clang") + Android.ndk.appStl: "gnustl_shared" + } + Properties { + condition: _enableSdkSupport + Android.sdk.customManifestProcessing: true + java._tagJniHeaders: false // prevent rule cycle + } + + Rule { + condition: _enableSdkSupport + multiplex: true + property stringList inputTags: "android.nativelibrary" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags + Artifact { + filePath: "androiddeployqt.json" + fileTags: "qt_androiddeployqt_input" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var theBinary; + var nativeLibs = inputs["android.nativelibrary"]; + if (nativeLibs.length === 1) { + theBinary = nativeLibs[0]; + } else { + for (i = 0; i < nativeLibs.length; ++i) { + var candidate = nativeLibs[i]; + if (!candidate.fileName.contains(candidate.product.targetName)) + continue; + if (!theBinary) { + theBinary = candidate; + continue; + } + if (theBinary.product.name === product.name + && candidate.product.name !== product.name) { + continue; // We already have a better match. + } + if (candidate.product.name === product.name + && theBinary.product.name !== product.name) { + theBinary = candidate; // The new candidate is a better match. + continue; + } + throw "Qt applications for Android support only one native binary " + + "per package.\n" + + "In particular, you cannot build a Qt app for more than " + + "one architecture at the same time."; + } + } + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("{"); + f.writeLine('"description": "This file was generated by qbs to be read by ' + + 'androiddeployqt and should not be modified by hand.",'); + f.writeLine('"qt": "' + product.Qt.android_support._qtInstallDir + '",'); + f.writeLine('"sdk": "' + product.Android.sdk.sdkDir + '",'); + f.writeLine('"sdkBuildToolsRevision": "' + product.Android.sdk.buildToolsVersion + + '",'); + f.writeLine('"ndk": "' + product.Android.sdk.ndkDir + '",'); + var toolPrefix = theBinary.cpp.toolchainTriple; + var toolchainPrefix = toolPrefix.startsWith("i686-") ? "x86" : toolPrefix; + f.writeLine('"toolchain-prefix": "' + toolchainPrefix + '",'); + f.writeLine('"tool-prefix": "' + toolPrefix + '",'); + f.writeLine('"toolchain-version": "' + theBinary.Android.ndk.toolchainVersion + + '",'); + f.writeLine('"ndk-host": "' + theBinary.Android.ndk.hostArch + '",'); + f.writeLine('"target-architecture": "' + theBinary.Android.ndk.abi + '",'); + f.writeLine('"qml-root-path": "' + product.Qt.android_support.qmlRootDir + '",'); + var deploymentDeps = product.Qt.android_support.deploymentDependencies; + if (deploymentDeps && deploymentDeps.length > 0) + f.writeLine('"deployment-dependencies": "' + deploymentDeps.join() + '",'); + var extraPlugins = product.Qt.android_support.extraPlugins; + if (extraPlugins && extraPlugins.length > 0) + f.writeLine('"android-extra-plugins": "' + extraPlugins.join() + '",'); + var prefixDirs = product.Qt.android_support.extraPrefixDirs; + if (prefixDirs && prefixDirs.length > 0) + f.writeLine('"extraPrefixDirs": ' + JSON.stringify(prefixDirs) + ','); + if (Array.isArray(product.qmlImportPaths) && product.qmlImportPaths.length > 0) + f.writeLine('"qml-import-paths": "' + product.qmlImportPaths.join(',') + '",'); + f.writeLine('"application-binary": "' + theBinary.filePath + '"'); + f.writeLine("}"); + f.close(); + }; + return cmd; + } + } + + // We use the manifest template from the Qt installation if and only if the project + // does not provide a manifest file. + Rule { + condition: _enableSdkSupport + multiplex: true + requiresInputs: false + inputs: "android.manifest" + excludedInputs: "qt.android_manifest" + outputFileTags: ["android.manifest", "qt.android_manifest"] + outputArtifacts: { + if (inputs["android.manifest"]) + return []; + return [{ + filePath: "qt_manifest/AndroidManifest.xml", + fileTags: ["android.manifest", "qt.android_manifest"] + }]; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "copying Qt Android manifest template"; + cmd.sourceCode = function() { + File.copy(FileInfo.joinPaths(product.Qt.android_support._templatesBaseDir, + "templates", "AndroidManifest.xml"), output.filePath); + }; + return cmd; + } + } + + Rule { + condition: _enableSdkSupport + multiplex: true + inputs: ["qt_androiddeployqt_input", "android.manifest_processed"] + outputFileTags: [ + "android.manifest_final", "android.resources", "android.assets", "bundled_jar", + "android.deployqt_list", + ] + outputArtifacts: { + var artifacts = [ + { + filePath: "AndroidManifest.xml", + fileTags: "android.manifest_final" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml", + fileTags: "android.resources" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + + "/res/values/strings.xml", + fileTags: "android.resources" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + "/assets/.dummy", + fileTags: "android.assets" + }, + { + filePath: "deployqt.list", + fileTags: "android.deployqt_list" + }, + + ]; + if (!product.Qt.android_support.useMinistro) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.java.classFilesDir, "QtAndroid.jar"), + fileTags: ["bundled_jar"] + }); + } + return artifacts; + } + prepare: { + var copyCmd = new JavaScriptCommand(); + copyCmd.description = "copying Qt resource templates"; + copyCmd.sourceCode = function() { + File.copy(inputs["android.manifest_processed"][0].filePath, + product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml"); + File.copy(product.Qt.android_support._templatesBaseDir + "/java/res", + product.Qt.android_support._deployQtOutDir + "/res"); + File.copy(product.Qt.android_support._templatesBaseDir + + "/templates/res/values/libs.xml", + product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml"); + try { + File.remove(FileInfo.path(outputs["android.assets"][0].filePath)); + } catch (e) { + } + }; + var androidDeployQtArgs = [ + "--output", product.Qt.android_support._deployQtOutDir, + "--input", inputs["qt_androiddeployqt_input"][0].filePath, "--aux-mode", + "--deployment", product.Qt.android_support.useMinistro ? "ministro" : "bundled", + "--android-platform", product.Android.sdk.platform, + ]; + if (product.Qt.android_support.verboseAndroidDeployQt) + args.push("--verbose"); + var androidDeployQtCmd = new Command( + product.Qt.android_support._androidDeployQtFilePath, androidDeployQtArgs); + androidDeployQtCmd.description = "running androiddeployqt"; + + // We do not want androiddeployqt to write directly into our APK base dir, so + // we ran it on an isolated directory and now we move stuff over. + // We remember the files for which we do that, so if the next invocation + // of androiddeployqt creates fewer files, the other ones are removed from + // the APK base dir. + var moveCmd = new JavaScriptCommand(); + moveCmd.description = "processing androiddeployqt outout"; + moveCmd.sourceCode = function() { + File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml", + outputs["android.manifest_final"][0].filePath); + var libsDir = product.Qt.android_support._deployQtOutDir + "/libs"; + var libDir = product.Android.sdk.apkContentsDir + "/lib"; + var listFilePath = outputs["android.deployqt_list"][0].filePath; + var oldLibs = []; + try { + var listFile = new TextFile(listFilePath, TextFile.ReadOnly); + var listFileLine = listFile.readLine(); + while (listFileLine) { + oldLibs.push(listFileLine); + listFileLine = listFile.readLine(); + } + listFile.close(); + } catch (e) { + } + listFile = new TextFile(listFilePath, TextFile.WriteOnly); + var newLibs = []; + var moveLibFiles = function(prefix) { + var fullSrcPrefix = FileInfo.joinPaths(libsDir, prefix); + var files = File.directoryEntries(fullSrcPrefix, File.Files); + for (var i = 0; i < files.length; ++i) { + var file = files[i]; + var srcFilePath = FileInfo.joinPaths(fullSrcPrefix, file); + var targetFilePath; + if (file.endsWith(".jar")) + targetFilePath = FileInfo.joinPaths(product.java.classFilesDir, file); + else + targetFilePath = FileInfo.joinPaths(libDir, prefix, file); + File.move(srcFilePath, targetFilePath); + listFile.writeLine(targetFilePath); + newLibs.push(targetFilePath); + } + var dirs = File.directoryEntries(fullSrcPrefix, + File.Dirs | File.NoDotAndDotDot); + for (i = 0; i < dirs.length; ++i) + moveLibFiles(FileInfo.joinPaths(prefix, dirs[i])); + }; + moveLibFiles(""); + listFile.close(); + for (i = 0; i < oldLibs.length; ++i) { + if (!newLibs.contains(oldLibs[i])) + File.remove(oldLibs[i]); + } + }; + return [copyCmd, androidDeployQtCmd, moveCmd]; + } + } + + Group { + condition: Qt.android_support._enableSdkSupport + name: "helper sources from qt" + prefix: Qt.android_support._templatesBaseDir + "/java/" + Android.sdk.aidlSearchPaths: prefix + "src" + files: [ + "**/*.java", + "**/*.aidl", + ] + } + + validate: { + if (Utilities.versionCompare(version, "5.12") < 0) + throw ModUtils.ModuleError("Cannot use Qt " + version + " with Android. " + + "Version 5.12 or later is required."); + } +} diff --git a/share/qbs/module-providers/Qt/templates/core.qbs b/share/qbs/module-providers/Qt/templates/core.qbs new file mode 100644 index 000000000..b2f05d8e9 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/core.qbs @@ -0,0 +1,510 @@ +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile +import qbs.Utilities +import qbs.Xml +import "moc.js" as Moc +import "qdoc.js" as Qdoc + +Module { + condition: (qbs.targetPlatform === targetPlatform || isCombinedUIKitBuild) + && (!qbs.architecture + || architectures.length === 0 + || architectures.contains(qbs.architecture)) + + readonly property bool isCombinedUIKitBuild: ["ios", "tvos", "watchos"].contains(targetPlatform) + && ["x86", "x86_64"].contains(qbs.architecture) + && qbs.targetPlatform === targetPlatform + "-simulator" + + Depends { name: "cpp" } + + Depends { name: "Qt.android_support"; condition: qbs.targetOS.contains("android") } + Properties { + condition: qbs.targetOS.contains("android") + Qt.android_support._qtInstallDir: FileInfo.path(binPath) + Qt.android_support.version: version + } + + version: @version@ + property stringList architectures: @archs@ + property string targetPlatform: @targetPlatform@ + property string libInfix: @libInfix@ + property stringList config: @config@ + property stringList qtConfig: @qtConfig@ + property path binPath: @binPath@ + property path incPath: @incPath@ + property path libPath: @libPath@ + property path pluginPath: @pluginPath@ + property string mkspecName: @mkspecName@ + property path mkspecPath: @mkspecPath@ + property string mocName: "moc" + property stringList mocFlags: [] + property string lreleaseName: "lrelease" + property string qdocName: versionMajor >= 5 ? "qdoc" : "qdoc3" + property stringList qdocEnvironment + property path docPath: @docPath@ + property stringList helpGeneratorArgs: versionMajor >= 5 ? ["-platform", "minimal"] : [] + property var versionParts: version ? version.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + property bool frameworkBuild: @frameworkBuild@ + property bool staticBuild: @staticBuild@ + property stringList pluginMetaData: [] + property bool enableKeywords: true + + property stringList availableBuildVariants: @availableBuildVariants@ + property string qtBuildVariant: { + if (availableBuildVariants.contains(qbs.buildVariant)) + return qbs.buildVariant; + return availableBuildVariants.length > 0 ? availableBuildVariants[0] : ""; + } + + property stringList staticLibsDebug: @staticLibsDebug@ + property stringList staticLibsRelease: @staticLibsRelease@ + property stringList dynamicLibsDebug: @dynamicLibsDebug@ + property stringList dynamicLibsRelease: @dynamicLibsRelease@ + property stringList staticLibs: qtBuildVariant === "debug" + ? staticLibsDebug : staticLibsRelease + property stringList dynamicLibs: qtBuildVariant === "debug" + ? dynamicLibsDebug : dynamicLibsRelease + property stringList linkerFlagsDebug: @linkerFlagsDebug@ + property stringList linkerFlagsRelease: @linkerFlagsRelease@ + property stringList coreLinkerFlags: qtBuildVariant === "debug" + ? linkerFlagsDebug : linkerFlagsRelease + property stringList frameworksDebug: @frameworksDebug@ + property stringList frameworksRelease: @frameworksRelease@ + property stringList coreFrameworks: qtBuildVariant === "debug" + ? frameworksDebug : frameworksRelease + property stringList frameworkPathsDebug: @frameworkPathsDebug@ + property stringList frameworkPathsRelease: @frameworkPathsRelease@ + property stringList coreFrameworkPaths: qtBuildVariant === "debug" + ? frameworkPathsDebug : frameworkPathsRelease + property string libNameForLinkerDebug: @libNameForLinkerDebug@ + property string libNameForLinkerRelease: @libNameForLinkerRelease@ + property string libNameForLinker: qtBuildVariant === "debug" + ? libNameForLinkerDebug : libNameForLinkerRelease + property string libFilePathDebug: @libFilePathDebug@ + property string libFilePathRelease: @libFilePathRelease@ + property string libFilePath: qtBuildVariant === "debug" + ? libFilePathDebug : libFilePathRelease + + property stringList coreLibPaths: @libraryPaths@ + + // These are deliberately not path types + // We don't want to resolve them against the source directory + property string generatedHeadersDir: product.buildDirectory + "/qt.headers" + property string qdocOutputDir: product.buildDirectory + "/qdoc_html" + property string qmDir: product.destinationDirectory + property string qmBaseName: product.targetName + property bool lreleaseMultiplexMode: false + + property stringList moduleConfig: @moduleConfig@ + Properties { + condition: moduleConfig.contains("use_gold_linker") + cpp.linkerVariant: "gold" + } + + cpp.entryPoint: qbs.targetOS.containsAny(["ios", "tvos"]) + && Utilities.versionCompare(version, "5.6.0") >= 0 + ? "_qt_main_wrapper" + : undefined + cpp.cxxLanguageVersion: Utilities.versionCompare(version, "5.7.0") >= 0 ? "c++11" : original + cpp.enableCompilerDefinesByLanguage: ["cpp"].concat( + qbs.targetOS.contains("darwin") ? ["objcpp"] : []) + cpp.defines: { + var defines = @defines@; + // ### QT_NO_DEBUG must be added if the current build variant is derived + // from the build variant "release" + if (!qbs.debugInformation) + defines.push("QT_NO_DEBUG"); + if (!enableKeywords) + defines.push("QT_NO_KEYWORDS"); + if (qbs.targetOS.containsAny(["ios", "tvos"])) { + defines = defines.concat(["DARWIN_NO_CARBON", "QT_NO_CORESERVICES", "QT_NO_PRINTER", + "QT_NO_PRINTDIALOG"]); + if (Utilities.versionCompare(version, "5.6.0") < 0) + defines.push("main=qtmn"); + } + return defines; + } + cpp.driverFlags: { + var flags = []; + if (qbs.toolchain.contains("gcc")) { + if (config.contains("sanitize_address")) + flags.push("-fsanitize=address"); + if (config.contains("sanitize_undefined")) + flags.push("-fsanitize=undefined"); + if (config.contains("sanitize_thread")) + flags.push("-fsanitize=thread"); + if (config.contains("sanitize_memory")) + flags.push("-fsanitize=memory"); + } + return flags; + } + cpp.includePaths: { + var paths = @includes@; + paths.push(mkspecPath, generatedHeadersDir); + return paths; + } + cpp.libraryPaths: { + var libPaths = [libPath]; + if (staticBuild && pluginPath) + libPaths.push(pluginPath + "/platforms"); + libPaths = libPaths.concat(coreLibPaths); + return libPaths; + } + cpp.staticLibraries: { + var libs = []; + if (qbs.targetOS.contains('windows') && !product.consoleApplication) { + libs = libs.concat(qtBuildVariant === "debug" + ? @entryPointLibsDebug@ : @entryPointLibsRelease@); + } + libs = libs.concat(staticLibs); + return libs; + } + cpp.dynamicLibraries: dynamicLibs + cpp.linkerFlags: coreLinkerFlags + cpp.frameworkPaths: coreFrameworkPaths.concat(frameworkBuild ? [libPath] : []) + cpp.frameworks: { + var frameworks = coreFrameworks + if (frameworkBuild) + frameworks.push(libNameForLinker); + if (qbs.targetOS.contains('ios') && staticBuild) + frameworks = frameworks.concat(["Foundation", "CoreFoundation"]); + if (frameworks.length === 0) + return undefined; + return frameworks; + } + cpp.rpaths: qbs.targetOS.contains('linux') ? [libPath] : undefined + cpp.runtimeLibrary: qbs.toolchain.contains("msvc") + ? config.contains("static_runtime") ? "static" : "dynamic" + : original + cpp.positionIndependentCode: versionMajor >= 5 ? true : undefined + cpp.cxxFlags: { + var flags = []; + if (qbs.toolchain.contains('msvc')) { + if (versionMajor < 5) + flags.push('/Zc:wchar_t-'); + } + + return flags; + } + cpp.cxxStandardLibrary: { + if (qbs.targetOS.contains('darwin') && qbs.toolchain.contains('clang') + && config.contains('c++11')) + return "libc++"; + return original; + } + cpp.minimumWindowsVersion: @minWinVersion@ + cpp.minimumMacosVersion: @minMacVersion@ + cpp.minimumIosVersion: @minIosVersion@ + cpp.minimumTvosVersion: @minTvosVersion@ + cpp.minimumWatchosVersion: @minWatchosVersion@ + cpp.minimumAndroidVersion: @minAndroidVersion@ + + // Universal Windows Platform support + cpp.windowsApiFamily: mkspecName.startsWith("winrt-") ? "pc" : undefined + cpp.windowsApiAdditionalPartitions: mkspecPath.startsWith("winrt-") ? ["phone"] : undefined + cpp.requireAppContainer: mkspecName.startsWith("winrt-") + + additionalProductTypes: ["qm"] + + validate: { + var validator = new ModUtils.PropertyValidator("Qt.core"); + validator.setRequiredProperty("binPath", binPath); + validator.setRequiredProperty("incPath", incPath); + validator.setRequiredProperty("libPath", libPath); + validator.setRequiredProperty("mkspecPath", mkspecPath); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("config", config); + validator.setRequiredProperty("qtConfig", qtConfig); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + + if (!staticBuild) + validator.setRequiredProperty("pluginPath", pluginPath); + + // Allow custom version suffix since some distributions might want to do this, + // but otherwise the version must start with a valid 3-component string + validator.addVersionValidator("version", version, 3, 3, true); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + + validator.addCustomValidator("availableBuildVariants", availableBuildVariants, function (v) { + return v.length > 0; + }, "the Qt installation supports no build variants"); + + validator.addCustomValidator("qtBuildVariant", qtBuildVariant, function (variant) { + return availableBuildVariants.contains(variant); + }, "'" + qtBuildVariant + "' is not supported by this Qt installation"); + + validator.addCustomValidator("qtBuildVariant", qtBuildVariant, function (variant) { + return variant === qbs.buildVariant || !qbs.toolchain.contains("msvc"); + }, " is '" + qtBuildVariant + "', but qbs.buildVariant is '" + qbs.buildVariant + + "', which is not allowed when using MSVC"); + + validator.addFileNameValidator("resourceFileBaseName", resourceFileBaseName); + + validator.validate(); + } + + FileTagger { + patterns: ["*.qrc"] + fileTags: ["qrc"] + } + + FileTagger { + patterns: ["*.ts"] + fileTags: ["ts"] + } + + FileTagger { + patterns: ["*.qdoc", "*.qdocinc"] + fileTags: ["qdoc"] + } + + FileTagger { + patterns: ["*.qdocconf"] + fileTags: ["qdocconf"] + } + + FileTagger { + patterns: ["*.qhp"] + fileTags: ["qhp"] + } + + property bool combineMocOutput: cpp.combineCxxSources + property bool enableBigResources: false + + Rule { + name: "QtCoreMocRuleCpp" + property string cppInput: cpp.combineCxxSources ? "cpp.combine" : "cpp" + property string objcppInput: cpp.combineObjcxxSources ? "objcpp.combine" : "objcpp" + inputs: [objcppInput, cppInput] + auxiliaryInputs: "qt_plugin_metadata" + excludedInputs: "unmocable" + outputFileTags: ["hpp", "unmocable"] + outputArtifacts: Moc.outputArtifacts.apply(Moc, arguments) + prepare: Moc.commands.apply(Moc, arguments) + } + Rule { + name: "QtCoreMocRuleHpp" + inputs: "hpp" + auxiliaryInputs: ["qt_plugin_metadata", "cpp", "objcpp"]; + excludedInputs: "unmocable" + outputFileTags: ["hpp", "cpp", "moc_cpp", "unmocable"] + outputArtifacts: Moc.outputArtifacts.apply(Moc, arguments) + prepare: Moc.commands.apply(Moc, arguments) + } + + Rule { + multiplex: true + inputs: ["moc_cpp"] + Artifact { + filePath: "amalgamated_moc_" + product.targetName + ".cpp" + fileTags: ["cpp", "unmocable"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["moc_cpp"], output.filePath); + }; + return [cmd]; + } + } + + property path resourceSourceBase + property string resourcePrefix: "/" + property string resourceFileBaseName: product.targetName + Rule { + multiplex: true + inputs: ["qt.core.resource_data"] + Artifact { + filePath: product.Qt.core.resourceFileBaseName + ".qrc" + fileTags: ["qrc"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var doc = new Xml.DomDocument("RCC"); + + var rccNode = doc.createElement("RCC"); + rccNode.setAttribute("version", "1.0"); + doc.appendChild(rccNode); + + var inputsByPrefix = {} + for (var i = 0; i < inputs["qt.core.resource_data"].length; ++i) { + var inp = inputs["qt.core.resource_data"][i]; + var prefix = inp.Qt.core.resourcePrefix; + var inputsList = inputsByPrefix[prefix] || []; + inputsList.push(inp); + inputsByPrefix[prefix] = inputsList; + } + + for (var prefix in inputsByPrefix) { + var qresourceNode = doc.createElement("qresource"); + qresourceNode.setAttribute("prefix", prefix); + rccNode.appendChild(qresourceNode); + + for (var i = 0; i < inputsByPrefix[prefix].length; ++i) { + var inp = inputsByPrefix[prefix][i]; + var fullResPath = inp.filePath; + var baseDir = inp.Qt.core.resourceSourceBase; + var resAlias = baseDir + ? FileInfo.relativePath(baseDir, fullResPath) : inp.fileName; + + var fileNode = doc.createElement("file"); + fileNode.setAttribute("alias", resAlias); + qresourceNode.appendChild(fileNode); + + var fileTextNode = doc.createTextNode(fullResPath); + fileNode.appendChild(fileTextNode); + } + } + + doc.save(output.filePath, 4); + }; + return [cmd]; + } + } + + Rule { + inputs: ["qrc"] + outputFileTags: ["cpp", "cpp_intermediate_object"] + outputArtifacts: { + var artifact = { + filePath: "qrc_" + input.completeBaseName + ".cpp", + fileTags: ["cpp"] + }; + if (input.Qt.core.enableBigResources) + artifact.fileTags.push("cpp_intermediate_object"); + return [artifact]; + } + prepare: { + var args = [input.filePath, + "-name", FileInfo.completeBaseName(input.filePath), + "-o", output.filePath]; + if (input.Qt.core.enableBigResources) + args.push("-pass", "1"); + var cmd = new Command(product.Qt.core.binPath + '/rcc', args); + cmd.description = "rcc " + + (input.Qt.core.enableBigResources ? "(pass 1) " : "") + + input.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + Rule { + inputs: ["intermediate_obj"] + Artifact { + filePath: input.completeBaseName + ".2.o" + fileTags: ["obj"] + } + prepare: { + function findChild(artifact, predicate) { + var children = artifact.children; + var len = children.length; + for (var i = 0; i < len; ++i) { + var child = children[i]; + if (predicate(child)) + return child; + child = findChild(child, predicate); + if (child) + return child; + } + return undefined; + } + var qrcArtifact = findChild(input, function(c) { return c.fileTags.contains("qrc"); }); + var cppArtifact = findChild(input, function(c) { return c.fileTags.contains("cpp"); }); + var cmd = new Command(product.Qt.core.binPath + '/rcc', + [qrcArtifact.filePath, + "-temp", input.filePath, + "-name", FileInfo.completeBaseName(input.filePath), + "-o", output.filePath, + "-pass", "2"]); + cmd.description = "rcc (pass 2) " + qrcArtifact.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + Rule { + inputs: ["ts"] + multiplex: lreleaseMultiplexMode + + Artifact { + filePath: FileInfo.joinPaths(product.Qt.core.qmDir, + (product.Qt.core.lreleaseMultiplexMode + ? product.Qt.core.qmBaseName + : input.baseName) + ".qm") + fileTags: ["qm"] + } + + prepare: { + var inputFilePaths; + if (product.Qt.core.lreleaseMultiplexMode) + inputFilePaths = inputs["ts"].map(function(artifact) { return artifact.filePath; }); + else + inputFilePaths = [input.filePath]; + var args = ['-silent', '-qm', output.filePath].concat(inputFilePaths); + var cmd = new Command(product.Qt.core.binPath + '/' + + product.Qt.core.lreleaseName, args); + cmd.description = 'Creating ' + output.fileName; + cmd.highlight = 'filegen'; + return cmd; + } + } + + Rule { + inputs: "qdocconf-main" + explicitlyDependsOn: ["qdoc", "qdocconf"] + + outputFileTags: ModUtils.allFileTags(Qdoc.qdocFileTaggers()) + outputArtifacts: Qdoc.outputArtifacts(product, input) + + prepare: { + var outputDir = product.Qt.core.qdocOutputDir; + var args = Qdoc.qdocArgs(product, input, outputDir); + var cmd = new Command(product.Qt.core.binPath + '/' + product.Qt.core.qdocName, args); + cmd.description = 'qdoc ' + input.fileName; + cmd.highlight = 'filegen'; + cmd.environment = product.Qt.core.qdocEnvironment; + cmd.environment.push("OUTDIR=" + outputDir); // Qt 4 replacement for -outputdir + return cmd; + } + } + + Rule { + inputs: "qhp" + auxiliaryInputs: ModUtils.allFileTags(Qdoc.qdocFileTaggers()) + .filter(function(tag) { return tag !== "qhp"; }) + + Artifact { + filePath: input.completeBaseName + ".qch" + fileTags: ["qch"] + } + + prepare: { + var args = [input.filePath]; + args = args.concat(product.Qt.core.helpGeneratorArgs); + args.push("-o"); + args.push(output.filePath); + var cmd = new Command(product.Qt.core.binPath + "/qhelpgenerator", args); + cmd.description = 'qhelpgenerator ' + input.fileName; + cmd.highlight = 'filegen'; + cmd.stdoutFilterFunction = function(output) { + return ""; + }; + return cmd; + } + } + + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/dbus.js b/share/qbs/module-providers/Qt/templates/dbus.js new file mode 100644 index 000000000..0674bf684 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/dbus.js @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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"); + +function outputFileName(input, suffix) +{ + var parts = input.fileName.split('.').filter(function(s) { return s.length > 0; }); + if (parts.length === 0) + throw "Cannot run qdbusxml2cpp on '" + input.filePath + "': Unsuitable file name."; + var outputBaseName = parts.length === 1 ? parts[0] : parts[parts.length - 2]; + return outputBaseName.toLowerCase() + suffix; +} + +function createCommands(product, input, outputs, option) +{ + var exe = ModUtils.moduleProperty(product, "binPath") + '/' + + ModUtils.moduleProperty(product, "xml2cppName"); + var hppOutput = outputs["hpp"][0]; + var hppArgs = ModUtils.moduleProperty(product, "xml2CppHeaderFlags"); + hppArgs.push(option, hppOutput.fileName + ':', input.filePath); // Can't use filePath on Windows + var hppCmd = new Command(exe, hppArgs) + hppCmd.description = "qdbusxml2cpp " + input.fileName + " -> " + hppOutput.fileName; + hppCmd.highlight = "codegen"; + hppCmd.workingDirectory = FileInfo.path(hppOutput.filePath); + var cppOutput = outputs["cpp"][0]; + var cppArgs = ModUtils.moduleProperty(product, "xml2CppSourceFlags"); + cppArgs.push("-i", hppOutput.filePath, option, ':' + cppOutput.fileName, input.filePath); + var cppCmd = new Command(exe, cppArgs) + cppCmd.description = "qdbusxml2cpp " + input.fileName + " -> " + cppOutput.fileName; + cppCmd.highlight = "codegen"; + cppCmd.workingDirectory = FileInfo.path(cppOutput.filePath); + return [hppCmd, cppCmd]; +} diff --git a/share/qbs/module-providers/Qt/templates/dbus.qbs b/share/qbs/module-providers/Qt/templates/dbus.qbs new file mode 100644 index 000000000..6556e2c9b --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/dbus.qbs @@ -0,0 +1,74 @@ +import qbs.FileInfo +import qbs.ModUtils +import "../QtModule.qbs" as QtModule +import "dbus.js" as DBus + +QtModule { + qtModuleName: "DBus" + + property string xml2cppName: "qdbusxml2cpp" + property stringList xml2CppHeaderFlags: [] + property stringList xml2CppSourceFlags: [] + + Rule { + inputs: ["qt.dbus.adaptor"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + DBus.outputFileName(input, "_adaptor.h")) + fileTags: ["hpp"] + } + Artifact { + filePath: DBus.outputFileName(input, "_adaptor.cpp") + fileTags: ["cpp"] + } + + prepare: { + return DBus.createCommands(product, input, outputs, "-a"); + } + } + + Rule { + inputs: ["qt.dbus.interface"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + DBus.outputFileName(input, "_interface.h")) + fileTags: ["hpp"] + } + Artifact { + filePath: DBus.outputFileName(input, "_interface.cpp") + fileTags: ["cpp"] + } + + prepare: { + return DBus.createCommands(product, input, outputs, "-p"); + } + } + + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + @additionalContent@ +} + diff --git a/share/qbs/module-providers/Qt/templates/gui.qbs b/share/qbs/module-providers/Qt/templates/gui.qbs new file mode 100644 index 000000000..eb69e0cad --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/gui.qbs @@ -0,0 +1,65 @@ +import qbs.FileInfo +import qbs.ModUtils +import '../QtModule.qbs' as QtModule + +QtModule { + qtModuleName: "Gui" + + property string uicName: "uic" + + FileTagger { + patterns: ["*.ui"] + fileTags: ["ui"] + } + + Rule { + inputs: ["ui"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + 'ui_' + input.completeBaseName + '.h') + fileTags: ["hpp"] + } + + prepare: { + var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + '/' + + ModUtils.moduleProperty(product, "uicName"), + [input.filePath, '-o', output.filePath]) + cmd.description = 'uic ' + input.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + property string defaultQpaPlugin: @defaultQpaPlugin@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + Properties { + condition: Qt.core.staticBuild && qbs.targetOS.contains("ios") + cpp.frameworks: base.concat(["UIKit", "QuartzCore", "CoreText", "CoreGraphics", + "Foundation", "CoreFoundation", "AudioToolbox"]) + } + cpp.frameworks: base + @additionalContent@ +} + diff --git a/share/qbs/module-providers/Qt/templates/moc.js b/share/qbs/module-providers/Qt/templates/moc.js new file mode 100644 index 000000000..aa67766b9 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/moc.js @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +var ModUtils = require("qbs.ModUtils"); + +function args(product, input, outputFileName) +{ + var defines = product.cpp.compilerDefinesByLanguage; + if (input.fileTags.contains("objcpp")) + defines = ModUtils.flattenDictionary(defines["objcpp"]) || []; + else if (input.fileTags.contains("cpp")) + defines = ModUtils.flattenDictionary(defines["cpp"]) || []; + else + defines = []; + defines = defines.uniqueConcat(product.cpp.platformDefines); + defines = defines.uniqueConcat(input.cpp.defines); + var includePaths = input.cpp.includePaths; + includePaths = includePaths.uniqueConcat(input.cpp.systemIncludePaths); + var useCompilerPaths = product.Qt.core.versionMajor >= 5; + if (useCompilerPaths) { + includePaths = includePaths.uniqueConcat(input.cpp.compilerIncludePaths); + } + var frameworkPaths = product.cpp.frameworkPaths; + frameworkPaths = frameworkPaths.uniqueConcat( + product.cpp.systemFrameworkPaths); + if (useCompilerPaths) { + frameworkPaths = frameworkPaths.uniqueConcat( + product.cpp.compilerFrameworkPaths); + } + var pluginMetaData = product.Qt.core.pluginMetaData; + var args = []; + args = args.concat( + defines.map(function(item) { return '-D' + item; }), + includePaths.map(function(item) { return '-I' + item; }), + frameworkPaths.map(function(item) { return '-F' + item; }), + pluginMetaData.map(function(item) { return '-M' + item; }), + product.Qt.core.mocFlags, + '-o', outputFileName, + input.filePath); + return args; +} + +function fullPath(product) +{ + return product.Qt.core.binPath + '/' + product.Qt.core.mocName; +} + +function outputArtifacts(project, product, inputs, input) +{ + var mocInfo = QtMocScanner.apply(input); + if (!mocInfo.hasQObjectMacro) + return []; + var artifact = { fileTags: ["unmocable"] }; + if (mocInfo.hasPluginMetaDataMacro) + artifact.explicitlyDependsOn = ["qt_plugin_metadata"]; + if (input.fileTags.contains("hpp")) { + artifact.filePath = input.Qt.core.generatedHeadersDir + + "/moc_" + input.completeBaseName + ".cpp"; + var amalgamate = input.Qt.core.combineMocOutput; + artifact.fileTags.push(mocInfo.mustCompile ? (amalgamate ? "moc_cpp" : "cpp") : "hpp"); + } else { + artifact.filePath = input.Qt.core.generatedHeadersDir + '/' + + input.completeBaseName + ".moc"; + artifact.fileTags.push("hpp"); + } + return [artifact]; +} + +function commands(project, product, inputs, outputs, input, output) +{ + var cmd = new Command(fullPath(product), args(product, input, output.filePath)); + cmd.description = 'moc ' + input.fileName; + cmd.highlight = 'codegen'; + return cmd; +} diff --git a/share/qbs/module-providers/Qt/templates/module.qbs b/share/qbs/module-providers/Qt/templates/module.qbs new file mode 100644 index 000000000..b09f79a87 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/module.qbs @@ -0,0 +1,30 @@ +import '../QtModule.qbs' as QtModule + +QtModule { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@} + + architectures: @archs@ + targetPlatform: @targetPlatform@ + hasLibrary: @has_library@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/plugin.qbs b/share/qbs/module-providers/Qt/templates/plugin.qbs new file mode 100644 index 000000000..e73e2a4d9 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/plugin.qbs @@ -0,0 +1,27 @@ +import '../QtPlugin.qbs' as QtPlugin + +QtPlugin { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@} + + className: @className@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + cpp.libraryPaths: @libraryPaths@ + extendsModules: @extends@ + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/plugin_support.qbs b/share/qbs/module-providers/Qt/templates/plugin_support.qbs new file mode 100644 index 000000000..13d95c383 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/plugin_support.qbs @@ -0,0 +1,75 @@ +Module { + // Set by user. + property varList pluginsByType + + // Set by Qt modules. + property stringList pluginTypes + + // Set by setup-qt. + readonly property var allPluginsByType: @allPluginsByType@ + readonly property stringList nonEssentialPlugins: @nonEssentialPlugins@ + + // Derived. + readonly property var defaultPluginsByType: { + var map = {}; + for (var i = 0; i < (pluginTypes || []).length; ++i) { + var pType = pluginTypes[i]; + map[pType] = (allPluginsByType[pType] || []).filter(function(p) { + return !nonEssentialPlugins.contains(p); }); + } + return map; + } + readonly property var effectivePluginsByType: { + var ppt = pluginsByType || []; + var eppt = {}; + for (var i = 0; i < ppt.length; ++i) { + var listEntry = ppt[i]; + for (var pluginType in listEntry) { + var newValue = listEntry[pluginType]; + if (!newValue) + newValue = []; + else if (typeof newValue == "string") + newValue = [newValue]; + if (!Array.isArray(newValue)) + throw "Invalid value '" + newValue + "' in Qt.plugin_support.pluginsByType"; + eppt[pluginType] = (eppt[pluginType] || []).uniqueConcat(newValue); + } + } + var dppt = defaultPluginsByType; + for (var pluginType in dppt) { + if (!eppt[pluginType]) + eppt[pluginType] = dppt[pluginType]; + } + return eppt; + } + readonly property stringList enabledPlugins: { + var list = []; + var eppt = effectivePluginsByType; + for (var t in eppt) + Array.prototype.push.apply(list, eppt[t]); + return list; + } + + validate: { + var ppt = pluginsByType; + if (!ppt) + return; + var appt = allPluginsByType; + for (var i = 0; i < ppt.length; ++i) { + for (var pluginType in ppt[i]) { + var requestedPlugins = ppt[i][pluginType]; + if (!requestedPlugins) + continue; + var availablePlugins = appt[pluginType] || []; + if (typeof requestedPlugins === "string") + requestedPlugins = [requestedPlugins]; + for (var j = 0; j < requestedPlugins.length; ++j) { + if (!availablePlugins.contains(requestedPlugins[j])) { + throw "Plugin '" + requestedPlugins[j] + "' of type '" + pluginType + + "' was requested, but is not available."; + } + } + } + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/qdoc.js b/share/qbs/module-providers/Qt/templates/qdoc.js new file mode 100644 index 000000000..72c161c56 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qdoc.js @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 qdocArgs(product, input, outputDir) { + var args = [input.filePath]; + var qtVersion = ModUtils.moduleProperty(product, "versionMajor"); + if (qtVersion >= 5) { + args.push("-outputdir"); + args.push(outputDir); + } + + return args; +} + +var _qdocDefaultFileTag = "qdoc-output"; +function qdocFileTaggers() { + var t = _qdocDefaultFileTag; + return { + ".qhp": [t, "qhp"], + ".qhp.sha1": [t, "qhp-sha1"], + ".css": [t, "qdoc-css"], + ".html": [t, "qdoc-html"], + ".index": [t, "qdoc-index"], + ".png": [t, "qdoc-png"] + }; +} + +function outputArtifacts(product, input) { + var tracker = new ModUtils.BlackboxOutputArtifactTracker(); + tracker.hostOS = product.moduleProperty("qbs", "hostOS"); + tracker.shellPath = product.moduleProperty("qbs", "shellPath"); + tracker.defaultFileTags = [_qdocDefaultFileTag]; + tracker.fileTaggers = qdocFileTaggers(); + tracker.command = FileInfo.joinPaths(ModUtils.moduleProperty(product, "binPath"), + ModUtils.moduleProperty(product, "qdocName")); + tracker.commandArgsFunction = function (outputDirectory) { + return qdocArgs(product, input, outputDirectory); + }; + tracker.commandEnvironmentFunction = function (outputDirectory) { + var env = {}; + var qdocEnv = ModUtils.moduleProperty(product, "qdocEnvironment"); + for (var j = 0; j < qdocEnv.length; ++j) { + var e = qdocEnv[j]; + var idx = e.indexOf("="); + var name = e.slice(0, idx); + var value = e.slice(idx + 1, e.length); + env[name] = value; + } + env["OUTDIR"] = outputDirectory; // Qt 4 replacement for -outputdir + return env; + }; + return tracker.artifacts(ModUtils.moduleProperty(product, "qdocOutputDir")); +} diff --git a/share/qbs/module-providers/Qt/templates/qml.js b/share/qbs/module-providers/Qt/templates/qml.js new file mode 100644 index 000000000..c7829d81b --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qml.js @@ -0,0 +1,66 @@ +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); + +function scannerData(scannerFilePath, qmlFiles, qmlPath) +{ + var p; + try { + p = new Process(); + p.exec(scannerFilePath, ["-qmlFiles"].concat(qmlFiles).concat(["-importPath", qmlPath]), + true); + return JSON.parse(p.readStdOut()); + } finally { + if (p) + p.close(); + } +} + +function getPrlRhs(line) +{ + return line.split('=')[1].trim(); +} + +function getLibsForPlugin(pluginData, buildVariant, targetOS, toolchain, qtLibDir) +{ + if (!pluginData.path) + return ""; + var prlFileName = ""; + if (!targetOS.contains("windows")) + prlFileName += "lib"; + prlFileName += pluginData.plugin; + if (buildVariant === "debug" && targetOS.contains("windows")) + prlFileName += "d"; + prlFileName += ".prl"; + var prlFilePath = FileInfo.joinPaths(pluginData.path, prlFileName); + if (!File.exists(prlFilePath)) { + console.warn("prl file for QML plugin '" + pluginData.plugin + "' not present at '" + + prlFilePath + "'. Linking may fail."); + return ""; + } + var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly); + try { + var pluginLib; + var otherLibs = ""; + var line; + while (line = prlFile.readLine()) { + if (line.startsWith("QMAKE_PRL_TARGET")) + pluginLib = FileInfo.joinPaths(pluginData.path, getPrlRhs(line)); + if (line.startsWith("QMAKE_PRL_LIBS")) { + var otherLibsLine = ' ' + getPrlRhs(line); + if (toolchain.contains("msvc")) { + otherLibsLine = otherLibsLine.replace(/ -L/g, " /LIBPATH:"); + otherLibsLine = otherLibsLine.replace(/-l([^ ]+)/g, "$1" + ".lib"); + } + otherLibsLine = otherLibsLine.replace(/\$\$\[QT_INSTALL_LIBS\]/g, qtLibDir); + otherLibs += otherLibsLine; + } + } + if (!pluginLib) + throw "Malformed prl file '" + prlFilePath + "'."; + return pluginLib + ' ' + otherLibs; + } finally { + prlFile.close(); + } +} diff --git a/share/qbs/module-providers/Qt/templates/qml.qbs b/share/qbs/module-providers/Qt/templates/qml.qbs new file mode 100644 index 000000000..2b11abbd5 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qml.qbs @@ -0,0 +1,132 @@ +import qbs.TextFile +import '../QtModule.qbs' as QtModule +import "qml.js" as Qml + +QtModule { + qtModuleName: "Qml" + Depends { name: "Qt"; submodules: @dependencies@} + + property string qmlImportScannerName: "qmlimportscanner" + property string qmlImportScannerFilePath: Qt.core.binPath + '/' + qmlImportScannerName + property string qmlPath: @qmlPath@ + + property bool generateCacheFiles: false + Depends { name: "Qt.qmlcache"; condition: generateCacheFiles; required: false } + readonly property bool cachingEnabled: generateCacheFiles && Qt.qmlcache.present + property string qmlCacheGenPath + Properties { + condition: cachingEnabled + Qt.qmlcache.qmlCacheGenPath: qmlCacheGenPath || original + Qt.qmlcache.installDir: cacheFilesInstallDir || original + } + + property string cacheFilesInstallDir + + readonly property string pluginListFilePathDebug: product.buildDirectory + "/plugins.list.d" + readonly property string pluginListFilePathRelease: product.buildDirectory + "/plugins.list" + + hasLibrary: @has_library@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: (isStaticLibrary ? ['@' + pluginListFilePathDebug] : []).concat(@staticLibsDebug@) + staticLibsRelease: (isStaticLibrary ? ['@' + pluginListFilePathRelease] : []).concat(@staticLibsRelease@) + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ + + FileTagger { + patterns: ["*.qml"] + fileTags: ["qt.qml.qml"] + } + + FileTagger { + patterns: ["*.js"] + fileTags: ["qt.qml.js"] + } + + Rule { + condition: isStaticLibrary + multiplex: true + requiresInputs: false + inputs: ["qt.qml.qml"] + outputFileTags: ["cpp", "qt.qml.pluginlist"] + outputArtifacts: { + var list = []; + if (inputs["qt.qml.qml"]) + list.push({ filePath: "qml_plugin_import.cpp", fileTags: ["cpp"] }); + list.push({ + filePath: product.Qt.core.qtBuildVariant === "debug" + ? product.Qt.qml.pluginListFilePathDebug + : product.Qt.qml.pluginListFilePathRelease, + fileTags: ["qt.qml.pluginlist"] + }); + return list; + } + prepare: { + var cmd = new JavaScriptCommand(); + if (inputs["qt.qml.qml"]) + cmd.description = "Creating " + outputs["cpp"][0].fileName; + else + cmd.silent = true; + cmd.sourceCode = function() { + var qmlInputs = inputs["qt.qml.qml"]; + if (!qmlInputs) + qmlInputs = []; + var scannerData = Qml.scannerData(product.Qt.qml.qmlImportScannerFilePath, + qmlInputs.map(function(inp) { return inp.filePath; }), + product.Qt.qml.qmlPath); + var cppFile; + var listFile; + try { + if (qmlInputs.length > 0) + cppFile = new TextFile(outputs["cpp"][0].filePath, TextFile.WriteOnly); + listFile = new TextFile(outputs["qt.qml.pluginlist"][0].filePath, + TextFile.WriteOnly); + if (cppFile) + cppFile.writeLine("#include <QtPlugin>"); + var plugins = { }; + for (var p in scannerData) { + var plugin = scannerData[p].plugin; + if (!plugin || plugins[plugin]) + continue; + plugins[plugin] = true; + var className = scannerData[p].classname; + if (!className) { + throw "QML plugin '" + plugin + "' is missing a classname entry. " + + "Please add one to the qmldir file."; + } + if (cppFile) + cppFile.writeLine("Q_IMPORT_PLUGIN(" + className + ")"); + var libs = Qml.getLibsForPlugin(scannerData[p], + product.Qt.core.qtBuildVariant, + product.qbs.targetOS, + product.qbs.toolchain, + product.Qt.core.libPath); + listFile.write(libs + ' '); + } + } finally { + if (cppFile) + cppFile.close(); + if (listFile) + listFile.close(); + }; + }; + return [cmd]; + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/qmlcache.qbs b/share/qbs/module-providers/Qt/templates/qmlcache.qbs new file mode 100644 index 000000000..9111eb500 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qmlcache.qbs @@ -0,0 +1,85 @@ +import qbs.File +import qbs.FileInfo +import qbs.Process +import qbs.Utilities + +Module { + additionalProductTypes: ["qt.qml.qmlc", "qt.qml.jsc"] + validate: { + if (!qmlcachegenProbe.found) + throw "qmlcachegen unsupported for this target"; + } + property string qmlCacheGenPath: FileInfo.joinPaths(Qt.core.binPath, "qmlcachegen") + + (qbs.hostOS.contains("windows") ? ".exe" : "") + property bool supportsAllArchitectures: Utilities.versionCompare(Qt.core.version, "5.11") >= 0 + property string installDir + + readonly property stringList _targetArgs: { + if (supportsAllArchitectures) + return []; + function translateArch(arch) { + if (arch === "x86") + return "i386"; + if (arch.startsWith("armv")) + return "arm"; + return arch; + } + + var args = ["--target-architecture", translateArch(qbs.architecture)]; + return args; + } + + Depends { name: "Qt.core" } + Probe { + id: qmlcachegenProbe + + property string arch: qbs.architecture + property string _qmlCacheGenPath: qmlCacheGenPath + property stringList targetArgs: _targetArgs + property bool _supportsAllArchitectures: supportsAllArchitectures + + configure: { + if (_supportsAllArchitectures) { + found = File.exists(_qmlCacheGenPath); + return; + } + var process = new Process(); + found = false; + try { + found = process.exec(_qmlCacheGenPath, + targetArgs.concat("--check-if-supported")) == 0; + if (!found) { + var msg = "QML cache generation was requested but is unsupported on " + + "architecture '" + arch + "'."; + console.warn(msg); + } + } finally { + process.close(); + } + } + } + Rule { + condition: qmlcachegenProbe.found + inputs: ["qt.qml.qml", "qt.qml.js"] + outputArtifacts: [{ + filePath: input.fileName + 'c', + fileTags: input.fileTags.filter(function(tag) { + return tag === "qt.qml.qml" || tag === "qt.qml.js"; + })[0] + 'c' + }] + outputFileTags: ["qt.qml.qmlc", "qt.qml.jsc"] + prepare: { + var args = input.Qt.qmlcache._targetArgs.concat(input.filePath, "-o", output.filePath); + var cmd = new Command(input.Qt.qmlcache.qmlCacheGenPath, args); + cmd.description = "precompiling " + input.fileName; + cmd.highlight = "compiler"; + return cmd; + } + } + Group { + condition: product.Qt.qmlcache.installDir !== undefined + fileTagsFilter: product.Qt.qmlcache.additionalProductTypes + qbs.install: true + qbs.installDir: product.Qt.qmlcache.installDir + } +} diff --git a/share/qbs/module-providers/Qt/templates/quick.js b/share/qbs/module-providers/Qt/templates/quick.js new file mode 100644 index 000000000..4f3da2fb0 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/quick.js @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 Process = require("qbs.Process"); + +function scanQrc(qrcFilePath) { + var absInputDir = FileInfo.path(qrcFilePath); + var result = []; + var process = new Process(); + try { + var rcc = FileInfo.joinPaths(product.Qt.core.binPath, 'rcc' + product.cpp.executableSuffix); + var exitCode = process.exec(rcc, ["--list", qrcFilePath], true); + for (;;) { + var line = process.readLine(); + if (!line) + break; + line = line.trim(); + line = FileInfo.relativePath(absInputDir, line); + result.push(line); + } + } finally { + process.close(); + } + return result; +} + +function qtQuickCompilerOutputName(filePath) { + var str = filePath.replace(/\//g, '_'); + var i = str.lastIndexOf('.'); + if (i != -1) + str = str.substr(0, i) + '_' + str.substr(i + 1); + str += ".cpp"; + return str; +} + +function qtQuickResourceFileOutputName(fileName) { + return fileName.replace(/\.qrc$/, "_qtquickcompiler.qrc"); +} + +function contentFromQrc(qrcFilePath) { + var filesInQrc = scanQrc(qrcFilePath); + var qmlJsFiles = filesInQrc.filter(function (filePath) { + return (/\.(js|qml)$/).test(filePath); + } ); + var content = {}; + if (filesInQrc.length - qmlJsFiles.length > 0) { + content.newQrcFileName = qtQuickResourceFileOutputName(input.fileName); + } + content.qmlJsFiles = qmlJsFiles.map(function (filePath) { + return { + input: filePath, + output: qtQuickCompilerOutputName(filePath) + }; + }); + return content; +} diff --git a/share/qbs/module-providers/Qt/templates/quick.qbs b/share/qbs/module-providers/Qt/templates/quick.qbs new file mode 100644 index 000000000..5968949c8 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/quick.qbs @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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.File +import qbs.FileInfo +import qbs.Process +import qbs.TextFile +import qbs.Utilities +import '../QtModule.qbs' as QtModule +import 'quick.js' as QC + +QtModule { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@.concat("qml-private") } + + hasLibrary: @has_library@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ + + readonly property bool _compilerIsQmlCacheGen: Utilities.versionCompare(Qt.core.version, + "5.11") >= 0 + readonly property string _generatedLoaderFileName: _compilerIsQmlCacheGen + ? "qmlcache_loader.cpp" + : "qtquickcompiler_loader.cpp" + property string compilerBaseName: (_compilerIsQmlCacheGen ? "qmlcachegen" : "qtquickcompiler") + property string compilerFilePath: FileInfo.joinPaths(Qt.core.binPath, + compilerBaseName + product.cpp.executableSuffix) + property bool compilerAvailable: File.exists(compilerFilePath); + property bool useCompiler: compilerAvailable && !_compilerIsQmlCacheGen + + Scanner { + condition: useCompiler + inputs: 'qt.quick.qrc' + searchPaths: [FileInfo.path(input.filePath)] + scan: QC.scanQrc(input.filePath) + } + + FileTagger { + condition: useCompiler + patterns: "*.qrc" + fileTags: ["qt.quick.qrc"] + priority: 100 + } + + Rule { + condition: useCompiler + inputs: ["qt.quick.qrc"] + Artifact { + filePath: input.fileName + ".json" + fileTags: ["qt.quick.qrcinfo"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var content = QC.contentFromQrc(input.filePath); + content.qrcFilePath = input.filePath; + + var tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.write(JSON.stringify(content)); + tf.close(); + } + return cmd; + } + } + + Rule { + condition: useCompiler + inputs: ["qt.quick.qrcinfo"] + outputFileTags: ["cpp", "qrc"] + multiplex: true + outputArtifacts: { + var infos = []; + inputs["qt.quick.qrcinfo"].forEach(function (input) { + var tf = new TextFile(input.filePath, TextFile.ReadOnly); + infos.push(JSON.parse(tf.readAll())); + tf.close(); + }); + + var result = []; + infos.forEach(function (info) { + if (info.newQrcFileName) { + result.push({ + filePath: info.newQrcFileName, + fileTags: ["qrc"] + }); + } + info.qmlJsFiles.forEach(function (qmlJsFile) { + result.push({ + filePath: qmlJsFile.output, + fileTags: ["cpp"] + }); + }); + }); + result.push({ + filePath: product.Qt.quick._generatedLoaderFileName, + fileTags: ["cpp"] + }); + return result; + } + prepare: { + var infos = []; + inputs["qt.quick.qrcinfo"].forEach(function (input) { + var tf = new TextFile(input.filePath, TextFile.ReadOnly); + infos.push(JSON.parse(tf.readAll())); + tf.close(); + }); + + var cmds = []; + var qmlCompiler = product.Qt.quick.compilerFilePath; + var useCacheGen = product.Qt.quick._compilerIsQmlCacheGen; + var cmd; + var loaderFlags = []; + + function findOutput(fileName) { + for (var k in outputs) { + for (var i in outputs[k]) { + if (outputs[k][i].fileName === fileName) + return outputs[k][i]; + } + } + throw new Error("Qt Quick compiler rule: Cannot find output artifact " + + fileName + "."); + } + + infos.forEach(function (info) { + if (info.newQrcFileName) { + loaderFlags.push("--resource-file-mapping=" + + FileInfo.fileName(info.qrcFilePath) + + ":" + info.newQrcFileName); + var args = ["--filter-resource-file", + info.qrcFilePath]; + if (useCacheGen) + args.push("-o"); + args.push(findOutput(info.newQrcFileName).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating " + info.newQrcFileName; + cmds.push(cmd); + } else { + loaderFlags.push("--resource-file-mapping=" + info.qrcFilePath); + } + info.qmlJsFiles.forEach(function (qmlJsFile) { + var args = ["--resource=" + info.qrcFilePath, qmlJsFile.input]; + if (useCacheGen) + args.push("-o"); + args.push(findOutput(qmlJsFile.output).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating " + qmlJsFile.output; + cmd.workingDirectory = FileInfo.path(info.qrcFilePath); + cmds.push(cmd); + }); + }); + + var args = loaderFlags.concat(infos.map(function (info) { return info.qrcFilePath; })); + if (useCacheGen) + args.push("-o"); + args.push(findOutput(product.Qt.quick._generatedLoaderFileName).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating loader source"; + cmds.push(cmd); + return cmds; + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/scxml.qbs b/share/qbs/module-providers/Qt/templates/scxml.qbs new file mode 100644 index 000000000..7125ec53c --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/scxml.qbs @@ -0,0 +1,80 @@ +import qbs.FileInfo +import qbs.Utilities +import "../QtModule.qbs" as QtModule + +QtModule { + qtModuleName: "Scxml" + + property string qscxmlcName: "qscxmlc" + property string className + property string namespace + property bool generateStateMethods + property stringList additionalCompilerFlags + + Rule { + inputs: ["qt.scxml.compilable"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + input.baseName + ".h") + fileTags: ["hpp", "unmocable"] + } + Artifact { + filePath: input.baseName + ".cpp" + fileTags: ["cpp", "unmocable"] + } + + prepare: { + var compilerName = product.moduleProperty("Qt.scxml", "qscxmlcName"); + var compilerPath = FileInfo.joinPaths(input.moduleProperty("Qt.core", "binPath"), + compilerName); + var args = ["--header", outputs["hpp"][0].filePath, + "--impl", outputs["cpp"][0].filePath]; + var cxx98 = input.moduleProperty("cpp", "cxxLanguageVersion") === "c++98"; + if (cxx98) + args.push("-no-c++11"); + var className = input.moduleProperty("Qt.scxml", "className"); + if (className) + args.push("--classname", className); + var namespace = input.moduleProperty("Qt.scxml", "namespace"); + if (namespace) + args.push("--namespace", namespace); + if (input.Qt.scxml.generateStateMethods + && Utilities.versionCompare(product.Qt.scxml.version, "5.9") >= 0) { + args.push("--statemethods"); + } + if (input.Qt.scxml.additionalCompilerFlags) + args = args.concat(input.Qt.scxml.additionalCompilerFlags); + args.push(input.filePath); + var cmd = new Command(compilerPath, args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "codegen"; + return [cmd]; + } + } + + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: @staticLibsDebug@ + staticLibsRelease: @staticLibsRelease@ + dynamicLibsDebug: @dynamicLibsDebug@ + dynamicLibsRelease: @dynamicLibsRelease@ + linkerFlagsDebug: @linkerFlagsDebug@ + linkerFlagsRelease: @linkerFlagsRelease@ + frameworksDebug: @frameworksDebug@ + frameworksRelease: @frameworksRelease@ + frameworkPathsDebug: @frameworkPathsDebug@ + frameworkPathsRelease: @frameworkPathsRelease@ + libNameForLinkerDebug: @libNameForLinkerDebug@ + libNameForLinkerRelease: @libNameForLinkerRelease@ + libFilePathDebug: @libFilePathDebug@ + libFilePathRelease: @libFilePathRelease@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + @additionalContent@ +} |