Unverified Commit 6a51e0c2 authored by michaellee8's avatar michaellee8 Committed by GitHub

[flutter_tools] Add support for launching fuchsia app using session_control (#85752)

parent d3c3b891
......@@ -32,6 +32,7 @@ import 'fuchsia_build.dart';
import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart';
import 'fuchsia_workflow.dart';
import 'session_control.dart';
import 'tiles_ctl.dart';
/// The [FuchsiaDeviceTools] instance.
......@@ -44,6 +45,9 @@ class FuchsiaDeviceTools {
FuchsiaTilesCtl _tilesCtl;
FuchsiaTilesCtl get tilesCtl => _tilesCtl ??= FuchsiaTilesCtl();
FuchsiaSessionControl _sessionControl;
FuchsiaSessionControl get sessionControl => _sessionControl ??= FuchsiaSessionControl();
}
final String _ipv4Loopback = InternetAddress.loopbackIPv4.address;
......@@ -266,6 +270,22 @@ class FuchsiaDevice extends Device {
@override
bool get supportsStartPaused => false;
bool _isSession;
Future<bool> get isSession async => _isSession ??= await _initIsSession();
/// Determine if the Fuchsia device is running a session based build.
///
/// If the device is running a session based build, `session_control` should be
/// used to launch apps, otherwise `tiles_ctl` should be used.
Future<bool> _initIsSession() async {
final RunResult result = await shell('which session_control');
if (result.exitCode != 0) {
return false;
}
return true;
}
@override
Future<bool> isAppInstalled(
ApplicationPackage app, {
......@@ -304,6 +324,12 @@ class FuchsiaDevice extends Device {
bool ipv6 = false,
String userIdentifier,
}) async {
if (await isSession) {
globals.printTrace('Running on a session framework based build.');
} else {
globals.printTrace('Running on a non session framework based build.');
}
if (!prebuiltApplication) {
await buildFuchsia(fuchsiaProject: FlutterProject.current().fuchsia,
targetPlatform: await targetPlatform,
......@@ -341,17 +367,26 @@ class FuchsiaDevice extends Device {
);
FuchsiaPackageServer fuchsiaPackageServer;
bool serverRegistered = false;
String fuchsiaUrl;
try {
// Ask amber to pre-fetch some things we'll need before setting up our own
// package server. This is to avoid relying on amber correctly using
// multiple package servers, support for which is in flux.
if (!await fuchsiaDeviceTools.amberCtl.getUp(this, 'tiles')) {
globals.printError('Failed to get amber to prefetch tiles');
return LaunchResult.failed();
}
if (!await fuchsiaDeviceTools.amberCtl.getUp(this, 'tiles_ctl')) {
globals.printError('Failed to get amber to prefetch tiles_ctl');
return LaunchResult.failed();
if (await isSession) {
// Prefetch session_control
if (!await fuchsiaDeviceTools.amberCtl.getUp(this, 'session_control')) {
globals.printError('Failed to get amber to prefetch session_control');
return LaunchResult.failed();
}
} else {
// Ask amber to pre-fetch some things we'll need before setting up our own
// package server. This is to avoid relying on amber correctly using
// multiple package servers, support for which is in flux.
if (!await fuchsiaDeviceTools.amberCtl.getUp(this, 'tiles')) {
globals.printError('Failed to get amber to prefetch tiles');
return LaunchResult.failed();
}
if (!await fuchsiaDeviceTools.amberCtl.getUp(this, 'tiles_ctl')) {
globals.printError('Failed to get amber to prefetch tiles_ctl');
return LaunchResult.failed();
}
}
// Start up a package server.
......@@ -417,17 +452,26 @@ class FuchsiaDevice extends Device {
return LaunchResult.failed();
}
// Ensure tiles_ctl is started, and start the app.
if (!await FuchsiaTilesCtl.ensureStarted(this)) {
globals.printError('Failed to ensure that tiles is started on the device');
return LaunchResult.failed();
}
fuchsiaUrl = 'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cmx';
// Instruct tiles_ctl to start the app.
final String fuchsiaUrl = 'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cmx';
if (!await fuchsiaDeviceTools.tilesCtl.add(this, fuchsiaUrl, <String>[])) {
globals.printError('Failed to add the app to tiles');
return LaunchResult.failed();
if (await isSession) {
// Instruct session_control to start the app
if (!await fuchsiaDeviceTools.sessionControl.add(this, fuchsiaUrl)) {
globals.printError('Failed to add the app to session_control');
return LaunchResult.failed();
}
} else {
// Ensure tiles_ctl is started, and start the app.
if (!await FuchsiaTilesCtl.ensureStarted(this)) {
globals.printError('Failed to ensure that tiles is started on the device');
return LaunchResult.failed();
}
// Instruct tiles_ctl to start the app.
if (!await fuchsiaDeviceTools.tilesCtl.add(this, fuchsiaUrl, <String>[])) {
globals.printError('Failed to add the app to tiles');
return LaunchResult.failed();
}
}
} finally {
// Try to un-teach the package controller about the package server if
......@@ -470,6 +514,11 @@ class FuchsiaDevice extends Device {
covariant FuchsiaApp app, {
String userIdentifier,
}) async {
if (await isSession) {
// Currently there are no way to close a running app programmatically
// using the session framework afaik. So this is a no-op.
return true;
}
final int appKey = await FuchsiaTilesCtl.findAppKey(this, app.id);
if (appKey != -1) {
if (!await fuchsiaDeviceTools.tilesCtl.remove(this, appKey)) {
......@@ -522,7 +571,7 @@ class FuchsiaDevice extends Device {
throw 'Could not take a screenshot on device $name:\n$screencapResult';
}
try {
final RunResult scpResult = await scp('/tmp/screenshot.ppm', outputFile.path);
final RunResult scpResult = await scp('/tmp/screenshot.ppm', outputFile.path);
if (scpResult.exitCode != 0) {
throw 'Failed to copy screenshot from device:\n$scpResult';
}
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import '../base/process.dart';
import 'fuchsia_device.dart';
// Usage: session_control <command> [<args>]
//
// Various operations to control sessions.
//
// Options:
// --help display usage information
//
// Commands:
// launch Launch a new session.
// restart Restart the current session.
// add Add an element to the current session.
//
// Usage: session_control launch <session_url>
//
// Launch a new session.
//
// Options:
// --help display usage information
//
// Usage: session_control restart
//
// Restart the current session.
//
// Options:
// --help display usage information
//
//
// Usage: session_control add <element_url>
//
// Add an element to the current session.
//
// Options:
// --help display usage information
/// A simple wrapper around the 'session_control' tool running on the Fuchsia device.
class FuchsiaSessionControl {
/// Instructs session_control on the device to add the app at [url] as an element.
///
/// [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) async {
final RunResult result = await device.shell('session_control add $url');
return result.exitCode == 0;
}
}
......@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/fuchsia/amber_ctl.dart';
......@@ -23,6 +24,7 @@ import 'package:flutter_tools/src/fuchsia/fuchsia_ffx.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/session_control.dart';
import 'package:flutter_tools/src/fuchsia/tiles_ctl.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/project.dart';
......@@ -41,7 +43,8 @@ void main() {
FakeFuchsiaSdk fuchsiaSdk;
Artifacts artifacts;
FakeProcessManager fakeSuccessfulProcessManager;
FakeProcessManager fakeFailedProcessManager;
FakeProcessManager fakeFailedProcessManagerForHostAddress;
FakeProcessManager fakeSuccessfulProcessManagerWithSession;
File sshConfig;
setUp(() {
......@@ -73,19 +76,32 @@ void main() {
).createSync();
}
fakeSuccessfulProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
exitCode: 1,
),
FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'],
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]);
fakeFailedProcessManager = FakeProcessManager.list(<FakeCommand>[
fakeFailedProcessManagerForHostAddress = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'],
stdout: '',
stderr: '',
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
exitCode: 1,
),
]);
fakeSuccessfulProcessManagerWithSession = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
stdout: '/bin/session_control',
),
FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'],
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]);
});
Future<LaunchResult> setupAndStartApp({
......@@ -134,6 +150,21 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start prebuilt in release mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start and stop prebuilt in release mode', () async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
......@@ -161,6 +192,33 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start and stop prebuilt in release mode with session', () async {
const String appName = 'app_name';
final FuchsiaDevice device = FuchsiaDeviceWithFakeDiscovery('123');
globals.fs.directory('fuchsia').createSync(recursive: true);
final File pubspecFile = globals.fs.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync('name: $appName');
final File far = globals.fs.file('app_name-0.far')..createSync();
final FuchsiaApp app = FuchsiaApp.fromPrebuiltApp(far);
final DebuggingOptions debuggingOptions =
DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false));
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>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start prebuilt in debug mode', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
......@@ -176,6 +234,21 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start prebuilt in debug mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.debug);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isTrue);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start buildable in release mode', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: false, mode: BuildMode.release);
......@@ -185,6 +258,10 @@ void main() {
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
exitCode: 1,
),
const FakeCommand(
command: <String>[
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
......@@ -205,6 +282,39 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start buildable in release mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: false, mode: BuildMode.release);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ssh', '-F', '/ssh_config', '123', 'which session_control'],
stdout: '/bin/session_control',
),
const FakeCommand(
command: <String>[
'Artifact.genSnapshot.TargetPlatform.fuchsia_arm64.release',
'--deterministic',
'--snapshot_kind=app-aot-elf',
'--elf=build/fuchsia/elf.aotsnapshot',
'build/fuchsia/app_name.dil'
],
),
FakeCommand(
command: <String>['ssh', '-F', sshConfig.absolute.path, '123', r'echo $SSH_CONNECTION'],
stdout: 'fe80::8c6c:2fff:fe3d:c5e1%ethp0003 50666 fe80::5054:ff:fe63:5e7a%ethp0003 22',
),
]),
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start buildable in debug mode', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: false, mode: BuildMode.debug);
......@@ -220,6 +330,21 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('start buildable in debug mode with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: false, mode: BuildMode.debug);
expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isTrue);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail when cant get ssh config', () async {
expect(() async =>
setupAndStartApp(prebuilt: true, mode: BuildMode.release),
......@@ -240,10 +365,13 @@ void main() {
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeFailedProcessManager,
ProcessManager: () => fakeFailedProcessManagerForHostAddress,
FuchsiaDeviceTools: () => fuchsiaDeviceTools,
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
OperatingSystemUtils: () => osUtils,
Platform: () => FakePlatform(
operatingSystem: 'linux',
),
});
testUsingContext('fail with correct LaunchResult when pm fails', () async {
......@@ -291,6 +419,21 @@ void main() {
OperatingSystemUtils: () => osUtils,
});
testUsingContext('fail with correct LaunchResult when tiles fails with session', () async {
final LaunchResult launchResult =
await setupAndStartApp(prebuilt: true, mode: BuildMode.release);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
FileSystem: () => memoryFileSystem,
ProcessManager: () => fakeSuccessfulProcessManagerWithSession,
FuchsiaDeviceTools: () => FakeFuchsiaDeviceTools(sessionControl: FailingFuchsiaSessionControl()),
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
FuchsiaSdk: () => fuchsiaSdk,
OperatingSystemUtils: () => osUtils,
});
});
}
......@@ -474,18 +617,37 @@ class FailingTilesCtl implements FuchsiaTilesCtl {
}
}
class FakeFuchsiaSessionControl implements FuchsiaSessionControl {
@override
Future<bool> add(FuchsiaDevice device, String url) async {
return true;
}
}
class FailingFuchsiaSessionControl implements FuchsiaSessionControl {
@override
Future<bool> add(FuchsiaDevice device, String url) async {
return false;
}
}
class FakeFuchsiaDeviceTools implements FuchsiaDeviceTools {
FakeFuchsiaDeviceTools({
FuchsiaAmberCtl amber,
FuchsiaTilesCtl tiles,
FuchsiaSessionControl sessionControl,
}) : amberCtl = amber ?? FakeFuchsiaAmberCtl(),
tilesCtl = tiles ?? FakeFuchsiaTilesCtl();
tilesCtl = tiles ?? FakeFuchsiaTilesCtl(),
sessionControl = sessionControl ?? FakeFuchsiaSessionControl();
@override
final FuchsiaAmberCtl amberCtl;
@override
final FuchsiaTilesCtl tilesCtl;
@override
final FuchsiaSessionControl sessionControl;
}
class FakeFuchsiaPM implements FuchsiaPM {
......
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