b464fc9037
This reverts commitcd2c566bcf. Reason for revert: Updating to not remove field used by Flutter engine. Original change's description: > Revert "Tweak `expect.dart` library." > > This reverts commitff5f391c0a. > > Reason for revert: The expect library is used by Flutter engine, and some of its tests use assertStatementsEnabled. There should be a migration path that doesn't require an atomic change, like adding the replacement api before removing the old one. > > Original change's description: > > Tweak `expect.dart` library. > > > > Make API more consistent for a few methods. > > Reduce the number of language features used in tests: > > * Never iterating an iterable, always converting it > > using `.toList()` first and iterating using indices > > (fx `setEquals`). > > Also require a `List` in places where an `Iterable` > > wasn't necessary. > > * Avoid doing complicated computations that are also > > used for the error message. Do simple check first, > > then recompute to get better error messages > > (fx `allDistinct`). > > > > Renamed some rarely used members for consistency > > (`stringContainsInOrder`->`containsInOrder`, > > where other string-contains functions just start > > with `contains`, and `containsOneOf` -> `containsAny` > > to match `Iterable.any` phrasing, and also it accepts > > if containing at least one, not precisely one.) > > > > Removed a function that wasn't used anywhere. > > > > Moved `assertStatementsEnabled` to `variations.dart` as `asserts`. > > Removed `typeAssertionsEnabled` and `checkedModeEnabled`. The former used in one place, where it was replaced with `checkedImplicitDowncasts` from `variations.dart`, the latter wasn't used anywhere. > > > > Deprecates `package:expect/minitest.dart`. It was never intended > > to be used for new tests, only as a help to convert existing tests > > written against `package:unit_test`. > > All existing imports marked as `// ignore: deprecated_member_use`. > > > > Change-Id: I07e21d4c0f3ccf11b82ee34af2668fdbb22264d2 > > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352360 > > Reviewed-by: Slava Egorov <vegorov@google.com> > > Reviewed-by: Ömer Ağacan <omersa@google.com> > > Reviewed-by: Nate Bosch <nbosch@google.com> > > Reviewed-by: Stephen Adams <sra@google.com> > > Commit-Queue: Lasse Nielsen <lrn@google.com> > > Change-Id: I360b4347470a0bb2b63c3108e2b83ee2a771bf3f > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362020 > Reviewed-by: Nate Bosch <nbosch@google.com> > Reviewed-by: Ömer Ağacan <omersa@google.com> > Reviewed-by: Stephen Adams <sra@google.com> > Reviewed-by: Leaf Petersen <leafp@google.com> > Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> > Commit-Queue: William Hesse <whesse@google.com> CoreLibraryReviewExempt: Reland Change-Id: I53db40edc0733842a008839c3913d51c885e39ab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362502 Reviewed-by: Alexander Thomas <athom@google.com> Reviewed-by: Nate Bosch <nbosch@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: William Hesse <whesse@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
492 lines
18 KiB
Dart
492 lines
18 KiB
Dart
import 'dart:convert' show jsonDecode;
|
|
import 'dart:ffi' show Abi;
|
|
import 'dart:io';
|
|
|
|
import 'package:_fe_analyzer_shared/src/testing/annotated_code_helper.dart';
|
|
import 'package:collection/collection.dart' show IterableNullableExtension;
|
|
import 'package:dart2js_tools/src/util.dart';
|
|
import 'package:expect/minitest.dart'; // ignore: deprecated_member_use
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:source_maps/source_maps.dart';
|
|
|
|
/// Runs D8 and steps as the AnnotatedCode dictates.
|
|
///
|
|
/// Note that the compiled javascript is expected to be called "js.js" inside
|
|
/// the outputPath directory. It is also expected that there is a "js.js.map"
|
|
/// file. It is furthermore expected that the js has been compiled from a file
|
|
/// in the same folder called test.dart.
|
|
ProcessResult runD8AndStep(String outputPath, String testFileName,
|
|
AnnotatedCode code, List<String> scriptD8Command) {
|
|
var outputFile = path.join(outputPath, 'js.js');
|
|
var sourcemapText = File('$outputFile.map').readAsStringSync();
|
|
SingleMapping sourceMap = parseSingleMapping(jsonDecode(sourcemapText));
|
|
|
|
Set<int?> mappedToLines = sourceMap.lines
|
|
.map((entry) => entry.entries.map((entry) => entry.sourceLine).toSet())
|
|
.fold(<int?>{}, (prev, e) => prev..addAll(e));
|
|
|
|
for (Annotation annotation
|
|
in code.annotations.where((a) => a.text.trim() == 'nm')) {
|
|
if (mappedToLines.contains(annotation.lineNo - 1)) {
|
|
fail('Was not allowed to have a mapping to line '
|
|
'${annotation.lineNo}, but did.\n'
|
|
'Sourcemap looks like this (note 0-indexed):\n'
|
|
'${sourceMap.debugString}');
|
|
}
|
|
}
|
|
|
|
List<String?> breakpoints = [];
|
|
// Annotations are 1-based, js breakpoints are 0-based.
|
|
for (Annotation breakAt
|
|
in code.annotations.where((a) => a.text.trim() == 'bl')) {
|
|
breakpoints
|
|
.add(_getJsBreakpointLine(testFileName, sourceMap, breakAt.lineNo - 1));
|
|
}
|
|
for (Annotation breakAt
|
|
in code.annotations.where((a) => a.text.trim().startsWith('bc:'))) {
|
|
breakpoints.add(_getJsBreakpointLineAndColumn(
|
|
testFileName, sourceMap, breakAt.lineNo - 1, breakAt.columnNo - 1));
|
|
}
|
|
|
|
File inspectorFile = File.fromUri(
|
|
sdkRoot!.uri.resolve('pkg/sourcemap_testing/lib/src/js/inspector.js'));
|
|
if (!inspectorFile.existsSync()) throw "Couldn't find 'inspector.js'";
|
|
var outInspectorPath = path.join(outputPath, 'inspector.js');
|
|
inspectorFile.copySync(outInspectorPath);
|
|
String debugAction = 'Debugger.stepInto';
|
|
if (code.annotations.any((a) => a.text.trim() == 'Debugger:stepOver')) {
|
|
debugAction = 'Debugger.stepOver';
|
|
}
|
|
return _runD8(outInspectorPath, scriptD8Command, debugAction, breakpoints);
|
|
}
|
|
|
|
/// Translates the D8 js steps and checks against expectations.
|
|
///
|
|
/// Note that the compiled javascript is expected to be called "js.js" inside
|
|
/// the outputPath directory. It is also expected that there is a "js.js.map"
|
|
/// file. It is furthermore expected that the js has been compiled from a file
|
|
/// in the same folder called test.dart.
|
|
void checkD8Steps(String outputPath, List<String> d8Output, AnnotatedCode code,
|
|
{bool debug = false}) {
|
|
var outputFilename = 'js.js';
|
|
var outputFile = path.join(outputPath, outputFilename);
|
|
var sourcemapText = File('$outputFile.map').readAsStringSync();
|
|
SingleMapping sourceMap = parseSingleMapping(jsonDecode(sourcemapText));
|
|
|
|
List<List<_DartStackTraceDataEntry>> result =
|
|
_extractStackTraces(d8Output, sourceMap, outputFilename);
|
|
|
|
List<_DartStackTraceDataEntry> trace =
|
|
result.map((entry) => entry.first).toList();
|
|
if (debug) _debugPrint(trace, outputPath);
|
|
|
|
List<String> recordStops =
|
|
trace.where((entry) => !entry.isError).map((entry) => '$entry').toList();
|
|
|
|
Set<int> recordStopLines =
|
|
trace.where((entry) => !entry.isError).map((entry) => entry.line).toSet();
|
|
Set<String> recordStopLineColumns = trace
|
|
.where((entry) => !entry.isError)
|
|
.map((entry) => '${entry.line}:${entry.column}')
|
|
.toSet();
|
|
|
|
List<String?> expectedStops = [];
|
|
for (Annotation annotation in code.annotations.where((annotation) =>
|
|
annotation.text.trim().startsWith('s:') ||
|
|
annotation.text.trim().startsWith('sl:') ||
|
|
annotation.text.trim().startsWith('bc:'))) {
|
|
String text = annotation.text.trim();
|
|
int stopNum = int.parse(text.substring(text.indexOf(':') + 1));
|
|
if (expectedStops.length < stopNum) expectedStops.length = stopNum;
|
|
if (text.startsWith('sl:')) {
|
|
expectedStops[stopNum - 1] = 'test.dart:${annotation.lineNo}:';
|
|
} else {
|
|
expectedStops[stopNum - 1] =
|
|
'test.dart:${annotation.lineNo}:${annotation.columnNo}:';
|
|
}
|
|
}
|
|
|
|
List<List<String>?> noBreaksStart = [];
|
|
List<List<String>?> noBreaksEnd = [];
|
|
for (Annotation annotation in code.annotations
|
|
.where((annotation) => annotation.text.trim().startsWith('nbb:'))) {
|
|
String text = annotation.text.trim();
|
|
var split = text.split(':');
|
|
int stopNum1 = int.parse(split[1]);
|
|
int stopNum2 = int.parse(split[2]);
|
|
if (noBreaksStart.length <= stopNum1) noBreaksStart.length = stopNum1 + 1;
|
|
(noBreaksStart[stopNum1] ??= []).add('test.dart:${annotation.lineNo}:');
|
|
if (noBreaksEnd.length <= stopNum2) noBreaksEnd.length = stopNum2 + 1;
|
|
(noBreaksEnd[stopNum2] ??= []).add('test.dart:${annotation.lineNo}:');
|
|
}
|
|
|
|
_checkRecordedStops(
|
|
recordStops, expectedStops, noBreaksStart, noBreaksEnd, debug);
|
|
|
|
for (Annotation annotation in code.annotations
|
|
.where((annotation) => annotation.text.trim() == 'nb')) {
|
|
// Check that we didn't break where we're not allowed to.
|
|
if (recordStopLines.contains(annotation.lineNo)) {
|
|
fail('Was not allowed to stop on line ${annotation.lineNo}, but did!'
|
|
' Actual line stops: $recordStopLines${_debugHint(debug)}');
|
|
}
|
|
}
|
|
for (Annotation annotation in code.annotations
|
|
.where((annotation) => annotation.text.trim() == 'nbc')) {
|
|
// Check that we didn't break where we're not allowed to.
|
|
if (recordStopLineColumns
|
|
.contains('${annotation.lineNo}:${annotation.columnNo}')) {
|
|
fail('Was not allowed to stop on line ${annotation.lineNo} '
|
|
'column ${annotation.columnNo}, but did!'
|
|
' Actual line stops: $recordStopLineColumns${_debugHint(debug)}');
|
|
}
|
|
}
|
|
|
|
if (code.annotations.any((a) => a.text.trim() == 'fail')) {
|
|
fail("Test contains 'fail' annotation.");
|
|
}
|
|
}
|
|
|
|
void _checkRecordedStops(
|
|
List<String> recordStops,
|
|
List<String?> expectedStops,
|
|
List<List<String>?> noBreaksStart,
|
|
List<List<String>?> noBreaksEnd,
|
|
bool debug) {
|
|
// We want to find all expected lines in recorded lines in order, but allow
|
|
// more in between in the recorded lines.
|
|
// noBreaksStart and noBreaksStart gives instructions on what's *NOT* allowed
|
|
// to be between those points though.
|
|
|
|
int expectedIndex = 0;
|
|
Set<String> aliveNoBreaks = {};
|
|
if (noBreaksStart.isNotEmpty && noBreaksStart[0] != null) {
|
|
aliveNoBreaks.addAll(noBreaksStart[0]!);
|
|
}
|
|
int stopNumber = 0;
|
|
for (String recorded in recordStops) {
|
|
stopNumber++;
|
|
if (expectedIndex == expectedStops.length) break;
|
|
var expectedStop = expectedStops[expectedIndex];
|
|
if (expectedStop != null && '$recorded:'.contains(expectedStop)) {
|
|
++expectedIndex;
|
|
if (noBreaksStart.length > expectedIndex &&
|
|
noBreaksStart[expectedIndex] != null) {
|
|
aliveNoBreaks.addAll(noBreaksStart[expectedIndex]!);
|
|
}
|
|
if (noBreaksEnd.length > expectedIndex &&
|
|
noBreaksEnd[expectedIndex] != null) {
|
|
aliveNoBreaks.removeAll(noBreaksEnd[expectedIndex]!);
|
|
}
|
|
} else {
|
|
if (debug) {
|
|
// One of the most helpful debugging tools is to see stops that weren't
|
|
// matched. The most common failure is we didn't match one particular
|
|
// stop location (e.g. because of the column). This gets reported
|
|
// as an aliveNoBreaks failure (if the test is using no-breaks like
|
|
// `nbb`) or it's reported as "stops don't match" message.
|
|
//
|
|
// Both failures are difficult to debug without seeing the stops that
|
|
// didn't match. No breaks failures are misleading (the problem isn't
|
|
// an incorrect break, but we missed a stop, so the aliveNoBreaks is
|
|
// wrong), and the normal failure list dumps the entire stop list,
|
|
// making it difficult to see where the mismatch was.
|
|
//
|
|
// Also we add 1 to expectedIndex, because the stop annotations are
|
|
// 1-based in the source files (e.g. `/*s:1*/` is expectedIndex 0)
|
|
print("Skipping stop `$recorded` that didn't match expected stop "
|
|
'${expectedIndex + 1} `${expectedStops[expectedIndex]}`');
|
|
}
|
|
if (aliveNoBreaks
|
|
.contains("${(recorded.split(":")..removeLast()).join(":")}:")) {
|
|
fail("Break '$recorded' was found when it wasn't allowed "
|
|
'(js step $stopNumber, after stop ${expectedIndex + 1}). '
|
|
'This can happen when an expected stop was not matched'
|
|
'${_debugHint(debug)}.');
|
|
}
|
|
}
|
|
}
|
|
if (expectedIndex != expectedStops.length) {
|
|
// Didn't find everything.
|
|
fail('Expected to find $expectedStops but found $recordStops'
|
|
'${_debugHint(debug)}');
|
|
}
|
|
}
|
|
|
|
/// If we're not in debug mode, this returns a message string with information
|
|
/// about how to enable debug mode in the test runner.
|
|
String _debugHint(bool debug) {
|
|
if (debug) return ''; // already in debug mode
|
|
return ' (pass -Ddebug=1 to the test runner to see debug information)';
|
|
}
|
|
|
|
void _debugPrint(List<_DartStackTraceDataEntry> trace, String outputPath) {
|
|
StringBuffer sb = StringBuffer();
|
|
var jsFile =
|
|
File(path.join(outputPath, 'js.js')).readAsStringSync().split('\n');
|
|
var dartFile =
|
|
File(path.join(outputPath, 'test.dart')).readAsStringSync().split('\n');
|
|
|
|
List<String> getSnippet(List<String> data, int line, int column) {
|
|
List<String> result = List<String>.filled(5, '');
|
|
if (line < 0 || column < 0) return result;
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
int j = line - 2 + i;
|
|
if (j < 0 || j >= data.length) continue;
|
|
result[i] = data[j];
|
|
}
|
|
if (result[2].length >= column) {
|
|
var before = result[2].substring(0, column);
|
|
var after = result[2].substring(column);
|
|
result[2] = '$before/*STOP*/$after';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
List<String> sideBySide(List<String> a, List<String> b, int columns) {
|
|
List<String> result = List<String>.filled(a.length, '');
|
|
for (int i = 0; i < a.length; ++i) {
|
|
String left = a[i].padRight(columns).substring(0, columns);
|
|
String right = b[i].padRight(columns).substring(0, columns);
|
|
result[i] = '$left | $right';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
for (int i = 0; i < trace.length; ++i) {
|
|
sb.write('\n\nStop #${i + 1}\n\n');
|
|
if (trace[i].isError && trace[i].jsLine < 0) {
|
|
sb.write('${trace[i].errorString}\n');
|
|
continue;
|
|
}
|
|
var jsSnippet = getSnippet(jsFile, trace[i].jsLine, trace[i].jsColumn);
|
|
var dartSnippet =
|
|
getSnippet(dartFile, trace[i].line - 1, trace[i].column - 1);
|
|
var view = sideBySide(jsSnippet, dartSnippet, 50);
|
|
sb.writeAll(view, '\n');
|
|
}
|
|
|
|
print(sb.toString());
|
|
}
|
|
|
|
List<List<_DartStackTraceDataEntry>> _extractStackTraces(
|
|
List<String> lines, SingleMapping sourceMap, String outputFilename) {
|
|
List<List<_DartStackTraceDataEntry>> result = [];
|
|
bool inStackTrace = false;
|
|
List<String> currentStackTrace = <String>[];
|
|
for (var line in lines) {
|
|
if (line.trim() == '--- Debugger stacktrace start ---') {
|
|
inStackTrace = true;
|
|
} else if (line.trim() == '--- Debugger stacktrace end ---') {
|
|
result.add(
|
|
_extractStackTrace(currentStackTrace, sourceMap, outputFilename));
|
|
currentStackTrace.clear();
|
|
inStackTrace = false;
|
|
} else if (inStackTrace) {
|
|
currentStackTrace.add(line.trim());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
List<_DartStackTraceDataEntry> _extractStackTrace(
|
|
List<String> js, SingleMapping sourceMap, String wantedFile) {
|
|
List<_DartStackTraceDataEntry> result = [];
|
|
for (String line in js) {
|
|
if (!line.contains('$wantedFile:')) {
|
|
result.add(_DartStackTraceDataEntry.error("Not correct file @ '$line'"));
|
|
continue;
|
|
}
|
|
Iterable<Match> ms = RegExp(r'(\d+):(\d+)').allMatches(line);
|
|
if (ms.isEmpty) {
|
|
result.add(_DartStackTraceDataEntry.error(
|
|
"Line and column not found for '$line'"));
|
|
continue;
|
|
}
|
|
Match m = ms.first;
|
|
int l = int.parse(m.group(1)!);
|
|
int c = int.parse(m.group(2)!);
|
|
SourceMapSpan? span = _getColumnOrPredecessor(sourceMap, l, c);
|
|
if (span?.start == null) {
|
|
result.add(_DartStackTraceDataEntry.errorWithJsPosition(
|
|
"Source map not found for '$line'", l, c));
|
|
continue;
|
|
}
|
|
var file = span!.sourceUrl?.pathSegments.last ?? '(unknown file)';
|
|
result.add(_DartStackTraceDataEntry(
|
|
file, span.start.line + 1, span.start.column + 1, l, c));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
SourceMapSpan? _getColumnOrPredecessor(
|
|
SingleMapping sourceMap, int line, int column) {
|
|
SourceMapSpan? span = sourceMap.spanFor(line, column);
|
|
if (span == null && line > 0) {
|
|
span = sourceMap.spanFor(line - 1, 999999);
|
|
}
|
|
return span;
|
|
}
|
|
|
|
class _DartStackTraceDataEntry {
|
|
final String? file;
|
|
final int line;
|
|
final int column;
|
|
final String? errorString;
|
|
final int jsLine;
|
|
final int jsColumn;
|
|
|
|
_DartStackTraceDataEntry(
|
|
this.file, this.line, this.column, this.jsLine, this.jsColumn)
|
|
: errorString = null;
|
|
_DartStackTraceDataEntry.error(this.errorString)
|
|
: file = null,
|
|
line = -1,
|
|
column = -1,
|
|
jsLine = -1,
|
|
jsColumn = -1;
|
|
_DartStackTraceDataEntry.errorWithJsPosition(
|
|
this.errorString, this.jsLine, this.jsColumn)
|
|
: file = null,
|
|
line = -1,
|
|
column = -1;
|
|
|
|
bool get isError => errorString != null;
|
|
|
|
@override
|
|
String toString() => isError ? errorString! : '$file:$line:$column';
|
|
}
|
|
|
|
class _PointMapping {
|
|
final int fromLine;
|
|
final int fromColumn;
|
|
final int toLine;
|
|
final int toColumn;
|
|
|
|
_PointMapping(this.fromLine, this.fromColumn, this.toLine, this.toColumn);
|
|
}
|
|
|
|
/// Input and output is expected to be 0-based.
|
|
///
|
|
/// The "magic 4" below is taken from
|
|
/// https://github.com/ChromeDevTools/devtools-frontend/blob/fa18d70a995f06cb73365b2e5b8ae974cf60bd3a/front_end/sources/JavaScriptSourceFrame.js#L1520-L1523
|
|
String? _getJsBreakpointLine(
|
|
String testFileName, SingleMapping sourceMap, int breakOnLine) {
|
|
List<_PointMapping> mappingsOnLines = [];
|
|
for (var line in sourceMap.lines) {
|
|
for (var entry in line.entries) {
|
|
final sourceLine = entry.sourceLine;
|
|
if (sourceLine == null) continue;
|
|
final sourceColumn = entry.sourceColumn!;
|
|
final sourceUrlId = entry.sourceUrlId;
|
|
if (sourceLine >= breakOnLine &&
|
|
sourceLine < breakOnLine + 4 &&
|
|
sourceUrlId != null &&
|
|
sourceMap.urls[sourceUrlId] == testFileName) {
|
|
mappingsOnLines.add(
|
|
_PointMapping(sourceLine, sourceColumn, line.line, entry.column));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mappingsOnLines.isEmpty) return null;
|
|
|
|
mappingsOnLines.sort((a, b) {
|
|
if (a.fromLine != b.fromLine) return a.fromLine - b.fromLine;
|
|
if (a.fromColumn != b.fromColumn) return a.fromColumn - b.fromColumn;
|
|
if (a.toLine != b.toLine) return a.toLine - b.toLine;
|
|
return a.toColumn - b.toColumn;
|
|
});
|
|
_PointMapping first = mappingsOnLines.first;
|
|
mappingsOnLines.retainWhere((p) => p.toLine >= first.toLine);
|
|
|
|
_PointMapping last = mappingsOnLines.last;
|
|
return '${first.toLine}:${first.toColumn}:${last.toLine}:${first.toColumn}';
|
|
}
|
|
|
|
/// Input and output is expected to be 0-based.
|
|
String? _getJsBreakpointLineAndColumn(String testFileName,
|
|
SingleMapping sourceMap, int breakOnLine, int breakOnColumn) {
|
|
for (var line in sourceMap.lines) {
|
|
for (var entry in line.entries) {
|
|
if (entry.sourceLine == breakOnLine &&
|
|
entry.sourceColumn == breakOnColumn &&
|
|
entry.sourceUrlId != null &&
|
|
sourceMap.urls[entry.sourceUrlId!] == testFileName) {
|
|
return '${line.line}:${entry.column}';
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ProcessResult _runD8(String outInspectorPath, List<String> scriptD8Command,
|
|
String debugAction, List<String?> breakpoints) {
|
|
var outInspectorPathRelative = path.relative(outInspectorPath);
|
|
ProcessResult runResult = Process.runSync(d8Executable, [
|
|
'--enable-inspector',
|
|
outInspectorPathRelative,
|
|
...scriptD8Command,
|
|
'--',
|
|
debugAction,
|
|
...breakpoints.whereNotNull()
|
|
]);
|
|
if (runResult.exitCode != 0) {
|
|
print(runResult.stderr);
|
|
print(runResult.stdout);
|
|
throw 'Exit code: ${runResult.exitCode} from d8';
|
|
}
|
|
return runResult;
|
|
}
|
|
|
|
File? _cachedD8File;
|
|
Directory? _cachedSdkRoot;
|
|
File getD8File() {
|
|
File attemptFileFromDir(Directory dir) {
|
|
final arch = Abi.current().toString().split('_')[1];
|
|
if (Platform.isWindows) {
|
|
return File(
|
|
'${dir.path}${Platform.pathSeparator}d8/windows/$arch/d8.exe');
|
|
} else if (Platform.isLinux) {
|
|
return File('${dir.path}${Platform.pathSeparator}d8/linux/$arch/d8');
|
|
} else if (Platform.isMacOS) {
|
|
return File('${dir.path}${Platform.pathSeparator}d8/macos/$arch/d8');
|
|
}
|
|
throw UnsupportedError('Unsupported platform.');
|
|
}
|
|
|
|
File search() {
|
|
Directory dir = File.fromUri(Platform.script).parent;
|
|
while (dir.path.length > 1) {
|
|
for (var entry in dir.listSync()) {
|
|
if (entry is! Directory) continue;
|
|
if (entry.path.endsWith('third_party')) {
|
|
List<String> segments = entry.uri.pathSegments;
|
|
if (segments[segments.length - 2] == 'third_party') {
|
|
File possibleD8 = attemptFileFromDir(entry);
|
|
if (possibleD8.existsSync()) {
|
|
_cachedSdkRoot = dir;
|
|
return possibleD8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dir = dir.parent;
|
|
}
|
|
|
|
throw 'Cannot find D8 directory.';
|
|
}
|
|
|
|
return _cachedD8File ??= search();
|
|
}
|
|
|
|
Directory? get sdkRoot {
|
|
getD8File();
|
|
return _cachedSdkRoot;
|
|
}
|
|
|
|
String get d8Executable {
|
|
return getD8File().path;
|
|
}
|