buildServerpodCli function

Future<String> buildServerpodCli({
  1. required String buildRoot,
  2. String? serverpodHome,
})

Builds the serverpod repository's in-repo CLI once and returns the path to the executable (<buildRoot>/bundle/bin/serverpod_cli).

For the repository's own test suites, which run the CLI without a globally activated serverpod:

  • A prebuilt bundle is reused when SERVERPOD_CLI_EXE points at one (CI's build_cli job builds the CLI once for the whole workflow).
  • One build per buildRoot: concurrent callers (a run's suite isolates sharing a per-run root) elect a single builder via a lock; the rest wait and reuse its output.
  • Builds against one checkout are serialized across runs (a second lock, keyed on the checkout): dart pub get and the compile both touch the shared tools/serverpod_cli/.dart_tool.

Implementation

Future<String> buildServerpodCli({
  required String buildRoot,
  String? serverpodHome,
}) async {
  final prebuilt = Platform.environment['SERVERPOD_CLI_EXE'];
  if (prebuilt != null && File(prebuilt).existsSync()) return prebuilt;

  final home = serverpodHome ?? findServerpodHome();
  final cliRoot = p.join(home, 'tools', 'serverpod_cli');
  final exePath = p.join(
    buildRoot,
    'bundle',
    'bin',
    Platform.isWindows ? 'serverpod_cli.exe' : 'serverpod_cli',
  );
  if (File(exePath).existsSync()) return exePath;

  await InterProcessLock.withLock(
    '$buildRoot.lock',
    staleWhen: const StaleLockPolicy.processLiveness(
      staleAfter: Duration(minutes: 2),
    ),
    timeout: const Duration(minutes: 10),
    heartbeatInterval: const Duration(seconds: 30),
    () async {
      if (File(exePath).existsSync()) return;

      await InterProcessLock.withLock(
        _treeBuildLockPath(cliRoot),
        staleWhen: const StaleLockPolicy.processLiveness(
          staleAfter: Duration(minutes: 2),
        ),
        timeout: const Duration(minutes: 10),
        heartbeatInterval: const Duration(seconds: 30),
        () async {
          var result = await Process.run(dartExecutablePath, [
            'pub',
            'get',
          ], workingDirectory: cliRoot);
          if (result.exitCode != 0) {
            throw StateError(
              'pub get in $cliRoot failed:'
              '\n${result.stdout}\n${result.stderr}',
            );
          }

          // `dart build cli` (not `dart compile exe`): serverpod_cli pulls
          // sqlite3, whose native-asset build hooks `dart compile exe`
          // rejects.
          result = await Process.run(dartExecutablePath, [
            'build',
            'cli',
            '-t',
            p.join(cliRoot, 'bin', 'serverpod_cli.dart'),
            '-o',
            buildRoot,
          ], workingDirectory: cliRoot);
          if (result.exitCode != 0) {
            throw StateError(
              'dart build cli failed:\n${result.stdout}\n${result.stderr}',
            );
          }
        },
      );
    },
  );

  return exePath;
}