-
Notifications
You must be signed in to change notification settings - Fork 225
/
dart.dart
197 lines (178 loc) · 6.98 KB
/
dart.dart
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
196
197
// Copyright (c) 2013, 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.
/// A library for compiling Dart code and manipulating analyzer parse trees.
library;
import 'dart:async';
import 'dart:io';
import 'package:analyzer/dart/analysis/context_builder.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart';
import 'package:frontend_server_client/frontend_server_client.dart';
import 'package:path/path.dart' as p;
import 'exceptions.dart';
import 'io.dart';
import 'log.dart' as log;
class AnalysisContextManager {
static final sessions = <String, AnalysisContextManager>{};
final String packagePath;
final AnalysisSession _session;
factory AnalysisContextManager(String packagePath) => sessions.putIfAbsent(
packagePath,
() => AnalysisContextManager._(packagePath),
);
AnalysisContextManager._(this.packagePath)
: _session = ContextBuilder()
.createContext(
contextRoot: ContextLocator().locateRoots(
includedPaths: [p.absolute(p.normalize(packagePath))],
optionsFile:
// We don't want to take 'analysis_options.yaml' files into
// account. So we replace it with an empty file.
Platform.isWindows ? r'C:\NUL' : '/dev/null',
).first,
)
.currentSession;
/// Parse the file with the given [path] into AST.
///
/// One of the containing directories must be used to create analysis
/// contexts using [createContextsForDirectory]. Throws [StateError] if
/// this has not been done.
///
/// Throws [AnalyzerErrorGroup] is the file has parsing errors.
CompilationUnit parse(String path) {
path = p.normalize(p.absolute(path));
var parseResult = _session.getParsedUnit(path);
if (parseResult is ParsedUnitResult) {
if (parseResult.errors.isNotEmpty) {
throw AnalyzerErrorGroup(parseResult.errors);
}
return parseResult.unit;
} else {
throw StateError('Unable to parse $path, ${parseResult.runtimeType}');
}
}
/// Return import and export directives in the file with the given [path].
///
/// Throws [AnalyzerErrorGroup] is the file has parsing errors.
List<UriBasedDirective> parseImportsAndExports(String path) {
var unit = parse(path);
var uriDirectives = <UriBasedDirective>[];
for (var directive in unit.directives) {
if (directive is UriBasedDirective) {
uriDirectives.add(directive);
}
}
return uriDirectives;
}
}
/// An error class that contains multiple [AnalysisError]s.
class AnalyzerErrorGroup implements Exception {
final List<AnalysisError> errors;
AnalyzerErrorGroup(this.errors);
String get message => toString();
@override
String toString() => errors.join('\n');
}
/// Precompiles the Dart executable at [executablePath].
///
/// If the compilation succeeds it is saved to a kernel file at [outputPath].
///
/// If compilation fails, the output is cached at "[outputPath].incremental".
///
/// Whichever of "[outputPath].incremental" and [outputPath] already exists is
/// used to initialize the compiler run. To avoid the potential for
/// race-conditions, it is first copied to a temporary location, and atomically
/// moved to either [outputPath] or "[outputPath].incremental" depending on the
/// result of compilation.
///
/// The [packageConfigPath] should point at the package config file to be used
/// for `package:` uri resolution.
///
/// The [name] is used to describe the executable in logs and error messages.
///
/// The [additionalSources], if provided, instruct the compiler to include
/// additional source files into compilation even if they are not referenced
/// from the main library.
///
/// The [nativeAssets], if provided, instruct the compiler include a native
/// assets map.
Future<void> precompile({
required String executablePath,
required String name,
required String outputPath,
required String packageConfigPath,
List<String> additionalSources = const [],
String? nativeAssets,
}) async {
const platformDill = 'lib/_internal/vm_platform_strong.dill';
final sdkRoot = p.relative(p.dirname(p.dirname(Platform.resolvedExecutable)));
String? tempDir;
FrontendServerClient? client;
try {
ensureDir(p.dirname(outputPath));
final incrementalDillPath = '$outputPath.incremental';
tempDir = createTempDir(p.dirname(outputPath), 'tmp');
// To avoid potential races we copy the incremental data to a temporary file
// for just this compilation.
final temporaryIncrementalDill =
p.join(tempDir, '${p.basename(incrementalDillPath)}.temp');
try {
if (fileExists(outputPath)) {
copyFile(outputPath, temporaryIncrementalDill);
} else if (fileExists(incrementalDillPath)) {
copyFile(incrementalDillPath, temporaryIncrementalDill);
}
} on FileSystemException {
// Not able to copy existing file, compilation will start from scratch.
}
client = await FrontendServerClient.start(
executablePath,
temporaryIncrementalDill,
platformDill,
sdkRoot: sdkRoot,
packagesJson: packageConfigPath,
additionalSources: additionalSources,
nativeAssets: nativeAssets,
printIncrementalDependencies: false,
);
final result = await client.compile();
// Sanity check. We've had reports of the compilation failing to provide a
// result, perhaps due to low-memory conditions.
// This should make this slightly easier to recognize in error reports.
if (!fileExists(temporaryIncrementalDill)) {
log.error(
'Compilation did not produce any result. Expected file at `$temporaryIncrementalDill`',
result.dillOutput,
);
}
final highlightedName = log.bold(name);
if (result.errorCount == 0) {
log.message('Built $highlightedName.');
// By using rename we ensure atomicity. An external observer will either
// see the old or the new snapshot.
renameFile(temporaryIncrementalDill, outputPath);
// Any old incremental data is deleted in case we started from a file on
// [incrementalDillPath].
deleteEntry(incrementalDillPath);
} else {
// By using rename we ensure atomicity. An external observer will either
// see the old or the new snapshot.
renameFile(temporaryIncrementalDill, incrementalDillPath);
// If compilation failed, don't leave an incorrect snapshot.
tryDeleteEntry(outputPath);
throw ApplicationException(
log.yellow('Failed to build $highlightedName:\n') +
result.compilerOutputLines.join('\n'),
);
}
} finally {
client?.kill();
if (tempDir != null) {
tryDeleteEntry(tempDir);
}
}
}