aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@petroules.com>2015-06-09 23:48:35 -0700
committerJake Petroules <jake.petroules@petroules.com>2015-07-29 19:17:07 +0000
commite8963d2a2ff314355ec6a44df2647eb84eda634f (patch)
treebba83ecbc2583f9656eafa5ad192fc27f1375a16
parentc50827bf53e3863adcfd8c0e38aa5b7773eb075f (diff)
Introduce Xcode module.
Change-Id: I3d338b1f3a41bb4c19e0c3c213139e09493ae10b Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
-rw-r--r--doc/reference/modules/xcode-module.qdoc121
-rw-r--r--share/qbs/modules/bundle/BundleModule.qbs8
-rw-r--r--share/qbs/modules/cpp/CppModule.qbs3
-rw-r--r--share/qbs/modules/cpp/DarwinGCC.qbs28
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs1
-rw-r--r--share/qbs/modules/cpp/ios-gcc.qbs2
-rw-r--r--share/qbs/modules/cpp/osx-gcc.qbs2
-rw-r--r--share/qbs/modules/ib/IBModule.qbs2
-rw-r--r--share/qbs/modules/xcode/xcode.js99
-rw-r--r--share/qbs/modules/xcode/xcode.qbs148
-rw-r--r--src/app/qbs-setup-toolchains/xcodeprobe.cpp323
-rw-r--r--tests/auto/blackbox/testdata/xcode/xcode-project.qbs37
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp65
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
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);