diff options
87 files changed, 2148 insertions, 1029 deletions
diff --git a/doc/reference/items/depends.qdoc b/doc/reference/items/depends.qdoc index ce633e5fa..17816a455 100644 --- a/doc/reference/items/depends.qdoc +++ b/doc/reference/items/depends.qdoc @@ -35,7 +35,7 @@ \ingroup list-of-items \title Depends Item - \brief Represents dependencies between between products and modules. + \brief Represents dependencies between products and modules. A \c Depends item can appear inside a \l{Product Item} or \l{Module Item}. diff --git a/doc/reference/items/group.qdoc b/doc/reference/items/group.qdoc index 5c03a4c97..89acd08f7 100644 --- a/doc/reference/items/group.qdoc +++ b/doc/reference/items/group.qdoc @@ -60,7 +60,7 @@ name: "Files to install" qbs.install: true qbs.installDir: "share" - files "runtime_resource.txt" + files: "runtime_resource.txt" } } \endcode @@ -129,10 +129,11 @@ \li overrideTags \li bool \li true - \li If the same file appears in the files list of both the top level of a product and - a group and this property is true, then the file tags set via the group - replace the ones set via the product. If it is false, the "group tags" are added to - the "product tags". + \li Determines how tags on files that are listed both at the top level of + a product and a group are handled. + If this property is true, then the file tags set via the group + replace the ones set via the product. + If it is false, the "group tags" are added to the "product tags". \row \li excludeFiles \li list diff --git a/doc/reference/items/product.qdoc b/doc/reference/items/product.qdoc index 295b3df67..28737c9b9 100644 --- a/doc/reference/items/product.qdoc +++ b/doc/reference/items/product.qdoc @@ -97,8 +97,9 @@ \row \li destinationDirectory \li string - \li "." - \li The directory where the target artifacts will be located. Relative to the build directory. + \li product.buildDirectory + \li The directory where the target artifacts will be located. If a relative path is + given, the base directory will be \c project.buildDirectory. \row \li files \li stringList @@ -122,7 +123,7 @@ \li stringList \li project.qbsSearchPaths \li See the documentation of the \l {Project Item} property of the same name. - Setting this property here will overwrite the default value inherited from + Setting this property here will override the default value inherited from the project, so use the \c concat() function if you want to add something. \row \li version @@ -132,7 +133,7 @@ Info.plist files in OS X and iOS application and framework bundles, for example. \endtable - The following properties are automatically set by \QBS and usually are not changed by the user: + The following properties are automatically set by \QBS and cannot be changed by the user: \table \header @@ -141,12 +142,12 @@ \li Description \row \li buildDirectory - \li string + \li path \li The build directory for this product. This is the directory where generated files are placed. \row \li sourceDirectory - \li string + \li path \li The source directory for this product. This is the directory of the file where this product is defined. \endtable diff --git a/doc/reference/items/project.qdoc b/doc/reference/items/project.qdoc index f2a4710d1..c1a62bc40 100644 --- a/doc/reference/items/project.qdoc +++ b/doc/reference/items/project.qdoc @@ -62,6 +62,11 @@ \li Default \li Description \row + \li buildDirectory + \li path + \li n/a + \li The build directory of the top-level project. This property is read-only. + \row \li name \li string \li basename of the file the project is defined in @@ -85,5 +90,11 @@ \li empty \li A list of files from which to import products. This is equivalent to defining the respective \c Product items directly under this \c Project item. + \row + \li sourceDirectory + \li path + \li n/a + \li The directory where the file containing the top-level \c Project item is located. + This property is read-only. \endtable */ diff --git a/doc/reference/items/rule.qdoc b/doc/reference/items/rule.qdoc index 91dcc6137..9a174e49c 100644 --- a/doc/reference/items/rule.qdoc +++ b/doc/reference/items/rule.qdoc @@ -39,10 +39,10 @@ A \e {multiplex rule} creates one \e transformer that takes all input artifacts with the matching input file tag and creates one or more artifacts (e.g. C++ linker). - A \e {non-multiplex rule} creates one transformer per matching input file (e.g. C++ - compiler). - As a real-world example of a non-multiplex rule, here is a simplified version of \QBS' rule for - transforming C++ sources into object files using gcc: + A \e {simplex rule} creates one transformer per matching input file + (e.g. C++ compiler). + As a real-world example of a simplex rule, here is a simplified version + of \QBS' rule for transforming C++ sources into object files using gcc: \code Rule { id: compiler @@ -51,7 +51,7 @@ Artifact { fileTags: ['obj'] - fileName: '.obj/' + product.name + '/' + input.baseDir + '/' + input.fileName + '.o' + fileName: '.obj/' + input.baseDir + '/' + input.fileName + '.o' } prepare: { @@ -79,7 +79,7 @@ args.push(output.filePath); var compilerPath = ModUtils.moduleProperty(product, 'compilerPath'); var cmd = new Command(compilerPath, args); - cmd.description = 'compiling ' + FileInfo.fileName(input.filePath); + cmd.description = 'compiling ' + input.fileName; cmd.highlight = 'compiler'; return cmd; } diff --git a/doc/reference/modules/nodejs-module.qdoc b/doc/reference/modules/nodejs-module.qdoc new file mode 100644 index 000000000..1e2d7bab1 --- /dev/null +++ b/doc/reference/modules/nodejs-module.qdoc @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +/*! + \contentspage index.html + \page nodejs-module.html + \ingroup list-of-modules + + \title Module nodejs + \brief Provides Node.js support. + + The \c nodejs module contains support for running \l{http://nodejs.org}{Node.js} + applications from \QBS. + + \section1 General Properties + + + \section2 applicationFile + + \table + \row \li \b{Type:} \li \c{path} + \row \li \b{Default:} \li \c{undefined} + \endtable + + The input JavaScript file whose corresponding output will be executed when running the Node.js + application. If this property is \c{undefined}, the product will not be runnable. +*/ diff --git a/doc/reference/modules/qt-modules.qdoc b/doc/reference/modules/qt-modules.qdoc index 7f29be65a..01e2226b9 100644 --- a/doc/reference/modules/qt-modules.qdoc +++ b/doc/reference/modules/qt-modules.qdoc @@ -408,6 +408,25 @@ Specifies whether QML debugging support should be compiled into your binaries. + \section3 qmlImportsPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li set by \c{qbs-setup-qt} + \endtable + + The absolute path to the directory where Qt's QML imports are installed. + + \section3 qmlPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li set by \c{qbs-setup-qt} + \endtable + + The absolute path to the directory where Qt's QML files are installed. + This property is undefined for Qt4. + \section2 gui Properties \section3 uicName @@ -430,4 +449,23 @@ Specifies whether QML debugging support should be compiled into your binaries. + \section3 qmlImportsPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li set by \c{qbs-setup-qt} + \endtable + + The absolute path to the directory where Qt's QML imports are installed. + + \section3 qmlPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li set by \c{qbs-setup-qt} + \endtable + + The absolute path to the directory where Qt's QML files are installed. + This property is undefined for Qt4. + */ diff --git a/doc/reference/modules/typescript-module.qdoc b/doc/reference/modules/typescript-module.qdoc new file mode 100644 index 000000000..b7a969a93 --- /dev/null +++ b/doc/reference/modules/typescript-module.qdoc @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +/*! + \contentspage index.html + \page typescript-module.html + \ingroup list-of-modules + + \title Module typescript + \brief Provides TypeScript support. + + The \c typescript module contains properties and rules for building + \l{http://www.typescriptlang.org}{TypeScript} applications and may be used in combination with + the \l {Module nodejs} {nodejs} module to run TypeScript applications directly from \QBS. + + \section1 General Properties + + + \section2 warningLevel + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"normal"}, \c{"pedantic"} + \row \li \b{Default:} \li \c{"normal"} + \endtable + + Severity of warnings to emit. The higher the level, the more warnings will be shown. + \c{pedantic} causes the TypeScript to emit warnings on expressions and declarations with an + implied 'any' type. + + \section2 targetVersion + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"ES3"}, \c{"ES5"} + \row \li \b{Default:} \li \c{undefined} + \endtable + + ECMAScript target version for generated JavaScript code. \c{undefined} uses the TypeScript + compiler default, which is currently \c{"ES3"}. + + \section2 moduleLoader + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"commonjs"}, \c{"amd"} + \row \li \b{Default:} \li \c{undefined} + \endtable + + If TypeScript modules are being used, the JavaScript module loading mechanism to use in the + generated JavaScript code. \c{undefined} indicates modules are not being used. + + \section2 stripComments + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{!qbs.debugInformation} + \endtable + + Whether to remove comments from the generated JavaScript files. + + \section2 generateDeclarations + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{false} + \endtable + + Whether to generate corresponding .d.ts files during compilation; these are TypeScript's + equivalent of header files. + + \section2 generateSourceMaps + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{qbs.debugInformation} + \endtable + + Whether to generate corresponding .map files during compilation. + + \section2 compilerFlags + + \table + \row \li \b{Type:} \li \c{stringList} + \row \li \b{Default:} \li undefined + \endtable + + Additional flags for the TypeScript compiler. + + \section2 singleFile + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{false} + \endtable + + Whether to compile all TypeScript source files to a single JavaScript output file. The default + is to compile each TypeScript file to a corresponding JavaScript file. This property is + incompatible with \c{moduleLoader}. + + \section2 version + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{undefined} + \endtable + + The TypeScript version. Consists of four numbers separated by dots, for instance "1.0.0.0". + + \section2 versionMajor + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[0]} + \endtable + + The TypeScript major version. + + \section2 versionMinor + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[1]} + \endtable + + The TypeScript minor version. + + \section2 versionParts + + \table + \row \li \b{Type:} \li \c{list} + \row \li \b{Default:} \li \c{empty} + \endtable + + The TypeScript version as a list. For instance, TypeScript version 1.0 would correspond to a + value of \c[1, 0, 0, 0]. + + \section2 versionPatch + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[2]} + \endtable + + The TypeScript patch level. + + \section2 versionBuild + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[3]} + \endtable + + The fourth TypeScript version number component. + + \section2 toolchainInstallPath + + \table + \row \li \b{Type:} \li \c{path} + \row \li \b{Default:} \li \c{undefined} + \endtable + + TypeScript installation directory. This should not normally need to be changed provided that + \c{tsc} is already available by searching the PATH environment variable. + + \section2 compilerName + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{"tsc"} + \endtable + + Name of the compiler binary. This should not normally need to be changed. + + \section2 compilerPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{compilerName} + \endtable + + Directory where the compiler binary is located. This should not normally need to be changed. +*/ diff --git a/qbs_version.pri b/qbs_version.pri index 7e8806517..4ef0da176 100644 --- a/qbs_version.pri +++ b/qbs_version.pri @@ -1,3 +1,3 @@ -QBS_VERSION = 1.2.1 +QBS_VERSION = 1.3.0 QBS_VERSION_MAJ = $$section(QBS_VERSION, ., 0, 0) DEFINES += QBS_VERSION=\\\"$$QBS_VERSION\\\" diff --git a/share/qbs/imports/qbs/BundleTools/bundle-tools.js b/share/qbs/imports/qbs/BundleTools/bundle-tools.js new file mode 100644 index 000000000..a2950a365 --- /dev/null +++ b/share/qbs/imports/qbs/BundleTools/bundle-tools.js @@ -0,0 +1,355 @@ +// NOTE: QBS and Xcode's "target" and "product" names are reversed + +var PropertyList = loadExtension("qbs.PropertyList"); + +function isBundleProduct(product) { + return product.type.contains("applicationbundle") + || product.type.contains("frameworkbundle") + || product.type.contains("bundle") + || product.type.contains("inapppurchase"); +} + +/** + * Returns the package creator code for the given product based on its type. + */ +function packageType(product) { + if (product.type.contains("application") || product.type.contains("applicationbundle")) + return "APPL"; + else if (product.type.contains("frameworkbundle")) + return "FMWK"; + else if (product.type.contains("bundle")) + return "BNDL"; + + throw ("Unsupported product type " + product.type + ". " + + "Must be in {application, applicationbundle, frameworkbundle, bundle}."); +} + +function infoPlistContents(infoPlistFilePath) { + if (infoPlistFilePath === undefined) + return undefined; + + var plist = new PropertyList(); + try { + plist.readFromFile(infoPlistFilePath); + return JSON.parse(plist.toJSONString()); + } finally { + plist.clear(); + } +} + +function infoPlistFormat(infoPlistFilePath) { + if (infoPlistFilePath === undefined) + return undefined; + + var plist = new PropertyList(); + try { + plist.readFromFile(infoPlistFilePath); + return plist.format(); + } finally { + plist.clear(); + } +} + +/** + * Returns the main bundle directory. + * @param version only used for framework bundles. + * @note Xcode equivalent: CONTENTS_FOLDER_PATH + */ +function contentsFolderPath(product, version) { + var path = wrapperName(product); + + if (product.type.contains("frameworkbundle")) + path += "/Versions/" + (version || frameworkVersion(product)); + else if (!isShallowBundle(product)) + path += "/Contents"; + + return path; +} + +/** + * Returns the directory for documentation files. + * @param version only used for framework bundles. + * @note Xcode equivalent: DOCUMENTATION_FOLDER_PATH + */ +function documentationFolderPath(product, localizationName, version) { + var path = localizedResourcesFolderPath(product, localizationName, version); + if (!product.type.contains("inapppurchase")) + path += "/Documentation"; + return path; +} + +/** + * Returns the destination directory for auxiliary executables. + * @param version only used for framework bundles. + * @note Xcode equivalent: EXECUTABLES_FOLDER_PATH + */ +function executablesFolderPath(product, localizationName, version) { + if (product.type.contains("frameworkbundle")) + return localizedResourcesFolderPath(product, localizationName, version); + else + return _contentsFolderSubDirPath(product, "Executables", version); +} + +/** + * Returns the destination directory for the primary executable. + * @param version only used for framework bundles. + * @note Xcode equivalent: EXECUTABLE_FOLDER_PATH + */ +function executableFolderPath(product, version) { + var path = contentsFolderPath(product, version); + if (!isShallowBundle(product) + && !product.type.contains("frameworkbundle") + && !product.type.contains("inapppurchase")) + path += "/MacOS"; + + return path; +} + +/** + * Returns the path to the bundle's primary executable file. + * @param version only used for framework bundles. + * @note Xcode equivalent: EXECUTABLE_PATH + */ +function executablePath(product, version) { + return executableFolderPath(product, version) + "/" + productName(product); +} + +/** + * Returns the major version number or letter corresponding to the bundle version. + * @note Xcode equivalent: FRAMEWORK_VERSION + */ +function frameworkVersion(product) { + if (!product.type.contains("frameworkbundle")) + throw "Product type must be a frameworkbundle, was " + product.type; + + var n = parseInt(product.version, 10); + return isNaN(n) ? 'A' : n; +} + +/** + * Returns the directory containing frameworks used by the bundle's executables. + * @param version only used for framework bundles. + * @note Xcode equivalent: FRAMEWORKS_FOLDER_PATH + */ +function frameworksFolderPath(product, version) { + return _contentsFolderSubDirPath(product, "Frameworks", version); +} + +/** + * Returns the path to the bundle's main information property list. + * @param version only used for framework bundles. + * @note Xcode equivalent: INFOPLIST_PATH + */ +function infoPlistPath(product, version) { + var path; + if (product.type.contains("application")) + path = ".tmp"; + else if (product.type.contains("frameworkbundle")) + path = unlocalizedResourcesFolderPath(product, version); + else if (product.type.contains("inapppurchase")) + path = wrapperName(product); + else + path = contentsFolderPath(product, version); + + return path + "/" + _infoFileNames(product)[0]; +} + +/** + * Returns the path to the strings file corresponding to the bundle's main information property list. + * @param version only used for framework bundles. + * @note Xcode equivalent: INFOSTRINGS_PATH + */ +function infoStringsPath(product, localizationName, version) { + return localizedResourcesFolderPath(product, localizationName, version) + "/" + _infoFileNames(product)[1]; +} + +/** + * Returns the path to the bundle's resources directory for the given localization. + * @param version only used for framework bundles. + * @note Xcode equivalent: LOCALIZED_RESOURCES_FOLDER_PATH + */ +function localizedResourcesFolderPath(product, localizationName, version) { + if (typeof localizationName !== "string") + throw("'" + localizationName + "' is not a valid localization name"); + + return unlocalizedResourcesFolderPath(product, version) + "/" + localizationName + ".lproj"; +} + +/** + * Returns the path to the bundle's PkgInfo (package info) file. + * @note Xcode equivalent: PKGINFO_PATH + */ +function pkgInfoPath(product) { + var path = (product.type.contains("frameworkbundle")) + ? wrapperName(product) + : contentsFolderPath(product); + return path + "/PkgInfo"; +} + +/** + * Returns the directory containing plugins used by the bundle's executables. + * @param version only used for framework bundles. + * @note Xcode equivalent: PLUGINS_FOLDER_PATH + */ +function pluginsFolderPath(product, version) { + if (product.type.contains("frameworkbundle")) + return unlocalizedResourcesFolderPath(product, version); + + return _contentsFolderSubDirPath(product, "PlugIns", version); +} + +/** + * Returns the directory containing private header files for the framework. + * @param version only used for framework bundles. + * @note Xcode equivalent: PRIVATE_HEADERS_FOLDER_PATH + */ +function privateHeadersFolderPath(product, version) { + return _contentsFolderSubDirPath(product, "PrivateHeaders", version); +} + +/** + * Returns the name of the product (in Xcode terms) which corresponds to the target name in Qbs terms. + * @note Xcode equivalent: PRODUCT_NAME + */ +function productName(product) { + return product.targetName; +} + +/** + * Returns the directory containing public header files for the framework. + * @param version only used for framework bundles. + * @note Xcode equivalent: PUBLIC_HEADERS_FOLDER_PATH + */ +function publicHeadersFolderPath(product, version) { + return _contentsFolderSubDirPath(product, "Headers", version); +} + +/** + * Returns the directory containing script files associated with the bundle. + * @param version only used for framework bundles. + * @note Xcode equivalent: SCRIPTS_FOLDER_PATH + */ +function scriptsFolderPath(product, version) { + return unlocalizedResourcesFolderPath(product, version) + "/Scripts"; +} + +/** + * Returns whether the bundle is a shallow bundle. + * This controls the presence or absence of the Contents, MacOS and Resources folders. + * iOS tends to store the majority of files in its bundles in the main directory. + * @note Xcode equivalent: SHALLOW_BUNDLE + */ +function isShallowBundle(product) { + return product.moduleProperty("qbs", "targetOS").contains("ios") + && product.type.contains("applicationbundle"); +} + +/** + * Returns the directory containing sub-frameworks that may be shared with other applications. + * @param version only used for framework bundles. + * @note Xcode equivalent: SHARED_FRAMEWORKS_FOLDER_PATH + */ +function sharedFrameworksFolderPath(product, version) { + return _contentsFolderSubDirPath(product, "SharedFrameworks", version); +} + +/** + * Returns the directory containing supporting files that may be shared with other applications. + * @param version only used for framework bundles. + * @note Xcode equivalent: SHARED_SUPPORT_FOLDER_PATH + */ +function sharedSupportFolderPath(product, version) { + if (product.type.contains("frameworkbundle")) + return unlocalizedResourcesFolderPath(product, version); + + return _contentsFolderSubDirPath(product, "SharedSupport", version); +} + +/** + * Returns the directory containing resource files that are not specific to any given localization. + * @note Xcode equivalent: UNLOCALIZED_RESOURCES_FOLDER_PATH + */ +function unlocalizedResourcesFolderPath(product, version) { + if (isShallowBundle(product)) + return contentsFolderPath(product, version); + + return _contentsFolderSubDirPath(product, "Resources", version); +} + +/** + * Returns the path to the bundle's version.plist file. + * @param version only used for framework bundles. + * @note Xcode equivalent: VERSIONPLIST_PATH + */ +function versionPlistPath(product, version) { + var path = (product.type.contains("frameworkbundle")) + ? unlocalizedResourcesFolderPath(product, version) + : contentsFolderPath(product, version); + return path + "/version.plist"; +} + +/** + * Returns the file extension of the bundle directory - app, framework, bundle, etc. + * @note Xcode equivalent: WRAPPER_EXTENSION + */ +function wrapperExtension(product) { + if (product.type.contains("applicationbundle")) { + return "app"; + } else if (product.type.contains("frameworkbundle")) { + return "framework"; + } else if (product.type.contains("inapppurchase")) { + return ""; + } else if (product.type.contains("bundle")) { + // Potentially: kext, prefPane, qlgenerator, saver, mdimporter, or a custom extension + var bundleExtension = ModUtils.moduleProperty(product, "bundleExtension"); + + // default to bundle if none was specified by the user + return bundleExtension || "bundle"; + } else { + throw ("Unsupported bundle product type " + product.type + ". " + + "Must be in {applicationbundle, frameworkbundle, bundle, inapppurchase}."); + } +} + +/** + * Returns the name of the bundle directory - the product name plus the bundle extension. + * @note Xcode equivalent: WRAPPER_NAME + */ +function wrapperName(product) { + return productName(product) + wrapperSuffix(product); +} + +/** + * Returns the suffix of the bundle directory, that is, its extension prefixed with a '.', + * or an empty string if the extension is also an empty string. + * @note Xcode equivalent: WRAPPER_SUFFIX + */ +function wrapperSuffix(product) { + var ext = wrapperExtension(product); + return ext ? ("." + ext) : ""; +} + +// Private helper functions + +/** + * In-App purchase content bundles use virtually no subfolders of Contents; + * this is a convenience method to avoid repeating that logic everywhere. + * @param version only used for framework bundles. + */ +function _contentsFolderSubDirPath(product, subdirectoryName, version) { + var path = contentsFolderPath(product, version); + if (!product.type.contains("inapppurchase")) + path += "/" + subdirectoryName; + return path; +} + +/** + * Returns a list containing the filename of the bundle's main information + * property list and filename of the corresponding strings file. + */ +function _infoFileNames(product) { + if (product.type.contains("inapppurchase")) + return ["ContentInfo.plist", "ContentInfo.strings"]; + else + return ["Info.plist", "InfoPlist.strings"]; +} diff --git a/share/qbs/modules/cpp/darwin-tools.js b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js index ef99bc8ca..3c30aba50 100644 --- a/share/qbs/modules/cpp/darwin-tools.js +++ b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js @@ -1,28 +1,30 @@ var FileInfo = loadExtension("qbs.FileInfo"); -// replace chars non safe for a domain name (rfc1034) with a "-" -function rfc1034(inStr) -{ - return inStr.replace(/[^-A-Za-z0-9.]/g,'-'); +/** + * Replace characters unsafe for use in a domain name with a '-' character (RFC 1034). + */ +function rfc1034(inStr) { + return inStr.replace(/[^-A-Za-z0-9.]/g, '-'); } -// Returns the localization of a resource at the given path, -// or undefined if the path does not contain an {xx}.lproj path segment -function localizationKey(path) -{ +/** + * Returns the localization of the resource at the given path, + * or undefined if the path does not contain an {xx}.lproj path segment. + */ +function localizationKey(path) { return _resourceFileProperties(path)[0]; } -// Returns the path of a localized resource at the given path, -// relative to its containing {xx}.lproj directory, or '.' -// if the resource is unlocalized (not contained in an lproj directory) -function relativeResourcePath(path) -{ +/** + * Returns the path of a localized resource at the given path, + * relative to its containing {xx}.lproj directory, or '.' + * if the resource is unlocalized (not contained in an lproj directory). + */ +function relativeResourcePath(path) { return _resourceFileProperties(path)[1]; } -function _resourceFileProperties(path) -{ +function _resourceFileProperties(path) { var lprojKey = ".lproj/"; var lproj = path.indexOf(lprojKey); if (lproj >= 0) { @@ -38,13 +40,15 @@ function _resourceFileProperties(path) return [ undefined, '.' ]; } -// perform replacements in env recursively -// JSON.stringify(expandPlistEnvironmentVariables({a:"$(x)3$$(y)",b:{t:"%$(y) $(k)"}}, -// {x:"X",y:"Y"}, true)) -// Warning undefined variable k in variable expansion -// => {"a":"X3$Y","b":{"t":"%Y $(k)"}} -function expandPlistEnvironmentVariables(obj, env, warn) -{ +/** + * Recursively perform variable replacements in an environment dictionary. + * + * JSON.stringify(expandPlistEnvironmentVariables({a:"$(x)3$$(y)",b:{t:"%$(y) $(k)"}}, + * {x:"X",y:"Y"}, true)) + * Warning undefined variable k in variable expansion + * => {"a":"X3$Y","b":{"t":"%Y $(k)"}} + */ +function expandPlistEnvironmentVariables(obj, env, warn) { // Possible syntaxes for wrapping an environment variable name var syntaxes = [ {"open": "${", "close": "}"}, @@ -52,8 +56,10 @@ function expandPlistEnvironmentVariables(obj, env, warn) {"open": "@", "close": "@"} ]; - // Finds the first index of a replacement starting with one of the supported syntaxes - // This is needed so we don't do recursive substitutions + /** + * Finds the first index of a replacement starting with one of the supported syntaxes + * This is needed so we don't do recursive substitutions + */ function indexOfReplacementStart(syntaxes, str, offset) { var syntax; var idx = str.length; @@ -70,7 +76,7 @@ function expandPlistEnvironmentVariables(obj, env, warn) function expandRecursive(obj, env, checked) { checked.push(obj); for (var key in obj) { - var value =obj[key]; + var value = obj[key]; var type = typeof(value); if (type === "object") { if (checked.indexOf(value) !== -1) diff --git a/share/qbs/imports/qbs/FileInfo/fileinfo.js b/share/qbs/imports/qbs/FileInfo/fileinfo.js index f04212f1e..fe672c546 100644 --- a/share/qbs/imports/qbs/FileInfo/fileinfo.js +++ b/share/qbs/imports/qbs/FileInfo/fileinfo.js @@ -1,9 +1,12 @@ +var _windowsAbsolutePathPattern = new RegExp("^[a-z,A-Z]:[/,\\\\]"); +var _removeDoubleSlashesPattern = new RegExp("/{2,}", "g"); + function path(fp) { if (fp === '/') return fp; // Yes, this will be wrong for "clever" unix users calling their directory 'c:'. Boohoo. - if (fp.length === 3 && fp.slice(-2) === ':/') + if (fp.length === 3 && fp.slice(-2) === ":/") return fp; var last = fp.lastIndexOf('/'); @@ -27,17 +30,16 @@ function baseName(fph) { function completeBaseName(fph) { var fn = fileName(fph); - var last = fn.lastIndexOf("."); + var last = fn.lastIndexOf('.'); if (last < 0) return fn; else return fn.slice(0, last); } -function relativePath(base, rel) -{ +function relativePath(base, rel) { var basel = base.split('/'); - var rell = rel.split('/'); + var rell = rel.split('/'); var i; for (i = basel.length; i-- >= 0;) { if (basel[i] === '.' || basel[i] === '') @@ -56,7 +58,7 @@ function relativePath(base, rel) var r = []; for (; i < basel.length; i++) - r.push('..'); + r.push(".."); for (; j < rell.length; j++) r.push(rell[j]); @@ -64,34 +66,27 @@ function relativePath(base, rel) return r.join('/'); } -var windowsAbsolutePathPattern = new RegExp("^[a-z,A-Z]:[/,\\\\]") -function isAbsolutePath(path) -{ +function isAbsolutePath(path) { if (!path) return false; - return (path.charAt(0) === '/' || windowsAbsolutePathPattern.test(path)); + return (path.charAt(0) === '/' || _windowsAbsolutePathPattern.test(path)); } -function toWindowsSeparators(str) -{ +function toWindowsSeparators(str) { return str.toString().replace(/\//g, '\\'); } -function fromWindowsSeparators(str) -{ +function fromWindowsSeparators(str) { return str.toString().replace(/\\/g, '/'); } -var removeDoubleSlashesPattern = new RegExp("/{2,}", "g") - -function joinPaths() -{ +function joinPaths() { function pathFilter(path) { - return path && typeof path === 'string'; + return path && typeof path === "string"; } var paths = Array.prototype.slice.call(arguments, 0).filter(pathFilter); var joinedPaths = paths.join('/'); - return joinedPaths.replace(removeDoubleSlashesPattern, "/") + return joinedPaths.replace(_removeDoubleSlashesPattern, '/'); } diff --git a/share/qbs/imports/qbs/ModUtils/utils.js b/share/qbs/imports/qbs/ModUtils/utils.js index 7b6310664..017155616 100644 --- a/share/qbs/imports/qbs/ModUtils/utils.js +++ b/share/qbs/imports/qbs/ModUtils/utils.js @@ -1,16 +1,11 @@ -// -// utility functions for modules -// - -/*! - * Given a list of file tags, returns the file tag (one of [c, cpp, objc, objcpp]) - * corresponding to the C-family language the file should be compiled as. - * - * If no such tag is found, undefined is returned. If more than one match is - * found, an exception is thrown. - */ -function fileTagForTargetLanguage(fileTags) -{ +/** + * Given a list of file tags, returns the file tag (one of [c, cpp, objc, objcpp]) + * corresponding to the C-family language the file should be compiled as. + * + * If no such tag is found, undefined is returned. If more than one match is + * found, an exception is thrown. + */ +function fileTagForTargetLanguage(fileTags) { var srcTags = ["c", "cpp", "objc", "objcpp", "asm", "asm_cpp"]; var pchTags = ["c_pch", "cpp_pch", "objc_pch", "objcpp_pch"]; @@ -34,45 +29,44 @@ function fileTagForTargetLanguage(fileTags) return foundTagCount == 1 ? canonicalTag : undefined; } -/* - * Returns the name of a language-specific property given the file tag - * for that property, and the base property name. - * - * If \a fileTag is undefined, the language-agnostic property name is returned. - * - * \param propertyName flags, platformFlags, precompiledHeader - * \param fileTag c, cpp, objc, objcpp - */ -function languagePropertyName(propertyName, fileTag) -{ +/** + * Returns the name of a language-specific property given the file tag + * for that property, and the base property name. + * + * If \a fileTag is undefined, the language-agnostic property name is returned. + * + * @param propertyName flags, platformFlags, precompiledHeader + * @param fileTag c, cpp, objc, objcpp + */ +function languagePropertyName(propertyName, fileTag) { if (!fileTag) - fileTag = 'common'; + fileTag = "common"; var map = { - 'c': { - 'flags': 'cFlags', - 'platformFlags': 'platformCFlags', - 'precompiledHeader': 'cPrecompiledHeader' + "c": { + "flags": "cFlags", + "platformFlags": "platformCFlags", + "precompiledHeader": "cPrecompiledHeader" }, - 'cpp': { - 'flags': 'cxxFlags', - 'platformFlags': 'platformCxxFlags', - 'precompiledHeader': 'cxxPrecompiledHeader' + "cpp": { + "flags": "cxxFlags", + "platformFlags": "platformCxxFlags", + "precompiledHeader": "cxxPrecompiledHeader" }, - 'objc': { - 'flags': 'objcFlags', - 'platformFlags': 'platformObjcFlags', - 'precompiledHeader': 'objcPrecompiledHeader' + "objc": { + "flags": "objcFlags", + "platformFlags": "platformObjcFlags", + "precompiledHeader": "objcPrecompiledHeader" }, - 'objcpp': { - 'flags': 'objcxxFlags', - 'platformFlags': 'platformObjcxxFlags', - 'precompiledHeader': 'objcxxPrecompiledHeader' + "objcpp": { + "flags": "objcxxFlags", + "platformFlags": "platformObjcxxFlags", + "precompiledHeader": "objcxxPrecompiledHeader" }, - 'common': { - 'flags': 'commonCompilerFlags', - 'platformFlags': 'platformCommonCompilerFlags', - 'precompiledHeader': 'precompiledHeader' + "common": { + "flags": "commonCompilerFlags", + "platformFlags": "platformCommonCompilerFlags", + "precompiledHeader": "precompiledHeader" } }; @@ -83,93 +77,200 @@ function languagePropertyName(propertyName, fileTag) return lang[propertyName] || propertyName; } -function moduleProperties(config, key, langFilter) -{ +function moduleProperties(config, key, langFilter) { return config.moduleProperties(config.moduleName, languagePropertyName(key, langFilter)) } -function modulePropertiesFromArtifacts(product, artifacts, moduleName, propertyName, langFilter) -{ +function modulePropertiesFromArtifacts(product, artifacts, moduleName, propertyName, langFilter) { var result = product.moduleProperties(moduleName, languagePropertyName(propertyName, langFilter)) for (var i in artifacts) result = result.concat(artifacts[i].moduleProperties(moduleName, languagePropertyName(propertyName, langFilter))) return result } -function moduleProperty(product, propertyName, langFilter) -{ +function moduleProperty(product, propertyName, langFilter) { return product.moduleProperty(product.moduleName, languagePropertyName(propertyName, langFilter)) } -function dumpProperty(key, value, level) -{ - var indent = '' - for (var k=0; k < level; ++k) - indent += ' ' - print(indent + key + ': ' + value) +function dumpProperty(key, value, level) { + var indent = ""; + for (var k = 0; k < level; ++k) + indent += " "; + print(indent + key + ": " + value); } -function traverseObject(obj, func, level) -{ +function traverseObject(obj, func, level) { if (!level) - level = 0 - var i, children = {} + level = 0; + var i, children = {}; for (i in obj) { if (typeof(obj[i]) === "object" && !(obj[i] instanceof Array)) - children[i] = obj[i] + children[i] = obj[i]; else - func.apply(this, [i, obj[i], level]) + func.apply(this, [i, obj[i], level]); } - level++ + level++; for (i in children) { - func.apply(this, [i, children[i], level - 1]) - traverseObject(children[i], func, level) + func.apply(this, [i, children[i], level - 1]); + traverseObject(children[i], func, level); } - level-- + level--; } -function dumpObject(obj, description) -{ +function dumpObject(obj, description) { if (!description) - description = 'object dump' - print('+++++++++ ' + description + ' +++++++++') - traverseObject(obj, dumpProperty) + description = "object dump"; + print("+++++++++ " + description + " +++++++++"); + traverseObject(obj, dumpProperty); } - -////////////////////////////////////////////////////////// -// The EnvironmentVariable class -// -function EnvironmentVariable(name, separator, convertPathSeparators) -{ - if (!name) - throw "EnvironmentVariable c'tor needs a name as first argument." - this.name = name - this.value = getEnv(name).toString() - this.separator = separator || '' - this.convertPathSeparators = convertPathSeparators || false +function concatAll() { + var result = []; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + if (arg === undefined) + continue; + else if (arg instanceof Array) + result = result.concat(arg); + else + result.push(arg); + } + return result; } -EnvironmentVariable.prototype.prepend = function(v) -{ - if (this.value.length > 0 && this.value.charAt(0) !== this.separator) - this.value = this.separator + this.value - if (this.convertPathSeparators) - v = FileInfo.toWindowsSeparators(v) - this.value = v + this.value -} +var EnvironmentVariable = (function () { + function EnvironmentVariable(name, separator, convertPathSeparators) { + if (!name) + throw "EnvironmentVariable c'tor needs a name as first argument."; + this.name = name; + this.value = getEnv(name).toString(); + this.separator = separator || ""; + this.convertPathSeparators = convertPathSeparators || false; + } + EnvironmentVariable.prototype.prepend = function (v) { + if (this.value.length > 0 && this.value.charAt(0) !== this.separator) + this.value = this.separator + this.value; + if (this.convertPathSeparators) + v = FileInfo.toWindowsSeparators(v); + this.value = v + this.value; + }; -EnvironmentVariable.prototype.append = function(v) -{ - if (this.value.length > 0) - this.value += this.separator - if (this.convertPathSeparators) - v = FileInfo.toWindowsSeparators(v) - this.value += v -} + EnvironmentVariable.prototype.append = function (v) { + if (this.value.length > 0) + this.value += this.separator; + if (this.convertPathSeparators) + v = FileInfo.toWindowsSeparators(v); + this.value += v; + }; -EnvironmentVariable.prototype.set = function() -{ - putEnv(this.name, this.value) -} + EnvironmentVariable.prototype.set = function () { + putEnv(this.name, this.value); + }; + return EnvironmentVariable; +})(); + +var PropertyValidator = (function () { + function PropertyValidator(moduleName) { + this.requiredProperties = {}; + this.propertyValidators = []; + if (!moduleName) + throw "PropertyValidator c'tor needs a module name as a first argument."; + this.moduleName = moduleName; + } + PropertyValidator.prototype.setRequiredProperty = function (propertyName, propertyValue, message) { + this.requiredProperties[propertyName] = { propertyValue: propertyValue, message: message }; + }; + + PropertyValidator.prototype.addRangeValidator = function (propertyName, propertyValue, min, max, allowFloats) { + var message = []; + if (min !== undefined) + message.push(">= " + min); + if (max !== undefined) + message.push("<= " + max); + + this.addCustomValidator(propertyName, propertyValue, function (value) { + if (typeof value !== "number") + return false; + if (!allowFloats && value % 1 !== 0) + return false; + if (min !== undefined && value < min) + return false; + if (max !== undefined && value > max) + return false; + return true; + }, "must be " + (!allowFloats ? "an integer " : "") + message.join(" and ")); + }; + PropertyValidator.prototype.addVersionValidator = function (propertyName, propertyValue, minComponents, maxComponents, allowSuffixes) { + if (minComponents !== undefined && (typeof minComponents !== "number" || minComponents % 1 !== 0 || minComponents < 1)) + throw "minComponents must be at least 1"; + if (maxComponents !== undefined && (typeof maxComponents !== "number" || maxComponents % 1 !== 0 || maxComponents < minComponents)) + throw "maxComponents must be >= minComponents"; + + this.addCustomValidator(propertyName, propertyValue, function (value) { + if (typeof value !== "string") + return false; + return value && value.match("^[0-9]+(\\.[0-9]+){" + ((minComponents - 1) || 0) + "," + ((maxComponents - 1) || "") + "}" + (!allowSuffixes ? "$" : "")) !== null; + }, "must be a version number with " + minComponents + " to " + maxComponents + " components"); + }; + + PropertyValidator.prototype.addCustomValidator = function (propertyName, propertyValue, validator, message) { + this.propertyValidators.push({ + propertyName: propertyName, + propertyValue: propertyValue, + validator: validator, + message: message + }); + }; + + PropertyValidator.prototype.validate = function (throwOnError) { + var i; + var lines; + + // Find any missing properties + var missingProperties = {}; + for (i in this.requiredProperties) { + var propValue = this.requiredProperties[i].propertyValue; + if (propValue === undefined || propValue === null || propValue === "") { + missingProperties[i] = this.requiredProperties[i]; + } + } + + // Find any properties that don't satisfy their validator function + var invalidProperties = {}; + for (var j = 0; j < this.propertyValidators.length; ++j) { + var v = this.propertyValidators[j]; + if (!v.validator(v.propertyValue)) { + invalidProperties[v.propertyName] = v.message; + } + } + + var errorMessage = ""; + if (Object.keys(missingProperties).length > 0) { + errorMessage += "The following properties are not set. Set them in your profile:\n"; + lines = []; + for (i in missingProperties) { + var obj = missingProperties[i]; + lines.push(this.moduleName + "." + i + ((obj && obj.message) ? (": " + obj.message) : "")); + } + errorMessage += lines.join("\n"); + } + + if (Object.keys(invalidProperties).length > 0) { + if (errorMessage) + errorMessage += "\n"; + errorMessage += "The following properties have invalid values:\n"; + lines = []; + for (i in invalidProperties) { + lines.push(this.moduleName + "." + i + ": " + invalidProperties[i]); + } + errorMessage += lines.join("\n"); + } + + if (throwOnError !== false && errorMessage.length > 0) + throw errorMessage; + + return errorMessage.length == 0; + }; + return PropertyValidator; +})(); diff --git a/share/qbs/modules/cpp/path-tools.js b/share/qbs/imports/qbs/PathTools/path-tools.js index 17df91af5..b6c25a555 100644 --- a/share/qbs/modules/cpp/path-tools.js +++ b/share/qbs/imports/qbs/PathTools/path-tools.js @@ -1,37 +1,32 @@ -var BundleTools = loadFile("bundle-tools.js"); +var BundleTools = loadExtension("qbs.BundleTools"); -function applicationFileName() -{ - return ModUtils.moduleProperty(product, "executablePrefix") +function applicationFileName(product) { + return product.moduleProperty("cpp", "executablePrefix") + product.targetName - + ModUtils.moduleProperty(product, "executableSuffix"); + + product.moduleProperty("cpp", "executableSuffix"); } -function applicationFilePath() -{ +function applicationFilePath(product) { if (BundleTools.isBundleProduct(product)) return BundleTools.executablePath(product); else - return applicationFileName(); + return applicationFileName(product); } -function staticLibraryFileName() -{ - return ModUtils.moduleProperty(product, "staticLibraryPrefix") +function staticLibraryFileName(product) { + return product.moduleProperty("cpp", "staticLibraryPrefix") + product.targetName - + ModUtils.moduleProperty(product, "staticLibrarySuffix"); + + product.moduleProperty("cpp", "staticLibrarySuffix"); } -function staticLibraryFilePath() -{ +function staticLibraryFilePath(product) { if (BundleTools.isBundleProduct(product)) return BundleTools.executablePath(product); else - return staticLibraryFileName(); + return staticLibraryFileName(product); } -function dynamicLibraryFileName(version, maxParts) -{ +function dynamicLibraryFileName(product, version, maxParts) { // If no override version was given, use the product's version // We specifically want to differentiate between undefined and i.e. // empty string as empty string should be taken to mean "no version" @@ -48,7 +43,7 @@ function dynamicLibraryFileName(version, maxParts) version = version.split('.').slice(0, maxParts).join('.'); // Start with prefix + name (i.e. libqbs, qbs) - var fileName = ModUtils.moduleProperty(product, "dynamicLibraryPrefix") + product.targetName; + var fileName = product.moduleProperty("cpp", "dynamicLibraryPrefix") + product.targetName; // For Darwin platforms, append the version number if there is one (i.e. libqbs.1.0.0) var targetOS = product.moduleProperty("qbs", "targetOS"); @@ -58,7 +53,7 @@ function dynamicLibraryFileName(version, maxParts) } // Append the suffix (i.e. libqbs.1.0.0.dylib, libqbs.so, qbs.dll) - fileName += ModUtils.moduleProperty(product, "dynamicLibrarySuffix"); + fileName += product.moduleProperty("cpp", "dynamicLibrarySuffix"); // For non-Darwin Unix platforms, append the version number if there is one (i.e. libqbs.so.1.0.0) if (version && targetOS.contains("unix") && !targetOS.contains("darwin")) @@ -67,33 +62,44 @@ function dynamicLibraryFileName(version, maxParts) return fileName; } -function dynamicLibraryFilePath(version, maxParts) -{ +function dynamicLibraryFilePath(product, version, maxParts) { if (BundleTools.isBundleProduct(product)) return BundleTools.executablePath(product, version); else - return dynamicLibraryFileName(version, maxParts); + return dynamicLibraryFileName(product, version, maxParts); } -function importLibraryFilePath() -{ - return ModUtils.moduleProperty(product, "dynamicLibraryPrefix") +function importLibraryFilePath(product) { + return product.moduleProperty("cpp", "dynamicLibraryPrefix") + product.targetName - + ModUtils.moduleProperty(product, "dynamicLibraryImportSuffix"); + + product.moduleProperty("cpp", "dynamicLibraryImportSuffix"); } // DWARF_DSYM_FILE_NAME // Filename of the target's corresponding dSYM file -function dwarfDsymFileName() -{ +function dwarfDsymFileName(product) { if (BundleTools.isBundleProduct(product)) return BundleTools.wrapperName(product) + ".dSYM"; else if (product.type.contains("application")) - return applicationFileName() + ".dSYM"; + return applicationFileName(product) + ".dSYM"; else if (product.type.contains("dynamiclibrary")) - return dynamicLibraryFileName() + ".dSYM"; + return dynamicLibraryFileName(product) + ".dSYM"; else if (product.type.contains("staticlibrary")) - return staticLibraryFileName() + ".dSYM"; + return staticLibraryFileName(product) + ".dSYM"; else return product.targetName + ".dSYM"; } + +// Returns whether the string looks like a library filename +function isLibraryFileName(product, fileName, prefix, suffixes, isShared) { + var suffix, i; + var os = product.moduleProperty("qbs", "targetOS"); + for (i = 0; i < suffixes.length; ++i) { + suffix = suffixes[i]; + if (isShared && os.contains("unix") && !os.contains("darwin")) + suffix += "(\\.[0-9]+){0,3}"; + if (fileName.match("^" + prefix + ".+?\\" + suffix + "$")) + return true; + } + return false; +} diff --git a/share/qbs/imports/qbs/Probes/PathProbe.qbs b/share/qbs/imports/qbs/Probes/PathProbe.qbs index 26871dc22..943007792 100644 --- a/share/qbs/imports/qbs/Probes/PathProbe.qbs +++ b/share/qbs/imports/qbs/Probes/PathProbe.qbs @@ -1,7 +1,7 @@ import qbs 1.0 import qbs.File import qbs.FileInfo -import "utils.js" as Utils +import qbs.ModUtils Probe { // Inputs @@ -21,19 +21,19 @@ Probe { configure: { if (!names) throw '"names" must be specified'; - var _names = Utils.concatAll(names); + var _names = ModUtils.concatAll(names); if (nameFilter) _names = _names.map(nameFilter); // FIXME: Suggest how to obtain paths from system - var _paths = Utils.concatAll(pathPrefixes, platformPaths); + var _paths = ModUtils.concatAll(pathPrefixes, platformPaths); // FIXME: Add getenv support - var envs = Utils.concatAll(platformEnvironmentPaths, environmentPaths); + var envs = ModUtils.concatAll(platformEnvironmentPaths, environmentPaths); for (var i = 0; i < envs.length; ++i) { var value = qbs.getEnv(envs[i]) || ''; if (value.length > 0) _paths = _paths.concat(value.split(qbs.pathListSeparator)); } - var _suffixes = Utils.concatAll('', pathSuffixes); + var _suffixes = ModUtils.concatAll('', pathSuffixes); for (i = 0; i < _names.length; ++i) { for (var j = 0; j < _paths.length; ++j) { for (var k = 0; k < _suffixes.length; ++k) { diff --git a/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs index 07509034d..7eafc355c 100644 --- a/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs +++ b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs @@ -1,7 +1,6 @@ import qbs 1.0 import qbs.Process import qbs.FileInfo -import "utils.js" as Utils Probe { // Inputs diff --git a/share/qbs/imports/qbs/Probes/utils.js b/share/qbs/imports/qbs/Probes/utils.js deleted file mode 100644 index f8b7e28c5..000000000 --- a/share/qbs/imports/qbs/Probes/utils.js +++ /dev/null @@ -1,18 +0,0 @@ -// -// utility functions for probes -// - -function concatAll() -{ - var result = []; - for (var i = 0; i < arguments.length; ++i) { - var arg = arguments[i]; - if (arg === undefined) - continue; - else if (arg instanceof Array) - result = result.concat(arg); - else - result.push(arg); - } - return result; -} diff --git a/share/qbs/imports/qbs/UnixUtils/unix-utils.js b/share/qbs/imports/qbs/UnixUtils/unix-utils.js new file mode 100644 index 000000000..261d1bb5b --- /dev/null +++ b/share/qbs/imports/qbs/UnixUtils/unix-utils.js @@ -0,0 +1,15 @@ +function soname(product, outputFileName) { + function majorVersion(version, defaultValue) { + var n = parseInt(version, 10); + return isNaN(n) ? defaultValue : n; + } + + if (product.version) { + var major = majorVersion(product.version); + if (major) { + return outputFileName.substr(0, outputFileName.length - product.version.length) + + major; + } + } + return outputFileName; +} diff --git a/share/qbs/modules/cpp/windows.js b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js index 35414eed7..35414eed7 100644 --- a/share/qbs/modules/cpp/windows.js +++ b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js diff --git a/share/qbs/imports/qbs/base/NodeJSApplication.qbs b/share/qbs/imports/qbs/base/NodeJSApplication.qbs new file mode 100644 index 000000000..d51024bdc --- /dev/null +++ b/share/qbs/imports/qbs/base/NodeJSApplication.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "nodejs" } +} diff --git a/share/qbs/modules/cpp/DarwinGCC.qbs b/share/qbs/modules/cpp/DarwinGCC.qbs index 181e5902e..fc496a1bb 100644 --- a/share/qbs/modules/cpp/DarwinGCC.qbs +++ b/share/qbs/modules/cpp/DarwinGCC.qbs @@ -1,12 +1,12 @@ import qbs 1.0 +import qbs.BundleTools +import qbs.DarwinTools import qbs.File +import qbs.PathTools import qbs.Process import qbs.PropertyList import qbs.TextFile import qbs.ModUtils -import "bundle-tools.js" as BundleTools -import "darwin-tools.js" as DarwinTools -import 'path-tools.js' as PathTools UnixGCC { condition: false @@ -16,10 +16,10 @@ UnixGCC { validate: { if (qbs.sysroot) { - if (!xcodeSdkName) - throw "cpp.xcodeSdkName not set. Set cpp.xcodeSdkName in your profile."; - if (!xcodeSdkVersion) - throw "cpp.xcodeSdkVersion not set. Set cpp.xcodeSdkVersion in your profile."; + var validator = new ModUtils.PropertyValidator("cpp"); + validator.setRequiredProperty("xcodeSdkName", xcodeSdkName); + validator.setRequiredProperty("xcodeSdkVersion", xcodeSdkVersion); + validator.validate(); } } @@ -318,7 +318,7 @@ UnixGCC { inputs: ["application"] Artifact { - fileName: product.destinationDirectory + "/" + PathTools.dwarfDsymFileName() + fileName: product.destinationDirectory + "/" + PathTools.dwarfDsymFileName(product) fileTags: ["application_dsym"] } diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs index 342a6b70c..d20b69b35 100644 --- a/share/qbs/modules/cpp/GenericGCC.qbs +++ b/share/qbs/modules/cpp/GenericGCC.qbs @@ -2,9 +2,11 @@ import qbs 1.0 import qbs.File import qbs.FileInfo import qbs.ModUtils +import qbs.PathTools import qbs.Process +import qbs.UnixUtils +import qbs.WindowsUtils import 'gcc.js' as Gcc -import 'path-tools.js' as PathTools CppModule { condition: false @@ -67,32 +69,32 @@ CppModule { usings: ["dynamiclibrary_copy", "staticlibrary", "frameworkbundle"] Artifact { - fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFilePath() + fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFilePath(product) fileTags: ["dynamiclibrary"] } // libfoo Artifact { - fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(undefined, 0) + fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(product, undefined, 0) fileTags: ["dynamiclibrary_symlink"] } // libfoo.1 Artifact { - fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(undefined, 1) + fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(product, undefined, 1) fileTags: ["dynamiclibrary_symlink"] } // libfoo.1.0 Artifact { - fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(undefined, 2) + fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFileName(product, undefined, 2) fileTags: ["dynamiclibrary_symlink"] } // Copy of dynamic lib for smart re-linking. Artifact { fileName: product.destinationDirectory + "/.socopy/" - + PathTools.dynamicLibraryFilePath() + + PathTools.dynamicLibraryFilePath(product) fileTags: ["dynamiclibrary_copy"] alwaysUpdated: false cpp.transitiveSOs: { @@ -110,7 +112,7 @@ CppModule { prepare: { // Actual linker command. - var libFilePath = outputs["dynamiclibrary"][0].filePath; + var lib = outputs["dynamiclibrary"][0]; var platformLinkerFlags = ModUtils.moduleProperties(product, 'platformLinkerFlags'); var linkerFlags = ModUtils.moduleProperties(product, 'linkerFlags'); var commands = []; @@ -121,13 +123,13 @@ CppModule { args = args.concat([ '-Wl,--hash-style=gnu', '-Wl,--as-needed', - '-Wl,-soname=' + Gcc.soname(product, libFilePath) + '-Wl,-soname=' + UnixUtils.soname(product, lib.fileName) ]); } else if (product.moduleProperty("qbs", "targetOS").contains('darwin')) { var installNamePrefix = product.moduleProperty("cpp", "installNamePrefix"); if (installNamePrefix !== undefined) args.push("-Wl,-install_name," - + installNamePrefix + FileInfo.fileName(libFilePath)); + + installNamePrefix + lib.fileName); args.push("-Wl,-headerpad_max_install_names"); } args = args.concat(platformLinkerFlags); @@ -144,11 +146,11 @@ CppModule { } args.push('-o'); - args.push(libFilePath); + args.push(lib.filePath); args = args.concat(Gcc.linkerFlags(product, inputs)); args = args.concat(Gcc.additionalCompilerAndLinkerFlags(product)); var cmd = new Command(ModUtils.moduleProperty(product, "linkerPath"), args); - cmd.description = 'linking ' + FileInfo.fileName(libFilePath); + cmd.description = 'linking ' + lib.fileName; cmd.highlight = 'linker'; cmd.responseFileUsagePrefix = '@'; commands.push(cmd); @@ -205,12 +207,12 @@ CppModule { var links = outputs["dynamiclibrary_symlink"]; var symlinkCount = links.length; for (var i = 0; i < symlinkCount; ++i) { - cmd = new Command("ln", ["-sf", FileInfo.fileName(libFilePath), + cmd = new Command("ln", ["-sf", lib.fileName, links[i].filePath]); cmd.highlight = "filegen"; cmd.description = "creating symbolic link '" - + FileInfo.fileName(links[i].filePath) + "'"; - cmd.workingDirectory = FileInfo.path(libFilePath); + + links[i].fileName + "'"; + cmd.workingDirectory = FileInfo.path(lib.filePath); commands.push(cmd); } } @@ -225,7 +227,7 @@ CppModule { usings: ["dynamiclibrary", "staticlibrary", "frameworkbundle"] Artifact { - fileName: product.destinationDirectory + "/" + PathTools.staticLibraryFilePath() + fileName: product.destinationDirectory + "/" + PathTools.staticLibraryFilePath(product) fileTags: ["staticlibrary"] cpp.staticLibraries: { var result = [] @@ -250,7 +252,7 @@ CppModule { for (var i in inputs.obj) args.push(inputs.obj[i].filePath); var cmd = new Command(ModUtils.moduleProperty(product, "archiverPath"), args); - cmd.description = 'creating ' + FileInfo.fileName(output.filePath); + cmd.description = 'creating ' + output.fileName; cmd.highlight = 'linker' cmd.responseFileUsagePrefix = '@'; return cmd; @@ -271,7 +273,7 @@ CppModule { usings: ["dynamiclibrary_copy", "staticlibrary", "frameworkbundle"] Artifact { - fileName: product.destinationDirectory + "/" + PathTools.applicationFilePath() + fileName: product.destinationDirectory + "/" + PathTools.applicationFilePath(product) fileTags: ["application"] } @@ -300,7 +302,7 @@ CppModule { var minimumWindowsVersion = ModUtils.moduleProperty(product, "minimumWindowsVersion"); if (minimumWindowsVersion) { - var subsystemVersion = Windows.getWindowsVersionInFormat(minimumWindowsVersion, 'subsystem'); + var subsystemVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'subsystem'); if (subsystemVersion) { var major = subsystemVersion.split('.')[0]; var minor = subsystemVersion.split('.')[1]; @@ -325,7 +327,7 @@ CppModule { args = args.concat(Gcc.linkerFlags(product, inputs)); args = args.concat(Gcc.additionalCompilerAndLinkerFlags(product)); var cmd = new Command(ModUtils.moduleProperty(product, "linkerPath"), args); - cmd.description = 'linking ' + FileInfo.fileName(output.filePath); + cmd.description = 'linking ' + output.fileName; cmd.highlight = 'linker' cmd.responseFileUsagePrefix = '@'; return cmd; @@ -340,7 +342,7 @@ CppModule { Artifact { fileTags: ["obj"] - fileName: ".obj/" + product.name + "/" + input.baseDir + "/" + input.fileName + ".o" + fileName: ".obj/" + input.baseDir + "/" + input.fileName + ".o" } prepare: { diff --git a/share/qbs/modules/cpp/bundle-tools.js b/share/qbs/modules/cpp/bundle-tools.js deleted file mode 100644 index dca1b2c51..000000000 --- a/share/qbs/modules/cpp/bundle-tools.js +++ /dev/null @@ -1,330 +0,0 @@ -// NOTE: QBS and Xcode's "target" and "product" names are reversed - -var PropertyList = loadExtension("qbs.PropertyList"); - -function isBundleProduct(product) -{ - return product.type.contains("applicationbundle") - || product.type.contains("frameworkbundle") - || product.type.contains("bundle") - || product.type.contains("inapppurchase"); -} - -// Returns the package creator code for the given product based on its type -function packageType(product) -{ - if (product.type.contains("application") || product.type.contains("applicationbundle")) - return 'APPL'; - else if (product.type.contains("frameworkbundle")) - return 'FMWK'; - else if (product.type.contains("bundle")) - return 'BNDL'; - - throw ("Unsupported product type " + product.type + ". " - + "Must be in {application, applicationbundle, frameworkbundle, bundle}."); -} - -function infoPlistContents(infoPlistFilePath) -{ - if (infoPlistFilePath === undefined) - return undefined; - - var plist = new PropertyList(); - try { - plist.readFromFile(infoPlistFilePath); - return JSON.parse(plist.toJSONString()); - } finally { - plist.clear(); - } -} - -function infoPlistFormat(infoPlistFilePath) -{ - if (infoPlistFilePath === undefined) - return undefined; - - var plist = new PropertyList(); - try { - plist.readFromFile(infoPlistFilePath); - return plist.format(); - } finally { - plist.clear(); - } -} - -// CONTENTS_FOLDER_PATH -// Main bundle directory -// the version parameter is only used for framework bundles -function contentsFolderPath(product, version) -{ - var path = wrapperName(product); - - if (product.type.contains("frameworkbundle")) - path += "/Versions/" + (version || frameworkVersion(product)); - else if (!isShallowBundle(product)) - path += "/Contents"; - - return path; -} - -// DOCUMENTATION_FOLDER_PATH -// Directory for documentation files -// the version parameter is only used for framework bundles -function documentationFolderPath(product, localizationName, version) -{ - var path = localizedResourcesFolderPath(product, localizationName, version); - if (!product.type.contains("inapppurchase")) - path += "/Documentation"; - return path; -} - -// EXECUTABLES_FOLDER_PATH -// Destination directory for auxiliary executables -// the version parameter is only used for framework bundles -function executablesFolderPath(product, localizationName, version) -{ - if (product.type.contains("frameworkbundle")) - return localizedResourcesFolderPath(product, localizationName, version); - else - return _contentsFolderSubDirPath(product, "Executables", version); -} - -// EXECUTABLE_FOLDER_PATH -// Destination directory for the primary executable -// the version parameter is only used for framework bundles -function executableFolderPath(product, version) -{ - var path = contentsFolderPath(product, version); - if (!isShallowBundle(product) - && !product.type.contains("frameworkbundle") - && !product.type.contains("inapppurchase")) - path += "/MacOS"; - - return path; -} - -// EXECUTABLE_PATH -// Path to the bundle's primary executable file -// the version parameter is only used for framework bundles -function executablePath(product, version) -{ - return executableFolderPath(product, version) + "/" + productName(product); -} - -// FRAMEWORK_VERSION -// Major version number or letter corresponding to the bundle version -function frameworkVersion(product) -{ - if (!product.type.contains("frameworkbundle")) - throw "Product type must be a frameworkbundle, was " + product.type; - - var n = parseInt(product.version, 10); - return isNaN(n) ? 'A' : n; -} - -// FRAMEWORKS_FOLDER_PATH -// Directory containing frameworks used by the bundle's executables -// the version parameter is only used for framework bundles -function frameworksFolderPath(product, version) -{ - return _contentsFolderSubDirPath(product, "Frameworks", version); -} - -// INFOPLIST_PATH -// Path to the bundle's main information property list -// the version parameter is only used for framework bundles -function infoPlistPath(product, version) -{ - var path; - if (product.type.contains("application")) - path = ".tmp/" + product.name; - else if (product.type.contains("frameworkbundle")) - path = unlocalizedResourcesFolderPath(product, version); - else if (product.type.contains("inapppurchase")) - path = wrapperName(product); - else - path = contentsFolderPath(product, version); - - return path + "/" + _infoFileNames(product)[0]; -} - -// INFOSTRINGS_PATH -// Path to the strings file corresponding to the bundle's main information property list -// the version parameter is only used for framework bundles -function infoStringsPath(product, localizationName, version) -{ - return localizedResourcesFolderPath(product, localizationName, version) + "/" + _infoFileNames(product)[1]; -} - -// LOCALIZED_RESOURCES_FOLDER_PATH -// Path to the bundle's resources directory for the given localization -// the version parameter is only used for framework bundles -function localizedResourcesFolderPath(product, localizationName, version) -{ - if (typeof localizationName !== "string") - throw("'" + localizationName + "' is not a valid localization name"); - - return unlocalizedResourcesFolderPath(product, version) + "/" + localizationName + ".lproj"; -} - -// PKGINFO_PATH -// Path to the bundle's PkgInfo file -function pkgInfoPath(product) -{ - var path = (product.type.contains("frameworkbundle")) - ? wrapperName(product) - : contentsFolderPath(product); - return path + "/PkgInfo"; -} - -// PLUGINS_FOLDER_PATH -// Directory containing plugins used by the bundle's executables -// the version parameter is only used for framework bundles -function pluginsFolderPath(product, version) -{ - if (product.type.contains("frameworkbundle")) - return unlocalizedResourcesFolderPath(product, version); - - return _contentsFolderSubDirPath(product, "PlugIns", version); -} - -// PRIVATE_HEADERS_FOLDER_PATH -// Directory containing private header files for the framework -// the version parameter is only used for framework bundles -function privateHeadersFolderPath(product, version) -{ - return _contentsFolderSubDirPath(product, "PrivateHeaders", version); -} - -// PRODUCT_NAME -// The name of the product (in Xcode terms) which corresponds to the target name in QBS terms -function productName(product) -{ - return product.targetName; -} - -// PUBLIC_HEADERS_FOLDER_PATH -// Directory containing public header files for the framework -// the version parameter is only used for framework bundles -function publicHeadersFolderPath(product, version) -{ - return _contentsFolderSubDirPath(product, "Headers", version); -} - -// SCRIPTS_FOLDER_PATH -// Directory containing script files associated with the bundle -// the version parameter is only used for framework bundles -function scriptsFolderPath(product, version) -{ - return unlocalizedResourcesFolderPath(product, version) + "/Scripts"; -} - -// SHALLOW_BUNDLE -// Controls the presence or absence of the Contents, MacOS and Resources folders -// iOS tends to store the majority of files in its bundles in the main directory -function isShallowBundle(product) -{ - return product.moduleProperty("qbs", "targetOS").contains("ios") - && product.type.contains("applicationbundle"); -} - -// SHARED_FRAMEWORKS_FOLDER_PATH -// Directory containing sub-frameworks that may be shared with other applications -// the version parameter is only used for framework bundles -function sharedFrameworksFolderPath(product, version) -{ - return _contentsFolderSubDirPath(product, "SharedFrameworks", version); -} - -// SHARED_SUPPORT_FOLDER_PATH -// Directory containing supporting files that may be shared with other applications -// the version parameter is only used for framework bundles -function sharedSupportFolderPath(product, version) -{ - if (product.type.contains("frameworkbundle")) - return unlocalizedResourcesFolderPath(product, version); - - return _contentsFolderSubDirPath(product, "SharedSupport", version); -} - -// UNLOCALIZED_RESOURCES_FOLDER_PATH -// Directory containing resource files that are not specific to any given localization -function unlocalizedResourcesFolderPath(product, version) -{ - if (isShallowBundle(product)) - return contentsFolderPath(product, version); - - return _contentsFolderSubDirPath(product, "Resources", version); -} - -// VERSIONPLIST_PATH -// Directory containing the bundle's version.plist file -// the version parameter is only used for framework bundles -function versionPlistPath(product, version) -{ - var path = (product.type.contains("frameworkbundle")) - ? unlocalizedResourcesFolderPath(product, version) - : contentsFolderPath(product, version); - return path + "/version.plist"; -} - -// WRAPPER_EXTENSION -// The file extension of the bundle directory - app, framework, bundle, etc. -function wrapperExtension(product) -{ - if (product.type.contains("applicationbundle")) { - return "app"; - } else if (product.type.contains("frameworkbundle")) { - return "framework"; - } else if (product.type.contains("inapppurchase")) { - return ""; - } else if (product.type.contains("bundle")) { - // Potentially: kext, prefPane, qlgenerator, saver, mdimporter, or a custom extension - var bundleExtension = ModUtils.moduleProperty(product, "bundleExtension"); - - // default to bundle if none was specified by the user - return bundleExtension || "bundle"; - } else { - throw ("Unsupported bundle product type " + product.type + ". " - + "Must be in {applicationbundle, frameworkbundle, bundle, inapppurchase}."); - } -} - -// WRAPPER_NAME -// The name of the bundle directory - the product name plus the bundle extension -function wrapperName(product) -{ - return productName(product) + wrapperSuffix(product); -} - -// WRAPPER_SUFFIX -// The suffix of the bundle directory, that is, its extension prefixed with a '.', -// or an empty string if the extension is also an empty string -function wrapperSuffix(product) -{ - var ext = wrapperExtension(product); - return ext ? ("." + ext) : ""; -} - -// Private helper functions - -// In-App purchase content bundles use virtually no subfolders of Contents; -// this is a convenience method to avoid repeating that logic everywhere -// the version parameter is only used for framework bundles -function _contentsFolderSubDirPath(product, subdirectoryName, version) -{ - var path = contentsFolderPath(product, version); - if (!product.type.contains("inapppurchase")) - path += "/" + subdirectoryName; - return path; -} - -// Returns a list containing the filename of the bundle's main information -// property list and filename of the corresponding strings file -function _infoFileNames(product) -{ - if (product.type.contains("inapppurchase")) - return ["ContentInfo.plist", "ContentInfo.strings"]; - else - return ["Info.plist", "InfoPlist.strings"]; -} diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js index 6fd5c49de..9045da4e6 100644 --- a/share/qbs/modules/cpp/gcc.js +++ b/share/qbs/modules/cpp/gcc.js @@ -1,7 +1,7 @@ -var Windows = loadFile("windows.js"); +var PathTools = loadExtension("qbs.PathTools"); +var WindowsUtils = loadExtension("qbs.WindowsUtils"); -function linkerFlags(product, inputs) -{ +function linkerFlags(product, inputs) { var libraryPaths = ModUtils.moduleProperties(product, 'libraryPaths'); var dynamicLibraries = ModUtils.moduleProperties(product, "dynamicLibraries"); var staticLibraries = ModUtils.modulePropertiesFromArtifacts(product, inputs.staticlibrary, 'cpp', 'staticLibraries'); @@ -38,8 +38,8 @@ function linkerFlags(product, inputs) prefix = ModUtils.moduleProperty(product, "staticLibraryPrefix"); suffixes = ModUtils.moduleProperty(product, "supportedStaticLibrarySuffixes"); for (i in staticLibraries) { - if (isLibraryFileName(product, FileInfo.fileName(staticLibraries[i]), prefix, suffixes, - false)) { + if (PathTools.isLibraryFileName(product, FileInfo.fileName(staticLibraries[i]), prefix, + suffixes, false)) { args.push(staticLibraries[i]); } else { args.push('-l' + staticLibraries[i]); @@ -49,8 +49,8 @@ function linkerFlags(product, inputs) prefix = ModUtils.moduleProperty(product, "dynamicLibraryPrefix"); suffix = ModUtils.moduleProperty(product, "dynamicLibrarySuffix"); for (i in dynamicLibraries) { - if (isLibraryFileName(product, FileInfo.fileName(dynamicLibraries[i]), prefix, [suffix], - true)) { + if (PathTools.isLibraryFileName(product, FileInfo.fileName(dynamicLibraries[i]), prefix, + [suffix], true)) { args.push(dynamicLibraries[i]); } else { args.push('-l' + dynamicLibraries[i]); @@ -92,21 +92,6 @@ function linkerFlags(product, inputs) return args; } -// Returns whether the string looks like a library filename -function isLibraryFileName(product, fileName, prefix, suffixes, isShared) -{ - var suffix, i; - var os = product.moduleProperty("qbs", "targetOS"); - for (i = 0; i < suffixes.length; ++i) { - suffix = suffixes[i]; - if (isShared && os.contains("unix") && !os.contains("darwin")) - suffix += "(\\.[0-9]+){0,3}"; - if (fileName.match("^" + prefix + ".+?\\" + suffix + "$")) - return true; - } - return false; -} - // for compiler AND linker function configFlags(config) { var args = []; @@ -143,15 +128,9 @@ function configFlags(config) { return args; } -function removePrefixAndSuffix(str, prefix, suffix) -{ - return str.substr(prefix.length, str.length - prefix.length - suffix.length); -} - // ### what we actually need here is something like product.usedFileTags // that contains all fileTags that have been used when applying the rules. -function additionalCompilerFlags(product, input, output) -{ +function additionalCompilerFlags(product, input, output) { var includePaths = ModUtils.moduleProperties(input, 'includePaths'); var frameworkPaths = ModUtils.moduleProperties(product, 'frameworkPaths'); var systemIncludePaths = ModUtils.moduleProperties(input, 'systemIncludePaths'); @@ -212,7 +191,7 @@ function additionalCompilerFlags(product, input, output) var minimumWindowsVersion = ModUtils.moduleProperty(product, "minimumWindowsVersion"); if (minimumWindowsVersion && product.moduleProperty("qbs", "targetOS").contains("windows")) { - var hexVersion = Windows.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); + var hexVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); if (hexVersion) { var versionDefs = [ 'WINVER', '_WIN32_WINNT', '_WIN32_WINDOWS' ]; for (i in versionDefs) @@ -247,28 +226,8 @@ function additionalCompilerAndLinkerFlags(product) { return args } -function majorVersion(version, defaultValue) -{ - var n = parseInt(product.version, 10); - return isNaN(n) ? defaultValue : n; -} - -function soname(product, outputFilePath) -{ - var outputFileName = FileInfo.fileName(outputFilePath); - if (product.version) { - var major = majorVersion(product.version); - if (major) { - return outputFileName.substr(0, outputFileName.length - product.version.length) - + major; - } - } - return outputFileName; -} - // Returns the GCC language name equivalent to fileTag, accepted by the -x argument -function languageName(fileTag) -{ +function languageName(fileTag) { if (fileTag === 'c') return 'c'; else if (fileTag === 'cpp') @@ -283,8 +242,7 @@ function languageName(fileTag) return 'assembler-with-cpp'; } -function prepareCompiler(project, product, inputs, outputs, input, output) -{ +function prepareCompiler(project, product, inputs, outputs, input, output) { var i, c; // Determine which C-language we're compiling @@ -342,7 +300,7 @@ function prepareCompiler(project, product, inputs, outputs, input, output) } var cmd = new Command(compilerPath, args); - cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + FileInfo.fileName(input.filePath); + cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + input.fileName; if (pchOutput) cmd.description += ' (' + tag + ')'; cmd.highlight = "compiler"; @@ -351,13 +309,11 @@ function prepareCompiler(project, product, inputs, outputs, input, output) } // Concatenates two arrays of library names and preserves the dependency order that ld needs. -function concatLibs(libs, deplibs) -{ +function concatLibs(libs, deplibs) { var r = []; var s = {}; - function addLibs(lst) - { + function addLibs(lst) { for (var i = lst.length; --i >= 0;) { var lib = lst[i]; if (!s[lib]) { diff --git a/share/qbs/modules/cpp/ios-gcc.qbs b/share/qbs/modules/cpp/ios-gcc.qbs index fae3a21fa..aeb1ad767 100644 --- a/share/qbs/modules/cpp/ios-gcc.qbs +++ b/share/qbs/modules/cpp/ios-gcc.qbs @@ -1,8 +1,8 @@ import qbs 1.0 +import qbs.BundleTools +import qbs.DarwinTools import qbs.File import qbs.ModUtils -import 'darwin-tools.js' as DarwinTools -import 'bundle-tools.js' as BundleTools DarwinGCC { condition: qbs.hostOS.contains('osx') && qbs.targetOS.contains('ios') && qbs.toolchain.contains('gcc') diff --git a/share/qbs/modules/cpp/msvc.js b/share/qbs/modules/cpp/msvc.js index 7ae2e6d55..3eb805556 100644 --- a/share/qbs/modules/cpp/msvc.js +++ b/share/qbs/modules/cpp/msvc.js @@ -1,7 +1,4 @@ -var Windows = loadFile("windows.js"); - -function prepareCompiler(product, input, outputs, platformDefines, defines, includePaths, systemIncludePaths, cFlags, cxxFlags) -{ +function prepareCompiler(product, input, outputs, platformDefines, defines, includePaths, systemIncludePaths, cFlags, cxxFlags) { var i; var optimization = ModUtils.moduleProperty(input, "optimization") var debugInformation = ModUtils.moduleProperty(input, "debugInformation") @@ -47,7 +44,7 @@ function prepareCompiler(product, input, outputs, platformDefines, defines, incl var minimumWindowsVersion = ModUtils.moduleProperty(product, "minimumWindowsVersion"); if (minimumWindowsVersion) { - var hexVersion = Windows.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); + var hexVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); if (hexVersion) { var versionDefs = [ 'WINVER', '_WIN32_WINNT', '_WIN32_WINDOWS' ]; for (i in versionDefs) { @@ -85,7 +82,7 @@ function prepareCompiler(product, input, outputs, platformDefines, defines, incl // use PCH var pchHeaderName = FileInfo.toWindowsSeparators(pch); var pchName = FileInfo.toWindowsSeparators(ModUtils.moduleProperty(product, "precompiledHeaderDir") - + "\\.obj\\" + product.name + "\\" + product.name + "_" + tag + ".pch"); + + "\\.obj\\" + product.name + "_" + tag + ".pch"); args.push("/FI" + pchHeaderName); args.push("/Yu" + pchHeaderName); args.push("/Fp" + pchName); @@ -105,22 +102,21 @@ function prepareCompiler(product, input, outputs, platformDefines, defines, incl args = wrapperArgs.concat(args); } var cmd = new Command(compilerPath, args) - cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + FileInfo.fileName(input.filePath); + cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + input.fileName; if (pchOutput) cmd.description += ' (' + tag + ')'; cmd.highlight = "compiler"; - cmd.workingDirectory = product.buildDirectory + "/.obj/" + product.name; + cmd.workingDirectory = product.buildDirectory + "/.obj"; cmd.responseFileUsagePrefix = '@'; // cl.exe outputs the cpp file name. We filter that out. cmd.stdoutFilterFunction = "function(output) {"; cmd.stdoutFilterFunction += "return output.replace(/" - + FileInfo.fileName(input.filePath) + "\\r\\n/g, '');"; + + input.fileName + "\\r\\n/g, '');"; cmd.stdoutFilterFunction += "}"; return cmd; } -function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, staticLibraries, linkerFlags) -{ +function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, staticLibraries, linkerFlags) { var i; var linkDLL = (outputs.dynamiclibrary ? true : false) var primaryOutput = (linkDLL ? outputs.dynamiclibrary[0] : outputs.application[0]) @@ -147,7 +143,8 @@ function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, } if (minimumWindowsVersion) { - var subsystemVersion = Windows.getWindowsVersionInFormat(minimumWindowsVersion, 'subsystem'); + var subsystemVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, + 'subsystem'); if (subsystemVersion) { subsystemSwitch += ',' + subsystemVersion; args.push('/OSVERSION:' + subsystemVersion); @@ -165,7 +162,7 @@ function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, linkerOutputNativeFilePath = FileInfo.toWindowsSeparators( FileInfo.path(primaryOutput.filePath) + "/intermediate." - + FileInfo.fileName(primaryOutput.filePath)); + + primaryOutput.fileName); manifestFileName = linkerOutputNativeFilePath + ".manifest"; args.push('/MANIFEST', '/MANIFESTFILE:' + manifestFileName) } else { @@ -202,15 +199,13 @@ function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, var commands = []; var cmd = new Command(product.moduleProperty("cpp", "linkerPath"), args) - cmd.description = 'linking ' + FileInfo.fileName(primaryOutput.filePath) + cmd.description = 'linking ' + primaryOutput.fileName; cmd.highlight = 'linker'; cmd.workingDirectory = FileInfo.path(primaryOutput.filePath) cmd.responseFileUsagePrefix = '@'; - cmd.stdoutFilterFunction = - function(output) - { - return output.replace(/^ +Creating library.*\r\n$/, ""); - }; + cmd.stdoutFilterFunction = function(output) { + return output.replace(/^ +Creating library.*\r\n$/, ""); + }; commands.push(cmd); if (generateManifestFiles) { @@ -228,7 +223,7 @@ function prepareLinker(product, inputs, outputs, libraryPaths, dynamicLibraries, "/outputresource:" + outputNativeFilePath + ";#" + (linkDLL ? "2" : "1") ] cmd = new Command("mt.exe", args) - cmd.description = 'embedding manifest into ' + FileInfo.fileName(primaryOutput.filePath) + cmd.description = 'embedding manifest into ' + primaryOutput.fileName; cmd.highlight = 'linker'; cmd.workingDirectory = FileInfo.path(primaryOutput.filePath) commands.push(cmd); diff --git a/share/qbs/modules/cpp/windows-mingw.qbs b/share/qbs/modules/cpp/windows-mingw.qbs index 5e6fb40ae..ea66d61f4 100644 --- a/share/qbs/modules/cpp/windows-mingw.qbs +++ b/share/qbs/modules/cpp/windows-mingw.qbs @@ -1,7 +1,6 @@ import qbs 1.0 -import qbs.FileInfo import qbs.ModUtils -import "windows.js" as Windows +import qbs.WindowsUtils GenericGCC { condition: qbs.targetOS.contains("windows") && qbs.toolchain.contains("mingw") @@ -13,7 +12,7 @@ GenericGCC { dynamicLibrarySuffix: ".dll" executableSuffix: ".exe" windowsApiCharacterSet: "unicode" - platformDefines: base.concat(Windows.characterSetDefines(windowsApiCharacterSet)) + platformDefines: base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet)) compilerDefines: ['__GNUC__', 'WIN32', '_WIN32'] property string windresName: 'windres' @@ -40,7 +39,7 @@ GenericGCC { inputs: ["rc"] Artifact { - fileName: ".obj/" + product.name + "/" + input.baseDir.replace(':', '') + "/" + input.completeBaseName + "_res.o" + fileName: ".obj/" + input.baseDir.replace(':', '') + "/" + input.completeBaseName + "_res.o" fileTags: ["obj"] } @@ -70,7 +69,7 @@ GenericGCC { args = args.concat(['-i', input.filePath, '-o', output.filePath]); var cmd = new Command(ModUtils.moduleProperty(product, "windresPath"), args); - cmd.description = 'compiling ' + FileInfo.fileName(input.filePath); + cmd.description = 'compiling ' + input.fileName; cmd.highlight = 'compiler'; return cmd; } diff --git a/share/qbs/modules/cpp/windows-msvc.qbs b/share/qbs/modules/cpp/windows-msvc.qbs index cd4ac01a2..2af9065a6 100644 --- a/share/qbs/modules/cpp/windows-msvc.qbs +++ b/share/qbs/modules/cpp/windows-msvc.qbs @@ -2,9 +2,9 @@ import qbs 1.0 import qbs.File import qbs.FileInfo import qbs.ModUtils -import 'windows.js' as Windows +import qbs.PathTools +import qbs.WindowsUtils import 'msvc.js' as MSVC -import 'path-tools.js' as PathTools CppModule { condition: qbs.hostOS.contains('windows') && qbs.targetOS.contains('windows') && qbs.toolchain.contains('msvc') @@ -12,7 +12,7 @@ CppModule { id: module windowsApiCharacterSet: "unicode" - platformDefines: base.concat(Windows.characterSetDefines(windowsApiCharacterSet)) + platformDefines: base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet)) compilerDefines: ['_WIN32'] warningLevel: "default" compilerName: "cl.exe" @@ -20,7 +20,6 @@ CppModule { property bool generateManifestFiles: true property path toolchainInstallPath - property path windowsSDKPath // ### remove in 1.3 architecture: qbs.architecture staticLibraryPrefix: "" dynamicLibraryPrefix: "" @@ -38,12 +37,12 @@ CppModule { fileName: { var completeBaseName = FileInfo.completeBaseName(product.moduleProperty("cpp", "cPrecompiledHeader")); - return ".obj/" + product.name + "/" + completeBaseName + '_c.obj' + return ".obj/" + completeBaseName + '_c.obj' } } Artifact { fileTags: ['c_pch'] - fileName: ".obj/" + product.name + "/" + product.name + '_c.pch' + fileName: ".obj/" + product.name + '_c.pch' } prepare: { var platformDefines = ModUtils.moduleProperty(input, 'platformDefines'); @@ -65,12 +64,12 @@ CppModule { fileName: { var completeBaseName = FileInfo.completeBaseName(product.moduleProperty("cpp", "cxxPrecompiledHeader")); - return ".obj/" + product.name + "/" + completeBaseName + '_cpp.obj' + return ".obj/" + completeBaseName + '_cpp.obj' } } Artifact { fileTags: ['cpp_pch'] - fileName: ".obj/" + product.name + "/" + product.name + '_cpp.pch' + fileName: ".obj/" + product.name + '_cpp.pch' } prepare: { var platformDefines = ModUtils.moduleProperty(input, 'platformDefines'); @@ -91,7 +90,7 @@ CppModule { Artifact { fileTags: ['obj'] - fileName: ".obj/" + product.name + "/" + input.baseDir.replace(':', '') + "/" + input.fileName + ".obj" + fileName: ".obj/" + input.baseDir.replace(':', '') + "/" + input.fileName + ".obj" } prepare: { @@ -114,7 +113,7 @@ CppModule { usings: ['staticlibrary', 'dynamiclibrary_import'] Artifact { fileTags: ["application"] - fileName: product.destinationDirectory + "/" + PathTools.applicationFilePath() + fileName: product.destinationDirectory + "/" + PathTools.applicationFilePath(product) } prepare: { @@ -135,12 +134,12 @@ CppModule { Artifact { fileTags: ["dynamiclibrary"] - fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFilePath() + fileName: product.destinationDirectory + "/" + PathTools.dynamicLibraryFilePath(product) } Artifact { fileTags: ["dynamiclibrary_import"] - fileName: product.destinationDirectory + "/" + PathTools.importLibraryFilePath() + fileName: product.destinationDirectory + "/" + PathTools.importLibraryFilePath(product) alwaysUpdated: false } @@ -162,7 +161,7 @@ CppModule { Artifact { fileTags: ["staticlibrary"] - fileName: product.destinationDirectory + "/" + PathTools.staticLibraryFilePath() + fileName: product.destinationDirectory + "/" + PathTools.staticLibraryFilePath(product) cpp.staticLibraries: { var result = [] for (var i in inputs.staticlibrary) { @@ -184,7 +183,7 @@ CppModule { args.push(fileName) } var cmd = new Command("lib.exe", args); - cmd.description = 'creating ' + FileInfo.fileName(output.filePath) + cmd.description = 'creating ' + output.fileName; cmd.highlight = 'linker'; cmd.workingDirectory = FileInfo.path(output.filePath) cmd.responseFileUsagePrefix = '@'; @@ -201,7 +200,7 @@ CppModule { inputs: ["rc"] Artifact { - fileName: ".obj/" + product.name + "/" + input.baseDir.replace(':', '') + "/" + input.completeBaseName + ".res" + fileName: ".obj/" + input.baseDir.replace(':', '') + "/" + input.completeBaseName + ".res" fileTags: ["obj"] } @@ -231,7 +230,7 @@ CppModule { args = args.concat(['/fo', output.filePath, input.filePath]); var cmd = new Command('rc', args); - cmd.description = 'compiling ' + FileInfo.fileName(input.filePath); + cmd.description = 'compiling ' + input.fileName; cmd.highlight = 'compiler'; // Remove the first two lines of stdout. That's the logo. diff --git a/share/qbs/modules/ib/IBModule.qbs b/share/qbs/modules/ib/IBModule.qbs index 21117103d..c8037f243 100644 --- a/share/qbs/modules/ib/IBModule.qbs +++ b/share/qbs/modules/ib/IBModule.qbs @@ -1,10 +1,10 @@ import qbs 1.0 +import qbs.BundleTools +import qbs.DarwinTools import qbs.FileInfo import qbs.ModUtils import qbs.Process import qbs.PropertyList -import "../cpp/bundle-tools.js" as BundleTools -import "../cpp/darwin-tools.js" as DarwinTools Module { condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin") @@ -126,7 +126,7 @@ Module { args.push(input.filePath); var cmd = new Command("ibtool", args); - cmd.description = 'ibtool ' + FileInfo.fileName(input.filePath); + cmd.description = 'ibtool ' + input.fileName; // Also display the language name of the XIB being compiled if it has one var localizationKey = DarwinTools.localizationKey(input.filePath); diff --git a/share/qbs/modules/nodejs/NodeJS.qbs b/share/qbs/modules/nodejs/NodeJS.qbs new file mode 100644 index 000000000..a18d5d98b --- /dev/null +++ b/share/qbs/modules/nodejs/NodeJS.qbs @@ -0,0 +1,56 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.ModUtils + +Module { + // JavaScript files which have been "processed" - currently this simply means "copied to output + // directory" but might later include minification and obfuscation processing + additionalProductTypes: ["nodejs_processed_js"] + + property path applicationFile + PropertyOptions { + name: "applicationFile" + description: "file whose corresponding output will be executed when running the Node.js app" + } + + setupRunEnvironment: { + var v = new ModUtils.EnvironmentVariable("NODE_PATH", qbs.pathListSeparator, qbs.hostOS.contains("windows")); + // can't use product.buildDirectory here, but RunEnvironment always sets the working + // directory to the directory containing the target file so we can exploit this for now + v.prepend("."); + v.set(); + } + + FileTagger { + patterns: ["*.js"] + fileTags: ["js"] + } + + Rule { + inputs: ["js"] + + outputArtifacts: { + var tags = ["nodejs_processed_js"]; + if (input.fileTags.contains("application_js") || + product.moduleProperty("nodejs", "applicationFile") === input.filePath) + tags.push("application"); + + return [{ + filePath: product.destinationDirectory + '/' + input.fileName, + fileTags: tags + }]; + } + + outputFileTags: ["nodejs_processed_js", "application"] + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "copying " + input.fileName; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } +} diff --git a/share/qbs/modules/nsis/NSISModule.qbs b/share/qbs/modules/nsis/NSISModule.qbs index f0756178e..ad8b84238 100755 --- a/share/qbs/modules/nsis/NSISModule.qbs +++ b/share/qbs/modules/nsis/NSISModule.qbs @@ -70,10 +70,23 @@ Module { } validate: { + var validator = new ModUtils.PropertyValidator("nsis"); + // Only *require* the toolchain install path on Windows // On other (Unix-like) operating systems it'll probably be in the PATH - if (qbs.hostOS.contains("windows") && !toolchainInstallPath) - throw "nsis.toolchainInstallPath is not defined. Set nsis.toolchainInstallPath in your profile."; + if (qbs.targetOS.contains("windows")) + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addVersionValidator("version", version, 2, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.addRangeValidator("versionBuild", versionBuild, 0); + validator.validate(); } setupBuildEnvironment: { @@ -180,7 +193,7 @@ Module { var inputFileNames = []; for (i in inputs.nsi) { - inputFileNames.push(FileInfo.fileName(inputs.nsi[i].filePath)); + inputFileNames.push(inputs.nsi[i].fileName); if (product.moduleProperty("qbs", "hostOS").contains("windows")) { args.push(FileInfo.toWindowsSeparators(inputs.nsi[i].filePath)); } else { diff --git a/share/qbs/modules/qbs/common.qbs b/share/qbs/modules/qbs/common.qbs index 76ec20b9b..0425d70d5 100644 --- a/share/qbs/modules/qbs/common.qbs +++ b/share/qbs/modules/qbs/common.qbs @@ -1,5 +1,6 @@ import qbs 1.0 import qbs.FileInfo +import qbs.ModUtils Module { property string buildVariant: "debug" @@ -8,10 +9,10 @@ Module { property string optimization: (buildVariant == "debug" ? "none" : "fast") property stringList hostOS: getHostOS() property string hostOSVersion: { - if (hostOS.contains("osx")) { + if (hostOS && hostOS.contains("osx")) { return getNativeSetting("/System/Library/CoreServices/ServerVersion.plist", "ProductVersion") || getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductVersion"); - } else if (hostOS.contains("windows")) { + } else if (hostOS && hostOS.contains("windows")) { var version = getNativeSetting("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentVersion"); return version + "." + hostOSBuildVersion; } @@ -38,7 +39,11 @@ Module { property stringList toolchain property string architecture - property string endianness + property string endianness: { + if (["x86", "x86_64"].contains(architecture)) + return "little"; + } + PropertyOptions { name: "endianness" allowedValues: ["big", "little", "mixed"] @@ -63,34 +68,34 @@ Module { } validate: { - if (!architecture) { - throw new Error("qbs.architecture is not set. " - + "You might want to re-run 'qbs setup-toolchains'."); - } + var validator = new ModUtils.PropertyValidator("qbs"); + validator.setRequiredProperty("architecture", architecture, + "you might want to re-run 'qbs-setup-toolchains'"); + validator.setRequiredProperty("hostOS", hostOS); + validator.setRequiredProperty("targetOS", targetOS); + if (hostOS && (hostOS.contains("windows") || hostOS.contains("osx"))) { + validator.setRequiredProperty("hostOSVersion", hostOSVersion, + "could not detect host operating system version; " + + "verify that system files and registry keys have not " + + "been modified."); + if (hostOSVersion) + validator.addVersionValidator("hostOSVersion", hostOSVersion, 2, 4); - var canonicalArch = canonicalArchitecture(architecture); - if (architecture !== canonicalArch) { - throw "qbs.architecture '" + architecture + "' is invalid. " + - "You must use the canonical name '" + canonicalArch + "'"; + validator.setRequiredProperty("hostOSBuildVersion", hostOSBuildVersion, + "could not detect host operating system build version; " + + "verify that system files or registry have not been " + + "tampered with."); } - if (hostOS.contains("windows") || hostOS.contains("osx")) { - if (!hostOSVersion) { - throw "Could not detect host operating system version; " + - "verify that system files or registry have not been " + - "tampered with."; - } + validator.addCustomValidator("architecture", architecture, function (value) { + return architecture === canonicalArchitecture(architecture); + }, "'" + architecture + "' is invalid. You must use the canonical name '" + + canonicalArchitecture(architecture) + "'"); - if (!/^[0-9]+(\.[0-9]+){1,3}$/.test(hostOSVersion)) { - throw "qbs.hostOSVersion is in an invalid format; it must be of the form x.y or " + - "x.y.z or x.y.z.w where x, y, z and w are positive integers."; - } + validator.addCustomValidator("endianness", endianness, function (value) { + return ["big", "little", "mixed"].indexOf(value) !== -1; + }, "must be in [big, little, mixed]"); - if (!hostOSBuildVersion) { - throw "Could not detect host operating system build version; " + - "verify that system files or registry have not been " + - "tampered with."; - } - } + validator.validate(); } } diff --git a/share/qbs/modules/typescript/TypeScriptModule.qbs b/share/qbs/modules/typescript/TypeScriptModule.qbs new file mode 100644 index 000000000..8f80b7ce6 --- /dev/null +++ b/share/qbs/modules/typescript/TypeScriptModule.qbs @@ -0,0 +1,266 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Process + +Module { + Depends { name: "nodejs" } + + additionalProductTypes: ["compiled_typescript"] + + property path toolchainInstallPath + property string version: { + var p = new Process(); + p.exec(compilerPath, ["--version"]); + var match = p.readStdOut().match(/^Version ([0-9]+(\.[0-9]+){1,3})\n$/); + if (match !== null) + return match[1]; + } + + 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 int versionBuild: versionParts[3] + + property string compilerName: "tsc" + property string compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + property string warningLevel: "normal" + PropertyOptions { + name: "warningLevel" + description: "pedantic to warn on expressions and declarations with an implied 'any' type" + allowedValues: ["normal", "pedantic"] + } + + property string targetVersion + PropertyOptions { + name: "targetVersion" + description: "ECMAScript target version" + allowedValues: ["ES3", "ES5"] + } + + property string moduleLoader + PropertyOptions { + name: "moduleLoader" + allowedValues: ["commonjs", "amd"] + } + + property bool stripComments: !qbs.debugInformation + PropertyOptions { + name: "stripComments" + description: "whether to remove comments from the generated output" + } + + property bool generateDeclarations: false + PropertyOptions { + name: "generateDeclarations" + description: "whether to generate corresponding .d.ts files during compilation" + } + + // In release mode, nodejs can/should default-enable minification and obfuscation, + // making the source maps useless, so these default settings work out fine + property bool generateSourceMaps: qbs.debugInformation + PropertyOptions { + name: "generateSourceMaps" + description: "whether to generate corresponding .map files during compilation" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the TypeScript compiler" + } + + property bool singleFile: false + PropertyOptions { + name: "singleFile" + description: "whether to compile all source files to a single output file" + } + + validate: { + var validator = new ModUtils.PropertyValidator("typescript"); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addVersionValidator("version", version, 4, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.addRangeValidator("versionBuild", versionBuild, 0); + validator.validate(); + } + + setupBuildEnvironment: { + if (toolchainInstallPath) { + var v = new ModUtils.EnvironmentVariable("PATH", qbs.pathListSeparator, qbs.hostOS.contains("windows")); + v.prepend(toolchainInstallPath); + v.set(); + } + } + + // TypeScript declaration files + FileTagger { + patterns: ["*.d.ts"] + fileTags: ["typescript_declaration"] + } + + // TypeScript source files + FileTagger { + patterns: ["*.ts"] + fileTags: ["typescript"] + } + + Rule { + id: typescriptCompiler + multiplex: true + inputs: ["typescript"] + usings: ["typescript_declaration"] + + outputArtifacts: { + var artifacts = []; + + if (product.moduleProperty("typescript", "singleFile")) { + var jsTags = ["js", "compiled_typescript"]; + + // We could check + // if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + // but since we're compiling to a single file there's no need to state it explicitly + jsTags.push("application_js"); + + var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths(".obj", product.targetName, "typescript", filePath + ".js")}); + artifacts.push({condition: product.moduleProperty("typescript", "generateDeclarations"), // ### QBS-412 + fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + artifacts.push({condition: product.moduleProperty("typescript", "generateSourceMaps"), // ### QBS-412 + fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } else { + for (var i = 0; i < inputs.typescript.length; ++i) { + var jsTags = ["js", "compiled_typescript"]; + if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + jsTags.push("application_js"); + + var filePath = FileInfo.joinPaths(product.destinationDirectory, FileInfo.baseName(inputs.typescript[i].filePath)); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths(".obj", product.targetName, "typescript", filePath + ".js")}); + artifacts.push({condition: product.moduleProperty("typescript", "generateDeclarations"), // ### QBS-412 + fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + artifacts.push({condition: product.moduleProperty("typescript", "generateSourceMaps"), // ### QBS-412 + fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } + } + + return artifacts; + } + + outputFileTags: { + var fileTags = ["js", "compiled_typescript"]; + if (product.moduleProperty("nodejs", "applicationFile")) + fileTags.push("application_js"); + if (product.moduleProperty("typescript", "generateDeclarations")) + fileTags.push("typescript_declaration"); + if (product.moduleProperty("typescript", "generateSourceMaps")) + fileTags.push("source_map"); + return fileTags; + } + + prepare: { + var i; + var args = []; + + var primaryOutput = outputs.compiled_typescript[0]; + + if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") { + args.push("--noImplicitAny"); + } + + var targetVersion = ModUtils.moduleProperty(product, "targetVersion"); + if (targetVersion) { + args.push("--target"); + args.push(targetVersion); + } + + var moduleLoader = ModUtils.moduleProperty(product, "moduleLoader"); + if (moduleLoader) { + if (ModUtils.moduleProperty(product, "singleFile")) { + throw("typescript.singleFile cannot be true when typescript.moduleLoader is set"); + } + + args.push("--module"); + args.push(moduleLoader); + } + + if (ModUtils.moduleProperty(product, "stripComments")) { + args.push("--removeComments"); + } + + if (ModUtils.moduleProperty(product, "generateDeclarations")) { + args.push("--declaration"); + } + + if (ModUtils.moduleProperty(product, "generateSourceMaps")) { + args.push("--sourcemap"); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "compilerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + args.push("--outDir"); + args.push(product.buildDirectory); + + if (ModUtils.moduleProperty(product, "singleFile")) { + args.push("--out"); + args.push(primaryOutput.filePath); + } + + if (inputs.typescript_declaration) { + for (i = 0; i < inputs.typescript_declaration.length; ++i) { + args.push(inputs.typescript_declaration[i].filePath); + } + } + + for (i = 0; i < inputs.typescript.length; ++i) { + args.push(inputs.typescript[i].filePath); + } + + var cmd, cmds = []; + + cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.description = "compiling " + (ModUtils.moduleProperty(product, "singleFile") + ? primaryOutput.fileName + : inputs.typescript.map(function(obj) { + return obj.fileName; }).join(", ")); + cmd.highlight = "compiler"; + cmds.push(cmd); + + // Move all the compiled TypeScript files to the proper intermediate directory + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.outDir = product.buildDirectory; + cmd.sourceCode = function() { + for (i = 0; i < outputs.compiled_typescript.length; ++i) { + var fp = outputs.compiled_typescript[i].filePath; + var originalFilePath = FileInfo.joinPaths(outDir, FileInfo.fileName(fp)); + File.copy(originalFilePath, fp); + File.remove(originalFilePath); + } + }; + cmds.push(cmd); + + return cmds; + } + } +} diff --git a/share/qbs/modules/wix/WiXModule.qbs b/share/qbs/modules/wix/WiXModule.qbs index 02642b874..39a56f5f8 100644 --- a/share/qbs/modules/wix/WiXModule.qbs +++ b/share/qbs/modules/wix/WiXModule.qbs @@ -105,19 +105,20 @@ Module { } validate: { - if (!toolchainInstallPath) - throw "wix.toolchainInstallPath is not defined. Set wix.toolchainInstallPath in your profile."; - - if (!toolchainInstallRoot) - throw "wix.toolchainInstallRoot is not defined. Set wix.toolchainInstallRoot in your profile." - - if (!version) - throw "wix.version is not defined. Set wix.version in your profile."; - - if (!/^[0-9]+(\.[0-9]+){1,3}$/.test(version)) { - throw "wix.version is in an invalid format; it must be of the form x.y.z.w " + - "where x, y, z and w are positive integers."; - } + var validator = new ModUtils.PropertyValidator("wix"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.setRequiredProperty("toolchainInstallRoot", toolchainInstallRoot); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addVersionValidator("version", version, 4, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.addRangeValidator("versionBuild", versionBuild, 0); + validator.validate(); } setupBuildEnvironment: { @@ -151,7 +152,7 @@ Module { Artifact { fileTags: ["wixobj"] - fileName: ".obj/" + product.name + "/" + input.baseDir.replace(':', '') + "/" + FileInfo.baseName(input.fileName) + ".wixobj" + fileName: ".obj/" + input.baseDir.replace(':', '') + "/" + FileInfo.baseName(input.fileName) + ".wixobj" } prepare: { @@ -279,7 +280,7 @@ Module { args.push(FileInfo.toWindowsSeparators(inputs.wxs[0].filePath)); var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); - cmd.description = "compiling " + FileInfo.fileName(inputs.wxs[0].filePath); + cmd.description = "compiling " + inputs.wxs[0].fileName; cmd.highlight = "compiler"; cmd.workingDirectory = FileInfo.path(output.filePath); return cmd; @@ -376,7 +377,7 @@ Module { } var cmd = new Command(ModUtils.moduleProperty(product, "linkerPath"), args); - cmd.description = "linking " + FileInfo.fileName(primaryOutput.filePath); + cmd.description = "linking " + primaryOutput.fileName; cmd.highlight = "linker"; cmd.workingDirectory = FileInfo.path(primaryOutput.filePath); return cmd; diff --git a/src/app/app.pri b/src/app/app.pri index 8dc6dc95b..3a86e5a0f 100644 --- a/src/app/app.pri +++ b/src/app/app.pri @@ -1,6 +1,12 @@ QT = core TEMPLATE = app -DESTDIR = ../../../bin +!isEmpty(QBS_APPS_DESTDIR):DESTDIR = $${QBS_APPS_DESTDIR} +else:DESTDIR = ../../../bin + +!isEmpty(QBS_APPS_RPATH_DIR) { + linux-*:QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,$${QBS_APPS_RPATH_DIR}\' + macx:QMAKE_LFLAGS += -Wl,-rpath,$${QBS_APPS_RPATH_DIR} +} CONFIG += console CONFIG -= app_bundle @@ -8,5 +14,6 @@ CONFIG -= app_bundle include($${PWD}/../lib/corelib/use_corelib.pri) include($${PWD}/shared/logging/logging.pri) -target.path = $${QBS_INSTALL_PREFIX}/bin +!isEmpty(QBS_APPS_INSTALL_DIR):target.path = $${QBS_APPS_INSTALL_DIR} +else:target.path = $${QBS_INSTALL_PREFIX}/bin INSTALLS += target diff --git a/src/app/apps.qbs b/src/app/apps.qbs index 56d8d3289..f3a87f81e 100644 --- a/src/app/apps.qbs +++ b/src/app/apps.qbs @@ -1,6 +1,7 @@ import qbs Project { + property string appInstallDir: "bin" references: [ "config/config.qbs", "config-ui/config-ui.qbs", diff --git a/src/app/apptemplate.qbs b/src/app/apptemplate.qbs index 2d87a0233..7ebe33b2d 100644 --- a/src/app/apptemplate.qbs +++ b/src/app/apptemplate.qbs @@ -13,7 +13,7 @@ Product { Group { fileTagsFilter: product.type qbs.install: true - qbs.installDir: "bin" + qbs.installDir: project.appInstallDir } Group { name: "logging" diff --git a/src/app/qbs-setup-qt/setupqt.cpp b/src/app/qbs-setup-qt/setupqt.cpp index b36272713..c7b1ea16d 100644 --- a/src/app/qbs-setup-qt/setupqt.cpp +++ b/src/app/qbs-setup-qt/setupqt.cpp @@ -180,6 +180,7 @@ QtEnvironment SetupQt::fetchEnvironment(const QString &qmakePath) qtEnvironment.binaryPath = pathQueryValue(queryOutput, "QT_INSTALL_BINS"); qtEnvironment.documentationPath = pathQueryValue(queryOutput, "QT_INSTALL_DOCS"); qtEnvironment.pluginPath = pathQueryValue(queryOutput, "QT_INSTALL_PLUGINS"); + qtEnvironment.qmlPath = pathQueryValue(queryOutput, "QT_INSTALL_QML"); qtEnvironment.qmlImportPath = pathQueryValue(queryOutput, "QT_INSTALL_IMPORTS"); qtEnvironment.qtVersion = QString::fromLocal8Bit(queryOutput.value("QT_VERSION")); diff --git a/src/app/qbs-setup-toolchains/probe.cpp b/src/app/qbs-setup-toolchains/probe.cpp index 63793dc63..4c35c4b65 100644 --- a/src/app/qbs-setup-toolchains/probe.cpp +++ b/src/app/qbs-setup-toolchains/probe.cpp @@ -250,9 +250,18 @@ void probe(Settings *settings) void createProfile(const QString &profileName, const QString &toolchainType, const QString &compilerFilePath, Settings *settings) { + QFileInfo compiler(compilerFilePath); + if (compilerFilePath == compiler.fileName() && !compiler.exists()) + compiler = QFileInfo(findExecutable(compilerFilePath)); + + if (!compiler.exists()) { + throw qbs::ErrorInfo(Tr::tr("Compiler '%1' not found") + .arg(compilerFilePath)); + } + QStringList toolchainTypes; if (toolchainType.isEmpty()) - toolchainTypes = toolchainTypeFromCompilerName(QFileInfo(compilerFilePath).fileName()); + toolchainTypes = toolchainTypeFromCompilerName(compiler.fileName()); else toolchainTypes = completeToolchainList(toolchainType); @@ -262,7 +271,7 @@ void createProfile(const QString &profileName, const QString &toolchainType, } if (toolchainTypes.contains(QLatin1String("gcc"))) - createGccProfile(compilerFilePath, settings, toolchainTypes, profileName); + createGccProfile(compiler.absoluteFilePath(), settings, toolchainTypes, profileName); else throw qbs::ErrorInfo(Tr::tr("Cannot create profile: Unknown toolchain type.")); } diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp index bea570aa3..dce270ef1 100644 --- a/src/lib/corelib/api/project.cpp +++ b/src/lib/corelib/api/project.cpp @@ -585,6 +585,14 @@ void ProjectPrivate::prepareChangeToProject() retrieveProjectData(m_projectData, internalProject); } +static bool productIsRunnable(const ResolvedProductConstPtr &product) +{ + return product->fileTags.contains("application") + || (product->fileTags.contains("applicationbundle") + && product->moduleProperties->qbsPropertyValue(QLatin1String("targetOS")) + .toStringList().contains(QLatin1String("darwin"))); +} + void ProjectPrivate::retrieveProjectData(ProjectData &projectData, const ResolvedProjectConstPtr &internalProject) { @@ -596,6 +604,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData, product.d->name = resolvedProduct->name; product.d->location = resolvedProduct->location; product.d->isEnabled = resolvedProduct->enabled; + product.d->isRunnable = productIsRunnable(resolvedProduct); foreach (const GroupPtr &resolvedGroup, resolvedProduct->groups) product.d->groups << createGroupDataFromGroup(resolvedGroup); if (resolvedProduct->enabled) { diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp index a5cc6093e..31ba1ef33 100644 --- a/src/lib/corelib/api/projectdata.cpp +++ b/src/lib/corelib/api/projectdata.cpp @@ -414,6 +414,12 @@ bool ProductData::isEnabled() const return d->isEnabled; } +bool ProductData::isRunnable() const +{ + QBS_ASSERT(isValid(), return false); + return d->isRunnable; +} + bool operator==(const ProductData &lhs, const ProductData &rhs) { return lhs.name() == rhs.name() diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h index ba0016d0c..1d7625b82 100644 --- a/src/lib/corelib/api/projectdata.h +++ b/src/lib/corelib/api/projectdata.h @@ -176,6 +176,7 @@ public: QList<TargetArtifact> targetArtifacts() const; QList<GroupData> groups() const; bool isEnabled() const; + bool isRunnable() const; private: QExplicitlySharedDataPointer<Internal::ProductDataPrivate> d; diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h index 2eda4d932..b0006e89a 100644 --- a/src/lib/corelib/api/projectdata_p.h +++ b/src/lib/corelib/api/projectdata_p.h @@ -84,6 +84,7 @@ public: QList<GroupData> groups; QList<TargetArtifact> targetArtifacts; bool isEnabled; + bool isRunnable; bool isValid; }; diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp index b0be216f8..386a1769d 100644 --- a/src/lib/corelib/api/runenvironment.cpp +++ b/src/lib/corelib/api/runenvironment.cpp @@ -129,6 +129,21 @@ int RunEnvironment::runShell() return system(command.toLocal8Bit().constData()); } +static QString findExecutable(const QStringList &fileNames) +{ + const QStringList path = QString::fromLocal8Bit(qgetenv("PATH")) + .split(HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + + foreach (const QString &fileName, fileNames) { + foreach (const QString &ppath, path) { + const QString fullPath = ppath + QLatin1Char('/') + fileName; + if (QFileInfo(fullPath).exists()) + return QDir::cleanPath(fullPath); + } + } + return QString(); +} + int RunEnvironment::runTarget(const QString &targetBin, const QStringList &arguments) { const QStringList targetOS = PropertyFinder().propertyValue( @@ -154,6 +169,15 @@ int RunEnvironment::runTarget(const QString &targetBin, const QStringList &argum } } + if (completeSuffix == QLatin1String("js")) { + // The Node.js binary is called nodejs on Debian/Ubuntu-family operating systems due to a + // conflict with another package containing a binary named node + targetExecutable = findExecutable(QStringList() + << QLatin1String("nodejs") + << QLatin1String("node")); + targetArguments.prepend(targetBin); + } + // Only check if the target is executable if we're not running it through another // known application such as msiexec or wine, as we can't check in this case anyways if (targetBin == targetExecutable && !QFileInfo(targetExecutable).isExecutable()) { @@ -166,6 +190,7 @@ int RunEnvironment::runTarget(const QString &targetBin, const QStringList &argum d->logger.qbsInfo() << Tr::tr("Starting target '%1'.").arg(QDir::toNativeSeparators(targetBin)); QProcess process; + process.setWorkingDirectory(QFileInfo(targetBin).absolutePath()); process.setProcessEnvironment(d->resolvedProduct->runEnvironment); process.setProcessChannelMode(QProcess::ForwardedChannels); process.start(targetExecutable, targetArguments); diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp index 024decbc7..283958220 100644 --- a/src/lib/corelib/buildgraph/executor.cpp +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -791,8 +791,10 @@ void Executor::runTransformer(const TransformerPtr &transformer) for (; it != transformer->outputs.end(); ++it) { Artifact *output = *it; QDir outDir = QFileInfo(output->filePath()).absoluteDir(); - if (!outDir.exists()) - outDir.mkpath(QLatin1String(".")); + if (!outDir.exists() && !outDir.mkpath(QLatin1String("."))) { + throw ErrorInfo(tr("Failed to create directory '%1'.") + .arg(QDir::toNativeSeparators(outDir.absolutePath()))); + } } } diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp index 7cbcbf997..d80c10432 100644 --- a/src/lib/corelib/buildgraph/rulesapplicator.cpp +++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp @@ -307,7 +307,7 @@ Artifact *RulesApplicator::createOutputArtifactFromRuleArtifact( QScriptValue scriptValue = engine()->evaluate(ruleArtifact->fileName); if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) throw ErrorInfo(Tr::tr("Error in Rule.Artifact fileName: ") + scriptValue.toString()); - QString outputPath = scriptValue.toString(); + QString outputPath = FileInfo::resolvePath(m_product->buildDirectory(), scriptValue.toString()); return createOutputArtifact(outputPath, ruleArtifact->fileTags, ruleArtifact->alwaysUpdated, inputArtifacts); } @@ -428,7 +428,8 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValu const ArtifactSet &inputArtifacts) { QBS_CHECK(obj.isObject()); - const QString filePath = obj.property(QLatin1String("filePath")).toVariant().toString(); + const QString filePath = FileInfo::resolvePath(m_product->buildDirectory(), + obj.property(QLatin1String("filePath")).toVariant().toString()); const FileTags fileTags = FileTags::fromStringList( obj.property(QLatin1String("fileTags")).toVariant().toStringList()); const QVariant alwaysUpdatedVar = obj.property(QLatin1String("alwaysUpdated")).toVariant(); diff --git a/src/lib/corelib/buildgraph/transformer.cpp b/src/lib/corelib/buildgraph/transformer.cpp index 492feeb0c..c82962ed7 100644 --- a/src/lib/corelib/buildgraph/transformer.cpp +++ b/src/lib/corelib/buildgraph/transformer.cpp @@ -54,12 +54,7 @@ QScriptValue Transformer::translateFileConfig(QScriptEngine *scriptEngine, Artif { QScriptValue obj = scriptEngine->newObject(); ModuleProperties::init(obj, artifact); - - // ### undeprecate "fileName" and turn into a real file name in qbs 1.3 - ScriptEngine *qbsScriptEngine = static_cast<ScriptEngine *>(scriptEngine); - qbsScriptEngine->setDeprecatedProperty(obj, QLatin1String("fileName"), - QLatin1String("filePath"), artifact->filePath()); - + obj.setProperty(QLatin1String("fileName"), artifact->fileName()); obj.setProperty(QLatin1String("filePath"), artifact->filePath()); const QStringList fileTags = artifact->fileTags.toStringList(); obj.setProperty(QLatin1String("fileTags"), scriptEngine->toScriptValue(fileTags)); diff --git a/src/lib/corelib/language/asttools.cpp b/src/lib/corelib/language/asttools.cpp index f1aec7d9b..838bb2e31 100644 --- a/src/lib/corelib/language/asttools.cpp +++ b/src/lib/corelib/language/asttools.cpp @@ -41,7 +41,7 @@ QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid) return result; } -CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location) +CodeLocation toCodeLocation(const QString &filePath, const QbsQmlJS::AST::SourceLocation &location) { return CodeLocation(filePath, location.startLine, location.startColumn); } diff --git a/src/lib/corelib/language/asttools.h b/src/lib/corelib/language/asttools.h index 9a84c2ade..7bbbb583b 100644 --- a/src/lib/corelib/language/asttools.h +++ b/src/lib/corelib/language/asttools.h @@ -38,7 +38,7 @@ namespace qbs { namespace Internal { QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid); -CodeLocation toCodeLocation(const QString &filePath, QbsQmlJS::AST::SourceLocation location); +CodeLocation toCodeLocation(const QString &filePath, const QbsQmlJS::AST::SourceLocation &location); QString textOf(const QString &source, QbsQmlJS::AST::Node *node); QStringRef textRefOf(const QString &source, QbsQmlJS::AST::Node *node); diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index 02046e358..3ff60944b 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -109,6 +109,11 @@ static PropertyDeclaration nameProperty() return PropertyDeclaration(QLatin1String("name"), PropertyDeclaration::String); } +static PropertyDeclaration buildDirProperty() +{ + return PropertyDeclaration(QLatin1String("buildDirectory"), PropertyDeclaration::Path); +} + static PropertyDeclaration prepareScriptProperty() { PropertyDeclaration decl(QLatin1String("prepare"), PropertyDeclaration::Verbatim); @@ -155,10 +160,6 @@ void BuiltinDeclarations::addExportItem() void BuiltinDeclarations::addFileTaggerItem() { ItemDeclaration item(QLatin1String("FileTagger")); - - // TODO: Remove in 1.3 - item << PropertyDeclaration(QLatin1String("pattern"), PropertyDeclaration::StringList); - item << PropertyDeclaration(QLatin1String("patterns"), PropertyDeclaration::StringList); item << PropertyDeclaration(QLatin1String("fileTags"), PropertyDeclaration::Variant); insert(item); @@ -245,9 +246,9 @@ void BuiltinDeclarations::addProductItem() item << nameProperty(); decl = PropertyDeclaration(QLatin1String("targetName"), PropertyDeclaration::String); decl.setInitialValueSource(QLatin1String("name")); + item << buildDirProperty(); item << decl; decl = PropertyDeclaration(QLatin1String("destinationDirectory"), PropertyDeclaration::String); - decl.setInitialValueSource(QLatin1String("'.'")); item << decl; item << PropertyDeclaration(QLatin1String("consoleApplication"), PropertyDeclaration::Boolean); @@ -272,6 +273,8 @@ void BuiltinDeclarations::addProjectItem() << QLatin1String("Rule")); item << nameProperty(); item << conditionProperty(); + item << buildDirProperty(); + item << PropertyDeclaration(QLatin1String("sourceDirectory"), PropertyDeclaration::Path); item << PropertyDeclaration(QLatin1String("references"), PropertyDeclaration::Variant, PropertyDeclaration::PropertyNotAvailableInConfig); item << PropertyDeclaration(QLatin1String("qbsSearchPaths"), diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp index 7b42df5e6..8b1dcb72a 100644 --- a/src/lib/corelib/language/item.cpp +++ b/src/lib/corelib/language/item.cpp @@ -122,6 +122,14 @@ JSSourceValuePtr Item::sourceProperty(const QString &name) const return v.staticCast<JSSourceValue>(); } +VariantValuePtr Item::variantProperty(const QString &name) const +{ + ValuePtr v = property(name); + if (!v || v->type() != Value::VariantValueType) + return VariantValuePtr(); + return v.staticCast<VariantValue>(); +} + const PropertyDeclaration Item::propertyDeclaration(const QString &name) const { const PropertyDeclaration decl = m_propertyDeclarations.value(name); diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h index 866927a0e..8859957b0 100644 --- a/src/lib/corelib/language/item.h +++ b/src/lib/corelib/language/item.h @@ -102,6 +102,7 @@ public: ValuePtr property(const QString &name) const; ItemValuePtr itemProperty(const QString &name, bool create = false); JSSourceValuePtr sourceProperty(const QString &name) const; + VariantValuePtr variantProperty(const QString &name) const; void setPropertyObserver(ItemObserver *observer) const; void setProperty(const QString &name, const ValuePtr &value); void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp index e824271e5..82cede787 100644 --- a/src/lib/corelib/language/itemreaderastvisitor.cpp +++ b/src/lib/corelib/language/itemreaderastvisitor.cpp @@ -449,7 +449,7 @@ bool ItemReaderASTVisitor::visitStatement(AST::Statement *statement) return false; } -CodeLocation ItemReaderASTVisitor::toCodeLocation(AST::SourceLocation location) const +CodeLocation ItemReaderASTVisitor::toCodeLocation(const AST::SourceLocation &location) const { return CodeLocation(m_file->filePath(), location.startLine, location.startColumn); } diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h index dae481c3b..5d12aac35 100644 --- a/src/lib/corelib/language/itemreaderastvisitor.h +++ b/src/lib/corelib/language/itemreaderastvisitor.h @@ -60,7 +60,7 @@ public: private: bool visitStatement(QbsQmlJS::AST::Statement *statement); - CodeLocation toCodeLocation(QbsQmlJS::AST::SourceLocation location) const; + CodeLocation toCodeLocation(const QbsQmlJS::AST::SourceLocation &location) const; void checkDuplicateBinding(Item *item, const QStringList &bindingName, const QbsQmlJS::AST::SourceLocation &sourceLocation); Item *targetItemForBinding(Item *item, const QStringList &binding, diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index eb5ad9652..4974a3e68 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -585,11 +585,6 @@ static QProcessEnvironment getProcessEnvironment(ScriptEngine *engine, EnvType e const QScriptValue getEnvValue = engine->newFunction(js_getEnv, 1); const QScriptValue putEnvValue = engine->newFunction(js_putEnv, 1); - - // TODO: Remove in 1.3 - scope.setProperty(QLatin1String("getenv"), getEnvValue); - scope.setProperty(QLatin1String("putenv"), putEnvValue); - scope.setProperty(QLatin1String("getEnv"), getEnvValue); scope.setProperty(QLatin1String("putEnv"), putEnvValue); @@ -817,6 +812,14 @@ QStringList ResolvedProduct::generatedFiles(const QString &baseFile, const FileT return QStringList(); } +QString ResolvedProduct::buildDirectory() const +{ + const QString result = productProperties.value(QLatin1String("buildDirectory")).toString(); + QBS_CHECK(!result.isEmpty()); + return result; +} + + ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(0) { } diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index 780af8f47..a76c8ad26 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -397,6 +397,7 @@ public: TopLevelProject *topLevelProject() const; QStringList generatedFiles(const QString &baseFile, const FileTags &tags) const; + QString buildDirectory() const; private: ResolvedProduct(); diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp index 62c1aadde..622cfa073 100644 --- a/src/lib/corelib/language/loader.cpp +++ b/src/lib/corelib/language/loader.cpp @@ -120,11 +120,7 @@ TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters ¶meters) cancelationTimer.start(1000); } - ModuleLoaderResult loadResult - = m_moduleLoader->load(parameters.projectFilePath(), - parameters.overriddenValuesTree(), - parameters.buildConfigurationTree(), - true); + ModuleLoaderResult loadResult = m_moduleLoader->load(parameters); const TopLevelProjectPtr project = m_projectResolver->resolve(loadResult, parameters); // E.g. if the top-level project is disabled. diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index abe09814c..3dc7df283 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -36,6 +36,7 @@ #include "item.h" #include "itemreader.h" #include "scriptengine.h" +#include "value.h" #include <language/language.h> #include <language/scriptengine.h> #include <logging/logger.h> @@ -101,15 +102,12 @@ void ModuleLoader::setSearchPaths(const QStringList &searchPaths) } } -ModuleLoaderResult ModuleLoader::load(const QString &filePath, - const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, - bool wrapWithProjectItem) +ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) { if (m_logger.traceEnabled()) - m_logger.qbsTrace() << "[MODLDR] load" << filePath; + m_logger.qbsTrace() << "[MODLDR] load" << parameters.projectFilePath(); + m_parameters = parameters; m_reader->clearItemCache(); - m_overriddenProperties = overriddenProperties; - m_buildConfigProperties = buildConfigProperties; m_validItemPropertyNamesPerItem.clear(); m_disabledItems.clear(); @@ -117,14 +115,21 @@ ModuleLoaderResult ModuleLoader::load(const QString &filePath, m_pool = result.itemPool.data(); m_reader->setPool(m_pool); - Item *root = m_reader->readFile(filePath); + Item *root = m_reader->readFile(parameters.projectFilePath()); if (!root) return ModuleLoaderResult(); - if (wrapWithProjectItem && root->typeName() != QLatin1String("Project")) + if (root->typeName() != QLatin1String("Project")) root = wrapWithProject(root); - handleProject(&result, root, QSet<QString>() << QDir::cleanPath(filePath)); + const QString buildDirectory + = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), + TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())); + root->setProperty(QLatin1String("sourceDirectory"), + VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath())); + root->setProperty(QLatin1String("buildDirectory"), VariantValue::create(buildDirectory)); + handleProject(&result, root, buildDirectory, + QSet<QString>() << QDir::cleanPath(parameters.projectFilePath())); result.root = root; result.qbsFiles = m_reader->filesRead(); return result; @@ -198,19 +203,20 @@ private: }; void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, - const QSet<QString> &referencedFilePaths) + const QString &buildDirectory, const QSet<QString> &referencedFilePaths) { if (!checkItemCondition(item)) return; ProjectContext projectContext; projectContext.result = loadResult; + projectContext.buildDirectory = buildDirectory; projectContext.localModuleSearchPath = FileInfo::resolvePath(item->file()->dirPath(), moduleSearchSubDir); ProductContext dummyProductContext; dummyProductContext.project = &projectContext; loadBaseModule(&dummyProductContext, item); - overrideItemProperties(item, QLatin1String("project"), m_overriddenProperties); + overrideItemProperties(item, QLatin1String("project"), m_parameters.overriddenValuesTree()); projectContext.extraSearchPaths = readExtraSearchPaths(item); m_reader->pushExtraSearchPaths(projectContext.extraSearchPaths); @@ -228,7 +234,7 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, handleSubProject(&projectContext, child, referencedFilePaths); } else if (child->typeName() == QLatin1String("Project")) { copyProperties(item, child); - handleProject(loadResult, child, referencedFilePaths); + handleProject(loadResult, child, buildDirectory, referencedFilePaths); } } @@ -267,7 +273,7 @@ void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, Item *item, handleProduct(&projectContext, subItem); } else if (subItem->typeName() == QLatin1String("Project")) { copyProperties(item, subItem); - handleProject(loadResult, subItem, + handleProject(loadResult, subItem, buildDirectory, QSet<QString>(referencedFilePaths) << absReferencePath); } else { throw ErrorInfo(Tr::tr("The top-level item of a file in a \"references\" list must be " @@ -290,6 +296,7 @@ void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) if (m_logger.traceEnabled()) m_logger.qbsTrace() << "[MODLDR] handleProduct " << item->file()->filePath(); + initProductProperties(projectContext, item); ProductContext productContext; productContext.project = projectContext; bool extraSearchPathsSet = false; @@ -332,6 +339,23 @@ void ModuleLoader::handleProduct(ProjectContext *projectContext, Item *item) m_reader->popExtraSearchPaths(); } +void ModuleLoader::initProductProperties(const ProjectContext *project, Item *item) +{ + QString productName = m_evaluator->stringValue(item, QLatin1String("name")); + if (productName.isEmpty()) { + productName = FileInfo::completeBaseName(item->file()->filePath()); + item->setProperty(QLatin1String("name"), VariantValue::create(productName)); + } + + item->setProperty(QLatin1String("buildDirectory"), + VariantValue::create( + FileInfo::resolvePath(project->buildDirectory, productName))); + + item->setProperty(QLatin1String("sourceDirectory"), + VariantValue::create( + QFileInfo(item->file()->filePath()).absolutePath())); +} + void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *item, const QSet<QString> &referencedFilePaths) { @@ -380,7 +404,7 @@ void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext Item::addChild(item, loadedItem); item->setScope(projectContext->scope); - handleProject(projectContext->result, loadedItem, + handleProject(projectContext->result, loadedItem, projectContext->buildDirectory, QSet<QString>(referencedFilePaths) << subProjectFilePath); } @@ -792,7 +816,7 @@ Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString // Module properties that are defined in the profile are used as default values. const QVariantMap profileModuleProperties - = m_buildConfigProperties.value(fullModuleName).toMap(); + = m_parameters.buildConfigurationTree().value(fullModuleName).toMap(); for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); vmit != profileModuleProperties.end(); ++vmit) { @@ -827,7 +851,6 @@ void ModuleLoader::setupBaseModulePrototype(Item *prototype) BuiltinValue::create(BuiltinValue::GetNativeSettingFunction)); const BuiltinValuePtr getEnvValue = BuiltinValue::create(BuiltinValue::GetEnvFunction); prototype->setProperty(QLatin1String("getEnv"), getEnvValue); - prototype->setProperty(QLatin1String("getenv"), getEnvValue); // TODO: Remove in 1.3. prototype->setProperty(QLatin1String("getHostOS"), BuiltinValue::create(BuiltinValue::GetHostOSFunction)); prototype->setProperty(QLatin1String("canonicalArchitecture"), @@ -913,7 +936,8 @@ void ModuleLoader::instantiateModule(ProductContext *productContext, Item *insta } // override module properties given on the command line - const QVariantMap userModuleProperties = m_overriddenProperties.value(fullName).toMap(); + const QVariantMap userModuleProperties + = m_parameters.overriddenValuesTree().value(fullName).toMap(); for (QVariantMap::const_iterator vmit = userModuleProperties.begin(); vmit != userModuleProperties.end(); ++vmit) { if (Q_UNLIKELY(!moduleInstance->hasProperty(vmit.key()))) { @@ -993,7 +1017,7 @@ void ModuleLoader::checkCancelation() const { if (m_progressObserver && m_progressObserver->canceled()) { throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") - .arg(TopLevelProject::deriveId(m_buildConfigProperties))); + .arg(TopLevelProject::deriveId(m_parameters.buildConfigurationTree()))); } } diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h index f6f62a2c3..5df1747fc 100644 --- a/src/lib/corelib/language/moduleloader.h +++ b/src/lib/corelib/language/moduleloader.h @@ -33,6 +33,7 @@ #include "forward_decls.h" #include "itempool.h" #include <logging/logger.h> +#include <tools/setupprojectparameters.h> #include <QMap> #include <QSet> @@ -98,9 +99,7 @@ public: void setSearchPaths(const QStringList &searchPaths); Evaluator *evaluator() const { return m_evaluator; } - ModuleLoaderResult load(const QString &filePath, - const QVariantMap &overriddenProperties, const QVariantMap &buildConfigProperties, - bool wrapWithProjectItem = false); + ModuleLoaderResult load(const SetupProjectParameters ¶meters); static QString fullModuleName(const QStringList &moduleName); static void overrideItemProperties(Item *item, const QString &buildConfigKey, @@ -124,6 +123,7 @@ private: { public: ModuleLoaderResult *result; + QString buildDirectory; QString localModuleSearchPath; }; @@ -146,9 +146,10 @@ private: typedef QPair<Item *, ModuleLoaderResult::ProductInfo::Dependency> ProductDependencyResult; typedef QList<ProductDependencyResult> ProductDependencyResults; - void handleProject(ModuleLoaderResult *loadResult, Item *item, + void handleProject(ModuleLoaderResult *loadResult, Item *item, const QString &buildDirectory, const QSet<QString> &referencedFilePaths); void handleProduct(ProjectContext *projectContext, Item *item); + void initProductProperties(const ProjectContext *project, Item *item); void handleSubProject(ProjectContext *projectContext, Item *item, const QSet<QString> &referencedFilePaths); void createAdditionalModuleInstancesInProduct(ProductContext *productContext); @@ -198,8 +199,7 @@ private: QMap<QString, QStringList> m_moduleDirListCache; QHash<Item *, QSet<QString> > m_validItemPropertyNamesPerItem; QSet<Item *> m_disabledItems; - QVariantMap m_overriddenProperties; - QVariantMap m_buildConfigProperties; + SetupProjectParameters m_parameters; }; } // namespace Internal diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp index fec042e1e..4fe03b7cb 100644 --- a/src/lib/corelib/language/projectresolver.cpp +++ b/src/lib/corelib/language/projectresolver.cpp @@ -274,23 +274,17 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) ProductContext productContext; m_productContext = &productContext; productContext.item = item; - const QString productSourceDirectory = QFileInfo(item->file()->filePath()).absolutePath(); - item->setProperty(QLatin1String("sourceDirectory"), - VariantValue::create(productSourceDirectory)); - item->setProperty(QLatin1String("buildDirectory"), VariantValue::create(projectContext - ->project->topLevelProject()->buildDirectory)); ResolvedProductPtr product = ResolvedProduct::create(); product->project = projectContext->project; m_productItemMap.insert(product, item); projectContext->project->products += product; productContext.product = product; product->name = m_evaluator->stringValue(item, QLatin1String("name")); - if (product->name.isEmpty()) { - product->name = FileInfo::completeBaseName(item->file()->filePath()); - item->setProperty(QLatin1String("name"), VariantValue::create(product->name)); - } m_logger.qbsTrace() << "[PR] resolveProduct " << product->name; + // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. + productContext.buildDirectory = m_evaluator->stringValue(item, QLatin1String("buildDirectory")); + if (std::find_if(item->modules().begin(), item->modules().end(), ModuleNameEquals(product->name)) != item->modules().end()) { throw ErrorInfo( @@ -314,11 +308,20 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) product->fileTags = m_evaluator->fileTagsValue(item, QLatin1String("type")); product->targetName = m_evaluator->stringValue(item, QLatin1String("targetName")); - product->sourceDirectory = productSourceDirectory; - product->destinationDirectory - = m_evaluator->stringValue(item, QLatin1String("destinationDirectory")); + product->sourceDirectory = m_evaluator->stringValue(item, QLatin1String("sourceDirectory")); + const QString destDirKey = QLatin1String("destinationDirectory"); + product->destinationDirectory = m_evaluator->stringValue(item, destDirKey); + + if (product->destinationDirectory.isEmpty()) { + product->destinationDirectory = productContext.buildDirectory; + } else { + product->destinationDirectory = FileInfo::resolvePath( + product->topLevelProject()->buildDirectory, + product->destinationDirectory); + } product->location = item->location(); product->productProperties = createProductConfig(); + product->productProperties.insert(destDirKey, product->destinationDirectory); QVariantMap moduleProperties; moduleProperties.insert(QLatin1String("modules"), product->productProperties.take(QLatin1String("modules"))); @@ -384,15 +387,6 @@ void ProjectResolver::resolveModule(const QStringList &moduleName, Item *item, m_productContext->additionalFileTags += m_evaluator->fileTagsValue(item, QLatin1String("additionalProductTypes")); - // TODO: Remove in 1.3. - bool additionalProductFileTagsWasSet; - const QStringList additionalProductFileTags = m_evaluator->stringListValue(item, QLatin1String("additionalProductFileTags"), - &additionalProductFileTagsWasSet); - if (additionalProductFileTagsWasSet) { - m_logger.printWarning(ErrorInfo(Tr::tr("The 'additionalProductFileTags' property is deprecated. Please " - "use 'additionalProductTypes' instead."), item->location())); - m_productContext->additionalFileTags += FileTags::fromStringList(additionalProductFileTags); - } foreach (const Item::Module &m, item->modules()) module->moduleDependencies += ModuleLoader::fullModuleName(m.name); @@ -463,6 +457,20 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) if (Q_UNLIKELY(!files.isEmpty())) throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), item->location()); + + ProductContext::ArtifactPropertiesInfo apinfo + = m_productContext->artifactPropertiesPerFilter.value(fileTagsFilter); + if (apinfo.first) { + if (apinfo.second.fileName() == item->location().fileName()) { + ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); + error.append(Tr::tr("First item"), apinfo.second); + error.append(Tr::tr("Second item"), item->location()); + throw error; + } + + // Discard any Group with the same fileTagsFilter that was defined in a base file. + m_productContext->product->artifactProperties.removeAll(apinfo.first); + } if (!isEnabled) return; ArtifactPropertiesPtr aprops = ArtifactProperties::create(); @@ -471,6 +479,8 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) cfg->setValue(evaluateModuleValues(item)); aprops->setPropertyMapInternal(cfg); m_productContext->product->artifactProperties += aprops; + m_productContext->artifactPropertiesPerFilter.insert(fileTagsFilter, + ProductContext::ArtifactPropertiesInfo(aprops, item->location())); return; } if (Q_UNLIKELY(files.isEmpty() && !item->hasProperty(QLatin1String("files")))) { @@ -710,24 +720,14 @@ void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectConte checkCancelation(); QList<FileTaggerConstPtr> &fileTaggers = m_productContext ? m_productContext->product->fileTaggers : projectContext->fileTaggers; - QStringList patterns = m_evaluator->stringListValue(item, QLatin1String("patterns")); + const QStringList patterns = m_evaluator->stringListValue(item, QLatin1String("patterns")); + if (patterns.isEmpty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + const FileTags fileTags = m_evaluator->fileTagsValue(item, QLatin1String("fileTags")); if (fileTags.isEmpty()) throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); - // TODO: Remove in 1.3. - bool patternWasSet; - const QStringList oldPatterns = m_evaluator->stringListValue(item, QLatin1String("pattern"), - &patternWasSet); - if (patternWasSet) { - m_logger.printWarning(ErrorInfo(Tr::tr("The 'pattern' property is deprecated. Please " - "use 'patterns' instead."), item->location())); - patterns << oldPatterns; - } - - if (patterns.isEmpty()) - throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); - foreach (const QString &pattern, patterns) { if (pattern.isEmpty()) throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); @@ -760,8 +760,8 @@ void ProjectResolver::resolveTransformer(Item *item, ProjectContext *projectCont QString fileName = m_evaluator->stringValue(child, QLatin1String("fileName")); if (Q_UNLIKELY(fileName.isEmpty())) throw ErrorInfo(Tr::tr("Artifact fileName must not be empty.")); - artifact->absoluteFilePath = FileInfo::resolvePath(m_productContext->product->topLevelProject()->buildDirectory, - fileName); + artifact->absoluteFilePath + = FileInfo::resolvePath(m_productContext->buildDirectory, fileName); artifact->fileTags = m_evaluator->fileTagsValue(child, QLatin1String("fileTags")); if (artifact->fileTags.isEmpty()) artifact->fileTags.insert(unknownFileTag()); diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h index 636fd7299..630786db7 100644 --- a/src/lib/corelib/language/projectresolver.h +++ b/src/lib/corelib/language/projectresolver.h @@ -77,8 +77,11 @@ private: struct ProductContext { ResolvedProductPtr product; + QString buildDirectory; FileTags additionalFileTags; Item *item; + typedef QPair<ArtifactPropertiesPtr, CodeLocation> ArtifactPropertiesInfo; + QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter; }; struct ModuleContext diff --git a/src/lib/corelib/language/testdata/erroneous/conflicting_fileTagsFilter.qbs b/src/lib/corelib/language/testdata/erroneous/conflicting_fileTagsFilter.qbs new file mode 100644 index 000000000..54b3343e4 --- /dev/null +++ b/src/lib/corelib/language/testdata/erroneous/conflicting_fileTagsFilter.qbs @@ -0,0 +1,13 @@ +import qbs 1.0 + +Application { + Group { + fileTagsFilter: "application" + qbs.install: true + } + Group { + fileTagsFilter: "application" + qbs.install: false + } +} + diff --git a/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs index cc1b7b2a2..0ba76c6f6 100644 --- a/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs +++ b/src/lib/corelib/language/testdata/profilevaluesandoverriddenvalues.qbs @@ -2,14 +2,15 @@ import qbs 1.0 Project { Application { - name: { + name: "product1" + type: { if (!(dummy.cFlags instanceof Array)) throw new Error("dummy.cFlags: Array type expected."); if (!(dummy.cxxFlags instanceof Array)) throw new Error("dummy.cxxFlags: Array type expected."); if (!(dummy.defines instanceof Array)) throw new Error("dummy.defines: Array type expected."); - return "product1"; + return "application"; } Depends { name: "dummy" } // dummy.cxxFlags is set via profile and is not overridden diff --git a/src/lib/corelib/language/tst_language.cpp b/src/lib/corelib/language/tst_language.cpp index ff19b8afe..6873391e9 100644 --- a/src/lib/corelib/language/tst_language.cpp +++ b/src/lib/corelib/language/tst_language.cpp @@ -370,6 +370,8 @@ void TestLanguage::erroneousFiles_data() << "Unexpected item type 'Narf'"; QTest::newRow("invalid_child_item_type") << "Items of type 'Project' cannot contain items of type 'Depends'."; + QTest::newRow("conflicting_fileTagsFilter") + << "Conflicting fileTagsFilter in Group items"; } void TestLanguage::erroneousFiles() @@ -1113,7 +1115,7 @@ void TestLanguage::productDirectories() QVERIFY(product); const QVariantMap config = product->productProperties; QCOMPARE(config.value(QLatin1String("buildDirectory")).toString(), - buildDir(defaultParameters)); + buildDir(defaultParameters) + QLatin1Char('/') + product->name); QCOMPARE(config.value(QLatin1String("sourceDirectory")).toString(), testDataDir()); } catch (const ErrorInfo &e) { diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp index f59e83e2c..617fa2137 100644 --- a/src/lib/corelib/tools/settings.cpp +++ b/src/lib/corelib/tools/settings.cpp @@ -45,26 +45,6 @@ static QSettings::Format format() return HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat; } -static void migrateValue(QSettings *settings, const QString &key) -{ - const QVariant v = settings->value(key); - if (!v.isValid()) - return; - settings->setValue(QLatin1String("org/qt-project/qbs/") + key, v); - settings->remove(key); -} - -static void migrateGroup(QSettings *settings, const QString &group) -{ - QStringList fullKeys; - settings->beginGroup(group); - foreach (const QString &key, settings->allKeys()) - fullKeys += group + QLatin1Char('/') + key; - settings->endGroup(); - foreach (const QString &key, fullKeys) - migrateValue(settings, key); -} - static QSettings *createQSettings(const QString &baseDir) { return baseDir.isEmpty() @@ -75,13 +55,6 @@ static QSettings *createQSettings(const QString &baseDir) Settings::Settings(const QString &baseDir) : m_settings(createQSettings(baseDir)) { - // Migrate settings to internal group. - // ### remove in qbs 1.3 - if (!m_settings->childGroups().contains(QLatin1String("org/qt-project/qbs"))) { - migrateValue(m_settings, QLatin1String("defaultProfile")); - migrateGroup(m_settings, QLatin1String("profiles")); - migrateGroup(m_settings, QLatin1String("preferences")); - } // Actual qbs settings are stored transparently within a group, because QSettings // can see non-qbs fallback settings e.g. from QtProject that we're not interested in. m_settings->beginGroup(QLatin1String("org/qt-project/qbs")); diff --git a/src/lib/qtprofilesetup/qtprofilesetup.cpp b/src/lib/qtprofilesetup/qtprofilesetup.cpp index 31b6950a1..ddaec6c8e 100644 --- a/src/lib/qtprofilesetup/qtprofilesetup.cpp +++ b/src/lib/qtprofilesetup/qtprofilesetup.cpp @@ -119,6 +119,13 @@ static void addDesignerComponentsModule(QList<QtModuleInfo> &modules) modules << module; } +static QString quotedPath(const QString &str) +{ + return QLatin1Char('"') + + QDir::fromNativeSeparators(str).replace(QLatin1Char('"'), QLatin1String("\\\"")) + + QLatin1Char('"'); +} + static void createModules(Profile &profile, Settings *settings, const QtEnvironment &qtEnvironment) { @@ -314,9 +321,21 @@ static void createModules(Profile &profile, Settings *settings, const QByteArray debugMacro = module.qbsName == QLatin1String("declarative") || qtEnvironment.qtMajorVersion < 5 ? "QT_DECLARATIVE_DEBUG" : "QT_QML_DEBUG"; - propertiesString = "property bool qmlDebugging: false\n" - " cpp.defines: " - "qmlDebugging ? base.concat('" + debugMacro + "') : base"; + + const QString indent = QLatin1String(" "); + QTextStream s(&propertiesString); + s << "property bool qmlDebugging: false" << endl + << indent << "cpp.defines: " + << "qmlDebugging ? base.concat('" + debugMacro + "') : base" << endl; + + s << indent << "property string qmlPath"; + if (qtEnvironment.qmlPath.isEmpty()) + s << endl; + else + s << ": " << quotedPath(qtEnvironment.qmlPath) << endl; + + s << indent << "property string qmlImportsPath: " + << quotedPath(qtEnvironment.qmlImportPath); } content.replace("### special properties", propertiesString); moduleFile.resize(0); diff --git a/src/lib/qtprofilesetup/qtprofilesetup.h b/src/lib/qtprofilesetup/qtprofilesetup.h index a2283202a..88e56d2c9 100644 --- a/src/lib/qtprofilesetup/qtprofilesetup.h +++ b/src/lib/qtprofilesetup/qtprofilesetup.h @@ -43,6 +43,7 @@ public: QString libraryPath; QString includePath; QString binaryPath; + QString qmlPath; QString qmlImportPath; QString documentationPath; QString dataPath; diff --git a/src/lib/qtprofilesetup/templates/core.qbs b/src/lib/qtprofilesetup/templates/core.qbs index 0d706b255..35ca1b8fd 100644 --- a/src/lib/qtprofilesetup/templates/core.qbs +++ b/src/lib/qtprofilesetup/templates/core.qbs @@ -37,7 +37,7 @@ Module { // These are deliberately not path types // We don't want to resolve them against the source directory - property string generatedFilesDir: "GeneratedFiles/" + product.name + property string generatedFilesDir: product.buildDirectory + "/GeneratedFiles" property string qmFilesDir: product.destinationDirectory // private properties @@ -62,7 +62,7 @@ Module { paths.push(libPath + '/QtCore' + libInfix + '.framework/Versions/' + versionMajor + '/Headers'); paths.push(incPath + '/QtCore'); paths.push(incPath); - paths.push(product.buildDirectory + '/' + generatedFilesDir); + paths.push(generatedFilesDir); return paths; } cpp.libraryPaths: { @@ -117,54 +117,29 @@ Module { additionalProductTypes: ["qm"] validate: { - var requiredProperties = { - "binPath": binPath, - "incPath": incPath, - "libPath": libPath, - "mkspecPath": mkspecPath, - "version": version, - "config": config, - "qtConfig": qtConfig, - // Validate these in case 'version' is in some non-standard format - "versionMajor": versionMajor, - "versionMinor": versionMinor, - "versionPatch": versionPatch - }; - - if (!staticBuild) { - requiredProperties["pluginPath"] = pluginPath; - } - - var missingProperties = []; - for (var i in requiredProperties) { - if (requiredProperties[i] === undefined) { - missingProperties.push("Qt.core." + i); - } - } - - var invalidProperties = {}; - if (versionMajor <= 0) - invalidProperties["versionMajor"] = "must be > 0"; - if (versionMinor < 0) - invalidProperties["versionMinor"] = "must be >= 0"; - if (versionPatch < 0) - invalidProperties["versionPatch"] = "must be >= 0"; - - var errorMessage = ""; - if (missingProperties.length > 0) { - errorMessage += "The following Qt module properties are not set. " + - "Set them in your profile:\n" + - missingProperties.sort().join("\n"); - } - - if (Object.keys(invalidProperties).length > 0) { - errorMessage += "The following Qt module properties have invalid values:\n" + - Object.map(invalidProperties, - function(msg, prop) { return prop + ": " + msg; }).join("\n"); - } - - if (errorMessage.length > 0) - throw errorMessage; + 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.validate(); } setupRunEnvironment: { @@ -228,7 +203,7 @@ Module { prepare: { var cmd = new Command(Moc.fullPath(product), Moc.args(product, input, output.filePath)); - cmd.description = 'moc ' + FileInfo.fileName(input.filePath); + cmd.description = 'moc ' + input.fileName; cmd.highlight = 'codegen'; return cmd; } @@ -247,7 +222,7 @@ Module { [input.filePath, '-name', FileInfo.completeBaseName(input.filePath), '-o', output.filePath]); - cmd.description = 'rcc ' + FileInfo.fileName(input.filePath); + cmd.description = 'rcc ' + input.fileName; cmd.highlight = 'codegen'; return cmd; } @@ -266,7 +241,7 @@ Module { var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + '/' + ModUtils.moduleProperty(product, "lreleaseName"), ['-silent', input.filePath, '-qm', output.filePath]); - cmd.description = 'lrelease ' + FileInfo.fileName(input.filePath); + cmd.description = 'lrelease ' + input.fileName; cmd.highlight = 'filegen'; return cmd; } @@ -297,7 +272,7 @@ Module { } var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + '/' + ModUtils.moduleProperty(product, "qdocName"), args); - cmd.description = 'qdoc ' + FileInfo.fileName(input.filePath); + cmd.description = 'qdoc ' + input.fileName; cmd.highlight = 'filegen'; cmd.environment = ModUtils.moduleProperty(product, "qdocEnvironment"); cmd.environment.push("OUTDIR=" + outputDir); // Qt 4 replacement for -outputdir @@ -321,7 +296,7 @@ Module { args.push(output.filePath); var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + "/qhelpgenerator", args); - cmd.description = 'qhelpgenerator ' + FileInfo.fileName(input.filePath); + cmd.description = 'qhelpgenerator ' + input.fileName; cmd.highlight = 'filegen'; return cmd; } diff --git a/src/lib/qtprofilesetup/templates/gui.qbs b/src/lib/qtprofilesetup/templates/gui.qbs index 02b90f40c..5e4f43e15 100644 --- a/src/lib/qtprofilesetup/templates/gui.qbs +++ b/src/lib/qtprofilesetup/templates/gui.qbs @@ -18,7 +18,7 @@ QtModule { Artifact { // ### TODO we want to access the module's property "Qt.core.generatedFilesDir" here. But without evaluating all available properties a priori. - fileName: 'GeneratedFiles/' + product.name + '/ui_' + input.completeBaseName + '.h' + fileName: 'GeneratedFiles/ui_' + input.completeBaseName + '.h' fileTags: ["hpp"] } @@ -26,7 +26,7 @@ QtModule { var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + '/' + ModUtils.moduleProperty(product, "uicName"), [input.filePath, '-o', output.filePath]) - cmd.description = 'uic ' + FileInfo.fileName(input.filePath); + cmd.description = 'uic ' + input.fileName; cmd.highlight = 'codegen'; return cmd; } diff --git a/tests/auto/api/testdata/is-runnable/project.qbs b/tests/auto/api/testdata/is-runnable/project.qbs new file mode 100644 index 000000000..02200fff2 --- /dev/null +++ b/tests/auto/api/testdata/is-runnable/project.qbs @@ -0,0 +1,10 @@ +import qbs + +Project { + CppApplication { + name: "app" + } + DynamicLibrary { + name: "lib" + } +} diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp index 3b0fb1828..ab52b7040 100644 --- a/tests/auto/api/tst_api.cpp +++ b/tests/auto/api/tst_api.cpp @@ -425,7 +425,6 @@ void TestApi::fileTagsFilterOverride() QList<qbs::InstallableFile> installableFiles = project.installableFilesForProduct(product, qbs::InstallOptions()); QCOMPARE(installableFiles.count(), 1); - QEXPECT_FAIL(0, "QBS-424", Continue); QVERIFY(installableFiles.first().targetDirectory().contains("habicht")); } @@ -517,6 +516,27 @@ void TestApi::installableFiles() QCOMPARE(installableFiles.last().targetFilePath(), QLatin1String("/tmp/dir/file2.txt")); } +void TestApi::isRunnable() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters(); + setupParams.setProjectFilePath(QDir::cleanPath(QLatin1String(SRCDIR "/testdata" + "/is-runnable/project.qbs"))); + QScopedPointer<qbs::SetupProjectJob> job(qbs::Project::setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.data()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::Project project = job->project(); + const QList<qbs::ProductData> products = project.projectData().products(); + QCOMPARE(products.count(), 2); + foreach (const qbs::ProductData &p, products) { + QVERIFY2(p.name() == "app" || p.name() == "lib", qPrintable(p.name())); + if (p.name() == "app") + QVERIFY(p.isRunnable()); + else + QVERIFY(!p.isRunnable()); + } +} + void TestApi::listBuildSystemFiles() { qbs::SetupProjectParameters setupParams = defaultSetupParameters(); diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h index 9aab60651..0ad65c5e5 100644 --- a/tests/auto/api/tst_api.h +++ b/tests/auto/api/tst_api.h @@ -55,6 +55,7 @@ private slots: void infiniteLoopBuilding_data(); void infiniteLoopResolving(); void installableFiles(); + void isRunnable(); void listBuildSystemFiles(); void nonexistingProjectPropertyFromProduct(); void nonexistingProjectPropertyFromCommandLine(); diff --git a/tests/auto/blackbox/testdata/build-directories/input.txt b/tests/auto/blackbox/testdata/build-directories/input.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/auto/blackbox/testdata/build-directories/input.txt diff --git a/tests/auto/blackbox/testdata/build-directories/project.qbs b/tests/auto/blackbox/testdata/build-directories/project.qbs new file mode 100644 index 000000000..025340a0a --- /dev/null +++ b/tests/auto/blackbox/testdata/build-directories/project.qbs @@ -0,0 +1,49 @@ +import qbs + +Project { + Product { + name: "p1" + type: "blubb1" + Transformer { + Artifact { + fileName: "dummy1.txt" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + print(product.buildDirectory); + } + return cmd; + } + } + } + Product { + name: "p2" + type: "blubb2" + Depends { name: "p1" } + Group { + files: "input.txt" + fileTags: "input" + } + Rule { + inputs: "input" + usings: "blubb1" + Artifact { + fileName: "dummy2.txt" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + print(product.buildDirectory); + print(project.buildDirectory); + print(project.sourceDirectory); + } + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs index 61504573f..2c145682a 100644 --- a/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs +++ b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs @@ -64,12 +64,12 @@ Project { var sourceFileName = options["outfile"] || "lex.yy.c"; var headerFileName = options["header-file"]; var result = [{ - filePath: "GeneratedFiles/" + product.name + "/" + sourceFileName, + filePath: "GeneratedFiles/" + sourceFileName, fileTags: ["c"] }]; if (headerFileName) { result.push({ - filePath: "GeneratedFiles/" + product.name + "/" + headerFileName, + filePath: "GeneratedFiles/" + headerFileName, fileTags: ["hpp"] }); } @@ -80,7 +80,7 @@ Project { if (product.isFlexAvailable) { // flex is available. Let's call it. cmd = new Command("flex", [input.filePath]); - cmd.workingDirectory = product.buildDirectory + "/GeneratedFiles/" + product.name; + cmd.workingDirectory = product.buildDirectory + "/GeneratedFiles"; } else { // No flex available here, generate some C source and header. cmd = new JavaScriptCommand(); diff --git a/tests/auto/blackbox/testdata/nodejs/hello.js b/tests/auto/blackbox/testdata/nodejs/hello.js new file mode 100644 index 000000000..43f1e2ffd --- /dev/null +++ b/tests/auto/blackbox/testdata/nodejs/hello.js @@ -0,0 +1,3 @@ +if (console) { + console.log("hello world"); +} diff --git a/tests/auto/blackbox/testdata/nodejs/hello.qbs b/tests/auto/blackbox/testdata/nodejs/hello.qbs new file mode 100644 index 000000000..e11b1a599 --- /dev/null +++ b/tests/auto/blackbox/testdata/nodejs/hello.qbs @@ -0,0 +1,7 @@ +import qbs + +NodeJSApplication { + nodejs.applicationFile: "hello.js" + name: "hello" + files: "hello.js" +} diff --git a/tests/auto/blackbox/testdata/simpleProbe/main.cpp b/tests/auto/blackbox/testdata/simpleProbe/main.cpp new file mode 100644 index 000000000..76e819701 --- /dev/null +++ b/tests/auto/blackbox/testdata/simpleProbe/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/simpleProbe/simpleProbe.qbs b/tests/auto/blackbox/testdata/simpleProbe/simpleProbe.qbs new file mode 100644 index 000000000..b409c7cc6 --- /dev/null +++ b/tests/auto/blackbox/testdata/simpleProbe/simpleProbe.qbs @@ -0,0 +1,31 @@ +import qbs 1.0 +import qbs.Probes + +CppApplication { + Probe { + id: probe1 + property string someString + configure: { + someString = "one"; + found = true; + } + } + Probe { + id: probe2 + configure: { + found = false; + } + } + name: "MyApp" + type: { + if (!probe1.found) + throw "probe1 not found"; + if (probe2.found) + throw "probe2 unexpectedly found"; + if (probe1.someString !== "one") + throw "probe1.someString expected to be \"one\"." + return "application" + } + files: ["main.cpp"] +} + diff --git a/tests/auto/blackbox/testdata/typescript/animals.ts b/tests/auto/blackbox/testdata/typescript/animals.ts new file mode 100644 index 000000000..a33ae5c11 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/animals.ts @@ -0,0 +1,21 @@ +export interface Mammal { + speak(): string; +} + +export class Cat implements Mammal { + public speak() { + return "Meow"; // a cat says meow + } +} + +export class Dog implements Mammal { + public speak() { + return "Woof"; // a dog says woof + } +} + +export class Human implements Mammal { + public speak() { + return "Hello"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/extra.js b/tests/auto/blackbox/testdata/typescript/extra.js new file mode 100644 index 000000000..5500e4688 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/extra.js @@ -0,0 +1,3 @@ +if (console) { + console.log("This doesn't do anything useful!"); +} diff --git a/tests/auto/blackbox/testdata/typescript/foo.ts b/tests/auto/blackbox/testdata/typescript/foo.ts new file mode 100644 index 000000000..3554d317a --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/foo.ts @@ -0,0 +1,5 @@ +export class Greeter { + public getGreeting(): string { + return "guten Tag!"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/main.ts b/tests/auto/blackbox/testdata/typescript/main.ts new file mode 100644 index 000000000..c41eebea5 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/main.ts @@ -0,0 +1,19 @@ +import Animals = require("animals"); +import Foo = require("foo"); + +function main() { + var mammals: Animals.Mammal[] = []; + mammals.push(new Animals.Human()); + mammals.push(new Animals.Dog()); + mammals.push(new Animals.Cat()); + + // Make everyone speak + for (var i = 0; i < mammals.length; ++i) { + console.log(mammals[i].speak()); + } + + var greeting: string = (new Foo.Greeter()).getGreeting(); + console.log(greeting); +} + +main(); diff --git a/tests/auto/blackbox/testdata/typescript/typescript.qbs b/tests/auto/blackbox/testdata/typescript/typescript.qbs new file mode 100644 index 000000000..8407d1203 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/typescript.qbs @@ -0,0 +1,34 @@ +import qbs + +Project { + NodeJSApplication { + Depends { name: "typescript" } + Depends { name: "lib" } + + typescript.warningLevel: ["pedantic"] + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + nodejs.applicationFile: "main.ts" + + name: "animals" + + files: [ + "animals.ts", + "extra.js", + "main.ts" + ] + } + + Product { + Depends { name: "typescript" } + + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + + name: "lib" + + files: [ + "foo.ts" + ] + } +} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index cc68a57d4..c48eb0611 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -47,6 +47,12 @@ using qbs::Internal::HostOsInfo; using qbs::Internal::removeDirectoryWithContents; using qbs::Profile; +static bool regularFileExists(const QString &filePath) +{ + const QFileInfo fi(filePath); + return fi.exists() && fi.isFile(); +} + static QString initQbsExecutableFilePath() { QString filePath = QCoreApplication::applicationDirPath() + QLatin1String("/qbs"); @@ -173,7 +179,7 @@ QByteArray TestBlackbox::unifiedLineEndings(const QByteArray &ba) void TestBlackbox::initTestCase() { - QVERIFY(QFile::exists(qbsExecutableFilePath)); + QVERIFY(regularFileExists(qbsExecutableFilePath)); SettingsPtr settings = qbsSettings(QString()); if (!settings->profiles().contains(buildProfileName)) @@ -259,49 +265,71 @@ void TestBlackbox::baseProperties() QCOMPARE(runQbs(), 0); } +void TestBlackbox::buildDirectories() +{ + const QString projectDir + = QDir::cleanPath(testDataDir + QLatin1String("/build-directories")); + const QString projectBuildDir = projectDir + '/' + buildDir; + QDir::setCurrent(projectDir); + QCOMPARE(runQbs(QStringList("-qq")), 0); + const QStringList outputLines + = QString::fromLocal8Bit(m_qbsStderr.trimmed()).split('\n', QString::SkipEmptyParts); + QCOMPARE(outputLines.count(), 4); + QCOMPARE(outputLines.at(0).trimmed(), projectBuildDir + "/p1"); + QCOMPARE(outputLines.at(1).trimmed(), projectBuildDir + "/p2"); + QCOMPARE(outputLines.at(2).trimmed(), projectBuildDir); + QCOMPARE(outputLines.at(3).trimmed(), projectDir); +} + void TestBlackbox::build_project_data() { QTest::addColumn<QString>("projectSubDir"); QTest::addColumn<QString>("productFileName"); QTest::newRow("BPs in Sources") << QString("buildproperties_source") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/HelloWorld")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/HelloWorld/HelloWorld")); QTest::newRow("code generator") << QString("codegen") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/codegen")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/codegen/codegen")); QTest::newRow("link static libs") << QString("link_staticlib") << QString(buildDir + QLatin1String("/") - + HostOsInfo::appendExecutableSuffix("HelloWorld")); + + HostOsInfo::appendExecutableSuffix("HelloWorld/HelloWorld")); QTest::newRow("precompiled header") << QString("precompiledHeader") << QString(buildDir + QLatin1String("/") - + HostOsInfo::appendExecutableSuffix("MyApp")); + + HostOsInfo::appendExecutableSuffix("MyApp/MyApp")); QTest::newRow("lots of dots") << QString("lotsofdots") << QString(buildDir + QLatin1String("/") - + HostOsInfo::appendExecutableSuffix("lots.of.dots")); + + HostOsInfo::appendExecutableSuffix("lots.of.dots/lots.of.dots")); QTest::newRow("Qt5 plugin") << QString("qt5plugin") - << QString(buildDir + QLatin1String("/") + HostOsInfo::dynamicLibraryName("echoplugin")); + << QString(buildDir + QLatin1String("/echoplugin/") + + HostOsInfo::dynamicLibraryName("echoplugin")); QTest::newRow("Q_OBJECT in source") << QString("moc_cpp") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/moc_cpp")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/moc_cpp/moc_cpp")); QTest::newRow("Q_OBJECT in header") << QString("moc_hpp") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/moc_hpp")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/moc_hpp/moc_hpp")); QTest::newRow("Q_OBJECT in header, moc_XXX.cpp included") << QString("moc_hpp_included") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/moc_hpp_included")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + + "/moc_hpp_included/moc_hpp_included")); QTest::newRow("app and lib with same source file") << QString("lib_samesource") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/HelloWorldApp")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + + "/HelloWorldApp/HelloWorldApp")); QTest::newRow("source files with the same base name but different extensions") << QString("sameBaseName") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/basename")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/basename/basename")); QTest::newRow("static library dependencies") << QString("staticLibDeps") - << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/staticLibDeps")); + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/staticLibDeps/staticLibDeps")); + QTest::newRow("simple probes") + << QString("simpleProbe") + << QString(HostOsInfo::appendExecutableSuffix(buildDir + "/MyApp/MyApp")); } void TestBlackbox::build_project() @@ -315,13 +343,13 @@ void TestBlackbox::build_project() rmDirR(buildDir); QCOMPARE(runQbs(), 0); - QVERIFY2(QFile::exists(productFileName), qPrintable(productFileName)); - QVERIFY(QFile::exists(buildGraphPath)); + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); + QVERIFY(regularFileExists(buildGraphPath)); QVERIFY2(QFile::remove(productFileName), qPrintable(productFileName)); waitForNewTimestamp(); QCOMPARE(runQbs(QbsRunParameters(QStringList("--check-timestamps"))), 0); - QVERIFY2(QFile::exists(productFileName), qPrintable(productFileName)); - QVERIFY(QFile::exists(buildGraphPath)); + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); + QVERIFY(regularFileExists(buildGraphPath)); } void TestBlackbox::build_project_dry_run_data() @@ -387,7 +415,7 @@ void TestBlackbox::dependenciesProperty() { QDir::setCurrent(testDataDir + QLatin1String("/dependenciesProperty")); QCOMPARE(runQbs(), 0); - QFile depsFile(buildDir + QLatin1String("/product1.deps")); + QFile depsFile(buildDir + QLatin1String("/product1/product1.deps")); QVERIFY(depsFile.open(QFile::ReadOnly)); QString deps = QString::fromLatin1(depsFile.readAll()); QVERIFY(!deps.isEmpty()); @@ -445,7 +473,7 @@ void TestBlackbox::resolve_project() QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); QVERIFY2(!QFile::exists(productFileName), qPrintable(productFileName)); - QVERIFY(QFile::exists(buildGraphPath)); + QVERIFY(regularFileExists(buildGraphPath)); } void TestBlackbox::resolve_project_dry_run_data() @@ -475,10 +503,10 @@ static bool symlinkExists(const QString &linkFilePath) void TestBlackbox::clean() { - const QString appObjectFilePath = buildDir + "/.obj/app/main.cpp" + QTC_HOST_OBJECT_SUFFIX; - const QString appExeFilePath = buildDir + "/app" + QTC_HOST_EXE_SUFFIX; - const QString depObjectFilePath = buildDir + "/.obj/dep/dep.cpp" + QTC_HOST_OBJECT_SUFFIX; - const QString depLibBase = buildDir + '/' + QTC_HOST_DYNAMICLIB_PREFIX + "dep"; + const QString appObjectFilePath = buildDir + "/app/.obj/main.cpp" + QTC_HOST_OBJECT_SUFFIX; + const QString appExeFilePath = buildDir + "/app/app" + QTC_HOST_EXE_SUFFIX; + const QString depObjectFilePath = buildDir + "/dep/.obj/dep.cpp" + QTC_HOST_OBJECT_SUFFIX; + const QString depLibBase = buildDir + "/dep/" + QTC_HOST_DYNAMICLIB_PREFIX + "dep"; QString depLibFilePath; QStringList symlinks; if (qbs::Internal::HostOsInfo::isOsxHost()) { @@ -499,24 +527,24 @@ void TestBlackbox::clean() // Default behavior: Remove only temporaries. QCOMPARE(runQbs(), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); - QVERIFY(QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); foreach (const QString &symLink, symlinks) - QVERIFY2(QFile(symLink).exists(), qPrintable(symLink)); + QVERIFY2(regularFileExists(symLink), qPrintable(symLink)); QCOMPARE(runQbs(QbsRunParameters("clean")), 0); QVERIFY(!QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); + QVERIFY(regularFileExists(appExeFilePath)); QVERIFY(!QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(depLibFilePath)); foreach (const QString &symLink, symlinks) QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); // Remove all. QCOMPARE(runQbs(), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("clean"), QStringList("--all-artifacts"))), 0); QVERIFY(!QFile(appObjectFilePath).exists()); QVERIFY(!QFile(appExeFilePath).exists()); @@ -527,28 +555,28 @@ void TestBlackbox::clean() // Dry run. QCOMPARE(runQbs(), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("clean"), QStringList("--all-artifacts") << "-n")), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); - QVERIFY(QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); foreach (const QString &symLink, symlinks) QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); // Product-wise, dependency only. QCOMPARE(runQbs(), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); - QVERIFY(QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("clean"), QStringList("--all-artifacts") << "-p" << "dep")), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); QVERIFY(!QFile(depObjectFilePath).exists()); QVERIFY(!QFile(depLibFilePath).exists()); foreach (const QString &symLink, symlinks) @@ -556,17 +584,17 @@ void TestBlackbox::clean() // Product-wise, dependent product only. QCOMPARE(runQbs(), 0); - QVERIFY(QFile(appObjectFilePath).exists()); - QVERIFY(QFile(appExeFilePath).exists()); - QVERIFY(QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("clean"), QStringList("--all-artifacts") << "-p" << "app")), 0); QVERIFY(!QFile(appObjectFilePath).exists()); QVERIFY(!QFile(appExeFilePath).exists()); - QVERIFY(QFile(depObjectFilePath).exists()); - QVERIFY(QFile(depLibFilePath).exists()); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); foreach (const QString &symLink, symlinks) QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); } @@ -708,8 +736,8 @@ void TestBlackbox::track_qrc() { QDir::setCurrent(testDataDir + "/qrc"); QCOMPARE(runQbs(), 0); - const QString fileName = HostOsInfo::appendExecutableSuffix(buildDir + "/i"); - QVERIFY2(QFile(fileName).exists(), qPrintable(fileName)); + const QString fileName = buildDir + "/i/" + HostOsInfo::appendExecutableSuffix("i"); + QVERIFY2(regularFileExists(fileName), qPrintable(fileName)); QDateTime dt = QFileInfo(fileName).lastModified(); QTest::qSleep(2020); { @@ -720,7 +748,7 @@ void TestBlackbox::track_qrc() f.close(); } QCOMPARE(runQbs(), 0); - QVERIFY(QFile(fileName).exists()); + QVERIFY(regularFileExists(fileName)); QVERIFY(dt < QFileInfo(fileName).lastModified()); } @@ -731,18 +759,18 @@ void TestBlackbox::track_qobject_change() QVERIFY(QFile("bla_qobject.h").copy("bla.h")); touch("bla.h"); QCOMPARE(runQbs(), 0); - const QString productFilePath = HostOsInfo::appendExecutableSuffix(buildDir + "/i"); - QVERIFY2(QFile(productFilePath).exists(), qPrintable(productFilePath)); + const QString productFilePath = buildDir + "/i/" + HostOsInfo::appendExecutableSuffix("i"); + QVERIFY2(regularFileExists(productFilePath), qPrintable(productFilePath)); QString moc_bla_objectFileName - = buildDir + "/.obj/i/GeneratedFiles/i/moc_bla.cpp" QTC_HOST_OBJECT_SUFFIX; - QVERIFY(QFile(moc_bla_objectFileName).exists()); + = buildDir + "/i/.obj/i/GeneratedFiles/moc_bla.cpp" QTC_HOST_OBJECT_SUFFIX; + QVERIFY2(regularFileExists(moc_bla_objectFileName), qPrintable(moc_bla_objectFileName)); QTest::qSleep(1000); QFile("bla.h").remove(); QVERIFY(QFile("bla_noqobject.h").copy("bla.h")); touch("bla.h"); QCOMPARE(runQbs(), 0); - QVERIFY(QFile(productFilePath).exists()); + QVERIFY(regularFileExists(productFilePath)); QVERIFY(!QFile(moc_bla_objectFileName).exists()); } @@ -758,7 +786,7 @@ void TestBlackbox::trackAddFile() QDir::setCurrent(testDataDir + "/trackAddFile/work"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY2(process.waitForStarted(), qPrintable(process.errorString())); QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); QCOMPARE(process.exitCode(), 0); @@ -774,7 +802,7 @@ void TestBlackbox::trackAddFile() touch("main.cpp"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY(process.waitForStarted()); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitCode(), 0); @@ -863,7 +891,7 @@ void TestBlackbox::trackRemoveFile() QDir::setCurrent(testDataDir + "/trackAddFile/work"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY2(process.waitForStarted(), qPrintable(process.errorString())); QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); QCOMPARE(process.exitCode(), 0); @@ -887,7 +915,7 @@ void TestBlackbox::trackRemoveFile() touch("project.qbs"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY(process.waitForStarted()); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitCode(), 0); @@ -900,7 +928,7 @@ void TestBlackbox::trackRemoveFile() QCOMPARE(unchangedObjectFileTime1, unchangedObjectFileTime2); // the object file for the removed cpp file should have vanished too - QCOMPARE(QFile::exists(buildDir + "/someapp/zort.cpp" QTC_HOST_OBJECT_SUFFIX), false); + QCOMPARE(regularFileExists(buildDir + "/someapp/zort.cpp" QTC_HOST_OBJECT_SUFFIX), false); } void TestBlackbox::trackAddFileTag() @@ -915,7 +943,7 @@ void TestBlackbox::trackAddFileTag() QDir::setCurrent(testDataDir + "/trackFileTags/work"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY2(process.waitForStarted(), qPrintable(process.errorString())); QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); QCOMPARE(process.exitCode(), 0); @@ -928,7 +956,7 @@ void TestBlackbox::trackAddFileTag() touch("project.qbs"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY(process.waitForStarted()); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitCode(), 0); @@ -949,12 +977,11 @@ void TestBlackbox::trackRemoveFileTag() QCOMPARE(runQbs(), 0); // check if the artifacts are here that will become stale in the 2nd step - QVERIFY2(QFile::exists(buildDir + "/.obj/someapp/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX), - qPrintable(buildDir + "/.obj/someapp/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX)); - QVERIFY2(QFile::exists(buildDir + "/main_foo.cpp"), qPrintable(buildDir + "/main_foo.cpp")); - QVERIFY2(QFile::exists(buildDir + "/main.foo"), qPrintable(buildDir + "/main.foo")); + QVERIFY(regularFileExists(buildDir + "/someapp/.obj/someapp/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX)); + QVERIFY(regularFileExists(buildDir + "/someapp/main_foo.cpp")); + QVERIFY(regularFileExists(buildDir + "/someapp/main.foo")); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY(process.waitForStarted()); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitCode(), 0); @@ -967,7 +994,7 @@ void TestBlackbox::trackRemoveFileTag() touch("project.qbs"); QCOMPARE(runQbs(), 0); - process.start(buildDir + "/someapp"); + process.start(buildDir + "/someapp/someapp"); QVERIFY(process.waitForStarted()); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitCode(), 0); @@ -975,9 +1002,9 @@ void TestBlackbox::trackRemoveFileTag() QCOMPARE(output.takeFirst().trimmed().constData(), "there's no foo here"); // check if stale artifacts have been removed - QCOMPARE(QFile::exists(buildDir + "/someapp/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX), false); - QCOMPARE(QFile::exists(buildDir + "/someapp/main_foo.cpp"), false); - QCOMPARE(QFile::exists(buildDir + "/someapp/main.foo"), false); + QCOMPARE(regularFileExists(buildDir + "/someapp/.obj/main_foo.cpp" QTC_HOST_OBJECT_SUFFIX), false); + QCOMPARE(regularFileExists(buildDir + "/someapp/main_foo.cpp"), false); + QCOMPARE(regularFileExists(buildDir + "/someapp/main.foo"), false); } void TestBlackbox::trackAddMocInclude() @@ -1108,10 +1135,12 @@ void TestBlackbox::ruleConditions() { QDir::setCurrent(testDataDir + "/ruleConditions"); QCOMPARE(runQbs(), 0); - QVERIFY(QFileInfo(buildDir + HostOsInfo::appendExecutableSuffix("/zorted")).exists()); - QVERIFY(QFileInfo(buildDir + HostOsInfo::appendExecutableSuffix("/unzorted")).exists()); - QVERIFY(QFileInfo(buildDir + "/zorted.foo.narf.zort").exists()); - QVERIFY(!QFileInfo(buildDir + "/unzorted.foo.narf.zort").exists()); + QVERIFY(QFileInfo(buildDir + "/zorted/" + + HostOsInfo::appendExecutableSuffix("zorted")).exists()); + QVERIFY(QFileInfo(buildDir + "/unzorted/" + + HostOsInfo::appendExecutableSuffix("unzorted")).exists()); + QVERIFY(QFileInfo(buildDir + "/zorted/zorted.foo.narf.zort").exists()); + QVERIFY(!QFileInfo(buildDir + "/unzorted/unzorted.foo.narf.zort").exists()); } void TestBlackbox::ruleCycle() @@ -1175,7 +1204,8 @@ void TestBlackbox::overrideProjectProperties() << QLatin1String("project.someInt:156") << QLatin1String("project.someStringList:one") << QLatin1String("MyAppForYou.mainFile:main.cpp"))), 0); - QVERIFY(QFile::exists(buildDir + HostOsInfo::appendExecutableSuffix("/MyAppForYou"))); + QVERIFY(regularFileExists(buildDir + "/MyAppForYou/" + + HostOsInfo::appendExecutableSuffix("MyAppForYou"))); QVERIFY(QFile::remove(buildGraphPath)); QbsRunParameters params; @@ -1196,7 +1226,8 @@ void TestBlackbox::productProperties() QDir::setCurrent(testDataDir + "/productproperties"); QCOMPARE(runQbs(QbsRunParameters(QStringList() << QLatin1String("-f") << QLatin1String("project.qbs"))), 0); - QVERIFY(QFile::exists(buildDir + HostOsInfo::appendExecutableSuffix("/blubb_user"))); + QVERIFY(regularFileExists(buildDir + "/blubb_user/" + + HostOsInfo::appendExecutableSuffix("blubb_user"))); } void TestBlackbox::propertyChanges() @@ -1214,7 +1245,7 @@ void TestBlackbox::propertyChanges() QVERIFY(m_qbsStdout.contains("linking product 1.debug")); QVERIFY(m_qbsStdout.contains("generated.txt")); QVERIFY(m_qbsStdout.contains("Making output from input")); - QFile generatedFile(buildDir + QLatin1String("/generated.txt")); + QFile generatedFile(buildDir + QLatin1String("/generated text file/generated.txt")); QVERIFY(generatedFile.open(QIODevice::ReadOnly)); QCOMPARE(generatedFile.readAll(), QByteArray("prefix 1contents 1suffix 1")); generatedFile.close(); @@ -1480,15 +1511,15 @@ void TestBlackbox::dynamicRuleOutputs() QDir::setCurrent(testDir + "/work"); QCOMPARE(runQbs(), 0); - const QString appFile = buildDir + "/genlexer" + QTC_HOST_EXE_SUFFIX; - const QString headerFile1 = buildDir + "/GeneratedFiles/genlexer/numberscanner.h"; - const QString sourceFile1 = buildDir + "/GeneratedFiles/genlexer/numberscanner.c"; - const QString sourceFile2 = buildDir + "/GeneratedFiles/genlexer/lex.yy.c"; + const QString appFile = buildDir + "/genlexer/genlexer" + QTC_HOST_EXE_SUFFIX; + const QString headerFile1 = buildDir + "/genlexer/GeneratedFiles/numberscanner.h"; + const QString sourceFile1 = buildDir + "/genlexer/GeneratedFiles/numberscanner.c"; + const QString sourceFile2 = buildDir + "/genlexer/GeneratedFiles/lex.yy.c"; // Check build #1: source and header file name are specified in numbers.l - QVERIFY(QFile::exists(appFile)); - QVERIFY(QFile::exists(headerFile1)); - QVERIFY(QFile::exists(sourceFile1)); + QVERIFY(regularFileExists(appFile)); + QVERIFY(regularFileExists(headerFile1)); + QVERIFY(regularFileExists(sourceFile1)); QVERIFY(!QFile::exists(sourceFile2)); QDateTime appFileTimeStamp1 = QFileInfo(appFile).lastModified(); @@ -1504,7 +1535,7 @@ void TestBlackbox::dynamicRuleOutputs() QVERIFY(appFileTimeStamp1 < appFileTimeStamp2); QVERIFY(!QFile::exists(headerFile1)); QVERIFY(!QFile::exists(sourceFile1)); - QVERIFY(QFile::exists(sourceFile2)); + QVERIFY(regularFileExists(sourceFile2)); waitForNewTimestamp(); QFile::remove("numbers.l"); @@ -1515,9 +1546,9 @@ void TestBlackbox::dynamicRuleOutputs() // Check build #3: source and header file name are specified in numbers.l QDateTime appFileTimeStamp3 = QFileInfo(appFile).lastModified(); QVERIFY(appFileTimeStamp2 < appFileTimeStamp3); - QVERIFY(QFile::exists(appFile)); - QVERIFY(QFile::exists(headerFile1)); - QVERIFY(QFile::exists(sourceFile1)); + QVERIFY(regularFileExists(appFile)); + QVERIFY(regularFileExists(headerFile1)); + QVERIFY(regularFileExists(sourceFile1)); QVERIFY(!QFile::exists(sourceFile2)); } @@ -1541,8 +1572,9 @@ void TestBlackbox::fileDependencies() QCOMPARE(runQbs(), 0); QVERIFY(m_qbsStdout.contains("compiling narf.cpp")); QVERIFY(m_qbsStdout.contains("compiling zort.cpp")); - const QString productFileName = HostOsInfo::appendExecutableSuffix(buildDir + "/myapp"); - QVERIFY2(QFile::exists(productFileName), qPrintable(productFileName)); + const QString productFileName = buildDir + "/myapp/" + + HostOsInfo::appendExecutableSuffix("myapp"); + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); // Incremental build without changes. QCOMPARE(runQbs(), 0); @@ -1720,7 +1752,8 @@ void TestBlackbox::qmlDebugging() QDir::setCurrent(testDataDir + "/qml-debugging"); QCOMPARE(runQbs(), 0); QProcess nm; - nm.start("nm", QStringList(HostOsInfo::appendExecutableSuffix(buildDir + "/debuggable-app"))); + nm.start("nm", QStringList(HostOsInfo::appendExecutableSuffix(buildDir + + "/debuggable-app/debuggable-app"))); if (nm.waitForStarted()) { // Let's ignore hosts without nm. QVERIFY2(nm.waitForFinished(), qPrintable(nm.errorString())); QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData()); @@ -1758,12 +1791,12 @@ void TestBlackbox::installedApp() QDir::setCurrent(testDataDir + "/installed_artifact"); QCOMPARE(runQbs(QbsRunParameters("install")), 0); - QVERIFY(QFile::exists(defaultInstallRoot + QVERIFY(regularFileExists(defaultInstallRoot + HostOsInfo::appendExecutableSuffix(QLatin1String("/usr/bin/installedApp")))); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("install"), QStringList("--install-root") << (testDataDir + "/installed-app"))), 0); - QVERIFY(QFile::exists(testDataDir + QVERIFY(regularFileExists(testDataDir + HostOsInfo::appendExecutableSuffix("/installed-app/usr/bin/installedApp"))); QFile addedFile(defaultInstallRoot + QLatin1String("/blubb.txt")); @@ -1771,9 +1804,9 @@ void TestBlackbox::installedApp() addedFile.close(); QVERIFY(addedFile.exists()); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("install"), QStringList("--remove-first"))), 0); - QVERIFY(QFile::exists(defaultInstallRoot + QVERIFY(regularFileExists(defaultInstallRoot + HostOsInfo::appendExecutableSuffix(QLatin1String("/usr/bin/installedApp")))); - QVERIFY(QFile::exists(defaultInstallRoot + QLatin1String("/usr/src/main.cpp"))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/src/main.cpp"))); QVERIFY(!addedFile.exists()); // Check whether changing install parameters on the product causes re-installation. @@ -1786,9 +1819,9 @@ void TestBlackbox::installedApp() projectFile.write(content); QVERIFY(projectFile.flush()); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("install"))), 0); - QVERIFY(QFile::exists(defaultInstallRoot + QVERIFY(regularFileExists(defaultInstallRoot + HostOsInfo::appendExecutableSuffix(QLatin1String("/usr/local/bin/installedApp")))); - QVERIFY(QFile::exists(defaultInstallRoot + QLatin1String("/usr/local/src/main.cpp"))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/src/main.cpp"))); // Check whether changing install parameters on the artifact causes re-installation. content.replace("qbs.installDir: \"bin\"", "qbs.installDir: 'custom'"); @@ -1797,7 +1830,7 @@ void TestBlackbox::installedApp() projectFile.write(content); QVERIFY(projectFile.flush()); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("install"))), 0); - QVERIFY(QFile::exists(defaultInstallRoot + QVERIFY(regularFileExists(defaultInstallRoot + HostOsInfo::appendExecutableSuffix(QLatin1String("/usr/local/custom/installedApp")))); // Check whether changing install parameters on a source file causes re-installation. @@ -1807,7 +1840,7 @@ void TestBlackbox::installedApp() projectFile.write(content); projectFile.close(); QCOMPARE(runQbs(QbsRunParameters(QLatin1String("install"))), 0); - QVERIFY(QFile::exists(defaultInstallRoot + QLatin1String("/usr/local/source/main.cpp"))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/source/main.cpp"))); rmDirR(buildDir); QbsRunParameters params; @@ -1910,7 +1943,7 @@ void TestBlackbox::testNsis() bool haveMakeNsis = false; foreach (const QString &path, paths) { - if (QFile::exists(QDir::fromNativeSeparators(path) + + if (regularFileExists(QDir::fromNativeSeparators(path) + HostOsInfo::appendExecutableSuffix(QLatin1String("/makensis")))) { haveMakeNsis = true; break; @@ -1970,9 +2003,9 @@ static bool haveWiX() } foreach (const QString &path, paths) { - if (QFile::exists(QDir::fromNativeSeparators(path) + + if (regularFileExists(QDir::fromNativeSeparators(path) + HostOsInfo::appendExecutableSuffix(QLatin1String("/candle"))) && - QFile::exists(QDir::fromNativeSeparators(path) + + regularFileExists(QDir::fromNativeSeparators(path) + HostOsInfo::appendExecutableSuffix(QLatin1String("/light")))) { return true; } @@ -2003,8 +2036,67 @@ void TestBlackbox::testWiX() QVERIFY(m_qbsStdout.contains("compiling QbsBootstrapper.wxs")); QVERIFY(m_qbsStdout.contains("linking qbs-" + arch + ".msi")); QVERIFY(m_qbsStdout.contains("linking qbs-setup-" + arch + ".exe")); - QVERIFY(QFile::exists(buildDir + "/qbs-" + arch + ".msi")); - QVERIFY(QFile::exists(buildDir + "/qbs-setup-" + arch + ".exe")); + QVERIFY(regularFileExists(buildDir + "/qbs-" + arch + ".msi")); + QVERIFY(regularFileExists(buildDir + "/qbs-setup-" + arch + ".exe")); +} + +static QString findExecutable(const QStringList &fileNames) +{ + const QStringList path = QString::fromLocal8Bit(qgetenv("PATH")) + .split(HostOsInfo::pathListSeparator(), QString::SkipEmptyParts); + + foreach (const QString &fileName, fileNames) { + foreach (const QString &ppath, path) { + const QString fullPath = ppath + QLatin1Char('/') + fileName; + if (QFileInfo(fullPath).exists()) + return QDir::cleanPath(fullPath); + } + } + return QString(); +} + +static bool haveNodeJs() +{ + // The Node.js binary is called nodejs on Debian/Ubuntu-family operating systems due to a + // conflict with another package containing a binary named node + return !findExecutable(QStringList() + << QLatin1String("nodejs") + << QLatin1String("node")).isEmpty(); +} + +void TestBlackbox::testNodeJs() +{ + if (!haveNodeJs()) { + SKIP_TEST("Node.js is not installed"); + return; + } + + QDir::setCurrent(testDataDir + QLatin1String("/nodejs")); + + QbsRunParameters params; + params.command = QLatin1String("run"); + QCOMPARE(runQbs(params), 0); + QVERIFY((bool)m_qbsStdout.contains("hello world")); + QVERIFY(regularFileExists(buildDir + "/hello/hello.js")); +} + +void TestBlackbox::testTypeScript() +{ + if (!haveNodeJs()) { + SKIP_TEST("node.js is not installed"); + return; + } + + QDir::setCurrent(testDataDir + QLatin1String("/typescript")); + + QbsRunParameters params; + params.command = QLatin1String("run"); + params.arguments = QStringList() << "-p" << "animals"; + QCOMPARE(runQbs(params), 0); + + QVERIFY(regularFileExists(buildDir + "/animals/animals.js")); + QVERIFY(regularFileExists(buildDir + "/animals/extra.js")); + QVERIFY(regularFileExists(buildDir + "/animals/main.js")); } QTEST_MAIN(TestBlackbox) diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 87008e266..fa0128022 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -99,6 +99,7 @@ private slots: void addedFilePersistent(); void addQObjectMacroToCppFile(); void baseProperties(); + void buildDirectories(); void build_project_data(); void build_project(); void build_project_dry_run_data(); @@ -170,6 +171,8 @@ private slots: void testNsis(); void testEmbedInfoPlist(); void testWiX(); + void testNodeJs(); + void testTypeScript(); private: QByteArray m_qbsStderr; diff --git a/version.js b/version.js index 0c2cf389a..3a7d7de44 100644 --- a/version.js +++ b/version.js @@ -1 +1 @@ -function qbsVersion() { return "1.2.1"; } +function qbsVersion() { return "1.3.0"; } |