Skip to content

Commit

Permalink
Merge pull request #6064 from Microsoft/users/htc/macOS_packaging_sup…
Browse files Browse the repository at this point in the history
…port

Add support for macOS packaging with 'auto' export selected.
  • Loading branch information
hashtagchris authored Jan 4, 2018
2 parents 0a69a20 + 8d2aaea commit 38480bb
Show file tree
Hide file tree
Showing 10 changed files with 534 additions and 45 deletions.
77 changes: 70 additions & 7 deletions Tasks/Common/ios-signing-common/ios-signing-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ export async function findSigningIdentity(keychainPath: string) {
}

/**
* Get Cloud entitlement type Production or Development according to the export method - if entitlement doesn't exists in provisioning profile returns null
* Get Cloud entitlement type Production or Development according to the export method - if entitlement doesn't exists in provisioning profile returns null
* @param provisioningProfilePath
* @param exportMethod
* @returns {string}
* @returns {string}
*/
export async function getCloudEntitlement(provisioningProfilePath: string, exportMethod: string): Promise<string> {
//find the provisioning profile details
Expand Down Expand Up @@ -164,7 +164,7 @@ export async function getCloudEntitlement(provisioningProfilePath: string, expor
throw tl.loc('ProvProfileDetailsNotFound', provisioningProfilePath);
}

//use PlistBuddy to figure out if cloud entitlement exists.
//use PlistBuddy to figure out if cloud entitlement exists.
const cloudEntitlement: string = await printFromPlist('Entitlements:com.apple.developer.icloud-container-environment', tmpPlist);

//delete the temporary plist file
Expand All @@ -177,7 +177,9 @@ export async function getCloudEntitlement(provisioningProfilePath: string, expor
}

tl.debug('Provisioning Profile contains cloud entitlement');
return exportMethod === 'app-store' ? "Production" : "Development";
return (exportMethod === 'app-store' || exportMethod === 'enterprise' || exportMethod === 'developer-id')
? "Production"
: "Development";
}

