Skip to content

Commit 8d62a1f

Browse files
srawlinsCommit Queue
authored and
Commit Queue
committed
Support the analysis server compiled as AOT.
This change adds a build target (see utils/analysis_server/BUILD.gn) called 'analysis_server_aot'. This new target is _not_ included in the Dart SDK (the create_sdk target). It's "opt-in" "for development." The name of the new output file matches that of other snapshots (see the dartdevc snapshots). Then we do special work in the plugin manager if "we are AOT." An analysis server running as AOT cannot spawn Isolates from a dart source files; we must first compile a dart source file to AOT as well, then we can spawn an Isolate to that AOT file. _Then_ when we run pub, we can no longer rely on using `Platform.executable`. `dartaotruntime pub get` is not a thing. We must instead find the `dart` tool on disk. To do that, we copy some complex discovery code from dartdev. Work towards #53402 Work towards #53576 Work towards #50498 Manually tested: * [+] analysis_server JIT snapshot works in IDE. * [+] analysis_server JIT snapshot works in IDE, with a legacy plugin (custom_lint). * [+] analysis_server JIT snapshot works at commandline. * [+] analysis_server AOT snapshot works in IDE. * [x] analysis_server AOT snapshot works in IDE, with a legacy plugin (custom_lint) - BROKEN. Need similar work that is done for new plugins. * [x] analysis_server AOT snapshot works at commandline - BROKEN. I think a fair bit of refactoring is required in dartdev lib/src/analysis_server.dart to use `VmInteropHandler.run` or similar. Change-Id: I53173c716fa2a763331ef524a96304f62165810e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/417942 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Siva Annamalai <[email protected]>
1 parent 57fbb55 commit 8d62a1f

File tree

5 files changed

+177
-10
lines changed

5 files changed

+177
-10
lines changed

pkg/analysis_server/lib/src/plugin/plugin_manager.dart

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'dart:io' show Platform, Process, ProcessResult;
1313

1414
import 'package:analysis_server/src/analytics/percentile_calculator.dart';
1515
import 'package:analysis_server/src/plugin/notification_manager.dart';
16+
import 'package:analysis_server/src/utilities/sdk.dart';
1617
import 'package:analyzer/dart/analysis/context_root.dart' as analyzer;
1718
import 'package:analyzer/exception/exception.dart';
1819
import 'package:analyzer/file_system/file_system.dart';
@@ -36,6 +37,8 @@ import 'package:meta/meta.dart';
3637
import 'package:watcher/watcher.dart' as watcher;
3738
import 'package:yaml/yaml.dart';
3839

40+
const _builtAsAot = bool.fromEnvironment('built_as_aot');
41+
3942
/// Information about a plugin that is built-in.
4043
class BuiltInPluginInfo extends PluginInfo {
4144
/// The entry point function that will be executed in the plugin's isolate.
@@ -685,6 +688,29 @@ class PluginManager {
685688
);
686689
}
687690

