diff options
author | Jake Petroules <jake.petroules@petroules.com> | 2015-06-09 23:48:35 -0700 |
---|---|---|
committer | Jake Petroules <jake.petroules@petroules.com> | 2015-07-29 19:17:07 +0000 |
commit | e8963d2a2ff314355ec6a44df2647eb84eda634f (patch) | |
tree | bba83ecbc2583f9656eafa5ad192fc27f1375a16 | |
parent | c50827bf53e3863adcfd8c0e38aa5b7773eb075f (diff) |
Introduce Xcode module.
Change-Id: I3d338b1f3a41bb4c19e0c3c213139e09493ae10b
Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
-rw-r--r-- | doc/reference/modules/xcode-module.qdoc | 121 | ||||
-rw-r--r-- | share/qbs/modules/bundle/BundleModule.qbs | 8 | ||||
-rw-r--r-- | share/qbs/modules/cpp/CppModule.qbs | 3 | ||||
-rw-r--r-- | share/qbs/modules/cpp/DarwinGCC.qbs | 28 | ||||
-rw-r--r-- | share/qbs/modules/cpp/GenericGCC.qbs | 1 | ||||
-rw-r--r-- | share/qbs/modules/cpp/ios-gcc.qbs | 2 | ||||
-rw-r--r-- | share/qbs/modules/cpp/osx-gcc.qbs | 2 | ||||
-rw-r--r-- | share/qbs/modules/ib/IBModule.qbs | 2 | ||||
-rw-r--r-- | share/qbs/modules/xcode/xcode.js | 99 | ||||
-rw-r--r-- | share/qbs/modules/xcode/xcode.qbs | 148 | ||||
-rw-r--r-- | src/app/qbs-setup-toolchains/xcodeprobe.cpp | 323 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/xcode/xcode-project.qbs | 37 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 65 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.h | 1 |
14 files changed, 559 insertions, 281 deletions
diff --git a/doc/reference/modules/xcode-module.qdoc b/doc/reference/modules/xcode-module.qdoc new file mode 100644 index 000000000..9ec9af37f --- /dev/null +++ b/doc/reference/modules/xcode-module.qdoc @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** Contact: http://www.qt.io/licensing +** +** 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 The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +/*! + \contentspage index.html + \page xcode-module.html + \ingroup list-of-modules + + \title Module xcode + \brief Provides Xcode support. + + The \c xcode module contains properties and rules for Xcode-based development. + This module provides the foundation for several other modules on Apple platforms, including the + \c cpp and \c ib modules. + + \section1 General Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li developerPath + \li path + \li \c{"/Applications/Xcode.app/Contents/Developer"} + \li Developer directory of the Xcode installation. By default this is set to the developer + directory of the Xcode installation at its default location in /Applications. + Corresponds to the \c DEVELOPER_DIR environment variable. + \row + \li sdk + \li string + \li \c{"macosx"} on OS X, \c{"iphoneos"} on iOS, \c{"iphonesimulator"} on iOS Simulator + \li Version of the Xcode SDK used to build products. This can be specified as a full + canonical SDK name (i.e. \c{"macosx10.10"}), a platform version number (i.e. + \c{"10.10"}), or a platform identifier (i.e. \c{"macosx"}) in which case the latest SDK + available for that platform will be used. + \endtable + + \section1 Read-only Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li sdkName + \li string + \li determined by \c{xcode.sdk} + \li Canonical name of the SDK used to build products. For example, macosx10.9. + \row + \li sdkVersion + \li string + \li determined by \c{xcode.sdk} + \li Version number of the SDK used to build products. For example, 10.9. + \row + \li latestSdkName + \li string + \li determined by \c{xcode.developerPath} + \li Canonical name of the latest SDK available in the Xcode installation. + For example, macosx10.10. + \row + \li latestSdkVersion + \li string + \li determined by \c{xcode.developerPath} + \li Version number of the latest SDK available in the Xcode installation. + For example, 10.9. + \row + \li availableSdkNames + \li stringList + \li determined by \c{xcode.developerPath} + \li Canonical names of all SDKs available in the Xcode installation for the current + platform. For example, [macosx10.9, macosx10.10]. + \row + \li availableSdkVersions + \li stringList + \li determined by \c{xcode.developerPath} + \li Version numbers of all SDK available in the Xcode installation for the current + platform. For example, [10.9, 10.10]. + \row + \li platformPath + \li path + \li determined by \c{xcode.developerPath} + \li Path of the platform directory containing \c{xcode.sdkPath}. + \row + \li sdkPath + \li path + \li determined by \c{xcode.developerPath} and \c{xcode.sdk} + \li Path of the SDK used to build products. Equivalent to \c{cpp.sysroot}. + \endtable +*/ diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs index 8572208e1..c553946d8 100644 --- a/share/qbs/modules/bundle/BundleModule.qbs +++ b/share/qbs/modules/bundle/BundleModule.qbs @@ -211,7 +211,7 @@ Module { cmd.infoPlistFormat = ModUtils.moduleProperty(product, "infoPlistFormat"); cmd.qmakeEnv = ModUtils.moduleProperty(product, "qmakeEnv"); - cmd.platformPath = product.moduleProperty("cpp", "platformPath"); + cmd.platformPath = product.moduleProperty("xcode", "platformPath"); cmd.toolchainInstallPath = product.moduleProperty("cpp", "toolchainInstallPath"); cmd.buildEnv = product.moduleProperty("cpp", "buildEnv"); cmd.defines = product.moduleProperty("cpp", "defines"); @@ -219,9 +219,9 @@ Module { cmd.compilerDefines = product.moduleProperty("cpp", "compilerDefines"); cmd.allDefines = [].concat(cmd.defines || []).concat(cmd.platformDefines || []).concat(cmd.compilerDefines || []); - cmd.platformInfoPlist = product.moduleProperty("cpp", "platformInfoPlist"); - cmd.sdkSettingsPlist = product.moduleProperty("cpp", "sdkSettingsPlist"); - cmd.toolchainInfoPlist = product.moduleProperty("cpp", "toolchainInfoPlist"); + cmd.platformInfoPlist = product.moduleProperty("xcode", "platformInfoPlist"); + cmd.sdkSettingsPlist = product.moduleProperty("xcode", "sdkSettingsPlist"); + cmd.toolchainInfoPlist = product.moduleProperty("xcode", "toolchainInfoPlist"); cmd.sysroot = product.moduleProperty("qbs", "sysroot"); cmd.osBuildVersion = product.moduleProperty("qbs", "hostOSBuildVersion"); diff --git a/share/qbs/modules/cpp/CppModule.qbs b/share/qbs/modules/cpp/CppModule.qbs index 62b6659aa..3443f1c7b 100644 --- a/share/qbs/modules/cpp/CppModule.qbs +++ b/share/qbs/modules/cpp/CppModule.qbs @@ -244,9 +244,6 @@ Module { property string signingIdentity property path provisioningProfile - property string xcodeSdkName - property string xcodeSdkVersion - property bool automaticReferenceCounting PropertyOptions { name: "automaticReferenceCounting" diff --git a/share/qbs/modules/cpp/DarwinGCC.qbs b/share/qbs/modules/cpp/DarwinGCC.qbs index 43ea72bc5..05e40d9d3 100644 --- a/share/qbs/modules/cpp/DarwinGCC.qbs +++ b/share/qbs/modules/cpp/DarwinGCC.qbs @@ -29,11 +29,14 @@ ****************************************************************************/ import qbs +import qbs.FileInfo import qbs.ModUtils UnixGCC { condition: false + Depends { name: "xcode"; required: false } + compilerDefines: ["__GNUC__", "__APPLE__"] loadableModulePrefix: "" loadableModuleSuffix: ".bundle" @@ -41,26 +44,11 @@ UnixGCC { separateDebugInformation: true debugInfoSuffix: ".dSYM" - validate: { - if (qbs.sysroot) { - var validator = new ModUtils.PropertyValidator("cpp"); - validator.setRequiredProperty("xcodeSdkName", xcodeSdkName); - validator.setRequiredProperty("xcodeSdkVersion", xcodeSdkVersion); - validator.validate(); - } - } + toolchainInstallPath: xcode.present + ? FileInfo.joinPaths(xcode.toolchainPath, "usr", "bin") : base + sysroot: xcode.present ? xcode.sdkPath : base setupBuildEnvironment: { - var v = new ModUtils.EnvironmentVariable("PATH", ":", false); - if (platformPath) { - v.prepend(platformPath + "/Developer/usr/bin"); - var platformPathL = platformPath.split("/"); - platformPathL.pop(); - platformPathL.pop(); - var devPath = platformPathL.join("/") - v.prepend(devPath + "/usr/bin"); - v.set(); - } for (var key in buildEnv) { v = new ModUtils.EnvironmentVariable(key); v.value = buildEnv[key]; @@ -113,8 +101,4 @@ UnixGCC { env["MACOSX_DEPLOYMENT_TARGET"] = minimumOsxVersion; return env; } - - readonly property path platformInfoPlist: platformPath ? [platformPath, "Info.plist"].join("/") : undefined - readonly property path sdkSettingsPlist: sysroot ? [sysroot, "SDKSettings.plist"].join("/") : undefined - readonly property path toolchainInfoPlist: toolchainInstallPath ? [toolchainInstallPath, "../../ToolchainInfo.plist"].join("/") : undefined } diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs index 1c7926136..f218434d5 100644 --- a/share/qbs/modules/cpp/GenericGCC.qbs +++ b/share/qbs/modules/cpp/GenericGCC.qbs @@ -58,7 +58,6 @@ CppModule { property string stripName: "strip" property string dsymutilName: "dsymutil" property path sysroot: qbs.sysroot - property path platformPath property string exportedSymbolsCheckMode: "ignore-undefined" PropertyOptions { diff --git a/share/qbs/modules/cpp/ios-gcc.qbs b/share/qbs/modules/cpp/ios-gcc.qbs index d087bb6d2..93df91c9c 100644 --- a/share/qbs/modules/cpp/ios-gcc.qbs +++ b/share/qbs/modules/cpp/ios-gcc.qbs @@ -39,7 +39,7 @@ DarwinGCC { qbs.toolchain && qbs.toolchain.contains('gcc') // Setting a minimum is especially important for Simulator or CC/LD thinks the target is OS X - minimumIosVersion: xcodeSdkVersion || (cxxStandardLibrary === "libc++" ? "5.0" : undefined) + minimumIosVersion: xcode.sdkVersion || (cxxStandardLibrary === "libc++" ? "5.0" : undefined) platformObjcFlags: base.concat(simulatorObjcFlags) platformObjcxxFlags: base.concat(simulatorObjcFlags) diff --git a/share/qbs/modules/cpp/osx-gcc.qbs b/share/qbs/modules/cpp/osx-gcc.qbs index a9263ec36..83246f509 100644 --- a/share/qbs/modules/cpp/osx-gcc.qbs +++ b/share/qbs/modules/cpp/osx-gcc.qbs @@ -36,5 +36,5 @@ DarwinGCC { qbs.targetOS.contains('osx') && qbs.toolchain && qbs.toolchain.contains('gcc') - minimumOsxVersion: xcodeSdkVersion || (cxxStandardLibrary === "libc++" ? "10.7" : undefined) + minimumOsxVersion: xcode.sdkVersion || (cxxStandardLibrary === "libc++" ? "10.7" : undefined) } diff --git a/share/qbs/modules/ib/IBModule.qbs b/share/qbs/modules/ib/IBModule.qbs index 5bed74d65..dd52984e5 100644 --- a/share/qbs/modules/ib/IBModule.qbs +++ b/share/qbs/modules/ib/IBModule.qbs @@ -37,8 +37,8 @@ import qbs.Process import 'ib.js' as Ib Module { - Depends { name: "cpp" } // to put toolchainInstallPath in the PATH for actool Depends { name: "bundle" } + Depends { name: "xcode" } condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin") diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js new file mode 100644 index 000000000..6b93b9985 --- /dev/null +++ b/share/qbs/modules/xcode/xcode.js @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** Contact: http://www.qt.io/licensing +** +** 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 The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +var File = loadExtension("qbs.File"); +var FileInfo = loadExtension("qbs.FileInfo"); +var PropertyList = loadExtension("qbs.PropertyList"); + +function applePlatformDirectoryName(targetOSList, version, throwOnError) { + if (!version) + version = ""; + if (targetOSList.contains("ios-simulator")) + return "iPhoneSimulator" + version; + else if (targetOSList.contains("ios")) + return "iPhoneOS" + version; + else if (targetOSList.contains("osx")) + return "MacOSX" + version; + if (throwOnError || throwOnError === undefined) + throw("No Apple platform corresponds to target OS list: " + targetOSList); +} + +function sdkInfoList(sdksPath) { + var sdkInfo = []; + var sdks = File.directoryEntries(sdksPath, File.Dirs | File.NoDotAndDotDot); + for (var i in sdks) { + // SDK directory name must contain a version number; + // we don't want the versionless iPhoneOS.sdk directory for example + if (!sdks[i].match(/[0-9]+/)) + continue; + + var settingsPlist = FileInfo.joinPaths(sdksPath, sdks[i], "SDKSettings.plist"); + var propertyList = new PropertyList(); + try { + propertyList.readFromFile(settingsPlist); + + function checkPlist(plist) { + if (!plist || !plist["CanonicalName"] || !plist["Version"]) + return false; + + var re = /^([0-9]+)\.([0-9]+)$/; + return plist["Version"].match(re); + } + + var plist = propertyList.toObject(); + if (!checkPlist(plist)) { + print("WARNING: Skipping corrupted SDK installation: " + + FileInfo.joinPaths(sdksPath, sdks[i])); + continue; + } + + sdkInfo.push(plist); + } finally { + propertyList.clear(); + } + } + + // Sort by SDK version number + sdkInfo.sort(function (a, b) { + var re = /^([0-9]+)\.([0-9]+)$/; + a = a["Version"].match(re); + if (a) + a = {major: a[1], minor: a[2]}; + b = b["Version"].match(re); + if (b) + b = {major: b[1], minor: b[2]}; + + if (a.major === b.major) + return a.minor - b.minor; + return a.major - b.major; + }); + + return sdkInfo; +} diff --git a/share/qbs/modules/xcode/xcode.qbs b/share/qbs/modules/xcode/xcode.qbs new file mode 100644 index 000000000..c363ff101 --- /dev/null +++ b/share/qbs/modules/xcode/xcode.qbs @@ -0,0 +1,148 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.DarwinTools +import qbs.ModUtils +import 'xcode.js' as Utils + +Module { + condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin") && + qbs.toolchain.contains("xcode") + + property path developerPath: "/Applications/Xcode.app/Contents/Developer" + property string sdk: DarwinTools.applePlatformName(qbs.targetOS) + + readonly property string sdkName: { + if (_sdkSettings) { + return _sdkSettings["CanonicalName"]; + } + } + + readonly property string sdkVersion: { + if (_sdkSettings) { + return _sdkSettings["Version"]; + } + } + + readonly property string latestSdkName: { + if (_availableSdks) { + return _availableSdks[_availableSdks.length - 1]["CanonicalName"]; + } + } + + readonly property string latestSdkVersion: { + if (_availableSdks) { + return _availableSdks[_availableSdks.length - 1]["Version"]; + } + } + + readonly property stringList availableSdkNames: { + if (_availableSdks) { + return _availableSdks.map(function (obj) { return obj["CanonicalName"]; }); + } + } + + readonly property stringList availableSdkVersions: { + if (_availableSdks) { + return _availableSdks.map(function (obj) { return obj["Version"]; }); + } + } + + readonly property path toolchainPath: FileInfo.joinPaths(toolchainsPath, + "XcodeDefault" + ".xctoolchain") + readonly property path platformPath: FileInfo.joinPaths(platformsPath, + Utils.applePlatformDirectoryName( + qbs.targetOS) + + ".platform") + readonly property path sdkPath: FileInfo.joinPaths(sdksPath, + Utils.applePlatformDirectoryName( + qbs.targetOS, sdkVersion) + + ".sdk") + + // private properties + readonly property path toolchainsPath: FileInfo.joinPaths(developerPath, "Toolchains") + readonly property path platformsPath: FileInfo.joinPaths(developerPath, "Platforms") + readonly property path sdksPath: FileInfo.joinPaths(platformPath, "Developer", "SDKs") + + readonly property path platformInfoPlist: FileInfo.joinPaths(platformPath, "Info.plist") + readonly property path sdkSettingsPlist: FileInfo.joinPaths(sdkPath, "SDKSettings.plist") + readonly property path toolchainInfoPlist: FileInfo.joinPaths(toolchainPath, + "ToolchainInfo.plist") + + readonly property var _availableSdks: Utils.sdkInfoList(sdksPath) + + readonly property var _sdkSettings: { + if (_availableSdks) { + for (var i in _availableSdks) { + if (_availableSdks[i]["Version"] === sdk) + return _availableSdks[i]; + if (_availableSdks[i]["CanonicalName"] === sdk) + return _availableSdks[i]; + } + + // Latest SDK available for the platform + if (DarwinTools.applePlatformName(qbs.targetOS) === sdk) + return _availableSdks[_availableSdks.length - 1]; + } + } + + readonly property pathList _availableProvisioningProfiles: { + var profiles = File.directoryEntries(provisioningProfilesPath, + File.Files | File.NoDotAndDotDot); + return profiles.map(function (s) { + return FileInfo.joinPaths(provisioningProfilesPath, s); + }).filter(function (s) { + return s.endsWith(".mobileprovision") || s.endsWith(".provisionprofile"); + }); + } + + qbs.sysroot: sdkPath + + validate: { + if (!_availableSdks) { + throw "There are no SDKs available for this platform in the Xcode installation."; + } + + if (!_sdkSettings) { + throw "There is no matching SDK available for ' + sdk + '."; + } + + var validator = new ModUtils.PropertyValidator("xcode"); + validator.setRequiredProperty("developerPath", developerPath); + validator.setRequiredProperty("sdk", sdk); + validator.setRequiredProperty("sdkName", sdkName); + validator.setRequiredProperty("sdkVersion", sdkVersion); + validator.setRequiredProperty("toolchainsPath", toolchainsPath); + validator.setRequiredProperty("toolchainPath", toolchainPath); + validator.setRequiredProperty("platformsPath", platformsPath); + validator.setRequiredProperty("platformPath", platformPath); + validator.setRequiredProperty("sdksPath", sdkPath); + validator.setRequiredProperty("sdkPath", sdkPath); + validator.addVersionValidator("sdkVersion", sdkVersion, 2, 2); + validator.addCustomValidator("sdkName", sdkName, function (value) { + return value === Utils.applePlatformDirectoryName( + qbs.targetOS, sdkVersion, false).toLowerCase(); + }, " is '" + sdkName + "', but target OS is [" + qbs.targetOS.join(",") + + "] and Xcode SDK version is '" + sdkVersion + "'"); + validator.validate(); + } + + property var buildEnv: { + return { + "DEVELOPER_DIR": developerPath + }; + } + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", qbs.pathListSeparator, false); + v.prepend(platformPath + "/Developer/usr/bin"); + v.prepend(developerPath + "/usr/bin"); + v.set(); + + for (var key in buildEnv) { + v = new ModUtils.EnvironmentVariable(key); + v.value = buildEnv[key]; + v.set(); + } + } +} diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.cpp b/src/app/qbs-setup-toolchains/xcodeprobe.cpp index 05a411dcc..3a9a5d068 100644 --- a/src/app/qbs-setup-toolchains/xcodeprobe.cpp +++ b/src/app/qbs-setup-toolchains/xcodeprobe.cpp @@ -44,20 +44,16 @@ #include <QFileInfo> #include <QDir> #include <QSettings> -#include <QRegExp> +#include <QRegularExpression> using namespace qbs; using Internal::Tr; namespace { -static QString qsystem(const QString &exe, const QStringList &args = QStringList()) -{ - QProcess p; - p.setProcessChannelMode(QProcess::MergedChannels); - p.start(exe, args); - p.waitForFinished(); - return QString::fromLocal8Bit(p.readAll()); -} +static const QString defaultDeveloperPath = + QStringLiteral("/Applications/Xcode.app/Contents/Developer"); +static const QRegularExpression defaultDeveloperPathRegex( + QStringLiteral("/Applications/Xcode([a-zA-Z0-9_-]+)\\.app/Contents/Developer")); class XcodeProbe { @@ -66,10 +62,8 @@ public: : settings(settings), profiles(profiles) { } - static int compareVersions(const QString &v1, const QString &v2); bool addDeveloperPath(const QString &path); void detectDeveloperPaths(); - void setArch(Profile *profile, const QString &pathToGcc, const QStringList &extraFlags); void setupDefaultToolchains(const QString &devPath, const QString &xCodeName); void detectAll(); private: @@ -78,32 +72,6 @@ private: QStringList developerPaths; }; -int XcodeProbe::compareVersions(const QString &v1, const QString &v2) -{ - QStringList v1L = v1.split(QLatin1Char('.'), QString::SkipEmptyParts); - QStringList v2L = v2.split(QLatin1Char('.'), QString::SkipEmptyParts); - int i = 0; - while (v1L.length() > i && v2L.length() > i) { - bool n1Ok, n2Ok; - int n1 = v1L.value(i).toInt(&n1Ok); - int n2 = v2L.value(i).toInt(&n2Ok); - if (!(n1Ok && n2Ok)) { - qbsInfo() << Tr::tr("Failed to compare version %1 and %2").arg(v1,v2); - return 0; - } - if (n1 > n2) - return -1; - else if (n1 < n2) - return 1; - ++i; - } - if (v1L.length() > v2L.length()) - return -1; - if (v1L.length() < v2L.length()) - return 1; - return 0; -} - bool XcodeProbe::addDeveloperPath(const QString &path) { if (path.isEmpty()) @@ -125,230 +93,104 @@ void XcodeProbe::detectDeveloperPaths() QStringList arguments(QLatin1String("--print-path")); selectedXcode.start(program, arguments, QProcess::ReadOnly); if (!selectedXcode.waitForFinished() || selectedXcode.exitCode()) { - qbsInfo() << Tr::tr("Could not detect selected xcode with /usr/bin/xcode-select"); + qbsInfo() << Tr::tr("Could not detect selected Xcode with /usr/bin/xcode-select"); } else { QString path = QString::fromLocal8Bit(selectedXcode.readAllStandardOutput()); addDeveloperPath(path); } - addDeveloperPath(QLatin1String("/Applications/Xcode.app/Contents/Developer")); + addDeveloperPath(defaultDeveloperPath); + + QProcess launchServices; + program = QLatin1String("/usr/bin/mdfind"); + arguments = QStringList(QLatin1String("kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'")); + launchServices.start(program, arguments, QProcess::ReadOnly); + if (!launchServices.waitForFinished() || launchServices.exitCode()) { + qbsInfo() << Tr::tr("Could not detect additional Xcode installations with /usr/bin/mdfind"); + } else { + for (const QString &path : QString::fromLocal8Bit(launchServices.readAllStandardOutput()) + .split(QLatin1Char('\n'), QString::SkipEmptyParts)) + addDeveloperPath(path + QStringLiteral("/Contents/Developer")); + } } -void XcodeProbe::setArch(Profile *profile, const QString &pathToGcc, const QStringList &extraFlags) +static QStringList targetOSList(const QString &applePlatformName) { - if (!extraFlags.isEmpty()) { - profile->setValue(QLatin1String("cpp.platformCommonCompilerFlags"), extraFlags); - profile->setValue(QLatin1String("cpp.platformLinkerFlags"), extraFlags); - } - // setting architecture only here, bercause the same compiler - // can support several ones - QStringList flags(extraFlags); - flags << QLatin1String("-dumpmachine"); - QString compilerTriplet = qsystem(pathToGcc, flags).simplified(); - QStringList compilerTripletl = compilerTriplet.split(QLatin1Char('-')); - if (compilerTripletl.count() < 2) { - qbsError() << QString::fromLocal8Bit("Detected '%1', but I do not understand " - "its architecture '%2'.") - .arg(pathToGcc, compilerTriplet); - return; + QStringList targetOS; + if (applePlatformName == QStringLiteral("macosx")) { + targetOS << QStringLiteral("osx"); + } else if (applePlatformName == QStringLiteral("iphoneos")) { + targetOS << QStringLiteral("ios"); + } else if (applePlatformName == QStringLiteral("iphonesimulator")) { + targetOS << QStringLiteral("ios") << QStringLiteral("ios-simulator"); } - const QString architecture = compilerTripletl.at(0); - - qbsInfo() << Tr::tr(" Toolchain %1 detected:\n" - " binary: %2\n" - " triplet: %3\n" - " arch: %4").arg(profile->name(), pathToGcc, compilerTriplet, - architecture); - - profile->setValue(QLatin1String("qbs.architecture"), canonicalArchitecture(architecture)); + targetOS << QStringLiteral("darwin") << QStringLiteral("bsd") << QStringLiteral("unix"); + return targetOS; } -void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &xCodeName) +static QStringList archList(const QString &applePlatformName) { - qbsInfo() << Tr::tr("Setting up profile '%1'.").arg(xCodeName); - QString indent = QLatin1String(" "); - - // detect clang (default toolchain) - QFileInfo clangFileInfo(devPath - + QLatin1String("/Toolchains/XcodeDefault.xctoolchain/usr/bin") - + QLatin1String("/clang++")); - bool hasClang = clangFileInfo.exists(); - if (!hasClang) - qbsInfo() << indent << Tr::tr("Default toolchain %1 not found.") - .arg(clangFileInfo.canonicalFilePath()); - - // Platforms - QDir platformsDir(devPath + QLatin1String("/Platforms")); - QFileInfoList platforms = platformsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QFileInfo &fInfo, platforms) { - if (fInfo.isDir() && fInfo.suffix() == QLatin1String("platform")) { - qbsInfo() << indent << Tr::tr("Setting up %1").arg(fInfo.fileName()); - QSettings infoSettings(fInfo.absoluteFilePath() + QLatin1String("/Info.plist") - , QSettings::NativeFormat); - if (!infoSettings.contains(QLatin1String("Name"))) { - qbsInfo() << indent << Tr::tr("Missing platform name in Info.plist of %1") - .arg(fInfo.absoluteFilePath()); - continue; - } - QStringList targetOS; - targetOS << QLatin1String("darwin") << QLatin1String("bsd") << QLatin1String("unix"); - QString name = infoSettings.value(QLatin1String("Name")).toString(); - if (name == QLatin1String("macosx")) { - targetOS << QLatin1String("osx"); - } else if (name == QLatin1String("iphoneos")) { - targetOS << QLatin1String("ios"); - } else if (name == QLatin1String("iphonesimulator")) { - targetOS << QLatin1String("ios") << QLatin1String("ios-simulator"); - } else { - qbsInfo() << indent << Tr::tr("Skipping unknown platform %1").arg(name); - continue; - } - - // prepare default platform properties - QVariantMap defaultProp = infoSettings.value(QLatin1String("DefaultProperties")) - .toMap(); - QVariantMap overrideProp = infoSettings.value(QLatin1String("OverrideProperties")) - .toMap(); - QMapIterator<QString, QVariant> i(overrideProp); - while (i.hasNext()) { - i.next(); - // use unite? might lead to double insertions... - defaultProp[i.key()] = i.value(); - } + QStringList archs; + if (applePlatformName == QStringLiteral("macosx") + || applePlatformName == QStringLiteral("iphonesimulator")) { + archs << QStringLiteral("x86") << QStringLiteral("x86_64"); + } else if (applePlatformName == QStringLiteral("iphoneos")) { + archs << QStringLiteral("armv7") << QStringLiteral("arm64"); + } - QString clangFullName = xCodeName + QLatin1Char('-') + name + QLatin1String("-clang"); - // detect gcc - QFileInfo gccFileInfo(fInfo.absoluteFilePath() + QLatin1String("/Developer/usr/bin/g++")); - QString gccFullName = xCodeName + QLatin1Char('-') + name + QLatin1String("-gcc"); - if (!gccFileInfo.exists()) - gccFileInfo = QFileInfo(devPath + QLatin1String("/usr/bin/g++")); - bool hasGcc = gccFileInfo.exists(); + return archs; +} - // set SDKs/sysroot - QString sysRoot; - { - QString sdkName; - if (defaultProp.contains(QLatin1String("SDKROOT"))) - sdkName = defaultProp.value(QLatin1String("SDKROOT")).toString(); - QString sdkPath; - QDir sdks(fInfo.absoluteFilePath() + QLatin1String("/Developer/SDKs")); - QString maxVersion; - foreach (const QFileInfo &sdkDirInfo, sdks.entryInfoList(QDir::Dirs - | QDir::NoDotAndDotDot)) { - indent = QLatin1String(" "); - QSettings sdkInfo(sdkDirInfo.absoluteFilePath() - + QLatin1String("/SDKSettings.plist") - , QSettings::NativeFormat); - QString versionStr = sdkInfo.value(QLatin1String("Version")).toString(); - QVariant currentSdkName = sdkInfo.value(QLatin1String("CanonicalName")); - bool isBaseSdk = sdkInfo.value((QLatin1String("isBaseSDK"))).toString() - .toLower() != QLatin1String("no"); - if (!isBaseSdk) { - qbsInfo() << indent << Tr::tr("Skipping non base Sdk %1").arg(currentSdkName.toString()); - continue; - } - QString safeName = currentSdkName.toString() - .replace(QRegExp(QLatin1String("[^-a-zA-Z0-9]")),QLatin1String("-")); - if (sdkName.isEmpty()) { - if (compareVersions(maxVersion,versionStr) > 0) { - maxVersion = versionStr; - sdkPath = sdkDirInfo.canonicalFilePath(); - } - } else if (currentSdkName == sdkName) { - sdkPath = sdkDirInfo.canonicalFilePath(); - } - if (hasClang){ - Profile pSdk(xCodeName + QLatin1Char('-') + safeName - + QLatin1String("-clang"), settings); - pSdk.removeProfile(); - pSdk.setBaseProfile(clangFullName); - pSdk.setValue(QLatin1String("qbs.sysroot"), sdkDirInfo.canonicalFilePath()); - pSdk.setValue(QLatin1String("cpp.xcodeSdkName"), currentSdkName.toString()); - pSdk.setValue(QLatin1String("cpp.xcodeSdkVersion"), versionStr); - qbsInfo() << indent << Tr::tr("* adding profile %1").arg(pSdk.name()); - profiles << pSdk; - } - if (hasGcc) { - Profile pSdk(xCodeName + QLatin1Char('-') + safeName - + QLatin1String("-gcc"), settings); - pSdk.removeProfile(); - pSdk.setBaseProfile(gccFullName); - pSdk.setValue(QLatin1String("qbs.sysroot"), sdkDirInfo.canonicalFilePath()); - pSdk.setValue(QLatin1String("cpp.xcodeSdkName"), currentSdkName.toString()); - pSdk.setValue(QLatin1String("cpp.xcodeSdkVersion"), versionStr); - qbsInfo() << indent << Tr::tr("* adding profile %1").arg(pSdk.name()); - profiles << pSdk; - } - } - if (!sdkPath.isEmpty()) - sysRoot = sdkPath; - else if (!sdkName.isEmpty()) - qbsInfo() << indent << Tr::tr("Failed to find sysroot %1").arg(sdkName); - } - if (hasClang) { - Profile clangProfile(clangFullName, settings); - clangProfile.removeProfile(); - clangProfile.setValue(QLatin1String("qbs.targetOS"), targetOS); - clangProfile.setValue(QLatin1String("qbs.toolchain"), - QStringList() << QLatin1String("clang") - << QLatin1String("llvm") - << QLatin1String("gcc")); - QStringList extraFlags; - if (defaultProp.contains(QLatin1String("ARCHS"))) { - QString arch = defaultProp.value(QLatin1String("ARCHS")).toString(); - if (arch == QLatin1String("$(NATIVE_ARCH_32_BIT)")) - extraFlags << QLatin1String("-arch") << QLatin1String("i386"); - } - if (defaultProp.contains(QLatin1String("NATIVE_ARCH"))) { - QString arch = defaultProp.value(QLatin1String("NATIVE_ARCH")).toString(); - if (!arch.startsWith(QLatin1String("arm"))) - qbsInfo() << indent << Tr::tr("Expected arm architecture, not %1").arg(arch); - extraFlags << QLatin1String("-arch") << arch; - } - if (!sysRoot.isEmpty()) - clangProfile.setValue(QLatin1String("qbs.sysroot"), sysRoot); - clangProfile.setValue(QLatin1String("cpp.platformPath"), fInfo.canonicalFilePath()); - clangProfile.setValue(QLatin1String("cpp.compilerName"), clangFileInfo.fileName()); - clangProfile.setValue(QLatin1String("cpp.linkerName"), QLatin1String("clang++")); - clangProfile.setValue(QLatin1String("cpp.toolchainInstallPath"), - clangFileInfo.canonicalPath()); - setArch(&clangProfile, clangFileInfo.canonicalFilePath(), extraFlags); - qbsInfo() << indent << Tr::tr("* adding profile %1").arg(clangProfile.name()); - profiles << clangProfile; - } - if (hasGcc) { - Profile gccProfile(gccFullName, settings); - gccProfile.removeProfile(); - // use the arm-apple-darwin10-llvm-* variant if available??? - gccProfile.setValue(QLatin1String("qbs.targetOS"), targetOS); - QStringList toolchainTypes; - toolchainTypes << QLatin1String("gcc"); - if (gccFullName.contains(QLatin1String("llvm"))) - toolchainTypes << QLatin1String("llvm"); - gccProfile.setValue(QLatin1String("qbs.toolchain"), toolchainTypes); - if (!sysRoot.isEmpty()) - gccProfile.setValue(QLatin1String("qbs.sysroot"), sysRoot); - gccProfile.setValue(QLatin1String("cpp.platformPath"),fInfo.canonicalFilePath()); - gccProfile.setValue(QLatin1String("cpp.compilerName"), gccFileInfo.fileName()); - gccProfile.setValue(QLatin1String("cpp.linkerName"), QLatin1String("g++")); - gccProfile.setValue(QLatin1String("cpp.toolchainInstallPath"), - gccFileInfo.canonicalPath()); - setArch(&gccProfile, gccFileInfo.canonicalFilePath(), QStringList()); - qbsInfo() << indent << Tr::tr("* adding profile %1").arg(gccProfile.name()); - profiles << gccProfile; - } +void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &xcodeName) +{ + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(xcodeName).arg(devPath); + + Profile installationProfile(xcodeName, settings); + installationProfile.removeProfile(); + installationProfile.setValue(QStringLiteral("cpp.compilerName"), QStringLiteral("clang++")); + installationProfile.setValue(QStringLiteral("cpp.linkerName"), QStringLiteral("clang++")); + installationProfile.setValue(QStringLiteral("qbs.toolchain"), QStringList() + << QLatin1String("xcode") + << QLatin1String("clang") + << QLatin1String("llvm") + << QLatin1String("gcc")); + if (devPath != defaultDeveloperPath) + installationProfile.setValue(QStringLiteral("xcode.developerPath"), devPath); + profiles << installationProfile; + + QStringList platforms; + platforms << QStringLiteral("macosx") + << QStringLiteral("iphoneos") + << QStringLiteral("iphonesimulator"); + for (const QString &platform : platforms) { + Profile platformProfile(xcodeName + QLatin1Char('-') + platform, settings); + platformProfile.removeProfile(); + platformProfile.setBaseProfile(installationProfile.name()); + platformProfile.setValue(QStringLiteral("qbs.targetOS"), targetOSList(platform)); + profiles << platformProfile; + + for (const QString &arch : archList(platform)) { + Profile archProfile(xcodeName + QLatin1Char('-') + platform + QLatin1Char('-') + arch, + settings); + archProfile.removeProfile(); + archProfile.setBaseProfile(platformProfile.name()); + archProfile.setValue(QStringLiteral("qbs.architecture"), + qbs::canonicalArchitecture(arch)); + profiles << archProfile; } - indent = QLatin1String(" "); } } void XcodeProbe::detectAll() { + int i = 1; detectDeveloperPaths(); - QString xcodeName = QLatin1String("xcode"); - for (int iXcode = 0; iXcode < developerPaths.count(); ++iXcode) { - setupDefaultToolchains(developerPaths.value(iXcode), xcodeName); - xcodeName = QString::fromLatin1("xcode%1").arg(iXcode + 2); + for (const QString &developerPath : developerPaths) { + QRegularExpressionMatch match(defaultDeveloperPathRegex.match(developerPath)); + const QString profileName = match.isValid() + ? QString::fromLatin1("xcode%1").arg(match.capturedTexts().value(1).toLower()) + : QString::fromLatin1("xcode%1").arg(i++); + setupDefaultToolchains(developerPath, profileName); } } } // end anonymous namespace @@ -358,4 +200,3 @@ void xcodeProbe(qbs::Settings *settings, QList<qbs::Profile> &profiles) XcodeProbe probe(settings, profiles); probe.detectAll(); } - diff --git a/tests/auto/blackbox/testdata/xcode/xcode-project.qbs b/tests/auto/blackbox/testdata/xcode/xcode-project.qbs new file mode 100644 index 000000000..30eff3669 --- /dev/null +++ b/tests/auto/blackbox/testdata/xcode/xcode-project.qbs @@ -0,0 +1,37 @@ +import qbs + +Project { + property stringList sdks: [] + + Product { + Depends { name: "xcode" } + + consoleApplication: { + print("Developer directory: " + xcode.developerPath); + print("SDK: " + xcode.sdk); + print("Target devices: " + xcode.targetDevices.join(", ")); + print("SDK name: " + xcode.sdkName); + print("SDK version: " + xcode.sdkVersion); + print("Latest SDK name: " + xcode.latestSdkName); + print("Latest SDK version: " + xcode.latestSdkVersion); + print("Available SDK names: " + xcode.availableSdkNames.join(", ")); + print("Available SDK versions: " + xcode.availableSdkVersions.join(", ")); + print("Actual SDK list: " + project.sdks.join(", ")); + + var msg = "Unexpected SDK list [" + xcode.availableSdkVersions.join(", ") + "]"; + var testArraysEqual = function(a, b) { + if (!a || !b || a.length !== b.length) { + throw msg; + } + + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) { + throw msg; + } + } + } + + testArraysEqual(project.sdks, xcode.availableSdkVersions); + } + } +} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 821f4f809..992c479ad 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -249,6 +249,13 @@ QString TestBlackbox::findArchiver(const QString &fileName, int *status) return binary; } +static bool isXcodeProfile(const QString &profileName) +{ + qbs::Settings settings((QString())); + qbs::Profile profile(profileName, &settings); + return profile.value("qbs.toolchain").toStringList().contains("xcode"); +} + void TestBlackbox::sevenZip() { QDir::setCurrent(testDataDir + "/archiver"); @@ -518,7 +525,6 @@ void TestBlackbox::dependenciesProperty() QScriptValue product2_deps = product2.property(QLatin1String("dependencies")); QVERIFY(product2_deps.isObject()); c = product2_deps.property(QLatin1String("length")).toInt32(); - QCOMPARE(c, 2); QScriptValue product2_qbs; QScriptValue product2_cpp; for (int i = 0; i < c; ++i) { @@ -528,7 +534,13 @@ void TestBlackbox::dependenciesProperty() product2_cpp = dep; else if (name == QLatin1String("qbs")) product2_qbs = dep; + else if (name == QLatin1String("")) // non-loaded xcode module + --c; } + if (isXcodeProfile(profileName())) + QCOMPARE(c, 3); + else + QCOMPARE(c, 2); QVERIFY(product2_qbs.isObject()); QVERIFY(product2_cpp.isObject()); QCOMPARE(product2_cpp.property("defines").toString(), QLatin1String("SMURF")); @@ -2654,8 +2666,8 @@ void TestBlackbox::typescript() void TestBlackbox::iconset() { - if (!HostOsInfo::isOsxHost()) - QSKIP("only applies on OS X"); + if (!HostOsInfo::isOsxHost() || !isXcodeProfile(profileName())) + QSKIP("only applies on OS X with Xcode based profiles"); QDir::setCurrent(testDataDir + QLatin1String("/ib/iconset")); @@ -2668,8 +2680,8 @@ void TestBlackbox::iconset() void TestBlackbox::iconsetApp() { - if (!HostOsInfo::isOsxHost()) - QSKIP("only applies on OS X"); + if (!HostOsInfo::isOsxHost() || !isXcodeProfile(profileName())) + QSKIP("only applies on OS X with Xcode based profiles"); QDir::setCurrent(testDataDir + QLatin1String("/ib/iconsetapp")); @@ -2682,8 +2694,8 @@ void TestBlackbox::iconsetApp() void TestBlackbox::assetCatalog() { - if (!HostOsInfo::isOsxHost()) - QSKIP("only applies on OS X"); + if (!HostOsInfo::isOsxHost() || !isXcodeProfile(profileName())) + QSKIP("only applies on OS X with Xcode based profiles"); #ifdef Q_OS_MAC if (QSysInfo::macVersion() < Q_MV_OSX(10, 9)) QSKIP("This test needs at least OS X 10.9."); @@ -2888,4 +2900,43 @@ void TestBlackbox::probesInNestedModules() QVERIFY(m_qbsStderr.contains("product a, outer.something = hello")); } +void TestBlackbox::xcode() +{ + if (!HostOsInfo::isOsxHost() || !isXcodeProfile(profileName())) + QSKIP("only applies on OS X with Xcode based profiles"); + + QProcess xcodeSelect; + xcodeSelect.start("xcode-select", QStringList() << "--print-path"); + QVERIFY2(xcodeSelect.waitForStarted(), qPrintable(xcodeSelect.errorString())); + QVERIFY2(xcodeSelect.waitForFinished(), qPrintable(xcodeSelect.errorString())); + QVERIFY2(xcodeSelect.exitCode() == 0, qPrintable(xcodeSelect.readAllStandardError().constData())); + const QString developerDir(QString::fromLocal8Bit(xcodeSelect.readAllStandardOutput().trimmed())); + + QMultiMap<QString, QString> sdks; + + QProcess xcodebuildShowSdks; + xcodebuildShowSdks.start("xcrun", QStringList() << "xcodebuild" << "-showsdks"); + QVERIFY2(xcodebuildShowSdks.waitForStarted(), qPrintable(xcodebuildShowSdks.errorString())); + QVERIFY2(xcodebuildShowSdks.waitForFinished(), qPrintable(xcodebuildShowSdks.errorString())); + QVERIFY2(xcodebuildShowSdks.exitCode() == 0, qPrintable(xcodebuildShowSdks.readAllStandardError().constData())); + for (const QString &line : QString::fromLocal8Bit(xcodebuildShowSdks.readAllStandardOutput().trimmed()).split('\n', QString::SkipEmptyParts)) { + static const QRegularExpression regexp(QStringLiteral("\\s+\\-sdk\\s+(?<platform>[a-z]+)(?<version>[0-9]+\\.[0-9]+)$")); + QRegularExpressionMatch match = regexp.match(line); + if (match.isValid()) { + sdks.insert(match.captured("platform"), match.captured("version")); + } + } + + // values() returns items with most recently added first; we want the reverse of that + QStringList sdkValues(sdks.values("macosx")); + std::reverse(std::begin(sdkValues), std::end(sdkValues)); + + QDir::setCurrent(testDataDir + "/xcode"); + QbsRunParameters params; + params.arguments = (QStringList() + << (QStringLiteral("xcode.developerDir:") + developerDir) + << (QStringLiteral("project.sdks:") + sdkValues.join(","))); + QCOMPARE(runQbs(params), 0); +} + QTEST_MAIN(TestBlackbox) diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 1500ffd8a..f616efeba 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -185,6 +185,7 @@ private slots: void transitiveOptionalDependencies(); void groupsInModules(); void probesInNestedModules(); + void xcode(); private: QString findArchiver(const QString &fileName, int *status = nullptr); |