/**
Expand Down Expand Up @@ -286,11 +288,11 @@ export async function getProvisioningProfileName(provProfilePath: string) {
}

/**
* Find the type of the provisioning profile - development, app-store or ad-hoc
* Find the type of the iOS provisioning profile - app-store, ad-hoc, enterprise or development
* @param provProfilePath
* @returns {string} type
*/
export async function getProvisioningProfileType(provProfilePath: string) {
export async function getiOSProvisioningProfileType(provProfilePath: string) {
let provProfileType: string;
try {
//find the provisioning profile details
Expand Down Expand Up @@ -352,9 +354,70 @@ export async function getProvisioningProfileType(provProfilePath: string) {
return provProfileType;
}

/**
* Find the type of the macOS provisioning profile - app-store, developer-id or development.
* mac-application is a fourth macOS export method, but it doesn't include signing.
* @param provProfilePath
* @returns {string} type
*/
export async function getmacOSProvisioningProfileType(provProfilePath: string) {
let provProfileType: string;
try {
//find the provisioning profile details
let provProfileDetails: string;
let getProvProfileDetailsCmd: ToolRunner = tl.tool(tl.which('security', true));
getProvProfileDetailsCmd.arg(['cms', '-D', '-i', provProfilePath]);
getProvProfileDetailsCmd.on('stdout', function (data) {
if (data) {
if (provProfileDetails) {
provProfileDetails = provProfileDetails.concat(data.toString().trim().replace(/[,\n\r\f\v]/gm, ''));
} else {
provProfileDetails = data.toString().trim().replace(/[,\n\r\f\v]/gm, '');
}
}
})
await getProvProfileDetailsCmd.exec();

let tmpPlist: string;
if (provProfileDetails) {
//write the provisioning profile to a plist
tmpPlist = '_xcodetasktmp.plist';
fs.writeFileSync(tmpPlist, provProfileDetails);
} else {
throw tl.loc('ProvProfileDetailsNotFound', provProfilePath);
}

//get ProvisionsAllDevices - this will exist for developer-id profiles
let provisionsAllDevices: string = await printFromPlist('ProvisionsAllDevices', tmpPlist);
tl.debug('provisionsAllDevices = ' + provisionsAllDevices);
if (provisionsAllDevices && provisionsAllDevices.trim().toLowerCase() === 'true') {
//ProvisionsAllDevices = true in developer-id profiles
provProfileType = 'developer-id';
} else {
let provisionedDevices: string = await printFromPlist('ProvisionedDevices', tmpPlist);
if (!provisionedDevices) {
// no provisioned devices means it is an app-store profile
provProfileType = 'app-store';
} else {
// profile with provisioned devices - use development
provProfileType = 'development';
}
}

//delete the temporary plist file
let deletePlistCommand: ToolRunner = tl.tool(tl.which('rm', true));
deletePlistCommand.arg(['-f', tmpPlist]);
await deletePlistCommand.exec();
} catch (err) {
tl.debug(err);
}

return provProfileType;
}

/**
* Find the bundle identifier in the specified Info.plist
* @param plistPath
* @param plistPath
* @returns {string} bundle identifier
*/
export async function getBundleIdFromPlist(plistPath: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"loc.messages.XcodeSuccess": "Xcode task execution completed with no errors.",
"loc.messages.TempKeychainDeleteFailed": "Failed to delete the temporary keychain created during the build: `%s`",
"loc.messages.ProvProfileDeleteFailed": "Failed to delete the provisioning profile `%s`.",
"loc.messages.ExportMethodNotIdentified": "Failed to automatically identify the export method to use from the archive file. This might cause errors during the build or produce an invalid package. Set `Export options` to `Plist` or `Specify`.",
"loc.messages.ExportMethodNotIdentified": "Failed to automatically identify the export method to use from the archive file. This might cause errors during the build or produce an invalid package. If export fails, either enable signing (set `Signing Style` to `Project Defaults`), or configure export manually (set `Export options` to `Plist` or `Specify`).",
"loc.messages.ExportOptionsPlistInvalidFilePath": "The `Export options` plist file does not exist at `%s`. Provide the path to a valid plist file.",
"loc.messages.SchemeRequiredForArchive": "The scheme must be specified to generate the package with xcodebuild archive and export.",
"loc.messages.WorkspaceOrProjectRequiredForArchive": "The workspace or project path must be specified to generate the package with xcodebuild archive and export.",
Expand Down
112 changes: 91 additions & 21 deletions Tasks/Xcode/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Xcode L0 Suite', function () {

});

it('Xcode 7 create IPA with archive and auto export', (done: MochaDone) => {
it('Xcode 7 create IPA with archive and auto export', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0ExportArchiveWithAuto.js');
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 7 create IPA with archive and export with specified method', (done: MochaDone) => {
it('Xcode 7 create IPA with archive and export with specified method', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0ExportArchiveSpecify.js');
Expand Down Expand Up @@ -82,7 +82,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 8 create IPA with export options plist', (done: MochaDone) => {
it('Xcode 8 create IPA with export options plist', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0ExportArchiveWithPlist.js');
Expand Down Expand Up @@ -116,7 +116,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 8 create IPA with bad exportOptionsPlist path', (done: MochaDone) => {
it('Xcode 8 create IPA with bad exportOptionsPlist path', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0ExportOptionsPlistBadPath.js');
Expand Down Expand Up @@ -146,7 +146,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode create IPA with file paths for archive path and export path', (done: MochaDone) => {
it('Xcode create IPA with file paths for archive path and export path', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0FilePathForArchiveAndExportPath.js');
Expand Down Expand Up @@ -180,7 +180,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 7 create IPA with code signing identifiers', (done: MochaDone) => {
it('Xcode 7 create IPA with code signing identifiers', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0CreateIpaWithCodeSigningIdentifiers.js');
Expand Down Expand Up @@ -215,7 +215,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 8 automatic code signing with identifiers', (done: MochaDone) => {
it('Xcode 8 automatic code signing with identifiers', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0Xcode8AutomaticSignWithIdentifiers.js');
Expand Down Expand Up @@ -248,7 +248,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 8 automatic signing with development team', (done: MochaDone) => {
it('Xcode 8 automatic signing with development team', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0Xcode8AutomaticSignWithDevTeam.js');
Expand Down Expand Up @@ -285,7 +285,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode archive and export with project path', (done: MochaDone) => {
it('Xcode archive and export with project path', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0XcodeArchiveExportProject.js');
Expand Down Expand Up @@ -323,7 +323,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 automatic signing with files', (done: MochaDone) => {
it('Xcode 9 automatic signing with files', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0Xcode9AutomaticSignWithFiles.js');
Expand Down Expand Up @@ -360,7 +360,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 automatic signing with allowProvisioningUpdates', (done: MochaDone) => {
it('Xcode 9 automatic signing with allowProvisioningUpdates', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0Xcode9AutomaticSignWithAllowProvisioningUpdates.js');
Expand Down Expand Up @@ -398,7 +398,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 signing defaults to automatic, with auto export', (done: MochaDone) => {
it('Xcode 9 signing defaults to automatic, with auto export', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0XCode9SigningDefaultsToAutoWithAutoExport.js');
Expand All @@ -418,7 +418,7 @@ describe('Xcode L0 Suite', function () {
//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive ' +
'-exportPath /user/build/_XcodeTaskExport_testScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run with -allowProvisioningUpdates to export the IPA from the .xcarchive');
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');
Expand All @@ -427,7 +427,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 signing defaults to manual, with auto export', (done: MochaDone) => {
it('Xcode 9 signing defaults to manual, with auto export', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0XCode9SigningDefaultsToManualWithAutoExport.js');
Expand Down Expand Up @@ -456,7 +456,7 @@ describe('Xcode L0 Suite', function () {
//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive ' +
'-exportPath /user/build/_XcodeTaskExport_testScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run with -allowProvisioningUpdates to export the IPA from the .xcarchive');
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');
Expand All @@ -465,7 +465,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 signing with auto export and cloud entitlement for production', (done: MochaDone) => {
it('Xcode 9 signing with auto export and cloud entitlement for production', function (done: MochaDone) {
this.timeout(1000);

const tp = path.join(__dirname, 'L0Xcode9ExportArchiveWithAutoAndCloudEntitlementForProduction.js');
Expand Down Expand Up @@ -497,7 +497,7 @@ describe('Xcode L0 Suite', function () {
//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive ' +
'-exportPath /user/build/_XcodeTaskExport_testScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run with -allowProvisioningUpdates to export the IPA from the .xcarchive');
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');
Expand All @@ -506,7 +506,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Xcode 9 signing with auto export and cloud entitlement for development', (done: MochaDone) => {
it('Xcode 9 signing with auto export and cloud entitlement for development', function (done: MochaDone) {
this.timeout(1000);

const tp = path.join(__dirname, 'L0Xcode9ExportArchiveWithAutoAndCloudEntitlementForDevelopment.js');
Expand Down Expand Up @@ -538,7 +538,7 @@ describe('Xcode L0 Suite', function () {
//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive ' +
'-exportPath /user/build/_XcodeTaskExport_testScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run with -allowProvisioningUpdates to export the IPA from the .xcarchive');
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');
Expand All @@ -547,7 +547,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Task defaults - v4.127.0', (done: MochaDone) => {
it('Task defaults - v4.127.0', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0TaskDefaults_4.127.0.js');
Expand Down Expand Up @@ -576,7 +576,7 @@ describe('Xcode L0 Suite', function () {
done();
});

it('Test results should be published in postexecution to work even when Xcode test has failures', (done: MochaDone) => {
it('Test results should be published in postexecution to work even when Xcode test has failures', function (done: MochaDone) {
this.timeout(1000);

let tp = path.join(__dirname, 'L0TestResultsPublishedInPostExecutionJob.js');
Expand All @@ -589,4 +589,74 @@ describe('Xcode L0 Suite', function () {
'test result should have been published even when there are test errors');
done();
});

it('macOS auto export', function (done: MochaDone) {
this.timeout(1000);

const tp = path.join(__dirname, 'L0macOSAutoExport.js');
const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);

tr.run();
//version
assert(tr.ran('/home/bin/xcodebuild -version'), 'xcodebuild for version should have been run.');

//export prep
assert(tr.ran("/usr/libexec/PlistBuddy -c Clear _XcodeTaskExportOptions.plist"),
'PlistBuddy Clear should have run.');

// macOS Developer ID provisioning profile from the developer portal.
assert(tr.ran("/usr/libexec/PlistBuddy -c Add method string developer-id _XcodeTaskExportOptions.plist"),
'PlistBuddy add method should have run.');

// provisioning profile includes iCloudContainerEnvironment.
assert(tr.ran("/usr/libexec/PlistBuddy -c Add iCloudContainerEnvironment string Production _XcodeTaskExportOptions.plist"),
'PlistBuddy add cloud entitlement list should have run.');

assert(tr.ran("/usr/libexec/PlistBuddy -c Add signingStyle string manual _XcodeTaskExportOptions.plist"),
'PlistBuddy add signingStyle should have run.');

assert(tr.ran("/usr/libexec/PlistBuddy -c Add provisioningProfiles dict _XcodeTaskExportOptions.plist"),
'PlistBuddy add provisioningProfiles should have run.');

assert(tr.ran("/usr/libexec/PlistBuddy -c Add provisioningProfiles:com.vsts.test.myApp string Bob _XcodeTaskExportOptions.plist"),
'PlistBuddy add provisioningProfiles:com.vsts.test.myApp should have run.');

//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive'
+' -exportPath /user/build/_XcodeTaskExport_funScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');

assert(tr.invokedToolCount === 21, 'Should have run \"PlistBuddy -c Add...\" five times, and 16 other command lines.');

done();
});

it('macOS provisionless auto export', function (done: MochaDone) {
this.timeout(1000);

const tp = path.join(__dirname, 'L0macOSProvisionlessAutoExport.js');
const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);

tr.run();
//version
assert(tr.ran('/home/bin/xcodebuild -version'), 'xcodebuild for version should have been run.');

//export prep
assert(tr.ran("/usr/libexec/PlistBuddy -c Clear _XcodeTaskExportOptions.plist"),
'PlistBuddy Clear should have run. An empty exportOptions plist should be used when there\'s not an embedded provisioning profile.');

//export
assert(tr.ran('/home/bin/xcodebuild -exportArchive -archivePath /user/build/testScheme.xcarchive'
+' -exportPath /user/build/_XcodeTaskExport_funScheme -exportOptionsPlist _XcodeTaskExportOptions.plist'),
'xcodebuild exportArchive should have been run to export the IPA from the .xcarchive');

assert(tr.stderr.length === 0, 'should not have written to stderr');
assert(tr.succeeded, 'task should have succeeded');
assert(tr.invokedToolCount === 6, 'Should have ran 6 command lines.');

done();
});
});
Loading

0 comments on commit 38480bb

Please sign in to comment.