Skip to content

Commit d9bb808

Browse files
kenzieschmollCommit Queue
authored andcommitted
Add a tools script to build a local Dart SDK and copy the output to a local Flutter SDK.
This script is helpful for testing local Dart SDK changes against a Flutter project. This CL also adds instructions to the analysis_server CONTRIBUTING guide. The help output of the script looks like this: > dart tools/copy_dart_to_flutter.dart -h -a, --arch Specify your machine's architecture. [ARM64 (default), X64] --[no-]build-sdk Whether to build the Dart SDK as part of running this script. Negate with --no-build-sdk if you have already built the Dart SDK locally and want to skip this step. (defaults to on) -d, --local-dart=</Users/me/path/to/dart-sdk/sdk> Path to your local Dart SDK directory. If unspecified, this value will default to the value of the LOCAL_DART_SDK environment variable. -f, --local-flutter=</Users/me/path/to/flutter> Path to your local Flutter SDK directory. If unspecified, this value will default to the value of the LOCAL_FLUTTER_SDK environment variable. -v, --verbose Run the script with verbose output, which will forward the stdout and stderr of all sub-processes. -h, --help Show the program usage. Additional commands --reset Reset your local Flutter SDK cache to undo the effects of running this script. Change-Id: Ie34bf169838c3b0ac366cd02817324352b9234be Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408481 Reviewed-by: Jake Macdonald <[email protected]> Reviewed-by: Nate Bosch <[email protected]> Commit-Queue: Kenzie Davisson <[email protected]>
1 parent 8ff42d9 commit d9bb808

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed

