buildIOS function

Future<void> buildIOS(
  1. ExecContext context, {
  2. required String gh,
  3. required String appName,
  4. 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 = IOSSigningEnvironment.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", "");
  final firebase = context.yaml.getAsMap("firebase");
  final projectId = firebase.get("project_id", "");
  final logger = firebase.getAsMap("logger");
  final enableLogger = logger.get("enable", false) && projectId.isNotEmpty;
  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, or set `${IOSSigningEnvironment.apiIssuerIdVariable}`.",
    );
    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;
  }
  String? firebaseAppId;
  if (enableLogger) {
    final googleServicePlist = await find(
      Directory("ios"),
      RegExp(r"GoogleService-Info.plist$"),
    );
    if (googleServicePlist == null) {
      error(
        "Cannot find the `GoogleService-Info.plist` file, please download the file from Firebase and place it under the IOS folder. `GoogleService-Info.plist`ファイルが見つかりません。Firebaseからファイルをダウンロードしiosフォルダ以下に配置してください。",
      );
      return;
    }
    final googleServiceDocument =
        XmlDocument.parse(await googleServicePlist.readAsString());
    final googleServiceDict =
        googleServiceDocument.findAllElements("dict").firstOrNull;
    if (googleServiceDict == null) {
      error(
        "Could not find `dict` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
      );
      return;
    }
    final googleAppIdNode = googleServiceDict.children.firstWhereOrNull((p0) {
      return p0 is XmlElement &&
          p0.name.toString() == "key" &&
          p0.innerText == "GOOGLE_APP_ID";
    });
    if (googleAppIdNode == null) {
      error(
        "Could not find `GOOGLE_APP_ID` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
      );
      return;
    }
    firebaseAppId = googleAppIdNode.nextElementSibling?.innerText;
    if (firebaseAppId.isEmpty) {
      error(
        "Could not find `GOOGLE_APP_ID` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
      );
      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");
  if (!isLocalApply) {
    final appSuffix = appName.toUpperCase();
    final openssl = context.yaml.getAsMap("bin").get("openssl", "openssl");
    final certificate = await _resolveIOSCertificate(openssl: openssl);
    if (certificate == null) {
      return;
    }
    final apiKey = await _resolveIOSApiKey();
    if (apiKey == null) {
      return;
    }
    final mobileProvisionFile = await find(
      Directory("ios"),
      RegExp("([^/.]+).mobileprovision"),
    );
    if (mobileProvisionFile != null) {
      final mobileProvision =
          base64.encode(await mobileProvisionFile.readAsBytes());
      await githubSecret(
        "Store `${mobileProvisionFile.path.last()}` in `secrets.IOS_PROVISIONING_PROFILE_$appSuffix`.",
        gh: gh,
        name: "IOS_PROVISIONING_PROFILE_$appSuffix",
        value: mobileProvision,
      );
    }
    await githubSecret(
      "Store ${certificate.source} in `secrets.IOS_CERTIFICATES_P12_$appSuffix`.",
      gh: gh,
      name: "IOS_CERTIFICATES_P12_$appSuffix",
      value: certificate.p12,
    );
    await githubSecret(
      "Store the p12 password in `secrets.IOS_CERTIFICATE_PASSWORD_$appSuffix`.",
      gh: gh,
      name: "IOS_CERTIFICATE_PASSWORD_$appSuffix",
      value: certificate.password,
    );
    await githubSecret(
      "Store API key id in `secrets.IOS_API_KEY_ID_$appSuffix`.",
      gh: gh,
      name: "IOS_API_KEY_ID_$appSuffix",
      value: apiKey.keyId,
    );
    await githubSecret(
      "Store ${apiKey.source} in `secrets.IOS_API_AUTHKEY_P8_$appSuffix`.",
      gh: gh,
      name: "IOS_API_AUTHKEY_P8_$appSuffix",
      value: apiKey.privateKeyBase64,
    );
    await githubSecret(
      "Store Issuer ID in `secrets.IOS_API_ISSUER_ID_$appSuffix`.",
      gh: gh,
      name: "IOS_API_ISSUER_ID_$appSuffix",
      value: issuerId,
    );
  }
  if (!isLocalApply) {
    if (!await registerDartDefinesSecrets(
      gh: gh,
      path: kProductionDartDefinesFile,
      suffix: appName.toUpperCase(),
    )) {
      return;
    }
    if (!await registerDartDefinesSecrets(
      gh: gh,
      path: kDevelopmentDartDefinesFile,
    )) {
      return;
    }
  }
  final gitDir = await findGitDirectory(Directory.current);
  await GitStatusCheckActionCliCode(
    workingDirectory: gitDir,
  ).generateFile("action.yaml");
  final iosCode = GithubActionsIOSCliCode(
    workingDirectory: gitDir,
    defaultIncrementNumber: defaultIncrementNumber,
    enableLogger: enableLogger,
    firebaseAppId: firebaseAppId,
  );
  await iosCode.generateFile(
    "build_ios_${appName.toLowerCase()}.yaml",
    filter: (value) {
      return iosCode._additionalSlackFilter(
        applyDartDefinesToWorkflow(
          value.replaceAll(
            "#### REPLACE_APP_NAME ####",
            appName.toUpperCase(),
          ),
          buildNames: dartDefinesEnvironmentNames(kProductionDartDefinesFile),
          statusCheckNames:
              dartDefinesEnvironmentNames(kDevelopmentDartDefinesFile),
          suffix: 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"));
}