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;
}
}
......@@ -9,8 +9,11 @@ import 'package:meta/meta.dart';
import '../application_package.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/os.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/process_manager.dart';
......@@ -21,8 +24,28 @@ import '../globals.dart';
import '../project.dart';
import '../vmservice.dart';
import 'amber_ctl.dart';
import 'application_package.dart';
import 'fuchsia_build.dart';
import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart';
import 'fuchsia_workflow.dart';
import 'tiles_ctl.dart';
/// The [FuchsiaDeviceTools] instance.
FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>();
/// Fuchsia device-side tools.
class FuchsiaDeviceTools {
FuchsiaAmberCtl _amberCtl;
FuchsiaAmberCtl get amberCtl => _amberCtl ??= FuchsiaAmberCtl();
FuchsiaTilesCtl _tilesCtl;
FuchsiaTilesCtl get tilesCtl => _tilesCtl ??= FuchsiaTilesCtl();
}
final FuchsiaAmberCtl _amberCtl = fuchsiaDeviceTools.amberCtl;
final FuchsiaTilesCtl _tilesCtl = fuchsiaDeviceTools.tilesCtl;
final String _ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String _ipv6Loopback = InternetAddress.loopbackIPv6.address;
......@@ -48,11 +71,15 @@ class _FuchsiaLogReader extends DeviceLogReader {
Stream<String> _logLines;
@override
Stream<String> get logLines {
_logLines ??= _processLogs(fuchsiaSdk.syslogs(_device.id));
final Stream<String> logStream = fuchsiaSdk.syslogs(_device.id);
_logLines ??= _processLogs(logStream);
return _logLines;
}
Stream<String> _processLogs(Stream<String> lines) {
if (lines == null) {
return null;
}
// Get the starting time of the log processor to filter logs from before
// the process attached.
final DateTime startTime = systemClock.now();
......@@ -60,7 +87,7 @@ class _FuchsiaLogReader extends DeviceLogReader {
// the correct fuchsia module.
final RegExp matchRegExp = _app == null
? _flutterLogOutput
: RegExp('INFO: ${_app.name}\\(flutter\\): ');
: RegExp('INFO: ${_app.name}(\.cmx)?\\(flutter\\): ');
return Stream<String>.eventTransformed(
lines,
(Sink<String> outout) => _FuchsiaLogSink(outout, matchRegExp, startTime),
......@@ -188,7 +215,7 @@ class FuchsiaDevice extends Device {
@override
Future<LaunchResult> startApp(
ApplicationPackage package, {
covariant FuchsiaApp package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
......@@ -196,14 +223,111 @@ class FuchsiaDevice extends Device {
bool prebuiltApplication = false,
bool usesTerminalUi = true,
bool ipv6 = false,
}) =>
Future<void>.error('unimplemented');
}) async {
if (!prebuiltApplication) {
await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia,
target: mainPath,
buildInfo: debuggingOptions.buildInfo);
}
// Stop the app if it's currently running.
await stopApp(package);
// Find out who the device thinks we are.
final String host = await fuchsiaSdk.fuchsiaDevFinder.resolve(name);
final int port = await os.findFreePort();
if (port == 0) {
printError('Failed to find a free port');
return LaunchResult.failed();
}
final Directory packageRepo =
fs.directory(fs.path.join(getFuchsiaBuildDirectory(), '.pkg-repo'));
packageRepo.createSync(recursive: true);
final String appName = FlutterProject.current().manifest.appName;
final Status status = logger.startProgress(
'Starting Fuchsia application...',
timeout: null,
);
FuchsiaPackageServer fuchsiaPackageServer;
bool serverRegistered = false;
try {
// Start up a package server.
fuchsiaPackageServer = FuchsiaPackageServer(packageRepo.path, host, port);
if (!await fuchsiaPackageServer.start()) {
printError('Failed to start the Fuchsia package server');
return LaunchResult.failed();
}
final File farArchive = package.farArchive(
debuggingOptions.buildInfo.mode);
if (!await fuchsiaPackageServer.addPackage(farArchive)) {
printError('Failed to add package to the package server');
return LaunchResult.failed();
}
// Teach amber about the package server.
if (!await _amberCtl.addSrc(this, fuchsiaPackageServer)) {
printError('Failed to teach amber about the package server');
return LaunchResult.failed();
}
serverRegistered = true;
// Tell amber to prefetch the app.
if (!await _amberCtl.getUp(this, appName)) {
printError('Failed to get amber to prefetch the package');
return LaunchResult.failed();
}
// Ensure tiles_ctl is started, and start the app.
if (!await FuchsiaTilesCtl.ensureStarted(this)) {
printError('Failed to ensure that tiles is started on the device');
return LaunchResult.failed();
}
// Instruct tiles_ctl to start the app.
final String fuchsiaUrl =
'fuchsia-pkg://fuchsia.com/$appName#meta/$appName.cmx';
if (!await _tilesCtl.add(this, fuchsiaUrl, <String>[])) {
printError('Failed to add the app to tiles');
return LaunchResult.failed();
}
} finally {
// Try to un-teach amber about the package server if needed.
if (serverRegistered) {
await _amberCtl.rmSrc(this, fuchsiaPackageServer);
}
// Shutdown the package server and delete the package repo;
fuchsiaPackageServer.stop();
packageRepo.deleteSync(recursive: true);
status.cancel();
}
if (!debuggingOptions.buildInfo.isDebug &&
!debuggingOptions.buildInfo.isProfile) {
return LaunchResult.succeeded();
}
// In a debug or profile build, try to find the observatory uri.
final FuchsiaIsolateDiscoveryProtocol discovery =
FuchsiaIsolateDiscoveryProtocol(this, appName);
try {
final Uri observatoryUri = await discovery.uri;
return LaunchResult.succeeded(observatoryUri: observatoryUri);
} finally {
discovery.dispose();
}
}
@override
Future<bool> stopApp(ApplicationPackage app) async {
// Currently we don't have a way to stop an app running on Fuchsia.
Future<bool> stopApp(covariant FuchsiaApp app) async {
final int appKey = await FuchsiaTilesCtl.findAppKey(this, app.id);
if (appKey != -1) {
if (!await _tilesCtl.remove(this, appKey)) {
printError('tiles_ctl remove on ${app.id} failed.');
return false;
}
}
return true;
}
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia;
......@@ -250,7 +374,13 @@ class FuchsiaDevice extends Device {
/// List the ports currently running a dart observatory.
Future<List<int>> servicePorts() async {
final String findOutput = await shell('find /hub -name vmservice-port');
const String findCommand = 'find /hub -name vmservice-port';
final RunResult findResult = await shell(findCommand);
if (findResult.exitCode != 0) {
throwToolExit("'$findCommand' on device $id failed");
return null;
}
final String findOutput = findResult.stdout;
if (findOutput.trim() == '') {
throwToolExit(
'No Dart Observatories found. Are you running a debug build?');
......@@ -261,7 +391,13 @@ class FuchsiaDevice extends Device {
if (path == '') {
continue;
}
final String lsOutput = await shell('ls $path');
final String lsCommand = 'ls $path';
final RunResult lsResult = await shell(lsCommand);
if (lsResult.exitCode != 0) {
throwToolExit("'$lsCommand' on device $id failed");
return null;
}
final String lsOutput = lsResult.stdout;
for (String line in lsOutput.split('\n')) {
if (line == '') {
continue;
......@@ -276,20 +412,18 @@ class FuchsiaDevice extends Device {
}
/// Run `command` on the Fuchsia device shell.
Future<String> shell(String command) async {
final RunResult result = await runAsync(<String>[
Future<RunResult> shell(String command) async {
if (fuchsiaArtifacts.sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
}
return await runAsync(<String>[
'ssh',
'-F',
fuchsiaArtifacts.sshConfig.absolute.path,
id,
command
]);
if (result.exitCode != 0) {
throwToolExit(
'Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
return null;
}
return result.stdout;
}
/// Finds the first port running a VM matching `isolateName` from the
......@@ -332,7 +466,9 @@ class FuchsiaDevice extends Device {
FuchsiaIsolateDiscoveryProtocol(this, isolateName);
@override
bool isSupportedForProject(FlutterProject flutterProject) => true;
bool isSupportedForProject(FlutterProject flutterProject) {
return flutterProject.fuchsia.existsSync();
}
}
class FuchsiaIsolateDiscoveryProtocol {
......@@ -431,7 +567,10 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
@override
Future<int> forward(int devicePort, {int hostPort}) async {
hostPort ??= await _findPort();
hostPort ??= await os.findFreePort();
if (hostPort == 0) {
throwToolExit('Failed to forward port $devicePort. No free host-side ports');
}
// Note: the provided command works around a bug in -N, see US-515
// for more explanation.
final List<String> command = <String>[
......@@ -483,27 +622,4 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
throwToolExit(result.stderr);
}
}
static Future<int> _findPort() async {
int port = 0;
ServerSocket serverSocket;
try {
serverSocket = await ServerSocket.bind(_ipv4Loopback, 0);
port = serverSocket.port;
} catch (e) {
// Failures are signaled by a return value of 0 from this function.
printTrace('_findPort failed: $e');
}
if (serverSocket != null) {
await serverSocket.close();
}
return port;
}
}
class FuchsiaModulePackage extends ApplicationPackage {
FuchsiaModulePackage({@required this.name}) : super(id: name);
@override
final String name;
}
......@@ -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;
}
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,23 +26,28 @@ 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',
group('Fuchsia build fails gracefully when', () {
testUsingContext('there is no Fuchsia project',
() async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
......@@ -52,9 +58,10 @@ void main() {
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifacts,
});
testUsingContext('Fuchsia build fails when there is no cmx file', () async {
testUsingContext('there is no cmx file', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.directory('fuchsia').createSync(recursive: true);
......@@ -68,9 +75,10 @@ void main() {
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
FileSystem: () => memoryFileSystem,
FuchsiaArtifacts: () => fuchsiaArtifacts,
});
testUsingContext('Fuchsia build fails on Windows platform', () async {
testUsingContext('on Windows platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
const String appName = 'app_name';
......@@ -88,6 +96,29 @@ void main() {
}, 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 {}
......@@ -5,26 +5,42 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/fuchsia/application_package.dart';
import 'package:flutter_tools/src/fuchsia/amber_ctl.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_dev_finder.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/fuchsia/tiles_ctl.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('fuchsia device', () {
MemoryFileSystem memoryFileSystem;
setUp(() {
memoryFileSystem = MemoryFileSystem();
});
testUsingContext('stores the requested id and name', () {
const String deviceId = 'e80::0000:a00a:f00f:2002/3';
const String name = 'halfbaked';
......@@ -49,14 +65,34 @@ void main() {
expect(names.length, 0);
});
test('default capabilities', () async {
testUsingContext('default capabilities', () async {
final FuchsiaDevice device = FuchsiaDevice('123');
fs.directory('fuchsia').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
expect(device.supportsHotReload, true);
expect(device.supportsHotRestart, false);
expect(device.supportsStopApp, false);
expect(device.isSupportedForProject(null), true);
expect(await device.stopApp(null), false);
expect(device.isSupportedForProject(FlutterProject.current()), true);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
testUsingContext('supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123');
fs.directory('fuchsia').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
expect(device.isSupportedForProject(FlutterProject.current()), true);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
testUsingContext('not supported for project', () async {
final FuchsiaDevice device = FuchsiaDevice('123');
fs.file('pubspec.yaml').createSync();
expect(device.isSupportedForProject(FlutterProject.current()), false);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
});
......@@ -215,7 +251,7 @@ void main() {
testUsingContext('can be parsed for an app', () async {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
final DeviceLogReader reader = device.getLogReader(
app: FuchsiaModulePackage(name: 'example_app.cmx'));
app: FuchsiaModulePackage(name: 'example_app'));
final List<String> logLines = <String>[];
final Completer<void> lock = Completer<void>();
reader.logLines.listen((String line) {
......@@ -244,7 +280,7 @@ void main() {
testUsingContext('cuts off prior logs', () async {
final FuchsiaDevice device = FuchsiaDevice('id', name: 'tester');
final DeviceLogReader reader = device.getLogReader(
app: FuchsiaModulePackage(name: 'example_app.cmx'));
app: FuchsiaModulePackage(name: 'example_app'));
final List<String> logLines = <String>[];
final Completer<void> lock = Completer<void>();
reader.logLines.listen((String line) {
......@@ -367,16 +403,119 @@ void main() {
Logger: () => StdoutLogger(),
});
});
group('fuchsia app start and stop: ', () {
MemoryFileSystem memoryFileSystem;
MockOperatingSystemUtils osUtils;
MockFuchsiaDeviceTools fuchsiaDeviceTools;
MockFuchsiaSdk fuchsiaSdk;
setUp(() {
memoryFileSystem = MemoryFileSystem();
osUtils = MockOperatingSystemUtils();
fuchsiaDeviceTools = MockFuchsiaDeviceTools();
fuchsiaSdk = MockFuchsiaSdk();
when(osUtils.findFreePort()).thenAnswer((_) => Future<int>.value(12345));
});
testUsingContext('start prebuilt app in release mode', () async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDevice('123');
fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final File far = fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions =
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null));
final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true,
debuggingOptions: debuggingOptions);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start and stop prebuilt app in release mode', () async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDevice('123');
fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final File far = fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions =
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null));
final LaunchResult launchResult = await device.startApp(app,
prebuiltApplication: true,
debuggingOptions: debuggingOptions);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
expect(await device.stopApp(app), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
});
}
class FuchsiaModulePackage extends ApplicationPackage {
FuchsiaModulePackage({@required this.name}) : super(id: name);
@override
final String name;
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcessResult extends Mock implements ProcessResult {}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
class MockFile extends Mock implements File {}
class MockProcess extends Mock implements Process {}
Process _createMockProcess({
int exitCode = 0,
String stdout = '',
String stderr = '',
bool persistent = false,
}) {
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stdout),
]);
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode(stderr),
]);
final Process process = MockProcess();
when(process.stdout).thenAnswer((_) => stdoutStream);
when(process.stderr).thenAnswer((_) => stderrStream);
if (persistent) {
final Completer<int> exitCodeCompleter = Completer<int>();
when(process.kill()).thenAnswer((_) {
exitCodeCompleter.complete(-11);
return true;
});
when(process.exitCode).thenAnswer((_) => exitCodeCompleter.future);
} else {
when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode));
}
return process;
}
class MockFuchsiaDevice extends Mock implements FuchsiaDevice {
MockFuchsiaDevice(this.id, this.portForwarder, this.ipv6);
......@@ -419,3 +558,192 @@ class MockIsolate extends Mock implements Isolate {
@override
final String name;
}
class MockFuchsiaAmberCtl extends Mock implements FuchsiaAmberCtl {
@override
Future<bool> addSrc(FuchsiaDevice device, FuchsiaPackageServer server) async {
return true;
}
@override
Future<bool> rmSrc(FuchsiaDevice device, FuchsiaPackageServer server) async {
return true;
}
@override
Future<bool> getUp(FuchsiaDevice device, String packageName) async {
return true;
}
}
class MockFuchsiaTilesCtl extends Mock implements FuchsiaTilesCtl {
final Map<int, String> _runningApps = <int, String>{};
bool _started = false;
int _nextAppId = 1;
@override
Future<bool> start(FuchsiaDevice device) async {
_started = true;
return true;
}
@override
Future<Map<int, String>> list(FuchsiaDevice device) async {
if (!_started) {
return null;
}
return _runningApps;
}
@override
Future<bool> add(FuchsiaDevice device, String url, List<String> args) async {
if (!_started) {
return false;
}
_runningApps[_nextAppId] = url;
_nextAppId++;
return true;
}
@override
Future<bool> remove(FuchsiaDevice device, int key) async {
if (!_started) {
return false;
}
_runningApps.remove(key);
return true;
}
@override
Future<bool> quit(FuchsiaDevice device) async {
if (!_started) {
return false;
}
_started = false;
return true;
}
}
class MockFuchsiaDeviceTools extends Mock implements FuchsiaDeviceTools {
@override
final FuchsiaAmberCtl amberCtl = MockFuchsiaAmberCtl();
@override
final FuchsiaTilesCtl tilesCtl = MockFuchsiaTilesCtl();
}
class MockFuchsiaPM extends Mock implements FuchsiaPM {
String _appName;
@override
Future<bool> init(String buildPath, String appName) async {
if (!fs.directory(buildPath).existsSync()) {
return false;
}
fs
.file(fs.path.join(buildPath, 'meta', 'package'))
.createSync(recursive: true);
_appName = appName;
return true;
}
@override
Future<bool> genkey(String buildPath, String outKeyPath) async {
if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync()) {
return false;
}
fs.file(outKeyPath).createSync(recursive: true);
return true;
}
@override
Future<bool> build(
String buildPath, String keyPath, String manifestPath) async {
if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
!fs.file(keyPath).existsSync() ||
!fs.file(manifestPath).existsSync()) {
return false;
}
fs.file(fs.path.join(buildPath, 'meta.far')).createSync(recursive: true);
return true;
}
@override
Future<bool> archive(
String buildPath, String keyPath, String manifestPath) async {
if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() ||
!fs.file(keyPath).existsSync() ||
!fs.file(manifestPath).existsSync()) {
return false;
}
if (_appName == null) {
return false;
}
fs
.file(fs.path.join(buildPath, '$_appName-0.far'))
.createSync(recursive: true);
return true;
}
@override
Future<bool> newrepo(String repoPath) async {
if (!fs.directory(repoPath).existsSync()) {
return false;
}
return true;
}
@override
Future<Process> serve(String repoPath, String host, int port) async {
return _createMockProcess(persistent: true);
}
@override
Future<bool> publish(String repoPath, String packagePath) async {
if (!fs.directory(repoPath).existsSync()) {
return false;
}
if (!fs.file(packagePath).existsSync()) {
return false;
}
return true;
}
}
class MockFuchsiaKernelCompiler extends Mock implements FuchsiaKernelCompiler {
@override
Future<void> build({
@required FuchsiaProject fuchsiaProject,
@required String target, // E.g., lib/main.dart
BuildInfo buildInfo = BuildInfo.debug,
}) async {
final String outDir = getFuchsiaBuildDirectory();
final String appName = fuchsiaProject.project.manifest.appName;
final String manifestPath = fs.path.join(outDir, '$appName.dilpmanifest');
fs.file(manifestPath).createSync(recursive: true);
}
}
class MockFuchsiaDevFinder extends Mock implements FuchsiaDevFinder {
@override
Future<List<String>> list() async {
return <String>['192.168.42.172 scare-cable-skip-joy'];
}
@override
Future<String> resolve(String deviceName) async {
return '192.168.42.10';
}
}
class MockFuchsiaSdk extends Mock implements FuchsiaSdk {
@override
final FuchsiaPM fuchsiaPM = MockFuchsiaPM();
@override
final FuchsiaKernelCompiler fuchsiaKernelCompiler =
MockFuchsiaKernelCompiler();
@override
final FuchsiaDevFinder fuchsiaDevFinder = MockFuchsiaDevFinder();
}
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