Skip to content

Commit 3d77a82

Browse files
committed
Add initial code for manual and automatic Xcode signing
1 parent 6a2685e commit 3d77a82

File tree

3 files changed

+195
-53
lines changed

3 files changed

+195
-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: 189 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,9 +304,62 @@ 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);
307+
// TRICKY: I am not sure why we totally disregard the buildConfig parameter here.
308+
buildConfig = buildConfig || {};
309+
310+
if (this.$options.teamId) {
311+
buildConfig.teamIdentifier = this.$options.teamId;
312+
} else {
313+
buildConfig = this.readXCConfigSigning();
314+
if (!buildConfig.codeSignIdentity && !buildConfig.mobileProvisionIdentifier && !buildConfig.teamIdentifier) {
315+
buildConfig = this.readBuildConfigFromPlatforms();
316+
}
317+
}
318+
319+
let signingStyle: XcodeSigningStyle;
320+
if (buildConfig.codeSignIdentity || buildConfig.mobileProvisionIdentifier) {
321+
signingStyle = "Manual";
322+
} else if (buildConfig.teamIdentifier) {
323+
signingStyle = "Automatic";
324+
} else {
325+
let signingStyles = [
326+
"Manual - Select existing provisioning profile for use",
327+
"Automatic - Select Team ID for signing and let Xcode select managed provisioning profile"
328+
];
329+
let signingStyleIndex = signingStyles.indexOf(this.$prompter.promptForChoice("Select codesiging style", signingStyles).wait());
330+
signingStyle = new Array<XcodeSigningStyle>("Manual", "Automatic")[signingStyleIndex];
331+
332+
switch(signingStyle) {
333+
case "Manual":
334+
let profile = this.getProvisioningProfile();
335+
if (!profile) {
336+
this.$logger.error("No matching provisioning profile found.");
337+
}
338+
this.persistProvisioningProfiles(profile.UUID);
339+
this.$logger.info("Apply provisioning profile: " + profile.Name + " (" + profile.TeamName + ") " + profile.Type + " UUID: " + profile.UUID);
340+
buildConfig.mobileProvisionIdentifier = profile.UUID;
341+
buildConfig.teamIdentifier = profile.TeamIdentifier[0];
342+
break;
343+
case "Automatic":
344+
buildConfig.teamIdentifier = this.getDevelopmentTeam();
345+
this.persistDevelopmentTeam(buildConfig.teamIdentifier);
346+
break;
347+
}
348+
}
349+
350+
switch(signingStyle) {
351+
case "Manual": {
352+
const pbxprojPath = path.join(projectRoot, this.$projectData.projectName + ".xcodeproj", "project.pbxproj");
353+
const xcode = Xcode.open(pbxprojPath);
354+
xcode.setManualSigningStyle(this.$projectData.projectName);
355+
xcode.save();
356+
} break;
357+
case "Automatic": {
358+
const pbxprojPath = path.join(projectRoot, this.$projectData.projectName + ".xcodeproj", "project.pbxproj");
359+
const xcode = Xcode.open(pbxprojPath);
360+
xcode.setAutomaticSigningStyle(this.$projectData.projectName, buildConfig.teamIdentifier);
361+
xcode.save();
362+
} break;
306363
}
307364
}
308365

