Adam Barth bdd20661d7 Teach sky_tools about prebuilt artifacts
This patch makes `flutter start` work without a clone of the engine git
repository. Making this work pulled a relatively large refactor of how the
commands interact with application packages and devices. Now commands that want
to interact with application packages or devices inherit from a common base
class that holds stores of those objects as members.

In production, the commands download and connect to devices based on the build
configuration stored on the FlutterCommandRunner. In testing, these fields are
used to mock out the real application package and devices.
2015-10-12 00:03:55 -07:00

130 lines
4.1 KiB
Dart

// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library sky_tools.artifacts;
import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
final Logger _logging = new Logger('sky_tools.artifacts');
enum Artifact {
flutterCompiler,
flutterShell,
skyViewerMojo,
}
class ArtifactStore {
static String packageRoot;
static String _engineRevision;
static String get engineRevision {
if (_engineRevision == null)
_engineRevision = new File(path.join(packageRoot, 'sky_engine', 'REVISION')).readAsStringSync();
return _engineRevision;
}
// Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/big_red_button.py#L50
static String googleStorageUrl(String category, String platform) {
return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${engineRevision}/';
}
static Future _downloadFile(String url, File file) async {
_logging.fine('Downloading $url to ${file.path}');
HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close();
_logging.fine('Received response');
if (response.statusCode != 200) throw new Exception(response.reasonPhrase);
IOSink sink = file.openWrite();
await sink.addStream(response);
await sink.close();
_logging.fine('Wrote file');
}
static Future<Directory> _cacheDir() async {
Directory cacheDir = new Directory(path.join(packageRoot, 'sky_tools', 'cache'));
if (!await cacheDir.exists()) {
await cacheDir.create(recursive: true);
}
return cacheDir;
}
static Future<Directory> _engineSpecificCacheDir() async {
Directory cacheDir = await _cacheDir();
// For now, all downloaded artifacts are release mode host binaries so use
// a path that mirrors a local release build.
// TODO(jamesr): Add support for more configurations.
String config = 'Release';
Directory engineSpecificDir = new Directory(path.join(cacheDir.path, 'sky_engine', engineRevision, config));
if (!await engineSpecificDir.exists()) {
await engineSpecificDir.create(recursive: true);
}
return engineSpecificDir;
}
// Whether the artifact needs to be marked as executable on disk.
static bool _needsToBeExecutable(Artifact artifact) {
return artifact == Artifact.flutterCompiler;
}
static Future<String> getPath(Artifact artifact) async {
Directory cacheDir = await _engineSpecificCacheDir();
String category;
String platform;
String name;
switch (artifact) {
case Artifact.flutterCompiler:
category = 'shell';
name = 'sky_snapshot';
break;
case Artifact.flutterShell:
category = 'shell';
platform = 'android-arm';
name = 'SkyShell.apk';
break;
case Artifact.skyViewerMojo:
category = 'viewer';
name = 'sky_viewer.mojo';
break;
}
File cachedFile = new File(path.join(cacheDir.path, name));
if (!await cachedFile.exists()) {
_logging.info('Downloading ${name} from the cloud, one moment please...');
if (platform == null) {
if (!Platform.isLinux)
throw new Exception('Platform unsupported.');
platform = 'linux-x64';
}
String url = googleStorageUrl(category, platform) + name;
await _downloadFile(url, cachedFile);
if (_needsToBeExecutable(artifact)) {
ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]);
if (result.exitCode != 0) throw new Exception(result.stderr);
}
}
return cachedFile.path;
}
static Future clear() async {
Directory cacheDir = await _cacheDir();
_logging.fine('Clearing cache directory ${cacheDir.path}');
await cacheDir.delete(recursive: true);
}
static Future populate() async {
for (Artifact artifact in Artifact.values) {
_logging.fine('Populating cache with $artifact');
await getPath(artifact);
}
}
}