Stream _run()

in build_runner/lib/src/generate/watch_impl.dart [220:377]


  Stream<BuildResult> _run(
      BuildOptions options,
      BuildEnvironment environment,
      List<BuilderApplication> builders,
      Map<String, Map<String, dynamic>> builderConfigOverrides,
      Future until,
      {bool isReleaseMode = false}) {
    var watcherEnvironment = OverrideableEnvironment(environment,
        writer: OnDeleteWriter(environment.writer, _expectedDeletes.add));
    var firstBuildCompleter = Completer<BuildResult>();
    currentBuild = firstBuildCompleter.future;
    var controller = StreamController<BuildResult>();

    Future<BuildResult> doBuild(List<List<AssetChange>> changes) async {
      var build = _build!;
      _logger
        ..info('${'-' * 72}\n')
        ..info('Starting Build\n');
      var mergedChanges = collectChanges(changes);

      _expectedDeletes.clear();
      if (!options.skipBuildScriptCheck) {
        if (build.buildScriptUpdates!
            .hasBeenUpdated(mergedChanges.keys.toSet())) {
          _terminateCompleter.complete();
          _logger.severe('Terminating builds due to build script update');
          return BuildResult(BuildStatus.failure, [],
              failureType: FailureType.buildScriptChanged);
        }
      }
      return build.run(mergedChanges,
          buildDirs: _buildDirs, buildFilters: _buildFilters);
    }

    var terminate = Future.any([until, _terminateCompleter.future]).then((_) {
      _logger.info('Terminating. No further builds will be scheduled\n');
    });

    Digest? originalRootPackagesDigest;
    Digest? originalRootPackageConfigDigest;
    final rootPackagesId = AssetId(packageGraph.root.name, '.packages');
    final rootPackageConfigId =
        AssetId(packageGraph.root.name, '.dart_tool/package_config.json');

    // Start watching files immediately, before the first build is even started.
    var graphWatcher = PackageGraphWatcher(packageGraph,
        logger: _logger,
        watch: (node) =>
            PackageNodeWatcher(node, watch: _directoryWatcherFactory));
    graphWatcher
        .watch()
        .asyncMap<AssetChange>((change) {
          // Delay any events until the first build is completed.
          if (firstBuildCompleter.isCompleted) return change;
          return firstBuildCompleter.future.then((_) => change);
        })
        .asyncMap<AssetChange>((change) {
          var id = change.id;
          if (id == rootPackagesId || id == rootPackageConfigId) {
            var digest = id == rootPackagesId
                ? originalRootPackagesDigest
                : originalRootPackageConfigDigest;
            assert(digest != null);
            // Kill future builds if the root packages file changes.
            //
            // We retry the reads for a little bit to handle the case where a
            // user runs `pub get` and it hasn't been re-written yet.
            return _readOnceExists(id, watcherEnvironment.reader).then((bytes) {
              if (md5.convert(bytes) != digest) {
                _terminateCompleter.complete();
                _logger
                    .severe('Terminating builds due to package graph update, '
                        'please restart the build.');
              }
              return change;
            });
          } else if (_isBuildYaml(id) ||
              _isConfiguredBuildYaml(id) ||
              _isPackageBuildYamlOverride(id)) {
            controller.add(BuildResult(BuildStatus.failure, [],
                failureType: FailureType.buildConfigChanged));

            // Kill future builds if the build.yaml files change.
            _terminateCompleter.complete();
            _logger.severe(
                'Terminating builds due to ${id.package}:${id.path} update.');
          }
          return change;
        })
        .asyncWhere((change) {
          assert(_readerCompleter.isCompleted);
          return shouldProcess(
            change,
            assetGraph!,
            options,
            _willCreateOutputDirs,
            _expectedDeletes,
            watcherEnvironment.reader,
          );
        })
        .debounceBuffer(_debounceDelay)
        .takeUntil(terminate)
        .asyncMapBuffer((changes) => currentBuild = doBuild(changes)
          ..whenComplete(() => currentBuild = null))
        .listen((BuildResult result) {
          if (controller.isClosed) return;
          controller.add(result);
        })
        .onDone(() async {
          await currentBuild;
          await _build?.beforeExit();
          if (!controller.isClosed) await controller.close();
          _logger.info('Builds finished. Safe to exit\n');
        });

    // Schedule the actual first build for the future so we can return the
    // stream synchronously.
    () async {
      await logTimedAsync(_logger, 'Waiting for all file watchers to be ready',
          () => graphWatcher.ready);
      originalRootPackagesDigest = md5
          .convert(await watcherEnvironment.reader.readAsBytes(rootPackagesId));
      originalRootPackageConfigDigest = md5.convert(
          await watcherEnvironment.reader.readAsBytes(rootPackageConfigId));

      BuildResult firstBuild;
      BuildImpl? build;
      try {
        build = _build = await BuildImpl.create(
            options, watcherEnvironment, builders, builderConfigOverrides,
            isReleaseBuild: isReleaseMode);

        firstBuild = await build
            .run({}, buildDirs: _buildDirs, buildFilters: _buildFilters);
      } on CannotBuildException catch (e, s) {
        _terminateCompleter.complete();

        firstBuild = BuildResult(BuildStatus.failure, []);
        _readerCompleter.completeError(e, s);
      } on BuildScriptChangedException catch (e, s) {
        _terminateCompleter.complete();

        firstBuild = BuildResult(BuildStatus.failure, [],
            failureType: FailureType.buildScriptChanged);
        _readerCompleter.completeError(e, s);
      }
      if (build != null) {
        assert(!_readerCompleter.isCompleted);
        _readerCompleter.complete(build.finalizedReader);
      }
      // It is possible this is already closed if the user kills the process
      // early, which results in an exception without this check.
      if (!controller.isClosed) controller.add(firstBuild);
      firstBuildCompleter.complete(firstBuild);
    }();

    return controller.stream;
  }