Merge pull request #652 from dart-lang/flutter_sdk_configuration
add the ability to provision a flutter sdk via a configuration file
diff --git a/.gitignore b/.gitignore
index c15e696..2816616 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,6 @@
# Editor configuration
.vscode/
+
+# A Flutter SDK checkout
+flutter-sdk/
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 04f3167..a747c44 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -6,6 +6,7 @@
implicit-casts: false
exclude:
- dart-sdk/**
+ - flutter-sdk/**
- doc/generated/**
- flutter/**
# TODO: This seems to be hiding ~2 dozen legitimate analysis issues. https://github.com/dart-lang/dart-pad/issues/1712
diff --git a/flutter-sdk-version.yaml b/flutter-sdk-version.yaml
new file mode 100644
index 0000000..e1fb793
--- /dev/null
+++ b/flutter-sdk-version.yaml
@@ -0,0 +1,15 @@
+# The channel or version of the Flutter SDK to use.
+
+# Note: either the 'channel' field or the 'version' field should be provided
+# below, but not both. If neither a channel nor version is provided then no
+# particular git configuration will be done to the Flutter SDK in `flutter-sdk/`
+# (apart from any initial git clone).
+
+# The dart-services server will automatically create or configure the
+# flutter-sdk/ directory based on this configuration file at startup.
+
+# In order to update the SDK manually, run 'dart tool/update_sdk.dart'.
+
+flutter_sdk:
+ channel: beta
+ # version: 1.25.0-8.1.pre
diff --git a/lib/src/sdk_manager.dart b/lib/src/sdk_manager.dart
index fd6cb68..5ea8fa6 100644
--- a/lib/src/sdk_manager.dart
+++ b/lib/src/sdk_manager.dart
@@ -3,9 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:convert' show utf8;
import 'dart:io';
+import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
+import 'package:yaml/yaml.dart';
/// Generally, this should be a singleton instance (it's a heavy-weight object).
class SdkManager {
@@ -50,16 +53,12 @@
/// Represents a Dart SDK present on the server.
class PlatformSdk extends Sdk {
- String _versionFull = '';
+ @override
+ Future<void> init() => Future.value();
@override
- Future<void> init() async {
- _versionFull =
- (await File(path.join(sdkPath, 'version')).readAsString()).trim();
- }
-
- @override
- String get versionFull => _versionFull;
+ String get versionFull =>
+ File(path.join(sdkPath, 'version')).readAsStringSync().trim();
@override
String get sdkPath => path.dirname(path.dirname(Platform.resolvedExecutable));
@@ -94,3 +93,215 @@
String get flutterVersion => _flutterVersion;
}
+
+// TODO: Have an option to skip git calls (for testing)?
+// TODO(devoncarew): Collapse this with the other SDK managers.
+class DownloadingSdkManager {
+ DownloadingSdkManager();
+
+ /// Read and return the Flutter sdk configuration file info
+ /// (`flutter-sdk-version.yaml`).
+ static Map<String, Object> getSdkConfigInfo() {
+ final File file =
+ File(path.join(Directory.current.path, 'flutter-sdk-version.yaml'));
+ return (loadYaml(file.readAsStringSync()) as Map).cast<String, Object>();
+ }
+
+ /// Create a Flutter SDK in `flutter-sdk/` that configured using the
+ /// `flutter-sdk-version.yaml` file.
+ ///
+ /// Note that this is an expensive operation.
+ Future<DownloadedFlutterSdk> createFromConfigFile() async {
+ final Map<String, Object> sdkConfig = getSdkConfigInfo();
+
+ // flutter_sdk:
+ // channel: beta
+ // #version: 1.25.0-8.1.pre
+ if (!sdkConfig.containsKey('flutter_sdk')) {
+ throw "No key 'flutter_sdk' found in sdk config file";
+ }
+
+ final Map<String, Object> config =
+ (sdkConfig['flutter_sdk'] as Map).cast<String, Object>();
+
+ if (config.containsKey('channel') && config.containsKey('version')) {
+ throw "config file contains both 'channel' and 'version' config settings";
+ }
+
+ if (config.containsKey('channel')) {
+ return createUsingFlutterChannel(channel: config['channel'] as String);
+ } else if (config.containsKey('version')) {
+ return createUsingFlutterVersion(version: config['version'] as String);
+ } else {
+ // Clone the repo if necessary but don't do any other setup.
+ return _cloneSdkIfNecessary();
+ }
+ }
+
+ /// Create a Flutter SDK in `flutter-sdk/` that tracks a specific Flutter
+ /// channel.
+ ///
+ /// Note that this is an expensive operation.
+ Future<DownloadedFlutterSdk> createUsingFlutterChannel({
+ @required String channel,
+ }) async {
+ final DownloadedFlutterSdk sdk = await _cloneSdkIfNecessary();
+
+ // git checkout master
+ await sdk.checkout('master');
+
+ // Check if 'beta' exists.
+ if (await sdk.checkChannelAvailableLocally(channel)) {
+ await sdk.checkout(channel);
+ } else {
+ await sdk.trackChannel(channel);
+ }
+
+ // git pull
+ await sdk.pull();
+
+ return sdk;
+ }
+
+ /// Create a Flutter SDK in `flutter-sdk/` that tracks a specific Flutter
+ /// version.
+ ///
+ /// Note that this is an expensive operation.
+ Future<DownloadedFlutterSdk> createUsingFlutterVersion({
+ @required String version,
+ }) async {
+ final DownloadedFlutterSdk sdk = await _cloneSdkIfNecessary();
+
+ // git checkout master
+ await sdk.checkout('master');
+ // git fetch --tags
+ await sdk.fetchTags();
+ // git checkout 1.25.0-8.1.pre
+ await sdk.checkout(version);
+
+ return sdk;
+ }
+
+ Future<DownloadedFlutterSdk> _cloneSdkIfNecessary() async {
+ final DownloadedFlutterSdk sdk = DownloadedFlutterSdk();
+
+ if (!Directory(sdk.flutterSdkPath).existsSync()) {
+ // This takes perhaps ~20 seconds.
+ await sdk.clone(
+ [
+ '--depth',
+ '1',
+ '--no-single-branch',
+ 'https://github.com/flutter/flutter',
+ sdk.flutterSdkPath,
+ ],
+ cwd: Directory.current.path,
+ );
+ }
+
+ return sdk;
+ }
+}
+
+class DownloadedFlutterSdk extends Sdk {
+ @override
+ Future<void> init() async {
+ // flutter --version takes ~28s
+ await _execLog('bin/flutter', ['--version'], flutterSdkPath);
+ }
+
+ @override
+ String get sdkPath => path.join(flutterSdkPath, 'bin/cache/dart-sdk');
+
+ @override
+ String get versionFull =>
+ File(path.join(sdkPath, 'version')).readAsStringSync().trim();
+
+ String get flutterSdkPath => path.join(Directory.current.path, 'flutter-sdk');
+
+ String get flutterVersion =>
+ File(path.join(flutterSdkPath, 'version')).readAsStringSync().trim();
+
+ /// Perform a git clone, logging the command and any output, and throwing an
+ /// exception if there are any issues with the clone.
+ Future<void> clone(List<String> args, {@required String cwd}) async {
+ final result = await _execLog('git', ['clone', ...args], cwd);
+ if (result != 0) {
+ throw 'result from git clone: $result';
+ }
+ }
+
+ Future<void> checkout(String branch) async {
+ final result = await _execLog('git', ['checkout', branch], flutterSdkPath);
+ if (result != 0) {
+ throw 'result from git checkout: $result';
+ }
+ }
+
+ Future<void> fetchTags() async {
+ final result = await _execLog('git', ['fetch', '--tags'], flutterSdkPath);
+ if (result != 0) {
+ throw 'result from git fetch: $result';
+ }
+ }
+
+ Future<void> pull() async {
+ final result = await _execLog('git', ['pull'], flutterSdkPath);
+ if (result != 0) {
+ throw 'result from git pull: $result';
+ }
+ }
+
+ Future<void> trackChannel(String channel) async {
+ // git checkout --track -b beta origin/beta
+ final result = await _execLog(
+ 'git',
+ [
+ 'checkout',
+ '--track',
+ '-b',
+ channel,
+ 'origin/$channel',
+ ],
+ flutterSdkPath,
+ );
+ if (result != 0) {
+ throw 'result from git checkout $channel: $result';
+ }
+ }
+
+ Future<bool> checkChannelAvailableLocally(String channel) async {
+ // git show-ref --verify --quiet refs/heads/beta
+ final result = await _execLog(
+ 'git',
+ [
+ 'show-ref',
+ '--verify',
+ '--quiet',
+ 'refs/heads/$channel',
+ ],
+ flutterSdkPath,
+ );
+
+ return result == 0;
+ }
+
+ Future<int> _execLog(
+ String executable, List<String> arguments, String cwd) async {
+ print('$executable ${arguments.join(' ')}');
+
+ final process = await Process.start(
+ executable,
+ arguments,
+ workingDirectory: cwd,
+ );
+ process.stdout
+ .transform<String>(utf8.decoder)
+ .listen((string) => stdout.write(string));
+ process.stderr
+ .transform<String>(utf8.decoder)
+ .listen((string) => stderr.write(string));
+
+ return await process.exitCode;
+ }
+}
diff --git a/tool/update_sdk.dart b/tool/update_sdk.dart
new file mode 100644
index 0000000..583f98d
--- /dev/null
+++ b/tool/update_sdk.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, 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.
+
+import 'package:dart_services/src/sdk_manager.dart';
+
+// This tool is used to manually update the `flutter-sdk/` Flutter SDK to match
+// the current configuration information in the `flutter-sdk-version.yaml` file.
+
+void main(List<String> args) async {
+ final info = DownloadingSdkManager.getSdkConfigInfo();
+ print('configuration: $info\n');
+
+ final DownloadingSdkManager sdkManager = DownloadingSdkManager();
+ final DownloadedFlutterSdk sdk = await sdkManager.createFromConfigFile();
+
+ print('\nSDK setup complete (${sdk.flutterSdkPath}).');
+}