diff options
author | Jake Petroules <jake.petroules@petroules.com> | 2015-06-09 23:53:05 -0700 |
---|---|---|
committer | Jake Petroules <jake.petroules@petroules.com> | 2015-07-29 19:18:55 +0000 |
commit | ef5992bb58473bca8c019f2eba9faa0048aa0c05 (patch) | |
tree | afa5f222f3d72be13f904b0fdfcdea80a4aaef29 | |
parent | 248ed3e0e0e809f8994c2e8983db830fbc2bdb21 (diff) |
Add code signing support for OS X and iOS.
Change-Id: Ia85f68692974288780484fa47a896d66f16bc11b
Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
-rw-r--r-- | doc/reference/modules/xcode-module.qdoc | 44 | ||||
-rw-r--r-- | share/qbs/modules/bundle/BundleModule.qbs | 36 | ||||
-rw-r--r-- | share/qbs/modules/cpp/CppModule.qbs | 5 | ||||
-rw-r--r-- | share/qbs/modules/cpp/gcc.js | 13 | ||||
-rw-r--r-- | share/qbs/modules/cpp/ios-gcc.qbs | 34 | ||||
-rw-r--r-- | share/qbs/modules/xcode/xcode.js | 51 | ||||
-rw-r--r-- | share/qbs/modules/xcode/xcode.qbs | 48 |
7 files changed, 203 insertions, 28 deletions
diff --git a/doc/reference/modules/xcode-module.qdoc b/doc/reference/modules/xcode-module.qdoc index 1969ef9ae..ac5a36f19 100644 --- a/doc/reference/modules/xcode-module.qdoc +++ b/doc/reference/modules/xcode-module.qdoc @@ -56,6 +56,11 @@ directory of the Xcode installation at its default location in /Applications. Corresponds to the \c DEVELOPER_DIR environment variable. \row + \li provisioningProfile + \li string + \li undefined + \li Name or UUID of the provisioning profile to embed in the product. + \row \li sdk \li string \li \c{"macosx"} on OS X, \c{"iphoneos"} on iOS, \c{"iphonesimulator"} on iOS Simulator @@ -64,6 +69,23 @@ \c{"10.10"}), or a platform identifier (i.e. \c{"macosx"}) in which case the latest SDK available for that platform will be used. \row + \li signingIdentity + \li string + \li undefined + \li 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". + The search string should generally be one of the following: + \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 + Complete documentation for the existing certificate types can be found at: + https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html#//apple_ref/doc/uid/TP40012582-CH31-SW41 + \row \li targetDevices \li stringList \li \c{["mac"]} on OS X; {["iphone", "ipad"]} on iOS @@ -71,6 +93,28 @@ "mac", and for iOS it can be one or both of "iphone" and "ipad". \endtable + \section1 Advanced Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li provisioningProfilePath + \li path + \li determined by \c{xcode.provisioningProfilesPath} and \c{xcode.provisioningProfile} + \li Path of the provisioning profile to embed in the product. Generally you should not set + this property and instead use \c{xcode.provisioningProfile} to automatically + determine the provisioning profile's path based on its name or UUID. + \row + \li provisioningProfilesPath + \li path + \li \c{"~/Library/MobileDevice/Provisioning Profiles"} + \li Path to directory containing provisioning profiles installed on the system. + \endtable + \section1 Read-only Properties \table diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs index c553946d8..d1dee8e14 100644 --- a/share/qbs/modules/bundle/BundleModule.qbs +++ b/share/qbs/modules/bundle/BundleModule.qbs @@ -426,7 +426,7 @@ Module { condition: qbs.targetOS.contains("darwin") multiplex: true inputs: ["infoplist", "pkginfo", "hpp", - "icns", "resourcerules", "ipa", + "icns", "resourcerules", "compiled_nib", "compiled_storyboard", "compiled_assetcatalog"] outputFileTags: ["bundle", @@ -610,12 +610,36 @@ Module { if (cmd.sources && cmd.sources.length) commands.push(cmd); - if (product.type.contains("application") && product.moduleProperty("qbs", "hostOS").contains("darwin")) { + if (product.moduleProperty("qbs", "hostOS").contains("darwin")) { for (i in bundles) { - cmd = new Command(ModUtils.moduleProperty(product, "lsregisterPath"), - ["-f", bundles[i].filePath]); - cmd.description = "register " + ModUtils.moduleProperty(product, "bundleName"); - commands.push(cmd); + var actualSigningIdentity = product.moduleProperty("xcode", "actualSigningIdentity"); + var codesignDisplayName = product.moduleProperty("xcode", "actualSigningIdentityDisplayName"); + if (actualSigningIdentity) { + // If this is a framework, we need to sign its versioned directory + var subpath = ""; + var frameworkVersion = ModUtils.moduleProperty(product, "frameworkVersion"); + if (frameworkVersion) { + subpath = ModUtils.moduleProperty(product, "contentsFolderPath"); + subpath = subpath.substring(subpath.indexOf(ModUtils.moduleProperty("qbs", "pathSeparator"))); + } + + cmd = new Command(product.moduleProperty("xcode", "codesignPath"), + (product.moduleProperty("xcode", "codesignFlags") || []) + .concat(["--force", "--sign", actualSigningIdentity, + bundles[i].filePath + subpath])); + cmd.description = "codesign " + + ModUtils.moduleProperty(product, "bundleName") + + " using " + codesignDisplayName + + " (" + actualSigningIdentity + ")"; + commands.push(cmd); + } + + if (product.type.contains("application")) { + cmd = new Command(ModUtils.moduleProperty(product, "lsregisterPath"), + ["-f", bundles[i].filePath]); + cmd.description = "register " + ModUtils.moduleProperty(product, "bundleName"); + commands.push(cmd); + } } } diff --git a/share/qbs/modules/cpp/CppModule.qbs b/share/qbs/modules/cpp/CppModule.qbs index 3443f1c7b..cc382c0b2 100644 --- a/share/qbs/modules/cpp/CppModule.qbs +++ b/share/qbs/modules/cpp/CppModule.qbs @@ -239,11 +239,6 @@ Module { property stringList platformLinkerFlags // OS X and iOS properties - property bool buildIpa: !qbs.targetOS.contains('ios-simulator') - - property string signingIdentity - property path provisioningProfile - property bool automaticReferenceCounting PropertyOptions { name: "automaticReferenceCounting" diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js index ce92771a5..a8af8353b 100644 --- a/share/qbs/modules/cpp/gcc.js +++ b/share/qbs/modules/cpp/gcc.js @@ -667,6 +667,19 @@ function prepareLinker(project, product, inputs, outputs, input, output) { } } + var actualSigningIdentity = product.moduleProperty("xcode", "actualSigningIdentity"); + var codesignDisplayName = product.moduleProperty("xcode", "actualSigningIdentityDisplayName"); + if (actualSigningIdentity && !product.moduleProperty("bundle", "isBundle")) { + cmd = new Command(product.moduleProperty("xcode", "codesignPath"), + ["--force", "--sign", actualSigningIdentity, + primaryOutput.filePath]); + cmd.description = "codesign " + + primaryOutput.fileName + + " using " + codesignDisplayName + + " (" + actualSigningIdentity + ")"; + commands.push(cmd); + } + return commands; } diff --git a/share/qbs/modules/cpp/ios-gcc.qbs b/share/qbs/modules/cpp/ios-gcc.qbs index 93df91c9c..c6ad1f8da 100644 --- a/share/qbs/modules/cpp/ios-gcc.qbs +++ b/share/qbs/modules/cpp/ios-gcc.qbs @@ -31,6 +31,7 @@ import qbs 1.0 import qbs.DarwinTools import qbs.File +import qbs.FileInfo import qbs.ModUtils DarwinGCC { @@ -78,36 +79,35 @@ DarwinGCC { } Rule { - condition: product.moduleProperty("cpp", "buildIpa") - multiplex: true - inputs: ["application", "infoplist", "pkginfo", "resourcerules", "compiled_nib"] + inputsFromDependencies: ["bundle"] Artifact { - filePath: product.destinationDirectory + "/" + product.targetName + ".ipa" + filePath: FileInfo.joinPaths(product.destinationDirectory, product.targetName + ".ipa") fileTags: ["ipa"] } prepare: { - var signingIdentity = product.moduleProperty("cpp", "signingIdentity"); + var signingIdentity = product.moduleProperty("xcode", "actualSigningIdentity"); + var signingIdentityDisplay = product.moduleProperty("xcode", + "actualSigningIdentityDisplayName"); if (!signingIdentity) throw "The name of a valid signing identity must be set using " + - "cpp.signingIdentity in order to build an IPA package."; + "xcode.signingIdentity in order to build an IPA package."; - var provisioningProfile = product.moduleProperty("cpp", "provisioningProfile"); - if (!provisioningProfile) + var provisioningProfilePath = product.moduleProperty("xcode", + "provisioningProfilePath"); + if (!provisioningProfilePath) throw "The path to a provisioning profile must be set using " + - "cpp.provisioningProfile in order to build an IPA package."; + "xcode.provisioningProfilePath in order to build an IPA package."; - var args = ["-sdk", product.moduleProperty("cpp", "xcodeSdkName"), "PackageApplication", - "-v", product.buildDirectory + "/" + product.moduleProperty("bundle", "bundleName"), - "-o", outputs.ipa[0].filePath, "--sign", signingIdentity, - "--embed", provisioningProfile]; + var args = [input.filePath, + "-o", output.filePath, + "--sign", signingIdentity, + "--embed", provisioningProfilePath]; - var command = "/usr/bin/xcrun"; - var cmd = new Command(command, args) - cmd.description = "creating ipa, signing with " + signingIdentity; + var cmd = new Command("PackageApplication", args); + cmd.description = "creating ipa, signing with " + signingIdentityDisplay; cmd.highlight = "codegen"; - cmd.workingDirectory = product.buildDirectory; return cmd; } } diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js index 6b93b9985..75c0ec580 100644 --- a/share/qbs/modules/xcode/xcode.js +++ b/share/qbs/modules/xcode/xcode.js @@ -30,6 +30,7 @@ var File = loadExtension("qbs.File"); var FileInfo = loadExtension("qbs.FileInfo"); +var Process = loadExtension("qbs.Process"); var PropertyList = loadExtension("qbs.PropertyList"); function applePlatformDirectoryName(targetOSList, version, throwOnError) { @@ -97,3 +98,53 @@ function sdkInfoList(sdksPath) { return sdkInfo; } + +function findSigningIdentities(security, searchString) { + var process; + var identities; + if (searchString) { + try { + process = new Process(); + if (process.exec(security, ["find-identity", "-p", "codesigning", "-v"], true) !== 0) + print(process.readStdErr()); + + var lines = process.readStdOut().split("\n"); + for (var i in lines) { + // e.g. 1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA "Mac Developer: John Doe (XXXXXXXXXX) john.doe@example.org" + var match = lines[i].match(/^\s*[0-9]+\)\s+([A-Fa-f0-9]{40})\s+"([^"]+)"$/); + if (match !== null) { + var hexId = match[1]; + var displayName = match[2]; + if (hexId === searchString || displayName.startsWith(searchString)) { + if (!identities) + identities = []; + identities.push([hexId, displayName]); + break; + } + } + } + } finally { + process.close(); + } + } + return identities; +} + +function readProvisioningProfileData(path) { + var process; + try { + process = new Process(); + if (process.exec("openssl", ["smime", "-verify", "-noverify", "-inform", "DER", "-in", path], true) !== 0) + print(process.readStdErr()); + + var propertyList = new PropertyList(); + try { + propertyList.readFromString(process.readStdOut()); + return propertyList.toObject(); + } finally { + propertyList.clear(); + } + } finally { + process.close(); + } +} diff --git a/share/qbs/modules/xcode/xcode.qbs b/share/qbs/modules/xcode/xcode.qbs index a8464b330..998520417 100644 --- a/share/qbs/modules/xcode/xcode.qbs +++ b/share/qbs/modules/xcode/xcode.qbs @@ -54,6 +54,36 @@ Module { } } + property string signingIdentity + readonly property string actualSigningIdentity: { + if (_actualSigningIdentity && _actualSigningIdentity.length === 1) + return _actualSigningIdentity[0][0]; + } + + readonly property string actualSigningIdentityDisplayName: { + if (_actualSigningIdentity && _actualSigningIdentity.length === 1) + return _actualSigningIdentity[0][1]; + } + + property string provisioningProfile + property path provisioningProfilePath: { + var files = _availableProvisioningProfiles; + for (var i in files) { + var data = Utils.readProvisioningProfileData(files[i]); + if (data["UUID"] === provisioningProfile || + data["Name"] === provisioningProfile) { + return files[i]; + } + } + } + + 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, @@ -75,6 +105,23 @@ Module { readonly property path toolchainInfoPlist: FileInfo.joinPaths(toolchainPath, "ToolchainInfo.plist") + readonly property stringList _actualSigningIdentity: { + if (/^[A-Fa-f0-9]{40}$/.test(signingIdentity)) { + return signingIdentity; + } + + var identities = Utils.findSigningIdentities(securityPath, signingIdentity); + if (identities && identities.length > 1) { + throw "Signing identity '" + signingIdentity + "' is ambiguous"; + } + + return identities; + } + + property path provisioningProfilesPath: { + return FileInfo.joinPaths(qbs.getEnv("HOME"), "Library/MobileDevice/Provisioning Profiles"); + } + readonly property var _availableSdks: Utils.sdkInfoList(sdksPath) readonly property var _sdkSettings: { @@ -135,6 +182,7 @@ Module { property var buildEnv: { return { + "CODESIGN_ALLOCATE": platformPath + "/Developer/usr/bin/codesign_allocate", "DEVELOPER_DIR": developerPath }; } |