buildAndroid function

Future<void> buildAndroid(
  1. ExecContext context, {
  2. required String gh,
  3. required String appName,
  4. required int defaultIncrementNumber,
})

Gibhut Actions build for Android.

Android用のGibhut Actionsのビルド。

Implementation

Future<void> buildAndroid(
  ExecContext context, {
  required String gh,
  required String appName,
  required int defaultIncrementNumber,
}) async {
  final github = context.yaml.getAsMap("github");
  final action = github.getAsMap("action");
  final android = action.getAsMap("android");
  final changesNotSentForReview =
      android.get("changes_not_sent_for_review", nullOfBool);
  final status = android.get("status", "draft");
  final secretGithub = context.secrets.getAsMap("github");
  final slack = secretGithub.getAsMap("slack");
  final slackIncomingWebhookUrl = slack.get("incoming_webhook_url", "");
  final useEnvironment = AndroidSigningEnvironment.isConfigured();
  if (!isLocalApply && !useEnvironment) {
    final keystoreFile = File("android/app/appkey.keystore");
    if (!keystoreFile.existsSync()) {
      error(
        "Cannot find `android/app/appkey.keystore`. Run `katana app keystore` to create the keystore file. `android/app/appkey.keystore`が見つかりません。`katana app keystore`を実行しキーストアのファイルを作成してください。",
      );
      return;
    }
    final keyPropertiesFile = File("android/key.properties");
    if (!keyPropertiesFile.existsSync()) {
      error(
        "Cannot find `android/key.properties`. Run `katana app keystore` to create the keystore file. `android/key.properties`が見つかりません。`katana app keystore`を実行しキーストアのファイルを作成してください。",
      );
      return;
    }
    final serviceAccountFile = await find(
      Directory("android"),
      RegExp("([a-z0-9_-]+).json"),
      recursive: false,
    );
    if (serviceAccountFile == null) {
      error(
        "Json for service account not found, please refer to https://mathru.notion.site/Google-Play-Developer-df655aff2dfb49988b82feb7aae3c61b to set it up. サービスアカウント用のJsonが見つかりません。https://mathru.notion.site/Google-Play-Developer-df655aff2dfb49988b82feb7aae3c61b を参考に設定してください。",
      );
      return;
    }
  }
  if (!isLocalApply && useEnvironment) {
    if (AndroidSigningEnvironment.value(
              AndroidSigningEnvironment.serviceAccountVariable,
            ) ==
            null &&
        await find(
              Directory("android"),
              RegExp("([a-z0-9_-]+).json"),
              recursive: false,
            ) ==
            null) {
      error(
        "Json for service account not found. Set `${AndroidSigningEnvironment.serviceAccountVariable}` or place the json in `android/`. サービスアカウント用のJsonが見つかりません。`${AndroidSigningEnvironment.serviceAccountVariable}`を設定するか`android/`にJsonを配置してください。",
      );
      return;
    }
  }
  final gradle = AppGradle();
  await gradle.load();
  final packageName = gradle.android?.defaultConfig.applicationId;
  if (packageName == null) {
    error(
      "Cannot find [android]->[defaultConfig]->[applicationId] in `android/app/build.gradle`.",
    );
    return;
  }
  if (useEnvironment &&
      !gradle.rawData.contains(AndroidSigningEnvironment.beginMarker)) {
    error(
      "The Gradle file does not read the keystore from environment variables. Set [app]->[keystore]->[enable] to `true` and run `katana apply`. Gradleファイルが環境変数からキーストアを読み込む設定になっていません。[app]->[keystore]->[enable]を`true`にして`katana apply`を実行してください。",
    );
    return;
  }
  final appSuffix = appName.toUpperCase();
  if (!isLocalApply && useEnvironment) {
    final keystore = _normalizeBase64(
      AndroidSigningEnvironment.value(
        AndroidSigningEnvironment.keystoreVariable,
      )!,
      AndroidSigningEnvironment.keystoreVariable,
    );
    final serviceAccountValue = AndroidSigningEnvironment.value(
      AndroidSigningEnvironment.serviceAccountVariable,
    );
    final serviceAccount = serviceAccountValue != null
        ? _normalizeBase64(
            serviceAccountValue,
            AndroidSigningEnvironment.serviceAccountVariable,
          )
        : base64.encode(
            await (await find(
              Directory("android"),
              RegExp("([a-z0-9_-]+).json"),
              recursive: false,
            ))!
                .readAsBytes(),
          );
    if (keystore == null || serviceAccount == null) {
      return;
    }
    await githubSecret(
      "Store `${AndroidSigningEnvironment.keystoreVariable}` in `secrets.ANDROID_KEYSTORE_B64_$appSuffix`.",
      gh: gh,
      name: "ANDROID_KEYSTORE_B64_$appSuffix",
      value: keystore,
    );
    await githubSecret(
      "Store `${AndroidSigningEnvironment.passwordVariable}` in `secrets.ANDROID_KEYSTORE_PASSWORD_$appSuffix`.",
      gh: gh,
      name: "ANDROID_KEYSTORE_PASSWORD_$appSuffix",
      value: AndroidSigningEnvironment.value(
        AndroidSigningEnvironment.passwordVariable,
      )!,
    );
    final alias = AndroidSigningEnvironment.value(
      AndroidSigningEnvironment.aliasVariable,
    );
    if (alias != null) {
      await githubSecret(
        "Store `${AndroidSigningEnvironment.aliasVariable}` in `secrets.ANDROID_KEYSTORE_ALIAS_$appSuffix`.",
        gh: gh,
        name: "ANDROID_KEYSTORE_ALIAS_$appSuffix",
        value: alias,
      );
    }
    await githubSecret(
      "Store the service account key in `secrets.ANDROID_SERVICE_ACCOUNT_KEY_JSON_$appSuffix`.",
      gh: gh,
      name: "ANDROID_SERVICE_ACCOUNT_KEY_JSON_$appSuffix",
      value: serviceAccount,
    );
  } else if (!isLocalApply) {
    final keystoreFile = File("android/app/appkey.keystore");
    final keyPropertiesFile = File("android/key.properties");
    final serviceAccountFile = await find(
      Directory("android"),
      RegExp("([a-z0-9_-]+).json"),
      recursive: false,
    );
    final keystore = base64.encode(await keystoreFile.readAsBytes());
    final keyProperties = base64.encode(await keyPropertiesFile.readAsBytes());
    final serviceAccount =
        base64.encode(await serviceAccountFile!.readAsBytes());
    await githubSecret(
      "Store `appkey.keystore` in `secrets.ANDROID_KEYSTORE_$appSuffix`.",
      gh: gh,
      name: "ANDROID_KEYSTORE_$appSuffix",
      value: keystore,
    );
    await githubSecret(
      "Store `key.properties` in `secrets.ANDROID_KEY_PROPERTIES_$appSuffix`.",
      gh: gh,
      name: "ANDROID_KEY_PROPERTIES_$appSuffix",
      value: keyProperties,
    );
    await githubSecret(
      "Store `service_account.json` in `secrets.ANDROID_SERVICE_ACCOUNT_KEY_JSON_$appSuffix`.",
      gh: gh,
      name: "ANDROID_SERVICE_ACCOUNT_KEY_JSON_$appSuffix",
      value: serviceAccount,
    );
  }
  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 androidCode = GithubActionsAndroidCliCode(
    workingDirectory: gitDir,
    defaultIncrementNumber: defaultIncrementNumber,
    changesNotSentForReview: changesNotSentForReview,
    status: status,
    useEnvironmentSigning: useEnvironment,
  );
  await androidCode.generateFile(
    "build_android_${appName.toLowerCase()}.yaml",
    filter: (value) {
      return androidCode._additionalSlackFilter(
        applyDartDefinesToWorkflow(
          value,
          buildNames: dartDefinesEnvironmentNames(kProductionDartDefinesFile),
          statusCheckNames:
              dartDefinesEnvironmentNames(kDevelopmentDartDefinesFile),
          suffix: appName.toUpperCase(),
        )
            .replaceAll(
              "#### REPLACE_ANDROID_PACKAGE_NAME ####",
              packageName.replaceAll('"', ""),
            )
            .replaceAll(
              "#### REPLACE_APP_NAME ####",
              appName.toUpperCase(),
            ),
        slackIncomingWebhookUrl,
      );
    },
  );
  label("Rewrite `.gitignore`.");
  final gitignore = File("android/.gitignore");
  if (!gitignore.existsSync()) {
    error("Cannot find `android/.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("key.properties"))) {
      gitignores.add("key.properties");
    }
    if (!gitignores.any((e) => e.startsWith("**/*.keystore"))) {
      gitignores.add("**/*.keystore");
    }
    if (!gitignores.any((e) => e.startsWith("**/*.jks"))) {
      gitignores.add("**/*.jks");
    }
    if (!gitignores.any((e) => e.startsWith("**/*.p12"))) {
      gitignores.add("**/*.p12");
    }
    if (!gitignores.any((e) => e.startsWith("*.json"))) {
      gitignores.add("*.json");
    }
    if (!gitignores.any((e) => e.startsWith("**/appkey_fingerprint.txt"))) {
      gitignores.add("**/appkey_fingerprint.txt");
    }
    if (!gitignores.any((e) => e.startsWith("**/appkey_password.key"))) {
      gitignores.add("**/appkey_password.key");
    }
  } else {
    gitignores.removeWhere((e) => e.startsWith("**/*.p12"));
    gitignores.removeWhere((e) => e.startsWith("*.json"));
    gitignores.removeWhere((e) => e.startsWith("**/appkey_fingerprint.txt"));
    gitignores.removeWhere((e) => e.startsWith("**/appkey_password.key"));
    gitignores.removeWhere((e) => e.startsWith("key.properties"));
    gitignores.removeWhere((e) => e.startsWith("**/*.keystore"));
    gitignores.removeWhere((e) => e.startsWith("**/*.jks"));
  }
  await gitignore.writeAsString(gitignores.join("\n"));
}