691+
/// Compiles [entrypoint] to an AOT snapshot and records timing to the
692+
/// instrumentation log.
693+
ProcessResult _compileAotSnapshot(String entrypoint) {
694+
instrumentationService.logInfo(
695+
'Running "dart compile aot-snapshot $entrypoint".',
696+
);
697+
698+
var stopwatch = Stopwatch()..start();
699+
var result = Process.runSync(
700+
sdk.dart,
701+
['compile', 'aot-snapshot', entrypoint],
702+
stderrEncoding: utf8,
703+
stdoutEncoding: utf8,
704+
);
705+
stopwatch.stop();
706+
707+
instrumentationService.logInfo(
708+
'Running "dart compile aot-snapshot" took ${stopwatch.elapsed}.',
709+
);
710+
711+
return result;
712+
}
713+
688714
/// Computes the plugin files, given that the plugin should exist in
689715
/// [pluginFolder].
690716
///
@@ -705,17 +731,17 @@ class PluginManager {
705731
.getChildAssumingFile(file_paths.packageConfigJson);
706732

707733
if (pubCommand != null) {
708-
var result = _runPubCommand(pubCommand, pluginFolder);
734+
var pubResult = _runPubCommand(pubCommand, pluginFolder);
709735
String? exceptionReason;
710-
if (result.exitCode != 0) {
736+
if (pubResult.exitCode != 0) {
711737
var buffer = StringBuffer();
712738
buffer.writeln(
713739
'An error occurred while setting up the analyzer plugin package at '
714740
"'${pluginFolder.path}'. The `dart pub $pubCommand` command failed:",
715741
);
716-
buffer.writeln(' exitCode = ${result.exitCode}');
717-
buffer.writeln(' stdout = ${result.stdout}');
718-
buffer.writeln(' stderr = ${result.stderr}');
742+
buffer.writeln(' exitCode = ${pubResult.exitCode}');
743+
buffer.writeln(' stdout = ${pubResult.stdout}');
744+
buffer.writeln(' stderr = ${pubResult.stderr}');
719745
exceptionReason = buffer.toString();
720746
instrumentationService.logError(exceptionReason);
721747
notificationManager.handlePluginError(exceptionReason);
@@ -724,6 +750,31 @@ class PluginManager {
724750
exceptionReason ??= 'File "${packageConfigFile.path}" does not exist.';
725751
throw PluginException(exceptionReason);
726752
}
753+
754+
if (_builtAsAot) {
755+
// When the Dart Analysis Server is built as AOT, then all spawned
756+
// Isolates must also be built as AOT.
757+
var aotResult = _compileAotSnapshot(pluginFile.path);
758+
if (aotResult.exitCode != 0) {
759+
var buffer = StringBuffer();
760+
buffer.writeln(
761+
'Failed to compile "${pluginFile.path}" to an AOT snapshot.',
762+
);
763+
buffer.writeln(' pluginFolder = ${pluginFolder.path}');
764+
buffer.writeln(' exitCode = ${aotResult.exitCode}');
765+
buffer.writeln(' stdout = ${aotResult.stdout}');
766+
buffer.writeln(' stderr = ${aotResult.stderr}');
767+
var exceptionReason = buffer.toString();
768+
instrumentationService.logError(exceptionReason);
769+
throw PluginException(exceptionReason);
770+
}
771+
772+
// Update the entrypoint path to be the AOT-compiled file.
773+
pluginFile = pluginFolder
774+
.getChildAssumingFolder('bin')
775+
.getChildAssumingFile('plugin.aot');
776+
}
777+
727778
return PluginFiles(pluginFile, packageConfigFile);
728779
}
729780

@@ -856,13 +907,13 @@ class PluginManager {
856907
/// [pubCommand] in [folder].
857908
ProcessResult _runPubCommand(String pubCommand, Folder folder) {
858909
instrumentationService.logInfo(
859-
'Running "pub $pubCommand" in "${folder.path}"',
910+
'Running "pub $pubCommand" in "${folder.path}".',
860911
);
861912

862913
var stopwatch = Stopwatch()..start();
863914
var result = Process.runSync(
864-
Platform.executable,
865-
<String>['pub', pubCommand],
915+
sdk.dart,
916+
['pub', pubCommand],
866917
stderrEncoding: utf8,
867918
stdoutEncoding: utf8,
868919
workingDirectory: folder.path,
@@ -871,7 +922,7 @@ class PluginManager {
871922
stopwatch.stop();
872923

873924
instrumentationService.logInfo(
874-
'Running "pub $pubCommand" took ${stopwatch.elapsed}',
925+
'Running "pub $pubCommand" took ${stopwatch.elapsed}.',
875926
);
876927

877928
return result;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Utilities for working with the "active" Dart SDK that a given analysis
6+
/// server process is running on.
7+
///
8+
/// These are copied from the dartdev package's 'lib/src/sdk.dart'.
9+
library;
10+
11+
import 'dart:io';
12+
13+
import 'package:path/path.dart' as path;
14+
15+
final Sdk sdk = Sdk._instance;
16+
17+
/// A utility class for finding and referencing paths within the Dart SDK.
18+
class Sdk {
19+
static final Sdk _instance = _createSingleton();
20+
21+
/// Path to SDK directory.
22+
final String sdkPath;
23+
24+
final bool _runFromBuildRoot;
25+
26+
factory Sdk() => _instance;
27+
28+
Sdk._(this.sdkPath, bool runFromBuildRoot)
29+
: _runFromBuildRoot = runFromBuildRoot;
30+
31+
/// Path to the 'dart' executable in the Dart SDK.
32+
String get dart {
33+
var basename = path.basename(Platform.executable);
34+
// It's possible that `Platform.executable` won't include the '.exe'
35+
// extension on Windows (e.g., launching `dart` from `cmd.exe` where `dart`
36+
// is on the `PATH`). Append '.exe' in this case so the
37+
// `checkArtifactExists` check won't fail.
38+
if (Platform.isWindows && !basename.endsWith('.exe')) {
39+
basename += '.exe';
40+
}
41+
return path.absolute(
42+
_runFromBuildRoot ? sdkPath : path.absolute(sdkPath, 'bin'),
43+
basename,
44+
);
45+
}
46+
47+
static bool _checkArtifactExists(String path) {
48+
return FileSystemEntity.typeSync(path) != FileSystemEntityType.notFound;
49+
}
50+
51+
static Sdk _createSingleton() {
52+
// Find SDK path.
53+
(String, bool)? trySDKPath(String executablePath) {
54+
// The common case, and how cli_util.dart computes the Dart SDK directory,
55+
// [path.dirname] called twice on Platform.executable. We confirm by
56+
// asserting that the directory `./bin/snapshots/` exists in this directory:
57+
var sdkPath = path.absolute(path.dirname(path.dirname(executablePath)));
58+
var snapshotsDir = path.join(sdkPath, 'bin', 'snapshots');
59+
var runFromBuildRoot = false;
60+
var type = FileSystemEntity.typeSync(snapshotsDir);
61+
if (type != FileSystemEntityType.directory &&
62+
type != FileSystemEntityType.link) {
63+
// This is the less common case where the user is in
64+
// the checked out Dart SDK, and is executing `dart` via:
65+
// ./out/ReleaseX64/dart ... or in google3.
66+
sdkPath = path.absolute(path.dirname(executablePath));
67+
snapshotsDir = sdkPath;
68+
runFromBuildRoot = true;
69+
}
70+
71+
// Try to locate the DartDev snapshot to determine if we're able to find
72+
// the SDK snapshots with this SDK path. This is meant to handle
73+
// non-standard SDK layouts that can involve symlinks (e.g., Brew
74+
// installations, google3 tests, etc).
75+
if (!_checkArtifactExists(
76+
path.join(snapshotsDir, 'dartdev.dart.snapshot'),
77+
)) {
78+
return null;
79+
}
80+
return (sdkPath, runFromBuildRoot);
81+
}
82+
83+
var (sdkPath, runFromBuildRoot) =
84+
trySDKPath(Platform.resolvedExecutable) ??
85+
trySDKPath(Platform.executable)!;
86+
87+
return Sdk._(sdkPath, runFromBuildRoot);
88+
}
89+
}

tools/bots/test_matrix.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2936,7 +2936,8 @@
29362936
"script": "tools/build.py",
29372937
"arguments": [
29382938
"create_sdk",
2939-
"utils/dartanalyzer"
2939+
"utils/dartanalyzer",
2940+
"analysis_server_aot"
29402941
]
29412942
},
29422943
{

utils/analysis_server/BUILD.gn

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22
# for details. All rights reserved. Use of this source code is governed by a
33
# BSD-style license that can be found in the LICENSE file.
44

5+
import("../aot_snapshot.gni")
56
import("../application_snapshot.gni")
67

8+
aot_snapshot("analysis_server_aot") {
9+
main_dart = "../../pkg/analysis_server/bin/server.dart"
10+
name = "analysis_server_aot"
11+
output = "$root_gen_dir/analysis_server_aot.dart.snapshot"
12+
vm_args = [ "-Dbuilt_as_aot=true" ]
13+
}
14+
15+
aot_snapshot("analysis_server_aot_product") {
16+
main_dart = "../../pkg/analysis_server/bin/server.dart"
17+
name = "analysis_server_aot_product"
18+
output = "$root_gen_dir/analysis_server_aot_product.dart.snapshot"
19+
20+
# dartaotruntime in the dart sdk has dart_product_config applied to it,
21+
# so it is built in product mode in both release and
22+
# product builds, and is only built in debug mode in debug
23+
# builds. The following line ensures that the dartaotruntime
24+
# and dartdevc_aot snapshot in an SDK build are
25+
# always compatible with each other.
26+
force_product_mode = !dart_debug
27+
}
28+
729
application_snapshot("analysis_server") {
830
main_dart = "../../pkg/analysis_server/bin/server.dart"
931
training_args = [
1032
"--sdk=" + rebase_path("../../sdk/"),
1133
"--train-using=" + rebase_path("../../pkg/compiler/lib"),
1234
]
35+
vm_args = [ "-Dbuilt_as_aot=false" ]
1336
}

utils/aot_snapshot.gni

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ template("aot_snapshot") {
128128
"--snapshot-kind=app-aot-elf",
129129
"--elf=$abs_output",
130130
] + gen_snapshot_args
131+
if (defined(invoker.vm_args)) {
132+
vm_args += invoker.vm_args
133+
}
131134

132135
args = [ rebase_path(dill) ]
133136

0 commit comments

Comments
 (0)