buildIOS function
Future<void>
buildIOS(
- ExecContext context, {
- required String gh,
- required String appName,
- required int defaultIncrementNumber,
Gibhut Actions build for IOS.
IOS用のGibhut Actionsのビルド。
Implementation
Future<void> buildIOS(
ExecContext context, {
required String gh,
required String appName,
required int defaultIncrementNumber,
}) async {
final github = context.yaml.getAsMap("github");
final action = github.getAsMap("action");
final ios = action.getAsMap("ios");
final issuerId = ios.get("issuer_id", "");
final teamId = ios.get("team_id", "");
final secretGithub = context.secrets.getAsMap("github");
final slack = secretGithub.getAsMap("slack");
final slackIncomingWebhookUrl = slack.get("incoming_webhook_url", "");
if (issuerId.isEmpty) {
error(
"The item [github]->[action]->[ios]->[issuer_id] is missing. Copy the Issuer ID listed on the page at https://appstoreconnect.apple.com/access/api.",
);
return;
}
if (teamId.isEmpty) {
error(
"The item [github]->[action]->[ios]->[team_id] is missing. Copy and include your team ID from https://developer.apple.com/account.",
);
return;
}
final p12File = await find(
Directory("ios"),
RegExp(r".p12$"),
);
if (p12File == null) {
error(
"Cannot find the `distribution/development.p12` file, download the cer file from AppleDeveloperProgram and create a p12 file with `katana app p12`. `distribution/development.p12`ファイルが見つかりません。cerファイルをAppleDeveloperProgramからダウンロードし、`katana app p12`でp12ファイルを作成してください。",
);
return;
}
final mobileProvisionFile = await find(
Directory("ios"),
RegExp("([^/.]+).mobileprovision"),
);
final passwordFile = File("ios/ios_certificate_password.key");
if (!passwordFile.existsSync()) {
error(
"Cannot find password file for Certificate. Please create a p12 file with `katana app p12`. Certificate用のパスワードファイルが見つかりません。`katana app p12`でp12ファイルを作成してください。",
);
return;
}
final p8File = await find(
Directory("ios"),
RegExp(r"AuthKey_([a-zA-Z0-9]+).p8$"),
);
if (p8File == null) {
error(
"Cannot find the `AuthKey` file, please download the file from AppStoreConnect and place it under the IOS folder. `AuthKey`ファイルが見つかりません。AppStoreConnectからファイルをダウンロードしiosフォルダ以下に配置してください。",
);
return;
}
label("Edit project.pbxproj");
final xcode = XCode();
await xcode.load();
for (final settings in xcode.pbxBuildConfiguration) {
settings.buildSettings.removeWhere((e) => e.key == "DEVELOPMENT_TEAM");
}
await xcode.save();
label("Edit Release.xcconfig");
final xcconfigFile = File("ios/Flutter/Release.xcconfig");
if (!xcconfigFile.existsSync()) {
error(
"Cannot find `ios/Flutter/Release.xcconfig`. Project is broken.",
);
return;
}
final xcconfig = await xcconfigFile.readAsLines();
if (!xcconfig.any((e) => e.startsWith("DEVELOPMENT_TEAM="))) {
xcconfig.add("DEVELOPMENT_TEAM=$teamId");
}
await xcconfigFile.writeAsString(xcconfig.join("\n"));
label("Create ExportOptions.plist");
await const ExportOptionsCliCode().generateFile("ExportOptions.plist");
final p8 = base64.encode(await p8File.readAsBytes());
final p8Key =
RegExp(r"AuthKey_([a-zA-Z0-9]+).p8$").firstMatch(p8File.path)!.group(1)!;
final p12 = base64.encode(await p12File.readAsBytes());
final password = await passwordFile.readAsString();
if (mobileProvisionFile != null) {
final mobileProvision =
base64.encode(await mobileProvisionFile.readAsBytes());
await command(
"Store `${mobileProvisionFile.path.last()}` in `secrets.IOS_PROVISIONING_PROFILE_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_PROVISIONING_PROFILE_${appName.toUpperCase()}",
"--body",
mobileProvision,
],
);
}
await command(
"Store `${p12File.path.last()}` in `secrets.IOS_CERTIFICATES_P12_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_CERTIFICATES_P12_${appName.toUpperCase()}",
"--body",
p12,
],
);
await command(
"Store `ios_certificate_password.key` in `secrets.IOS_CERTIFICATE_PASSWORD_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_CERTIFICATE_PASSWORD_${appName.toUpperCase()}",
"--body",
password,
],
);
await command(
"Store API key id in `secrets.IOS_API_KEY_ID_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_API_KEY_ID_${appName.toUpperCase()}",
"--body",
p8Key,
],
);
await command(
"Store `${p8File.path}` in `secrets.IOS_API_AUTHKEY_P8_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_API_AUTHKEY_P8_${appName.toUpperCase()}",
"--body",
p8,
],
);
await command(
"Store Issuer ID in `secrets.IOS_API_ISSUER_ID_${appName.toUpperCase()}`.",
[
gh,
"secret",
"set",
"IOS_API_ISSUER_ID_${appName.toUpperCase()}",
"--body",
issuerId,
],
);
final gitDir = await findGitDirectory(Directory.current);
final iosCode = GithubActionsIOSCliCode(
workingDirectory: gitDir,
defaultIncrementNumber: defaultIncrementNumber,
);
await iosCode.generateFile(
"build_ios_${appName.toLowerCase()}.yaml",
filter: (value) {
return iosCode._additionalSlackFilter(
value.replaceAll(
"#### REPLACE_APP_NAME ####",
appName.toUpperCase(),
),
slackIncomingWebhookUrl,
);
},
);
label("Edit Info.plist.");
final plist = File("ios/Runner/Info.plist");
final document = XmlDocument.parse(await plist.readAsString());
final dict = document.findAllElements("dict").firstOrNull;
if (dict == null) {
throw Exception(
"Could not find `dict` element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
final nodeITSEncryptionExportComplianceCode =
dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "ITSEncryptionExportComplianceCode";
});
if (nodeITSEncryptionExportComplianceCode == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[XmlText("ITSEncryptionExportComplianceCode")],
),
XmlElement(
XmlName("false"),
),
],
);
}
final nodeITSAppUsesNonExemptEncryption =
dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "ITSAppUsesNonExemptEncryption";
});
if (nodeITSAppUsesNonExemptEncryption == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[XmlText("ITSAppUsesNonExemptEncryption")],
),
XmlElement(
XmlName("false"),
),
],
);
}
await plist.writeAsString(
document.toXmlString(pretty: true, indent: "\t", newLine: "\n"),
);
label("Rewrite `.gitignore`.");
final gitignore = File("ios/.gitignore");
if (!gitignore.existsSync()) {
error("Cannot find `ios/.gitignore`. Project is broken.");
return;
}
final gitignores = await gitignore.readAsLines();
if (context.yaml.getAsMap("git").get("ignore_secure_file", true)) {
if (!gitignores.any((e) => e.startsWith("**/*.p12"))) {
gitignores.add("**/*.p12");
}
if (!gitignores.any((e) => e.startsWith("**/*.p8"))) {
gitignores.add("**/*.p8");
}
if (!gitignores.any((e) => e.startsWith("**/*.mobileprovision"))) {
gitignores.add("**/*.mobileprovision");
}
if (!gitignores.any((e) => e.startsWith("**/*.pem"))) {
gitignores.add("**/*.pem");
}
if (!gitignores.any((e) => e.startsWith("**/*.cer"))) {
gitignores.add("**/*.cer");
}
if (!gitignores.any((e) => e.startsWith("**/*.certSigningRequest"))) {
gitignores.add("**/*.certSigningRequest");
}
if (!gitignores
.any((e) => e.startsWith("**/ios_certificate_password.key"))) {
gitignores.add("**/ios_certificate_password.key");
}
if (!gitignores.any((e) => e.startsWith("**/ios_enterprise.key"))) {
gitignores.add("**/ios_enterprise.key");
}
} else {
gitignores.removeWhere((e) => e.startsWith("**/*.p12"));
gitignores.removeWhere((e) => e.startsWith("**/*.p8"));
gitignores.removeWhere((e) => e.startsWith("**/*.mobileprovision"));
gitignores.removeWhere((e) => e.startsWith("**/*.pem"));
gitignores.removeWhere((e) => e.startsWith("**/*.cer"));
gitignores.removeWhere((e) => e.startsWith("**/*.certSigningRequest"));
gitignores
.removeWhere((e) => e.startsWith("**/ios_certificate_password.key"));
gitignores.removeWhere((e) => e.startsWith("**/ios_enterprise.key"));
}
await gitignore.writeAsString(gitignores.join("\n"));
}