forked from dart-lang/build
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbootstrapper.dart
More file actions
195 lines (180 loc) · 7.43 KB
/
Copy pathbootstrapper.dart
File metadata and controls
195 lines (180 loc) · 7.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// 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 'package:built_collection/built_collection.dart';
import '../exceptions.dart';
import '../internal.dart';
import 'aot_compiler.dart';
import 'compiler.dart';
import 'depfile.dart';
import 'kernel_compiler.dart';
import 'processes.dart';
/// Generates, runs and checks freshness of the entrypoint script.
///
/// The entrypoint script calls [ChildProcess.run] passing a `BuilderFactories`
/// that knows how to instantiate all the builders that will run during the
/// build.
///
/// If [workspace] the `BuilderFactories` contains builders applied to any
/// package in the workspace. Otherwise, it only contains builders for the
/// current directory package and its transitive dependencies.
///
/// When the entrypoint script is compiled a "depfile" is created listing all
/// the sources it is compiled from. Then a digest is written based on the
/// contents of all these files so they can be checked for freshness later.
///
/// The entrypoint script is launched using [ParentProcess.runAndSend]
/// or [ParentProcess.runAotSnapshotAndSend] which passes initial state to it
/// and receives updated state when it exits.
class Bootstrapper {
final bool workspace;
final bool compileAot;
final Compiler _compiler;
Bootstrapper({required this.workspace, required this.compileAot})
: _compiler = compileAot ? AotCompiler() : KernelCompiler();
/// Generates the entrypoint script, compiles it and runs it with [arguments].
///
/// If the entrypoint script exits with
/// `ChildProcess.recompileBuildersExitCode` or
/// `ChildProcess.assetDeletedExitCode` then regenerates it and launches it
/// again with the same arguments.
///
/// If the entrypoint exits with any other exit code, returns it.
///
/// Throws `CannotBuildException` if the generated build script is invalid and
/// cannot be compiled. The generated build script will be invalid if the
/// build configuration points to invalid builder factories, for example if
/// they do not exist or have the wrong types.
Future<int> run(
BuiltList<String> arguments, {
required Iterable<String> jitVmArgs,
required bool dartAotPerf,
Iterable<String>? experiments,
bool retryCompileFailures = false,
}) async {
String? previousMessages;
while (true) {
// Write build script based on current config read from disk.
await _writeBuildScript();
// Compile if there was any change.
if (!_compiler.checkFreshness(digestsAreFresh: false).outputIsFresh) {
final result = await buildLog.logCompile(
isAot: compileAot,
function: () => _compiler.compile(experiments: experiments),
);
// When retrying: for the first failure, log the start of a new build.
// After that, only log if the compiler output changed. The "compiling"
// message is still shown, with a stopwatch that resets to zero every
// time the build restarts, but no other output until the compiler
// output changes.
final messagesMatchPreviousMessages =
result.messages == previousMessages;
previousMessages = result.messages;
if (!result.succeeded) {
final bool failedDueToMirrors;
if (result.messages == null) {
failedDueToMirrors = false;
} else {
failedDueToMirrors =
compileAot && result.messages!.contains('dart:mirrors');
}
if (failedDueToMirrors) {
// TODO(davidmorgan): when build_runner manages use of AOT compile
// this will be an automatic fallback to JIT instead of a message.
buildLog.error(result.messages!);
buildLog.error(
'Failed to compile build script. A configured builder '
'uses `dart:mirrors` and cannot be compiled AOT. Try again '
'without --force-aot to use a JIT compile.',
);
} else {
if (!messagesMatchPreviousMessages) {
buildLog.error(result.messages!);
buildLog.error(
'Failed to compile build script. '
'Check builder definitions and generated script '
'$entrypointScriptPath.'
'${retryCompileFailures ? ' Retrying.' : ''}',
);
if (retryCompileFailures) {
buildLog.nextBuild();
}
}
}
if (retryCompileFailures && !failedDueToMirrors) continue;
throw const CannotBuildException();
}
}
final result =
compileAot
? await ParentProcess.runAotSnapshotAndSend(
aotSnapshot: entrypointAotPath,
arguments: arguments,
message: buildProcessState.serialize(),
runUnderPerf: dartAotPerf,
)
: await ParentProcess.runAndSend(
script: entrypointDillPath,
arguments: arguments,
message: buildProcessState.serialize(),
jitVmArgs: jitVmArgs,
);
buildProcessState.deserializeAndSet(result.message);
final exitCode = result.exitCode;
if (exitCode != ChildProcess.recompileBuildersExitCode &&
exitCode != ChildProcess.assetDeletedExitCode) {
return exitCode;
}
buildLog.nextBuild(
recompilingBuilders: exitCode == ChildProcess.recompileBuildersExitCode,
);
}
}
/// Reads build configuration, writes the build script.
///
/// Reads before write so the file is not written if there is no change.
Future<void> _writeBuildScript() async {
final buildScript = await generateBuildScript(workspace: workspace);
final path = entrypointScriptPath;
final existingBuildScript =
File(path).existsSync() ? File(path).readAsStringSync() : null;
if (buildScript != existingBuildScript) {
File(path)
..createSync(recursive: true)
..writeAsStringSync(buildScript);
}
}
/// Checks freshness of the compiled entrypoint script.
///
/// Set [digestsAreFresh] if digests were very recently updated. Then, they
/// will be re-used from disk if possible instead of recomputed.
Future<FreshnessResult> checkCompileFreshness({
required bool digestsAreFresh,
}) async {
if (!ChildProcess.isRunning) {
// Any real use or realistic test has a child process; so this is only hit
// in small tests. Return "fresh" so nothing related to recompiling is
// triggered.
return FreshnessResult(outputIsFresh: true);
}
if (digestsAreFresh) {
final maybeResult = _compiler.checkFreshness(digestsAreFresh: true);
if (maybeResult.outputIsFresh) return maybeResult;
// Digest file must be missing, continue to compile.
}
await _writeBuildScript();
return _compiler.checkFreshness(digestsAreFresh: false);
}
/// Whether [path] is a dependency of the compiled entrypoint script.
bool isCompileDependency(String path) {
if (!ChildProcess.isRunning) {
// Any real use or realistic test has a child process; so this is only hit
// in small tests. Return "not a dependency" so nothing related to
// recompiling is triggered.
return false;
}
return _compiler.isDependency(path);
}
}