matrixPlanOf function

Future<({Map<String, Object> plan, List<String> problems})> matrixPlanOf(
  1. List<SmfModule> modules, {
  2. bool everyCombination = false,
  3. Role<Object>? native,
  4. Map<String, String?> roleOptions = const {},
})

The plan of the jobs of CI that check the matrix of modules with roleOptions, which the matrix tools print with --plan, or its problems: the cases of the contract harness that failed, which leave the plan empty. The plan has, as JSON:

  • combinations: the apps with every module that the jobs of the matrix check, as the value of --combinations: pairwise, or, with everyCombination, all when there are at most maxEveryCombination, those for the other values of the mode options included, and 3-wise when there are more;
  • shards: the shards of the matrix, 1/<count> to <count>/<count>, one for each job of the matrix: as many as it takes for each to check at most appsPerShard apps, the apps of the matrix and its apps with every module, which the jobs check once more in a directory whose name has letters beyond ASCII. The apps with every module for the other values of the mode options are apps of the matrix, and count once: the jobs do not check them a second time; and, with the role native, the app entry role, whose provider owns the native projects of an app, the names of the apps with every module that CI generates one for each job:
  • apps: a pairwise covering of them, which CI builds for Android and iOS;
  • start: a pairwise covering of those without the modules whose steps need an external service (see everyModuleAppsOf), which CI starts on devices;
  • entries: one app for each provider of native (see EveryModuleAppPerProvider), which CI archives, and configures with the external services of its modules to start it.

The apps of these three lists are apps of everyModuleAppsOf. None is an app for another value of a mode option, so a role that gets such an option, or such an option that gets another value, changes only the shards: what CI builds for a platform and starts on a device has the first value of every mode option, the one of an app that got none.

So a new provider or module changes the plan, not the jobs of CI, and a job that takes its app or shard from the plan does not take longer as the apps get more: only the number of jobs grows, with a pairwise or 3-wise covering rather than with every combination.

Implementation

Future<({Map<String, Object> plan, List<String> problems})> matrixPlanOf(
  List<SmfModule> modules, {
  bool everyCombination = false,
  Role? native,
  Map<String, String?> roleOptions = const {},
}) async {
  // In the order found, once each.
  final problems = <String>{};
  final every = await everyModuleAppsOf(modules, roleOptions: roleOptions);
  final modes = await modeAppsOf(modules, roleOptions: roleOptions);
  final combinations = switch (everyCombination) {
    false => EveryModuleCombinations.pairwise,
    true when every.apps.length + modes.apps.length > maxEveryCombination =>
      EveryModuleCombinations.threeWise,
    true => EveryModuleCombinations.all,
  };
  final matrix = await matrixOf(
    modules,
    roleOptions: roleOptions,
    everyModuleApps: combinations,
  );
  final start = native == null
      ? null
      : await everyModuleAppsOf(
          modules,
          roleOptions: roleOptions,
          withoutExternalSteps: true,
        );
  for (final result in [
    ...matrix.failed,
    ...every.failed,
    ...?start?.failed,
  ]) {
    problems.add('${result.contractCase}: ${result.errors.join('; ')}');
  }
  if (problems.isNotEmpty) {
    return (plan: const <String, Object>{}, problems: [...problems]);
  }

  // The apps with every module for the other values of the mode options
  // are among the apps of the matrix; the second directory has none.
  final checked =
      matrix.apps.length + combinations.select(every.apps, modules).length;
  List<String> names(List<MatrixApp> apps) =>
      [for (final app in apps) app.name];
  return (
    plan: <String, Object>{
      'combinations': combinations.option,
      'shards': [
        for (final shard in MatrixShard.allOf(
          max(1, (checked + appsPerShard - 1) ~/ appsPerShard),
        ))
          '$shard',
      ],
      if (native != null) ...{
        'apps': names(
          EveryModuleCombinations.pairwise.select(every.apps, modules),
        ),
        'start': names(
          EveryModuleCombinations.pairwise.select(start!.apps, modules),
        ),
        'entries': names(
          EveryModuleAppPerProvider(native).select(every.apps, modules),
        ),
      },
    },
    problems: const <String>[],
  );
}