Skip to content

New bootstrap code #4114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion build_runner/bin/src/commands/generate_build_script.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ class GenerateBuildScript extends Command<int> {
var buildScript = await generateBuildScript();
File(scriptLocation)
..createSync(recursive: true)
..writeAsStringSync(buildScript);
..writeAsStringSync(buildScript.script);
File(scriptDepsPath)
..createSync(recursive: true)
..writeAsStringSync(
'$scriptLocation: ${buildScript.dependencyPaths.join(' ')}',
);
print(p.absolute(scriptLocation));
return 0;
}
Expand Down
35 changes: 20 additions & 15 deletions build_runner/lib/src/build_script_generate/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,29 @@ import 'build_script_generate.dart';
/// Returns the exit code from running the build script.
///
/// If an exit code of 75 is returned, this function should be re-ran.
///
/// Pass [script] to override the default build script for testing.
Future<int> generateAndRun(
List<String> args, {
List<String>? experiments,
Logger? logger,
Future<String> Function() generateBuildScript = generateBuildScript,
void Function(Object error, StackTrace stackTrace) handleUncaughtError =
_defaultHandleUncaughtError,
GeneratedScript? script,
}) {
return buildLog.runWithLoggerDisplay(
logger,
() => _generateAndRun(
args,
experiments,
generateBuildScript,
handleUncaughtError,
),
() =>
_generateAndRun(args, experiments, handleUncaughtError, script: script),
);
}

