Commit c8f66a1f authored by Adam Barth's avatar Adam Barth

Merge pull request #31 from abarth/flutter_tools

Flutter tools
parents a4d0b560 654faaf5
# tools
[![Build Status](https://travis-ci.org/flutter/tools.svg)](https://travis-ci.org/flutter/tools)
[![Build status](https://ci.appveyor.com/api/projects/status/fpokp26jprqddfms/branch/master?svg=true)](https://ci.appveyor.com/project/devoncarew/tools/branch/master)
[![pub package](https://img.shields.io/pub/v/sky_tools.svg)](https://pub.dartlang.org/packages/sky_tools)
Tools for building Flutter applications.
## Installing
To install, run:
pub global activate sky_tools
or, depend on this package in your pubspec:
```yaml
dev_dependencies:
sky_tools: any
```
## Running sky_tools
Run `sky_tools` (or `pub global run sky_tools`) to see a list of available
commands:
- `init` to create a new project
Then, run a `sky_tools` command:
sky_tools init --out my_sky_project
## Running sky_tools:sky_server
To serve the current directory using `sky_server`:
pub run sky_tools:sky_server [-v] PORT
## Running sky_tools:build_sky_apk
```
usage: pub run sky_tools:build_sky_apk <options>
-h, --help
--android-sdk
--skyx
```
## Filing Issues
Please file reports on the
[GitHub Issue Tracker](https://github.com/flutter/tools/issues).
// 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.
import 'dart:io';
import 'package:args/args.dart';
const String kBuildToolsVersion = '22.0.1';
const String kAndroidPlatformVersion = '22';
const String kKeystoreKeyName = "chromiumdebugkey";
const String kKeystorePassword = "chromium";
class AssetBuilder {
final Directory outDir;
Directory _assetDir;
AssetBuilder(this.outDir) {
_assetDir = new Directory('${outDir.path}/assets');
_assetDir.createSync(recursive: true);
}
void add(File asset, String assetName) {
asset.copySync('${_assetDir.path}/$assetName');
}
Directory get directory => _assetDir;
}
class ApkBuilder {
final String androidSDK;
File _androidJar;
File _aapt;
File _zipalign;
String _jarsigner;
ApkBuilder(this.androidSDK) {
_androidJar = new File('$androidSDK/platforms/android-$kAndroidPlatformVersion/android.jar');
String buildTools = '$androidSDK/build-tools/$kBuildToolsVersion';
_aapt = new File('$buildTools/aapt');
_zipalign = new File('$buildTools/zipalign');
_jarsigner = 'jarsigner';
}
void package(File androidManifest, Directory assets, File outputApk) {
_run(_aapt.path, [
'package',
'-M', androidManifest.path,
'-A', assets.path,
'-I', _androidJar.path,
'-F', outputApk.path,
]);
}
void add(Directory base, String resource, File outputApk) {
_run(_aapt.path, [
'add', '-f', outputApk.absolute.path, resource,
], workingDirectory: base.path);
}
void sign(File keystore, String keystorePassword, String keyName, File outputApk) {
_run(_jarsigner, [
'-keystore', keystore.path,
'-storepass', keystorePassword,
outputApk.path,
keyName,
]);
}
void align(File unalignedApk, File outputApk) {
_run(_zipalign.path, ['4', unalignedApk.path, outputApk.path]);
}
void _run(String command, List<String> args, { String workingDirectory }) {
ProcessResult result = Process.runSync(
command, args, workingDirectory: workingDirectory);
if (result.exitCode == 0)
return;
stdout.write(result.stdout);
stderr.write(result.stderr);
}
}
main(List<String> argv) async {
ArgParser parser = new ArgParser();
parser.addFlag('help', abbr: 'h', negatable: false);
parser.addOption('android-sdk');
parser.addOption('skyx');
ArgResults args = parser.parse(argv);
if (args['help']) {
print('usage: pub run sky_tools:build_sky_apk <options>');
print('');
print(parser.usage);
return;
}
Directory artifacts = new Directory('artifacts');
File keystore = new File('${artifacts.path}/chromium-debug.keystore');
File androidManifest = new File('${artifacts.path}/AndroidManifest.xml');
File icuData = new File('${artifacts.path}/assets/icudtl.dat');
File appSkyx = new File(args['skyx']);
Directory outDir = new Directory('out');
outDir.createSync(recursive: true);
AssetBuilder assetBuilder = new AssetBuilder(outDir);
assetBuilder.add(icuData, 'icudtl.dat');
assetBuilder.add(appSkyx, 'app.skyx');
ApkBuilder builder = new ApkBuilder(args['android-sdk']);
File unalignedApk = new File('${outDir.path}/Example.apk.unaligned');
File finalApk = new File('${outDir.path}/Example.apk');
builder.package(androidManifest, assetBuilder.directory, unalignedApk);
builder.add(artifacts, 'classes.dex', unalignedApk);
builder.add(artifacts, 'lib/armeabi-v7a/libsky_shell.so', unalignedApk);
builder.sign(keystore, kKeystorePassword, kKeystoreKeyName, unalignedApk);
builder.align(unalignedApk, finalApk);
}
// 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.
import 'dart:io';
import 'package:args/args.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_route/shelf_route.dart' as shelf_route;
import 'package:shelf_static/shelf_static.dart';
void printUsage(parser) {
print('Usage: sky_server [-v] PORT');
print(parser.usage);
}
void addRoute(var router, String route, String path) {
router.add(
route,
['GET', 'HEAD'],
createStaticHandler(
path,
serveFilesOutsidePath: true,
listDirectories: true
), exactMatch: false
);
}
main(List<String> argv) async {
ArgParser parser = new ArgParser();
parser.addFlag('help', abbr: 'h', negatable: false,
help: 'Display this help message.');
parser.addFlag('verbose', abbr: 'v', negatable: false,
help: 'Log requests to stdout.');
parser.addOption('route', allowMultiple: true, splitCommas: false,
help: 'Adds a virtual directory to the root.');
ArgResults args = parser.parse(argv);
if (args['help'] || args.rest.length != 1) {
printUsage(parser);
return;
}
int port;
try {
port = int.parse(args.rest[0]);
} catch(e) {
printUsage(parser);
return;
}
var router = shelf_route.router();
if (args['route'] != null) {
for (String arg in args['route']) {
List<String> parsedArgs = arg.split(',');
addRoute(router, parsedArgs[0], parsedArgs[1]);
}
}
addRoute(router, '/', Directory.current.path);
var handler = router.handler;
if (args['verbose'])
handler = const Pipeline().addMiddleware(logRequests()).addHandler(handler);
HttpServer server;
try {
server = await io.serve(handler, InternetAddress.LOOPBACK_IP_V4, port);
print('Serving ${Directory.current.absolute.path} from '
'http://${server.address.address}:${server.port}.');
} catch(e) {
print(e);
exit(1);
}
server.defaultResponseHeaders
..removeAll('x-content-type-options')
..removeAll('x-frame-options')
..removeAll('x-xss-protection')
..add('cache-control', 'no-store');
}
// 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.
import 'package:sky_tools/src/test/loader.dart' as loader;
import 'package:test/src/executable.dart' as executable;
main(List<String> args) {
loader.installHook();
return executable.main(args);
}
// 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.
import 'package:sky_tools/executable.dart' as executable;
main(List<String> args) => executable.main(args);
// 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.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:stack_trace/stack_trace.dart';
import 'src/commands/build.dart';
import 'src/commands/cache.dart';
import 'src/commands/daemon.dart';
import 'src/commands/flutter_command_runner.dart';
import 'src/commands/init.dart';
import 'src/commands/install.dart';
import 'src/commands/list.dart';
import 'src/commands/listen.dart';
import 'src/commands/logs.dart';
import 'src/commands/run_mojo.dart';
import 'src/commands/start.dart';
import 'src/commands/stop.dart';
import 'src/commands/trace.dart';
import 'src/process.dart';
/// Main entry point for commands.
///
/// This function is intended to be used from the [flutter] command line tool.
Future main(List<String> args) async {
// This level can be adjusted by users through the `--verbose` option.
Logger.root.level = Level.WARNING;
Logger.root.onRecord.listen((LogRecord record) {
if (record.level >= Level.WARNING) {
stderr.writeln(record.message);
} else {
print(record.message);
}
if (record.error != null)
stderr.writeln(record.error);
if (record.stackTrace != null)
stderr.writeln(record.stackTrace);
});
FlutterCommandRunner runner = new FlutterCommandRunner()
..addCommand(new BuildCommand())
..addCommand(new CacheCommand())
..addCommand(new DaemonCommand())
..addCommand(new InitCommand())
..addCommand(new InstallCommand())
..addCommand(new ListCommand())
..addCommand(new ListenCommand())
..addCommand(new LogsCommand())
..addCommand(new RunMojoCommand())
..addCommand(new StartCommand())
..addCommand(new StopCommand())
..addCommand(new TraceCommand());
return Chain.capture(() async {
dynamic result = await runner.run(args);
if (result is int)
exit(result);
}, onError: (error, Chain chain) {
if (error is UsageException) {
stderr.writeln(error);
// Argument error exit code.
exit(64);
} else if (error is ProcessExit) {
// We've caught an exit code.
exit(error.exitCode);
} else {
stderr.writeln(error);
Logger.root.log(Level.SEVERE, '\nException:', null, chain.terse.toTrace());
exit(1);
}
});
}
// 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.
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'artifacts.dart';
import 'build_configuration.dart';
final Logger _logging = new Logger('sky_tools.application_package');
abstract class ApplicationPackage {
/// Path to the actual apk or bundle.
final String localPath;
/// Package ID from the Android Manifest or equivalent.
final String id;
/// File name of the apk or bundle.
final String name;
ApplicationPackage({
String localPath,
this.id
}) : localPath = localPath, name = path.basename(localPath) {
assert(localPath != null);
assert(id != null);
}
}
class AndroidApk extends ApplicationPackage {
static const String _defaultName = 'SkyShell.apk';
static const String _defaultId = 'org.domokit.sky.shell';
static const String _defaultLaunchActivity = '$_defaultId/$_defaultId.SkyActivity';
/// The path to the activity that should be launched.
/// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity'
final String launchActivity;
AndroidApk({
String localPath,
String id: _defaultId,
this.launchActivity: _defaultLaunchActivity
}) : super(localPath: localPath, id: id) {
assert(launchActivity != null);
}
}
class IOSApp extends ApplicationPackage {
static const String _defaultName = 'SkyShell.app';
static const String _defaultId = 'com.google.SkyShell';
IOSApp({
String localPath,
String id: _defaultId
}) : super(localPath: localPath, id: id);
}
class ApplicationPackageStore {
final AndroidApk android;
final IOSApp iOS;
final IOSApp iOSSimulator;
ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator });
ApplicationPackage getPackageForPlatform(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return iOS;
case TargetPlatform.iOSSimulator:
return iOSSimulator;
case TargetPlatform.linux:
return null;
}
}
static Future<ApplicationPackageStore> forConfigs(List<BuildConfiguration> configs) async {
AndroidApk android;
IOSApp iOS;
IOSApp iOSSimulator;
for (BuildConfiguration config in configs) {
switch (config.targetPlatform) {
case TargetPlatform.android:
assert(android == null);
if (config.type != BuildType.prebuilt) {
String localPath = path.join(config.buildDir, 'apks', AndroidApk._defaultName);
android = new AndroidApk(localPath: localPath);
} else {
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.shell, targetPlatform: TargetPlatform.android);
android = new AndroidApk(localPath: await ArtifactStore.getPath(artifact));
}
break;
case TargetPlatform.iOS:
assert(iOS == null);
assert(config.type != BuildType.prebuilt);
iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break;
case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null);
assert(config.type != BuildType.prebuilt);
iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break;
case TargetPlatform.linux:
break;
}
}
return new ApplicationPackageStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
}
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'build_configuration.dart';
import 'os_utils.dart';
final Logger _logging = new Logger('sky_tools.artifacts');
const String _kShellCategory = 'shell';
const String _kViewerCategory = 'viewer';
String _getNameForHostPlatform(HostPlatform platform) {
switch (platform) {
case HostPlatform.linux:
return 'linux-x64';
case HostPlatform.mac:
return 'darwin-x64';
}
}
String _getNameForTargetPlatform(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
return 'android-arm';
case TargetPlatform.iOS:
return 'ios-arm';
case TargetPlatform.iOSSimulator:
return 'ios-x64';
case TargetPlatform.linux:
return 'linux-x64';
}
}
// Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/release_engine.py
// and https://github.com/flutter/buildbot/blob/master/travis/build.sh
String _getCloudStorageBaseUrl({String category, String platform, String revision}) {
if (platform == 'darwin-x64') {
// In the fullness of time, we'll have a consistent URL pattern for all of
// our artifacts, but, for the time being, Mac OS X artifacts are stored in a
// different cloud storage bucket.
return 'https://storage.googleapis.com/mojo_infra/flutter/${platform}/${revision}/';
}
return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${revision}/';
}
enum ArtifactType {
snapshot,
shell,
viewer,
}
class Artifact {
const Artifact._({
this.name,
this.fileName,
this.category,
this.type,
this.hostPlatform,
this.targetPlatform
});
final String name;
final String fileName;
final String category; // TODO(abarth): Remove categories.
final ArtifactType type;
final HostPlatform hostPlatform;
final TargetPlatform targetPlatform;
String get platform {
if (targetPlatform != null)
return _getNameForTargetPlatform(targetPlatform);
if (hostPlatform != null)
return _getNameForHostPlatform(hostPlatform);
assert(false);
return null;
}
String getUrl(String revision) {
return _getCloudStorageBaseUrl(
category: category,
platform: platform,
revision: revision
) + fileName;
}
// Whether the artifact needs to be marked as executable on disk.
bool get executable => type == ArtifactType.snapshot;
}
class ArtifactStore {
static const List<Artifact> knownArtifacts = const <Artifact>[
const Artifact._(
name: 'Sky Shell',
fileName: 'SkyShell.apk',
category: _kShellCategory,
type: ArtifactType.shell,
targetPlatform: TargetPlatform.android
),
const Artifact._(
name: 'Sky Snapshot',
fileName: 'sky_snapshot',
category: _kShellCategory,
type: ArtifactType.snapshot,
hostPlatform: HostPlatform.linux
),
const Artifact._(
name: 'Sky Snapshot',
fileName: 'sky_snapshot',
category: _kShellCategory,
type: ArtifactType.snapshot,
hostPlatform: HostPlatform.mac
),
const Artifact._(
name: 'Sky Viewer',
fileName: 'sky_viewer.mojo',
category: _kViewerCategory,
type: ArtifactType.viewer,
targetPlatform: TargetPlatform.android
),
const Artifact._(
name: 'Sky Viewer',
fileName: 'sky_viewer.mojo',
category: _kViewerCategory,
type: ArtifactType.viewer,
targetPlatform: TargetPlatform.linux
),
];
static Artifact getArtifact({
ArtifactType type,
HostPlatform hostPlatform,
TargetPlatform targetPlatform
}) {
for (Artifact artifact in ArtifactStore.knownArtifacts) {
if (type != null &&
type != artifact.type)
continue;
if (hostPlatform != null &&
artifact.hostPlatform != null &&
hostPlatform != artifact.hostPlatform)
continue;
if (targetPlatform != null &&
artifact.targetPlatform != null &&
targetPlatform != artifact.targetPlatform)
continue;
return artifact;
}
return null;
}
static String packageRoot;
static String _engineRevision;
static String get engineRevision {
if (_engineRevision == null) {
File revisionFile = new File(path.join(packageRoot, 'sky_engine', 'REVISION'));
if (revisionFile.existsSync())
_engineRevision = revisionFile.readAsStringSync();
}
return _engineRevision;
}
static String getCloudStorageBaseUrl(String category, String platform) {
return _getCloudStorageBaseUrl(
category: category,
platform: platform,
revision: engineRevision
);
}
static Future _downloadFile(String url, File file) async {
_logging.info('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 statusCode=${response.statusCode}');
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 Directory _getBaseCacheDir() {
Directory cacheDir = new Directory(path.join(packageRoot, 'sky_tools', 'cache'));
if (!cacheDir.existsSync())
cacheDir.createSync(recursive: true);
return cacheDir;
}
static Directory _getCacheDirForArtifact(Artifact artifact) {
Directory baseDir = _getBaseCacheDir();
// 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 artifactSpecificDir = new Directory(path.join(
baseDir.path, 'sky_engine', engineRevision, config, artifact.platform));
if (!artifactSpecificDir.existsSync())
artifactSpecificDir.createSync(recursive: true);
return artifactSpecificDir;
}
static Future<String> getPath(Artifact artifact) async {
Directory cacheDir = _getCacheDirForArtifact(artifact);
File cachedFile = new File(path.join(cacheDir.path, artifact.fileName));
if (!cachedFile.existsSync()) {
print('Downloading ${artifact.name} from the cloud, one moment please...');
await _downloadFile(artifact.getUrl(engineRevision), cachedFile);
if (artifact.executable) {
ProcessResult result = osUtils.makeExecutable(cachedFile);
if (result.exitCode != 0)
throw new Exception(result.stderr);
}
}
return cachedFile.path;
}
static void clear() {
Directory cacheDir = _getBaseCacheDir();
_logging.fine('Clearing cache directory ${cacheDir.path}');
cacheDir.deleteSync(recursive: true);
}
static Future populate() {
return Future.wait(knownArtifacts.map((artifact) => getPath(artifact)));
}
}
// 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.
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
final Logger _logging = new Logger('sky_tools.build_configuration');
enum BuildType {
prebuilt,
release,
debug,
}
enum HostPlatform {
mac,
linux,
}
enum TargetPlatform {
android,
iOS,
iOSSimulator,
linux,
}
HostPlatform getCurrentHostPlatform() {
if (Platform.isMacOS)
return HostPlatform.mac;
if (Platform.isLinux)
return HostPlatform.linux;
_logging.warning('Unsupported host platform, defaulting to Linux');
return HostPlatform.linux;
}
class BuildConfiguration {
BuildConfiguration.prebuilt({ this.hostPlatform, this.targetPlatform })
: type = BuildType.prebuilt, buildDir = null;
BuildConfiguration.local({
this.type,
this.hostPlatform,
this.targetPlatform,
String enginePath,
String buildPath
}) : buildDir = path.normalize(path.join(enginePath, buildPath)) {
assert(type == BuildType.debug || type == BuildType.release);
}
final BuildType type;
final HostPlatform hostPlatform;
final TargetPlatform targetPlatform;
final String buildDir;
}
// 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.
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:flx/bundle.dart';
import 'package:flx/signing.dart';
import 'package:yaml/yaml.dart';
import '../toolchain.dart';
import 'flutter_command.dart';
const String _kSnapshotKey = 'snapshot_blob.bin';
const List<String> _kDensities = const ['drawable-xxhdpi'];
const List<String> _kThemes = const ['white', 'black'];
const List<int> _kSizes = const [24];
class _Asset {
final String base;
final String key;
_Asset({ this.base, this.key });
}
Iterable<_Asset> _parseAssets(Map manifestDescriptor, String manifestPath) sync* {
if (manifestDescriptor == null || !manifestDescriptor.containsKey('assets'))
return;
String basePath = new File(manifestPath).parent.path;
for (String asset in manifestDescriptor['assets'])
yield new _Asset(base: basePath, key: asset);
}
class _MaterialAsset {
final String name;
final String density;
final String theme;
final int size;
_MaterialAsset(Map descriptor)
: name = descriptor['name'],
density = descriptor['density'],
theme = descriptor['theme'],
size = descriptor['size'];
String get key {
List<String> parts = name.split('/');
String category = parts[0];
String subtype = parts[1];
return '$category/$density/ic_${subtype}_${theme}_${size}dp.png';
}
}
List _generateValues(Map assetDescriptor, String key, List defaults) {
if (assetDescriptor.containsKey(key))
return [assetDescriptor[key]];
return defaults;
}
Iterable<_MaterialAsset> _generateMaterialAssets(Map assetDescriptor) sync* {
Map currentAssetDescriptor = new Map.from(assetDescriptor);
for (String density in _generateValues(assetDescriptor, 'density', _kDensities)) {
currentAssetDescriptor['density'] = density;
for (String theme in _generateValues(assetDescriptor, 'theme', _kThemes)) {
currentAssetDescriptor['theme'] = theme;
for (int size in _generateValues(assetDescriptor, 'size', _kSizes)) {
currentAssetDescriptor['size'] = size;
yield new _MaterialAsset(currentAssetDescriptor);
}
}
}
}
Iterable<_MaterialAsset> _parseMaterialAssets(Map manifestDescriptor) sync* {
if (manifestDescriptor == null || !manifestDescriptor.containsKey('material-design-icons'))
return;
for (Map assetDescriptor in manifestDescriptor['material-design-icons']) {
for (_MaterialAsset asset in _generateMaterialAssets(assetDescriptor)) {
yield asset;
}
}
}
dynamic _loadManifest(String manifestPath) {
if (manifestPath == null || !FileSystemEntity.isFileSync(manifestPath))
return null;
String manifestDescriptor = new File(manifestPath).readAsStringSync();
return loadYaml(manifestDescriptor);
}
ArchiveFile _createFile(String key, String assetBase) {
File file = new File('${assetBase}/${key}');
if (!file.existsSync())
return null;
List<int> content = file.readAsBytesSync();
return new ArchiveFile.noCompress(key, content.length, content);
}
ArchiveFile _createSnapshotFile(String snapshotPath) {
File file = new File(snapshotPath);
List<int> content = file.readAsBytesSync();
return new ArchiveFile(_kSnapshotKey, content.length, content);
}
const String _kDefaultAssetBase = 'packages/material_design_icons/icons';
const String _kDefaultMainPath = 'lib/main.dart';
const String _kDefaultManifestPath = 'flutter.yaml';
const String _kDefaultOutputPath = 'app.flx';
const String _kDefaultSnapshotPath = 'snapshot_blob.bin';
const String _kDefaultPrivateKeyPath = 'privatekey.der';
class BuildCommand extends FlutterCommand {
final String name = 'build';
final String description = 'Create a Flutter app.';
BuildCommand() {
argParser.addFlag('precompiled', negatable: false);
argParser.addOption('asset-base', defaultsTo: _kDefaultAssetBase);
argParser.addOption('compiler');
argParser.addOption('main', defaultsTo: _kDefaultMainPath);
argParser.addOption('manifest', defaultsTo: _kDefaultManifestPath);
argParser.addOption('private-key', defaultsTo: _kDefaultPrivateKeyPath);
argParser.addOption('output-file', abbr: 'o', defaultsTo: _kDefaultOutputPath);
argParser.addOption('snapshot', defaultsTo: _kDefaultSnapshotPath);
}
@override
Future<int> runInProject() async {
String compilerPath = argResults['compiler'];
if (compilerPath == null)
await downloadToolchain();
else
toolchain = new Toolchain(compiler: new Compiler(compilerPath));
return await build(
assetBase: argResults['asset-base'],
mainPath: argResults['main'],
manifestPath: argResults['manifest'],
outputPath: argResults['output-file'],
snapshotPath: argResults['snapshot'],
privateKeyPath: argResults['private-key'],
precompiledSnapshot: argResults['precompiled']
);
}
Future<int> build({
String assetBase: _kDefaultAssetBase,
String mainPath: _kDefaultMainPath,
String manifestPath: _kDefaultManifestPath,
String outputPath: _kDefaultOutputPath,
String snapshotPath: _kDefaultSnapshotPath,
String privateKeyPath: _kDefaultPrivateKeyPath,
bool precompiledSnapshot: false
}) async {
Map manifestDescriptor = _loadManifest(manifestPath);
Iterable<_Asset> assets = _parseAssets(manifestDescriptor, manifestPath);
Iterable<_MaterialAsset> materialAssets = _parseMaterialAssets(manifestDescriptor);
Archive archive = new Archive();
if (!precompiledSnapshot) {
// In a precompiled snapshot, the instruction buffer contains script
// content equivalents
int result = await toolchain.compiler.compile(mainPath: mainPath, snapshotPath: snapshotPath);
if (result != 0)
return result;
archive.addFile(_createSnapshotFile(snapshotPath));
}
for (_Asset asset in assets)
archive.addFile(_createFile(asset.key, asset.base));
for (_MaterialAsset asset in materialAssets) {
ArchiveFile file = _createFile(asset.key, assetBase);
if (file != null)
archive.addFile(file);
}
await CipherParameters.get().seedRandom();
AsymmetricKeyPair keyPair = keyPairFromPrivateKeyFileSync(privateKeyPath);
Uint8List zipBytes = new Uint8List.fromList(new ZipEncoder().encode(archive));
Bundle bundle = new Bundle.fromContent(
path: outputPath,
manifest: manifestDescriptor,
contentBytes: zipBytes,
keyPair: keyPair
);
bundle.writeSync();
return 0;
}
}
// 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.
import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import '../artifacts.dart';
final Logger _logging = new Logger('sky_tools.cache');
class CacheCommand extends Command {
final String name = 'cache';
final String description = 'Manages sky_tools\' cache of binary artifacts.';
CacheCommand() {
addSubcommand(new _ClearCommand());
addSubcommand(new _PopulateCommand());
}
}
class _ClearCommand extends Command {
final String name = 'clear';
final String description = 'Clears all artifacts from the cache.';
@override
Future<int> run() async {
await ArtifactStore.clear();
return 0;
}
}
class _PopulateCommand extends Command {
final String name = 'populate';
final String description = 'Populates the cache with all known artifacts.';
@override
Future<int> run() async {
await ArtifactStore.populate();
return 0;
}
}
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
import 'flutter_command.dart';
import 'start.dart';
import 'stop.dart';
const String protocolVersion = '0.0.1';
/// A @domain annotation.
const String domain = 'domain';
/// A domain @command annotation.
const String command = 'command';
final Logger _logging = new Logger('sky_tools.daemon');
// TODO: Create a `device` domain in order to list devices and fire events when
// devices are added or removed.
// TODO: Is this the best name? Server? Daemon?
/// A server process command. This command will start up a long-lived server.
/// It reads JSON-RPC based commands from stdin, executes them, and returns
/// JSON-RPC based responses and events to stdout.
///
/// It can be shutdown with a `daemon.shutdown` command (or by killing the
/// process).
class DaemonCommand extends FlutterCommand {
final String name = 'daemon';
final String description =
'Run a persistent, JSON-RPC based server to communicate with devices.';
final String usageFooter =
'\nThis command is intended to be used by tooling environments that need '
'a programatic interface into launching Flutter applications.';
@override
Future<int> runInProject() async {
print('Starting device daemon...');
Stream<Map> commandStream = stdin
.transform(UTF8.decoder)
.transform(const LineSplitter())
.where((String line) => line.startsWith('[{') && line.endsWith('}]'))
.map((String line) {
line = line.substring(1, line.length - 1);
return JSON.decode(line);
});
await downloadApplicationPackagesAndConnectToDevices();
Daemon daemon = new Daemon(commandStream, (Map command) {
stdout.writeln('[${JSON.encode(command)}]');
}, daemonCommand: this);
return daemon.onExit;
}
}
typedef void DispatchComand(Map command);
typedef Future<dynamic> CommandHandler(dynamic args);
class Daemon {
final DispatchComand sendCommand;
final DaemonCommand daemonCommand;
final Completer<int> _onExitCompleter = new Completer();
final Map<String, Domain> _domains = {};
Daemon(Stream<Map> commandStream, this.sendCommand, {this.daemonCommand}) {
// Set up domains.
_registerDomain(new DaemonDomain(this));
_registerDomain(new AppDomain(this));
// Start listening.
commandStream.listen(
(Map command) => _handleCommand(command),
onDone: () => _onExitCompleter.complete(0)
);
}
void _registerDomain(Domain domain) {
_domains[domain.name] = domain;
}
Future<int> get onExit => _onExitCompleter.future;
void _handleCommand(Map command) {
// {id, event, params}
var id = command['id'];
if (id == null) {
_logging.severe('no id for command: ${command}');
return;
}
try {
String event = command['event'];
if (event.indexOf('.') == -1)
throw 'command not understood: ${event}';
String prefix = event.substring(0, event.indexOf('.'));
String name = event.substring(event.indexOf('.') + 1);
if (_domains[prefix] == null)
throw 'no domain for command: ${command}';
_domains[prefix].handleEvent(name, id, command['params']);
} catch (error, trace) {
_send({'id': id, 'error': _toJsonable(error)});
_logging.warning('error handling ${command['event']}', error, trace);
}
}
void _send(Map map) => sendCommand(map);
void shutdown() {
if (!_onExitCompleter.isCompleted)
_onExitCompleter.complete(0);
}
}
abstract class Domain {
final Daemon daemon;
final String name;
final Map<String, CommandHandler> _handlers = {};
Domain(this.daemon, this.name);
void registerHandler(String name, CommandHandler handler) {
_handlers[name] = handler;
}
String toString() => name;
void handleEvent(String name, dynamic id, dynamic args) {
new Future.sync(() {
if (_handlers.containsKey(name))
return _handlers[name](args);
throw 'command not understood: ${name}';
}).then((result) {
if (result == null) {
_send({'id': id});
} else {
_send({'id': id, 'result': _toJsonable(result)});
}
}).catchError((error, trace) {
_send({'id': id, 'error': _toJsonable(error)});
_logging.warning('error handling ${name}', error, trace);
});
}
void _send(Map map) => daemon._send(map);
}
/// This domain responds to methods like [version] and [shutdown].
@domain
class DaemonDomain extends Domain {
DaemonDomain(Daemon daemon) : super(daemon, 'daemon') {
registerHandler('version', version);
registerHandler('shutdown', shutdown);
}
@command
Future<dynamic> version(dynamic args) {
return new Future.value(protocolVersion);
}
@command
Future<dynamic> shutdown(dynamic args) {
Timer.run(() => daemon.shutdown());
return new Future.value();
}
}
/// This domain responds to methods like [start] and [stopAll].
///
/// It'll be extended to fire events for when applications start, stop, and
/// log data.
@domain
class AppDomain extends Domain {
AppDomain(Daemon daemon) : super(daemon, 'app') {
registerHandler('start', start);
registerHandler('stopAll', stopAll);
}
@command
Future<dynamic> start(dynamic args) {
// TODO: Add the ability to pass args: target, http, checked
StartCommand startComand = new StartCommand();
startComand.inheritFromParent(daemon.daemonCommand);
return startComand.runInProject().then((_) => null);
}
@command
Future<bool> stopAll(dynamic args) {
StopCommand stopCommand = new StopCommand();
stopCommand.inheritFromParent(daemon.daemonCommand);
return stopCommand.stop();
}
}
dynamic _toJsonable(dynamic obj) {
if (obj is String || obj is int || obj is bool || obj is Map || obj is List || obj == null)
return obj;
return '${obj}';
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import '../application_package.dart';
import '../device.dart';
import '../toolchain.dart';
import 'flutter_command_runner.dart';
abstract class FlutterCommand extends Command {
FlutterCommandRunner get runner => super.runner;
/// Whether this command needs to be run from the root of a project.
bool get requiresProjectRoot => true;
Future downloadApplicationPackages() async {
if (applicationPackages == null)
applicationPackages = await ApplicationPackageStore.forConfigs(runner.buildConfigurations);
}
Future downloadToolchain() async {
if (toolchain == null)
toolchain = await Toolchain.forConfigs(runner.buildConfigurations);
}
void connectToDevices() {
if (devices == null)
devices = new DeviceStore.forConfigs(runner.buildConfigurations);
}
Future downloadApplicationPackagesAndConnectToDevices() async {
await downloadApplicationPackages();
connectToDevices();
}
void inheritFromParent(FlutterCommand other) {
applicationPackages = other.applicationPackages;
toolchain = other.toolchain;
devices = other.devices;
}
Future<int> run() async {
if (requiresProjectRoot) {
if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
stderr.writeln('No pubspec.yaml file found. '
'This command should be run from the root of a project.');
return 1;
}
}
return runInProject();
}
Future<int> runInProject();
ApplicationPackageStore applicationPackages;
Toolchain toolchain;
DeviceStore devices;
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import '../artifacts.dart';
import '../build_configuration.dart';
import '../process.dart';
final Logger _logging = new Logger('sky_tools.flutter_command_runner');
class FlutterCommandRunner extends CommandRunner {
FlutterCommandRunner()
: super('flutter', 'Manage your Flutter app development.') {
argParser.addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Noisy logging, including all shell commands executed.');
argParser.addFlag('very-verbose',
negatable: false,
help: 'Very noisy logging, including the output of all '
'shell commands executed.');
argParser.addOption('package-root',
help: 'Path to your packages directory.', defaultsTo: 'packages');
argParser.addSeparator('Local build selection options:');
argParser.addFlag('debug',
negatable: false,
help:
'Set this if you are building Flutter locally and want to use the debug build products. '
'When set, attempts to automaticaly determine engine-src-path if engine-src-path is '
'not set. Not normally required.');
argParser.addFlag('release',
negatable: false,
help:
'Set this if you are building Flutter locally and want to use the release build products. '
'When set, attempts to automaticaly determine engine-src-path if engine-src-path is '
'not set. Note that release is not compatible with the listen command '
'on iOS devices and simulators. Not normally required.');
argParser.addFlag('local-build',
negatable: false,
help:
'Automatically detect your engine src directory from an overridden Flutter package.'
'Useful if you are building Flutter locally and are using a dependency_override for'
'the Flutter package that points to your engine src directory.');
argParser.addOption('engine-src-path', hide: true,
help:
'Path to your engine src directory, if you are building Flutter locally. '
'Ignored if neither debug nor release is set. Not normally required.');
argParser.addOption('android-debug-build-path', hide: true,
help:
'Path to your Android Debug out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/android_Debug/');
argParser.addOption('android-release-build-path', hide: true,
help:
'Path to your Android Release out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/android_Release/');
argParser.addOption('ios-debug-build-path', hide: true,
help:
'Path to your iOS Debug out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_Debug/');
argParser.addOption('ios-release-build-path', hide: true,
help:
'Path to your iOS Release out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_Release/');
argParser.addOption('ios-sim-debug-build-path', hide: true,
help:
'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Debug/');
argParser.addOption('ios-sim-release-build-path', hide: true,
help:
'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Release/');
}
List<BuildConfiguration> get buildConfigurations {
if (_buildConfigurations == null)
_buildConfigurations = _createBuildConfigurations(_globalResults);
return _buildConfigurations;
}
List<BuildConfiguration> _buildConfigurations;
ArgResults _globalResults;
Future<int> runCommand(ArgResults globalResults) {
if (globalResults['verbose'])
Logger.root.level = Level.INFO;
if (globalResults['very-verbose'])
Logger.root.level = Level.FINE;
_globalResults = globalResults;
ArtifactStore.packageRoot = globalResults['package-root'];
return super.runCommand(globalResults);
}
List<BuildConfiguration> _createBuildConfigurations(ArgResults globalResults) {
if (!FileSystemEntity.isDirectorySync(ArtifactStore.packageRoot)) {
String message = '${ArtifactStore.packageRoot} is not a valid directory.';
if (ArtifactStore.packageRoot == 'packages') {
if (FileSystemEntity.isFileSync('pubspec.yaml'))
message += '\nDid you run `pub get` in this directory?';
else
message += '\nDid you run this command from the same directory as your pubspec.yaml file?';
}
stderr.writeln(message);
throw new ProcessExit(2);
}
String enginePath = globalResults['engine-src-path'];
bool isDebug = globalResults['debug'];
bool isRelease = globalResults['release'];
HostPlatform hostPlatform = getCurrentHostPlatform();
if (enginePath == null && globalResults['local-build']) {
Directory flutterDir = new Directory(path.join(globalResults['package-root'], 'flutter'));
String realFlutterPath = flutterDir.resolveSymbolicLinksSync();
enginePath = path.dirname(path.dirname(path.dirname(path.dirname(realFlutterPath))));
bool dirExists = FileSystemEntity.isDirectorySync(path.join(enginePath, 'out'));
if (enginePath == '/' || enginePath.isEmpty || !dirExists) {
stderr.writeln('Unable to detect local build in $enginePath.\n'
'Do you have a dependency override for the flutter package?');
throw new ProcessExit(2);
}
}
List<BuildConfiguration> configs = <BuildConfiguration>[];
if (enginePath == null) {
configs.add(new BuildConfiguration.prebuilt(
hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android));
} else {
if (!FileSystemEntity.isDirectorySync(enginePath))
_logging.warning('$enginePath is not a valid directory');
if (!isDebug && !isRelease)
isDebug = true;
if (isDebug) {
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.android,
enginePath: enginePath,
buildPath: globalResults['android-debug-build-path']
));
if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOS,
enginePath: enginePath,
buildPath: globalResults['ios-debug-build-path']
));
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOSSimulator,
enginePath: enginePath,
buildPath: globalResults['ios-sim-debug-build-path']
));
}
}
if (isRelease) {
configs.add(new BuildConfiguration.local(
type: BuildType.release,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.android,
enginePath: enginePath,
buildPath: globalResults['android-release-build-path']
));
if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local(
type: BuildType.release,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOS,
enginePath: enginePath,
buildPath: globalResults['ios-release-build-path']
));
configs.add(new BuildConfiguration.local(
type: BuildType.release,
hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOSSimulator,
enginePath: enginePath,
buildPath: globalResults['ios-sim-release-build-path']
));
}
}
}
return configs;
}
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:mustache4dart/mustache4dart.dart' as mustache;
import 'package:path/path.dart' as p;
import '../process.dart';
class InitCommand extends Command {
final String name = 'init';
final String description = 'Create a new Flutter project.';
InitCommand() {
argParser.addOption('out', abbr: 'o', help: 'The output directory.');
argParser.addFlag('pub',
defaultsTo: true,
help: 'Whether to run pub after the project has been created.');
}
@override
Future<int> run() async {
if (!argResults.wasParsed('out')) {
print('No option specified for the output directory.');
print(argParser.usage);
return 2;
}
// TODO: Confirm overwrite of an existing directory with the user.
Directory out = new Directory(argResults['out']);
new FlutterSimpleTemplate().generateInto(out);
print('');
String message = '''All done! To run your application:
\$ cd ${out.path}
\$ flutter start --checked
''';
if (argResults['pub']) {
print("Running pub get...");
Process process = await Process.start(
sdkBinaryName('pub'), ['get'], workingDirectory: out.path);
stdout.addStream(process.stdout);
stderr.addStream(process.stderr);
int code = await process.exitCode;
if (code != 0) return code;
}
print(message);
return 0;
}
}
abstract class Template {
final String name;
final String description;
Map<String, String> files = {};
Template(this.name, this.description);
void generateInto(Directory dir) {
String dirPath = p.normalize(dir.absolute.path);
String projectName = _normalizeProjectName(p.basename(dirPath));
print('Creating ${p.basename(projectName)}...');
dir.createSync(recursive: true);
files.forEach((String path, String contents) {
Map m = {'projectName': projectName, 'description': description};
contents = mustache.render(contents, m);
path = path.replaceAll('/', Platform.pathSeparator);
File file = new File(p.join(dir.path, path));
file.parent.createSync();
file.writeAsStringSync(contents);
print(file.path);
});
}
String toString() => name;
}
class FlutterSimpleTemplate extends Template {
FlutterSimpleTemplate() : super('flutter-simple', 'A minimal Flutter project.') {
files['.gitignore'] = _gitignore;
files['pubspec.yaml'] = _pubspec;
files['README.md'] = _readme;
files['lib/main.dart'] = _libMain;
}
}
String _normalizeProjectName(String name) {
name = name.replaceAll('-', '_').replaceAll(' ', '_');
// Strip any extension (like .dart).
if (name.contains('.')) {
name = name.substring(0, name.indexOf('.'));
}
return name;
}
const String _gitignore = r'''
.DS_Store
.idea
.packages
.pub/
build/
packages
pubspec.lock
''';
const String _readme = r'''
# {{projectName}}
{{description}}
## Getting Started
For help getting started with Flutter, view our online
[documentation](http://flutter.io/).
''';
const String _pubspec = r'''
name: {{projectName}}
description: {{description}}
dependencies:
flutter: ">=0.0.2 <0.1.0"
dev_dependencies:
sky_tools: any
''';
const String _libMain = r'''
import 'package:flutter/material.dart';
void main() {
runApp(
new MaterialApp(
title: "Flutter Demo",
routes: {
'/': (RouteArguments args) => new FlutterDemo()
}
)
);
}
class FlutterDemo extends StatelessComponent {
Widget build(BuildContext context) {
return new Scaffold(
toolBar: new ToolBar(
center: new Text("Flutter Demo")
),
body: new Material(
child: new Center(
child: new Text("Hello world!")
)
),
floatingActionButton: new FloatingActionButton(
child: new Icon(
icon: 'content/add'
)
)
);
}
}
''';
// 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.
import 'dart:async';
import '../application_package.dart';
import '../device.dart';
import 'flutter_command.dart';
class InstallCommand extends FlutterCommand {
final String name = 'install';
final String description = 'Install Flutter apps on attached devices.';
InstallCommand() {
argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.');
}
@override
Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices();
return install(boot: argResults['boot']) ? 0 : 2;
}
bool install({ bool boot: false }) {
if (boot)
devices.iOSSimulator?.boot();
bool installedSomewhere = false;
for (Device device in devices.all) {
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
if (package == null || !device.isConnected() || device.isAppInstalled(package))
continue;
if (device.installApp(package))
installedSomewhere = true;
}
return installedSomewhere;
}
}
// 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.
import 'dart:async';
import 'package:logging/logging.dart';
import '../device.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.list');
class ListCommand extends FlutterCommand {
final String name = 'list';
final String description = 'List all connected devices.';
ListCommand() {
argParser.addFlag('details',
abbr: 'd',
negatable: false,
help: 'Log additional details about attached devices.');
}
@override
Future<int> runInProject() async {
connectToDevices();
bool details = argResults['details'];
if (details)
print('Android Devices:');
for (AndroidDevice device in AndroidDevice.getAttachedDevices(devices.android)) {
if (details) {
print('${device.id}\t'
'${device.modelID}\t'
'${device.productID}\t'
'${device.deviceCodeName}');
} else {
print(device.id);
}
}
if (details)
print('iOS Devices:');
for (IOSDevice device in IOSDevice.getAttachedDevices(devices.iOS)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
print(device.id);
}
}
if (details) {
print('iOS Simulators:');
}
for (IOSSimulator device in IOSSimulator.getAttachedDevices(devices.iOSSimulator)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
print(device.id);
}
}
return 0;
}
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart';
import '../application_package.dart';
import '../device.dart';
import '../process.dart';
import 'build.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.listen');
class ListenCommand extends FlutterCommand {
final String name = 'listen';
final String description = 'Listen for changes to files and reload the running app on all connected devices.';
List<String> watchCommand;
/// Only run once. Used for testing.
bool singleRun;
ListenCommand({ this.singleRun: false }) {
argParser.addFlag('checked',
negatable: true,
defaultsTo: true,
help: 'Toggle Dart\'s checked mode.');
argParser.addOption('target',
defaultsTo: '.',
abbr: 't',
help: 'Target app path or filename to start.');
}
static const String _localFlutterBundle = 'app.flx';
static const String _remoteFlutterBundle = 'Documents/app.flx';
@override
Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices();
await downloadToolchain();
if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest);
} else {
watchCommand = _initWatchCommand(['.']);
}
while (true) {
_logging.info('Updating running Flutter apps...');
BuildCommand builder = new BuildCommand();
builder.inheritFromParent(this);
builder.build(outputPath: _localFlutterBundle);
for (Device device in devices.all) {
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
if (package == null || !device.isConnected())
continue;
if (device is AndroidDevice) {
await devices.android.startServer(
argResults['target'], true, argResults['checked'], package);
} else if (device is IOSDevice) {
device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
} else if (device is IOSSimulator) {
// TODO(abarth): Move pushFile up to Device once Android supports
// pushing new bundles.
device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
} else {
assert(false);
}
}
if (singleRun || !watchDirectory())
break;
}
return 0;
}
List<String> _initWatchCommand(List<String> directories) {
if (Platform.isMacOS) {
try {
runCheckedSync(['which', 'fswatch']);
} catch (e) {
_logging.severe('"listen" command is only useful if you have installed '
'fswatch on Mac. Run "brew install fswatch" to install it with '
'homebrew.');
return null;
}
return ['fswatch', '-r', '-v', '-1']..addAll(directories);
} else if (Platform.isLinux) {
try {
runCheckedSync(['which', 'inotifywait']);
} catch (e) {
_logging.severe('"listen" command is only useful if you have installed '
'inotifywait on Linux. Run "apt-get install inotify-tools" or '
'equivalent to install it.');
return null;
}
return [
'inotifywait',
'-r',
'-e',
// Only listen for events that matter, to avoid triggering constantly
// from the editor watching files
'modify,close_write,move,create,delete',
]..addAll(directories);
} else {
_logging.severe('"listen" command is only available on Mac and Linux.');
}
return null;
}
bool watchDirectory() {
if (watchCommand == null)
return false;
try {
runCheckedSync(watchCommand);
} catch (e) {
_logging.warning('Watching directories failed.', e);
return false;
}
return true;
}
}
// 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.
import 'dart:async';
import 'package:logging/logging.dart';
import '../device.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.logs');
class LogsCommand extends FlutterCommand {
final String name = 'logs';
final String description = 'Show logs for running Sky apps.';
LogsCommand() {
argParser.addFlag('clear',
negatable: false,
help: 'Clear log history before reading from logs (Android only).');
}
@override
Future<int> runInProject() async {
connectToDevices();
bool clear = argResults['clear'];
Iterable<Future<int>> results = devices.all.map(
(Device device) => device.logs(clear: clear));
for (Future<int> result in results)
await result;
return 0;
}
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import '../artifacts.dart';
import '../build_configuration.dart';
import '../process.dart';
final Logger _logging = new Logger('sky_tools.run_mojo');
enum _MojoConfig { Debug, Release }
class RunMojoCommand extends Command {
final String name = 'run_mojo';
final String description = 'Run a Flutter app in mojo.';
RunMojoCommand() {
argParser.addFlag('android', negatable: false, help: 'Run on an Android device');
argParser.addFlag('checked', negatable: false, help: 'Run Flutter in checked mode');
argParser.addFlag('mojo-debug', negatable: false, help: 'Use Debug build of mojo');
argParser.addFlag('mojo-release', negatable: false, help: 'Use Release build of mojo (default)');
argParser.addOption('app', defaultsTo: 'app.flx');
argParser.addOption('mojo-path', help: 'Path to directory containing mojo_shell and services for Linux and to mojo devtools from Android.');
}
// TODO(abarth): Why not use path.absolute?
String _makePathAbsolute(String relativePath) {
File file = new File(relativePath);
if (!file.existsSync()) {
throw new Exception("Path \"${relativePath}\" does not exist");
}
return file.absolute.path;
}
Future<int> _runAndroid(String devtoolsPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) {
String skyViewerUrl = ArtifactStore.getCloudStorageBaseUrl('viewer', 'android-arm');
String command = _makePathAbsolute(devtoolsPath);
String appName = path.basename(appPath);
String appDir = path.dirname(appPath);
String buildFlag = mojoConfig == _MojoConfig.Debug ? '--debug' : '--release';
List<String> cmd = [
command,
'--android',
buildFlag,
'http://app/$appName',
'--map-origin=http://app/=$appDir',
'--map-origin=http://sky_viewer/=$skyViewerUrl',
'--url-mappings=mojo:sky_viewer=http://sky_viewer/sky_viewer.mojo',
];
if (_logging.level <= Level.INFO) {
cmd.add('--verbose');
if (_logging.level <= Level.FINE) {
cmd.add('--verbose');
}
}
cmd.addAll(additionalArgs);
return runCommandAndStreamOutput(cmd);
}
Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async {
Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.viewer, targetPlatform: TargetPlatform.linux);
String viewerPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release';
String mojoShellPath = _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell'));
List<String> cmd = [
mojoShellPath,
'file://${appPath}',
'--url-mappings=mojo:sky_viewer=file://${viewerPath}'
];
cmd.addAll(additionalArgs);
return runCommandAndStreamOutput(cmd);
}
@override
Future<int> run() async {
if (argResults['mojo-path'] == null) {
_logging.severe('Must specify --mojo-path.');
return 1;
}
if (argResults['mojo-debug'] && argResults['mojo-release']) {
_logging.severe('Cannot specify both --mojo-debug and --mojo-release');
return 1;
}
List<String> args = [];
if (argResults['checked']) {
args.add('--args-for=mojo:sky_viewer --enable-checked-mode');
}
String mojoPath = argResults['mojo-path'];
_MojoConfig mojoConfig = argResults['mojo-debug'] ? _MojoConfig.Debug : _MojoConfig.Release;
String appPath = _makePathAbsolute(argResults['app']);
args.addAll(argResults.rest);
if (argResults['android']) {
return _runAndroid(mojoPath, mojoConfig, appPath, args);
} else {
return _runLinux(mojoPath, mojoConfig, appPath, args);
}
}
}
// 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.
import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import '../application_package.dart';
import '../device.dart';
import 'build.dart';
import 'flutter_command.dart';
import 'install.dart';
import 'stop.dart';
final Logger _logging = new Logger('sky_tools.start');
const String _localBundleName = 'app.flx';
const String _localSnapshotName = 'snapshot_blob.bin';
class StartCommand extends FlutterCommand {
final String name = 'start';
final String description = 'Start your Flutter app on attached devices.';
StartCommand() {
argParser.addFlag('poke',
negatable: false,
help: 'Restart the connection to the server (Android only).');
argParser.addFlag('checked',
negatable: true,
defaultsTo: true,
help: 'Toggle Dart\'s checked mode.');
argParser.addOption('target',
defaultsTo: '.',
abbr: 't',
help: 'Target app path or filename to start.');
argParser.addFlag('http',
negatable: true,
help: 'Use a local HTTP server to serve your app to your device.');
argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.');
}
@override
Future<int> runInProject() async {
await Future.wait([
downloadToolchain(),
downloadApplicationPackagesAndConnectToDevices(),
]);
bool poke = argResults['poke'];
if (!poke) {
StopCommand stopper = new StopCommand();
stopper.inheritFromParent(this);
stopper.stop();
// Only install if the user did not specify a poke
InstallCommand installer = new InstallCommand();
installer.inheritFromParent(this);
installer.install(boot: argResults['boot']);
}
bool startedSomething = false;
for (Device device in devices.all) {
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
if (package == null || !device.isConnected())
continue;
if (device is AndroidDevice) {
String target = path.absolute(argResults['target']);
if (argResults['http']) {
if (await device.startServer(target, poke, argResults['checked'], package))
startedSomething = true;
} else {
String mainPath = target;
if (FileSystemEntity.isDirectorySync(target))
mainPath = path.join(target, 'lib', 'main.dart');
BuildCommand builder = new BuildCommand();
builder.inheritFromParent(this);
Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
try {
String localBundlePath = path.join(tempDir.path, _localBundleName);
String localSnapshotPath = path.join(tempDir.path, _localSnapshotName);
await builder.build(
snapshotPath: localSnapshotPath,
outputPath: localBundlePath,
mainPath: mainPath);
if (device.startBundle(package, localBundlePath, poke, argResults['checked']))
startedSomething = true;
} finally {
tempDir.deleteSync(recursive: true);
}
}
} else {
if (await device.startApp(package))
startedSomething = true;
}
}
if (!startedSomething) {
if (!devices.all.any((device) => device.isConnected())) {
_logging.severe('Unable to run application - no connected devices.');
} else {
_logging.severe('Unable to run application.');
}
}
return startedSomething ? 0 : 2;
}
}
// 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.
import 'dart:async';
import 'package:logging/logging.dart';
import '../application_package.dart';
import '../device.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.stop');
class StopCommand extends FlutterCommand {
final String name = 'stop';
final String description = 'Stop your Flutter app on all attached devices.';
@override
Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices();
return await stop() ? 0 : 2;
}
Future<bool> stop() async {
bool stoppedSomething = false;
for (Device device in devices.all) {
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
if (package == null || !device.isConnected())
continue;
if (await device.stopApp(package))
stoppedSomething = true;
}
return stoppedSomething;
}
}
// 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.
import 'dart:async';
import 'package:logging/logging.dart';
import '../application_package.dart';
import '../device.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.trace');
class TraceCommand extends FlutterCommand {
final String name = 'trace';
final String description = 'Start and stop tracing a running Flutter app '
'(Android only, requires root).\n'
'To start a trace, wait, and then stop the trace, don\'t set any flags '
'except (optionally) duration.\n'
'Otherwise, specify either start or stop to manually control the trace.';
TraceCommand() {
argParser.addFlag('start', negatable: false, help: 'Start tracing.');
argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
argParser.addOption('duration',
defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
}
@override
Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices();
if (!devices.android.isConnected()) {
_logging.warning('No device connected, so no trace was completed.');
return 1;
}
ApplicationPackage androidApp = applicationPackages.android;
if ((!argResults['start'] && !argResults['stop']) ||
(argResults['start'] && argResults['stop'])) {
// Setting neither flags or both flags means do both commands and wait
// duration seconds in between.
devices.android.startTracing(androidApp);
await new Future.delayed(
new Duration(seconds: int.parse(argResults['duration'])),
() => _stopTracing(devices.android, androidApp));
} else if (argResults['stop']) {
_stopTracing(devices.android, androidApp);
} else {
devices.android.startTracing(androidApp);
}
return 0;
}
void _stopTracing(AndroidDevice android, AndroidApk androidApp) {
String tracePath = android.stopTracing(androidApp);
if (tracePath == null) {
_logging.warning('No trace file saved.');
} else {
print('Trace file saved to $tracePath');
}
}
}
This diff is collapsed.
// 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.
import 'dart:io';
import 'package:logging/logging.dart';
import 'process.dart';
final OperatingSystemUtils osUtils = new OperatingSystemUtils._();
final Logger _logging = new Logger('sky_tools.os');
abstract class OperatingSystemUtils {
factory OperatingSystemUtils._() {
if (Platform.isWindows) {
return new _WindowsUtils();
} else if (Platform.isMacOS) {
return new _MacUtils();
} else {
return new _LinuxUtils();
}
}
/// Make the given file executable. This may be a no-op on some platforms.
ProcessResult makeExecutable(File file);
/// A best-effort attempt to kill all listeners on the given TCP port.
void killTcpPortListeners(int tcpPort);
}
abstract class _PosixUtils implements OperatingSystemUtils {
ProcessResult makeExecutable(File file) {
return Process.runSync('chmod', ['u+x', file.path]);
}
}
class _WindowsUtils implements OperatingSystemUtils {
// This is a no-op.
ProcessResult makeExecutable(File file) {
return new ProcessResult(0, 0, null, null);
}
void killTcpPortListeners(int tcpPort) {
// Get list of network processes and split on newline
List<String> processes = runSync(['netstat.exe','-ano']).split("\r");
// List entries from netstat is formatted like so:
// TCP 192.168.2.11:50945 192.30.252.90:443 LISTENING 1304
// This regexp is to find process where the the port exactly matches
RegExp pattern = new RegExp(':$tcpPort[ ]+');
// Split the columns by 1 or more spaces
RegExp columnPattern = new RegExp('[ ]+');
processes.forEach((String process) {
if (process.contains(pattern)) {
// The last column is the Process ID
String processId = process.split(columnPattern).last;
// Force and Tree kill the process
_logging.info('kill $processId');
runSync(['TaskKill.exe', '/F', '/T', '/PID', processId]);
}
});
}
}
class _MacUtils extends _PosixUtils {
void killTcpPortListeners(int tcpPort) {
String pids = runSync(['lsof', '-i', ':$tcpPort', '-t']).trim();
if (pids.isNotEmpty) {
// Handle multiple returned pids.
for (String pidString in pids.split('\n')) {
// Killing a pid with a shell command from within dart is hard, so use a
// library command, but it's still nice to give the equivalent command
// when doing verbose logging.
_logging.info('kill $pidString');
int pid = int.parse(pidString, onError: (_) => null);
if (pid != null)
Process.killPid(pid);
}
}
}
}
class _LinuxUtils extends _PosixUtils {
void killTcpPortListeners(int tcpPort) {
runSync(['fuser', '-k', '$tcpPort/tcp']);
}
}
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
final Logger _logging = new Logger('sky_tools.process');
/// This runs the command and streams stdout/stderr from the child process to
/// this process' stdout/stderr.
Future<int> runCommandAndStreamOutput(List<String> cmd,
{String prefix: '', RegExp filter}) async {
_logging.info(cmd.join(' '));
Process proc =
await Process.start(cmd[0], cmd.getRange(1, cmd.length).toList());
proc.stdout.transform(UTF8.decoder).listen((String data) {
List<String> dataLines = data.trimRight().split('\n');
if (filter != null) {
dataLines = dataLines.where((String s) => filter.hasMatch(s)).toList();
}
if (dataLines.length > 0) {
stdout.write('$prefix${dataLines.join('\n$prefix')}\n');
}
});
proc.stderr.transform(UTF8.decoder).listen((String data) {
List<String> dataLines = data.trimRight().split('\n');
if (filter != null) {
dataLines = dataLines.where((String s) => filter.hasMatch(s));
}
if (dataLines.length > 0) {
stderr.write('$prefix${dataLines.join('\n$prefix')}\n');
}
});
return proc.exitCode;
}
Future runAndKill(List<String> cmd, Duration timeout) async {
Future<Process> proc = runDetached(cmd);
return new Future.delayed(timeout, () async {
_logging.info('Intentionally killing ${cmd[0]}');
Process.killPid((await proc).pid);
});
}
Future<Process> runDetached(List<String> cmd) async {
_logging.info(cmd.join(' '));
Future<Process> proc = Process.start(
cmd[0], cmd.getRange(1, cmd.length).toList(),
mode: ProcessStartMode.DETACHED);
return proc;
}
/// Run cmd and return stdout.
/// Throws an error if cmd exits with a non-zero value.
String runCheckedSync(List<String> cmd) =>
_runWithLoggingSync(cmd, checked: true);
/// Run cmd and return stdout.
String runSync(List<String> cmd) => _runWithLoggingSync(cmd);
/// Return the platform specific name for the given Dart SDK binary. So, `pub`
/// ==> `pub.bat`.
String sdkBinaryName(String name) {
return Platform.isWindows ? '${name}.bat' : name;
}
String _runWithLoggingSync(List<String> cmd, {bool checked: false}) {
_logging.info(cmd.join(' '));
ProcessResult results =
Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList());
if (results.exitCode != 0) {
String errorDescription = 'Error code ${results.exitCode} '
'returned when attempting to run command: ${cmd.join(' ')}';
_logging.fine(errorDescription);
if (results.stderr.length > 0) {
_logging.info('Errors logged: ${results.stderr.trim()}');
}
if (checked) {
throw errorDescription;
}
}
_logging.fine(results.stdout.trim());
return results.stdout;
}
class ProcessExit implements Exception {
final int exitCode;
ProcessExit(this.exitCode);
String get message => 'ProcessExit: ${exitCode}';
String toString() => message;
}
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
class JSONSocket {
JSONSocket(WebSocket socket, this.unusualTermination)
: _socket = socket, stream = socket.map(JSON.decode).asBroadcastStream();
final WebSocket _socket;
final Stream stream;
final Future<String> unusualTermination;
void send(dynamic data) {
_socket.add(JSON.encode(data));
}
}
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:sky_tools/src/test/json_socket.dart';
import 'package:sky_tools/src/test/remote_test.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:test/src/backend/group.dart';
import 'package:test/src/backend/metadata.dart';
import 'package:test/src/backend/test_platform.dart';
import 'package:test/src/runner/configuration.dart';
import 'package:test/src/runner/hack_load_vm_file_hook.dart' as hack;
import 'package:test/src/runner/load_exception.dart';
import 'package:test/src/runner/runner_suite.dart';
import 'package:test/src/runner/vm/environment.dart';
import 'package:test/src/util/io.dart';
import 'package:test/src/util/remote_exception.dart';
void installHook() {
hack.loadVMFileHook = _loadVMFile;
}
final String _kSkyShell = Platform.environment['SKY_SHELL'];
const String _kHost = '127.0.0.1';
const String _kPath = '/runner';
// Right now a bunch of our tests crash or assert after the tests have finished running.
// Mostly this is just because the test puts the framework in an inconsistent state with
// a scheduled microtask that verifies that state. Eventually we should fix all these
// problems but for now we'll just paper over them.
const bool kExpectAllTestsToCloseCleanly = false;
class _ServerInfo {
final String url;
final Future<WebSocket> socket;
final HttpServer server;
_ServerInfo(this.server, this.url, this.socket);
}
Future<_ServerInfo> _createServer() async {
HttpServer server = await HttpServer.bind(_kHost, 0);
Completer<WebSocket> socket = new Completer<WebSocket>();
server.listen((HttpRequest request) {
if (request.uri.path == _kPath)
socket.complete(WebSocketTransformer.upgrade(request));
});
return new _ServerInfo(server, 'ws://$_kHost:${server.port}$_kPath', socket.future);
}
Future<Process> _startProcess(String path, { String packageRoot }) {
assert(_kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
return Process.start(_kSkyShell, [
'--enable-checked-mode',
'--non-interactive',
'--package-root=$packageRoot',
path,
]);
}
Future<RunnerSuite> _loadVMFile(String path,
Metadata metadata,
Configuration config) async {
String encodedMetadata = Uri.encodeComponent(JSON.encode(
metadata.serialize()));
_ServerInfo info = await _createServer();
Directory tempDir = await Directory.systemTemp.createTemp(
'dart_test_listener');
File listenerFile = new File('${tempDir.path}/listener.dart');
await listenerFile.create();
await listenerFile.writeAsString('''
import 'dart:convert';
import 'package:test/src/backend/metadata.dart';
import 'package:sky_tools/src/test/remote_listener.dart';
import '${p.toUri(p.absolute(path))}' as test;
void main() {
String server = Uri.decodeComponent('${Uri.encodeComponent(info.url)}');
Metadata metadata = new Metadata.deserialize(
JSON.decode(Uri.decodeComponent('$encodedMetadata')));
RemoteListener.start(server, metadata, () => test.main);
}
''');
Completer<Iterable<RemoteTest>> completer = new Completer<Iterable<RemoteTest>>();
Completer<String> deathCompleter = new Completer();
Process process = await _startProcess(
listenerFile.path,
packageRoot: p.absolute(config.packageRoot)
);
Future cleanupTempDirectory() async {
if (tempDir == null)
return;
Directory dirToDelete = tempDir;
tempDir = null;
await dirToDelete.delete(recursive: true);
}
process.exitCode.then((int exitCode) async {
try {
info.server.close(force: true);
await cleanupTempDirectory();
String output = '';
if (exitCode < 0) {
// Abnormal termination (high bit of signed 8-bit exitCode is set)
switch (exitCode) {
case -0x0f: // ProcessSignal.SIGTERM
break; // we probably killed it ourselves
case -0x0b: // ProcessSignal.SIGSEGV
output += 'Segmentation fault in subprocess for: $path\n';
break;
default:
output += 'Unexpected exit code $exitCode from subprocess for: $path\n';
}
}
String stdout = await process.stdout.transform(UTF8.decoder).join('\n');
String stderr = await process.stderr.transform(UTF8.decoder).join('\n');
if (stdout != '')
output += '\nstdout:\n$stdout';
if (stderr != '')
output += '\nstderr:\n$stderr';
if (!completer.isCompleted) {
if (output == '')
output = 'No output.';
completer.completeError(
new LoadException(path, output),
new Trace.current()
);
} else {
if (kExpectAllTestsToCloseCleanly && output != '')
print('Unexpected failure after test claimed to pass:\n$output');
}
deathCompleter.complete(output);
} catch (e) {
// Throwing inside this block causes all kinds of hard-to-debug issues
// like stack overflows and hangs. So catch everything just in case.
print("exception while handling subprocess termination: $e");
}
});
JSONSocket socket = new JSONSocket(await info.socket, deathCompleter.future);
await cleanupTempDirectory();
StreamSubscription subscription;
subscription = socket.stream.listen((response) {
if (response["type"] == "print") {
print(response["line"]);
} else if (response["type"] == "loadException") {
process.kill(ProcessSignal.SIGTERM);
completer.completeError(
new LoadException(path, response["message"]),
new Trace.current());
} else if (response["type"] == "error") {
process.kill(ProcessSignal.SIGTERM);
AsyncError asyncError = RemoteException.deserialize(response["error"]);
completer.completeError(
new LoadException(path, asyncError.error),
asyncError.stackTrace);
} else {
assert(response["type"] == "success");
subscription.cancel();
completer.complete(response["tests"].map((test) {
var testMetadata = new Metadata.deserialize(test['metadata']);
return new RemoteTest(test['name'], testMetadata, socket, test['index']);
}));
}
});
Iterable<RemoteTest> entries = await completer.future;
return new RunnerSuite(
const VMEnvironment(),
new Group.root(entries, metadata: metadata),
path: path,
platform: TestPlatform.vm,
os: currentOS,
onClose: () { process.kill(ProcessSignal.SIGTERM); }
);
}
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:stack_trace/stack_trace.dart';
import 'package:test/src/backend/declarer.dart';
import 'package:test/src/backend/live_test.dart';
import 'package:test/src/backend/metadata.dart';
import 'package:test/src/backend/operating_system.dart';
import 'package:test/src/backend/suite.dart';
import 'package:test/src/backend/test_platform.dart';
import 'package:test/src/backend/test.dart';
import 'package:test/src/util/remote_exception.dart';
final OperatingSystem currentOS = (() {
var name = Platform.operatingSystem;
var os = OperatingSystem.findByIoName(name);
if (os != null) return os;
throw new UnsupportedError('Unsupported operating system "$name".');
})();
typedef AsyncFunction();
class RemoteListener {
RemoteListener._(this._suite, this._socket);
final Suite _suite;
final WebSocket _socket;
final Set<LiveTest> _liveTests = new Set<LiveTest>();
static Future start(String server, Metadata metadata, Function getMain()) async {
WebSocket socket = await WebSocket.connect(server);
// Capture any top-level errors (mostly lazy syntax errors, since other are
// caught below) and report them to the parent isolate. We set errors
// non-fatal because otherwise they'll be double-printed.
var errorPort = new ReceivePort();
Isolate.current.setErrorsFatal(false);
Isolate.current.addErrorListener(errorPort.sendPort);
errorPort.listen((message) {
// Masquerade as an IsolateSpawnException because that's what this would
// be if the error had been detected statically.
var error = new IsolateSpawnException(message[0]);
var stackTrace =
message[1] == null ? new Trace([]) : new Trace.parse(message[1]);
socket.add(JSON.encode({
"type": "error",
"error": RemoteException.serialize(error, stackTrace)
}));
});
var main;
try {
main = getMain();
} on NoSuchMethodError catch (_) {
_sendLoadException(socket, "No top-level main() function defined.");
return;
}
if (main is! Function) {
_sendLoadException(socket, "Top-level main getter is not a function.");
return;
} else if (main is! AsyncFunction) {
_sendLoadException(
socket, "Top-level main() function takes arguments.");
return;
}
Declarer declarer = new Declarer(metadata);
try {
await runZoned(() => new Future.sync(main), zoneValues: {
#test.declarer: declarer
}, zoneSpecification: new ZoneSpecification(print: (_, __, ___, line) {
socket.add(JSON.encode({"type": "print", "line": line}));
}));
} catch (error, stackTrace) {
socket.add(JSON.encode({
"type": "error",
"error": RemoteException.serialize(error, stackTrace)
}));
return;
}
Suite suite = new Suite(declarer.build(),
platform: TestPlatform.vm, os: currentOS);
new RemoteListener._(suite, socket)._listen();
}
static void _sendLoadException(WebSocket socket, String message) {
socket.add(JSON.encode({"type": "loadException", "message": message}));
}
void _send(data) {
_socket.add(JSON.encode(data));
}
void _listen() {
List tests = [];
for (var i = 0; i < _suite.group.entries.length; i++) {
// TODO(ianh): entries[] might return a Group instead of a Test. We don't
// currently support nested groups.
Test test = _suite.group.entries[i];
tests.add({
"name": test.name,
"metadata": test.metadata.serialize(),
"index": i,
});
}
_send({"type": "success", "tests": tests});
_socket.listen(_handleCommand);
}
void _handleCommand(String data) {
var message = JSON.decode(data);
if (message['command'] == 'run') {
// TODO(ianh): entries[] might return a Group instead of a Test. We don't
// currently support nested groups.
Test test = _suite.group.entries[message['index']];
LiveTest liveTest = test.load(_suite);
_liveTests.add(liveTest);
liveTest.onStateChange.listen((state) {
_send({
"type": "state-change",
"status": state.status.name,
"result": state.result.name
});
});
liveTest.onError.listen((asyncError) {
_send({
"type": "error",
"error": RemoteException.serialize(
asyncError.error,
asyncError.stackTrace
)
});
});
liveTest.onPrint.listen((line) {
_send({"type": "print", "line": line});
});
liveTest.run().then((_) {
_send({"type": "complete"});
_liveTests.remove(liveTest);
});
} else if (message['command'] == 'close') {
if (_liveTests.isNotEmpty)
print('closing with ${_liveTests.length} live tests');
for (LiveTest liveTest in _liveTests)
liveTest.close();
_liveTests.clear();
} else {
print('remote_listener.dart: ignoring command "${message["command"]}" from test harness');
}
}
}
// 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.
import 'dart:async';
import 'package:stack_trace/stack_trace.dart';
import 'package:test/src/backend/live_test.dart';
import 'package:test/src/backend/live_test_controller.dart';
import 'package:test/src/backend/metadata.dart';
import 'package:test/src/backend/operating_system.dart';
import 'package:test/src/backend/state.dart';
import 'package:test/src/backend/suite.dart';
import 'package:test/src/backend/test.dart';
import 'package:test/src/backend/test_platform.dart';
import 'package:test/src/util/remote_exception.dart';
import 'package:sky_tools/src/test/json_socket.dart';
class RemoteTest extends Test {
RemoteTest(this.name, this.metadata, this._socket, this._index);
final String name;
final Metadata metadata;
final JSONSocket _socket;
final int _index;
LiveTest load(Suite suite) {
LiveTestController controller;
StreamSubscription subscription;
controller = new LiveTestController(suite, this, () async {
controller.setState(const State(Status.running, Result.success));
_socket.send({'command': 'run', 'index': _index});
subscription = _socket.stream.listen((message) {
if (message['type'] == 'error') {
AsyncError asyncError = RemoteException.deserialize(message['error']);
controller.addError(asyncError.error, asyncError.stackTrace);
} else if (message['type'] == 'state-change') {
controller.setState(
new State(
new Status.parse(message['status']),
new Result.parse(message['result'])));
} else if (message['type'] == 'print') {
controller.print(message['line']);
} else {
assert(message['type'] == 'complete');
subscription.cancel();
subscription = null;
controller.completer.complete();
}
});
_socket.unusualTermination.then((String message) {
if (subscription != null) {
controller.print('Unexpected subprocess termination: $message');
controller.addError(new Exception('Unexpected subprocess termination.'), new Trace.current());
controller.setState(new State(Status.complete, Result.error));
subscription.cancel();
subscription = null;
controller.completer.complete();
}
});
}, () async {
_socket.send({'command': 'close'});
if (subscription != null) {
subscription.cancel();
subscription = null;
}
});
return controller.liveTest;
}
Test change({String name, Metadata metadata}) {
if (name == name && metadata == this.metadata) return this;
if (name == null) name = this.name;
if (metadata == null) metadata = this.metadata;
return new RemoteTest(name, metadata, _socket, _index);
}
// TODO(ianh): Implement this if we need it.
Test forPlatform(TestPlatform platform, {OperatingSystem os}) {
if (!metadata.testOn.evaluate(platform, os: os))
return null;
return new RemoteTest(
name,
metadata.forPlatform(platform, os: os),
_socket,
_index
);
}
}
// 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.
import 'dart:async';
import 'package:path/path.dart' as path;
import 'artifacts.dart';
import 'build_configuration.dart';
import 'process.dart';
class Compiler {
Compiler(this._path);
String _path;
Future<int> compile({
String mainPath,
String snapshotPath
}) {
return runCommandAndStreamOutput([
_path,
mainPath,
'--package-root=${ArtifactStore.packageRoot}',
'--snapshot=$snapshotPath'
]);
}
}
Future<String> _getCompilerPath(BuildConfiguration config) async {
if (config.type != BuildType.prebuilt)
return path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.snapshot, hostPlatform: config.hostPlatform);
return await ArtifactStore.getPath(artifact);
}
class Toolchain {
Toolchain({ this.compiler });
final Compiler compiler;
static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async {
// TODO(abarth): Shouldn't we consider all the configs?
String compilerPath = await _getCompilerPath(configs.first);
return new Toolchain(compiler: new Compiler(compilerPath));
}
}
name: sky_tools
version: 0.0.37
description: Tools for building Flutter applications
homepage: http://flutter.io
author: Flutter Authors <flutter-dev@googlegroups.com>
environment:
sdk: '>=1.12.0 <2.0.0'
dependencies:
analyzer: ">=0.26.1+17" # see note below
archive: ^1.0.20
args: ^0.13.0
flx: ">=0.0.7 <0.1.0"
crypto: ^0.9.1
mustache4dart: ^1.0.0
path: ^1.3.0
shelf_route: ^0.13.4
shelf_static: ^0.2.3
shelf: ^0.6.2
stack_trace: ^1.4.0
test: ^0.12.5
yaml: ^2.1.3
# A note about 'analyzer':
# We don't actually depend on 'analyzer', but 'test' does. We aren't
# compatible with some older versions of 'analyzer'. We lie here,
# saying we do depend on it, so that we constrain the version that
# 'test' will get to a version that we'll probably be ok with. (This
# is why there's no upper bound on our dependency.)
# See also https://github.com/dart-lang/pub/issues/1356
dev_dependencies:
mockito: "^0.10.1"
# Add the bin/sky_tools.dart script to the scripts pub installs.
executables:
sky_tools:
// 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.
import 'android_device_test.dart' as android_device_test;
import 'daemon_test.dart' as daemon_test;
import 'init_test.dart' as init_test;
import 'install_test.dart' as install_test;
import 'listen_test.dart' as listen_test;
import 'list_test.dart' as list_test;
import 'logs_test.dart' as logs_test;
import 'os_utils_test.dart' as os_utils_test;
import 'start_test.dart' as start_test;
import 'stop_test.dart' as stop_test;
import 'trace_test.dart' as trace_test;
main() {
android_device_test.defineTests();
daemon_test.defineTests();
init_test.defineTests();
install_test.defineTests();
listen_test.defineTests();
list_test.defineTests();
logs_test.defineTests();
os_utils_test.defineTests();
start_test.defineTests();
stop_test.defineTests();
trace_test.defineTests();
}
// 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.
import 'package:sky_tools/src/device.dart';
import 'package:test/test.dart';
main() => defineTests();
defineTests() {
group('android_device', () {
test('uses the correct default ID', () {
AndroidDevice android = new AndroidDevice();
expect(android.id, equals(AndroidDevice.defaultDeviceID));
});
test('stores the requested id', () {
String deviceId = '1234';
AndroidDevice android = new AndroidDevice(id: deviceId);
expect(android.id, equals(deviceId));
});
test('correctly creates only one of each requested device id', () {
String deviceID = '1234';
AndroidDevice a1 = new AndroidDevice(id: deviceID);
AndroidDevice a2 = new AndroidDevice(id: deviceID);
expect(a1, equals(a2));
});
});
}
// 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.
import 'dart:async';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/daemon.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('daemon', () {
Daemon daemon;
tearDown(() {
if (daemon != null)
return daemon.shutdown();
});
test('daemon.version', () async {
StreamController<Map> commands = new StreamController();
StreamController<Map> responses = new StreamController();
daemon = new Daemon(
commands.stream,
(Map result) => responses.add(result)
);
commands.add({'id': 0, 'event': 'daemon.version'});
Map response = await responses.stream.first;
expect(response['id'], 0);
expect(response['result'], isNotEmpty);
expect(response['result'] is String, true);
});
test('daemon.shutdown', () async {
StreamController<Map> commands = new StreamController();
StreamController<Map> responses = new StreamController();
daemon = new Daemon(
commands.stream,
(Map result) => responses.add(result)
);
commands.add({'id': 0, 'event': 'daemon.shutdown'});
return daemon.onExit.then((int code) {
expect(code, 0);
});
});
test('daemon.stopAll', () async {
DaemonCommand command = new DaemonCommand();
applyMocksToCommand(command);
StreamController<Map> commands = new StreamController();
StreamController<Map> responses = new StreamController();
daemon = new Daemon(
commands.stream,
(Map result) => responses.add(result),
daemonCommand: command
);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(true);
when(mockDevices.android.stopApp(any)).thenReturn(true);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
commands.add({'id': 0, 'event': 'app.stopAll'});
Map response = await responses.stream.first;
expect(response['id'], 0);
expect(response['result'], true);
});
});
}
// 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.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as p;
import 'package:sky_tools/src/commands/init.dart';
import 'package:sky_tools/src/process.dart';
import 'package:test/test.dart';
main() => defineTests();
defineTests() {
group('init', () {
Directory temp;
setUp(() {
temp = Directory.systemTemp.createTempSync('sky_tools');
});
tearDown(() {
temp.deleteSync(recursive: true);
});
// This test consistently times out on our windows bot. The code is already
// covered on the linux one.
if (!Platform.isWindows) {
// Verify that we create a project that is well-formed.
test('flutter-simple', () async {
InitCommand command = new InitCommand();
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
await runner.run(['init', '--out', temp.path])
.then((int code) => expect(code, equals(0)));
String path = p.join(temp.path, 'lib', 'main.dart');
expect(new File(path).existsSync(), true);
ProcessResult exec = Process.runSync(
sdkBinaryName('dartanalyzer'), ['--fatal-warnings', path],
workingDirectory: temp.path);
if (exec.exitCode != 0) {
print(exec.stdout);
print(exec.stderr);
}
expect(exec.exitCode, 0);
},
// This test can take a while due to network requests.
timeout: new Timeout(new Duration(minutes: 2)));
}
});
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/install.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('install', () {
test('returns 0 when Android is connected and ready for an install', () {
InstallCommand command = new InstallCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(true);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(true);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOS.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOS.installApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['install']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready for an install', () {
InstallCommand command = new InstallCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(true);
when(mockDevices.iOS.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOS.installApp(any)).thenReturn(true);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['install']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/list.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('list', () {
test('returns 0 when called', () {
final String mockCommand = Platform.isWindows ? 'cmd /c echo' : 'echo';
ListCommand command = new ListCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
// Avoid relying on adb being installed on the test system.
// Instead, cause the test to run the echo command.
when(mockDevices.android.adbPath).thenReturn(mockCommand);
// Avoid relying on idevice* being installed on the test system.
// Instead, cause the test to run the echo command.
when(mockDevices.iOS.informerPath).thenReturn(mockCommand);
when(mockDevices.iOS.installerPath).thenReturn(mockCommand);
when(mockDevices.iOS.listerPath).thenReturn(mockCommand);
// Avoid relying on xcrun being installed on the test system.
// Instead, cause the test to run the echo command.
when(mockDevices.iOSSimulator.xcrunPath).thenReturn(mockCommand);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['list']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/listen.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('listen', () {
test('returns 0 when no device is connected', () {
ListenCommand command = new ListenCommand(singleRun: true);
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['listen']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/logs.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('logs', () {
test('returns 0 when no device is connected', () {
LogsCommand command = new LogsCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['logs']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'dart:io';
import 'package:sky_tools/src/os_utils.dart';
import 'package:test/test.dart';
import 'package:path/path.dart' as p;
main() => defineTests();
defineTests() {
group('OperatingSystemUtils', () {
Directory temp;
setUp(() {
temp = Directory.systemTemp.createTempSync('sky_tools');
});
tearDown(() {
temp.deleteSync(recursive: true);
});
test('makeExecutable', () {
File file = new File(p.join(temp.path, 'foo.script'));
file.writeAsStringSync('hello world');
osUtils.makeExecutable(file);
// Skip this test on windows.
if (!Platform.isWindows) {
String mode = file.statSync().modeString();
// rwxr--r--
expect(mode.substring(0, 3), endsWith('x'));
}
});
/// Start a script listening on a port, try and kill that process.
test('killTcpPortListeners', () async {
final int port = 40170;
File file = new File(p.join(temp.path, 'script.dart'));
file.writeAsStringSync('''
import 'dart:io';
void main() async {
ServerSocket serverSocket = await ServerSocket.bind(
InternetAddress.LOOPBACK_IP_V4, ${port});
// wait...
print('listening on port ${port}...');
}
''');
Process process = await Process.start('dart', [file.path]);
await process.stdout.first;
osUtils.killTcpPortListeners(40170);
int exitCode = await process.exitCode;
expect(exitCode, isNot(equals(0)));
});
/// Try and kill with a port that no process is listening to.
test('killTcpPortListeners none', () {
osUtils.killTcpPortListeners(40171);
});
});
}
// 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.
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/build_configuration.dart';
import 'package:sky_tools/src/commands/flutter_command.dart';
import 'package:sky_tools/src/device.dart';
import 'package:sky_tools/src/toolchain.dart';
class MockApplicationPackageStore extends ApplicationPackageStore {
MockApplicationPackageStore() : super(
android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'),
iOS: new IOSApp(localPath: '/mock/path/to/iOS/SkyShell.app'),
iOSSimulator: new IOSApp(localPath: '/mock/path/to/iOSSimulator/SkyShell.app'));
}
class MockCompiler extends Mock implements Compiler {
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockToolchain extends Toolchain {
MockToolchain() : super(compiler: new MockCompiler());
}
class MockAndroidDevice extends Mock implements AndroidDevice {
TargetPlatform get platform => TargetPlatform.android;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockIOSDevice extends Mock implements IOSDevice {
TargetPlatform get platform => TargetPlatform.iOS;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockIOSSimulator extends Mock implements IOSSimulator {
TargetPlatform get platform => TargetPlatform.iOSSimulator;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockDeviceStore extends DeviceStore {
MockDeviceStore() : super(
android: new MockAndroidDevice(),
iOS: new MockIOSDevice(),
iOSSimulator: new MockIOSSimulator());
}
void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain()
..devices = new MockDeviceStore();
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/start.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('start', () {
test('returns 0 when Android is connected and ready to be started', () {
StartCommand command = new StartCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(true);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(true);
when(mockDevices.android.startBundle(any, any, any, any)).thenReturn(true);
when(mockDevices.android.stopApp(any)).thenReturn(true);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOS.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOS.installApp(any)).thenReturn(false);
when(mockDevices.iOS.startApp(any)).thenReturn(false);
when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.startApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['start']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready to be started', () {
StartCommand command = new StartCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(false);
when(mockDevices.android.startBundle(any, any, any, any)).thenReturn(false);
when(mockDevices.android.stopApp(any)).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(true);
when(mockDevices.iOS.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOS.installApp(any)).thenReturn(true);
when(mockDevices.iOS.startApp(any)).thenReturn(true);
when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.startApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['start']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/stop.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('stop', () {
test('returns 0 when Android is connected and ready to be stopped', () {
StopCommand command = new StopCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(true);
when(mockDevices.android.stopApp(any)).thenReturn(true);
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['stop']).then((int code) => expect(code, equals(0)));
});
test('returns 0 when iOS is connected and ready to be stopped', () {
StopCommand command = new StopCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.android.stopApp(any)).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(true);
when(mockDevices.iOS.stopApp(any)).thenReturn(true);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['stop']).then((int code) => expect(code, equals(0)));
});
});
}
// 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.
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/trace.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('trace', () {
test('returns 1 when no Android device is connected', () {
TraceCommand command = new TraceCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
when(mockDevices.android.isConnected()).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['trace']).then((int code) => expect(code, equals(1)));
});
});
}
// 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.
import 'dart:convert';
import 'dart:io';
Process daemon;
main() async {
daemon = await Process.start('dart', ['bin/sky_tools.dart', 'daemon']);
print('daemon process started, pid: ${daemon.pid}');
daemon.stdout
.transform(UTF8.decoder)
.transform(const LineSplitter())
.listen((String line) => print('<== ${line}'));
daemon.stderr.listen((data) => stderr.add(data));
stdout.write('> ');
stdin.transform(UTF8.decoder).transform(const LineSplitter()).listen((String line) {
if (line == 'version' || line == 'v') {
_send({'event': 'daemon.version'});
} else if (line == 'shutdown' || line == 'q') {
_send({'event': 'daemon.shutdown'});
} else if (line == 'start') {
_send({'event': 'app.start'});
} else if (line == 'stopAll') {
_send({'event': 'app.stopAll'});
} else {
print('command not understood: ${line}');
}
stdout.write('> ');
});
daemon.exitCode.then((int code) {
print('daemon exiting (${code})');
exit(code);
});
}
int id = 0;
void _send(Map map) {
map['id'] = id++;
String str = '[${JSON.encode(map)}]';
daemon.stdin.writeln(str);
print('==> ${str}');
}
#!/bin/bash
# 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.
# Fast fail the script on failures.
set -e
# Fetch all our dependencies
pub get
# Verify that the libraries are error free.
pub global activate tuneup
pub global run tuneup check
# And run our tests.
pub run test -j1
...@@ -3,5 +3,6 @@ set -ex ...@@ -3,5 +3,6 @@ set -ex
(cd packages/cassowary; pub get) (cd packages/cassowary; pub get)
(cd packages/newton; pub get) (cd packages/newton; pub get)
(cd packages/flutter_tools; pub get)
pub global activate tuneup pub global activate tuneup
...@@ -3,3 +3,4 @@ set -ex ...@@ -3,3 +3,4 @@ set -ex
(cd packages/cassowary; pub global run tuneup check; pub run test -j1) (cd packages/cassowary; pub global run tuneup check; pub run test -j1)
(cd packages/newton; pub global run tuneup check; pub run test -j1) (cd packages/newton; pub global run tuneup check; pub run test -j1)
(cd packages/flutter_tools; pub global run tuneup check; pub run test -j1)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment