blob: 919237b69e980f6aeafe71f87494f070e46622ed [file] [log] [blame]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// This script builds a local Dart SDK and copies the contents to the cache of
// a local Flutter SDK. This script is helpful for testing local SDK changes,
// such as Dart Analysis Server changes for example, against a Flutter project.
//
// Note: this script will not be sufficient if the local Dart SDK changes also
// need to be included in the build of the Flutter enginge. There are no
// guarantees you will be able to run a Flutter app with these changes applied.
// This script is mainly useful for testing static features, like static
// analysis.
//
// For ease of use, consider setting the LOCAL_DART_SDK and LOCAL_FLUTTER_SDK
// environment variables. Otherwise, you will need to specify these paths via
// the -d and -f options when running this script. You can add the following to
// your .bash_profile or .zshrc file to set the environment variables:
//
// export LOCAL_DART_SDK='/Users/me/path/to/dart-sdk/sdk'
// export LOCAL_FLUTTER_SDK='/Users/me/path/to/flutter'
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
const _architecture = 'arch';
const _buildSdk = 'build-sdk';
const _help = 'help';
const _localDart = 'local-dart';
const _localFlutter = 'local-flutter';
const _reset = 'reset';
const _verbose = 'verbose';
final _parser =
ArgParser()
..addOption(
_architecture,
abbr: 'a',
help: 'Specify your machine\'s architecture.',
allowed: ['ARM64', 'X64'],
defaultsTo: 'ARM64',
)
..addFlag(
_buildSdk,
negatable: true,
defaultsTo: true,
help:
'Whether to build the Dart SDK as part of running this script. '
'Negate with --no-$_buildSdk if you have already built the Dart '
'SDK locally and want to skip this step.',
)
..addOption(
_localDart,
abbr: 'd',
help:
'Path to your local Dart SDK directory. If unspecified, this value '
'will default to the value of the LOCAL_DART_SDK environment '
'variable.',
valueHelp: '/Users/me/path/to/dart-sdk/sdk',
)
..addOption(
_localFlutter,
abbr: 'f',
help:
'Path to your local Flutter SDK directory. If unspecified, this '
'value will default to the value of the LOCAL_FLUTTER_SDK '
'environment variable.',
valueHelp: '/Users/me/path/to/flutter',
)
..addFlag(
_verbose,
negatable: false,
abbr: 'v',
help:
'Run the script with verbose output, which will forward the stdout '
'and stderr of all sub-processes.',
)
..addFlag(
_help,
negatable: false,
abbr: 'h',
help: 'Show the program usage.',
)
..addSeparator('Additional commands')
..addFlag(
_reset,
negatable: false,
help:
'Reset your local Flutter SDK cache to undo the effects of running '
'this script.',
);
void main(List<String> args) async {
if (Platform.isWindows) {
throw Exception('This script is not currently supported for Windows.');
}
final options = _parser.parse(args);
if (options.flag(_help)) {
print(_parser.usage);
exit(0);
}
_verboseOutput = options.flag(_verbose);
final reset = options.flag(_reset);
if (reset) {
await _resetLocalFlutterSdk(options);
exit(0);
}
var localDartSdk =
options.option(_localDart) ?? Platform.environment['LOCAL_DART_SDK'];
var localFlutterSdk =
options.option(_localFlutter) ??
Platform.environment['LOCAL_FLUTTER_SDK'];
if (localDartSdk == null || localFlutterSdk == null) {
stderr.writeln(
'Error: either the --$_localDart and --$_localFlutter arguments must be '
'passed or the LOCAL_DART_SDK and LOCAL_FLUTTER_SDK environment '
'variables must be set.',
);
exit(1);
}
localDartSdk = _maybeRemoveTrailingSlash(localDartSdk);
localFlutterSdk = _maybeRemoveTrailingSlash(localFlutterSdk);
if (options.flag(_buildSdk)) {
stdout.writeln('Building the Dart SDK...');
await _runCommand('./tools/build.py', [
'-mrelease',
'create_sdk',
], workingDirectory: localDartSdk);
}
await _deleteDartSdkInFlutterCache(localFlutterSdk);
// Copy the built Dart SDK to the Flutter SDK cache.
String outDirectory;
if (Platform.isMacOS) {
outDirectory = 'xcodebuild';
} else if (Platform.isLinux) {
outDirectory = 'out';
} else {
outDirectory = 'unsupported';
}
final builtDartSdkPath = path.join(
localDartSdk,
outDirectory,
'Release${options.option(_architecture)}',
'dart-sdk',
);
final flutterCacheDartSdkPath = _flutterCachePrefix(
'dart-sdk',
localFlutterSdk: localFlutterSdk,
);
stdout.writeln(
'Copying the built Dart SDK at $builtDartSdkPath to the Flutter SDK cache '
'at $flutterCacheDartSdkPath...',
);
await _runCommand('cp', ['-R', builtDartSdkPath, flutterCacheDartSdkPath]);
// Delete and regenerate the Flutter tools snapshot file so that Flutter tools
// will rebuild the snapshot with your local Dart SDK changes.
final flutterToolsSnapshotPath = _flutterCachePrefix(
'flutter_tools.snapshot',
localFlutterSdk: localFlutterSdk,
);
await _runCommand('rm', [flutterToolsSnapshotPath]);
stdout.writeln('Regenerating the $flutterToolsSnapshotPath file...');
await _runCommand(path.join(localFlutterSdk, 'bin', 'flutter'), [
'--version',
]);
stdout.writeln(
'Finished copying local Dart SDK build to the local Flutter SDK.\n\nTo '
'reset your local Flutter SDK state, run: '
'dart tools/copy_dart_to_flutter.dart --reset.',
);
}
Future<void> _resetLocalFlutterSdk(ArgResults options) async {
var localFlutterSdk =
options.option(_localFlutter) ??
Platform.environment['LOCAL_FLUTTER_SDK'];
if (localFlutterSdk == null) {
stderr.writeln(
'Error: either the --$_localFlutter argument must be passed or the '
'LOCAL_FLUTTER_SDK environment variable must be set.',
);
exit(1);
}
await _deleteDartSdkInFlutterCache(localFlutterSdk);
final flutterToolsSnapshotPath = _flutterCachePrefix(
'flutter_tools.snapshot',
localFlutterSdk: localFlutterSdk,
);
final flutterToolsStampPath = _flutterCachePrefix(
'flutter_tools.stamp',
localFlutterSdk: localFlutterSdk,
);
final engineStampPath = _flutterCachePrefix(
'engine-dart-sdk.stamp',
localFlutterSdk: localFlutterSdk,
);
await _runCommand('rm', [
flutterToolsSnapshotPath,
flutterToolsStampPath,
engineStampPath,
]);
// Regenerate the local Flutter cache with the original values.
await _runCommand(path.join(localFlutterSdk, 'bin', 'flutter'), [
'--version',
]);
stdout.writeln('Finished restting your local Flutter SDK cache.');
}
Future<void> _deleteDartSdkInFlutterCache(String localFlutterSdk) async {
final flutterCacheDartSdkPath = _flutterCachePrefix(
'dart-sdk',
localFlutterSdk: localFlutterSdk,
);
stdout.writeln(
'Deleting the Dart SDK in the Flutter SDK cache at '
'$flutterCacheDartSdkPath...',
);
await _runCommand('rm', ['-rf', flutterCacheDartSdkPath]);
}
String _flutterCachePrefix(String value, {required String localFlutterSdk}) =>
path.join(localFlutterSdk, 'bin', 'cache', value);
String _maybeRemoveTrailingSlash(String path) {
if (path.endsWith('/')) {
return path.substring(0, path.length - 1);
} else {
return path;
}
}
/// Top level variable to track whether the --verbose flag was specified.
///
/// Tracked as a top level variable so that it does not have to be passed as a
/// parameter everywhere.
var _verboseOutput = false;
/// Runs a command in a sub-process and optionally forwards stdout and stderr to
/// the main process running this script.
///
/// If a sub-process exits with a non-zero exit code, the main process will
/// exit.
Future<void> _runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
}) async {
stdout.writeln(
'${workingDirectory != null ? '$workingDirectory ' : ''}'
'> $executable ${arguments.join(' ')}',
);
final process = await Process.start(
executable,
arguments,
workingDirectory: workingDirectory,
);
if (_verboseOutput) {
process.stdout.transform(utf8.decoder).listen(stdout.write);
process.stderr.transform(utf8.decoder).listen(stderr.write);
}
final exitCode = await process.exitCode;
if (exitCode != 0) {
exit(exitCode);
}
}