Skip to content

Commit

Permalink
feat: Support Apple Cloud Distribution signing (#1276)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpogue committed Apr 14, 2023
1 parent 7352502 commit 2c86d1a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 35 deletions.
68 changes: 52 additions & 16 deletions lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ const util = require('util');
const check_reqs = require('./check_reqs');
const projectFile = require('./projectFile');

const buildConfigProperties = [
'codeSignIdentity',
'codeSignResourceRules',
'provisioningProfile',
'developmentTeam',
'packageType',
'buildFlag',
'iCloudContainerEnvironment',
'automaticProvisioning',
'authenticationKeyPath',
'authenticationKeyID',
'authenticationKeyIssuerID'
];

// These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs
/* eslint-disable no-useless-escape */
const buildFlagMatchers = {
Expand Down Expand Up @@ -111,10 +125,9 @@ module.exports.run = function (buildOpts) {
const buildType = buildOpts.release ? 'release' : 'debug';
const config = buildConfig.ios[buildType];
if (config) {
['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment', 'automaticProvisioning'].forEach(
key => {
buildOpts[key] = buildOpts[key] || config[key];
});
buildConfigProperties.forEach(key => {
buildOpts[key] = buildOpts[key] || config[key];
});
}
}
}
Expand Down Expand Up @@ -211,7 +224,7 @@ module.exports.run = function (buildOpts) {
// remove the build/device folder before building
fs.removeSync(buildOutputDir);

const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget, buildOpts.automaticProvisioning);
const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, emulatorTarget, buildOpts);
return execa('xcodebuild', xcodebuildArgs, { cwd: projectPath, stdio: 'inherit' });
}).then(() => {
if (!buildOpts.device || buildOpts.noSign) {
Expand Down Expand Up @@ -264,7 +277,7 @@ module.exports.run = function (buildOpts) {
}

function packageArchive () {
const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts.automaticProvisioning);
const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts);
return execa('xcodebuild', xcodearchiveArgs, { cwd: projectPath, stdio: 'inherit' });
}

Expand Down Expand Up @@ -302,16 +315,15 @@ module.exports.findXCodeProjectIn = findXCodeProjectIn;
* @param {String} projectName Name of xcode project
* @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
* @param {String} configuration Configuration name: debug|release
* @param {Boolean} isDevice Flag that specify target for package (device/emulator)
* @param {Array} buildFlags
* @param {String} emulatorTarget Target for emulator (rather than default)
* @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
* @param {Object} buildConfig The build configuration options
* @return {Array} Array of arguments that could be passed directly to spawn method
*/
function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget, autoProvisioning) {
function getXcodeBuildArgs (projectName, projectPath, configuration, emulatorTarget, buildConfig = {}) {
let options;
let buildActions;
let settings;
const buildFlags = buildConfig.buildFlag;
const customArgs = {};
customArgs.otherFlags = [];

Expand All @@ -325,7 +337,7 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b
}
}

if (isDevice) {
if (buildConfig.device) {
options = [
'-workspace', customArgs.workspace || `${projectName}.xcworkspace`,
'-scheme', customArgs.scheme || projectName,
Expand All @@ -344,8 +356,17 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b
customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]);
}

if (autoProvisioning) {
options = options.concat(['-allowProvisioningUpdates']);
if (buildConfig.automaticProvisioning) {
options.push('-allowProvisioningUpdates');
}
if (buildConfig.authenticationKeyPath) {
options.push('-authenticationKeyPath', buildConfig.authenticationKeyPath);
}
if (buildConfig.authenticationKeyID) {
options.push('-authenticationKeyID', buildConfig.authenticationKeyID);
}
if (buildConfig.authenticationKeyIssuerID) {
options.push('-authenticationKeyIssuerID', buildConfig.authenticationKeyIssuerID);
}
} else { // emulator
options = [
Expand Down Expand Up @@ -376,16 +397,31 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b
* @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
* @param {String} outputPath Output directory to contain the IPA
* @param {String} exportOptionsPath Path to the exportOptions.plist file
* @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning
* @param {Object} buildConfig Build configuration options
* @return {Array} Array of arguments that could be passed directly to spawn method
*/
function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, autoProvisioning) {
function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, buildConfig = {}) {
const options = [];

if (buildConfig.automaticProvisioning) {
options.push('-allowProvisioningUpdates');
}
if (buildConfig.authenticationKeyPath) {
options.push('-authenticationKeyPath', buildConfig.authenticationKeyPath);
}
if (buildConfig.authenticationKeyID) {
options.push('-authenticationKeyID', buildConfig.authenticationKeyID);
}
if (buildConfig.authenticationKeyIssuerID) {
options.push('-authenticationKeyIssuerID', buildConfig.authenticationKeyIssuerID);
}

return [
'-exportArchive',
'-archivePath', `${projectName}.xcarchive`,
'-exportOptionsPlist', exportOptionsPath,
'-exportPath', outputPath
].concat(autoProvisioning ? ['-allowProvisioningUpdates'] : []);
].concat(options);
}

