blob: c048e1a7582538a22d37692f2afe90a721a04b86 [file] [log] [blame]
// Copyright (c) 2012, 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:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:pub_semver/pub_semver.dart';
import 'http.dart';
import 'log.dart';
String get flutterReleasesUrl =>
Platform.environment['_PUB_TEST_FLUTTER_RELEASES_URL'] ??
'https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json';
// Retrieves all released versions of Flutter.
Future<List<FlutterRelease>> _flutterReleases = () async {
final response = await retryForHttp(
'fetching available Flutter releases',
() => globalHttpClient.fetch(Request('GET', Uri.parse(flutterReleasesUrl))),
);
final decoded = jsonDecode(response.body);
if (decoded is! Map) {
throw const FormatException('Bad response - should be a Map');
}
final releases = decoded['releases'];
if (releases is! List) {
throw const FormatException('Bad response - releases should be a list.');
}
final result = <FlutterRelease>[];
for (final release in releases) {
if (release is! Map) {
throw const FormatException(
'Bad response - releases should be a list of maps.',
);
}
final channel =
{
'beta': Channel.beta,
'stable': Channel.stable,
'dev': Channel.dev,
}[release['channel']];
if (channel == null) {
throw const FormatException('Release with bad channel');
}
final dartVersion = release['dart_sdk_version'];
// Some releases don't have an associated dart version, ignore.
if (dartVersion is! String) continue;
final flutterVersion = release['version'];
if (flutterVersion is! String) throw const FormatException('Not a string');
result.add(
FlutterRelease(
flutterVersion: Version.parse(flutterVersion),
dartVersion: Version.parse(dartVersion.split(' ').first),
channel: channel,
),
);
}
return result
// Sort releases by channel and version.
.sorted((a, b) {
final compareChannels = b.channel.index - a.channel.index;
if (compareChannels != 0) return compareChannels;
return a.flutterVersion.compareTo(b.flutterVersion);
})
// Newest first.
.reversed
.toList();
}();
/// The "best" Flutter release for a given set of constraints is the first one
/// in [_flutterReleases] that matches both the flutter and dart constraint.
///
/// Returns if no such release could be found.
Future<FlutterRelease?> inferBestFlutterRelease(
Map<String, VersionConstraint> sdkConstraints,
) async {
final List<FlutterRelease> flutterReleases;
try {
flutterReleases = await _flutterReleases;
} on Exception catch (e) {
fine('Failed retrieving the list of flutter-releases: $e');
return null;
}
return flutterReleases.firstWhereOrNull(
(release) =>
(sdkConstraints['flutter'] ?? VersionConstraint.any).allows(
release.flutterVersion,
) &&
(sdkConstraints['dart'] ?? VersionConstraint.any).allows(
release.dartVersion,
),
);
}
enum Channel { stable, beta, dev }
/// A version of the Flutter SDK and its related Dart SDK.
class FlutterRelease {
final Version flutterVersion;
final Version dartVersion;
final Channel channel;
FlutterRelease({
required this.flutterVersion,
required this.dartVersion,
required this.channel,
});
@override
String toString() =>
'FlutterRelease(flutter=$flutterVersion, '
'dart=$dartVersion, '
'channel=$channel)';
}