pkg/analysis_server/CONTRIBUTING.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,35 @@ Once set up to build the SDK, run:
1515
./tools/build.py -mrelease create_sdk
1616
```
1717

18+
### Testing your changes against a local Flutter project
19+
20+
There may be times where you want to test your SDK changes locally against a
21+
Flutter project. To do this, you can use the `copy_dart_to_flutter.dart` script
22+
in the `sdk/tools/` directory.
23+
24+
To see usage information for this script, run:
25+
```sh
26+
dart ./tools/copy_dart_to_flutter.dart -h
27+
```
28+
29+
For ease of use, consider setting the `LOCAL_DART_SDK` and `LOCAL_FLUTTER_SDK`
30+
environment variables. Otherwise, you will need to specify these paths via the
31+
`-d` and `-f` options when running the script. You can add the following to your
32+
`.bash_profile` or `.zshrc` file to set the environment variables:
33+
34+
```sh
35+
export LOCAL_DART_SDK='/Users/me/path/to/dart-sdk/sdk'
36+
export LOCAL_FLUTTER_SDK='/Users/me/path/to/flutter'
37+
```
38+
39+
Instructions for testing your local changes against a Flutter project:
40+
1. Run the `copy_dart_to_flutter.dart` script.
41+
```sh
42+
dart ./tools/copy_dart_to_flutter.dart
43+
```
44+
2. Open a Flutter project in your IDE and restart the Analysis Server to test
45+
your changes.
46+
1847
## Running tests
1948

2049
To run analyzer tests:

tools/copy_dart_to_flutter.dart

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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+
// This script builds a local Dart SDK and copies the contents to the cache of
6+
// a local Flutter SDK. This script is helpful for testing local SDK changes,
7+
// such as Dart Analysis Server changes for example, against a Flutter project.
8+
//
9+
// Note: this script will not be sufficient if the local Dart SDK changes also
10+
// need to be included in the build of the Flutter enginge. There are no
11+
// guarantees you will be able to run a Flutter app with these changes applied.
12+
// This script is mainly useful for testing static features, like static
13+
// analysis.
14+
//
15+
// For ease of use, consider setting the LOCAL_DART_SDK and LOCAL_FLUTTER_SDK
16+
// environment variables. Otherwise, you will need to specify these paths via
17+
// the -d and -f options when running this script. You can add the following to
18+
// your .bash_profile or .zshrc file to set the environment variables:
19+
//
20+
// export LOCAL_DART_SDK='/Users/me/path/to/dart-sdk/sdk'
21+
// export LOCAL_FLUTTER_SDK='/Users/me/path/to/flutter'
22+
23+
import 'dart:async';
24+
import 'dart:convert';
25+
import 'dart:io';
26+
27+
import 'package:args/args.dart';
28+
import 'package:path/path.dart' as path;
29+
30+
const _architecture = 'arch';
31+
const _buildSdk = 'build-sdk';
32+
const _help = 'help';
33+
const _localDart = 'local-dart';
34+
const _localFlutter = 'local-flutter';
35+
const _reset = 'reset';
36+
const _verbose = 'verbose';
37+
38+
final _parser =
39+
ArgParser()
40+
..addOption(
41+
_architecture,
42+
abbr: 'a',
43+
help: 'Specify your machine\'s architecture.',
44+
allowed: ['ARM64', 'X64'],
45+
defaultsTo: 'ARM64',
46+
)
47+
..addFlag(
48+
_buildSdk,
49+
negatable: true,
50+
defaultsTo: true,
51+
help:
52+
'Whether to build the Dart SDK as part of running this script. '
53+
'Negate with --no-$_buildSdk if you have already built the Dart '
54+
'SDK locally and want to skip this step.',
55+
)
56+
..addOption(
57+
_localDart,
58+
abbr: 'd',
59+
help:
60+
'Path to your local Dart SDK directory. If unspecified, this value '
61+
'will default to the value of the LOCAL_DART_SDK environment '
62+
'variable.',
63+
valueHelp: '/Users/me/path/to/dart-sdk/sdk',
64+
)
65+
..addOption(
66+
_localFlutter,
67+
abbr: 'f',
68+
help:
69+
'Path to your local Flutter SDK directory. If unspecified, this '
70+
'value will default to the value of the LOCAL_FLUTTER_SDK '
71+
'environment variable.',
72+
valueHelp: '/Users/me/path/to/flutter',
73+
)
74+
..addFlag(
75+
_verbose,
76+
negatable: false,
77+
abbr: 'v',
78+
help:
79+
'Run the script with verbose output, which will forward the stdout '
80+
'and stderr of all sub-processes.',
81+
)
82+
..addFlag(
83+
_help,
84+
negatable: false,
85+
abbr: 'h',
86+
help: 'Show the program usage.',
87+
)
88+
..addSeparator('Additional commands')
89+
..addFlag(
90+
_reset,
91+
negatable: false,
92+
help:
93+
'Reset your local Flutter SDK cache to undo the effects of running '
94+
'this script.',
95+
);
96+
97+
void main(List<String> args) async {
98+
if (Platform.isWindows) {
99+
throw Exception('This script is not currently supported for Windows.');
100+
}
101+
102+
final options = _parser.parse(args);
103+
if (options.flag(_help)) {
104+
print(_parser.usage);
105+
exit(0);
106+
}
107+
108+
_verboseOutput = options.flag(_verbose);
109+
110+
final reset = options.flag(_reset);
111+
if (reset) {
112+
await _resetLocalFlutterSdk(options);
113+
exit(0);
114+
}
115+
116+
var localDartSdk =
117+
options.option(_localDart) ?? Platform.environment['LOCAL_DART_SDK'];
118+
var localFlutterSdk =
119+
options.option(_localFlutter) ??
120+
Platform.environment['LOCAL_FLUTTER_SDK'];
121+
if (localDartSdk == null || localFlutterSdk == null) {
122+
stderr.writeln(
123+
'Error: either the --$_localDart and --$_localFlutter arguments must be '
124+
'passed or the LOCAL_DART_SDK and LOCAL_FLUTTER_SDK environment '
125+
'variables must be set.',
126+
);
127+
exit(1);
128+
}
129+
localDartSdk = _maybeRemoveTrailingSlash(localDartSdk);
130+
localFlutterSdk = _maybeRemoveTrailingSlash(localFlutterSdk);
131+
132+
if (options.flag(_buildSdk)) {
133+
stdout.writeln('Building the Dart SDK...');
134+
await _runCommand('./tools/build.py', [
135+
'-mrelease',
136+
'create_sdk',
137+
], workingDirectory: localDartSdk);
138+
}
139+
140+
await _deleteDartSdkInFlutterCache(localFlutterSdk);
141+
142+
// Copy the built Dart SDK to the Flutter SDK cache.
143+
String outDirectory;
144+
if (Platform.isMacOS) {
145+
outDirectory = 'xcodebuild';
146+
} else if (Platform.isLinux) {
147+
outDirectory = 'out';
148+
} else {
149+
outDirectory = 'unsupported';
150+
}
151+
final builtDartSdkPath = path.join(
152+
localDartSdk,
153+
outDirectory,
154+
'Release${options.option(_architecture)}',
155+
'dart-sdk',
156+
);
157+
final flutterCacheDartSdkPath = _flutterCachePrefix(
158+
'dart-sdk',
159+
localFlutterSdk: localFlutterSdk,
160+
);
161+
stdout.writeln(
162+
'Copying the built Dart SDK at $builtDartSdkPath to the Flutter SDK cache '
163+
'at $flutterCacheDartSdkPath...',
164+
);
165+
await _runCommand('cp', ['-R', builtDartSdkPath, flutterCacheDartSdkPath]);
166+
167+
// Delete and regenerate the Flutter tools snapshot file so that Flutter tools
168+
// will rebuild the snapshot with your local Dart SDK changes.
169+
final flutterToolsSnapshotPath = _flutterCachePrefix(
170+
'flutter_tools.snapshot',
171+
localFlutterSdk: localFlutterSdk,
172+
);
173+
await _runCommand('rm', [flutterToolsSnapshotPath]);
174+
stdout.writeln('Regenerating the $flutterToolsSnapshotPath file...');
175+
await _runCommand(path.join(localFlutterSdk, 'bin', 'flutter'), [
176+
'--version',
177+
]);
178+
179+
stdout.writeln(
180+
'Finished copying local Dart SDK build to the local Flutter SDK.\n\nTo '
181+
'reset your local Flutter SDK state, run: '
182+
'dart tools/copy_dart_to_flutter.dart --reset.',
183+
);
184+
}
185+
186+
Future<void> _resetLocalFlutterSdk(ArgResults options) async {
187+
var localFlutterSdk =
188+
options.option(_localFlutter) ??
189+
Platform.environment['LOCAL_FLUTTER_SDK'];
190+
if (localFlutterSdk == null) {
191+
stderr.writeln(
192+
'Error: either the --$_localFlutter argument must be passed or the '
193+
'LOCAL_FLUTTER_SDK environment variable must be set.',
194+
);
195+
exit(1);
196+
}
197+
198+
await _deleteDartSdkInFlutterCache(localFlutterSdk);
199+
final flutterToolsSnapshotPath = _flutterCachePrefix(
200+
'flutter_tools.snapshot',
201+
localFlutterSdk: localFlutterSdk,
202+
);
203+
final flutterToolsStampPath = _flutterCachePrefix(
204+
'flutter_tools.stamp',
205+
localFlutterSdk: localFlutterSdk,
206+
);
207+
final engineStampPath = _flutterCachePrefix(
208+
'engine-dart-sdk.stamp',
209+
localFlutterSdk: localFlutterSdk,
210+
);
211+
await _runCommand('rm', [
212+
flutterToolsSnapshotPath,
213+
flutterToolsStampPath,
214+
engineStampPath,
215+
]);
216+
// Regenerate the local Flutter cache with the original values.
217+
await _runCommand(path.join(localFlutterSdk, 'bin', 'flutter'), [
218+
'--version',
219+
]);
220+
221+
stdout.writeln('Finished restting your local Flutter SDK cache.');
222+
}
223+
224+
Future<void> _deleteDartSdkInFlutterCache(String localFlutterSdk) async {
225+
final flutterCacheDartSdkPath = _flutterCachePrefix(
226+
'dart-sdk',
227+
localFlutterSdk: localFlutterSdk,
228+
);
229+
stdout.writeln(
230+
'Deleting the Dart SDK in the Flutter SDK cache at '
231+
'$flutterCacheDartSdkPath...',
232+
);
233+
await _runCommand('rm', ['-rf', flutterCacheDartSdkPath]);
234+
}
235+
236+
String _flutterCachePrefix(String value, {required String localFlutterSdk}) =>
237+
path.join(localFlutterSdk, 'bin', 'cache', value);
238+
239+
String _maybeRemoveTrailingSlash(String path) {
240+
if (path.endsWith('/')) {
241+
return path.substring(0, path.length - 1);
242+
} else {
243+
return path;
244+
}
245+
}
246+
247+
/// Top level variable to track whether the --verbose flag was specified.
248+
///
249+
/// Tracked as a top level variable so that it does not have to be passed as a
250+
/// parameter everywhere.
251+
var _verboseOutput = false;
252+
253+
/// Runs a command in a sub-process and optionally forwards stdout and stderr to
254+
/// the main process running this script.
255+
///
256+
/// If a sub-process exits with a non-zero exit code, the main process will
257+
/// exit.
258+
Future<void> _runCommand(
259+
String executable,
260+
List<String> arguments, {
261+
String? workingDirectory,
262+
}) async {
263+
stdout.writeln(
264+
'${workingDirectory != null ? '$workingDirectory ' : ''}'
265+
'> $executable ${arguments.join(' ')}',
266+
);
267+
final process = await Process.start(
268+
executable,
269+
arguments,
270+
workingDirectory: workingDirectory,
271+
);
272+
if (_verboseOutput) {
273+
process.stdout.transform(utf8.decoder).listen(stdout.write);
274+
process.stderr.transform(utf8.decoder).listen(stderr.write);
275+
}
276+
final exitCode = await process.exitCode;
277+
if (exitCode != 0) {
278+
exit(exitCode);
279+
}
280+
}

0 commit comments

Comments
 (0)