diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index d6328fdbbc..f30d1fec13 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -1,5 +1,7 @@ ## 2.8.1-wip +- Watch mode: handle `build.yaml` changes without restarting. +- Remove log output about `build_runner` internals. - Print the port that gets picked if you pass 0 for a port number, for example with `dart run build_runner serve web:0`. - Improved warnings when an option is specified for an unknown builder. diff --git a/build_runner/lib/src/bootstrap/bootstrap.dart b/build_runner/lib/src/bootstrap/bootstrap.dart index 016ea6b86e..c13e71f318 100644 --- a/build_runner/lib/src/bootstrap/bootstrap.dart +++ b/build_runner/lib/src/bootstrap/bootstrap.dart @@ -64,7 +64,6 @@ Future generateAndRun( if (kernelFile.existsSync()) { kernelFile.deleteSync(); } - buildLog.fullBuildBecause(FullBuildReason.incompatibleScript); } } on CannotBuildException { return ExitCode.config.code; @@ -109,8 +108,6 @@ Future generateAndRun( ); messagePort.sendPort.send(ExitCode.config.code); exitPort.sendPort.send(null); - } else { - buildLog.fullBuildBecause(FullBuildReason.incompatibleScript); } File(scriptKernelLocation).renameSync(scriptKernelCachedLocation); } @@ -157,10 +154,8 @@ Future _createKernelIfNeeded(Iterable experiments) async { // If we failed to serialize an asset graph for the snapshot, then we // don't want to re-use it because we can't check if it is up to date. kernelFile.renameSync(scriptKernelCachedLocation); - buildLog.fullBuildBecause(FullBuildReason.incompatibleAssetGraph); } else if (!await _checkImportantPackageDepsAndExperiments(experiments)) { kernelFile.renameSync(scriptKernelCachedLocation); - buildLog.fullBuildBecause(FullBuildReason.incompatibleScript); } } @@ -175,7 +170,6 @@ Future _createKernelIfNeeded(Iterable experiments) async { ); var hadErrors = false; - buildLog.doing('Compiling the build script.'); try { final result = await client.compile(); hadErrors = result.errorCount > 0 || !kernelCacheFile.existsSync(); diff --git a/build_runner/lib/src/bootstrap/build_process_state.dart b/build_runner/lib/src/bootstrap/build_process_state.dart index 1c5e5265c2..df10380dfb 100644 --- a/build_runner/lib/src/bootstrap/build_process_state.dart +++ b/build_runner/lib/src/bootstrap/build_process_state.dart @@ -33,14 +33,6 @@ class BuildProcessState { int get displayedLines => (_state['displayedLines'] as int?) ?? 0; set displayedLines(int? value) => _state['displayedLines'] = value; - /// For `buildLog`, the reason why a full build was needed. - FullBuildReason get fullBuildReason => FullBuildReason.values.singleWhere( - (v) => v.name == _state['fullBuildReason'], - orElse: () => FullBuildReason.clean, - ); - set fullBuildReason(FullBuildReason buildType) => - _state['fullBuildReason'] = buildType.name; - /// For `buildLog`, the elapsed time since the process started. int get elapsedMillis => _state['elapsedMillis'] as int? ?? 0; set elapsedMillis(int elapsedMillis) => @@ -104,20 +96,6 @@ class BuildProcessState { } } -/// Reason why `build_runner` will do a full build; or `none` for an -/// incremental build. -enum FullBuildReason { - clean('full build'), - incompatibleScript('full build because builders changed'), - incompatibleAssetGraph('full build because there is no valid asset graph'), - incompatibleBuild('full build because target changed'), - none('incremental build'); - - const FullBuildReason(this.message); - - final String message; -} - /// The `BuildLog` mode for the process. enum BuildLogMode { /// Line by line logging. diff --git a/build_runner/lib/src/bootstrap/build_script_generate.dart b/build_runner/lib/src/bootstrap/build_script_generate.dart index 151a76424a..cb30487eff 100644 --- a/build_runner/lib/src/bootstrap/build_script_generate.dart +++ b/build_runner/lib/src/bootstrap/build_script_generate.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:build_config/build_config.dart'; import 'package:code_builder/code_builder.dart'; @@ -27,8 +28,13 @@ const scriptKernelCachedSuffix = '.cached'; final _lastShortFormatDartVersion = Version(3, 6, 0); +Future hasGeneratedBuildScriptChanged() async { + final script = await generateBuildScript(); + final file = File(scriptLocation); + return !file.existsSync() || file.readAsStringSync() != script; +} + Future generateBuildScript() async { - buildLog.doing('Generating the build script.'); final builderFactories = await loadBuilderFactories(); final library = Library( (b) => b.body.addAll([ diff --git a/build_runner/lib/src/build/build.dart b/build_runner/lib/src/build/build.dart index 466da55f73..8f5b9f72c8 100644 --- a/build_runner/lib/src/build/build.dart +++ b/build_runner/lib/src/build/build.dart @@ -14,7 +14,6 @@ import 'package:glob/glob.dart'; import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; -import '../bootstrap/build_process_state.dart'; import '../build_plan/build_options.dart'; import '../build_plan/build_phases.dart'; import '../build_plan/build_plan.dart'; @@ -137,9 +136,6 @@ class Build { BuildPhases get buildPhases => buildPlan.buildPhases; Future run(Map updates) async { - if (!assetGraph.cleanBuild) { - buildLog.fullBuildBecause(FullBuildReason.none); - } buildLog.configuration = buildLog.configuration.rebuild( (b) => b..rootPackageName = packageGraph.root.name, ); @@ -234,14 +230,11 @@ class Build { final done = Completer(); runZonedGuarded( () async { - buildLog.doing('Updating the asset graph.'); if (!assetGraph.cleanBuild) { await _updateAssetGraph(updates); } - buildLog.startBuild(); final result = await _runPhases(); - buildLog.doing('Writing the asset graph.'); assetGraph.previousBuildTriggersDigest = targetGraph.buildTriggers.digest; @@ -269,7 +262,6 @@ class Build { // Log performance information if requested if (buildOptions.logPerformanceDir != null) { - buildLog.doing('Writing the performance log.'); assert(result.performance != null); final now = DateTime.now(); final logPath = p.join( @@ -365,7 +357,6 @@ class Build { } // Post build phase. - buildLog.doing('Running the post build.'); if (buildPhases.postBuildPhase.builderActions.isNotEmpty) { outputs.addAll( await performanceTracker.trackBuildPhase( diff --git a/build_runner/lib/src/build/build_result.dart b/build_runner/lib/src/build/build_result.dart index b6af35d10c..9d859abec0 100644 --- a/build_runner/lib/src/build/build_result.dart +++ b/build_runner/lib/src/build/build_result.dart @@ -78,7 +78,6 @@ enum BuildStatus { success, failure } class FailureType { static final general = FailureType._(1); static final cantCreate = FailureType._(73); - static final buildConfigChanged = FailureType._(75); static final buildScriptChanged = FailureType._(75); final int exitCode; FailureType._(this.exitCode); diff --git a/build_runner/lib/src/build/build_series.dart b/build_runner/lib/src/build/build_series.dart index 7dd5756ea6..dcf0513656 100644 --- a/build_runner/lib/src/build/build_series.dart +++ b/build_runner/lib/src/build/build_series.dart @@ -8,15 +8,16 @@ import 'package:build/build.dart'; import 'package:built_collection/built_collection.dart'; import 'package:watcher/watcher.dart'; +import '../bootstrap/build_script_generate.dart'; import '../build_plan/build_directory.dart'; import '../build_plan/build_filter.dart'; import '../build_plan/build_plan.dart'; import '../commands/watch/asset_change.dart'; import '../constants.dart'; import '../io/asset_tracker.dart'; -import '../io/build_output_reader.dart'; import '../io/filesystem_cache.dart'; import '../io/reader_writer.dart'; +import '../logging/build_log.dart'; import 'asset_graph/graph.dart'; import 'asset_graph/node.dart'; import 'build.dart'; @@ -35,11 +36,10 @@ import 'build_result.dart'; /// this serialized state is not actually used: the `AssetGraph` instance /// already in memory is used directly. class BuildSeries { - final BuildPlan _buildPlan; + BuildPlan _buildPlan; + AssetGraph _assetGraph; + ReaderWriter _readerWriter; - final AssetGraph _assetGraph; - - final ReaderWriter _readerWriter; final ResourceManager _resourceManager = ResourceManager(); /// For the first build only, updates from the previous serialized build @@ -105,19 +105,21 @@ class BuildSeries { return false; } - final node = - _assetGraph.contains(change.id) ? _assetGraph.get(change.id) : null; + final id = change.id; + if (_isBuildConfiguration(id)) return true; + + final node = _assetGraph.contains(id) ? _assetGraph.get(id) : null; // Changes to files that are not currently part of the build. if (node == null) { // Ignore under `.dart_tool/build`. - if (change.id.path.startsWith(cacheDir)) return false; + if (id.path.startsWith(cacheDir)) return false; // Ignore modifications and deletes. if (change.type != ChangeType.ADD) return false; // It's an add: return whether it's a new input. - return _buildPlan.targetGraph.anyMatchesAsset(change.id); + return _buildPlan.targetGraph.anyMatchesAsset(id); } // Changes to files that are part of the build. @@ -136,8 +138,8 @@ class BuildSeries { // For modifications, confirm that the content actually changed. if (change.type == ChangeType.MODIFY) { - _readerWriter.cache.invalidate([change.id]); - final newDigest = await _readerWriter.digest(change.id); + _readerWriter.cache.invalidate([id]); + final newDigest = await _readerWriter.digest(id); return node.digest != newDigest; } @@ -145,6 +147,12 @@ class BuildSeries { return true; } + bool _isBuildConfiguration(AssetId id) => + id.path == 'build.yaml' || + id.path.endsWith('.build.yaml') || + (id.package == _buildPlan.packageGraph.root.name && + id.path == 'build.${_buildPlan.buildOptions.configKey}.yaml'); + Future> checkForChanges() async { final updates = await AssetTracker( _buildPlan.readerWriter, @@ -178,14 +186,29 @@ class BuildSeries { BuiltSet? buildFilters, }) async { if (_hasBuildScriptChanged(updates.keys.toSet())) { - return BuildResult( - status: BuildStatus.failure, - failureType: FailureType.buildScriptChanged, - buildOutputReader: BuildOutputReader( - buildPlan: _buildPlan, - readerWriter: _readerWriter, - assetGraph: _assetGraph, - ), + return BuildResult.buildScriptChanged(); + } + + if (updates.keys.any(_isBuildConfiguration)) { + _buildPlan = await _buildPlan.reload(); + await _buildPlan.deleteFilesAndFolders(); + // A config change might have caused new builders to be needed, which + // needs a restart to change the build script. + if (_buildPlan.restartIsNeeded) { + return BuildResult.buildScriptChanged(); + } + // A config change might have changed builder factories, which needs a + // restart to change the build script. + if (await hasGeneratedBuildScriptChanged()) { + return BuildResult.buildScriptChanged(); + } + _assetGraph = _buildPlan.takeAssetGraph(); + _readerWriter = _buildPlan.readerWriter.copyWith( + generatedAssetHider: _assetGraph, + cache: + _buildPlan.buildOptions.enableLowResourcesMode + ? const PassthroughFilesystemCache() + : InMemoryFilesystemCache(), ); } @@ -202,6 +225,7 @@ class BuildSeries { } } + if (!firstBuild) buildLog.nextBuild(); final build = Build( buildPlan: _buildPlan.copyWith( buildDirs: buildDirs, diff --git a/build_runner/lib/src/build_plan/build_plan.dart b/build_runner/lib/src/build_plan/build_plan.dart index b6b00b5529..d6862f3f91 100644 --- a/build_runner/lib/src/build_plan/build_plan.dart +++ b/build_runner/lib/src/build_plan/build_plan.dart @@ -9,7 +9,6 @@ import 'package:build/experiments.dart'; import 'package:built_collection/built_collection.dart'; import 'package:watcher/watcher.dart'; -import '../bootstrap/build_process_state.dart'; import '../bootstrap/build_script_updates.dart'; import '../build/asset_graph/exceptions.dart'; import '../build/asset_graph/graph.dart'; @@ -125,7 +124,6 @@ class BuildPlan { var restartIsNeeded = false; if (builderApplications == null) { restartIsNeeded = true; - buildLog.fullBuildBecause(FullBuildReason.incompatibleScript); builderApplications = BuiltList(); } @@ -139,11 +137,8 @@ class BuildPlan { ); buildPhases.checkOutputLocations(packageGraph.root.name); if (buildPhases.inBuildPhases.isEmpty && - buildPhases.postBuildPhase.builderActions.isEmpty) { - buildLog.warning('Nothing to build.'); - } + buildPhases.postBuildPhase.builderActions.isEmpty) {} - buildLog.doing('Reading the asset graph.'); AssetGraph? previousAssetGraph; final filesToDelete = {}; final foldersToDelete = {}; @@ -158,9 +153,7 @@ class BuildPlan { previousAssetGraph = AssetGraph.deserialize( await readerWriter.readAsBytes(assetGraphId), ); - if (previousAssetGraph == null) { - buildLog.fullBuildBecause(FullBuildReason.incompatibleAssetGraph); - } else { + if (previousAssetGraph != null) { final buildPhasesChanged = buildPhases.digest != previousAssetGraph.buildPhasesDigest; final pkgVersionsChanged = @@ -175,7 +168,6 @@ class BuildPlan { previousAssetGraph.dartVersion, Platform.version, )) { - buildLog.fullBuildBecause(FullBuildReason.incompatibleBuild); // Mark old outputs for deletion. filesToDelete.addAll( previousAssetGraph.outputsToDelete(packageGraph), @@ -211,7 +203,6 @@ class BuildPlan { BuildScriptUpdates? buildScriptUpdates; Map? updates; if (previousAssetGraph != null) { - buildLog.doing('Checking for updates.'); updates = await assetTracker.computeSourceUpdates( inputSources, cacheDirSources, @@ -229,7 +220,6 @@ class BuildPlan { !buildOptions.skipBuildScriptCheck && buildScriptUpdates.hasBeenUpdated(updates.keys.toSet()); if (buildScriptUpdated) { - buildLog.fullBuildBecause(FullBuildReason.incompatibleScript); // Mark old outputs for deletion. filesToDelete.addAll(previousAssetGraph.outputsToDelete(packageGraph)); foldersToDelete.add(generatedOutputDirectoryId); @@ -255,8 +245,6 @@ class BuildPlan { } if (assetGraph == null) { - buildLog.doing('Creating the asset graph.'); - // Files marked for deletion are not inputs. inputSources.removeAll(filesToDelete); @@ -369,8 +357,6 @@ class BuildPlan { } Future deleteFilesAndFolders() async { - buildLog.doing('Doing initial build cleanup.'); - // Hidden outputs are deleted if needed by deleting the entire folder. So, // only outputs in the source folder need to be deleted explicitly. Use a // `ReaderWriter` that only acts on the source folder. @@ -386,6 +372,19 @@ class BuildPlan { await cleanupReaderWriter.deleteDirectory(id); } } + + /// Reloads the build plan. + /// + /// Works just like a new load of the build plan, but supresses the usual log + /// output. + /// + /// The caller must call [deleteFilesAndFolders] on the result and check + /// [restartIsNeeded]. + Future reload() => BuildPlan.load( + builderFactories: builderFactories, + buildOptions: buildOptions, + testingOverrides: testingOverrides, + ); } bool isSameSdkVersion(String? thisVersion, String? thatVersion) => diff --git a/build_runner/lib/src/commands/clean_command.dart b/build_runner/lib/src/commands/clean_command.dart index 82c1180da4..0325c1b5c1 100644 --- a/build_runner/lib/src/commands/clean_command.dart +++ b/build_runner/lib/src/commands/clean_command.dart @@ -7,13 +7,11 @@ import 'dart:io'; import 'package:io/io.dart'; import '../constants.dart'; -import '../logging/build_log.dart'; import 'build_runner_command.dart'; class CleanCommand implements BuildRunnerCommand { @override Future run() async { - buildLog.doing('Deleting the build cache.'); final generatedDir = Directory(cacheDir); if (generatedDir.existsSync()) { generatedDir.deleteSync(recursive: true); diff --git a/build_runner/lib/src/commands/watch/watcher.dart b/build_runner/lib/src/commands/watch/watcher.dart index 39dc46661c..b0c2e217f9 100644 --- a/build_runner/lib/src/commands/watch/watcher.dart +++ b/build_runner/lib/src/commands/watch/watcher.dart @@ -14,7 +14,6 @@ import '../../build_plan/build_options.dart'; import '../../build_plan/build_plan.dart'; import '../../build_plan/package_graph.dart'; import '../../build_plan/testing_overrides.dart'; -import '../../io/build_output_reader.dart'; import '../../io/reader_writer.dart'; import '../../logging/build_log.dart'; import 'asset_change.dart'; @@ -62,7 +61,6 @@ class Watcher { final controller = StreamController(); Future doBuild(List> changes) async { - buildLog.nextBuild(); final buildSeries = _buildSeries!; final mergedChanges = collectChanges(changes); @@ -70,9 +68,7 @@ class Watcher { return buildSeries.run(mergedChanges); } - final terminate = Future.any([until, _terminateCompleter.future]).then((_) { - buildLog.info('Terminating. No further builds will be scheduled.'); - }); + final terminate = Future.any([until, _terminateCompleter.future]); Digest? originalRootPackageConfigDigest; final rootPackageConfigId = AssetId( @@ -107,28 +103,9 @@ class Watcher { return _readOnceExists(id, readerWriter).then((bytes) { if (md5.convert(bytes) != digest) { _terminateCompleter.complete(); - buildLog.error( - 'Terminating builds due to package graph update.', - ); } return change; }); - } else if (_isBuildYaml(id) || - _isConfiguredBuildYaml(id) || - _isPackageBuildYamlOverride(id)) { - controller.add( - BuildResult( - status: BuildStatus.failure, - failureType: FailureType.buildConfigChanged, - buildOutputReader: BuildOutputReader.empty(), - ), - ); - - // Kill future builds if the build.yaml files change. - _terminateCompleter.complete(); - buildLog.error( - 'Terminating builds due to ${id.package}:${id.path} update.', - ); } return change; }) @@ -143,7 +120,6 @@ class Watcher { .listen((BuildResult result) { if (result.failureType == FailureType.buildScriptChanged) { _terminateCompleter.complete(); - buildLog.error('Terminating builds due to build script update.'); } if (controller.isClosed) return; controller.add(result); @@ -152,13 +128,11 @@ class Watcher { await currentBuildResult; await _buildSeries?.beforeExit(); if (!controller.isClosed) await controller.close(); - buildLog.info('Builds finished. Safe to exit\n'); }); // Schedule the actual first build for the future so we can return the // stream synchronously. () async { - buildLog.doing('Waiting for file watchers to be ready.'); await graphWatcher.ready; if (await readerWriter.canRead(rootPackageConfigId)) { originalRootPackageConfigDigest = md5.convert( @@ -183,15 +157,6 @@ class Watcher { return controller.stream; } - - bool _isBuildYaml(AssetId id) => id.path == 'build.yaml'; - bool _isConfiguredBuildYaml(AssetId id) => - id.package == packageGraph.root.name && - id.path == 'build.${buildOptions.configKey}.yaml'; - bool _isPackageBuildYamlOverride(AssetId id) => - id.package == packageGraph.root.name && - id.path.contains(_packageBuildYamlRegexp); - final _packageBuildYamlRegexp = RegExp(r'^[a-z0-9_]+\.build\.yaml$'); } /// Reads [id] using [readerWriter], waiting for it to exist for up to 1 second. diff --git a/build_runner/lib/src/commands/watch_command.dart b/build_runner/lib/src/commands/watch_command.dart index f08bcc331b..82ac68e4ef 100644 --- a/build_runner/lib/src/commands/watch_command.dart +++ b/build_runner/lib/src/commands/watch_command.dart @@ -84,8 +84,7 @@ void handleBuildResultsStream( final subscription = buildResults.listen((result) { if (completer.isCompleted) return; if (result.status == BuildStatus.failure) { - if (result.failureType == FailureType.buildScriptChanged || - result.failureType == FailureType.buildConfigChanged) { + if (result.failureType == FailureType.buildScriptChanged) { completer.complete(ExitCode.tempFail.code); } } diff --git a/build_runner/lib/src/io/create_merged_dir.dart b/build_runner/lib/src/io/create_merged_dir.dart index 12334bd301..bf308ca354 100644 --- a/build_runner/lib/src/io/create_merged_dir.dart +++ b/build_runner/lib/src/io/create_merged_dir.dart @@ -35,8 +35,6 @@ Future createMergedOutputDirectories({ required BuildOutputReader buildOutputReader, required ReaderWriter readerWriter, }) async { - buildLog.doing('Writing the output directory.'); - return await TimedActivity.write.runAsync(() async { if (outputSymlinksOnly && readerWriter.filesystem is! IoFilesystem) { buildLog.error( diff --git a/build_runner/lib/src/logging/build_log.dart b/build_runner/lib/src/logging/build_log.dart index 6a723bd10d..72a6bcaf38 100644 --- a/build_runner/lib/src/logging/build_log.dart +++ b/build_runner/lib/src/logging/build_log.dart @@ -145,19 +145,6 @@ class BuildLog { BuildLogLogger loggerForOther(String context) => BuildLogLogger(context: context); - /// Logs why `build_runner` needs to do a full build. - /// - /// The reason will be displayed when [startBuild] is called. - void fullBuildBecause(FullBuildReason reason) { - if (buildProcessState.fullBuildReason == FullBuildReason.clean) { - buildProcessState.fullBuildReason = reason; - } - _tick(); - if (_display.displayingBlocks) { - _display.block(render()); - } - } - /// Logs a `build_runner` info. void info(String message) { if (_display.displayingBlocks) { @@ -338,19 +325,6 @@ class BuildLog { _popPhase(); } - /// Describe what `build_runner` is doing. - /// - /// Logs the task, or in console mode updates the status line. - void doing(String task) { - if (_display.displayingBlocks) { - _status = [task]; - _tick(); - _display.block(render()); - } else { - _display.message(Severity.info, task); - } - } - /// For `watch` and `serve` modes, logs that a new build (not the initial /// build) has started. /// @@ -360,14 +334,10 @@ class BuildLog { _processDuration = Duration.zero; activities.clear(); _messages.clear(); + _status.clear(); _display.flushAndPrint('\nStarting build #${++_buildNumber}.\n'); } - /// Logs that the build has started. - void startBuild() { - doing('Building, ${buildProcessState.fullBuildReason.message}.'); - } - /// Logs that the build has finished with [result] and the count of [outputs]. void finishBuild({required bool result, required int outputs}) { _tick(); @@ -466,26 +436,26 @@ class BuildLog { result.write(_renderPhase(phaseName).withHangingIndent(indent)); } - result.writeEmptyLine(); } final renderedMessages = _messages.render(); // Log output blocks with no errors show before the status line. if (renderedMessages.nonFailureLines.isNotEmpty) { + if (result.lines.isNotEmpty) result.writeEmptyLine(); for (final line in renderedMessages.nonFailureLines) { result.write(line); } - result.writeEmptyLine(); } if (_status.isNotEmpty) { + if (result.lines.isNotEmpty) result.writeEmptyLine(); result.writeLine(_status); } // Log output blocks that do have errors show after the status line. if (renderedMessages.failureLines.isNotEmpty) { - if (_status.isNotEmpty) result.writeEmptyLine(); + if (result.lines.isNotEmpty) result.writeEmptyLine(); for (final line in renderedMessages.failureLines) { result.write(line); } diff --git a/build_runner/test/common/build_runner_tester.dart b/build_runner/test/common/build_runner_tester.dart index 7ce3bfc5d5..028e7deb69 100644 --- a/build_runner/test/common/build_runner_tester.dart +++ b/build_runner/test/common/build_runner_tester.dart @@ -203,13 +203,22 @@ class BuildRunnerProcess { /// Expects nothing new on stdout or stderr for [duration]. Future expectNoOutput(Duration duration) async { printOnFailure('--- $_testLine expects no output'); - try { - final line = await _outputs.next.timeout(duration); - fail('While expecting no output, got `$line`.'); - } on TimeoutException catch (_) { - // Expected. - } catch (_) { - fail('While expecting no output, process exited.'); + final output = []; + final stopwatch = Stopwatch()..start(); + while (stopwatch.elapsed < duration) { + try { + output.add(await _outputs.next.timeout(duration - stopwatch.elapsed)); + } on TimeoutException catch (_) { + // Expected. + } catch (_) { + fail('While expecting no output, process exited.'); + } + } + + if (output.isNotEmpty) { + fail( + 'While expecting no output, got:\n\n===\n${output.join('\n')}\n===\n', + ); } } @@ -266,6 +275,8 @@ class BuildRunnerProcess { await process.exitCode; } + Future get exitCode => process.exitCode; + // Expects the server to log that it is serving, records the port. Future expectServing() async { final regexp = RegExp('Serving `web` on http://localhost:([0-9]+)'); diff --git a/build_runner/test/integration_tests/build_command_invalidation_test.dart b/build_runner/test/integration_tests/build_command_invalidation_test.dart index 4707e8dfeb..6098c4860a 100644 --- a/build_runner/test/integration_tests/build_command_invalidation_test.dart +++ b/build_runner/test/integration_tests/build_command_invalidation_test.dart @@ -40,7 +40,6 @@ void main() async { tester.update('builder_pkg/lib/builder.dart', (script) => '$script\n'); tester.write(fakeGeneratedOutput, ''); output = await tester.run('root_pkg', 'dart run build_runner build'); - expect(output, contains('Building, full build because builders changed.')); expect(output, contains('wrote 1 output')); expect(tester.read(fakeGeneratedOutput), null); @@ -63,10 +62,7 @@ void main() async { ); tester.write(fakeGeneratedOutput, ''); output = await tester.run('root_pkg', 'dart run build_runner build'); - expect( - output, - contains('Building, full build because there is no valid asset graph.'), - ); + expect(output, contains('wrote 1 output')); expect(tester.read(fakeGeneratedOutput), null); // "Core packages" location changed. @@ -75,6 +71,6 @@ void main() async { (txt) => '$txt\n', ); output = await tester.run('root_pkg', 'dart run build_runner build'); - expect(output, contains('Building, full build because builders changed.')); + expect(output, contains('wrote 0 outputs')); }); } diff --git a/build_runner/test/integration_tests/watch_command_test.dart b/build_runner/test/integration_tests/watch_command_test.dart index 6a0e47d285..6e490c780c 100644 --- a/build_runner/test/integration_tests/watch_command_test.dart +++ b/build_runner/test/integration_tests/watch_command_test.dart @@ -6,12 +6,13 @@ library; import 'package:build_runner/src/logging/build_log.dart'; +import 'package:io/io.dart'; import 'package:test/test.dart'; import '../common/common.dart'; void main() async { - test('watch command invalidation', () async { + test('watch command', () async { final pubspecs = await Pubspecs.load(); final tester = BuildRunnerTester(pubspecs); @@ -63,67 +64,77 @@ void main() async { // Deleted output. tester.delete('root_pkg/web/a.txt.copy'); - await watch.expect(BuildLog.successPattern); + await watch.expect('wrote 1 output'); expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); - // Builder change. - tester.update('builder_pkg/lib/builder.dart', (script) => '$script\n'); - await watch.expect('Terminating builds due to build script update'); - await watch.expect('Compiling the build script'); - await watch.expect('Creating the asset graph'); + // File change during build. + tester.write('root_pkg/web/a.txt', 'a'); + await watch.expect('builder_pkg:test_builder'); + tester.write('root_pkg/web/a.txt', 'updated'); + await watch.expect(BuildLog.successPattern); + expect(tester.read('root_pkg/web/a.txt.copy'), 'a'); await watch.expect(BuildLog.successPattern); expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); + // Builder change to one that writes output from config key `output` if + // present or writes `hardcoded` if not. There is a second factory that + // ignores the config and writes `second_factory`. + tester.write('builder_pkg/lib/builder.dart', ''' +import 'package:build/build.dart'; +Builder testBuilderFactory(BuilderOptions options) + => TestBuilder(options.config['output'] ?? 'hardcoded'); +Builder testBuilderFactory2(BuilderOptions options) + => TestBuilder('second_factory'); +class TestBuilder implements Builder { + final String output; + TestBuilder(this.output); + Map> get buildExtensions => {'.txt': ['.txt.copy']}; + Future build(BuildStep buildStep) async { + buildStep.writeAsString(buildStep.inputId.addExtension('.copy'), output); + } +} +'''); + await watch.expect(BuildLog.successPattern); + expect(tester.read('root_pkg/web/a.txt.copy'), 'hardcoded'); + // State on disk is updated so `build` knows to do nothing. output = await tester.run('root_pkg', 'dart run build_runner build'); expect(output, contains('wrote 0 outputs')); - // Builder config change, add a file. + // Builder config change, add a file but it has no effect. tester.write('root_pkg/build.yaml', '# new file, nothing here'); - await watch.expect('Terminating builds due to root_pkg:build.yaml update'); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); - - // Builder config change, update a file. - tester.update('root_pkg/build.yaml', (yaml) => '$yaml\n'); - await watch.expect('Terminating builds due to root_pkg:build.yaml update'); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); - - // Builder config change in dependency. + await watch.expect('wrote 0 outputs'); + + // Builder config change, update a file to change options. + tester.write('root_pkg/build.yaml', r''' +targets: + $default: + builders: + builder_pkg:test_builder: + options: + output: "configured" +'''); + await watch.expect('wrote 1 output'); + expect(tester.read('root_pkg/web/a.txt.copy'), 'configured'); + + // Builder config change in dependency but it has no effect. tester.write('other_pkg/build.yaml', '# new file, nothing here'); - await watch.expect('Terminating builds due to other_pkg:build.yaml update'); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); + await watch.expect('wrote 0 outputs'); - // Builder config change in root overriding dependency. + // Builder config change in root overriding dependency but it has no effect. tester.write('root_pkg/other_pkg.build.yaml', '# new file, nothing here'); - await watch.expect( - 'Terminating builds due to root_pkg:other_pkg.build.yaml update', - ); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); + await watch.expect('wrote 0 outputs'); // State on disk is updated so `build` knows to do nothing. output = await tester.run('root_pkg', 'dart run build_runner build'); expect(output, contains('wrote 0 outputs')); - // File change during build. - tester.write('root_pkg/web/a.txt', 'a'); - await watch.expect('Building'); - tester.write('root_pkg/web/a.txt', 'updated'); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'a'); - await watch.expect(BuildLog.successPattern); - expect(tester.read('root_pkg/web/a.txt.copy'), 'updated'); - // Change to `package_config.json` causes the watcher to exit. tester.update( 'root_pkg/.dart_tool/package_config.json', (script) => '$script\n', ); - await watch.expect('Terminating builds due to package graph update.'); - await watch.kill(); + expect(await watch.exitCode, ExitCode.success.code); // Now with --output. watch = await tester.start( @@ -132,12 +143,41 @@ void main() async { ); await watch.expect(BuildLog.successPattern); expect(tester.read('root_pkg/build/a.txt'), 'updated'); - expect(tester.read('root_pkg/build/a.txt.copy'), 'updated'); + expect(tester.read('root_pkg/build/a.txt.copy'), 'configured'); // Changed inputs and outputs are written to output directory. tester.write('root_pkg/web/a.txt', 'a'); await watch.expect(BuildLog.successPattern); expect(tester.read('root_pkg/build/a.txt'), 'a'); - expect(tester.read('root_pkg/build/a.txt.copy'), 'a'); + expect(tester.read('root_pkg/build/a.txt.copy'), 'configured'); + + // Builder config change in dependency that changes the build script by + // changing the factory to `testBuilderFactory2`. + tester.write('builder_pkg/build.yaml', ''' +builders: + test_builder: + import: 'package:builder_pkg/builder.dart' + builder_factories: ['testBuilderFactory2'] + build_extensions: {'.txt': ['.txt.copy']} + auto_apply: root_package + build_to: source +'''); + await watch.expect(BuildLog.successPattern); + expect(tester.read('root_pkg/build/a.txt.copy'), 'second_factory'); + + // Builder config change in dependency that changes the build script to + // something broken. + tester.update( + 'builder_pkg/build.yaml', + (yaml) => ''' +$yaml + missing_builder: + import: 'package:builder_pkg/builder.dart' + builder_factories: ['missingBuilderFactory'] + build_extensions: {'.txt': ['']} +''', + ); + await watch.expect('Failed to compile build script.'); + expect(await watch.exitCode, ExitCode.config.code); }); } diff --git a/build_runner/test/logging/build_log_console_mode_test.dart b/build_runner/test/logging/build_log_console_mode_test.dart index b95d938497..f790731c5e 100644 --- a/build_runner/test/logging/build_log_console_mode_test.dart +++ b/build_runner/test/logging/build_log_console_mode_test.dart @@ -28,31 +28,7 @@ void main() { expect(render(), []); }); - test('simple status', () { - buildLog.doing('Generating build script.'); - expect( - render(), - padLinesRight(''' -Generating build script.'''), - ); - }); - - test('simple status wraps', () { - buildLog.doing( - 'This is a long message that will need to wrap for ' - 'display so that it fits in 80 columns because it will ' - 'not fit.', - ); - expect( - render(), - padLinesRight(''' -This is a long message that will need to wrap for display so that it fits in 80 -columns because it will not fit.'''), - ); - }); - test('build_runner info, warnings and errors', () { - buildLog.doing('Some setup.'); buildLog.info('Some info.'); buildLog.warning('A warning.'); buildLog.warning('Another warning.'); @@ -60,8 +36,6 @@ columns because it will not fit.'''), expect( render(), padLinesRight(''' -Some setup. - build_runner Some info. W A warning. @@ -71,7 +45,6 @@ E An error.'''), }); test('phase progress', () { - buildLog.startBuild(); final phases = _createPhases({'builder1': 10, 'builder2': 15}); buildLog.startPhases(phases); buildLog.startStep( @@ -83,9 +56,7 @@ E An error.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs; pkg|lib/l0.dart -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.finishStep( @@ -98,9 +69,7 @@ Building, full build.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs: 1 output -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.startStep( @@ -112,9 +81,7 @@ Building, full build.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs: 1 output; pkg|lib/l1.dart -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.finishStep( @@ -127,9 +94,7 @@ Building, full build.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs: 1 output, 1 same -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.startStep( @@ -147,9 +112,7 @@ Building, full build.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs: 1 output, 1 same, 1 no-op -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.startStep( @@ -162,9 +125,7 @@ Building, full build.'''), render(), padLinesRight(''' 0s builder1 on 10 inputs: 1 skipped, 1 output, 1 same, 1 no-op -0s builder2 on 15 inputs - -Building, full build.'''), +0s builder2 on 15 inputs'''), ); buildLog.startStep( @@ -183,14 +144,11 @@ Building, full build.'''), padLinesRight(''' 0s builder1 on 10 inputs: 1 skipped, 1 output, 1 same, 1 no-op 0s builder2 on 15 inputs -0s builder1 (lazy): 1 output - -Building, full build.'''), +0s builder1 (lazy): 1 output'''), ); }); test('phase progress with builder log output', () { - buildLog.startBuild(); final phases = _createPhases({'builder1': 10, 'builder2': 15}); buildLog.startPhases(phases); buildLog.startStep( @@ -242,8 +200,6 @@ lib/l0.dart builder1 Some info. Some more info. -Building, full build. - lib/l1.dart builder2 W A warning. E An error. @@ -280,8 +236,6 @@ lib/l0.dart builder1 Some info. Some more info. -Building, full build. - lib/l1.dart builder2 W A warning. E An error. @@ -293,8 +247,6 @@ E An error.'''), }); test('complete build with builder log output', () { - buildLog.fullBuildBecause(FullBuildReason.none); - buildLog.startBuild(); final phases = _createPhases({'builder1': 1, 'builder2': 1}); buildLog.startPhases(phases); buildLog.startStep( diff --git a/build_runner/test/logging/build_log_line_mode_test.dart b/build_runner/test/logging/build_log_line_mode_test.dart index a9af0c6264..060a6e9e19 100644 --- a/build_runner/test/logging/build_log_line_mode_test.dart +++ b/build_runner/test/logging/build_log_line_mode_test.dart @@ -24,19 +24,12 @@ void main() { }); }); - test('simple status', () { - buildLog.doing('Generating build script.'); - expect(lines, [' Generating build script.']); - }); - test('build_runner info, warnings and errors', () { - buildLog.doing('Some setup.'); buildLog.info('Some info.'); buildLog.warning('A warning.'); buildLog.warning('Another warning.'); buildLog.error('An error.'); expect(lines, [ - ' Some setup.', ' Some info.', 'W A warning.', 'W Another warning.', @@ -45,7 +38,6 @@ void main() { }); test('phase progress', () { - buildLog.startBuild(); final phases = _createPhases({'builder1': 10, 'builder2': 15}); buildLog.startPhases(phases); buildLog.startStep( @@ -53,10 +45,7 @@ void main() { primaryInput: AssetId('pkg', 'lib/l0.dart'), lazy: false, ); - expect(lines, [ - ' Building, full build.', - ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', - ]); + expect(lines, [' 0s builder1 on 10 inputs; pkg|lib/l0.dart']); buildLog.finishStep( phase: phases.keys.first, @@ -70,7 +59,6 @@ void main() { lazy: false, ); expect(lines, [ - ' Building, full build.', ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', ' 0s builder1 on 10 inputs: 1 output; pkg|lib/l1.dart', ]); @@ -93,7 +81,6 @@ void main() { lazy: false, ); expect(lines, [ - ' Building, full build.', ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', ' 0s builder1 on 10 inputs: 1 output; pkg|lib/l1.dart', ' 0s builder1 on 10 inputs: 1 output, 1 same; pkg|lib/l2.dart', @@ -117,7 +104,6 @@ void main() { anyChangedOutputs: true, ); expect(lines, [ - ' Building, full build.', ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', ' 0s builder1 on 10 inputs: 1 output; pkg|lib/l1.dart', ' 0s builder1 on 10 inputs: 1 output, 1 same; pkg|lib/l2.dart', @@ -131,7 +117,6 @@ void main() { b.mode = BuildLogMode.build; }); - buildLog.startBuild(); final phases = _createPhases({'builder1': 10, 'builder2': 15}); buildLog.startPhases(phases); buildLog.startStep( @@ -174,7 +159,6 @@ void main() { ); expect(lines, [ - ' Building, full build.', ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', ' builder1 on lib/l0.dart:\n' 'Some info.', @@ -207,7 +191,6 @@ void main() { ); expect(lines, [ - ' Building, full build.', ' 0s builder1 on 10 inputs; pkg|lib/l0.dart', ' builder1 on lib/l0.dart:\n' 'Some info.', @@ -229,8 +212,6 @@ void main() { buildLog.configuration = buildLog.configuration.rebuild((b) { b.mode = BuildLogMode.build; }); - buildLog.fullBuildBecause(FullBuildReason.none); - buildLog.startBuild(); final phases = _createPhases({'builder1': 1, 'builder2': 1}); buildLog.startPhases(phases); buildLog.startStep( @@ -276,7 +257,6 @@ void main() { buildLog.finishBuild(result: true, outputs: 2); expect(lines, [ - ' Building, incremental build.', ' 0s builder1 on 1 input; pkg|lib/l0.dart', ' builder1 on lib/l0.dart:\n' 'Some info.',