aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@qt.io>2016-04-22 01:20:24 -0700
committerIvan Komissarov <ABBAPOH@gmail.com>2021-02-18 00:10:00 +0000
commit2bc823ec00cec8a1d58981710eb50ba85b4f58d7 (patch)
tree4397fc51e963e00f8ab6dbf7686d5e28cda40c01
parent05b74ff728e8c6bc975e8c55160e1feb137895a1 (diff)
Implement codesign module
This moves code signing functionality into a dedicated module, and also implements automatic provisioning for Apple platforms, which automatically selects appropriate signing identities and provisioning profiles based on the product being built. This also results in a significant performance improvement since all code signing setup information is retrieved in process instead of forking off the openssl and security command line tools. Task-number: QBS-899 Change-Id: I60d0aeaeb2d1004929505bcb1e0bc77512fe77bc Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
-rw-r--r--doc/reference/modules/codesign-module.qdoc246
-rw-r--r--examples/cocoa-application/app.qbs9
-rw-r--r--share/qbs/imports/qbs/DarwinTools/darwin-tools.js16
-rw-r--r--share/qbs/imports/qbs/Probes/XcodeProbe.qbs3
-rw-r--r--share/qbs/modules/bundle/BundleModule.qbs71
-rw-r--r--share/qbs/modules/codesign/CodeSignModule.qbs45
-rw-r--r--share/qbs/modules/codesign/apple.qbs385
-rw-r--r--share/qbs/modules/codesign/codesign.js279
-rw-r--r--share/qbs/modules/codesign/noop.qbs37
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs20
-rw-r--r--share/qbs/modules/cpp/gcc.js25
-rw-r--r--share/qbs/modules/xcode/xcode.js21
-rw-r--r--share/qbs/modules/xcode/xcode.qbs272
-rw-r--r--tests/auto/api/tst_api.cpp4
-rw-r--r--tests/auto/blackbox/testdata-apple/codesign/app.cpp1
-rw-r--r--tests/auto/blackbox/testdata-apple/codesign/codesign.qbs38
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp4
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.cpp99
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.h2
-rw-r--r--tests/auto/blackbox/tst_blackboxbase.h9
20 files changed, 1233 insertions, 353 deletions
diff --git a/doc/reference/modules/codesign-module.qdoc b/doc/reference/modules/codesign-module.qdoc
new file mode 100644
index 000000000..ab95798a7
--- /dev/null
+++ b/doc/reference/modules/codesign-module.qdoc
@@ -0,0 +1,246 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \qmltype codesign
+ \inqmlmodule QbsModules
+ \since Qbs 1.19
+
+ \brief Provides code signing support.
+
+ The \c codesign module contains properties and rules for code signing on Apple platforms.
+
+ \section2 Relevant File Tags
+
+ \table
+ \header
+ \li Tag
+ \li Auto-tagged File Names
+ \li Since
+ \li Description
+ \row
+ \li \c{"codesign.entitlements"}
+ \li \c{*.entitlements}
+ \li 1.19.0
+ \li \l{https://developer.apple.com/documentation/bundleresources/entitlements}{Xcode entitlements}
+ \row
+ \li \c{"codesign.provisioningprofile"}
+ \li \c{*.mobileprovision, *.provisionprofile}
+ \li 1.19.0
+ \li Xcode provisioning profiles
+ \row
+ \li \c{"codesign.signed_artifact"}
+ \li n/a
+ \li 1.19.0
+ \li This tag is attached to all signed artifacts such as applications or libraries
+ \endtable
+*/
+
+/*!
+ \qmlproperty string codesign::codesignFlags
+
+ Additional flags passed to the \c{codesign} tool.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c{undefined}
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::codesignName
+
+ The name of the \c{codesign} binary.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c{"codesign"}
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::codesignPath
+
+ Path to the \c{codesign} tool.
+
+ \since Qbs 1.19
+
+ \defaultvalue Determined automatically
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty bool codesign::enableCodeSigning
+
+ Whether to actually perform code signing.
+
+ \since Qbs 1.19
+ \defaultvalue \c false
+*/
+
+/*!
+ \qmlproperty string codesign::provisioningProfile
+
+ Name or UUID of the provisioning profile to embed in the product.
+ Typically this should be left blank to allow \QBS to use automatic provisioning.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c undefined
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty path codesign::provisioningProfilesPath
+
+ Path to directory containing provisioning profiles installed on the system.
+ This should not normally need to be changed.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c{"~/Library/MobileDevice/Provisioning Profiles"}
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::signingIdentity
+
+ Search string used to find the certificate to sign the product. This does not have to be
+ a full certificate name like "Mac Developer: John Doe (XXXXXXXXXX)", and can instead be
+ a partial string like "Mac Developer" or the certificate's SHA1 fingerprint.
+ The search string should generally be one of the following:
+ \list
+ \li 3rd Party Mac Developer Application
+ \li 3rd Party Mac Developer Installer
+ \li Developer ID Application
+ \li Developer ID Installer
+ \li iPhone Developer
+ \li iPhone Distribution
+ \li Mac Developer
+ \endlist
+
+ It is also possible to use the special \c "-" value to use the ad-hoc signing.
+
+ See \l{https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html#//apple_ref/doc/uid/TP40012582-CH31-SW41}{Maintaining Your Signing Identities and Certificates}
+ for complete documentation on the existing certificate types.
+ In general you should use \l{codesign::signingType}{signingType} instead.
+
+ \since Qbs 1.19
+
+ \defaultvalue Determined by \l{codesign::signingType}{signingType}
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::signingTimestamp
+
+ URL of the timestamp authority server to be contacted to authenticate code signing.
+ \c{undefined} indicates that a system-specific default should be used, and the empty
+ string indicates the default server provided by Apple. \c{"none"} explicitly disables
+ the use of timestamp services and this should not usually need to be changed.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c{"none"}
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::signingType
+
+ Type of code signing to use. This should generally be used in preference to an explicit
+ signing identity like "Mac Developer: John Doe (XXXXXXXXXX)" since it is not user
+ specific and can be set in a project file.
+ Possible values include: \c{"app-store"}, \c{"apple-id"}, \c{"ad-hoc"}, which sign for
+ the App Store or Mac App Store, Developer ID, and Ad-hoc code signing, respectively.
+
+ \section1 Relation between the signingType and signingIdentity
+
+ The following table shows how the signingIdentity's default value is calculated.
+
+ \table
+ \header
+ \li \c qbs.targetOS
+ \li \c codesign.signingType
+ \li \c qbs.buildVariant
+ \li \c codesign.signingIdentity
+ \row
+ \li {1, 4} \c "macos"
+ \li \c "ad-hoc"
+ \li any
+ \li \c "-"
+ \row
+ \li {1, 2} \c "app-store"
+ \li \c "debug", \c "profiling"
+ \li \c "Mac Developer"
+ \row
+ \li \c "release"
+ \li \c "3rd Party Mac Developer Application"
+ \row
+ \li \c "apple-id"
+ \li any
+ \li \c "Developer ID Application"
+ \row
+ \li {1, 2} \c "ios", \c "tvos", \c "watchos"
+ \li {1, 2} \c "app-store"
+ \li \c "debug", \c "profiling"
+ \li \c "iPhone Developer"
+ \row
+ \li \c "release"
+ \li \c "iPhone Distribution"
+ \endtable
+
+ \since Qbs 1.19
+
+ \defaultvalue Determined automatically
+
+ \appleproperty
+*/
+
+/*!
+ \qmlproperty string codesign::teamIdentifier
+
+ Human readable name or 10-digit identifier of the Apple development team that the
+ signing identity belongs to. This is used to disambiguate between multiple certificates
+ of the same type in different teams. Typically this can be left blank if the development
+ machine is only signed in to a single development team, and should be set in a profile
+ otherwise.
+
+ \since Qbs 1.19
+
+ \defaultvalue \c undefined
+
+ \appleproperty
+*/
diff --git a/examples/cocoa-application/app.qbs b/examples/cocoa-application/app.qbs
index f51f94e8b..67558e8a5 100644
--- a/examples/cocoa-application/app.qbs
+++ b/examples/cocoa-application/app.qbs
@@ -48,7 +48,7 @@
**
****************************************************************************/
-import qbs
+import qbs.Utilities
CppApplication {
Depends { condition: product.condition; name: "ib" }
@@ -100,4 +100,11 @@ CppApplication {
}
ib.appIconName: "AppIcon"
+
+ Properties {
+ // codesign module only present starting from 1.19
+ condition: Utilities.versionCompare(qbs.version, "1.19") >= 0
+ codesign.enableCodeSigning: true
+ codesign.signingType: "ad-hoc"
+ }
}
diff --git a/share/qbs/imports/qbs/DarwinTools/darwin-tools.js b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
index 9b81310f0..0a944802d 100644
--- a/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
+++ b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
@@ -260,19 +260,3 @@ function cleanPropertyList(plist) {
cleanPropertyList(plist[key]);
}
}
-
-function _codeSignTimestampFlags(product) {
- // If signingTimestamp is undefined, do not specify the flag at all -
- // this uses the system-specific default behavior
- var signingTimestamp = product.moduleProperty("xcode", "signingTimestamp");
- if (signingTimestamp !== undefined) {
- // If signingTimestamp is an empty string, specify the flag but do
- // not specify a value - this uses a default Apple-provided server
- var flag = "--timestamp";
- if (signingTimestamp)
- flag += "=" + signingTimestamp;
- return [flag];
- }
-
- return [];
-}
diff --git a/share/qbs/imports/qbs/Probes/XcodeProbe.qbs b/share/qbs/imports/qbs/Probes/XcodeProbe.qbs
index e0ed99346..edd67433b 100644
--- a/share/qbs/imports/qbs/Probes/XcodeProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/XcodeProbe.qbs
@@ -48,6 +48,7 @@ Probe {
// Outputs
property var architectureSettings
property var availableSdks
+ property var platformSettings
property string xcodeVersion
configure: {
@@ -97,6 +98,8 @@ Probe {
});
availableSdks = Xcode.sdkInfoList(sdksPath);
+ var platformInfoPlist = FileInfo.joinPaths(platformPath, "Info.plist");
+ platformSettings = Xcode.platformInfo(platformInfoPlist);
found = !!xcodeVersion;
}
}
diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs
index 05e77c81b..63f12db07 100644
--- a/share/qbs/modules/bundle/BundleModule.qbs
+++ b/share/qbs/modules/bundle/BundleModule.qbs
@@ -38,9 +38,11 @@ import qbs.PropertyList
import qbs.TextFile
import qbs.Utilities
import "bundle.js" as Bundle
+import "../codesign/codesign.js" as Codesign
Module {
Depends { name: "xcode"; required: false; }
+ Depends { name: "codesign"; required: false; }
Probe {
id: bundleSettingsProbe
@@ -510,9 +512,9 @@ Module {
multiplex: true
inputs: ["bundle.input",
"aggregate_infoplist", "pkginfo", "hpp",
- "icns", "xcent",
+ "icns", "codesign.xcent",
"compiled_ibdoc", "compiled_assetcatalog",
- "xcode.provisioningprofile.main"]
+ "codesign.embedded_provisioningprofile"]
// Make sure the inputs of this rule are only those rules which produce outputs compatible
// with the type of the bundle being produced.
@@ -540,13 +542,13 @@ Module {
});
}
- for (i in inputs["xcode.provisioningprofile.main"]) {
- var ext = inputs["xcode.provisioningprofile.main"][i].fileName.split('.')[1];
+ var provprofiles = inputs["codesign.embedded_provisioningprofile"];
+ for (i in provprofiles) {
artifacts.push({
filePath: FileInfo.joinPaths(product.destinationDirectory,
ModUtils.moduleProperty(product,
"contentsFolderPath"),
- "embedded." + ext),
+ provprofiles[i].fileName),
fileTags: ["bundle.provisioningprofile", "bundle.content"]
});
}
@@ -613,8 +615,8 @@ Module {
for (var i = 0; i < artifacts.length; ++i)
artifacts[i].bundle = { wrapperPath: wrapperPath };
- if (product.qbs.hostOS.contains("darwin") && product.xcode
- && product.xcode.signingIdentity) {
+ if (product.qbs.hostOS.contains("darwin") && product.codesign
+ && product.codesign.enableCodeSigning) {
artifacts.push({
filePath: FileInfo.joinPaths(product.bundle.contentsFolderPath, "_CodeSignature/CodeResources"),
fileTags: ["bundle.code-signature", "bundle.content"]
@@ -706,18 +708,21 @@ Module {
commands.push(cmd);
}
- var provisioningProfiles = outputs["bundle.provisioningprofile"];
- for (i in provisioningProfiles) {
- cmd = new JavaScriptCommand();
- cmd.description = "copying provisioning profile";
- cmd.highlight = "filegen";
- cmd.source = inputs["xcode.provisioningprofile.main"][i].filePath;
- cmd.destination = provisioningProfiles[i].filePath;
- cmd.sourceCode = function() {
- File.copy(source, destination);
- };
+ cmd = new JavaScriptCommand();
+ cmd.description = "copying provisioning profile";
+ cmd.highlight = "filegen";
+ cmd.sources = (inputs["codesign.embedded_provisioningprofile"] || [])
+ .map(function(artifact) { return artifact.filePath; });
+ cmd.destination = (outputs["bundle.provisioningprofile"] || [])
+ .map(function(artifact) { return artifact.filePath; });
+ cmd.sourceCode = function() {
+ var i;
+ for (var i in sources) {
+ File.copy(sources[i], destination[i]);
+ }
+ };
+ if (cmd.sources && cmd.sources.length)
commands.push(cmd);
- }
cmd = new JavaScriptCommand();
cmd.description = "copying public headers";
@@ -762,34 +767,8 @@ Module {
commands.push(cmd);
if (product.moduleProperty("qbs", "hostOS").contains("darwin")) {
- var actualSigningIdentity = product.moduleProperty("xcode", "actualSigningIdentity");
- var codesignDisplayName = product.moduleProperty("xcode", "actualSigningIdentityDisplayName");
- if (actualSigningIdentity) {
- var args = product.moduleProperty("xcode", "codesignFlags") || [];
- args.push("--force");
- args.push("--sign", actualSigningIdentity);
- args = args.concat(DarwinTools._codeSignTimestampFlags(product));
-
- for (var j in inputs.xcent) {
- args.push("--entitlements", inputs.xcent[j].filePath);
- break; // there should only be one
- }
-
- // If this is a framework, we need to sign its versioned directory
- if (bundleType === "framework") {
- args.push(product.bundle.contentsFolderPath);
- } else {
- args.push(product.bundle.bundleName);
- }
-
- cmd = new Command(product.moduleProperty("xcode", "codesignPath"), args);
- cmd.workingDirectory = product.destinationDirectory;
- cmd.description = "codesign "
- + ModUtils.moduleProperty(product, "bundleName")
- + " using " + codesignDisplayName
- + " (" + actualSigningIdentity + ")";
- commands.push(cmd);
- }
+ Array.prototype.push.apply(commands, Codesign.prepareSign(
+ project, product, inputs, outputs, input, output));
if (bundleType === "application"
&& product.moduleProperty("qbs", "targetOS").contains("macos")) {
diff --git a/share/qbs/modules/codesign/CodeSignModule.qbs b/share/qbs/modules/codesign/CodeSignModule.qbs
new file mode 100644
index 000000000..caa9e2e60
--- /dev/null
+++ b/share/qbs/modules/codesign/CodeSignModule.qbs
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import qbs
+import qbs.File
+import qbs.FileInfo
+import "codesign.js" as CodeSign
+
+Module {
+ condition: false
+
+ property bool enableCodeSigning: false
+
+ property string codesignName
+ property string codesignPath: codesignName
+ property stringList codesignFlags
+}
diff --git a/share/qbs/modules/codesign/apple.qbs b/share/qbs/modules/codesign/apple.qbs
new file mode 100644
index 000000000..dbcd0a215
--- /dev/null
+++ b/share/qbs/modules/codesign/apple.qbs
@@ -0,0 +1,385 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import qbs
+import qbs.BundleTools
+import qbs.DarwinTools
+import qbs.Environment
+import qbs.File
+import qbs.FileInfo
+import qbs.ModUtils
+import qbs.PropertyList
+import qbs.Probes
+import qbs.Utilities
+import "codesign.js" as CodeSign
+import "../xcode/xcode.js" as XcodeUtils
+
+CodeSignModule {
+ Depends { name: "xcode"; required: qbs.toolchain && qbs.toolchain.contains("xcode") }
+
+ Probes.BinaryProbe {
+ id: codesignProbe
+ names: [codesignName]
+ }
+
+ condition: qbs.hostOS.contains("macos") && qbs.targetOS.contains("darwin")
+ priority: 0
+
+ enableCodeSigning: _codeSigningRequired
+
+ codesignName: "codesign"
+ codesignPath: codesignProbe.filePath
+
+ property string signingType: {
+ if (_adHocCodeSigningAllowed)
+ return "ad-hoc";
+ if (_codeSigningAllowed)
+ return "app-store";
+ }
+
+ PropertyOptions {
+ name: "signingType"
+ allowedValues: ["app-store", "apple-id", "ad-hoc"]
+ }
+
+ property string signingIdentity: {
+ if (signingType === "ad-hoc") // only useful on macOS
+ return "-";
+
+ var isDebug = qbs.buildVariant !== "release";
+
+ if (qbs.targetOS.contains("ios") || qbs.targetOS.contains("tvos")
+ || qbs.targetOS.contains("watchos")) {
+ switch (signingType) {
+ case "app-store":
+ return isDebug ? "iPhone Developer" : "iPhone Distribution";
+ }
+ }
+
+ if (qbs.targetOS.contains("macos")) {
+ switch (signingType) {
+ case "app-store":
+ return isDebug ? "Mac Developer" : "3rd Party Mac Developer Application";
+ case "apple-id":
+ return "Developer ID Application";
+ }
+ }
+ }
+
+ property string signingTimestamp: "none"
+
+ property string provisioningProfile
+ PropertyOptions {
+ name: "provisioningProfile"
+ description: "Name or UUID of the provisioning profile to embed in the application; " +
+ "typically left blank to allow automatic provisioning"
+ }
+
+ property string teamIdentifier
+ PropertyOptions {
+ name: "teamIdentifier"
+ description: "Name or identifier of the development team whose identities will be used; " +
+ "typically left blank unless signed into multiple development teams"
+ }
+
+ property path provisioningProfilesPath: "~/Library/MobileDevice/Provisioning Profiles"
+
+ readonly property var _actualSigningIdentity: {
+ if (signingIdentity === "-") {
+ return {
+ SHA1: signingIdentity,
+ subjectInfo: { CN: "ad hoc" }
+ }
+ }
+
+ var identities = CodeSign.findSigningIdentities(signingIdentity, teamIdentifier);
+ if (identities && Object.keys(identities).length > 1) {
+ throw "Multiple codesigning identities (i.e. certificate and private key pairs) " +
+ "matching “" + signingIdentity + "” were found." +
+ CodeSign.humanReadableIdentitySummary(identities);
+ }
+
+ for (var i in identities)
+ return identities[i];
+ }
+
+ // Allowed for macOS
+ readonly property bool _adHocCodeSigningAllowed:
+ XcodeUtils.boolFromSdkOrPlatform("AD_HOC_CODE_SIGNING_ALLOWED",
+ xcode._sdkProps, xcode._platformProps, true)
+
+ // Allowed for all device platforms (not simulators)
+ readonly property bool _codeSigningAllowed:
+ XcodeUtils.boolFromSdkOrPlatform("CODE_SIGNING_ALLOWED",
+ xcode._sdkProps, xcode._platformProps, true)
+
+ // Required for tvOS, iOS, and watchOS (not simulators)
+ property bool _codeSigningRequired: {
+ // allow to override value from Xcode so tests do not require signing
+ var envRequired = Environment.getEnv("QBS_AUTOTEST_CODE_SIGNING_REQUIRED");
+ if (envRequired)
+ return envRequired === "1";
+ return XcodeUtils.boolFromSdkOrPlatform("CODE_SIGNING_REQUIRED",
+ xcode._sdkProps, xcode._platformProps, false)
+ }
+
+ // Required for tvOS, iOS, and watchOS (not simulators)
+ readonly property bool _entitlementsRequired:
+ XcodeUtils.boolFromSdkOrPlatform("ENTITLEMENTS_REQUIRED",
+ xcode._sdkProps, xcode._platformProps, false)
+
+ readonly property bool _provisioningProfileAllowed:
+ product.bundle
+ && product.bundle.isBundle
+ && product.type.contains("application")
+ && xcode.platformType !== "simulator"
+
+ // Required for tvOS, iOS, and watchOS (not simulators)
+ // PROVISIONING_PROFILE_REQUIRED is specified only in Embedded-Device.xcspec in the
+ // IDEiOSSupportCore IDE plugin, so we'll just write out the logic here manually
+ readonly property bool _provisioningProfileRequired:
+ _provisioningProfileAllowed && !qbs.targetOS.contains("macos")
+
+ // Not used on simulator platforms either but provisioning profiles aren't used there anyways
+ readonly property string _provisioningProfilePlatform: {
+ if (qbs.targetOS.contains("macos"))
+ return "OSX";
+ if (qbs.targetOS.contains("ios") || qbs.targetOS.contains("watchos"))
+ return "iOS";
+ if (qbs.targetOS.contains("tvos"))
+ return "tvOS";
+ }
+
+ readonly property string _embeddedProfileName:
+ (xcode._platformProps || {})["EMBEDDED_PROFILE_NAME"]
+
+ setupBuildEnvironment: {
+ var prefixes = product.xcode ? [
+ product.xcode.platformPath + "/Developer",
+ product.xcode.toolchainPath,
+ product.xcode.developerPath
+ ] : [];
+ for (var i = 0; i < prefixes.length; ++i) {
+ var codesign_allocate = prefixes[i] + "/usr/bin/codesign_allocate";
+ if (File.exists(codesign_allocate)) {
+ var v = new ModUtils.EnvironmentVariable("CODESIGN_ALLOCATE");
+ v.value = codesign_allocate;
+ v.set();
+ break;
+ }
+ }
+ }
+
+ Group {
+ name: "Provisioning Profiles"
+ prefix: codesign.provisioningProfilesPath + "/"
+ files: ["*.mobileprovision", "*.provisionprofile"]
+ }
+
+ FileTagger {
+ fileTags: ["codesign.entitlements"]
+ patterns: ["*.entitlements"]
+ }
+
+ FileTagger {
+ fileTags: ["codesign.provisioningprofile"]
+ patterns: ["*.mobileprovision", "*.provisionprofile"]
+ }
+
+ Rule {
+ multiplex: true
+ condition: product.codesign.enableCodeSigning &&
+ product.codesign._provisioningProfileAllowed
+ inputs: ["codesign.provisioningprofile"]
+
+ outputFileTags: ["codesign.embedded_provisioningprofile"]
+ outputArtifacts: {
+ var artifacts = [];
+ var provisioningProfiles = (inputs["codesign.provisioningprofile"] || [])
+ .map(function (a) { return a.filePath; });
+ var bestProfile = CodeSign.findBestProvisioningProfile(product, provisioningProfiles);
+ var uuid = product.provisioningProfile;
+ if (bestProfile) {
+ artifacts.push({
+ filePath: FileInfo.joinPaths(product.destinationDirectory,
+ product.codesign._embeddedProfileName),
+ fileTags: ["codesign.embedded_provisioningprofile"],
+ codesign: {
+ _provisioningProfileFilePath: bestProfile.filePath,
+ _provisioningProfileData: JSON.stringify(bestProfile.data),
+ }
+ });
+ } else if (uuid) {
+ throw "Your build settings specify a provisioning profile with the UUID “"
+ + uuid + "”, however, no such provisioning profile was found.";
+ } else if (product._provisioningProfileRequired) {
+ var hasProfiles = !!((inputs["codesign.provisioningprofile"] || []).length);
+ var teamIdentifier = product.teamIdentifier;
+ var codeSignIdentity = product.signingIdentity;
+ if (hasProfiles) {
+ if (codeSignIdentity) {
+ console.warn("No provisioning profiles matching the bundle identifier “"
+ + product.bundle.identifier
+ + "” were found.");
+ } else {
+ console.warn("No provisioning profiles matching an applicable signing "
+ + "identity were found.");
+ }
+ } else {
+ if (codeSignIdentity) {
+ if (teamIdentifier) {
+ console.warn("No provisioning profiles with a valid signing identity "
+ + "(i.e. certificate and private key pair) matching the "
+ + "team ID “" + teamIdentifier + "” were found.")
+ } else {
+ console.warn("No provisioning profiles with a valid signing identity "
+ + "(i.e. certificate and private key pair) were found.");
+ }
+ } else {
+ console.warn("No non–expired provisioning profiles were found.");
+ }
+ }
+ }
+ return artifacts;
+ }
+
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ var data = JSON.parse(output.codesign._provisioningProfileData);
+ cmd.source = output.codesign._provisioningProfileFilePath;
+ cmd.destination = output.filePath;
+ cmd.description = "using provisioning profile " + data.Name + " (" + data.UUID + ")";
+ cmd.highlight = "filegen";
+ cmd.sourceCode = function() {
+ File.copy(source, destination);
+ };
+ return [cmd];
+ }
+ }
+
+ Rule {
+ multiplex: true
+ condition: product.codesign.enableCodeSigning
+ inputs: ["codesign.entitlements", "codesign.embedded_provisioningprofile"]
+
+ Artifact {
+ filePath: FileInfo.joinPaths(product.destinationDirectory,
+ product.targetName + ".xcent")
+ fileTags: ["codesign.xcent"]
+ }
+
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating entitlements";
+ cmd.highlight = "codegen";
+ cmd.bundleIdentifier = product.bundle.identifier;
+ cmd.signingEntitlements = (inputs["codesign.entitlements"] || [])
+ .map(function (a) { return a.filePath; });
+ cmd.provisioningProfiles = (inputs["codesign.embedded_provisioningprofile"] || [])
+ .map(function (a) { return a.filePath; });
+ cmd.platformPath = product.xcode ? product.xcode.platformPath : undefined;
+ cmd.sdkPath = product.xcode ? product.xcode.sdkPath : undefined;
+ cmd.sourceCode = function() {
+ var i;
+ var provData = {};
+ var provisionProfiles = inputs["codesign.embedded_provisioningprofile"];
+ for (i in provisionProfiles) {
+ var plist = new PropertyList();
+ try {
+ plist.readFromData(Utilities.smimeMessageContent(
+ provisionProfiles[i].filePath));
+ provData = plist.toObject();
+ } finally {
+ plist.clear();
+ }
+ }
+
+ var aggregateEntitlements = {};
+
+ // Start building up an aggregate entitlements plist from the files in the SDKs,
+ // which contain placeholders in the same manner as Info.plist
+ function entitlementsFileContents(path) {
+ return File.exists(path) ? BundleTools.infoPlistContents(path) : undefined;
+ }
+ var entitlementsSources = [];
+ if (platformPath) {
+ entitlementsSources.push(
+ entitlementsFileContents(
+ FileInfo.joinPaths(platformPath, "Entitlements.plist")));
+ }
+ if (sdkPath) {
+ entitlementsSources.push(
+ entitlementsFileContents(
+ FileInfo.joinPaths(sdkPath, "Entitlements.plist")));
+ }
+
+ for (i = 0; i < signingEntitlements.length; ++i) {
+ entitlementsSources.push(entitlementsFileContents(signingEntitlements[i]));
+ }
+
+ for (i = 0; i < entitlementsSources.length; ++i) {
+ var contents = entitlementsSources[i];
+ for (var key in contents) {
+ if (contents.hasOwnProperty(key))
+ aggregateEntitlements[key] = contents[key];
+ }
+ }
+
+ contents = provData["Entitlements"];
+ for (key in contents) {
+ if (contents.hasOwnProperty(key) && !aggregateEntitlements.hasOwnProperty(key))
+ aggregateEntitlements[key] = contents[key];
+ }
+
+ // Expand entitlements variables with data from the provisioning profile
+ var env = {
+ "AppIdentifierPrefix": (provData["ApplicationIdentifierPrefix"] || "") + ".",
+ "CFBundleIdentifier": bundleIdentifier
+ };
+ DarwinTools.expandPlistEnvironmentVariables(aggregateEntitlements, env, true);
+
+ // Anything with an undefined or otherwise empty value should be removed
+ // Only JSON-formatted plists can have null values, other formats error out
+ // This also follows Xcode behavior
+ DarwinTools.cleanPropertyList(aggregateEntitlements);
+
+ var plist = new PropertyList();
+ try {
+ plist.readFromObject(aggregateEntitlements);
+ plist.writeToFile(outputs["codesign.xcent"][0].filePath, "xml1");
+ } finally {
+ plist.clear();
+ }
+ };
+ return [cmd];
+ }
+ }
+}
diff --git a/share/qbs/modules/codesign/codesign.js b/share/qbs/modules/codesign/codesign.js
new file mode 100644
index 000000000..1d039e7ca
--- /dev/null
+++ b/share/qbs/modules/codesign/codesign.js
@@ -0,0 +1,279 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+var File = require("qbs.File");
+var FileInfo = require("qbs.FileInfo");
+var PathTools = require("qbs.PathTools");
+var PropertyList = require("qbs.PropertyList");
+var Utilities = require("qbs.Utilities");
+
+function findSigningIdentities(searchString, team) {
+ if (!searchString)
+ return {};
+ var identities = Utilities.signingIdentities();
+ var matchedIdentities = {};
+ for (var key in identities) {
+ var identity = identities[key];
+ if (team && ![identity.subjectInfo.O, identity.subjectInfo.OU].contains(team))
+ continue;
+ if (searchString === key || identity.subjectInfo.CN.startsWith(searchString))
+ matchedIdentities[key] = identity;
+ }
+ return matchedIdentities;
+}
+
+function humanReadableIdentitySummary(identities) {
+ return "\n\t" + Object.keys(identities).map(function (key) {
+ return identities[key].subjectInfo.CN
+ + " in team "
+ + identities[key].subjectInfo.O
+ + " (" + identities[key].subjectInfo.OU + ")";
+ }).join("\n\t");
+}
+
+/**
+ * Returns the best provisioning profile for code signing a binary with the given parameters.
+ * Ideally, this should behave identically as Xcode but the algorithm is not documented
+ * \l{https://developer.apple.com/library/ios/qa/qa1814/_index.html}{Automatic Provisioning}
+ */
+function findBestProvisioningProfile(product, files) {
+ var actualSigningIdentity = product.codesign._actualSigningIdentity || {};
+ var teamIdentifier = product.codesign.teamIdentifier;
+ var bundleIdentifier = product.bundle.identifier;
+ var targetOS = product.qbs.targetOS;
+ var buildVariant = product.qbs.buildVariant;
+ var query = product.codesign.provisioningProfile;
+ var profilePlatform = product.codesign._provisioningProfilePlatform;
+
+ // Read all provisioning profiles on disk into plist objects in memory
+ var profiles = files.map(function(filePath) {
+ var plist = new PropertyList();
+ try {
+ plist.readFromData(Utilities.smimeMessageContent(filePath));
+ return {
+ data: plist.toObject(),
+ filePath: filePath
+ };
+ } finally {
+ plist.clear();
+ }
+ });
+
+ // Do a simple search by matching UUID or Name
+ if (query) {
+ for (var i = 0; i < profiles.length; ++i) {
+ var obj = profiles[i];
+ if (obj.data && (obj.data.UUID === query || obj.data.Name === query))
+ return obj;
+ }
+
+ // If we asked for a specific provisioning profile, don't select one automatically
+ return undefined;
+ }
+
+ // Provisioning profiles are not normally used with ad-hoc code signing or non-apps
+ // We do these checks down here only for the automatic selection but not above because
+ // if the user explicitly selects a provisioning profile it should be used no matter what
+ if (actualSigningIdentity.SHA1 === "-" || !product.type.contains("application"))
+ return undefined;
+
+ // Filter out any provisioning profiles we know to be unsuitable from the start
+ profiles = profiles.filter(function (profile) {
+ var data = profile.data;
+
+ if (actualSigningIdentity.subjectInfo) {
+ var certCommonNames = (data["DeveloperCertificates"] || []).map(function (cert) {
+ return Utilities.certificateInfo(cert).subjectInfo.CN;
+ });
+ if (!certCommonNames.contains(actualSigningIdentity.subjectInfo.CN)) {
+ console.log("Skipping provisioning profile with no matching certificate names for '"
+ + actualSigningIdentity.subjectInfo.CN
+ + "' (found " + certCommonNames.join(", ") + "): "
+ + profile.filePath);
+ return false;
+ }
+ }
+
+ var platforms = data["Platform"] || [];
+ if (platforms.length > 0 && profilePlatform && !platforms.contains(profilePlatform)) {
+ console.log("Skipping provisioning profile for platform " + platforms.join(", ")
+ + " (current platform " + profilePlatform + ")"
+ + ": " + profile.filePath);
+ return false;
+ }
+
+ if (teamIdentifier
+ && !data["TeamIdentifier"].contains(teamIdentifier)
+ && data["TeamName"] !== teamIdentifier) {
+ console.log("Skipping provisioning profile for team " + data["TeamIdentifier"]
+ + " (" + data["TeamName"] + ") (current team " + teamIdentifier + ")"
+ + ": " + profile.filePath);
+ return false;
+ }
+
+ if (Date.parse(data["ExpirationDate"]) <= Date.now()) {
+ console.log("Skipping expired provisioning profile: " + profile.filePath);
+ return false;
+ }
+
+ // Filter development vs distribution profiles;
+ // though the certificate common names check should have been sufficient
+ var isDebug = buildVariant === "debug";
+ if (data["Entitlements"]["get-task-allow"] !== isDebug) {
+ console.log("Skipping provisioning profile for wrong debug mode: " + profile.filePath);
+ return false;
+ }
+
+ var prefix = data["ApplicationIdentifierPrefix"];
+ var fullAppId = data["Entitlements"]["application-identifier"];
+ if ([prefix, bundleIdentifier].join(".") !== fullAppId
+ && [prefix, "*"].join(".") !== fullAppId) {
+ console.log("Skipping provisioning profile not matching full ("
+ + [prefix, bundleIdentifier].join(".") + ") or wildcard ("
+ + [prefix, "*"].join(".") + ") app ID (found " + fullAppId + "): "
+ + profile.filePath);
+ return false;
+ }
+
+ return true;
+ });
+
+ // Sort by expiration date - sooner expiration dates come last
+ profiles.sort(function(profileA, profileB) {
+ var expA = Date.parse(profileA.data["ExpirationDate"]);
+ var expB = Date.parse(profileB.data["ExpirationDate"]);
+ if (expA < expB)
+ return -1;
+ if (expA > expB)
+ return 1;
+ return 0;
+ });
+
+ // Sort by application identifier - wildcard profiles come last
+ profiles.sort(function(profileA, profileB) {
+ var idA = profileA.data["Entitlements"]["application-identifier"];
+ var idB = profileB.data["Entitlements"]["application-identifier"];
+ if (!idA.endsWith(".*") && idB.endsWith(".*"))
+ return -1;
+ if (idA.endsWith(".*") && !idB.endsWith(".*"))
+ return 1;
+ return 0;
+ });
+
+ if (profiles.length) {
+ console.log("Automatic provisioning using profile "
+ + profiles[0].data.UUID
+ + " ("
+ + profiles[0].data.TeamName
+ + " - "
+ + profiles[0].data.Name
+ + ") in product "
+ + product.name);
+ return profiles[0];
+ }
+}
+
+function prepareSign(project, product, inputs, outputs, input, output) {
+ var cmd, cmds = [];
+
+ if (!product.codesign.enableCodeSigning)
+ return cmds;
+
+ var isBundle = "bundle.content" in outputs;
+ var outputFilePath = isBundle
+ ? FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName)
+ : outputs["codesign.signed_artifact"][0].filePath;
+ var outputFileName = isBundle
+ ? product.bundle.bundleName
+ : outputs["codesign.signed_artifact"][0].fileName;
+ var isProductBundle = product.bundle && product.bundle.isBundle;
+
+ // If the product is a bundle, just sign the bundle
+ // instead of signing the bundle and executable separately
+ var shouldSignArtifact = !isProductBundle || isBundle;
+
+ var enableCodeSigning = product.codesign.enableCodeSigning;
+ if (enableCodeSigning && shouldSignArtifact) {
+ var actualSigningIdentity = product.codesign._actualSigningIdentity;
+ if (!actualSigningIdentity) {
+ throw "No codesigning identities (i.e. certificate and private key pairs) matching “"
+ + product.codesign.signingIdentity + "” were found.";
+ }
+
+ // If this is a framework, we need to sign its versioned directory
+ var subpath = "";
+ if (isBundle) {
+ var frameworkVersion = product.bundle.frameworkVersion;
+ if (frameworkVersion) {
+ subpath = product.bundle.contentsFolderPath;
+ subpath = subpath.substring(product.bundle.bundleName.length);
+ }
+ }
+
+ var args = product.codesign.codesignFlags || [];
+ args.push("--force");
+ args.push("--sign", actualSigningIdentity.SHA1);
+
+ // If signingTimestamp is undefined, do not specify the flag at all -
+ // this uses the system-specific default behavior
+ var signingTimestamp = product.codesign.signingTimestamp;
+ if (signingTimestamp !== undefined) {
+ // If signingTimestamp is an empty string, specify the flag but do
+ // not specify a value - this uses a default Apple-provided server
+ var flag = "--timestamp";
+ if (signingTimestamp)
+ flag += "=" + signingTimestamp;
+ args.push(flag);
+ }
+
+ for (var j in inputs["codesign.xcent"]) {
+ args.push("--entitlements", inputs["codesign.xcent"][j].filePath);
+ break; // there should only be one
+ }
+ args.push(outputFilePath + subpath);
+ cmd = new Command(product.codesign.codesignPath, args);
+ cmd.description = "codesign " + outputFileName
+ + " (" + actualSigningIdentity.subjectInfo.CN + ")";
+ cmd.outputFilePath = outputFilePath;
+ cmd.stderrFilterFunction = function(stderr) {
+ return stderr.replace(outputFilePath + ": replacing existing signature\n", "");
+ };
+ cmds.push(cmd);
+ }
+
+ if (isBundle) {
+ cmd = new Command("touch", ["-c", outputFilePath]);
+ cmd.silent = true;
+ cmds.push(cmd);
+ }
+
+ return cmds;
+}
diff --git a/share/qbs/modules/codesign/noop.qbs b/share/qbs/modules/codesign/noop.qbs
new file mode 100644
index 000000000..3234d7476
--- /dev/null
+++ b/share/qbs/modules/codesign/noop.qbs
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+import qbs
+
+CodeSignModule {
+ condition: true
+ priority: -100
+}
diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs
index 702a1ad93..34a0b47de 100644
--- a/share/qbs/modules/cpp/GenericGCC.qbs
+++ b/share/qbs/modules/cpp/GenericGCC.qbs
@@ -44,6 +44,8 @@ CppModule {
condition: qbs.toolchain && qbs.toolchain.contains("gcc")
priority: -100
+ Depends { name: "codesign" }
+
Probes.GccBinaryProbe {
id: compilerPathProbe
condition: !toolchainInstallPath && !_skipAllChecks
@@ -400,12 +402,15 @@ CppModule {
"bundle.input",
"dynamiclibrary", "dynamiclibrary_symlink", "dynamiclibrary_symbols", "debuginfo_dll",
"debuginfo_bundle","dynamiclibrary_import", "debuginfo_plist",
+ "codesign.signed_artifact",
]
outputArtifacts: {
var artifacts = [{
filePath: product.destinationDirectory + "/"
+ PathTools.dynamicLibraryFilePath(product),
- fileTags: ["bundle.input", "dynamiclibrary"],
+ fileTags: ["bundle.input", "dynamiclibrary"]
+ .concat(product.codesign.enableCodeSigning
+ ? ["codesign.signed_artifact"] : []),
bundle: {
_bundleFilePath: product.destinationDirectory + "/"
+ PathTools.bundleExecutableFilePath(product)
@@ -510,12 +515,14 @@ CppModule {
inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"]
outputFileTags: ["bundle.input", "loadablemodule", "debuginfo_loadablemodule",
- "debuginfo_bundle","debuginfo_plist"]
+ "debuginfo_bundle", "debuginfo_plist", "codesign.signed_artifact"]
outputArtifacts: {
var app = {
filePath: FileInfo.joinPaths(product.destinationDirectory,
PathTools.loadableModuleFilePath(product)),
- fileTags: ["bundle.input", "loadablemodule"],
+ fileTags: ["bundle.input", "loadablemodule"]
+ .concat(product.codesign.enableCodeSigning
+ ? ["codesign.signed_artifact"] : []),
bundle: {
_bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
PathTools.bundleExecutableFilePath(product))
@@ -547,13 +554,14 @@ CppModule {
}
inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"]
- outputFileTags: ["bundle.input", "application", "debuginfo_app","debuginfo_bundle",
- "debuginfo_plist", "mem_map"]
+ outputFileTags: ["bundle.input", "application", "debuginfo_app", "debuginfo_bundle",
+ "debuginfo_plist", "mem_map", "codesign.signed_artifact"]
outputArtifacts: {
var app = {
filePath: FileInfo.joinPaths(product.destinationDirectory,
PathTools.applicationFilePath(product)),
- fileTags: ["bundle.input", "application"],
+ fileTags: ["bundle.input", "application"].concat(
+ product.codesign.enableCodeSigning ? ["codesign.signed_artifact"] : []),
bundle: {
_bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
PathTools.bundleExecutableFilePath(product))
diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js
index f49609f94..99452b6da 100644
--- a/share/qbs/modules/cpp/gcc.js
+++ b/share/qbs/modules/cpp/gcc.js
@@ -28,6 +28,7 @@
**
****************************************************************************/
+var Codesign = require("../codesign/codesign.js");
var Cpp = require("cpp.js");
var File = require("qbs.File");
var FileInfo = require("qbs.FileInfo");
@@ -1384,28 +1385,8 @@ function prepareLinker(project, product, inputs, outputs, input, output) {
}
}
- if (product.xcode && product.bundle) {
- var actualSigningIdentity = product.xcode.actualSigningIdentity;
- var codesignDisplayName = product.xcode.actualSigningIdentityDisplayName;
- if (actualSigningIdentity && !product.bundle.isBundle) {
- args = product.xcode.codesignFlags || [];
- args.push("--force");
- args.push("--sign", actualSigningIdentity);
- args = args.concat(DarwinTools._codeSignTimestampFlags(product));
-
- for (var j in inputs.xcent) {
- args.push("--entitlements", inputs.xcent[j].filePath);
- break; // there should only be one
- }
- args.push(primaryOutput.filePath);
- cmd = new Command(product.xcode.codesignPath, args);
- cmd.description = "codesign "
- + primaryOutput.fileName
- + " using " + codesignDisplayName
- + " (" + actualSigningIdentity + ")";
- commands.push(cmd);
- }
- }
+ Array.prototype.push.apply(
+ commands, Codesign.prepareSign(project, product, inputs, outputs, input, output));
return commands;
}
diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js
index 9c87e09dc..1060894d4 100644
--- a/share/qbs/modules/xcode/xcode.js
+++ b/share/qbs/modules/xcode/xcode.js
@@ -93,6 +93,16 @@ var XcodeArchSpecsReader = (function () {
return XcodeArchSpecsReader;
}());
+function platformInfo(platformInfoPlist) {
+ var propertyList = new PropertyList();
+ try {
+ propertyList.readFromFile(platformInfoPlist);
+ return propertyList.toObject();
+ } finally {
+ propertyList.clear();
+ }
+}
+
function sdkInfoList(sdksPath) {
var sdkInfo = [];
var sdks = File.directoryEntries(sdksPath, File.Dirs | File.NoDotAndDotDot);
@@ -181,6 +191,17 @@ function provisioningProfilePlistContents(filePath) {
}
}
+function boolFromSdkOrPlatform(varName, sdkProps, platformProps, defaultValue) {
+ var values = [(sdkProps || {})[varName], (platformProps || {})[varName]];
+ for (var i = 0; i < values.length; ++i) {
+ if (values[i] === "YES")
+ return true;
+ if (values[i] === "NO")
+ return false;
+ }
+ return defaultValue;
+}
+
function archsSpecsPath(version, targetOS, platformType, platformPath, devicePlatformPath) {
var _specsPluginBaseName;
if (Utilities.versionCompare(version, "12") >= 0) {
diff --git a/share/qbs/modules/xcode/xcode.qbs b/share/qbs/modules/xcode/xcode.qbs
index e4df1f20b..72120ed37 100644
--- a/share/qbs/modules/xcode/xcode.qbs
+++ b/share/qbs/modules/xcode/xcode.qbs
@@ -7,7 +7,6 @@ import qbs.ModUtils
import qbs.Probes
import qbs.PropertyList
import qbs.Utilities
-import 'xcode.js' as Xcode
Module {
id: xcodeModule
@@ -80,31 +79,6 @@ Module {
}
}
- property string signingIdentity
- readonly property string actualSigningIdentity: {
- if (_actualSigningIdentity && _actualSigningIdentity.length === 2)
- return _actualSigningIdentity[0];
- }
-
- readonly property string actualSigningIdentityDisplayName: {
- if (_actualSigningIdentity && _actualSigningIdentity.length === 2)
- return _actualSigningIdentity[1];
- }
-
- property string signingTimestamp: "none"
-
- property string provisioningProfile
-
- property string xcodebuildName: "xcodebuild"
- property string xcodebuildPath: FileInfo.joinPaths(developerPath, "usr", "bin", xcodebuildName)
-
- property string securityName: "security"
- property string securityPath: securityName
-
- property string codesignName: "codesign"
- property string codesignPath: codesignName
- property stringList codesignFlags
-
readonly property path toolchainPath: FileInfo.joinPaths(toolchainsPath,
"XcodeDefault" + ".xctoolchain")
readonly property path platformPath: FileInfo.joinPaths(platformsPath,
@@ -136,35 +110,11 @@ Module {
readonly property path toolchainInfoPlist: FileInfo.joinPaths(toolchainPath,
"ToolchainInfo.plist")
- readonly property stringList _actualSigningIdentity: {
- if (/^[A-Fa-f0-9]{40}$/.test(signingIdentity)) {
- return [signingIdentity, signingIdentity];
- }
-
- var result = [];
-
- if (signingIdentity) {
- var identities = Utilities.signingIdentities();
- for (var key in identities) {
- if (identities[key].subjectInfo.CN === signingIdentity) {
- result.push([key, signingIdentity]);
- }
- }
-
- if (result.length == 0) {
- throw "Unable to find signingIdentity '" + signingIdentity + "'";
- }
-
- if (result.length > 1) {
- throw "Signing identity '" + signingIdentity + "' is ambiguous";
- }
- }
-
- return result[0];
- }
+ readonly property var _platformSettings: xcodeProbe.platformSettings
- property path provisioningProfilesPath: {
- return FileInfo.joinPaths(Environment.getEnv("HOME"), "Library/MobileDevice/Provisioning Profiles");
+ readonly property var _platformProps: {
+ if (_platformSettings)
+ return _platformSettings["DefaultProperties"];
}
readonly property stringList standardArchitectures: _architectureSettings["ARCHS_STANDARD"]
@@ -190,6 +140,12 @@ Module {
}
}
+ readonly property var _sdkProps: {
+ if (_sdkSettings) {
+ return _sdkSettings["DefaultProperties"];
+ }
+ }
+
qbs.sysroot: sdkPath
validate: {
@@ -224,23 +180,10 @@ Module {
validator.validate();
}
- property var buildEnv: {
- var env = {
- "DEVELOPER_DIR": developerPath,
- "SDKROOT": sdkPath
- };
-
- var prefixes = [platformPath + "/Developer", toolchainPath, developerPath];
- for (var i = 0; i < prefixes.length; ++i) {
- var codesign_allocate = prefixes[i] + "/usr/bin/codesign_allocate";
- if (File.exists(codesign_allocate)) {
- env["CODESIGN_ALLOCATE"] = codesign_allocate;
- break;
- }
- }
-
- return env;
- }
+ property var buildEnv: ({
+ "DEVELOPER_DIR": developerPath,
+ "SDKROOT": sdkPath
+ })
setupBuildEnvironment: {
var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, false);
@@ -254,191 +197,4 @@ Module {
v.set();
}
}
-
- Group {
- name: "Provisioning Profiles"
- prefix: xcode.provisioningProfilesPath + "/"
- files: ["*.mobileprovision", "*.provisionprofile"]
- fileTags: ["xcode.provisioningprofile"]
- }
-
- FileTagger {
- fileTags: ["xcode.entitlements"]
- patterns: ["*.entitlements"]
- }
-
- FileTagger {
- fileTags: ["xcode.provisioningprofile"]
- patterns: ["*.mobileprovision", "*.provisionprofile"]
- }
-
- Rule {
- inputs: ["xcode.provisioningprofile"]
-
- Artifact {
- filePath: FileInfo.joinPaths("provisioning-profiles", input.fileName + ".xml")
- fileTags: ["xcode.provisioningprofile.data"]
- }
-
- prepare: {
- var cmds = [];
-
- var cmd = new Command("openssl", ["smime", "-verify", "-noverify", "-inform", "DER",
- "-in", input.filePath, "-out", output.filePath]);
- cmd.silent = true;
- cmd.stderrFilterFunction = function (output) {
- return output.replace("Verification successful\n", "");
- };
- cmds.push(cmd);
-
- cmd = new JavaScriptCommand();
- cmd.silent = true;
- cmd.inputFilePath = input.filePath;
- cmd.outputFilePath = output.filePath;
- cmd.sourceCode = function() {
- var propertyList = new PropertyList();
- try {
- propertyList.readFromFile(outputFilePath);
- propertyList.readFromObject({
- data: propertyList.toObject(),
- fileName: FileInfo.fileName(inputFilePath),
- filePath: inputFilePath
- });
- propertyList.writeToFile(outputFilePath, "xml1");
- } finally {
- propertyList.clear();
- }
- };
- cmds.push(cmd);
-
- return cmds;
- }
- }
-
- Rule {
- multiplex: true
- inputs: ["xcode.provisioningprofile.data"]
- outputFileTags: ["xcode.provisioningprofile.main", "xcode.provisioningprofile.data.main"]
-
- outputArtifacts: {
- var artifacts = [];
- for (var i = 0; i < inputs["xcode.provisioningprofile.data"].length; ++i) {
- var dataFile = inputs["xcode.provisioningprofile.data"][i].filePath;
- var query = product.moduleProperty("xcode", "provisioningProfile");
- var obj = Xcode.provisioningProfilePlistContents(dataFile);
- if (obj && obj.data && (obj.data.UUID === query || obj.data.Name === query)) {
- console.log("Using provisioning profile: " + obj.filePath);
- artifacts.push({
- filePath: obj.fileName,
- fileTags: ["xcode.provisioningprofile.main"],
- qbs: { _inputFilePath: obj.filePath }
- });
-
- artifacts.push({
- filePath: obj.fileName + ".xml",
- fileTags: ["xcode.provisioningprofile.data.main"],
- qbs: { _inputFilePath: dataFile }
- });
- }
- }
- return artifacts;
- }
-
- prepare: {
- var cmds = [];
- for (var tag in outputs) {
- for (var i = 0; i < outputs[tag].length; ++i) {
- var output = outputs[tag][i];
- var cmd = new JavaScriptCommand();
- cmd.silent = true;
- cmd.inputFilePath = output.qbs._inputFilePath; // there's no such prop in qbs, see QBS-754
- cmd.outputFilePath = output.filePath;
- cmd.sourceCode = function() {
- File.copy(inputFilePath, outputFilePath);
- };
- cmds.push(cmd);
- }
- }
- return cmds;
- }
- }
-
- Rule {
- inputs: ["xcode.entitlements", "xcode.provisioningprofile.data.main"]
-
- Artifact {
- filePath: FileInfo.joinPaths(product.destinationDirectory,
- product.targetName + ".xcent")
- fileTags: ["xcent"]
- }
-
- prepare: {
- var cmd = new JavaScriptCommand();
- cmd.description = "generating entitlements";
- cmd.highlight = "codegen";
- cmd.bundleIdentifier = product.moduleProperty("bundle", "identifier");
- cmd.signingEntitlements = inputs["xcode.entitlements"]
- ? inputs["xcode.entitlements"].map(function (a) { return a.filePath; })
- : [];
- cmd.platformPath = ModUtils.moduleProperty(product, "platformPath");
- cmd.sdkPath = ModUtils.moduleProperty(product, "sdkPath");
- cmd.sourceCode = function() {
- var i;
- var provData = Xcode.provisioningProfilePlistContents(input.filePath);
- if (provData)
- provData = provData.data;
-
- var aggregateEntitlements = {};
-
- // Start building up an aggregate entitlements plist from the files in the SDKs,
- // which contain placeholders in the same manner as Info.plist
- function entitlementsFileContents(path) {
- return File.exists(path) ? BundleTools.infoPlistContents(path) : undefined;
- }
- var entitlementsSources = [
- entitlementsFileContents(FileInfo.joinPaths(platformPath, "Entitlements.plist")),
- entitlementsFileContents(FileInfo.joinPaths(sdkPath, "Entitlements.plist"))
- ];
-
- for (i = 0; i < signingEntitlements.length; ++i) {
- entitlementsSources.push(entitlementsFileContents(signingEntitlements[i]));
- }
-
- for (i = 0; i < entitlementsSources.length; ++i) {
- var contents = entitlementsSources[i];
- for (var key in contents) {
- if (contents.hasOwnProperty(key))
- aggregateEntitlements[key] = contents[key];
- }
- }
-
- contents = provData["Entitlements"];
- for (key in contents) {
- if (contents.hasOwnProperty(key) && !aggregateEntitlements.hasOwnProperty(key))
- aggregateEntitlements[key] = contents[key];
- }
-
- // Expand entitlements variables with data from the provisioning profile
- var env = {
- "AppIdentifierPrefix": provData["ApplicationIdentifierPrefix"] + ".",
- "CFBundleIdentifier": bundleIdentifier
- };
- DarwinTools.expandPlistEnvironmentVariables(aggregateEntitlements, env, true);
-
- // Anything with an undefined or otherwise empty value should be removed
- // Only JSON-formatted plists can have null values, other formats error out
- // This also follows Xcode behavior
- DarwinTools.cleanPropertyList(aggregateEntitlements);
-
- var plist = new PropertyList();
- try {
- plist.readFromObject(aggregateEntitlements);
- plist.writeToFile(outputs.xcent[0].filePath, "xml1");
- } finally {
- plist.clear();
- }
- };
- return [cmd];
- }
- }
}
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 7a585f641..a51eb3e0c 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -2507,7 +2507,9 @@ qbs::SetupProjectParameters TestApi::defaultSetupParameters(const QString &proje
}
qbs::SetupProjectParameters setupParams;
- setupParams.setEnvironment(QProcessEnvironment::systemEnvironment());
+ auto environment = QProcessEnvironment::systemEnvironment();
+ environment.insert("QBS_AUTOTEST_CODE_SIGNING_REQUIRED", "0");
+ setupParams.setEnvironment(environment);
setupParams.setProjectFilePath(projectFilePath);
setupParams.setPropertyCheckingMode(qbs::ErrorHandlingMode::Strict);
setupParams.setOverrideBuildGraphData(true);
diff --git a/tests/auto/blackbox/testdata-apple/codesign/app.cpp b/tests/auto/blackbox/testdata-apple/codesign/app.cpp
new file mode 100644
index 000000000..76e819701
--- /dev/null
+++ b/tests/auto/blackbox/testdata-apple/codesign/app.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs b/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs
new file mode 100644
index 000000000..312e9f001
--- /dev/null
+++ b/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs
@@ -0,0 +1,38 @@
+Project {
+ name: "p"
+
+ property bool isBundle: true
+ property bool enableSigning: true
+
+ CppApplication {
+ name: "A"
+ bundle.isBundle: project.isBundle
+ files: "app.cpp"
+ codesign.enableCodeSigning: project.enableSigning
+ codesign.signingType: "ad-hoc"
+ install: true
+ installDir: ""
+ }
+
+ DynamicLibrary {
+ Depends { name: "cpp" }
+ name: "B"
+ bundle.isBundle: project.isBundle
+ files: "app.cpp"
+ codesign.enableCodeSigning: project.enableSigning
+ codesign.signingType: "ad-hoc"
+ install: true
+ installDir: ""
+ }
+
+ LoadableModule {
+ Depends { name: "cpp" }
+ name: "C"
+ bundle.isBundle: project.isBundle
+ files: "app.cpp"
+ codesign.enableCodeSigning: project.enableSigning
+ codesign.signingType: "ad-hoc"
+ install: true
+ installDir: ""
+ }
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index 20116ff56..2a3c40124 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -2173,7 +2173,7 @@ void TestBlackbox::trackExternalProductChanges()
const QStringList toolchainTypes = profileToolchain(profile);
if (!toolchainTypes.contains("gcc"))
QSKIP("Need GCC-like compiler to run this test");
- params.environment = QProcessEnvironment::systemEnvironment();
+ params.environment = QbsRunParameters::defaultEnvironment();
params.environment.insert("INCLUDE_PATH_TEST", "1");
params.expectFailure = true;
QVERIFY(runQbs(params) != 0);
@@ -6054,7 +6054,7 @@ void TestBlackbox::qbsSession()
QJsonObject overriddenValues;
overriddenValues.insert("products.theLib.cpp.cxxLanguageVersion", "c++17");
resolveMessage.insert("overridden-properties", overriddenValues);
- resolveMessage.insert("environment", envToJson(QProcessEnvironment::systemEnvironment()));
+ resolveMessage.insert("environment", envToJson(QbsRunParameters::defaultEnvironment()));
resolveMessage.insert("data-mode", "only-if-changed");
resolveMessage.insert("log-time", true);
resolveMessage.insert("module-properties",
diff --git a/tests/auto/blackbox/tst_blackboxapple.cpp b/tests/auto/blackbox/tst_blackboxapple.cpp
index adde389ec..8915ac8b2 100644
--- a/tests/auto/blackbox/tst_blackboxapple.cpp
+++ b/tests/auto/blackbox/tst_blackboxapple.cpp
@@ -140,6 +140,39 @@ static QString findFatLibrary(const QString &dir, const QString &libraryName)
return {};
}
+enum class CodeSignResult { Failed = 0, Signed, Unsigned };
+using CodeSignData = QMap<QByteArray, QByteArray>;
+static std::pair<CodeSignResult, CodeSignData> parseCodeSignOutput(const QByteArray &output)
+{
+ CodeSignData data;
+ if (output.contains("code object is not signed at all"))
+ return {CodeSignResult::Unsigned, data};
+ const auto lines = output.split('\n');
+ for (const auto &line: lines) {
+ if (line.isEmpty()
+ || line.startsWith("CodeDirectory")
+ || line.startsWith("Sealed Resources")
+ || line.startsWith("Internal requirements")) {
+ continue;
+ }
+ const int index = line.indexOf('=');
+ if (index == -1)
+ return {CodeSignResult::Failed, {}};
+ data[line.mid(0, index)] = line.mid(index + 1);
+ }
+ return {CodeSignResult::Signed, data};
+}
+
+static std::pair<CodeSignResult, CodeSignData> getCodeSignInfo(const QString &path)
+{
+ QProcess codesign;
+ codesign.start("codesign", { QStringLiteral("-dv"), path });
+ if (!codesign.waitForStarted() || !codesign.waitForFinished())
+ return {CodeSignResult::Failed, {}};
+ const auto output = codesign.readAllStandardError();
+ return parseCodeSignOutput(output);
+}
+
TestBlackboxApple::TestBlackboxApple()
: TestBlackboxBase (SRCDIR "/testdata-apple", "blackbox-apple")
{
@@ -680,6 +713,72 @@ void TestBlackboxApple::bundleStructure_data()
QTest::newRow("G") << "G" << "com.apple.product-type.in-app-purchase-content";
}
+void TestBlackboxApple::codesign()
+{
+ QFETCH(bool, isBundle);
+ QFETCH(bool, enableSigning);
+
+ QDir::setCurrent(testDataDir + "/codesign");
+ QbsRunParameters params(QStringList{"qbs.installPrefix:''"});
+ params.arguments
+ << QStringLiteral("project.isBundle:%1").arg(isBundle ? "true" : "false");
+ params.arguments
+ << QStringLiteral("project.enableSigning:%1").arg(enableSigning ? "true" : "false");
+
+ rmDirR(relativeBuildDir());
+ QCOMPARE(runQbs(params), 0);
+
+ const auto appName = isBundle ? QStringLiteral("A.app") : QStringLiteral("A");
+ const auto appPath = defaultInstallRoot + "/" + appName;
+ QVERIFY(QFileInfo(appPath).exists());
+ auto codeSignInfo = getCodeSignInfo(appPath);
+ QVERIFY(codeSignInfo.first != CodeSignResult::Failed);
+ QCOMPARE(codeSignInfo.first == CodeSignResult::Signed, enableSigning);
+ QCOMPARE(codeSignInfo.second.isEmpty(), !enableSigning);
+ if (!codeSignInfo.second.isEmpty()) {
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Executable")));
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Identifier")));
+ QCOMPARE(codeSignInfo.second.value(QByteArrayLiteral("Signature")), "adhoc");
+ }
+
+ const auto libName = isBundle ? QStringLiteral("B.framework") : QStringLiteral("libB.dylib");
+ const auto libPath = defaultInstallRoot + "/" + libName;
+ QVERIFY(QFileInfo(libPath).exists());
+ codeSignInfo = getCodeSignInfo(libPath);
+ QVERIFY(codeSignInfo.first != CodeSignResult::Failed);
+ QCOMPARE(codeSignInfo.first == CodeSignResult::Signed, enableSigning);
+ QCOMPARE(codeSignInfo.second.isEmpty(), !enableSigning);
+ if (!codeSignInfo.second.isEmpty()) {
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Executable")));
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Identifier")));
+ QCOMPARE(codeSignInfo.second.value(QByteArrayLiteral("Signature")), "adhoc");
+ }
+
+ const auto pluginPath = defaultInstallRoot + "/" + QStringLiteral("C.bundle");
+ QVERIFY(QFileInfo(pluginPath).exists());
+ QVERIFY(QFileInfo(pluginPath).isDir() == isBundle);
+ codeSignInfo = getCodeSignInfo(pluginPath);
+ QVERIFY(codeSignInfo.first != CodeSignResult::Failed);
+ QCOMPARE(codeSignInfo.first == CodeSignResult::Signed, enableSigning);
+ QCOMPARE(codeSignInfo.second.isEmpty(), !enableSigning);
+ if (!codeSignInfo.second.isEmpty()) {
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Executable")));
+ QVERIFY(codeSignInfo.second.contains(QByteArrayLiteral("Identifier")));
+ QCOMPARE(codeSignInfo.second.value(QByteArrayLiteral("Signature")), "adhoc");
+ }
+}
+
+void TestBlackboxApple::codesign_data()
+{
+ QTest::addColumn<bool>("isBundle");
+ QTest::addColumn<bool>("enableSigning");
+
+ QTest::newRow("bundle, unsigned") << true << false;
+ QTest::newRow("standalone, unsigned") << false << false;
+ QTest::newRow("bundle, signed") << true << true;
+ QTest::newRow("standalone, signed") << false << true;
+}
+
void TestBlackboxApple::deploymentTarget()
{
QFETCH(QString, sdk);
diff --git a/tests/auto/blackbox/tst_blackboxapple.h b/tests/auto/blackbox/tst_blackboxapple.h
index eeaa28d2f..32eee2432 100644
--- a/tests/auto/blackbox/tst_blackboxapple.h
+++ b/tests/auto/blackbox/tst_blackboxapple.h
@@ -55,6 +55,8 @@ private slots:
void assetCatalogsMultiple();
void bundleStructure();
void bundleStructure_data();
+ void codesign();
+ void codesign_data();
void deploymentTarget();
void deploymentTarget_data();
void dmg();
diff --git a/tests/auto/blackbox/tst_blackboxbase.h b/tests/auto/blackbox/tst_blackboxbase.h
index ed9a233de..d020b7cd9 100644
--- a/tests/auto/blackbox/tst_blackboxbase.h
+++ b/tests/auto/blackbox/tst_blackboxbase.h
@@ -60,7 +60,14 @@ public:
expectCrash = false;
profile = profileName();
settingsDir = settings()->baseDirectory();
- environment = QProcessEnvironment::systemEnvironment();
+ environment = defaultEnvironment();
+ }
+
+ static QProcessEnvironment defaultEnvironment()
+ {
+ auto result = QProcessEnvironment::systemEnvironment();
+ result.insert(QStringLiteral("QBS_AUTOTEST_CODE_SIGNING_REQUIRED"), QStringLiteral("0"));
+ return result;
}
QString command;