[frontend_server_client] Migrate package to the SDK repository
This CL migrates the `frontend_server_client` package into the Dart SDK repository. Key Changes: - Migration: Moved source code and tests into `pkg/frontend_server_client`. - Monorepo Compliance: Updated the pubspec to align with the SDK pub workspace setup. - Linting: Adopts `package:dart_flutter_team_lints` for analysis options and fixes associated linting errors. - Path Resolution: Resolved package configuration and script path issues encountered in local and CI environments; see the [Workspace Tests Fixes](https://docs.google.com/document/d/1UdiRqP19qYgj-ItxDfqXSdUU6CGimiyK3BmdvjtTEyw/edit?pli=1&tab=t.0#heading=h.xfm1ezicaoik) in the design doc for implementation details. Testing: - All tests passing locally. - CI try bots are green. Design Doc: http://goto.google.com/migrating-webdev Cq-Include-Trybots: luci.dart.try:pkg-win-release-try,pkg-win-release-arm64-try,pkg-mac-release-try,pkg-mac-release-arm64-try,pkg-linux-release-try,pkg-linux-release-arm64-try,pkg-linux-debug-try Change-Id: Id44a701107d626df5fcd21d724980243981c7958 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461200 Reviewed-by: Alexander Thomas <athom@google.com> Commit-Queue: Jessy Yameogo <yjessy@google.com>
This commit is contained in:
committed by
Commit Queue
parent
808b2512b4
commit
81a139584e
@@ -0,0 +1,57 @@
|
||||
## 4.0.0
|
||||
|
||||
- Update Dart SDK constraint to `^3.0.0`.
|
||||
- Support changes in the SDK layout for Dart 3.0.
|
||||
- By default, start the frontend server from the AOT snapshot shipped in the
|
||||
Dart SDK.
|
||||
- Throw an `ArgumentError` when `FrontendServerClient.start` is called with the
|
||||
`frontendServerPath` argument omitted and the `debug` argument set to true.
|
||||
- Update `package:vm_service` constraint to `^14.0.0`.
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Add `nativeAssets` parameter to `FrontendServerClient`, for passing
|
||||
additional `--native-assets` to the kernel compiler.
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- Add `additionalSources` parameter to `FrontendServerClient`, for passing
|
||||
additional `--source`s to the kernel compiler.
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- Update the `compile` api to return a non-null `CompileResult`, and instead
|
||||
make the `dillOutput` field nullable. This allows you to still get compiler
|
||||
output if no dill file was produced.
|
||||
|
||||
## 2.1.3
|
||||
|
||||
- Update `package:vm_service` to version `^8.0.0`
|
||||
|
||||
## 2.1.2
|
||||
|
||||
- Force kill the frontend server after one second when calling shutdown. It
|
||||
appears to hang on windows sometimes.
|
||||
|
||||
## 2.1.1
|
||||
|
||||
- Fix a bug where spaces in the output dill path would cause a parse error when
|
||||
reading the error count output.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- Support enabling experiments when starting the compiler.
|
||||
|
||||
## 2.0.1
|
||||
|
||||
- Widen the upper bound sdk constraint to `<3.0.0`. The frontend server api is
|
||||
now considered quite stable and this package is now depended on by
|
||||
package:test, so a tight constraint would cause unnecessary headaches.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- Support null safety.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Initial version
|
||||
@@ -0,0 +1,26 @@
|
||||
Copyright 2012, the Dart project authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,7 @@
|
||||
set noparent
|
||||
# Dart Development Infrastructure Team
|
||||
file:/tools/OWNERS_DEV_INFRA
|
||||
# VM Team
|
||||
file:/tools/OWNERS_VM
|
||||
# Global owners.
|
||||
file:/OWNERS
|
||||
@@ -0,0 +1,37 @@
|
||||
[](https://pub.dev/packages/frontend_server_client)
|
||||
[](https://pub.dev/packages/frontend_server_client/publisher)
|
||||
|
||||
This package provides a client interface around the frontend_server compiler
|
||||
which ships with the Dart SDK.
|
||||
|
||||
## SDK Versioning Policy
|
||||
|
||||
This package keeps a relatively tight version constraint on the SDK to allow for
|
||||
breaking changes in the frontend_server binary itself.
|
||||
|
||||
Specifically, releases of this package will have an upper bound of less than the
|
||||
next _minor_ (middle) version number of the latest stable SDK. There are no
|
||||
requirements for the lower bound (other than the package must pass tests on that
|
||||
SDK).
|
||||
|
||||
The effect of this policy is that breaking changes will be allowed to the
|
||||
frontend_server binary, but only in _minor_ SDK version releases.
|
||||
|
||||
**Note**: This also means that when a new stable SDK is released, this package
|
||||
will also need a new version published on pub before users can get a valid
|
||||
version solve.
|
||||
|
||||
### Working with dev SDK releases
|
||||
|
||||
By default, when you do a pub get/upgrade, a constraint like `<2.9.0` will
|
||||
actually allow dev releases of `2.9.0` if the current SDK is a dev release. It
|
||||
emits a warning when it does this, but will happily allow it.
|
||||
|
||||
- This means that we don't have to publish versions that explicitly allow dev
|
||||
releases. We will be notified of breakages by our bots if a dev release does
|
||||
break us, and can release a patch.
|
||||
|
||||
If we need to depend on some new feature from a dev release, the min sdk
|
||||
constraint should be bumped to that version but the max sdk constraint should
|
||||
_not_ be changed. So for example we will have constraints like
|
||||
`>=2.9.0-dev.1 <2.9.0`.
|
||||
@@ -0,0 +1 @@
|
||||
include: package:dart_flutter_team_lints/analysis_options.yaml
|
||||
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script defer src="main.dart.js" type="application/javascript"></script>
|
||||
</head>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<void> main() async {
|
||||
print(message);
|
||||
while (!message.contains('goodbye')) {
|
||||
print('waiting for hot reload to change message');
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
print(message);
|
||||
}
|
||||
|
||||
String get message => p.join('hello', 'world');
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2020 The Dart Authors. 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:frontend_server_client/frontend_server_client.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Change to package root so relative paths work in CI
|
||||
final scriptDir = p.dirname(p.fromUri(Platform.script));
|
||||
final packageRoot = p.dirname(scriptDir);
|
||||
Directory.current = packageRoot;
|
||||
|
||||
try {
|
||||
watch.start();
|
||||
if (args.isNotEmpty) {
|
||||
throw ArgumentError('No command line args are supported');
|
||||
}
|
||||
|
||||
final packagesPath = findNearestPackageConfigPath();
|
||||
final client = await FrontendServerClient.start(
|
||||
'org-dartlang-root:///$app',
|
||||
outputDill,
|
||||
p.join(sdkDir, 'lib', '_internal', 'vm_platform_strong.dill'),
|
||||
packagesJson: packagesPath ?? '.dart_tool/package_config.json',
|
||||
target: 'vm',
|
||||
// Use an absolute filesystem root so org-dartlang-root:/// URIs resolve reliably in CI
|
||||
fileSystemRoots: [Directory.current.path],
|
||||
fileSystemScheme: 'org-dartlang-root',
|
||||
verbose: true,
|
||||
);
|
||||
_print('compiling $app');
|
||||
var result = await client.compile();
|
||||
client.accept();
|
||||
_print('done compiling $app');
|
||||
|
||||
Process appProcess;
|
||||
final vmServiceCompleter = Completer<VmService>();
|
||||
appProcess = await Process.start(Platform.resolvedExecutable, [
|
||||
'--enable-vm-service',
|
||||
result.dillOutput!,
|
||||
]);
|
||||
final sawHelloWorld = Completer<void>();
|
||||
appProcess.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
stdout.writeln('APP -> $line');
|
||||
if (line == 'hello/world') {
|
||||
sawHelloWorld.complete();
|
||||
}
|
||||
if (line.startsWith(
|
||||
'The Dart DevTools debugger and profiler is available at:',
|
||||
)) {
|
||||
final observatoryUri =
|
||||
'${line.split(' ').last.replaceFirst('http', 'ws')}ws';
|
||||
if (!vmServiceCompleter.isCompleted) {
|
||||
vmServiceCompleter.complete(vmServiceConnectUri(observatoryUri));
|
||||
}
|
||||
}
|
||||
});
|
||||
appProcess.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
stderr.writeln('APP -> $line');
|
||||
});
|
||||
|
||||
final vmService = await vmServiceCompleter.future;
|
||||
await sawHelloWorld.future;
|
||||
|
||||
_print('editing $app');
|
||||
final appFile = File(app);
|
||||
final originalContent = await appFile.readAsString();
|
||||
final newContent = originalContent.replaceFirst('hello', 'goodbye');
|
||||
await appFile.writeAsString(newContent);
|
||||
|
||||
_print('recompiling $app with edits');
|
||||
result = await client.compile([Uri.parse('org-dartlang-root:///$app')]);
|
||||
client.accept();
|
||||
_print('done recompiling $app');
|
||||
_print('reloading $app');
|
||||
final vm = await vmService.getVM();
|
||||
await vmService.reloadSources(
|
||||
vm.isolates!.first.id!,
|
||||
rootLibUri: result.dillOutput!,
|
||||
);
|
||||
|
||||
_print('restoring $app to original contents');
|
||||
await appFile.writeAsString(originalContent);
|
||||
_print('exiting');
|
||||
await client.shutdown().timeout(
|
||||
const Duration(seconds: 1),
|
||||
onTimeout: () {
|
||||
client.kill();
|
||||
return 1;
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
Directory(p.join('.dart_tool', 'out')).deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _print(String message) {
|
||||
print('${watch.elapsed}: $message');
|
||||
}
|
||||
|
||||
final app = 'example/app/main.dart';
|
||||
final outputDill = p.join('.dart_tool', 'out', 'example_app.dill');
|
||||
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
|
||||
final watch = Stopwatch();
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2020 The Dart Authors. 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:async/async.dart';
|
||||
import 'package:frontend_server_client/frontend_server_client.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:shelf_packages_handler/shelf_packages_handler.dart';
|
||||
import 'package:shelf_static/shelf_static.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Change to package root so relative paths work in CI
|
||||
final scriptDir = p.dirname(p.fromUri(Platform.script));
|
||||
final packageRoot = p.dirname(scriptDir);
|
||||
Directory.current = packageRoot;
|
||||
|
||||
try {
|
||||
watch.start();
|
||||
if (args.isNotEmpty) {
|
||||
throw ArgumentError('No command line args are supported');
|
||||
}
|
||||
|
||||
_print('compiling the dart sdk');
|
||||
final sdkCompileResult = await Process.run(Platform.resolvedExecutable, [
|
||||
'compile',
|
||||
'js-dev',
|
||||
'--multi-root-scheme=org-dartlang-sdk',
|
||||
'--modules=amd',
|
||||
'--module-name=dart_sdk',
|
||||
'-o',
|
||||
dartSdkJs,
|
||||
p.url.join(sdkDir, sdkKernelPath),
|
||||
]);
|
||||
if (sdkCompileResult.exitCode != 0) {
|
||||
_print(
|
||||
'Failed to compile the dart sdk to JS:\n'
|
||||
'${sdkCompileResult.stdout}\n'
|
||||
'${sdkCompileResult.stderr}',
|
||||
);
|
||||
exit(sdkCompileResult.exitCode);
|
||||
}
|
||||
|
||||
_print('starting frontend server');
|
||||
final packagesPath = findNearestPackageConfigPath();
|
||||
final client = await DartDevcFrontendServerClient.start(
|
||||
'org-dartlang-root:///$app',
|
||||
outputDill,
|
||||
// Use an absolute filesystem root so org-dartlang-root:/// URIs resolve reliably in CI
|
||||
fileSystemRoots: [Directory.current.path],
|
||||
fileSystemScheme: 'org-dartlang-root',
|
||||
platformKernel: p.toUri(sdkKernelPath).toString(),
|
||||
packagesJson: packagesPath ?? '.dart_tool/package_config.json',
|
||||
verbose: true,
|
||||
);
|
||||
|
||||
_print('compiling $app');
|
||||
await client.compile([]);
|
||||
client.accept();
|
||||
_print('done compiling $app');
|
||||
|
||||
_print('starting shelf server');
|
||||
final cascade = Cascade()
|
||||
.add(_clientHandler(client))
|
||||
.add(createStaticHandler(Directory.current.path))
|
||||
.add(createFileHandler(dartSdkJs, url: 'example/app/dart_sdk.js'))
|
||||
.add(
|
||||
createFileHandler(
|
||||
p.join(
|
||||
sdkDir,
|
||||
'lib',
|
||||
'dev_compiler',
|
||||
'web',
|
||||
'dart_stack_trace_mapper.js',
|
||||
),
|
||||
url: 'example/app/dart_stack_trace_mapper.js',
|
||||
),
|
||||
)
|
||||
.add(
|
||||
createFileHandler(
|
||||
p.join(sdkDir, 'lib', 'dev_compiler', 'amd', 'require.js'),
|
||||
url: 'example/app/require.js',
|
||||
),
|
||||
)
|
||||
.add(packagesDirHandler());
|
||||
final server = await shelf_io.serve(cascade.handler, 'localhost', 8080);
|
||||
_print('server ready');
|
||||
|
||||
// The file we will be editing in the repl
|
||||
final appFile = File(app);
|
||||
final originalContent = await appFile.readAsString();
|
||||
final appLines = const LineSplitter().convert(originalContent);
|
||||
final getterText = 'String get message =>';
|
||||
final messageLine = appLines.indexWhere(
|
||||
(line) => line.startsWith(getterText),
|
||||
);
|
||||
|
||||
final stdinQueue = StreamQueue(
|
||||
stdin.transform(utf8.decoder).transform(const LineSplitter()),
|
||||
);
|
||||
_prompt();
|
||||
while (await stdinQueue.hasNext) {
|
||||
final newMessage = await stdinQueue.next;
|
||||
if (newMessage == 'quit') {
|
||||
await server.close();
|
||||
await stdinQueue.cancel();
|
||||
break;
|
||||
} else if (newMessage == 'reset') {
|
||||
print('resetting');
|
||||
client.reset();
|
||||
_print('restoring $app');
|
||||
await appFile.writeAsString(originalContent);
|
||||
} else {
|
||||
_print('editing $app');
|
||||
appLines[messageLine] = '$getterText "$newMessage";';
|
||||
final newContent = appLines.join('\n');
|
||||
await appFile.writeAsString(newContent);
|
||||
|
||||
_print('recompiling $app with edits');
|
||||
final result = await client.compile([
|
||||
Uri.parse('org-dartlang-root:///$app'),
|
||||
]);
|
||||
if (result.errorCount > 0) {
|
||||
print('Compile errors: \n${result.compilerOutputLines.join('\n')}');
|
||||
await client.reject();
|
||||
} else {
|
||||
_print('Recompile succeeded for $app');
|
||||
client.accept();
|
||||
// TODO: support hot restart
|
||||
print('reload app to see the new message');
|
||||
}
|
||||
}
|
||||
|
||||
_prompt();
|
||||
}
|
||||
|
||||
_print('restoring $app');
|
||||
await appFile.writeAsString(originalContent);
|
||||
_print('exiting');
|
||||
await client.shutdown();
|
||||
} finally {
|
||||
Directory(p.join('.dart_tool', 'out')).deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
Handler _clientHandler(DartDevcFrontendServerClient client) {
|
||||
return (Request request) {
|
||||
var path = request.url.path;
|
||||
final packagesIndex = path.indexOf('/packages/');
|
||||
if (packagesIndex > 0) {
|
||||
path = request.url.path.substring(packagesIndex);
|
||||
} else {
|
||||
path = request.url.path;
|
||||
}
|
||||
if (!path.startsWith('/')) path = '/$path';
|
||||
if (path.endsWith('.dart.js') && path != '/example/app/main.dart.js') {
|
||||
path = path.replaceFirst('.dart.js', '.dart.lib.js', path.length - 8);
|
||||
}
|
||||
final assetBytes = client.assetBytes(path);
|
||||
if (assetBytes == null) return Response.notFound('path not found');
|
||||
return Response.ok(
|
||||
assetBytes,
|
||||
headers: {HttpHeaders.contentTypeHeader: 'application/javascript'},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
void _print(String message) {
|
||||
print('${watch.elapsed}: $message');
|
||||
}
|
||||
|
||||
void _prompt() => stdout.write(
|
||||
'Enter a new message to print and recompile, or type `quit` to exit:',
|
||||
);
|
||||
|
||||
final app = 'example/app/main.dart';
|
||||
final dartSdkJs = p.join('.dart_tool', 'out', 'dart_sdk.js');
|
||||
final outputDill = p.join('.dart_tool', 'out', 'example_app.dill');
|
||||
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
|
||||
final sdkKernelPath = p.join(sdkDir, 'lib', '_internal', 'ddc_platform.dill');
|
||||
final watch = Stopwatch();
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
export 'src/dartdevc_frontend_server_client.dart'
|
||||
show DartDevcFrontendServerClient;
|
||||
export 'src/frontend_server_client.dart'
|
||||
show CompileResult, FrontendServerClient;
|
||||
export 'src/package_config_utils.dart' show findNearestPackageConfigPath;
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// The JavaScript bootstrap script to support in-browser hot restart.
|
||||
///
|
||||
/// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl]
|
||||
/// loads the special Dart stack trace mapper.
|
||||
///
|
||||
/// This file is served when the browser requests `$entrypoint.js` in debug
|
||||
/// mode, and is responsible for bootstrapping the RequireJS modules and
|
||||
/// attaching the hot reload hooks.
|
||||
String generateAmdBootstrapScript({
|
||||
required String requireUrl,
|
||||
required String mapperUrl,
|
||||
required String entrypoint,
|
||||
}) {
|
||||
return '''
|
||||
"use strict";
|
||||
|
||||
// Attach source mapping.
|
||||
var mapperEl = document.createElement("script");
|
||||
mapperEl.defer = true;
|
||||
mapperEl.async = false;
|
||||
mapperEl.src = "$mapperUrl";
|
||||
document.head.appendChild(mapperEl);
|
||||
|
||||
// Attach require JS.
|
||||
var requireEl = document.createElement("script");
|
||||
requireEl.defer = true;
|
||||
requireEl.async = false;
|
||||
requireEl.src = "$requireUrl";
|
||||
|
||||
// This attribute tells require JS what to load as main (defined below).
|
||||
requireEl.setAttribute("data-main", "$entrypoint.bootstrap");
|
||||
document.head.appendChild(requireEl);
|
||||
''';
|
||||
}
|
||||
|
||||
/// Generate a synthetic main module which captures the application's main
|
||||
/// method.
|
||||
///
|
||||
/// RE: Object.keys usage in app.main:
|
||||
/// This attaches the main entrypoint and hot reload functionality to the
|
||||
/// window. The app module will have a single property which contains the
|
||||
/// actual application code. The property name is based off of the entrypoint
|
||||
/// that is generated, for example the file `foo/bar/baz.dart` will generate
|
||||
/// a property named approximately `foo__bar__baz`. Rather than attempt to
|
||||
/// guess, we assume the first property of this object is the module.
|
||||
String generateAmdMainModule({required String entrypoint}) {
|
||||
return '''/* ENTRYPOINT_EXTENTION_MARKER */
|
||||
// Create the main module loaded below.
|
||||
require(["$entrypoint.lib.js", "dart_sdk"], function(app, dart_sdk) {
|
||||
dart_sdk.dart.setStartAsyncSynchronously(true);
|
||||
dart_sdk._debugger.registerDevtoolsFormatter();
|
||||
|
||||
// See the generateMainModule doc comment.
|
||||
app[Object.keys(app)[0]].main();
|
||||
});
|
||||
''';
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// Copyright 2020 The Dart Authors. 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 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'dartdevc_bootstrap_amd.dart';
|
||||
import 'frontend_server_client.dart';
|
||||
import 'shared.dart';
|
||||
|
||||
/// Wraps a [FrontendServerClient] with opinionated web specific functionality,
|
||||
/// and provides some typical defaults.
|
||||
///
|
||||
/// Loads into memory the [CompileResult]s from each [compile] call, and
|
||||
/// provides access to the up to date sources and source maps.
|
||||
///
|
||||
/// Also has the ability to create a bootstrap file for the current entrypoint.
|
||||
class DartDevcFrontendServerClient implements FrontendServerClient {
|
||||
final FrontendServerClient _frontendServerClient;
|
||||
|
||||
final _assets = <String, Uint8List>{};
|
||||
final String _entrypoint;
|
||||
|
||||
/// The last compile, or `null` once it has been accepted or rejected.
|
||||
CompileResult? _lastResult;
|
||||
|
||||
/// The bootstrap js contents, provided in [_assets] at
|
||||
/// the path `${_entrypointModule}.js`.
|
||||
///
|
||||
/// This is `null` if the module format is not supported for bootstrapping.
|
||||
final String? _bootstrapJs;
|
||||
|
||||
/// The generated main module js contents, provided in [_assets] at
|
||||
/// the path `${_entrypointModule}.bootstrap.js`.
|
||||
///
|
||||
/// This is `null` if the module format is not supported for bootstrapping.
|
||||
final String? _mainModuleJs;
|
||||
|
||||
DartDevcFrontendServerClient._(
|
||||
this._frontendServerClient, this._entrypoint, String moduleFormat)
|
||||
: _bootstrapJs = moduleFormat == 'amd'
|
||||
? generateAmdBootstrapScript(
|
||||
requireUrl: 'require.js',
|
||||
mapperUrl: 'dart_stack_trace_mapper.js',
|
||||
entrypoint: _entrypoint)
|
||||
: null,
|
||||
_mainModuleJs = moduleFormat == 'amd'
|
||||
? generateAmdMainModule(entrypoint: _entrypoint)
|
||||
: null {
|
||||
_resetAssets();
|
||||
}
|
||||
|
||||
static Future<DartDevcFrontendServerClient> start(
|
||||
String entrypoint,
|
||||
String outputDillPath, {
|
||||
String dartdevcModuleFormat = 'amd',
|
||||
bool debug = false,
|
||||
bool enableHttpUris = false,
|
||||
List<String> fileSystemRoots = const [], // For `fileSystemScheme` uris,
|
||||
String fileSystemScheme =
|
||||
'org-dartlang-root', // Custom scheme for virtual `fileSystemRoots`.
|
||||
String? frontendServerPath, // Defaults to the snapshot in the sdk.
|
||||
String packagesJson = '.dart_tool/package_config.json',
|
||||
String? platformKernel, // Defaults to the dartdevc platform from the sdk.
|
||||
String? sdkRoot, // Defaults to the current SDK root.
|
||||
bool verbose = false,
|
||||
bool printIncrementalDependencies = true,
|
||||
}) async {
|
||||
final feServer = await FrontendServerClient.start(
|
||||
entrypoint,
|
||||
outputDillPath,
|
||||
platformKernel ?? _dartdevcPlatformKernel,
|
||||
dartdevcModuleFormat: dartdevcModuleFormat,
|
||||
debug: debug,
|
||||
enableHttpUris: enableHttpUris,
|
||||
fileSystemRoots: fileSystemRoots,
|
||||
fileSystemScheme: fileSystemScheme,
|
||||
frontendServerPath: frontendServerPath,
|
||||
packagesJson: packagesJson,
|
||||
sdkRoot: sdkRoot,
|
||||
target: 'dartdevc',
|
||||
verbose: verbose,
|
||||
);
|
||||
return DartDevcFrontendServerClient._(
|
||||
feServer, Uri.parse(entrypoint).path, dartdevcModuleFormat);
|
||||
}
|
||||
|
||||
/// Returns the current bytes for the asset at [path].
|
||||
///
|
||||
/// The [path] should be exactly as it appears in the
|
||||
/// [CompileResult.jsManifestOutput] file.
|
||||
///
|
||||
/// **Note**: Assets are not updated until `accept` is called after a
|
||||
/// successful compile. They are not updated if `reject` is called.
|
||||
///
|
||||
/// Returns `null` if no asset exists at [path].
|
||||
///
|
||||
/// In addition to any DDC compiled assets, this serves
|
||||
Uint8List? assetBytes(String path) => _assets[path];
|
||||
|
||||
/// The contents of a JS file capable of bootstrapping the current app.
|
||||
///
|
||||
/// TODO: implement
|
||||
String bootstrapJs() => throw UnimplementedError();
|
||||
|
||||
/// Updates [_assets] for [result].
|
||||
void _updateAssets(CompileResult result) {
|
||||
if (result.dillOutput == null) {
|
||||
return;
|
||||
}
|
||||
final manifest =
|
||||
jsonDecode(File(result.jsManifestOutput!).readAsStringSync())
|
||||
as Map<String, dynamic>;
|
||||
final sourceBytes = File(result.jsSourcesOutput!).readAsBytesSync();
|
||||
final sourceMapBytes = File(result.jsSourceMapsOutput!).readAsBytesSync();
|
||||
|
||||
for (final entry in manifest.entries) {
|
||||
final metadata = entry.value as Map<String, dynamic>;
|
||||
final sourceOffsets = metadata['code'] as List;
|
||||
_assets[entry.key] =
|
||||
sourceBytes.sublist(sourceOffsets[0] as int, sourceOffsets[1] as int);
|
||||
final sourceMapOffsets = metadata['sourcemap'] as List;
|
||||
_assets['${entry.key}.map'] = sourceMapBytes.sublist(
|
||||
sourceMapOffsets[0] as int, sourceMapOffsets[1] as int);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CompileResult> compile([List<Uri>? invalidatedUris]) async {
|
||||
return _lastResult = await _frontendServerClient.compile(invalidatedUris);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CompileResult> compileExpression({
|
||||
required String expression,
|
||||
required List<String> definitions,
|
||||
required bool isStatic,
|
||||
required String klass,
|
||||
required String libraryUri,
|
||||
required List<String> typeDefinitions,
|
||||
}) =>
|
||||
throw UnsupportedError(
|
||||
'Use `compileExpressionToJs` for dartdevc based clients');
|
||||
|
||||
@override
|
||||
Future<CompileResult> compileExpressionToJs({
|
||||
required String expression,
|
||||
required int column,
|
||||
required Map<String, String> jsFrameValues,
|
||||
required Map<String, String> jsModules,
|
||||
required String libraryUri,
|
||||
required int line,
|
||||
required String moduleName,
|
||||
}) =>
|
||||
_frontendServerClient.compileExpressionToJs(
|
||||
expression: expression,
|
||||
column: column,
|
||||
jsFrameValues: jsFrameValues,
|
||||
jsModules: jsModules,
|
||||
libraryUri: libraryUri,
|
||||
line: line,
|
||||
moduleName: moduleName);
|
||||
|
||||
@override
|
||||
void accept() {
|
||||
_frontendServerClient.accept();
|
||||
if (_lastResult != null) _updateAssets(_lastResult!);
|
||||
_lastResult = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reject() async {
|
||||
await _frontendServerClient.reject();
|
||||
_lastResult = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_frontendServerClient.reset();
|
||||
_resetAssets();
|
||||
}
|
||||
|
||||
@override
|
||||
bool kill({ProcessSignal processSignal = ProcessSignal.sigterm}) =>
|
||||
_frontendServerClient.kill();
|
||||
|
||||
@override
|
||||
Future<int> shutdown() => _frontendServerClient.shutdown();
|
||||
|
||||
/// Clears any previously compiled assets and adds the bootstrap modules as
|
||||
/// assets if available.
|
||||
void _resetAssets() {
|
||||
_assets.clear();
|
||||
final bootstrapJs = _bootstrapJs;
|
||||
if (bootstrapJs != null) {
|
||||
_assets['$_entrypoint.js'] = Uint8List.fromList(utf8.encode(bootstrapJs));
|
||||
}
|
||||
final mainModuleJs = _mainModuleJs;
|
||||
if (mainModuleJs != null) {
|
||||
_assets['$_entrypoint.bootstrap.js'] =
|
||||
Uint8List.fromList(utf8.encode(mainModuleJs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final _dartdevcPlatformKernel =
|
||||
p.toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_sdk.dill')).toString();
|
||||
@@ -0,0 +1,455 @@
|
||||
// Copyright 2020 The Dart Authors. 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:async/async.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'shared.dart';
|
||||
|
||||
/// Wrapper around the incremental frontend server compiler.
|
||||
class FrontendServerClient {
|
||||
final String _entrypoint;
|
||||
final Process _feServer;
|
||||
final StreamQueue<String> _feServerStdoutLines;
|
||||
final bool _verbose;
|
||||
|
||||
_ClientState _state;
|
||||
|
||||
FrontendServerClient._(
|
||||
this._entrypoint, this._feServer, this._feServerStdoutLines,
|
||||
{bool? verbose})
|
||||
: _verbose = verbose ?? false,
|
||||
_state = _ClientState.waitingForFirstCompile {
|
||||
_feServer.stderr.transform(utf8.decoder).listen(stderr.write);
|
||||
}
|
||||
|
||||
/// Starts the frontend server.
|
||||
///
|
||||
/// Most arguments directly mirror the command line arguments for the
|
||||
/// frontend_server (see `pkg/frontend_server/lib/frontend_server.dart` in
|
||||
/// the sdk). Options are exposed on an as-needed basis.
|
||||
///
|
||||
/// The [entrypoint] and [packagesJson] may be a relative path or any uri
|
||||
/// supported by the frontend server.
|
||||
///
|
||||
/// The [outputDillPath] determines where the primary output should be, and
|
||||
/// some targets may output additional files based on that file name (by
|
||||
/// adding file extensions for instance).
|
||||
///
|
||||
/// When the [frontendServerPath] argument is provided, the frontend server
|
||||
/// will be started from the specified file. The specified file can either be
|
||||
/// a Dart source file or an AppJIT snapshot.
|
||||
///
|
||||
/// When the [frontendServerPath] argument is provided, setting [debug] to
|
||||
/// true permits debuggers to attach to the frontend server. When the
|
||||
/// [frontendServerPath] argument is omitted, setting [debug] to true will
|
||||
/// cause an [ArgumentError] to be thrown.
|
||||
static Future<FrontendServerClient> start(
|
||||
String entrypoint,
|
||||
String outputDillPath,
|
||||
String platformKernel, {
|
||||
String dartdevcModuleFormat = 'amd',
|
||||
bool debug = false,
|
||||
List<String>? enabledExperiments,
|
||||
bool enableHttpUris = false,
|
||||
List<String> fileSystemRoots = const [], // For `fileSystemScheme` uris,
|
||||
String fileSystemScheme =
|
||||
'org-dartlang-root', // Custom scheme for virtual `fileSystemRoots`.
|
||||
String? frontendServerPath, // Defaults to the snapshot in the sdk.
|
||||
String packagesJson = '.dart_tool/package_config.json',
|
||||
String? sdkRoot, // Defaults to the current SDK root.
|
||||
String target = 'vm', // The kernel target type.
|
||||
bool verbose = false, // Verbose logs, including server/client messages
|
||||
bool printIncrementalDependencies = true,
|
||||
List<String> additionalSources = const [],
|
||||
String? nativeAssets,
|
||||
}) async {
|
||||
final commonArguments = <String>[
|
||||
'--sdk-root',
|
||||
sdkRoot ?? sdkDir,
|
||||
'--platform=$platformKernel',
|
||||
'--target=$target',
|
||||
if (target == 'dartdevc')
|
||||
'--dartdevc-module-format=$dartdevcModuleFormat',
|
||||
for (final root in fileSystemRoots) '--filesystem-root=$root',
|
||||
'--filesystem-scheme',
|
||||
fileSystemScheme,
|
||||
'--output-dill',
|
||||
outputDillPath,
|
||||
'--packages=$packagesJson',
|
||||
if (enableHttpUris) '--enable-http-uris',
|
||||
'--incremental',
|
||||
if (verbose) '--verbose',
|
||||
if (!printIncrementalDependencies) '--no-print-incremental-dependencies',
|
||||
if (enabledExperiments != null)
|
||||
for (final experiment in enabledExperiments)
|
||||
'--enable-experiment=$experiment',
|
||||
for (final source in additionalSources) ...[
|
||||
'--source',
|
||||
source,
|
||||
],
|
||||
if (nativeAssets != null) ...[
|
||||
'--native-assets',
|
||||
nativeAssets,
|
||||
],
|
||||
];
|
||||
late final Process feServer;
|
||||
if (frontendServerPath != null) {
|
||||
feServer = await Process.start(
|
||||
Platform.resolvedExecutable,
|
||||
<String>[
|
||||
if (debug) '--observe',
|
||||
frontendServerPath,
|
||||
...commonArguments,
|
||||
],
|
||||
);
|
||||
} else if (File(_feServerAotSnapshotPath).existsSync()) {
|
||||
if (debug) {
|
||||
throw ArgumentError('The debug argument cannot be set to true when the '
|
||||
'frontendServerPath argument is omitted.');
|
||||
}
|
||||
feServer = await Process.start(
|
||||
_dartAotRuntimePath,
|
||||
<String>[_feServerAotSnapshotPath, ...commonArguments],
|
||||
);
|
||||
} else {
|
||||
// AOT snapshots cannot be generated on IA32, so we need this fallback
|
||||
// branch until support for IA32 is dropped (https://dartbug.com/49969).
|
||||
feServer = await Process.start(
|
||||
Platform.resolvedExecutable,
|
||||
<String>[
|
||||
if (debug) '--observe',
|
||||
_feServerAppJitSnapshotPath,
|
||||
...commonArguments,
|
||||
],
|
||||
);
|
||||
}
|
||||
final feServerStdoutLines = StreamQueue(feServer.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter()));
|
||||
|
||||
// The frontend_server doesn't appear to recursively create files, so we
|
||||
// need to make sure the output dir already exists.
|
||||
final outputDir = Directory(p.dirname(outputDillPath));
|
||||
if (!await outputDir.exists()) await outputDir.create();
|
||||
|
||||
return FrontendServerClient._(
|
||||
entrypoint,
|
||||
feServer,
|
||||
feServerStdoutLines,
|
||||
verbose: verbose,
|
||||
);
|
||||
}
|
||||
|
||||
/// Compiles [_entrypoint], using an incremental recompile if possible.
|
||||
///
|
||||
/// [invalidatedUris] must not be null for all but the very first compile.
|
||||
///
|
||||
/// The frontend server _does not_ do any of its own invalidation.
|
||||
Future<CompileResult> compile([List<Uri>? invalidatedUris]) async {
|
||||
String action;
|
||||
switch (_state) {
|
||||
case _ClientState.waitingForFirstCompile:
|
||||
action = 'compile';
|
||||
break;
|
||||
case _ClientState.waitingForRecompile:
|
||||
action = 'recompile';
|
||||
break;
|
||||
case _ClientState.waitingForAcceptOrReject:
|
||||
throw StateError(
|
||||
'Previous `CompileResult` must be accepted or rejected by '
|
||||
'calling `accept` or `reject`.');
|
||||
case _ClientState.compiling:
|
||||
throw StateError(
|
||||
'App is already being compiled, you must wait for that to '
|
||||
'complete and `accept` or `reject` the result before compiling '
|
||||
'again.');
|
||||
case _ClientState.rejecting:
|
||||
throw StateError('Still waiting for previous `reject` call to finish. '
|
||||
'You must await that before compiling again.');
|
||||
}
|
||||
_state = _ClientState.compiling;
|
||||
|
||||
try {
|
||||
final command = StringBuffer('$action $_entrypoint');
|
||||
if (action == 'recompile') {
|
||||
if (invalidatedUris == null || invalidatedUris.isEmpty) {
|
||||
throw StateError(
|
||||
'Subsequent compile invocations must provide a non-empty list '
|
||||
'of invalidated uris.');
|
||||
}
|
||||
final boundaryKey = generateUuidV4();
|
||||
command.writeln(' $boundaryKey');
|
||||
for (final uri in invalidatedUris) {
|
||||
command.writeln('$uri');
|
||||
}
|
||||
command.write(boundaryKey);
|
||||
}
|
||||
|
||||
_sendCommand(command.toString());
|
||||
var state = _CompileState.started;
|
||||
late String feBoundaryKey;
|
||||
final newSources = <Uri>{};
|
||||
final removedSources = <Uri>{};
|
||||
final compilerOutputLines = <String>[];
|
||||
var errorCount = 0;
|
||||
String? outputDillPath;
|
||||
while (
|
||||
state != _CompileState.done && await _feServerStdoutLines.hasNext) {
|
||||
final line = await _nextInputLine();
|
||||
switch (state) {
|
||||
case _CompileState.started:
|
||||
assert(line.startsWith('result'));
|
||||
feBoundaryKey = line.substring(line.indexOf(' ') + 1);
|
||||
state = _CompileState.waitingForKey;
|
||||
continue;
|
||||
case _CompileState.waitingForKey:
|
||||
if (line == feBoundaryKey) {
|
||||
state = _CompileState.gettingSourceDiffs;
|
||||
} else {
|
||||
compilerOutputLines.add(line);
|
||||
}
|
||||
continue;
|
||||
case _CompileState.gettingSourceDiffs:
|
||||
if (line.startsWith(feBoundaryKey)) {
|
||||
state = _CompileState.done;
|
||||
final parts = line.split(' ');
|
||||
outputDillPath = parts.getRange(1, parts.length - 1).join(' ');
|
||||
errorCount = int.parse(parts.last);
|
||||
continue;
|
||||
}
|
||||
final diffUri = Uri.parse(line.substring(1));
|
||||
if (line.startsWith('+')) {
|
||||
newSources.add(diffUri);
|
||||
} else if (line.startsWith('-')) {
|
||||
removedSources.add(diffUri);
|
||||
} else {
|
||||
throw StateError(
|
||||
'unrecognized diff line, should start with a + or - '
|
||||
'but got: $line');
|
||||
}
|
||||
continue;
|
||||
case _CompileState.done:
|
||||
throw StateError('Unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
return CompileResult._(
|
||||
dillOutput: outputDillPath,
|
||||
errorCount: errorCount,
|
||||
newSources: newSources,
|
||||
removedSources: removedSources,
|
||||
compilerOutputLines: compilerOutputLines);
|
||||
} finally {
|
||||
_state = _ClientState.waitingForAcceptOrReject;
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Document
|
||||
Future<CompileResult> compileExpression({
|
||||
required String expression,
|
||||
required List<String> definitions,
|
||||
required bool isStatic,
|
||||
required String klass,
|
||||
required String libraryUri,
|
||||
required List<String> typeDefinitions,
|
||||
}) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
/// TODO: Document
|
||||
Future<CompileResult> compileExpressionToJs({
|
||||
required String expression,
|
||||
required int column,
|
||||
required Map<String, String> jsFrameValues,
|
||||
required Map<String, String> jsModules,
|
||||
required String libraryUri,
|
||||
required int line,
|
||||
required String moduleName,
|
||||
}) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
/// Should be invoked when results of compilation are accepted by the client.
|
||||
///
|
||||
/// Either [accept] or [reject] should be called after every [compile] call.
|
||||
void accept() {
|
||||
if (_state != _ClientState.waitingForAcceptOrReject) {
|
||||
throw StateError(
|
||||
'Called `accept` but there was no previous compile to accept.');
|
||||
}
|
||||
_sendCommand('accept');
|
||||
_state = _ClientState.waitingForRecompile;
|
||||
}
|
||||
|
||||
/// Should be invoked when results of compilation are rejected by the client.
|
||||
///
|
||||
/// Either [accept] or [reject] should be called after every [compile] call.
|
||||
///
|
||||
/// The result of this call must be awaited before a new [compile] can be
|
||||
/// done.
|
||||
Future<void> reject() async {
|
||||
if (_state != _ClientState.waitingForAcceptOrReject) {
|
||||
throw StateError(
|
||||
'Called `reject` but there was no previous compile to reject.');
|
||||
}
|
||||
_state = _ClientState.rejecting;
|
||||
_sendCommand('reject');
|
||||
late String boundaryKey;
|
||||
var rejectState = _RejectState.started;
|
||||
while (rejectState != _RejectState.done &&
|
||||
await _feServerStdoutLines.hasNext) {
|
||||
final line = await _nextInputLine();
|
||||
switch (rejectState) {
|
||||
case _RejectState.started:
|
||||
if (!line.startsWith('result')) {
|
||||
throw StateError(
|
||||
'Expected a line like `result <boundary-key>` after a `reject` '
|
||||
'command, but got:\n$line');
|
||||
}
|
||||
boundaryKey = line.split(' ').last;
|
||||
rejectState = _RejectState.waitingForKey;
|
||||
continue;
|
||||
case _RejectState.waitingForKey:
|
||||
if (line != boundaryKey) {
|
||||
throw StateError('Expected exactly `$boundaryKey` but got:\n$line');
|
||||
}
|
||||
rejectState = _RejectState.done;
|
||||
continue;
|
||||
case _RejectState.done:
|
||||
throw StateError('Unreachable');
|
||||
}
|
||||
}
|
||||
_state = _ClientState.waitingForRecompile;
|
||||
}
|
||||
|
||||
/// Should be invoked when frontend server compiler should forget what was
|
||||
/// accepted previously so that next call to [compile] produces complete
|
||||
/// kernel file.
|
||||
void reset() {
|
||||
if (_state == _ClientState.compiling) {
|
||||
throw StateError(
|
||||
'Called `reset` during an active compile, you must wait for that to '
|
||||
'complete first.');
|
||||
}
|
||||
_sendCommand('reset');
|
||||
_state = _ClientState.waitingForRecompile;
|
||||
}
|
||||
|
||||
/// Stop the service gracefully (using the shutdown command)
|
||||
Future<int> shutdown() async {
|
||||
_sendCommand('quit');
|
||||
final timer = Timer(const Duration(seconds: 1), _feServer.kill);
|
||||
final exitCode = await _feServer.exitCode;
|
||||
timer.cancel();
|
||||
await _feServerStdoutLines.cancel();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/// Kills the server forcefully by calling `kill` on the process, and
|
||||
/// returns the result.
|
||||
bool kill({ProcessSignal processSignal = ProcessSignal.sigterm}) {
|
||||
_feServerStdoutLines.cancel();
|
||||
return _feServer.kill(processSignal);
|
||||
}
|
||||
|
||||
/// Sends [command] to the [_feServer] via stdin, and logs it if [_verbose].
|
||||
void _sendCommand(String command) {
|
||||
if (_verbose) {
|
||||
final lines = const LineSplitter().convert(command);
|
||||
for (final line in lines) {
|
||||
print('>> $line');
|
||||
}
|
||||
}
|
||||
_feServer.stdin.writeln(command);
|
||||
}
|
||||
|
||||
/// Reads a line from [_feServerStdoutLines] and logs it if [_verbose].
|
||||
Future<String> _nextInputLine() async {
|
||||
final line = await _feServerStdoutLines.next;
|
||||
if (_verbose) print('<< $line');
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a compile call.
|
||||
class CompileResult {
|
||||
const CompileResult._(
|
||||
{required this.dillOutput,
|
||||
required this.compilerOutputLines,
|
||||
required this.errorCount,
|
||||
required this.newSources,
|
||||
required this.removedSources});
|
||||
|
||||
/// The produced dill output file, this will either be a full dill file, an
|
||||
/// incremental dill file, or `null` if no file was produced.
|
||||
final String? dillOutput;
|
||||
|
||||
/// All output from the compiler, typically this would contain errors or
|
||||
/// warnings.
|
||||
final Iterable<String> compilerOutputLines;
|
||||
|
||||
/// The total count of errors, details should appear in
|
||||
/// [compilerOutputLines].
|
||||
final int errorCount;
|
||||
|
||||
/// A single file containing all source maps for all JS outputs.
|
||||
///
|
||||
/// Read [jsManifestOutput] for file offsets for each sourcemap.
|
||||
String? get jsSourceMapsOutput =>
|
||||
dillOutput == null ? null : '$dillOutput.map';
|
||||
|
||||
/// A single file containing all JS outputs.
|
||||
///
|
||||
/// Read [jsManifestOutput] for file offsets for each source.
|
||||
String? get jsSourcesOutput =>
|
||||
dillOutput == null ? null : '$dillOutput.sources';
|
||||
|
||||
/// A JSON manifest containing offsets for the sources and source maps in
|
||||
/// the [jsSourcesOutput] and [jsSourceMapsOutput] files.
|
||||
String? get jsManifestOutput =>
|
||||
dillOutput == null ? null : '$dillOutput.json';
|
||||
|
||||
/// All the transitive source dependencies that were added as a part of this
|
||||
/// compile.
|
||||
final Iterable<Uri> newSources;
|
||||
|
||||
/// All the transitive source dependencies that were removed as a part of
|
||||
/// this compile.
|
||||
final Iterable<Uri> removedSources;
|
||||
}
|
||||
|
||||
/// Internal states for the client.
|
||||
enum _ClientState {
|
||||
compiling,
|
||||
rejecting,
|
||||
waitingForAcceptOrReject,
|
||||
waitingForFirstCompile,
|
||||
waitingForRecompile,
|
||||
}
|
||||
|
||||
/// Frontend server interaction states for a `compile` call.
|
||||
enum _CompileState {
|
||||
started,
|
||||
waitingForKey,
|
||||
gettingSourceDiffs,
|
||||
done,
|
||||
}
|
||||
|
||||
/// Frontend server interaction states for a `reject` call.
|
||||
enum _RejectState {
|
||||
started,
|
||||
waitingForKey,
|
||||
done,
|
||||
}
|
||||
|
||||
final _dartAotRuntimePath = p.join(sdkDir, 'bin', 'dartaotruntime');
|
||||
|
||||
final _feServerAppJitSnapshotPath =
|
||||
p.join(sdkDir, 'bin', 'snapshots', 'frontend_server.dart.snapshot');
|
||||
|
||||
final _feServerAotSnapshotPath =
|
||||
p.join(sdkDir, 'bin', 'snapshots', 'frontend_server_aot.dart.snapshot');
|
||||
@@ -0,0 +1,46 @@
|
||||
// Utility functions to locate a package_config.json for pub/workspace setups.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// Walks up from [start] (or the current directory if omitted) to find the
|
||||
/// nearest `.dart_tool/package_config.json`.
|
||||
///
|
||||
/// Returns the absolute file path, or `null` if none is found.
|
||||
String? findNearestPackageConfigPath([Directory? start]) {
|
||||
var dir = (start ?? Directory.current).absolute;
|
||||
while (true) {
|
||||
final file = File(p.join(dir.path, '.dart_tool', 'package_config.json'));
|
||||
if (file.existsSync()) return file.path;
|
||||
final parent = dir.parent;
|
||||
if (parent.path == dir.path) return null;
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an absolute path under the given [packageName]'s root directory,
|
||||
/// resolving using the nearest workspace `.dart_tool/package_config.json`.
|
||||
///
|
||||
/// This is robust for pub workspace monorepos where the nearest package
|
||||
/// config lives at the repo root and contains individual entries for each
|
||||
/// package with its own root.
|
||||
Future<String> pathFromNearestPackageConfig(
|
||||
String relativePath, {
|
||||
String packageName = 'frontend_server_client',
|
||||
}) async {
|
||||
final configPath = findNearestPackageConfigPath();
|
||||
if (configPath == null) {
|
||||
throw StateError('Could not locate .dart_tool/package_config.json');
|
||||
}
|
||||
final config = await loadPackageConfigUri(Uri.file(configPath));
|
||||
final pkg = config.packages.firstWhere(
|
||||
(p0) => p0.name == packageName,
|
||||
orElse: () => throw StateError(
|
||||
'Package $packageName not found in package config at $configPath',
|
||||
),
|
||||
);
|
||||
final packageRootDir = p.fromUri(pkg.root);
|
||||
return p.normalize(p.join(packageRootDir, relativePath));
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020 The Dart Authors. 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:io';
|
||||
import 'dart:math' show Random;
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
|
||||
final sdkUri = p.toUri(sdkDir).toString();
|
||||
|
||||
/// Returns a unique ID in the format:
|
||||
///
|
||||
/// f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||
///
|
||||
/// The generated uuids are 128 bit numbers encoded in a specific string format.
|
||||
/// For more information, see
|
||||
/// [en.wikipedia.org/wiki/Universally_unique_identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier).
|
||||
String generateUuidV4() {
|
||||
final random = Random();
|
||||
|
||||
int generateBits(int bitCount) => random.nextInt(1 << bitCount);
|
||||
|
||||
String printDigits(int value, int count) =>
|
||||
value.toRadixString(16).padLeft(count, '0');
|
||||
String bitsDigits(int bitCount, int digitCount) =>
|
||||
printDigits(generateBits(bitCount), digitCount);
|
||||
|
||||
// Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
|
||||
final special = 8 + random.nextInt(4);
|
||||
|
||||
return '${bitsDigits(16, 4)}${bitsDigits(16, 4)}-'
|
||||
'${bitsDigits(16, 4)}-'
|
||||
'4${bitsDigits(12, 3)}-'
|
||||
'${printDigits(special, 1)}${bitsDigits(12, 3)}-'
|
||||
'${bitsDigits(16, 4)}${bitsDigits(16, 4)}${bitsDigits(16, 4)}';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
name: frontend_server_client
|
||||
version: 4.0.0
|
||||
description: >-
|
||||
Client code to start and interact with the frontend_server compiler from the
|
||||
Dart SDK.
|
||||
repository: https://github.com/dart-lang/webdev/tree/main/frontend_server_client
|
||||
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
async: ^2.5.0
|
||||
package_config: ^2.1.0
|
||||
path: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
dart_flutter_team_lints: any
|
||||
shelf: any
|
||||
shelf_packages_handler: any
|
||||
shelf_static: any
|
||||
test: any
|
||||
test_descriptor: any
|
||||
test_process: any
|
||||
vm_service: any
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO: The examples don't work on windows
|
||||
@TestOn('!windows')
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:frontend_server_client/src/package_config_utils.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_process/test_process.dart';
|
||||
|
||||
void main() {
|
||||
test('vm client example can build and rebuild an app', () async {
|
||||
// Resolve the example script path based on the package root.
|
||||
final exampleFilePath = await pathFromNearestPackageConfig(
|
||||
'example/vm_client.dart',
|
||||
);
|
||||
final process = await TestProcess.start(
|
||||
Platform.resolvedExecutable, ['run', exampleFilePath]);
|
||||
await expectLater(process.stdout,
|
||||
emitsThrough(contains('done compiling example/app/main.dart')));
|
||||
await expectLater(
|
||||
process.stdout, emitsThrough(contains('APP -> hello/world')));
|
||||
await expectLater(process.stdout,
|
||||
emitsThrough(contains('done recompiling example/app/main.dart')));
|
||||
await expectLater(
|
||||
process.stdout, emitsThrough(contains('APP -> goodbye/world')));
|
||||
expect(await process.exitCode, 0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO: The examples don't work on windows
|
||||
@TestOn('!windows')
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:frontend_server_client/src/package_config_utils.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_process/test_process.dart';
|
||||
|
||||
void main() {
|
||||
test('web client example can build and rebuild an app', () async {
|
||||
// Resolve the example script path based on the package root.
|
||||
final exampleFilePath = await pathFromNearestPackageConfig(
|
||||
'example/web_client.dart',
|
||||
);
|
||||
final process = await TestProcess.start(
|
||||
Platform.resolvedExecutable, ['run', exampleFilePath]);
|
||||
await expectLater(process.stdout,
|
||||
emitsThrough(contains('done compiling example/app/main.dart')));
|
||||
process.stdin.writeln('new message');
|
||||
await expectLater(
|
||||
process.stdout,
|
||||
emitsThrough(
|
||||
contains('Recompile succeeded for example/app/main.dart')));
|
||||
process.stdin.writeln('quit');
|
||||
expect(await process.exitCode, 0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// Copyright 2020 The Dart Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:frontend_server_client/frontend_server_client.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_descriptor/test_descriptor.dart' as d;
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
void main() async {
|
||||
FrontendServerClient? client;
|
||||
late PackageConfig packageConfig;
|
||||
late String packageRoot;
|
||||
late String packagesJsonPath;
|
||||
|
||||
setUp(() async {
|
||||
await d.dir('a', [
|
||||
d.file('pubspec.yaml', '''
|
||||
name: a
|
||||
dependencies:
|
||||
path: ^1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.0.0
|
||||
'''),
|
||||
d.dir('bin', [
|
||||
d.file('main.dart', '''
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() async {
|
||||
print(message);
|
||||
/// Runs in a loop until it is hot reloaded with a new message.
|
||||
while (!message.contains('goodbye')) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
print(message);
|
||||
}
|
||||
|
||||
String get message => p.join('hello', 'world');
|
||||
|
||||
'''),
|
||||
]),
|
||||
]).create();
|
||||
packageRoot = p.join(d.sandbox, 'a');
|
||||
await Process.run(
|
||||
Platform.resolvedExecutable,
|
||||
[
|
||||
'pub',
|
||||
'get',
|
||||
],
|
||||
workingDirectory: packageRoot);
|
||||
packageConfig = (await findPackageConfig(Directory(packageRoot)))!;
|
||||
packagesJsonPath = findNearestPackageConfigPath(Directory(packageRoot)) ??
|
||||
p.join(packageRoot, '.dart_tool', 'package_config.json');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await client?.shutdown();
|
||||
});
|
||||
|
||||
test('can compile, recompile, and hot reload a vm app', () async {
|
||||
final entrypoint = p.join(packageRoot, 'bin', 'main.dart');
|
||||
client = await FrontendServerClient.start(
|
||||
entrypoint,
|
||||
p.join(packageRoot, 'out.dill'),
|
||||
vmPlatformDill,
|
||||
packagesJson: packagesJsonPath,
|
||||
);
|
||||
var result = await client!.compile();
|
||||
client!.accept();
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(
|
||||
result.newSources,
|
||||
containsAll([
|
||||
File(entrypoint).uri,
|
||||
packageConfig.resolve(Uri.parse('package:path/path.dart')),
|
||||
]),
|
||||
);
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.dillOutput, isNotNull);
|
||||
expect(File(result.dillOutput!).existsSync(), true);
|
||||
final process = await Process.start(Platform.resolvedExecutable, [
|
||||
'--observe',
|
||||
'--no-pause-isolates-on-exit',
|
||||
'--pause-isolates-on-start',
|
||||
result.dillOutput!,
|
||||
]);
|
||||
addTearDown(process.kill);
|
||||
final stdoutLines = StreamQueue(
|
||||
process.stdout.transform(utf8.decoder).transform(const LineSplitter()),
|
||||
);
|
||||
addTearDown(stdoutLines.cancel);
|
||||
|
||||
final observatoryLine = await stdoutLines.next;
|
||||
final observatoryUri =
|
||||
'${observatoryLine.split(' ').last.replaceFirst('http', 'ws')}ws';
|
||||
final vmService = await vmServiceConnectUri(observatoryUri);
|
||||
addTearDown(vmService.dispose);
|
||||
final isolate = await waitForIsolatesAndResume(vmService);
|
||||
|
||||
await expectLater(stdoutLines, emitsThrough(p.join('hello', 'world')));
|
||||
|
||||
final appFile = File(entrypoint);
|
||||
final originalContent = await appFile.readAsString();
|
||||
final newContent = originalContent.replaceFirst('hello', 'goodbye');
|
||||
await appFile.writeAsString(newContent);
|
||||
|
||||
result = await client!.compile([File(entrypoint).uri]);
|
||||
|
||||
client!.accept();
|
||||
expect(result.newSources, isEmpty);
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(result.dillOutput, endsWith('.incremental.dill'));
|
||||
|
||||
await vmService.reloadSources(isolate.id!, rootLibUri: result.dillOutput);
|
||||
|
||||
expect(await stdoutLines.next, p.join('goodbye', 'world'));
|
||||
expect(await process.exitCode, 0);
|
||||
});
|
||||
|
||||
test('can handle compile errors and reload fixes', () async {
|
||||
final entrypoint = p.join(packageRoot, 'bin', 'main.dart');
|
||||
final entrypointFile = File(entrypoint);
|
||||
final originalContent = await entrypointFile.readAsString();
|
||||
// append two compile errors to the bottom
|
||||
await entrypointFile.writeAsString(
|
||||
'$originalContent\nint foo = 1.0;\nString bar = 4;',
|
||||
);
|
||||
|
||||
client = await FrontendServerClient.start(
|
||||
entrypoint,
|
||||
p.join(packageRoot, 'out.dill'),
|
||||
vmPlatformDill,
|
||||
packagesJson: packagesJsonPath,
|
||||
);
|
||||
var result = await client!.compile();
|
||||
|
||||
client!.accept();
|
||||
expect(result.errorCount, 2);
|
||||
expect(
|
||||
result.compilerOutputLines,
|
||||
allOf(contains('int foo = 1.0;'), contains('String bar = 4;')),
|
||||
);
|
||||
expect(
|
||||
result.newSources,
|
||||
containsAll([
|
||||
File(entrypoint).uri,
|
||||
packageConfig.resolve(Uri.parse('package:path/path.dart')),
|
||||
]),
|
||||
);
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.dillOutput, isNotNull);
|
||||
expect(File(result.dillOutput!).existsSync(), true);
|
||||
|
||||
final process = await Process.start(Platform.resolvedExecutable, [
|
||||
'--observe',
|
||||
'--no-pause-isolates-on-exit',
|
||||
'--pause-isolates-on-start',
|
||||
result.dillOutput!,
|
||||
]);
|
||||
addTearDown(process.kill);
|
||||
final stdoutLines = StreamQueue(
|
||||
process.stdout.transform(utf8.decoder).transform(const LineSplitter()),
|
||||
);
|
||||
addTearDown(stdoutLines.cancel);
|
||||
|
||||
final observatoryLine = await stdoutLines.next;
|
||||
final observatoryUri =
|
||||
'${observatoryLine.split(' ').last.replaceFirst('http', 'ws')}ws';
|
||||
final vmService = await vmServiceConnectUri(observatoryUri);
|
||||
addTearDown(vmService.dispose);
|
||||
final isolate = await waitForIsolatesAndResume(vmService);
|
||||
|
||||
// The program actually runs regardless of the errors, as they don't affect
|
||||
// the runtime behavior.
|
||||
await expectLater(stdoutLines, emitsThrough(p.join('hello', 'world')));
|
||||
|
||||
await entrypointFile.writeAsString(
|
||||
originalContent.replaceFirst('hello', 'goodbye'),
|
||||
);
|
||||
result = await client!.compile([entrypointFile.uri]);
|
||||
client!.accept();
|
||||
expect(result.errorCount, 0);
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.newSources, isEmpty);
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.dillOutput, isNotNull);
|
||||
expect(File(result.dillOutput!).existsSync(), true);
|
||||
|
||||
await vmService.reloadSources(isolate.id!, rootLibUri: result.dillOutput);
|
||||
|
||||
expect(await stdoutLines.next, p.join('goodbye', 'world'));
|
||||
expect(await process.exitCode, 0);
|
||||
});
|
||||
|
||||
test('can compile and recompile a dartdevc app', () async {
|
||||
final entrypoint =
|
||||
p.toUri(p.join(packageRoot, 'bin', 'main.dart')).toString();
|
||||
final dartDevcClient = client = await DartDevcFrontendServerClient.start(
|
||||
entrypoint,
|
||||
p.join(packageRoot, 'out.dill'),
|
||||
platformKernel: p
|
||||
.toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_platform.dill'))
|
||||
.toString(),
|
||||
packagesJson: packagesJsonPath,
|
||||
);
|
||||
var result = await client!.compile();
|
||||
client!.accept();
|
||||
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(
|
||||
result.newSources,
|
||||
containsAll([
|
||||
Uri.parse(entrypoint),
|
||||
packageConfig.resolve(Uri.parse('package:path/path.dart')),
|
||||
]),
|
||||
);
|
||||
expect(result.removedSources, isEmpty);
|
||||
|
||||
expect(result.dillOutput, isNotNull);
|
||||
expect(File(result.jsManifestOutput!).existsSync(), true);
|
||||
expect(File(result.jsSourcesOutput!).existsSync(), true);
|
||||
expect(File(result.jsSourceMapsOutput!).existsSync(), true);
|
||||
|
||||
final entrypointUri = Uri.parse(entrypoint);
|
||||
expect(
|
||||
utf8.decode(dartDevcClient.assetBytes('${entrypointUri.path}.lib.js')!),
|
||||
contains('hello'),
|
||||
);
|
||||
|
||||
final appFile = File(entrypointUri.toFilePath());
|
||||
final originalContent = await appFile.readAsString();
|
||||
final newContent = originalContent.replaceFirst('hello', 'goodbye');
|
||||
await appFile.writeAsString(newContent);
|
||||
|
||||
result = await client!.compile([entrypointUri]);
|
||||
client!.accept();
|
||||
expect(result.newSources, isEmpty);
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(result.jsManifestOutput, endsWith('.incremental.dill.json'));
|
||||
|
||||
expect(
|
||||
utf8.decode(dartDevcClient.assetBytes('${entrypointUri.path}.lib.js')!),
|
||||
contains('goodbye'),
|
||||
);
|
||||
});
|
||||
|
||||
test('can enable experiments', () async {
|
||||
await d.dir('a', [
|
||||
d.dir('bin', [
|
||||
d.file('nnbd.dart', '''
|
||||
|
||||
// Compile time error if nnbd is enabled
|
||||
int x;
|
||||
|
||||
void main() {
|
||||
print(x);
|
||||
}
|
||||
'''),
|
||||
]),
|
||||
]).create();
|
||||
final entrypoint = p.join(packageRoot, 'bin', 'nnbd.dart');
|
||||
client = await FrontendServerClient.start(
|
||||
entrypoint,
|
||||
p.join(packageRoot, 'out.dill'),
|
||||
vmPlatformDill,
|
||||
enabledExperiments: ['non-nullable'],
|
||||
packagesJson: packagesJsonPath,
|
||||
);
|
||||
final result = await client!.compile();
|
||||
client!.accept();
|
||||
expect(result.errorCount, 1);
|
||||
expect(result.compilerOutputLines, contains(contains('int x;')));
|
||||
});
|
||||
|
||||
test('can compile and recompile filenames with spaces', () async {
|
||||
await d.dir('a', [
|
||||
d.dir('bin', [
|
||||
d.file('main with spaces.dart', '''
|
||||
void main() {
|
||||
print('hello world');
|
||||
}
|
||||
'''),
|
||||
]),
|
||||
]).create();
|
||||
|
||||
final entrypoint = p.join(packageRoot, 'bin', 'main with spaces.dart');
|
||||
client = await FrontendServerClient.start(
|
||||
entrypoint,
|
||||
p.join(packageRoot, 'out with spaces.dill'),
|
||||
vmPlatformDill,
|
||||
packagesJson: packagesJsonPath,
|
||||
);
|
||||
var result = await client!.compile();
|
||||
client!.accept();
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(result.newSources, containsAll([File(entrypoint).uri]));
|
||||
expect(result.removedSources, isEmpty);
|
||||
expect(result.dillOutput, isNotNull);
|
||||
expect(File(result.dillOutput!).existsSync(), true);
|
||||
var processResult = await Process.run(Platform.resolvedExecutable, [
|
||||
result.dillOutput!,
|
||||
]);
|
||||
|
||||
expect(processResult.stdout, startsWith('hello world'));
|
||||
expect(processResult.exitCode, 0);
|
||||
|
||||
final appFile = File(entrypoint);
|
||||
final originalContent = await appFile.readAsString();
|
||||
final newContent = originalContent.replaceFirst('hello', 'goodbye');
|
||||
await appFile.writeAsString(newContent);
|
||||
result = await client!.compile([appFile.uri]);
|
||||
expect(result.compilerOutputLines, isEmpty);
|
||||
expect(result.errorCount, 0);
|
||||
expect(result.newSources, isEmpty);
|
||||
expect(result.removedSources, isEmpty);
|
||||
|
||||
processResult = await Process.run(Platform.resolvedExecutable, [
|
||||
result.dillOutput!,
|
||||
]);
|
||||
expect(processResult.stdout, startsWith('goodbye world'));
|
||||
expect(processResult.exitCode, 0);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Isolate> waitForIsolatesAndResume(VmService vmService) async {
|
||||
var vm = await vmService.getVM();
|
||||
var isolates = vm.isolates;
|
||||
while (isolates == null || isolates.isEmpty) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
vm = await vmService.getVM();
|
||||
isolates = vm.isolates;
|
||||
}
|
||||
final isolateRef = isolates.first;
|
||||
var isolate = await vmService.getIsolate(isolateRef.id!);
|
||||
while (isolate.pauseEvent?.kind != EventKind.kPauseStart) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
isolate = await vmService.getIsolate(isolateRef.id!);
|
||||
}
|
||||
await vmService.resume(isolate.id!);
|
||||
return isolate;
|
||||
}
|
||||
|
||||
final vmPlatformDill = p
|
||||
.toUri(p.join(sdkDir, 'lib', '_internal', 'vm_platform_strong.dill'))
|
||||
.toString();
|
||||
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
|
||||
@@ -256,6 +256,8 @@ vm_snapshot_analysis/test/*: SkipByDesign # Only meant to run on vm
|
||||
[ $system == windows ]
|
||||
front_end/test/bootstrap_test: Skip # Issue 31902
|
||||
front_end/test/incremental_dart2js_load_from_dill_test: Pass, Slow
|
||||
frontend_server_client/test/example/vm_client_test: SkipByDesign # Test is incompatible with Windows platform.
|
||||
frontend_server_client/test/example/web_client_test: SkipByDesign # Test is incompatible with Windows platform.
|
||||
vm_service/test/private_rpcs/dev_fs_http_put_test: Skip # Windows disallows "?" in paths
|
||||
vm_service/test/private_rpcs/dev_fs_http_put_weird_char_test: Skip # Windows disallows "\r" in paths
|
||||
vm_service/test/private_rpcs/dev_fs_weird_char_test: Skip # Windows disallows "\r" in paths
|
||||
|
||||
+1
-4
@@ -48,6 +48,7 @@ workspace:
|
||||
- pkg/_fe_analyzer_shared
|
||||
- pkg/front_end
|
||||
- pkg/frontend_server
|
||||
- pkg/frontend_server_client
|
||||
- pkg/heap_snapshot
|
||||
- pkg/js
|
||||
- pkg/js_ast
|
||||
@@ -96,8 +97,6 @@ workspace:
|
||||
|
||||
# All third_party packages are retrieved via the DEPS-file and overridden here.
|
||||
dependency_overrides:
|
||||
analysis_config:
|
||||
path: third_party/pkg/webdev/_analysis_config
|
||||
args:
|
||||
path: third_party/pkg/core/pkgs/args
|
||||
async:
|
||||
@@ -156,8 +155,6 @@ dependency_overrides:
|
||||
path: third_party/pkg/tools/pkgs/file_testing
|
||||
fixnum:
|
||||
path: third_party/pkg/core/pkgs/fixnum
|
||||
frontend_server_client:
|
||||
path: third_party/pkg/webdev/frontend_server_client
|
||||
glob:
|
||||
path: third_party/pkg/tools/pkgs/glob
|
||||
graphs:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
bkonyi@google.com
|
||||
yjessy@google.com
|
||||
nshahan@google.com
|
||||
markzipan@google.com
|
||||
natebiggs@google.com
|
||||
Reference in New Issue
Block a user