Skip to content

Commit 3e307ba

Browse files
Merge pull request #2340 from NativeScript/manual-provisioning
Implement Automatic and Manual Xcode Signing Styles
2 parents 6a2685e + cb9eaed commit 3e307ba

File tree

3 files changed

+214
-53
lines changed

3 files changed

+214
-53
lines changed

lib/definitions/project.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ interface IiOSBuildConfig extends IBuildConfig {
5858
* Code sign identity used for build. If not set iPhone Developer is used as a default when building for device.
5959
*/
6060
codeSignIdentity?: string;
61+
/**
62+
* Team identifier.
63+
*/
64+
teamIdentifier?: string;
6165
}
6266

6367
interface IPlatformProjectService {

lib/services/ios-project-service.ts

Lines changed: 208 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { PlistSession } from "plist-merge-patch";
1111
import {EOL} from "os";
1212
import * as temp from "temp";
1313
import * as plist from "plist";
14+
import { cert, provision } from "ios-mobileprovision-finder";
15+
import { Xcode } from "pbxproj-dom/xcode";
16+
17+
type XcodeSigningStyle = "Manual" | "Automatic";
1418

1519
export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
1620
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
@@ -300,10 +304,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
300304

301305
let xcodeBuildVersion = this.getXcodeVersion();
302306
if (helpers.versionCompare(xcodeBuildVersion, "8.0") >= 0) {
303-
let teamId = this.getDevelopmentTeam();
304-
if (teamId) {
305-
args = args.concat("DEVELOPMENT_TEAM=" + teamId);
306-
}
307+
buildConfig = this.getBuildConfig(projectRoot, buildConfig).wait();
307308
}
308309

309310
if (buildConfig && buildConfig.codeSignIdentity) {
@@ -314,6 +315,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
314315
args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`);
315316
}
316317

318+
if (buildConfig && buildConfig.teamIdentifier) {
319+
args.push(`DEVELOPMENT_TEAM=${buildConfig.teamIdentifier}`);
320+
}
321+
317322
this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { cwd: this.$options, stdio: 'inherit' }).wait();
318323
this.createIpa(projectRoot).wait();
319324

@@ -1010,6 +1015,69 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10101015
return xcodeBuildVersion;
10111016
}
10121017

1018+
private getBuildConfig(projectRoot: string, buildConfig: IiOSBuildConfig): IFuture<IiOSBuildConfig> {
1019+
return (() => {
1020+
// TRICKY: I am not sure why we totally disregard the buildConfig parameter here.
1021+
buildConfig = buildConfig || {};
1022+
1023+
if (this.$options.teamId) {
1024+
buildConfig.teamIdentifier = this.$options.teamId;
1025+
} else {
1026+
buildConfig = this.readXCConfigSigning().wait();
1027+
if (!buildConfig.codeSignIdentity && !buildConfig.mobileProvisionIdentifier && !buildConfig.teamIdentifier) {
1028+
buildConfig = this.readBuildConfigFromPlatforms().wait();
1029+
}
1030+
}
1031+
1032+
let signingStyle: XcodeSigningStyle;
1033+
if (buildConfig.codeSignIdentity || buildConfig.mobileProvisionIdentifier) {
1034+
signingStyle = "Manual";
1035+
} else if (buildConfig.teamIdentifier) {
1036+
signingStyle = "Automatic";
1037+
} else if (helpers.isInteractive()) {
1038+
let signingStyles = [
1039+
"Manual - Select existing provisioning profile for use",
1040+
"Automatic - Select Team ID for signing and let Xcode select managed provisioning profile"
1041+
];
1042+
let signingStyleIndex = signingStyles.indexOf(this.$prompter.promptForChoice("Select codesiging style", signingStyles).wait());
1043+
signingStyle = new Array<XcodeSigningStyle>("Manual", "Automatic")[signingStyleIndex];
1044+
1045+
switch(signingStyle) {
1046+
case "Manual":
1047+
let profile = this.getProvisioningProfile().wait();
1048+
if (!profile) {
1049+
this.$errors.failWithoutHelp("No matching provisioning profile found.");
1050+
}
1051+
this.persistProvisioningProfiles(profile.UUID).wait();
1052+
this.$logger.info("Apply provisioning profile: " + profile.Name + " (" + profile.TeamName + ") " + profile.Type + " UUID: " + profile.UUID);
1053+
buildConfig.mobileProvisionIdentifier = profile.UUID;
1054+
buildConfig.teamIdentifier = profile.TeamIdentifier[0];
1055+
break;
1056+
case "Automatic":
1057+
buildConfig.teamIdentifier = this.getDevelopmentTeam().wait();
1058+
this.persistDevelopmentTeam(buildConfig.teamIdentifier).wait();
1059+
break;
1060+
}
1061+
}
1062+
1063+
if (signingStyle) {
1064+
const pbxprojPath = path.join(projectRoot, this.$projectData.projectName + ".xcodeproj", "project.pbxproj");
1065+
const xcode = Xcode.open(pbxprojPath);
1066+
switch(signingStyle) {
1067+
case "Manual":
1068+
xcode.setManualSigningStyle(this.$projectData.projectName);
1069+
break;
1070+
case "Automatic":
1071+
xcode.setAutomaticSigningStyle(this.$projectData.projectName, buildConfig.teamIdentifier);
1072+
break;
1073+
}
1074+
xcode.save();
1075+
}
1076+
1077+
return buildConfig;
1078+
}).future<IiOSBuildConfig>()();
1079+
}
1080+
10131081
private getDevelopmentTeams(): Array<{ id: string, name: string }> {
10141082
let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/");
10151083
let files = this.$fs.readDirectory(dir).wait();
@@ -1045,39 +1113,78 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10451113
return null;
10461114
}
10471115

1048-
private readTeamId(): string {
1049-
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1050-
if (this.$fs.exists(xcconfigFile).wait()) {
1051-
let text = this.$fs.readText(xcconfigFile).wait();
1052-
let teamId: string;
1053-
text.split(/\r?\n/).forEach((line) => {
1054-
line = line.replace(/\/(\/)[^\n]*$/, "");
1055-
if (line.indexOf("DEVELOPMENT_TEAM") >= 0) {
1056-
teamId = line.split("=")[1].trim();
1057-
if (teamId[teamId.length - 1] === ';') {
1058-
teamId = teamId.slice(0, -1);
1059-
}
1060-
}
1061-
});
1062-
if (teamId) {
1063-
return teamId;
1116+
private readXCConfigSigning(): IFuture<IiOSBuildConfig> {
1117+
return (() => {
1118+
const result: IiOSBuildConfig = {};
1119+
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1120+
if (this.$fs.exists(xcconfigFile).wait()) {
1121+
let text = this.$fs.readText(xcconfigFile).wait();
1122+
text.split(/\r?\n/).forEach((line) => {
1123+
line = line.replace(/\/(\/)[^\n]*$/, "");
1124+
const read = (name: string) => {
1125+
if (line.indexOf(name) >= 0) {
1126+
let value = line.substr(line.lastIndexOf("=") + 1).trim();
1127+
if (value.charAt(value.length - 1) === ';') {
1128+
value = value.substr(0, value.length - 1).trim();
1129+
}
1130+
return value;
1131+
}
1132+
return undefined;
1133+
};
1134+
result.teamIdentifier = read("DEVELOPMENT_TEAM") || result.teamIdentifier;
1135+
result.codeSignIdentity = read("CODE_SIGN_IDENTITY") || result.codeSignIdentity;
1136+
result.mobileProvisionIdentifier = read("PROVISIONING_PROFILE[sdk=iphoneos*]") || result.mobileProvisionIdentifier;
1137+
});
10641138
}
1065-
}
1066-
let fileName = path.join(this.platformData.projectRoot, "teamid");
1067-
if (this.$fs.exists(fileName).wait()) {
1068-
return this.$fs.readText(fileName).wait();
1069-
}
1070-
return null;
1139+
return result;
1140+
}).future<provision.MobileProvision>()();
10711141
}
10721142

1073-
private getDevelopmentTeam(): string {
1074-
let teamId: string;
1075-
if (this.$options.teamId) {
1076-
teamId = this.$options.teamId;
1077-
} else {
1078-
teamId = this.readTeamId();
1079-
}
1080-
if (!teamId) {
1143+
private getProvisioningProfile(): IFuture<provision.MobileProvision> {
1144+
return (() => {
1145+
let profile: provision.MobileProvision;
1146+
1147+
const allCertificates = cert.read();
1148+
const allProfiles = provision.read();
1149+
const query: provision.Query = {
1150+
Certificates: allCertificates.valid,
1151+
AppId: this.$projectData.projectId,
1152+
Type: "Development"
1153+
};
1154+
1155+
if (this.$options.device) {
1156+
query.ProvisionedDevices = [this.$options.device];
1157+
} else {
1158+
this.$devicesService.initialize().wait();
1159+
let deviceUDIDs = _(this.$devicesService.getDeviceInstances())
1160+
.filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform))
1161+
.map(d => d.deviceInfo.identifier)
1162+
.toJSON();
1163+
query.ProvisionedDevices = deviceUDIDs;
1164+
}
1165+
1166+
const result = provision.select(allProfiles, query);
1167+
const choiceMap = result.eligable.reduce((acc, p) => {
1168+
acc[`'${p.Name}' (${p.TeamName}) ${p.Type}`] = p;
1169+
return acc;
1170+
}, <{ [display: string]: provision.MobileProvision }>{});
1171+
1172+
const choices = Object.keys(choiceMap);
1173+
if (choices.length > 0) {
1174+
const choice = this.$prompter.promptForChoice(
1175+
`Select provisioning profiles (found ${result.eligable.length} eligable, and ${result.nonEligable.length} non-eligable)`,
1176+
choices
1177+
).wait();
1178+
profile = choiceMap[choice];
1179+
}
1180+
1181+
return profile;
1182+
}).future<provision.MobileProvision>()();
1183+
}
1184+
1185+
private getDevelopmentTeam(): IFuture<string> {
1186+
return (() => {
1187+
let teamId: string;
10811188
let teams = this.getDevelopmentTeams();
10821189
this.$logger.warn("Xcode 8 requires a team id to be specified when building for device.");
10831190
this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commnads.");
@@ -1091,27 +1198,75 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10911198
}
10921199
let choice = this.$prompter.promptForChoice('Found multiple development teams, select one:', choices).wait();
10931200
teamId = teams[choices.indexOf(choice)].id;
1201+
}
1202+
return teamId;
1203+
}).future<string>()();
1204+
}
10941205

1095-
let choicesPersist = [
1096-
"Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.",
1097-
"Yes, persist the team id in platforms folder.",
1098-
"No, don't persist this setting."
1099-
];
1100-
let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist).wait();
1101-
switch (choicesPersist.indexOf(choicePersist)) {
1102-
case 0:
1103-
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1104-
this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n").wait();
1105-
break;
1106-
case 1:
1107-
this.$fs.writeFile(path.join(this.platformData.projectRoot, "teamid"), teamId);
1108-
break;
1109-
default:
1110-
break;
1111-
}
1206+
private persistProvisioningProfiles(uuid: string): IFuture<void> {
1207+
return (() => {
1208+
let choicesPersist = [
1209+
"Yes, set the PROVISIONING_PROFILE[sdk=iphoneos*] setting in build.xcconfig file.",
1210+
"Yes, persist the mobileprovision uuid in platforms folder.",
1211+
"No, don't persist this setting."
1212+
];
1213+
let choicePersist = this.$prompter.promptForChoice("Do you want to make mobileprovision: " + uuid + " a persistent choice for your app?", choicesPersist).wait();
1214+
switch (choicesPersist.indexOf(choicePersist)) {
1215+
case 0:
1216+
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1217+
this.$fs.appendFile(xcconfigFile, "\nPROVISIONING_PROFILE[sdk=iphoneos*] = " + uuid + "\n").wait();
1218+
break;
1219+
case 1:
1220+
this.$fs.writeFile(path.join(this.platformData.projectRoot, "mobileprovision"), uuid).wait();
1221+
const teamidPath = path.join(this.platformData.projectRoot, "teamid");
1222+
if (this.$fs.exists(teamidPath).wait()) {
1223+
this.$fs.deleteFile(teamidPath).wait();
1224+
}
1225+
break;
1226+
default:
1227+
break;
11121228
}
1113-
}
1114-
return teamId;
1229+
}).future<void>()();
1230+
}
1231+
1232+
private persistDevelopmentTeam(teamId: string): IFuture<void> {
1233+
return (() => {
1234+
let choicesPersist = [
1235+
"Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.",
1236+
"Yes, persist the team id in platforms folder.",
1237+
"No, don't persist this setting."
1238+
];
1239+
let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist).wait();
1240+
switch (choicesPersist.indexOf(choicePersist)) {
1241+
case 0:
1242+
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1243+
this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n").wait();
1244+
break;
1245+
case 1:
1246+
this.$fs.writeFile(path.join(this.platformData.projectRoot, "teamid"), teamId).wait();
1247+
const mobileprovisionPath = path.join(this.platformData.projectRoot, "mobileprovision");
1248+
if (this.$fs.exists(mobileprovisionPath).wait()) {
1249+
this.$fs.deleteFile(mobileprovisionPath).wait();
1250+
}
1251+
break;
1252+
default:
1253+
break;
1254+
}
1255+
}).future<void>()();
1256+
}
1257+
1258+
private readBuildConfigFromPlatforms(): IFuture<IiOSBuildConfig> {
1259+
return (() => {
1260+
let mobileprovisionPath = path.join(this.platformData.projectRoot, "mobileprovision");
1261+
if (this.$fs.exists(mobileprovisionPath).wait()) {
1262+
return { mobileProvisionIdentifier: this.$fs.readText(mobileprovisionPath).wait() };
1263+
}
1264+
let teamidPath = path.join(this.platformData.projectRoot, "teamid");
1265+
if (this.$fs.exists(teamidPath).wait()) {
1266+
return { teamIdentifier: this.$fs.readText(teamidPath).wait() };
1267+
}
1268+
return <IiOSBuildConfig>{};
1269+
}).future<IiOSBuildConfig>()();
11151270
}
11161271
}
11171272

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"glob": "^7.0.3",
4545
"iconv-lite": "0.4.11",
4646
"inquirer": "0.9.0",
47+
"ios-mobileprovision-finder": "1.0.8",
4748
"ios-sim-portable": "~1.6.0",
4849
"lockfile": "1.0.1",
4950
"lodash": "4.13.1",
@@ -57,6 +58,7 @@
5758
"node-inspector": "https://github.com/NativeScript/node-inspector/tarball/v0.7.4.2",
5859
"open": "0.0.5",
5960
"osenv": "0.1.3",
61+
"pbxproj-dom": "1.0.3",
6062
"plist": "1.1.0",
6163
"plist-merge-patch": "0.0.9",
6264
"plistlib": "0.2.1",

0 commit comments

Comments
 (0)