function parseBuildFlag (buildFlag, args) {
Expand Down
56 changes: 37 additions & 19 deletions tests/spec/unit/build.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ describe('build', () => {
build.__set__('__dirname', path.join('/test', 'dir'));

it('should generate appropriate args if a single buildFlag is passed in', () => {
const isDevice = true;
const buildFlags = '';

const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags);
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: '' });
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -54,7 +51,6 @@ describe('build', () => {
});

it('should generate appropriate args if buildFlags are passed in', () => {
const isDevice = true;
const buildFlags = [
'-workspace TestWorkspaceFlag',
'-scheme TestSchemeFlag',
Expand All @@ -65,7 +61,7 @@ describe('build', () => {
'SHARED_PRECOMPS_DIR=TestSharedPrecompsDirFlag'
];

const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags);
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: buildFlags });
expect(args).toEqual([
'-workspace',
'TestWorkspaceFlag',
Expand All @@ -85,8 +81,7 @@ describe('build', () => {
});

it('should generate appropriate args for device', () => {
const isDevice = true;
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null);
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true });
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -106,8 +101,7 @@ describe('build', () => {
});

it('should generate appropriate args for simulator', () => {
const isDevice = false;
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null, 'iPhone 5s');
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', 'iPhone 5s', { device: false });
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -127,10 +121,9 @@ describe('build', () => {
});

it('should add matched flags that are not overriding for device', () => {
const isDevice = true;
const buildFlags = '-sdk TestSdkFlag';

const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags);
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: buildFlags });
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -152,10 +145,9 @@ describe('build', () => {
});

it('should add matched flags that are not overriding for simulator', () => {
const isDevice = false;
const buildFlags = '-archivePath TestArchivePathFlag';

const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags, 'iPhone 5s');
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', 'iPhone 5s', { device: false, buildFlag: buildFlags });
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -177,8 +169,15 @@ describe('build', () => {
});

it('should generate appropriate args for automatic provisioning', () => {
const isDevice = true;
const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null, null, true);
const buildOpts = {
device: true,
automaticProvisioning: true,
authenticationKeyPath: '/tmp/asc-key.p8',
authenticationKeyID: '12345',
authenticationKeyIssuerID: '00000000-0000-0000-0000-000000000000'
};

const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', buildOpts);
expect(args).toEqual([
'-workspace',
'TestProjectName.xcworkspace',
Expand All @@ -191,11 +190,17 @@ describe('build', () => {
'-archivePath',
'TestProjectName.xcarchive',
'-allowProvisioningUpdates',
'-authenticationKeyPath',
'/tmp/asc-key.p8',
'-authenticationKeyID',
'12345',
'-authenticationKeyIssuerID',
'00000000-0000-0000-0000-000000000000',
'archive',
`CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`,
`SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}`
]);
expect(args.length).toEqual(14);
expect(args.length).toEqual(20);
});
});

Expand All @@ -215,7 +220,14 @@ describe('build', () => {
});

it('should generate the appropriate arguments for automatic provisioning', () => {
const archiveArgs = getXcodeArchiveArgs('TestProjectName', testProjectPath, '/test/output/path', '/test/export/options/path', true);
const buildOpts = {
automaticProvisioning: true,
authenticationKeyPath: '/tmp/asc-key.p8',
authenticationKeyID: '12345',
authenticationKeyIssuerID: '00000000-0000-0000-0000-000000000000'
};

const archiveArgs = getXcodeArchiveArgs('TestProjectName', testProjectPath, '/test/output/path', '/test/export/options/path', buildOpts);
expect(archiveArgs[0]).toEqual('-exportArchive');
expect(archiveArgs[1]).toEqual('-archivePath');
expect(archiveArgs[2]).toEqual('TestProjectName.xcarchive');
Expand All @@ -224,7 +236,13 @@ describe('build', () => {
expect(archiveArgs[5]).toEqual('-exportPath');
expect(archiveArgs[6]).toEqual('/test/output/path');
expect(archiveArgs[7]).toEqual('-allowProvisioningUpdates');
expect(archiveArgs.length).toEqual(8);
expect(archiveArgs[8]).toEqual('-authenticationKeyPath');
expect(archiveArgs[9]).toEqual('/tmp/asc-key.p8');
expect(archiveArgs[10]).toEqual('-authenticationKeyID');
expect(archiveArgs[11]).toEqual('12345');
expect(archiveArgs[12]).toEqual('-authenticationKeyIssuerID');
expect(archiveArgs[13]).toEqual('00000000-0000-0000-0000-000000000000');
expect(archiveArgs.length).toEqual(14);
});
});

Expand Down

0 comments on commit 2c86d1a

Please sign in to comment.