aboutsummaryrefslogtreecommitdiffstats
path: root/share/qbs/modules/codesign/apple.qbs
diff options
context:
space:
mode:
Diffstat (limited to 'share/qbs/modules/codesign/apple.qbs')
-rw-r--r--share/qbs/modules/codesign/apple.qbs387
1 files changed, 387 insertions, 0 deletions
diff --git a/share/qbs/modules/codesign/apple.qbs b/share/qbs/modules/codesign/apple.qbs
new file mode 100644
index 000000000..31e2c366d
--- /dev/null
+++ b/share/qbs/modules/codesign/apple.qbs
@@ -0,0 +1,387 @@
+/****************************************************************************
+**
+** 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
+
+ _canSignArtifacts: true
+
+ 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];
+ }
+ }
+}