| // 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); | |
| } | |
| } |