Unverified Commit 94ce956f authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Adds support for 'run' for Fuchsia devices (#32849)

parent 3265e159
......@@ -17,6 +17,7 @@ import 'base/os.dart' show os;
import 'base/process.dart';
import 'base/user_messages.dart';
import 'build_info.dart';
import 'fuchsia/application_package.dart';
import 'globals.dart';
import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart' as plist;
......@@ -66,7 +67,9 @@ class ApplicationPackageFactory {
? WindowsApp.fromWindowsProject(FlutterProject.current().windows)
: WindowsApp.fromPrebuiltApp(applicationBinary);
case TargetPlatform.fuchsia:
return null;
return applicationBinary == null
? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia)
: FuchsiaApp.fromPrebuiltApp(applicationBinary);
}
assert(platform != null);
return null;
......
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
import 'package:archive/archive.dart';
import '../globals.dart';
import 'context.dart';
import 'file_system.dart';
import 'io.dart';
......@@ -72,6 +74,37 @@ abstract class OperatingSystemUtils {
/// Returns the separator between items in the PATH environment variable.
String get pathVarSeparator;
/// Returns an unused network port.
///
/// Returns 0 if an unused port cannot be found.
///
/// The port returned by this function may become used before it is bound by
/// its intended user.
Future<int> findFreePort({bool ipv6 = false}) async {
int port = 0;
ServerSocket serverSocket;
final InternetAddress loopback =
ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4;
try {
serverSocket = await ServerSocket.bind(loopback, 0);
port = serverSocket.port;
} on SocketException catch (e) {
// If ipv4 loopback bind fails, try ipv6.
if (!ipv6) {
return findFreePort(ipv6: true);
}
printTrace('findFreePort failed: $e');
} catch (e) {
// Failures are signaled by a return value of 0 from this function.
printTrace('findFreePort failed: $e');
} finally {
if (serverSocket != null) {
await serverSocket.close();
}
}
return port;
}
}
class _PosixUtils extends OperatingSystemUtils {
......
......@@ -27,10 +27,9 @@ import 'devfs.dart';
import 'device.dart';
import 'doctor.dart';
import 'emulator.dart';
import 'fuchsia/fuchsia_kernel_compiler.dart';
import 'fuchsia/fuchsia_pm.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'fuchsia/fuchsia_workflow.dart';
import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
import 'ios/cocoapods.dart';
import 'ios/ios_workflow.dart';
import 'ios/mac.dart';
......@@ -76,8 +75,7 @@ Future<T> runInContext<T>(
Flags: () => const EmptyFlags(),
FlutterVersion: () => FlutterVersion(const SystemClock()),
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
FuchsiaKernelCompiler: () => FuchsiaKernelCompiler(),
FuchsiaPM: () => FuchsiaPM(),
FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
FuchsiaSdk: () => FuchsiaSdk(),
FuchsiaWorkflow: () => FuchsiaWorkflow(),
GenSnapshot: () => const GenSnapshot(),
......
// Copyright 2019 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 '../base/process.dart';
import 'fuchsia_device.dart';
import 'fuchsia_pm.dart';
// usage: amber_ctl <command> [opts]
// Commands
// get_up - get an update for a package
// Options
// -n: name of the package
// -v: version of the package to retrieve, if none is supplied any
// package instance could match
// -m: merkle root of the package to retrieve, if none is supplied
// any package instance could match
//
// get_blob - get the specified content blob
// -i: content ID of the blob
//
// add_src - add a source to the list we can use
// -n: name of the update source (optional, with URL)
// -f: file path or url to a source config file
// -h: SHA256 hash of source config file (optional, with URL)
// -x: do not disable other active sources (if the provided source is
// enabled)
//
// rm_src - remove a source, if it exists
// -n: name of the update source
//
// list_srcs - list the set of sources we can use
//
// enable_src
// -n: name of the update source
// -x: do not disable other active sources
//
// disable_src
// -n: name of the update source
//
// system_update - check for, download, and apply any available system
// update
//
// gc - trigger a garbage collection
//
// print_state - print go routine state of amber process
/// Simple wrapper for interacting with the 'amber_ctl' tool running on the
/// Fuchsia device.
class FuchsiaAmberCtl {
/// Teaches the amber instance running on [device] about the Fuchsia package
/// server accessible via [configUrl].
Future<bool> addSrc(FuchsiaDevice device, FuchsiaPackageServer server) async {
final String configUrl = '${server.url}/config.json';
final RunResult result =
await device.shell('amber_ctl add_src -x -f $configUrl');
return result.exitCode == 0;
}
/// Instructs the amber instance running on [device] to forget about the
/// Fuchsia package server that it was accessing via [serverUrl].
Future<bool> rmSrc(FuchsiaDevice device, FuchsiaPackageServer server) async {
final RunResult result =
await device.shell('amber_ctl rm_src -n ${server.url}');
return result.exitCode == 0;
}
/// Instructs the amber instance running on [device] to prefetch the package
/// [packageName].
Future<bool> getUp(FuchsiaDevice device, String packageName) async {
final RunResult result =
await device.shell('amber_ctl get_up -n $packageName');
return result.exitCode == 0;
}
}
// Copyright 2019 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:meta/meta.dart';
import '../application_package.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../project.dart';
abstract class FuchsiaApp extends ApplicationPackage {
FuchsiaApp({@required String projectBundleId}) : super(id: projectBundleId);
/// Creates a new [FuchsiaApp] from a fuchsia sub project.
factory FuchsiaApp.fromFuchsiaProject(FuchsiaProject project) {
return BuildableFuchsiaApp(
project: project,
);
}
/// Creates a new [FuchsiaApp] from an existing .far archive.
///
/// [applicationBinary] is the path to the .far archive.
factory FuchsiaApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
return PrebuiltFuchsiaApp(
farArchive: applicationBinary.path,
);
}
@override
String get displayName => id;
/// The location of the 'far' archive containing the built app.
File farArchive(BuildMode buildMode);
}
class PrebuiltFuchsiaApp extends FuchsiaApp {
PrebuiltFuchsiaApp({
@required String farArchive,
}) : _farArchive = farArchive,
// TODO(zra): Extract the archive and extract the id from meta/package.
super(projectBundleId: farArchive);
final String _farArchive;
@override
File farArchive(BuildMode buildMode) => fs.file(_farArchive);
@override
String get name => _farArchive;
}
class BuildableFuchsiaApp extends FuchsiaApp {
BuildableFuchsiaApp({this.project}) :
super(projectBundleId: project.project.manifest.appName);
final FuchsiaProject project;
@override
File farArchive(BuildMode buildMode) {
// TODO(zra): Distinguish among build modes.
final String outDir = getFuchsiaBuildDirectory();
final String pkgDir = fs.path.join(outDir, 'pkg');
final String appName = project.project.manifest.appName;
return fs.file(fs.path.join(pkgDir, '$appName-0.far'));
}
@override
String get name => project.project.manifest.appName;
}
......@@ -12,8 +12,8 @@ import '../bundle.dart';
import '../devfs.dart';
import '../project.dart';
import 'fuchsia_kernel_compiler.dart';
import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart';
// Building a Fuchsia package has a few steps:
// 1. Do the custom kernel compile using the kernel compiler from the Fuchsia
......@@ -30,7 +30,7 @@ Future<void> buildFuchsia(
outDir.createSync(recursive: true);
}
await fuchsiaKernelCompiler.build(
await fuchsiaSdk.fuchsiaKernelCompiler.build(
fuchsiaProject: fuchsiaProject, target: target, buildInfo: buildInfo);
await _buildAssets(fuchsiaProject, target, buildInfo);
await _buildPackage(fuchsiaProject, target, buildInfo);
......@@ -96,6 +96,7 @@ Future<void> _buildPackage(
manifestFile.writeAsStringSync('meta/package=$pkgDir/meta/package\n',
mode: FileMode.append);
final FuchsiaPM fuchsiaPM = fuchsiaSdk.fuchsiaPM;
if (!await fuchsiaPM.init(pkgDir, appName)) {
return;
}
......
// Copyright 2019 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 '../base/common.dart';
import '../base/process.dart';
import 'fuchsia_sdk.dart';
// Usage: dev_finder <flags> <subcommand> <subcommand args>
//
// Subcommands:
// commands list all command names
// flags describe all known top-level flags
// help describe subcommands and their syntax
// list lists all Fuchsia devices on the network
// resolve attempts to resolve all passed Fuchsia domain names on the
// network
/// A simple wrapper for the Fuchsia SDK's 'dev_finder' tool.
class FuchsiaDevFinder {
/// Returns a list of attached devices as a list of strings with entries
/// formatted as follows:
/// 192.168.42.172 scare-cable-skip-joy
Future<List<String>> list() async {
if (fuchsiaArtifacts.devFinder == null) {
throwToolExit('Fuchsia dev_finder tool not found.');
}
final List<String> command = <String>[
fuchsiaArtifacts.devFinder.path,
'list',
'-full'
];
final RunResult result = await runAsync(command);
return (result.exitCode == 0) ? result.stdout.split('\n') : null;
}
/// Returns the host address by which the device [deviceName] should use for
/// the host.
///
/// The string [deviceName] should be the name of the device from the
/// 'list' command, e.g. 'scare-cable-skip-joy'.
Future<String> resolve(String deviceName) async {
if (fuchsiaArtifacts.devFinder == null) {
throwToolExit('Fuchsia dev_finder tool not found.');
}
final List<String> command = <String>[
fuchsiaArtifacts.devFinder.path,
'resolve',
'-local',
'-device-limit', '1',
deviceName
];
final RunResult result = await runAsync(command);
return (result.exitCode == 0) ? result.stdout.trim() : null;
}
}
......@@ -6,11 +6,10 @@ import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process_manager.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart';
......@@ -18,10 +17,8 @@ import '../project.dart';
import 'fuchsia_sdk.dart';
/// The [FuchsiaKernelCompiler] instance.
FuchsiaKernelCompiler get fuchsiaKernelCompiler =>
context.get<FuchsiaKernelCompiler>();
/// This is a simple wrapper around the custom kernel compiler from the Fuchsia
/// SDK.
class FuchsiaKernelCompiler {
/// Compiles the [fuchsiaProject] with entrypoint [target] to a collection of
/// .dilp files (consisting of the app split along package: boundaries, but
......@@ -33,6 +30,9 @@ class FuchsiaKernelCompiler {
BuildInfo buildInfo = BuildInfo.debug,
}) async {
// TODO(zra): Use filesystem root and scheme information from buildInfo.
if (fuchsiaArtifacts.kernelCompiler == null) {
throwToolExit('Fuchisa kernel compiler not found');
}
const String multiRootScheme = 'main-root';
final String packagesFile = fuchsiaProject.project.packagesFile.path;
final String outDir = getFuchsiaBuildDirectory();
......@@ -87,8 +87,7 @@ class FuchsiaKernelCompiler {
artifacts.getArtifactPath(Artifact.engineDartBinary),
fuchsiaArtifacts.kernelCompiler.path,
]..addAll(flags);
printTrace("Running: '${command.join(' ')}'");
final Process process = await processManager.start(command);
final Process process = await runCommand(command);
final Status status = logger.startProgress(
'Building Fuchsia application...',
timeout: null,
......
......@@ -2,16 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/context.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/process_manager.dart';
import '../base/process.dart';
import '../convert.dart';
import '../globals.dart';
import 'fuchsia_sdk.dart';
/// The [FuchsiaPM] instance.
FuchsiaPM get fuchsiaPM => context.get<FuchsiaPM>();
/// This is a basic wrapper class for the Fuchsia SDK's `pm` tool.
class FuchsiaPM {
/// Initializes the staging area at [buildPath] for creating the Fuchsia
......@@ -21,47 +20,27 @@ class FuchsiaPM {
///
/// NB: The [buildPath] should probably be e.g. `build/fuchsia/pkg`, and the
/// [appName] should probably be the name of the app from the pubspec file.
Future<bool> init(String buildPath, String appName) async {
final List<String> command = <String>[
fuchsiaArtifacts.pm.path,
Future<bool> init(String buildPath, String appName) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-n',
appName,
'init',
];
printTrace("Running: '${command.join(' ')}'");
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
printError('Error initializing Fuchsia package for $appName: ');
printError(result.stdout);
printError(result.stderr);
return false;
}
return true;
]);
}
/// Generates a new private key to be used to sign a Fuchsia package.
///
/// [buildPath] should be the same [buildPath] passed to [init].
Future<bool> genkey(String buildPath, String outKeyPath) async {
final List<String> command = <String>[
fuchsiaArtifacts.pm.path,
Future<bool> genkey(String buildPath, String outKeyPath) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-k',
outKeyPath,
'genkey',
];
printTrace("Running: '${command.join(' ')}'");
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
printError('Error generating key for Fuchsia package: ');
printError(result.stdout);
printError(result.stderr);
return false;
}
return true;
]);
}
/// Updates, signs, and seals a Fuchsia package.
......@@ -80,9 +59,8 @@ class FuchsiaPM {
/// where $APPNAME is the same [appName] passed to [init], and meta/package
/// is set up to be the file `meta/package` created by [init].
Future<bool> build(
String buildPath, String keyPath, String manifestPath) async {
final List<String> command = <String>[
fuchsiaArtifacts.pm.path,
String buildPath, String keyPath, String manifestPath) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-k',
......@@ -90,16 +68,7 @@ class FuchsiaPM {
'-m',
manifestPath,
'build',
];
printTrace("Running: '${command.join(' ')}'");
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
printError('Error building Fuchsia package: ');
printError(result.stdout);
printError(result.stderr);
return false;
}
return true;
]);
}
/// Constructs a .far representation of the Fuchsia package.
......@@ -109,10 +78,8 @@ class FuchsiaPM {
///
/// [buildPath] should be the same path passed to [init], and [manfiestPath]
/// should be the same manifest passed to [build].
Future<bool> archive(
String buildPath, String keyPath, String manifestPath) async {
final List<String> command = <String>[
fuchsiaArtifacts.pm.path,
Future<bool> archive(String buildPath, String keyPath, String manifestPath) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-k',
......@@ -120,15 +87,155 @@ class FuchsiaPM {
'-m',
manifestPath,
'archive',
]);
}
/// Initializes a new package repository at [repoPath] to be later served by
/// the 'serve' command.
Future<bool> newrepo(String repoPath) {
return _runPMCommand(<String>[
'newrepo',
'-repo',
repoPath,
]);
}
/// Spawns an http server in a new process for serving Fuchisa packages.
///
/// The arguemnt [repoPath] should have previously been an arguemnt to
/// [newrepo]. The [host] should be the host reported by
/// [FuchsiaDevFinder.resolve], and [port] should be an unused port for the
/// http server to bind.
Future<Process> serve(String repoPath, String host, int port) async {
if (fuchsiaArtifacts.pm == null) {
throwToolExit('Fuchsia pm tool not found');
}
final List<String> command = <String>[
fuchsiaArtifacts.pm.path,
'serve',
'-repo',
repoPath,
'-l',
'$host:$port',
];
printTrace("Running: '${command.join(' ')}'");
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
printError('Error archiving Fuchsia package: ');
printError(result.stdout);
printError(result.stderr);
final Process process = await runCommand(command);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printTrace);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printError);
return process;
}
/// Publishes a Fuchsia package to a served package repository.
///
/// For a package repo initialized with [newrepo] at [repoPath] and served
/// by [serve], this call publishes the `far` package at [packagePath] to
/// the repo such that it will be visible to devices connecting to the
/// package server.
Future<bool> publish(String repoPath, String packagePath) {
return _runPMCommand(<String>[
'publish',
'-a',
'-r',
repoPath,
'-f',
packagePath,
]);
}
Future<bool> _runPMCommand(List<String> args) async {
if (fuchsiaArtifacts.pm == null) {
throwToolExit('Fuchsia pm tool not found');
}
final List<String> command = <String>[fuchsiaArtifacts.pm.path] + args;
final RunResult result = await runAsync(command);
return result.exitCode == 0;
}
}
/// A class for running and retaining state for a Fuchsia package server.
///
/// [FuchsiaPackageServer] takes care of initializing the package repository,
/// spinning up the package server, publishing packages, and shutting down the
/// the server.
///
/// Example usage:
/// var server = FuchsiaPackageServer(
/// '/path/to/repo',
/// await FuchsiaDevFinder.resolve(deviceName),
/// await freshPort());
/// try {
/// await server.start();
/// await server.addPackage(farArchivePath);
/// ...
/// } finally {
/// server.stop();
/// }
class FuchsiaPackageServer {
FuchsiaPackageServer(this._repo, this._host, this._port);
final String _repo;
final String _host;
final int _port;
Process _process;
/// The url that can be used by the device to access this package server.
String get url => 'http://$_host:$_port';
/// Usees [FuchiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia
/// package server.
///
/// Returns false if ther repo could not be created or the server could not
/// be spawned, and true otherwise.
Future<bool> start() async {
if (_process != null) {
printError('$this already started!');
return false;
}
// initialize a new repo.
if (!await fuchsiaSdk.fuchsiaPM.newrepo(_repo)) {
printError('Failed to create a new package server repo');
return false;
}
_process = await fuchsiaSdk.fuchsiaPM.serve(_repo, _host, _port);
// Put a completer on _process.exitCode to watch for error.
unawaited(_process.exitCode.whenComplete(() {
// If _process is null, then the server was stopped deliberately.
if (_process != null) {
printError('Error running Fuchsia pm tool "serve" command');
}
}));
return true;
}
/// Forcefully stops the package server process by sending it SIGTERM.
void stop() {
if (_process != null) {
_process.kill();
_process = null;
}
}
/// Uses [FuchsiaPM.publish] to add the Fuchsia 'far' package at
/// [packagePath] to the package server.
///
/// Returns true on success and false if the server wasn't started or the
/// publish command failed.
Future<bool> addPackage(File package) async {
if (_process == null) {
return false;
}
return await fuchsiaSdk.fuchsiaPM.publish(_repo, package.path);
}
@override
String toString() {
final String p = (_process == null) ? 'stopped' : 'running ${_process.pid}';
return 'FuchsiaPackageServer at $_host:$_port ($p)';
}
}
......@@ -8,12 +8,15 @@ import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/process_manager.dart';
import '../cache.dart';
import '../convert.dart';
import '../globals.dart';
import 'fuchsia_dev_finder.dart';
import 'fuchsia_kernel_compiler.dart';
import 'fuchsia_pm.dart';
/// The [FuchsiaSdk] instance.
FuchsiaSdk get fuchsiaSdk => context.get<FuchsiaSdk>();
......@@ -25,23 +28,32 @@ FuchsiaArtifacts get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
/// This workflow assumes development within the fuchsia source tree,
/// including a working fx command-line tool in the user's PATH.
class FuchsiaSdk {
/// Interface to the 'pm' tool.
FuchsiaPM get fuchsiaPM => _fuchsiaPM ??= FuchsiaPM();
FuchsiaPM _fuchsiaPM;
/// Interface to the 'dev_finder' tool.
FuchsiaDevFinder _fuchsiaDevFinder;
FuchsiaDevFinder get fuchsiaDevFinder =>
_fuchsiaDevFinder ??= FuchsiaDevFinder();
/// Interface to the 'kernel_compiler' tool.
FuchsiaKernelCompiler _fuchsiaKernelCompiler;
FuchsiaKernelCompiler get fuchsiaKernelCompiler =>
_fuchsiaKernelCompiler ??= FuchsiaKernelCompiler();
/// Example output:
/// $ dev_finder list -full
/// > 192.168.42.56 paper-pulp-bush-angel
Future<String> listDevices() async {
try {
final String path = fuchsiaArtifacts.devFinder.absolute.path;
final RunResult process = await runAsync(<String>[path, 'list', '-full']);
return process.stdout.trim();
} catch (exception) {
printTrace('$exception');
if (fuchsiaArtifacts.devFinder == null) {
return null;
}
return null;
final List<String> devices = await fuchsiaDevFinder.list();
return devices.isNotEmpty ? devices[0] : null;
}
/// Returns the fuchsia system logs for an attached device.
///
/// Does not currently support multiple attached devices.
Stream<String> syslogs(String id) {
Process process;
try {
......@@ -50,6 +62,8 @@ class FuchsiaSdk {
process.kill();
});
if (fuchsiaArtifacts.sshConfig == null) {
printError('Cannot read device logs: No ssh config.');
printError('Have you set FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR?');
return null;
}
const String remoteCommand = 'log_listener --clock Local';
......@@ -101,6 +115,15 @@ class FuchsiaArtifacts {
final String tools = fs.path.join(fuchsia, 'tools');
final String dartPrebuilts = fs.path.join(tools, 'dart_prebuilts');
final File devFinder = fs.file(fs.path.join(tools, 'dev_finder'));
final File platformDill = fs.file(fs.path.join(
dartPrebuilts, 'flutter_runner', 'platform_strong.dill'));
final File patchedSdk = fs.file(fs.path.join(
dartPrebuilts, 'flutter_runner'));
final File kernelCompiler = fs.file(fs.path.join(
dartPrebuilts, 'kernel_compiler.snapshot'));
final File pm = fs.file(fs.path.join(tools, 'pm'));
// If FUCHSIA_BUILD_DIR is defined, then look for the ssh_config dir
// relative to it. Next, if FUCHSIA_SSH_CONFIG is defined, then use it.
// TODO(zra): Consider passing the ssh config path in with a flag.
......@@ -113,14 +136,11 @@ class FuchsiaArtifacts {
}
return FuchsiaArtifacts(
sshConfig: sshConfig,
devFinder: fs.file(fs.path.join(tools, 'dev_finder')),
platformKernelDill: fs.file(fs.path.join(
dartPrebuilts, 'flutter_runner', 'platform_strong.dill')),
flutterPatchedSdk: fs.file(fs.path.join(
dartPrebuilts, 'flutter_runner')),
kernelCompiler: fs.file(fs.path.join(
dartPrebuilts, 'kernel_compiler.snapshot')),
pm: fs.file(fs.path.join(tools, 'pm')),
devFinder: devFinder.existsSync() ? devFinder : null,
platformKernelDill: platformDill.existsSync() ? platformDill : null,
flutterPatchedSdk: patchedSdk.existsSync() ? patchedSdk : null,
kernelCompiler: kernelCompiler.existsSync() ? kernelCompiler : null,
pm: pm.existsSync() ? pm : null,
);
}
......
// Copyright 2019 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 '../base/process.dart';
import '../globals.dart';
import 'fuchsia_device.dart';
// Usage: tiles_ctl <command>
// Supported commands:
// start
// add [--disable-focus] <url> [<args>...]
// remove <key>
// list
// quit
/// A simple wrapper around the 'tiles_ctl' tool running on the Fuchsia device.
class FuchsiaTilesCtl {
/// Finds the key for the app called [appName], or returns -1 if it can't be
/// found.
static Future<int> findAppKey(FuchsiaDevice device, String appName) async {
final FuchsiaTilesCtl tilesCtl = fuchsiaDeviceTools.tilesCtl;
final Map<int, String> runningApps = await tilesCtl.list(device);
if (runningApps == null) {
printTrace('tiles_ctl is not running');
return -1;
}
for (MapEntry<int, String> entry in runningApps.entries) {
if (entry.value.contains('$appName#meta')) {
return entry.key;
}
}
return -1;
}
/// Ensures that tiles is running on the device.
static Future<bool> ensureStarted(FuchsiaDevice device) async {
final FuchsiaTilesCtl tilesCtl = fuchsiaDeviceTools.tilesCtl;
final Map<int, String> runningApps = await tilesCtl.list(device);
if (runningApps == null) {
return tilesCtl.start(device);
}
return true;
}
/// Instructs 'tiles' to start on the device.
///
/// Returns true on success and false on failure.
Future<bool> start(FuchsiaDevice device) async {
final RunResult result = await device.shell('tiles_ctl start');
return result.exitCode == 0;
}
/// Returns a mapping of tile keys to app urls.
///
/// Returns an empty mapping if tiles_ctl is running but no apps are running.
/// Returns null if tiles_ctl is not running.
Future<Map<int, String>> list(FuchsiaDevice device) async {
// Output of tiles_ctl list has the format:
// Found 1 tiles:
// Tile key 1 url fuchsia-pkg://fuchsia.com/stocks#meta/stocks.cmx ...
final Map<int, String> tiles = <int, String>{};
final RunResult result = await device.shell('tiles_ctl list');
if (result.exitCode != 0) {
return null;
}
// Look for evidence that tiles_ctl is not running.
if (result.stdout.contains("Couldn't find tiles component in realm")) {
return null;
}
// Find lines beginning with 'Tile'
for (String line in result.stdout.split('\n')) {
final List<String> words = line.split(' ');
if (words.isNotEmpty && words[0] == 'Tile') {
final int key = int.tryParse(words[2]);
final String url = words[4];
tiles[key] = url;
}
}
return tiles;
}
/// Instructs tiles on the device to begin running the app at [url] in a new
/// tile.
///
/// The app is passed the arguemnts in [args]. Flutter apps receive these
/// arguments as arguments to `main()`. [url] should be formatted as a
/// Fuchsia-style package url, e.g.:
/// fuchsia-pkg://fuchsia.com/flutter_gallery#meta/flutter_gallery.cmx
/// Returns true on success and false on failure.
Future<bool> add(FuchsiaDevice device, String url, List<String> args) async {
final RunResult result = await device.shell(
'tiles_ctl add $url ${args.join(" ")}');
return result.exitCode == 0;
}
/// Instructs tiles on the device to remove the app with key [key].
///
/// Returns true on success and false on failure.
Future<bool> remove(FuchsiaDevice device, int key) async {
final RunResult result = await device.shell('tiles_ctl remove $key');
return result.exitCode == 0;
}
/// Instructs tiles on the device to quit.
///
/// Returns true on success and false on failure.
Future<bool> quit(FuchsiaDevice device) async {
final RunResult result = await device.shell('tiles_ctl quit');
return result.exitCode == 0;
}
}
......@@ -313,9 +313,15 @@ class FlutterDevice {
}
void startEchoingDeviceLog() {
if (_loggingSubscription != null)
if (_loggingSubscription != null) {
return;
_loggingSubscription = device.getLogReader(app: package).logLines.listen((String line) {
}
final Stream<String> logStream = device.getLogReader(app: package).logLines;
if (logStream == null) {
printError('Failed to read device log stream');
return;
}
_loggingSubscription = logStream.listen((String line) {
if (!line.contains('Observatory listening on http'))
printStatus(line, wrap: false);
});
......
......@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
......@@ -25,69 +26,99 @@ void main() {
MemoryFileSystem memoryFileSystem;
MockPlatform linuxPlatform;
MockPlatform windowsPlatform;
MockFuchsiaPM fuchsiaPM;
MockFuchsiaKernelCompiler fuchsiaKernelCompiler;
MockFuchsiaSdk fuchsiaSdk;
MockFuchsiaArtifacts fuchsiaArtifacts;
MockFuchsiaArtifacts fuchsiaArtifactsNoCompiler;
setUp(() {
memoryFileSystem = MemoryFileSystem();
linuxPlatform = MockPlatform();
windowsPlatform = MockPlatform();
fuchsiaPM = MockFuchsiaPM();
fuchsiaKernelCompiler = MockFuchsiaKernelCompiler();
fuchsiaSdk = MockFuchsiaSdk();
fuchsiaArtifacts = MockFuchsiaArtifacts();
fuchsiaArtifactsNoCompiler = MockFuchsiaArtifacts();
when(linuxPlatform.isLinux).thenReturn(true);
when(windowsPlatform.isWindows).thenReturn(true);
when(windowsPlatform.isLinux).thenReturn(false);
when(windowsPlatform.isMacOS).thenReturn(false);
when(fuchsiaArtifacts.kernelCompiler).thenReturn(MockFile());
when(fuchsiaArtifactsNoCompiler.kernelCompiler).thenReturn(null);
});
testUsingContext('Fuchsia build fails when there is no fuchsia project',
() async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
});
testUsingContext('Fuchsia build fails when there is no cmx file', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.directory('fuchsia').createSync(recursive: true);
fs.file('.packages').createSync();
fs.file('pubspec.yaml').createSync();
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
});
testUsingContext('Fuchsia build fails on Windows platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
const String appName = 'app_name';
fs
.file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
.createSync(recursive: true);
fs.file('.packages').createSync();
final File pubspecFile = fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
FileSystem: () => memoryFileSystem,
group('Fuchsia build fails gracefully when', () {
testUsingContext('there is no Fuchsia project',
() async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifacts,
});
testUsingContext('there is no cmx file', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.directory('fuchsia').createSync(recursive: true);
fs.file('.packages').createSync();
fs.file('pubspec.yaml').createSync();
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifacts,
});
testUsingContext('on Windows platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
const String appName = 'app_name';
fs
.file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
.createSync(recursive: true);
fs.file('.packages').createSync();
final File pubspecFile = fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifacts,
});
testUsingContext('there is no Fuchsia kernel compiler', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
const String appName = 'app_name';
fs
.file(fs.path.join('fuchsia', 'meta', '$appName.cmx'))
.createSync(recursive: true);
fs.file('.packages').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
final File pubspecFile = fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
expect(
createTestCommandRunner(command)
.run(const <String>['build', 'fuchsia']),
throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifactsNoCompiler,
});
});
testUsingContext('Fuchsia build parts fit together right', () async {
......@@ -110,8 +141,7 @@ void main() {
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaPM: () => fuchsiaPM,
FuchsiaKernelCompiler: () => fuchsiaKernelCompiler,
FuchsiaSdk: () => fuchsiaSdk,
});
}
......@@ -189,3 +219,16 @@ class MockFuchsiaKernelCompiler extends Mock implements FuchsiaKernelCompiler {
fs.file(manifestPath).createSync(recursive: true);
}
}
class MockFuchsiaSdk extends Mock implements FuchsiaSdk {
@override
final FuchsiaPM fuchsiaPM = MockFuchsiaPM();
@override
final FuchsiaKernelCompiler fuchsiaKernelCompiler =
MockFuchsiaKernelCompiler();
}
class MockFile extends Mock implements File {}
class MockFuchsiaArtifacts extends Mock implements FuchsiaArtifacts {}
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