@@ -314,6 +371,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
314371
args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`);
315372
}
316373

374+
if (buildConfig && buildConfig.teamIdentifier) {
375+
args.push(`DEVELOPMENT_TEAM=${buildConfig.teamIdentifier}`);
376+
}
377+
317378
this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { cwd: this.$options, stdio: 'inherit' }).wait();
318379
this.createIpa(projectRoot).wait();
319380

@@ -1045,73 +1106,148 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10451106
return null;
10461107
}
10471108

1048-
private readTeamId(): string {
1109+
private readXCConfigSigning(): IiOSBuildConfig {
1110+
const result: IiOSBuildConfig = {};
10491111
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
10501112
if (this.$fs.exists(xcconfigFile).wait()) {
10511113
let text = this.$fs.readText(xcconfigFile).wait();
1052-
let teamId: string;
10531114
text.split(/\r?\n/).forEach((line) => {
10541115
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);
1116+
const read = (name: string) => {
1117+
if (line.indexOf(name) >= 0) {
1118+
let value = line.substr(line.lastIndexOf("=") + 1).trim();
1119+
if (value.charAt(value.length - 1) === ';') {
1120+
value = value.substr(0, value.length - 1).trim();
1121+
}
1122+
return value;
10591123
}
1060-
}
1124+
return undefined;
1125+
};
1126+
result.teamIdentifier = read("DEVELOPMENT_TEAM") || result.teamIdentifier;
1127+
result.codeSignIdentity = read("CODE_SIGN_IDENTITY") || result.codeSignIdentity;
1128+
result.mobileProvisionIdentifier = read("PROVISIONING_PROFILE[sdk=iphoneos*]") || result.mobileProvisionIdentifier;
10611129
});
1062-
if (teamId) {
1063-
return teamId;
1064-
}
10651130
}
1066-
let fileName = path.join(this.platformData.projectRoot, "teamid");
1067-
if (this.$fs.exists(fileName).wait()) {
1068-
return this.$fs.readText(fileName).wait();
1131+
return result;
1132+
}
1133+
1134+
private getProvisioningProfile(): provision.MobileProvision {
1135+
let profile: provision.MobileProvision;
1136+
1137+
const allCertificates = cert.read();
1138+
const allProfiles = provision.read();
1139+
const query: provision.Query = {
1140+
Certificates: allCertificates.valid,
1141+
AppId: this.$projectData.projectId,
1142+
Type: "Development"
1143+
};
1144+
1145+
if (this.$options.device) {
1146+
query.ProvisionedDevices = [this.$options.device];
1147+
} else {
1148+
this.$devicesService.initialize().wait();
1149+
let deviceUDIDs = _(this.$devicesService.getDeviceInstances())
1150+
.filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform))
1151+
.map(d => d.deviceInfo.identifier)
1152+
.toJSON();
1153+
query.ProvisionedDevices = deviceUDIDs;
1154+
}
1155+
1156+
const result = provision.select(allProfiles, query);
1157+
const choiceMap = result.eligable.reduce((acc, p) => {
1158+
acc[`'${p.Name}' (${p.TeamName}) ${p.Type}`] = p;
1159+
return acc;
1160+
}, <{ [display: string]: provision.MobileProvision }>{});
1161+
1162+
const choices = Object.keys(choiceMap);
1163+
if (choices.length > 0) {
1164+
const choice = this.$prompter.promptForChoice(
1165+
`Select provisioning profiles (found ${result.eligable.length} eligable, and ${result.nonEligable.length} non-eligable)`,
1166+
choices
1167+
).wait();
1168+
profile = choiceMap[choice];
10691169
}
1070-
return null;
1170+
1171+
return profile;
10711172
}
10721173

10731174
private getDevelopmentTeam(): string {
10741175
let teamId: string;
1075-
if (this.$options.teamId) {
1076-
teamId = this.$options.teamId;
1077-
} else {
1078-
teamId = this.readTeamId();
1176+
let teams = this.getDevelopmentTeams();
1177+
this.$logger.warn("Xcode 8 requires a team id to be specified when building for device.");
1178+
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.");
1179+
if (teams.length === 1) {
1180+
teamId = teams[0].id;
1181+
this.$logger.warn("Found and using the following development team installed on your system: " + teams[0].name + " (" + teams[0].id + ")");
1182+
} else if (teams.length > 0) {
1183+
let choices: string[] = [];
1184+
for (let team of teams) {
1185+
choices.push(team.name + " (" + team.id + ")");
1186+
}
1187+
let choice = this.$prompter.promptForChoice('Found multiple development teams, select one:', choices).wait();
1188+
teamId = teams[choices.indexOf(choice)].id;
10791189
}
1080-
if (!teamId) {
1081-
let teams = this.getDevelopmentTeams();
1082-
this.$logger.warn("Xcode 8 requires a team id to be specified when building for device.");
1083-
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.");
1084-
if (teams.length === 1) {
1085-
teamId = teams[0].id;
1086-
this.$logger.warn("Found and using the following development team installed on your system: " + teams[0].name + " (" + teams[0].id + ")");
1087-
} else if (teams.length > 0) {
1088-
let choices: string[] = [];
1089-
for (let team of teams) {
1090-
choices.push(team.name + " (" + team.id + ")");
1190+
return teamId;
1191+
}
1192+
1193+
private persistProvisioningProfiles(uuid: string) {
1194+
let choicesPersist = [
1195+
"Yes, set the PROVISIONING_PROFILE[sdk=iphoneos*] setting in build.xcconfig file.",
1196+
"Yes, persist the mobileprovision uuid in platforms folder.",
1197+
"No, don't persist this setting."
1198+
];
1199+
let choicePersist = this.$prompter.promptForChoice("Do you want to make mobileprovision: " + uuid + " a persistent choice for your app?", choicesPersist).wait();
1200+
switch (choicesPersist.indexOf(choicePersist)) {
1201+
case 0:
1202+
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1203+
this.$fs.appendFile(xcconfigFile, "\nPROVISIONING_PROFILE[sdk=iphoneos*] = " + uuid + "\n").wait();
1204+
break;
1205+
case 1:
1206+
this.$fs.writeFile(path.join(this.platformData.projectRoot, "mobileprovision"), uuid).wait();
1207+
const teamidPath = path.join(this.platformData.projectRoot, "teamid");
1208+
if (this.$fs.exists(teamidPath).wait()) {
1209+
this.$fs.deleteFile(teamidPath).wait();
10911210
}
1092-
let choice = this.$prompter.promptForChoice('Found multiple development teams, select one:', choices).wait();
1093-
teamId = teams[choices.indexOf(choice)].id;
1094-
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;
1211+
break;
1212+
default:
1213+
break;
1214+
}
1215+
}
1216+
1217+
private persistDevelopmentTeam(teamId: string) {
1218+
let choicesPersist = [
1219+
"Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.",
1220+
"Yes, persist the team id in platforms folder.",
1221+
"No, don't persist this setting."
1222+
];
1223+
let choicePersist = this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist).wait();
1224+
switch (choicesPersist.indexOf(choicePersist)) {
1225+
case 0:
1226+
let xcconfigFile = path.join(this.$projectData.appResourcesDirectoryPath, this.platformData.normalizedPlatformName, "build.xcconfig");
1227+
this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n").wait();
1228+
break;
1229+
case 1:
1230+
this.$fs.writeFile(path.join(this.platformData.projectRoot, "teamid"), teamId).wait();
1231+
const mobileprovisionPath = path.join(this.platformData.projectRoot, "mobileprovision");
1232+
if (this.$fs.exists(mobileprovisionPath).wait()) {
1233+
this.$fs.deleteFile(mobileprovisionPath).wait();
11111234
}
1112-
}
1235+
break;
1236+
default:
1237+
break;
11131238
}
1114-
return teamId;
1239+
}
1240+
1241+
private readBuildConfigFromPlatforms(): IiOSBuildConfig {
1242+
let mobileprovisionPath = path.join(this.platformData.projectRoot, "mobileprovision");
1243+
if (this.$fs.exists(mobileprovisionPath).wait()) {
1244+
return { mobileProvisionIdentifier: this.$fs.readText(mobileprovisionPath).wait() };
1245+
}
1246+
let teamidPath = path.join(this.platformData.projectRoot, "teamid");
1247+
if (this.$fs.exists(teamidPath).wait()) {
1248+
return { teamIdentifier: this.$fs.readText(teamidPath).wait() };
1249+
}
1250+
return {};
11151251
}
11161252
}
11171253

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.4",
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)