blob: 77f30bde6802ad5305f100e437b42d15936d1ca1 [file] [log] [blame]
// Copyright (c) 2017, 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 'dart:async';
import 'dart:convert' show utf8;
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
const stableChannel = 'stable';
class Sdk {
static Sdk? _instance;
final String sdkPath;
/// The path to the Flutter binaries.
final String _flutterBinPath;
/// The path to the Dart SDK.
final String dartSdkPath;
/// The current version of the SDK, including any `-dev` suffix.
final String versionFull;
final String flutterVersion;
/// The current version of the SDK, not including any `-dev` suffix.
final String version;
/// Is this SDK being used in development mode. True if channel is `dev`.
bool get devMode => channel == 'dev';
/// Is this the old channel
bool get oldChannel => channel == 'old';
// Which channel is this SDK?
final String channel;
factory Sdk.create(String channel) {
final sdkPath = path.join(Sdk._flutterSdksPath, channel);
final flutterBinPath = path.join(sdkPath, 'bin');
final dartSdkPath = path.join(flutterBinPath, 'cache', 'dart-sdk');
return _instance ??= Sdk._(
sdkPath: sdkPath,
flutterBinPath: flutterBinPath,
dartSdkPath: dartSdkPath,
versionFull: _readVersionFile(dartSdkPath),
flutterVersion: _readVersionFile(sdkPath),
channel: channel,
);
}
Sdk._({
required this.sdkPath,
required String flutterBinPath,
required this.dartSdkPath,
required this.versionFull,
required this.flutterVersion,
required this.channel,
}) : _flutterBinPath = flutterBinPath,
version = versionFull.contains('-')
? versionFull.substring(0, versionFull.indexOf('-'))
: versionFull;
/// The path to the 'flutter' tool (binary).
String get flutterToolPath => path.join(_flutterBinPath, 'flutter');
String get flutterWebSdkPath => path.join(
_flutterBinPath, 'cache', 'flutter_web_sdk', 'flutter_web_sdk', 'kernel');
static String _readVersionFile(String filePath) =>
(File(path.join(filePath, 'version')).readAsStringSync()).trim();
/// Get the path to the Flutter SDKs.
static String get _flutterSdksPath =>
path.join(Directory.current.path, 'flutter-sdks');
}
const channels = ['stable', 'beta', 'dev', 'old', 'master'];
class DownloadingSdkManager {
final String channel;
final String flutterVersion;
DownloadingSdkManager._(this.channel, this.flutterVersion);
factory DownloadingSdkManager(String channel) {
if (!channels.contains(channel)) {
throw StateError('Unknown channel name: $channel');
}
final flutterVersion =
_readVersionMap(channel)['flutter_version'] as String;
return DownloadingSdkManager._(channel, flutterVersion);
}
/// Creates a Flutter SDK in `flutter-sdks/` that is configured using the
/// `flutter-sdk-version.yaml` file.
///
/// Note that this is an expensive operation.
Future<String> createFromConfigFile() async {
final sdk = await _cloneSdkIfNecessary(channel);
// git checkout master
await sdk.checkout('master');
// git fetch --tags
await sdk.fetchTags();
// git checkout 1.25.0-8.1.pre
await sdk.checkout(flutterVersion);
// Force downloading of Dart SDK before constructing the Sdk singleton.
final exitCode = await sdk.init();
if (exitCode != 0) {
throw StateError('Initializing Flutter SDK resulted in error: $exitCode');
}
return sdk.flutterSdkPath;
}
Future<_DownloadedFlutterSdk> _cloneSdkIfNecessary(String channel) async {
final sdkPath = path.join(Sdk._flutterSdksPath, channel);
final sdk = _DownloadedFlutterSdk(sdkPath);
if (!Directory(sdk.flutterSdkPath).existsSync()) {
// This takes perhaps ~20 seconds.
await sdk.clone(
[
'https://github.com/flutter/flutter',
sdk.flutterSdkPath,
],
cwd: Directory.current.path,
);
}
return sdk;
}
}
String readDartLanguageVersion(String channelName) =>
_readVersionMap(channelName)['dart_language_version'] as String;
/// Read and return the Flutter SDK configuration file info
/// (`flutter-sdk-version.yaml`).
Map<String, Object> _readVersionMap(String channelName) {
final file = File(path.join(Directory.current.path, _flutterSdkConfigFile));
final sdkConfig =
(loadYaml(file.readAsStringSync()) as Map).cast<String, Object>();
if (!sdkConfig.containsKey('flutter_sdk')) {
throw StateError("No key 'flutter_sdk' found in '$_flutterSdkConfigFile'");
}
final flutterConfig = sdkConfig['flutter_sdk'] as Map;
if (!flutterConfig.containsKey(channelName)) {
throw StateError("No key '$channelName' found in '$_flutterSdkConfigFile'");
}
final channelConfig = flutterConfig[channelName] as Map;
if (!channelConfig.containsKey('flutter_version')) {
throw StateError(
"No key 'flutter_version' found in '$_flutterSdkConfigFile'");
}
if (!channelConfig.containsKey('dart_language_version')) {
throw StateError(
"No key 'dart_language_version' found in '$_flutterSdkConfigFile'");
}
return channelConfig.cast<String, Object>();
}
const String _flutterSdkConfigFile = 'flutter-sdk-version.yaml';
class _DownloadedFlutterSdk {
final String flutterSdkPath;
_DownloadedFlutterSdk(this.flutterSdkPath);
Future<int> init() =>
// `flutter --version` takes ~28s.
_execLog(path.join('bin', 'flutter'), ['--version'], flutterSdkPath);
String get sdkPath => path.join(flutterSdkPath, 'bin', 'cache', 'dart-sdk');
String get versionFull =>
File(path.join(sdkPath, 'version')).readAsStringSync().trim();
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;
}
}