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 9 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: 3 additions & 4 deletions build_runner/bin/src/commands/generate_build_script.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ class GenerateBuildScript extends Command<int> {
@override
Future<int> run() async {
Logger.root.clearListeners();
var buildScript = await generateBuildScript();
File(scriptLocation)
..createSync(recursive: true)
..writeAsStringSync(buildScript);
final action = generateBuildScriptBootstrapAction();
final result = await action.maybeRunAndWrite();
print(result);
print(p.absolute(scriptLocation));
return 0;
}
Expand Down
57 changes: 39 additions & 18 deletions build_runner/lib/src/build_script_generate/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';

import '../compiler/compiler.dart';
import 'build_process_state.dart';
import 'build_script_generate.dart';

Expand All @@ -26,31 +27,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 @@ -66,30 +65,50 @@ Future<int> _generateAndRun(
messagePort?.close();
await errorListener?.cancel();

final buildScriptAction = generateBuildScriptBootstrapAction();

try {
var buildScript = File(scriptLocation);
var result = await buildScriptAction.maybeRunAndWrite();

buildLog.debug(result.toString());

if (result.succeeded == false) {
return ExitCode.config.code;
}

/*var buildScript = File(scriptLocation);
var oldContents = '';
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()) {
kernelFile.deleteSync();
}
buildLog.fullBuildBecause(FullBuildReason.incompatibleScript);
}
}*/
} on CannotBuildException {
return ExitCode.config.code;
}

if (!await _createKernelIfNeeded(experiments)) {
final dillAction = compileToKernelBootstrapAction();
final result = await dillAction.maybeRunAndWrite();
print(result);

if (!result.succeeded) {
print(result.messages);
return buildProcessState.isolateExitCode = ExitCode.config.code;
}

Expand Down Expand Up @@ -183,7 +202,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 +215,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 @@ -13,10 +13,12 @@ import 'package:graphs/graphs.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';

import '../compiler/bootstrap_action.dart';
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 +27,14 @@ const scriptKernelCachedSuffix = '.cached';

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

Future<String> generateBuildScript() async {
BootstrapAction generateBuildScriptBootstrapAction() {
return BootstrapAction(
outputPath: scriptLocation,
action: generateBuildScript,
);
}

Future<BootstrapActionResult> generateBuildScript() async {
buildLog.doing('Generating the build script.');
final info = await findBuildScriptOptions();
final builders = info.builderApplications;
Expand Down Expand Up @@ -55,13 +64,20 @@ 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 BootstrapActionResult(
ran: true,
succeeded: true,
content: script,
// TODO(davidmorgan): this should also include the current script+runtime.
inputPaths: info.inputs,
);
} on FormatterException {
buildLog.error(
Expand Down Expand Up @@ -195,13 +211,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 +432,10 @@ extension ConvertToExpression on Object? {
}
}
}

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

GeneratedScript({required this.script, required this.dependencyPaths});
}
112 changes: 112 additions & 0 deletions build_runner/lib/src/compiler/bootstrap_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 BootstrapAction {
final String outputPath;
final Future<BootstrapActionResult> Function() action;

BootstrapAction({required this.outputPath, required this.action});

String get _depsPath => '$outputPath.deps';
String get _digestsPath => '$outputPath.digests';

Future<BootstrapActionResult> maybeRunAndWrite() async {
if (!File(_depsPath).existsSync()) return runAndWrite();
if (!File(outputPath).existsSync()) return runAndWrite();
if (!File(_digestsPath).existsSync()) return runAndWrite();

final digests = computeDigests();
final oldDigests = File(_digestsPath).readAsStringSync();
if (digests != oldDigests) return runAndWrite();

return BootstrapActionResult(ran: false, succeeded: true);
}

Future<BootstrapActionResult> runAndWrite() async {
final result = await run();

if (result._inputPaths != null) {
File(_depsPath)
..createSync(recursive: true)
..writeAsStringSync(
'$outputPath: '
'${result._inputPaths.join(' ')}',
);
}
if (result._content != null) {
File(outputPath)
..createSync(recursive: true)
..writeAsStringSync(result._content);
}
File(_digestsPath).writeAsStringSync(computeDigests());
return result;
}

Future<BootstrapActionResult> run() {
return action();
}

List<String> _readInputPaths() {
// TODO(davidmorgan): maybe return from memory?
// TODO(davidmorgan): escape spaces.
final result =
File(_depsPath).readAsStringSync().split(' ').skip(1).toList();
// File ends in a newline.
result.last = result.last.substring(0, result.last.length - 1);
return result;
}

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

String _computeDigest(Iterable<String> deps) {
final digestSink = AccumulatorSink<Digest>();
final result = md5.startChunkedConversion(digestSink);
for (final dep in deps) {
final file = File(dep);
if (file.existsSync()) {
result.add([1]);
result.add(File(dep).readAsBytesSync());
} else {
result.add([0]);
}
}
result.close();
return base64.encode(digestSink.events.first.bytes);
}
}

class BootstrapActionResult {
final bool ran;
final bool succeeded;
final String? messages;
final List<String>? _inputPaths;
final String? _content;

BootstrapActionResult({
required this.ran,
required this.succeeded,
this.messages,
List<String>? inputPaths,
String? content,
}) : _inputPaths = inputPaths,
_content = content;

@override
String toString() => '''
BootstrapActionResult(
ran: $ran,
succeeded: $succeeded,
messages: $messages,
_inputPaths: $_inputPaths,
_content: $_content)''';
}
41 changes: 41 additions & 0 deletions build_runner/lib/src/compiler/compiler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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:io';

import 'bootstrap_action.dart';

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

BootstrapAction compileToKernelBootstrapAction() => BootstrapAction(
outputPath: '.dart_tool/build/entrypoint/build.dart.dill',
action: () => Compiler().compile(),
);

class Compiler {
Future<BootstrapActionResult> compile() async {
final result = await Process.run('dart', [
'compile',
'kernel',
'.dart_tool/build/entrypoint/build.dart',
'--output',
'.dart_tool/build/entrypoint/build.dart.dill',
'--depfile',
'.dart_tool/build/entrypoint/build.dart.dill.deps',
]);
if (result.exitCode != 0) {
print('Compile failed: ${result.stdout} ${result.stderr}');
return BootstrapActionResult(
ran: true,
succeeded: false,
messages: result.stderr as String,
);
}

return BootstrapActionResult(ran: true, succeeded: true);
}
}
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
Loading
Loading