Future<int> _generateAndRun(
List<String> args,
List<String>? experiments,
Future<String> Function() generateBuildScript,
void Function(Object error, StackTrace stackTrace) handleUncaughtError,
) async {
void Function(Object error, StackTrace stackTrace) handleUncaughtError, {
GeneratedScript? script,
}) async {
experiments ??= [];
ReceivePort? exitPort;
ReceivePort? errorPort;
Expand All @@ -72,12 +70,17 @@ Future<int> _generateAndRun(
if (buildScript.existsSync()) {
oldContents = buildScript.readAsStringSync();
}
var newContents = await generateBuildScript();
var newContents = script ?? await generateBuildScript();
// Only trigger a build script update if necessary.
if (newContents != oldContents) {
if (newContents.script != oldContents) {
buildScript
..createSync(recursive: true)
..writeAsStringSync(newContents);
..writeAsStringSync(newContents.script);
File(scriptDepsPath)
..createSync(recursive: true)
..writeAsStringSync(
'$scriptLocation: ${newContents.dependencyPaths.join(' ')}',
);
// Delete the kernel file so it will be rebuilt.
final kernelFile = File(scriptKernelLocation);
if (kernelFile.existsSync()) {
Expand Down Expand Up @@ -183,7 +186,7 @@ Future<bool> _createKernelIfNeeded(List<String> experiments) async {
}
}

if (!kernelFile.existsSync()) {
if (!kernelFile.existsSync() || true) {
final client = await FrontendServerClient.start(
scriptLocation,
scriptKernelCachedLocation,
Expand All @@ -196,9 +199,11 @@ Future<bool> _createKernelIfNeeded(List<String> experiments) async {
var hadErrors = false;
buildLog.doing('Compiling the build script.');
try {
if (kernelCacheFile.existsSync()) kernelCacheFile.deleteSync();
final result = await client.compile();
buildLog.debug(result.jsSourcesOutput.toString());
buildLog.debug('built ${kernelCacheFile.path}');
hadErrors = result.errorCount > 0 || !kernelCacheFile.existsSync();

// Note: We're logging all output with a single log call to keep
// annotated source spans intact.
final logOutput = result.compilerOutputLines.join('\n');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import '../package_graph/build_config_overrides.dart';
import 'builder_ordering.dart';

const scriptLocation = '$entryPointDir/build.dart';
const scriptDepsPath = '$entryPointDir/build.dart.deps';
const scriptKernelLocation = '$scriptLocation$scriptKernelSuffix';
const scriptKernelSuffix = '.dill';
const scriptKernelCachedLocation =
Expand All @@ -25,7 +26,7 @@ const scriptKernelCachedSuffix = '.cached';

final _lastShortFormatDartVersion = Version(3, 6, 0);

Future<String> generateBuildScript() async {
Future<GeneratedScript> generateBuildScript() async {
buildLog.doing('Generating the build script.');
final info = await findBuildScriptOptions();
final builders = info.builderApplications;
Expand Down Expand Up @@ -55,14 +56,15 @@ Future<String> generateBuildScript() async {
// the host<->isolate relationship changed in a breaking way, for example
// if command line args or messages passed via sendports have changed
// in a breaking way.
return DartFormatter(languageVersion: _lastShortFormatDartVersion).format(
'''
final script = DartFormatter(
languageVersion: _lastShortFormatDartVersion,
).format('''
// @dart=${_lastShortFormatDartVersion.major}.${_lastShortFormatDartVersion.minor}
// ignore_for_file: directives_ordering
// build_runner >=2.4.16
${library.accept(emitter)}
''',
);
''');
return GeneratedScript(script: script, dependencyPaths: info.inputs);
} on FormatterException {
buildLog.error(
'Generated build script could not be parsed. '
Expand Down Expand Up @@ -195,13 +197,20 @@ Future<BuildScriptInfo> findBuildScriptOptions({
_applyPostProcessBuilder(builder),
];

return BuildScriptInfo(applications);
final inputs = <String>[];
for (final package in packageGraph.allPackages.values) {
inputs.add('${package.path}/build.yaml');
}
inputs.sort();

return BuildScriptInfo(inputs, applications);
}

class BuildScriptInfo {
final List<String> inputs;
final Iterable<Expression> builderApplications;

BuildScriptInfo(this.builderApplications);
BuildScriptInfo(this.inputs, this.builderApplications);
}

/// A method forwarding to `run`.
Expand Down Expand Up @@ -409,3 +418,10 @@ extension ConvertToExpression on Object? {
}
}
}

class GeneratedScript {
String script;
List<String> dependencyPaths;

GeneratedScript({required this.script, required this.dependencyPaths});
}
73 changes: 73 additions & 0 deletions build_runner/lib/src/compiler/compiler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';

void main(List<String> arguments) async {
await Compiler().compile(arguments[0]);
}

class Compiler {
Future<void> compile(String path) async {
final depfilePath = '$path.deps';
final digestFilePath = '$path.digest';

final depfile = File(depfilePath);
final digestFile = File(digestFilePath);

if (depfile.existsSync() && digestFile.existsSync()) {
final expectedDigest = digestFile.readAsStringSync();
final actualDigest = computeDigest(parseDepfile(depfilePath));
if (expectedDigest == actualDigest) {
print('Input digests matched, nothing to do.');
return;
} else {
print(
'Input digests changed from $expectedDigest to $actualDigest, rebuild.',
);
}
}

final result = await Process.run('dart', [
'compile',
'kernel',
path,
'--depfile',
depfilePath,
]);
if (result.exitCode != 0) {
print('Compile failed: ${result.stdout} ${result.stderr}');
exit(1);
}
final deps = parseDepfile(depfilePath);
final digest = computeDigest(deps);
digestFile.writeAsStringSync(digest);

//unawaited(dill.then((result) => print(result.stdout)));
}

List<String> parseDepfile(String depfilePath) {
// TODO(davidmorgan): handle spaces, they seem to be backslash escaped.
final result =
File(depfilePath).readAsStringSync().split(' ').skip(1).toList();
// File ends in a newline.
result.last = result.last.substring(0, result.last.length - 1);
return result;
}

String computeDigest(Iterable<String> deps) {
final digestSink = AccumulatorSink<Digest>();
final result = md5.startChunkedConversion(digestSink);
for (final dep in deps) {
result.add(File(dep).readAsBytesSync());
}
result.close();
return base64.encode(digestSink.events.first.bytes);
}
}
Binary file added build_runner/lib/src/compiler/compiler.exe
Binary file not shown.
50 changes: 50 additions & 0 deletions build_runner/lib/src/compiler/external_build_step.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:io';

import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';

class ExternalBuildStep {
List<String> inputPaths;
String outputPath;

ExternalBuildStep({required this.inputPaths, required this.outputPath});

String get depsPath => '$outputPath.deps';
String get digestsPath => '$outputPath.digests';

bool needsToRun() {
if (!File(depsPath).existsSync()) return true;
if (!File(outputPath).existsSync()) return true;
if (!File(digestsPath).existsSync()) return true;

final digests = computeDigests();
final oldDigests = File(digestsPath).readAsStringSync();
return digests != oldDigests;
}

void stamp() {
// TODO(davidmorgan): spaces.
File(depsPath).writeAsStringSync('$outputPath: ${inputPaths.join(' ')}');
File(digestsPath).writeAsStringSync(computeDigests());
}

String computeDigests() => '''
inputs digest: ${_computeDigest(inputPaths)}
output digest: ${_computeDigest([outputPath])}
''';

String _computeDigest(Iterable<String> deps) {
final digestSink = AccumulatorSink<Digest>();
final result = md5.startChunkedConversion(digestSink);
for (final dep in deps) {
result.add(File(dep).readAsBytesSync());
}
result.close();
return base64.encode(digestSink.events.first.bytes);
}
}
7 changes: 2 additions & 5 deletions build_runner/lib/src/daemon/daemon_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,8 @@ class BuildRunnerDaemonBuilder implements DaemonBuilder {
(change) => AssetChange(AssetId.parse(change.path), change.type),
)
.toList();

if (!_buildOptions.skipBuildScriptCheck &&
_buildSeries.buildScriptUpdates!.hasBeenUpdated(
changes.map<AssetId>((change) => change.id).toSet(),
)) {
// ignore_for_file: dead_code
if (!_buildOptions.skipBuildScriptCheck && false) {
if (!_buildScriptUpdateCompleter.isCompleted) {
_buildScriptUpdateCompleter.complete();
}
Expand Down
5 changes: 2 additions & 3 deletions build_runner/lib/src/generate/watch_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,8 @@ class WatchImpl implements BuildState {

_expectedDeletes.clear();
if (!options.skipBuildScriptCheck) {
if (build.buildScriptUpdates!.hasBeenUpdated(
mergedChanges.keys.toSet(),
)) {
// ignore_for_file: dead_code
if (false) {
_terminateCompleter.complete();
buildLog.error('Terminating builds due to build script update.');
return BuildResult(
Expand Down
1 change: 1 addition & 0 deletions build_runner/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
build_daemon: ^4.0.0
build_runner_core: '9.2.1-wip'
code_builder: ^4.2.0
convert: ^3.1.2
crypto: ^3.0.0
dart_style: '>=2.3.7 <4.0.0'
frontend_server_client: ">=3.0.0 <5.0.0"
Expand Down
Loading
Loading