Delete packages/flutter_tools/lib/src/fuchsia directory (#154880)

It's not being actively used, and fuchsia team does not have bandwidth
to maintain it.

Bug: https://b.corp.google.com/issues/353729557

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
zijiehe@ 2024-09-17 09:10:02 -07:00 committed by GitHub
parent 1db9a61348
commit a4f45471bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 2 additions and 1845 deletions

View File

@ -41,9 +41,6 @@ import 'flutter_application_package.dart';
import 'flutter_cache.dart';
import 'flutter_device_manager.dart';
import 'flutter_features.dart';
import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaArtifacts, FuchsiaSdk;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow, fuchsiaWorkflow;
import 'globals.dart' as globals;
import 'ios/ios_workflow.dart';
import 'ios/iproxy.dart';
@ -203,7 +200,6 @@ Future<T> runInContext<T>(
artifacts: globals.artifacts!,
flutterVersion: globals.flutterVersion,
androidWorkflow: androidWorkflow!,
fuchsiaWorkflow: fuchsiaWorkflow!,
xcDevice: globals.xcdevice!,
userMessages: globals.userMessages,
windowsWorkflow: windowsWorkflow!,
@ -211,7 +207,6 @@ Future<T> runInContext<T>(
platform: globals.platform,
featureFlags: featureFlags,
),
fuchsiaSdk: globals.fuchsiaSdk!,
operatingSystemUtils: globals.os,
customDevicesConfig: globals.customDevicesConfig,
nativeAssetsBuilder: globals.nativeAssetsBuilder,
@ -244,14 +239,6 @@ Future<T> runInContext<T>(
fs: globals.fs,
flutterRoot: Cache.flutterRoot!,
),
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
FuchsiaSdk: () => FuchsiaSdk(),
FuchsiaWorkflow: () => FuchsiaWorkflow(
featureFlags: featureFlags,
platform: globals.platform,
fuchsiaArtifacts: globals.fuchsiaArtifacts!,
),
GradleUtils: () => GradleUtils(
operatingSystemUtils: globals.os,
logger: globals.logger,

View File

@ -28,7 +28,6 @@ import 'custom_devices/custom_device_workflow.dart';
import 'device.dart';
import 'doctor_validator.dart';
import 'features.dart';
import 'fuchsia/fuchsia_workflow.dart';
import 'globals.dart' as globals;
import 'http_host_validator.dart';
import 'intellij/intellij_validator.dart';
@ -199,7 +198,6 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
return _workflows ??= <Workflow>[
if (globals.iosWorkflow!.appliesToHostPlatform) globals.iosWorkflow!,
if (androidWorkflow?.appliesToHostPlatform ?? false) androidWorkflow!,
if (fuchsiaWorkflow?.appliesToHostPlatform ?? false) fuchsiaWorkflow!,
if (linuxWorkflow.appliesToHostPlatform) linuxWorkflow,
if (macOSWorkflow.appliesToHostPlatform) macOSWorkflow,
if (windowsWorkflow?.appliesToHostPlatform ?? false) windowsWorkflow!,

View File

@ -12,7 +12,6 @@ import 'base/logger.dart';
import 'base/process.dart';
import 'base/user_messages.dart';
import 'build_info.dart';
import 'fuchsia/application_package.dart';
import 'globals.dart' as globals;
import 'ios/application_package.dart';
import 'linux/application_package.dart';
@ -104,9 +103,8 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory {
: WindowsApp.fromPrebuiltApp(applicationBinary);
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
return applicationBinary == null
? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia)
: FuchsiaApp.fromPrebuiltApp(applicationBinary);
// Unsupported yet.
throw UnimplementedError();
}
}
}

View File

@ -16,9 +16,6 @@ import 'custom_devices/custom_device.dart';
import 'custom_devices/custom_devices_config.dart';
import 'device.dart';
import 'features.dart';
import 'fuchsia/fuchsia_device.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'fuchsia/fuchsia_workflow.dart';
import 'ios/devices.dart';
import 'ios/ios_workflow.dart';
import 'ios/simulators.dart';
@ -48,11 +45,9 @@ class FlutterDeviceManager extends DeviceManager {
required XCDevice xcDevice,
required AndroidWorkflow androidWorkflow,
required IOSWorkflow iosWorkflow,
required FuchsiaWorkflow fuchsiaWorkflow,
required FlutterVersion flutterVersion,
required Artifacts artifacts,
required MacOSWorkflow macOSWorkflow,
required FuchsiaSdk fuchsiaSdk,
required UserMessages userMessages,
required OperatingSystemUtils operatingSystemUtils,
required WindowsWorkflow windowsWorkflow,
@ -77,12 +72,6 @@ class FlutterDeviceManager extends DeviceManager {
IOSSimulators(
iosSimulatorUtils: iosSimulatorUtils,
),
FuchsiaDevices(
fuchsiaSdk: fuchsiaSdk,
logger: logger,
fuchsiaWorkflow: fuchsiaWorkflow,
platform: platform,
),
FlutterTesterDevices(
fileSystem: fileSystem,
flutterVersion: flutterVersion,

View File

@ -1,80 +0,0 @@
// 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.
import '../application_package.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../project.dart';
abstract class FuchsiaApp extends ApplicationPackage {
FuchsiaApp({required String projectBundleId}) : super(id: projectBundleId);
/// Creates a new [FuchsiaApp] from a fuchsia sub project.
static FuchsiaApp? fromFuchsiaProject(FuchsiaProject project) {
if (!project.existsSync()) {
// If the project doesn't exist at all the current hint to run flutter
// create is accurate.
return null;
}
return BuildableFuchsiaApp(
project: project,
);
}
/// Creates a new [FuchsiaApp] from an existing .far archive.
///
/// [applicationBinary] is the path to the .far archive.
static FuchsiaApp? fromPrebuiltApp(FileSystemEntity applicationBinary) {
final FileSystemEntityType entityType = globals.fs.typeSync(applicationBinary.path);
if (entityType != FileSystemEntityType.file) {
globals.printError('File "${applicationBinary.path}" does not exist or is not a .far file. Use far archive.');
return null;
}
return PrebuiltFuchsiaApp(
applicationPackage: applicationBinary,
);
}
@override
String get displayName => id;
/// The location of the 'far' archive containing the built app.
File farArchive(BuildMode buildMode);
}
class PrebuiltFuchsiaApp extends FuchsiaApp implements PrebuiltApplicationPackage {
PrebuiltFuchsiaApp({
required this.applicationPackage,
}) : // TODO(zanderso): Extract the archive and extract the id from meta/package.
super(projectBundleId: applicationPackage.path);
@override
File farArchive(BuildMode buildMode) => globals.fs.file(applicationPackage);
@override
String get name => applicationPackage.path;
@override
final FileSystemEntity applicationPackage;
}
class BuildableFuchsiaApp extends FuchsiaApp {
BuildableFuchsiaApp({required this.project}) :
super(projectBundleId: project.project.manifest.appName);
final FuchsiaProject project;
@override
File farArchive(BuildMode buildMode) {
// TODO(zanderso): Distinguish among build modes.
final String outDir = getFuchsiaBuildDirectory();
final String pkgDir = globals.fs.path.join(outDir, 'pkg');
final String appName = project.project.manifest.appName;
return globals.fs.file(globals.fs.path.join(pkgDir, '$appName-0.far'));
}
@override
String get name => project.project.manifest.appName;
}

View File

@ -1,956 +0,0 @@
// 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.
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
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/net.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/time.dart';
import '../build_info.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../device_vm_service_discovery_for_attach.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../runner/flutter_command.dart';
import '../vmservice.dart';
import 'application_package.dart';
import 'fuchsia_ffx.dart';
import 'fuchsia_pm.dart';
import 'fuchsia_sdk.dart';
import 'fuchsia_workflow.dart';
import 'pkgctl.dart';
/// The [FuchsiaDeviceTools] instance.
FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!;
/// Fuchsia device-side tools.
class FuchsiaDeviceTools {
late final FuchsiaPkgctl pkgctl = FuchsiaPkgctl();
late final FuchsiaFfx ffx = FuchsiaFfx();
}
final String _ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String _ipv6Loopback = InternetAddress.loopbackIPv6.address;
// Enables testing the fuchsia isolate discovery
Future<FlutterVmService> _kDefaultFuchsiaIsolateDiscoveryConnector(Uri uri) {
return connectToVmService(uri, logger: globals.logger);
}
Future<void> _kDefaultDartDevelopmentServiceStarter(
Device device,
Uri vmServiceUri,
bool disableServiceAuthCodes,
) async {
await device.dds.startDartDevelopmentService(
vmServiceUri,
ddsPort: 0,
disableServiceAuthCodes: disableServiceAuthCodes,
ipv6: true,
);
}
/// Read the log for a particular device.
class _FuchsiaLogReader extends DeviceLogReader {
_FuchsiaLogReader(this._device, this._systemClock, [this._app]);
// \S matches non-whitespace characters.
static final RegExp _flutterLogOutput = RegExp(r'INFO: \S+\(flutter\): ');
final FuchsiaDevice _device;
final ApplicationPackage? _app;
final SystemClock _systemClock;
@override
String get name => _device.name;
Stream<String>? _logLines;
@override
Stream<String> get logLines {
final Stream<String>? logStream = globals.fuchsiaSdk?.syslogs(_device.id);
_logLines ??= _processLogs(logStream);
return _logLines ?? const Stream<String>.empty();
}
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();
// Determine if line comes from flutter, and optionally whether it matches
// the correct fuchsia module.
final ApplicationPackage? app = _app;
final RegExp matchRegExp = app == null
? _flutterLogOutput
: RegExp('INFO: ${app.name}(\\.cm)?\\(flutter\\): ');
return Stream<String>.eventTransformed(
lines,
(EventSink<String> output) => _FuchsiaLogSink(output, matchRegExp, startTime),
);
}
@override
String toString() => name;
@override
void dispose() {
// The Fuchsia SDK syslog process is killed when the subscription to the
// logLines Stream is canceled.
}
}
class _FuchsiaLogSink implements EventSink<String> {
_FuchsiaLogSink(this._outputSink, this._matchRegExp, this._startTime);
static final RegExp _utcDateOutput = RegExp(r'\d+\-\d+\-\d+ \d+:\d+:\d+');
final EventSink<String> _outputSink;
final RegExp _matchRegExp;
final DateTime _startTime;
@override
void add(String line) {
if (!_matchRegExp.hasMatch(line)) {
return;
}
final String? rawDate = _utcDateOutput.firstMatch(line)?.group(0);
if (rawDate == null) {
return;
}
final DateTime logTime = DateTime.parse(rawDate);
if (logTime.millisecondsSinceEpoch < _startTime.millisecondsSinceEpoch) {
return;
}
_outputSink.add(
'[${logTime.toLocal()}] Flutter: ${line.split(_matchRegExp).last}');
}
@override
void addError(Object error, [StackTrace? stackTrace]) {
_outputSink.addError(error, stackTrace);
}
@override
void close() {
_outputSink.close();
}
}
/// Device discovery for Fuchsia devices.
class FuchsiaDevices extends PollingDeviceDiscovery {
FuchsiaDevices({
required Platform platform,
required FuchsiaWorkflow fuchsiaWorkflow,
required FuchsiaSdk fuchsiaSdk,
required Logger logger,
}) : _platform = platform,
_fuchsiaWorkflow = fuchsiaWorkflow,
_fuchsiaSdk = fuchsiaSdk,
_logger = logger,
super('Fuchsia devices');
final Platform _platform;
final FuchsiaWorkflow _fuchsiaWorkflow;
final FuchsiaSdk _fuchsiaSdk;
final Logger _logger;
@override
bool get supportsPlatform => isFuchsiaSupportedPlatform(_platform);
@override
bool get canListAnything => _fuchsiaWorkflow.canListDevices;
@override
Future<List<Device>> pollingGetDevices({ Duration? timeout }) async {
if (!_fuchsiaWorkflow.canListDevices) {
return <Device>[];
}
// TODO(omerlevran): Remove once soft transition is complete fxb/67602.
final List<String>? text = (await _fuchsiaSdk.listDevices(
timeout: timeout,
))?.split('\n');
if (text == null || text.isEmpty) {
return <Device>[];
}
return <FuchsiaDevice>[
for (final String line in text)
if (await _parseDevice(line) case final FuchsiaDevice device) device,
];
}
@override
Future<List<String>> getDiagnostics() async => const <String>[];
Future<FuchsiaDevice?> _parseDevice(String text) async {
final String line = text.trim();
// ['ip', 'device name']
final List<String> words = line.split(' ');
if (words.length < 2) {
return null;
}
final String name = words[1];
// TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object.
final String? resolvedHost = await _fuchsiaSdk.fuchsiaFfx.resolve(name);
if (resolvedHost == null) {
_logger.printError('Failed to resolve host for Fuchsia device `$name`');
return null;
}
return FuchsiaDevice(resolvedHost, name: name, logger: _logger);
}
@override
List<String> get wellKnownIds => const <String>[];
}
class FuchsiaDevice extends Device {
FuchsiaDevice(super.id, {required this.name, required super.logger})
: super(
platformType: PlatformType.fuchsia,
category: null,
ephemeral: true,
);
@override
bool get supportsHotReload => true;
@override
bool get supportsHotRestart => false;
@override
bool get supportsFlutterExit => false;
@override
final String name;
@override
Future<bool> get isLocalEmulator async => false;
@override
Future<String?> get emulatorId async => null;
@override
bool get supportsStartPaused => false;
late final Future<bool> isSession = _initIsSession();
/// Determine if the Fuchsia device is running a session based build.
///
/// If the device is running a session based build, `ffx session` should be
/// used to launch apps. Fuchsia flutter apps cannot currently be launched
/// without a session.
Future<bool> _initIsSession() async {
return await globals.fuchsiaSdk?.fuchsiaFfx.sessionShow() != null;
}
@override
Future<bool> isAppInstalled(
ApplicationPackage app, {
String? userIdentifier,
}) async => false;
@override
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
@override
Future<bool> installApp(
ApplicationPackage app, {
String? userIdentifier,
}) => Future<bool>.value(false);
@override
Future<bool> uninstallApp(
ApplicationPackage app, {
String? userIdentifier,
}) async => false;
@override
bool isSupported() => true;
@override
bool supportsRuntimeMode(BuildMode buildMode) =>
buildMode != BuildMode.jitRelease;
@override
Future<LaunchResult> startApp(
FuchsiaApp package, {
String? mainPath,
String? route,
required DebuggingOptions debuggingOptions,
Map<String, Object?> platformArgs = const <String, Object?>{},
bool prebuiltApplication = 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) {
throwToolExit(
'This tool does not currently build apps for fuchsia.\n'
'Build the app using a supported Fuchsia workflow.\n'
'Then use the --${FlutterOptions.kUseApplicationBinary} flag.'
);
}
// Stop the app if it's currently running.
await stopApp(package);
// Find out who the device thinks we are.
final int port = await globals.os.findFreePort();
if (port == 0) {
globals.printError('Failed to find a free port');
return LaunchResult.failed();
}
// Try Start with a fresh package repo in case one was left over from a
// previous run.
final Directory packageRepo = globals.fs.directory(
globals.fs.path.join(getFuchsiaBuildDirectory(), '.pkg-repo'));
try {
if (packageRepo.existsSync()) {
packageRepo.deleteSync(recursive: true);
}
packageRepo.createSync(recursive: true);
} on Exception catch (e) {
globals.printError('Failed to create Fuchsia package repo directory '
'at ${packageRepo.path}: $e');
return LaunchResult.failed();
}
final String appName = FlutterProject.current().manifest.appName;
final Status status = globals.logger.startProgress(
'Starting Fuchsia application $appName...',
);
FuchsiaPackageServer? fuchsiaPackageServer;
bool serverRegistered = false;
String fuchsiaUrl;
try {
// Start up a package server.
const String packageServerName = FuchsiaPackageServer.toolHost;
fuchsiaPackageServer =
FuchsiaPackageServer(packageRepo.path, packageServerName, '', port);
if (!await fuchsiaPackageServer.start()) {
globals.printError('Failed to start the Fuchsia package server');
return LaunchResult.failed();
}
// Serve the application's package.
final File farArchive =
package.farArchive(debuggingOptions.buildInfo.mode);
if (!await fuchsiaPackageServer.addPackage(farArchive)) {
globals.printError('Failed to add package to the package server');
return LaunchResult.failed();
}
// Serve the flutter_runner.
final File flutterRunnerArchive =
globals.fs.file(globals.artifacts!.getArtifactPath(
Artifact.fuchsiaFlutterRunner,
platform: await targetPlatform,
mode: debuggingOptions.buildInfo.mode,
));
if (!await fuchsiaPackageServer.addPackage(flutterRunnerArchive)) {
globals.printError(
'Failed to add flutter_runner package to the package server');
return LaunchResult.failed();
}
// Teach the package controller about the package server.
if (!await fuchsiaDeviceTools.pkgctl
.addRepo(this, fuchsiaPackageServer)) {
globals.printError('Failed to teach amber about the package server');
return LaunchResult.failed();
}
serverRegistered = true;
// Tell the package controller to prefetch the flutter_runner.
String flutterRunnerName;
if (debuggingOptions.buildInfo.usesAot) {
if (debuggingOptions.buildInfo.mode.isRelease) {
flutterRunnerName = 'flutter_aot_product_runner';
} else {
flutterRunnerName = 'flutter_aot_runner';
}
} else {
if (debuggingOptions.buildInfo.mode.isRelease) {
flutterRunnerName = 'flutter_jit_product_runner';
} else {
flutterRunnerName = 'flutter_jit_runner';
}
}
if (!await fuchsiaDeviceTools.pkgctl
.resolve(this, fuchsiaPackageServer.name, flutterRunnerName)) {
globals
.printError('Failed to get pkgctl to prefetch the flutter_runner');
return LaunchResult.failed();
}
// Tell the package controller to prefetch the app.
if (!await fuchsiaDeviceTools.pkgctl
.resolve(this, fuchsiaPackageServer.name, appName)) {
globals.printError('Failed to get pkgctl to prefetch the package');
return LaunchResult.failed();
}
fuchsiaUrl = 'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cm';
if (await isSession) {
// Instruct ffx session to start the app
final bool addedApp =
await globals.fuchsiaSdk?.fuchsiaFfx.sessionAdd(fuchsiaUrl) ?? false;
if (!addedApp) {
globals.printError('Failed to add the app via `ffx session add`');
return LaunchResult.failed();
}
} else {
globals.printError(
'Fuchsia flutter apps can only be launched within a session');
return LaunchResult.failed();
}
} finally {
// Try to un-teach the package controller about the package server if
// needed.
if (serverRegistered && fuchsiaPackageServer != null) {
await fuchsiaDeviceTools.pkgctl.rmRepo(this, fuchsiaPackageServer);
}
// Shutdown the package server and delete the package repo;
globals.printTrace("Shutting down the tool's package server.");
fuchsiaPackageServer?.stop();
globals.printTrace(
"Removing the tool's package repo: at ${packageRepo.path}");
try {
packageRepo.deleteSync(recursive: true);
} on Exception catch (e) {
globals.printError('Failed to remove Fuchsia package repo directory '
'at ${packageRepo.path}: $e.');
}
status.cancel();
}
if (debuggingOptions.buildInfo.mode.isRelease) {
globals.printTrace('App successfully started in a release mode.');
return LaunchResult.succeeded();
}
globals.printTrace(
'App started in a non-release mode. Setting up vmservice connection.');
// In a debug or profile build, try to find the vmService uri.
final FuchsiaIsolateDiscoveryProtocol discovery =
getIsolateDiscoveryProtocol(appName);
try {
final Uri vmServiceUri = await discovery.uri;
return LaunchResult.succeeded(vmServiceUri: vmServiceUri);
} finally {
discovery.dispose();
}
}
@override
Future<bool> stopApp(
ApplicationPackage? 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;
}
// Fuchsia flutter apps currently require a session, but if that changes,
// add the relevant "stopApp" code here.
return true;
}
Future<TargetPlatform> _queryTargetPlatform() async {
const TargetPlatform defaultTargetPlatform = TargetPlatform.fuchsia_arm64;
if (!globals.fuchsiaArtifacts!.hasSshConfig) {
globals.printTrace('Could not determine Fuchsia target platform because '
'Fuchsia ssh configuration is missing.\n'
'Defaulting to arm64.');
return defaultTargetPlatform;
}
final RunResult result = await shell('uname -m');
if (result.exitCode != 0) {
globals.printError(
'Could not determine Fuchsia target platform type:\n$result\n'
'Defaulting to arm64.');
return defaultTargetPlatform;
}
final String machine = result.stdout.trim();
switch (machine) {
case 'aarch64':
return TargetPlatform.fuchsia_arm64;
case 'x86_64':
return TargetPlatform.fuchsia_x64;
default:
globals.printError('Unknown Fuchsia target platform "$machine". '
'Defaulting to arm64.');
return defaultTargetPlatform;
}
}
@override
bool get supportsScreenshot => isFuchsiaSupportedPlatform(globals.platform);
@override
Future<void> takeScreenshot(File outputFile) async {
if (outputFile.basename.split('.').last != 'ppm') {
throw Exception('${outputFile.path} must be a .ppm file');
}
final RunResult screencapResult =
await shell('screencap > /tmp/screenshot.ppm');
if (screencapResult.exitCode != 0) {
throw Exception(
'Could not take a screenshot on device $name:\n$screencapResult');
}
try {
final RunResult scpResult =
await scp('/tmp/screenshot.ppm', outputFile.path);
if (scpResult.exitCode != 0) {
throw Exception('Failed to copy screenshot from device:\n$scpResult');
}
} finally {
try {
final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm');
if (deleteResult.exitCode != 0) {
globals.printError(
'Failed to delete screenshot.ppm from the device:\n$deleteResult');
}
} on Exception catch (e) {
globals
.printError('Failed to delete screenshot.ppm from the device: $e');
}
}
}
@override
late final Future<TargetPlatform> targetPlatform = _queryTargetPlatform();
@override
Future<String> get sdkNameAndVersion async {
const String defaultName = 'Fuchsia';
if (!globals.fuchsiaArtifacts!.hasSshConfig) {
globals.printTrace('Could not determine Fuchsia sdk name or version '
'because Fuchsia ssh configuration is missing.');
return defaultName;
}
const String versionPath = '/pkgfs/packages/build-info/0/data/version';
final RunResult catResult = await shell('cat $versionPath');
if (catResult.exitCode != 0) {
globals.printTrace('Failed to cat $versionPath: ${catResult.stderr}');
return defaultName;
}
final String version = catResult.stdout.trim();
if (version.isEmpty) {
globals.printTrace('$versionPath was empty');
return defaultName;
}
return 'Fuchsia $version';
}
@override
DeviceLogReader getLogReader({
ApplicationPackage? app,
bool includePastLogs = false,
}) {
assert(!includePastLogs, 'Past log reading not supported on Fuchsia.');
return _logReader ??= _FuchsiaLogReader(this, globals.systemClock, app);
}
_FuchsiaLogReader? _logReader;
@override
DevicePortForwarder get portForwarder =>
_portForwarder ??= _FuchsiaPortForwarder(this);
DevicePortForwarder? _portForwarder;
@visibleForTesting
set portForwarder(DevicePortForwarder forwarder) {
_portForwarder = forwarder;
}
@override
void clearLogs() {}
@override
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
String? appId,
String? fuchsiaModule,
int? filterDevicePort,
int? expectedHostPort,
required bool ipv6,
required Logger logger,
}) {
if (fuchsiaModule == null) {
throwToolExit("'--module' is required for attaching to a Fuchsia device");
}
if (expectedHostPort != null) {
throwToolExit("'--host-vmservice-port' is not supported when attaching to a Fuchsia device");
}
return FuchsiaIsolateVMServiceDiscoveryForAttach(getIsolateDiscoveryProtocol(fuchsiaModule));
}
/// [true] if the current host address is IPv6.
late final bool ipv6 = isIPv6Address(id);
/// Return the address that the device should use to communicate with the
/// host.
late final Future<String> hostAddress = () async {
final RunResult result = await shell(r'echo $SSH_CONNECTION');
void fail() {
throwToolExit('Failed to get local address, aborting.\n$result');
}
if (result.exitCode != 0) {
fail();
}
final List<String> splitResult = result.stdout.split(' ');
if (splitResult.isEmpty) {
fail();
}
final String addr = splitResult[0].replaceAll('%', '%25');
if (addr.isEmpty) {
fail();
}
return addr;
}();
/// List the ports currently running a dart vmService.
Future<List<int>> servicePorts() async {
const String findCommand = 'find /hub -name vmservice-port';
final RunResult findResult = await shell(findCommand);
if (findResult.exitCode != 0) {
throwToolExit(
"'$findCommand' on device $name failed. stderr: '${findResult.stderr}'");
}
final String findOutput = findResult.stdout;
if (findOutput.trim() == '') {
throwToolExit(
'No Dart Observatories found. Are you running a debug build?');
}
final List<int> ports = <int>[];
for (final String path in findOutput.split('\n')) {
if (path == '') {
continue;
}
final String lsCommand = 'ls $path';
final RunResult lsResult = await shell(lsCommand);
if (lsResult.exitCode != 0) {
throwToolExit("'$lsCommand' on device $name failed");
}
final String lsOutput = lsResult.stdout;
for (final String line in lsOutput.split('\n')) {
if (line == '') {
continue;
}
final int? port = int.tryParse(line);
if (port != null) {
ports.add(port);
}
}
}
return ports;
}
/// Run `command` on the Fuchsia device shell.
Future<RunResult> shell(String command) async {
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
}
return globals.processUtils.run(<String>[
'ssh',
'-F',
sshConfig.absolute.path,
id, // Device's IP address.
command,
]);
}
/// Transfer the file [origin] from the device to [destination].
Future<RunResult> scp(String origin, String destination) async {
final File? sshConfig = globals.fuchsiaArtifacts!.sshConfig;
if (sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
}
return globals.processUtils.run(<String>[
'scp',
'-F',
sshConfig.absolute.path,
'$id:$origin',
destination,
]);
}
/// Finds the first port running a VM matching `isolateName` from the
/// provided set of `ports`.
///
/// Returns null if no isolate port can be found.
Future<int> findIsolatePort(String isolateName, List<int> ports) async {
for (final int port in ports) {
try {
// The square-bracket enclosure for using the IPv6 loopback
// didn't appear to work, but when assigning to the IPv4 loopback device,
// netstat shows that the local port is actually being used on the IPv6
// loopback (::1).
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port');
final FlutterVmService vmService =
await connectToVmService(uri, logger: globals.logger);
final List<FlutterView> flutterViews =
await vmService.getFlutterViews();
for (final FlutterView flutterView in flutterViews) {
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
if (uiIsolate == null) {
continue;
}
final int? port = vmService.httpAddress?.port;
if (port != null &&
(uiIsolate.name?.contains(isolateName) ?? false)) {
return port;
}
}
} on SocketException catch (err) {
globals.printTrace('Failed to connect to $port: $err');
}
}
throwToolExit('No ports found running $isolateName');
}
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
String isolateName) {
return FuchsiaIsolateDiscoveryProtocol(this, isolateName);
}
@override
bool isSupportedForProject(FlutterProject flutterProject) {
return flutterProject.fuchsia.existsSync();
}
@override
Future<void> dispose() async {
await _portForwarder?.dispose();
}
}
class FuchsiaIsolateVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
FuchsiaIsolateVMServiceDiscoveryForAttach(this.isolateDiscoveryProtocol);
final FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
@override
Stream<Uri> get uris {
final Future<Uri> uriFuture = (() async {
// Wrapping the call in an anonymous async function for easier error handling.
try {
return await isolateDiscoveryProtocol.uri;
} on Exception {
final FuchsiaDevice device = isolateDiscoveryProtocol._device;
isolateDiscoveryProtocol.dispose();
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
for (final ForwardedPort port in ports) {
await device.portForwarder.unforward(port);
}
rethrow;
}
})();
return Stream<Uri>.fromFuture(uriFuture).asBroadcastStream();
}
}
class FuchsiaIsolateDiscoveryProtocol {
FuchsiaIsolateDiscoveryProtocol(
this._device,
this._isolateName, [
this._vmServiceConnector = _kDefaultFuchsiaIsolateDiscoveryConnector,
this._ddsStarter = _kDefaultDartDevelopmentServiceStarter,
this._pollOnce = false,
]);
static const Duration _pollDuration = Duration(seconds: 10);
final Map<int, FlutterVmService> _ports = <int, FlutterVmService>{};
final FuchsiaDevice _device;
final String _isolateName;
final Completer<Uri> _foundUri = Completer<Uri>();
final Future<FlutterVmService> Function(Uri) _vmServiceConnector;
final Future<void> Function(Device, Uri, bool) _ddsStarter;
// whether to only poll once.
final bool _pollOnce;
Timer? _pollingTimer;
Status? _status;
FutureOr<Uri> get uri {
if (_uri != null) {
return _uri!;
}
_status ??= globals.logger.startProgress(
'Waiting for a connection from $_isolateName on ${_device.name}...',
);
unawaited(_findIsolate()); // Completes the _foundUri Future.
return _foundUri.future.then((Uri uri) {
_uri = uri;
return uri;
});
}
Uri? _uri;
void dispose() {
if (!_foundUri.isCompleted) {
_status?.cancel();
_status = null;
_pollingTimer?.cancel();
_pollingTimer = null;
_foundUri.completeError(Exception('Did not complete'));
}
}
Future<void> _findIsolate() async {
final List<int> ports = await _device.servicePorts();
for (final int port in ports) {
FlutterVmService? service;
if (_ports.containsKey(port)) {
service = _ports[port];
} else {
final int localPort = await _device.portForwarder.forward(port);
try {
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$localPort');
await _ddsStarter(_device, uri, true);
service = await _vmServiceConnector(_device.dds.uri!);
_ports[port] = service;
} on SocketException catch (err) {
globals.printTrace('Failed to connect to $localPort: $err');
continue;
}
}
final List<FlutterView> flutterViews =
await service?.getFlutterViews() ?? <FlutterView>[];
for (final FlutterView flutterView in flutterViews) {
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
if (uiIsolate == null) {
continue;
}
final int? port = service?.httpAddress?.port;
if (port != null && (uiIsolate.name?.contains(_isolateName) ?? false)) {
_foundUri.complete(_device.ipv6
? Uri.parse('http://[$_ipv6Loopback]:$port/')
: Uri.parse('http://$_ipv4Loopback:$port/'));
_status?.stop();
return;
}
}
}
if (_pollOnce) {
_foundUri.completeError(Exception('Max iterations exceeded'));
_status?.stop();
return;
}
_pollingTimer = Timer(_pollDuration, _findIsolate);
}
}
class _FuchsiaPortForwarder extends DevicePortForwarder {
_FuchsiaPortForwarder(this.device);
final FuchsiaDevice device;
final Map<int, Process> _processes = <int, Process>{};
@override
Future<int> forward(int devicePort, {int? hostPort}) async {
hostPort ??= await globals.os.findFreePort();
if (hostPort == 0) {
throwToolExit(
'Failed to forward port $devicePort. No free host-side ports');
}
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null) {
throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
}
// The provided command works around a bug in -N, see US-515
// for more explanation.
final List<String> command = <String>[
'ssh',
'-6',
'-F',
sshConfig.absolute.path,
'-nNT',
'-vvv',
'-f',
'-L',
'$hostPort:$_ipv4Loopback:$devicePort',
device.id, // Device's IP address.
'true',
];
final Process process = await globals.processManager.start(command);
unawaited(process.exitCode.then((int exitCode) {
if (exitCode != 0) {
throwToolExit('Failed to forward port:$devicePort');
}
}));
_processes[hostPort] = process;
_forwardedPorts.add(ForwardedPort(hostPort, devicePort));
return hostPort;
}
@override
List<ForwardedPort> get forwardedPorts => _forwardedPorts;
final List<ForwardedPort> _forwardedPorts = <ForwardedPort>[];
@override
Future<void> unforward(ForwardedPort forwardedPort) async {
_forwardedPorts.remove(forwardedPort);
final Process? process = _processes.remove(forwardedPort.hostPort);
process?.kill();
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null) {
// Nothing to cancel.
return;
}
final List<String> command = <String>[
'ssh',
'-F',
sshConfig.absolute.path,
'-O',
'cancel',
'-vvv',
'-L',
'${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}',
device.id, // Device's IP address.
];
final ProcessResult result = await globals.processManager.run(command);
if (result.exitCode != 0) {
throwToolExit(
'Unforward command failed:\n'
'stdout: ${result.stdout}\n'
'stderr: ${result.stderr}',
);
}
}
@override
Future<void> dispose() async {
final List<ForwardedPort> forwardedPortsCopy =
List<ForwardedPort>.of(forwardedPorts);
for (final ForwardedPort port in forwardedPortsCopy) {
await unforward(port);
}
}
}

View File

@ -1,150 +0,0 @@
// 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.
import 'package:file/file.dart';
import 'package:process/process.dart';
import '../base/common.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../globals.dart' as globals;
import 'fuchsia_sdk.dart';
// Usage: ffx [-c <config>] [-e <env>] [-t <target>] [-T <timeout>] [-v] [<command>] [<args>]
// Fuchsia's developer tool
// Options:
// -c, --config override default configuration
// -e, --env override default environment settings
// -t, --target apply operations across single or multiple targets
// -T, --timeout override default proxy timeout
// -v, --verbose use verbose output
// --help display usage information
// Commands:
// config View and switch default and user configurations
// daemon Interact with/control the ffx daemon
// target Interact with a target device or emulator
// session Control the current session. See
// https://fuchsia.dev/fuchsia-src/concepts/session/introduction
// for details.
/// A simple wrapper for the Fuchsia SDK's 'ffx' tool.
class FuchsiaFfx {
FuchsiaFfx({
FuchsiaArtifacts? fuchsiaArtifacts,
Logger? logger,
ProcessManager? processManager,
}) : _fuchsiaArtifacts = fuchsiaArtifacts ?? globals.fuchsiaArtifacts,
_logger = logger ?? globals.logger,
_processUtils = ProcessUtils(
logger: logger ?? globals.logger,
processManager: processManager ?? globals.processManager);
final FuchsiaArtifacts? _fuchsiaArtifacts;
final Logger _logger;
final ProcessUtils _processUtils;
/// Returns a list of attached devices as a list of strings with entries
/// formatted as follows:
///
/// abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy
Future<List<String>?> list({Duration? timeout}) async {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
if (timeout != null) ...<String>['-T', '${timeout.inSeconds}'],
'target',
'list',
// TODO(akbiggs): Revert -f back to --format once we've verified that
// analytics spam is coming from here.
'-f',
's',
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return null;
}
if (result.stderr.contains('No devices found')) {
return null;
}
return result.stdout.split('\n');
}
/// Returns the address of the named device.
///
/// 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 {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
'target',
'list',
'-f',
'a',
deviceName,
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return null;
}
return result.stdout.trim();
}
/// Show information about the current session
///
/// Returns `null` if the command failed, which can be interpreted as there is
/// no usable session.
Future<String?> sessionShow() async {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
'session',
'show',
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return null;
}
return result.stdout;
}
/// Add an element to the current session
///
/// [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> sessionAdd(String url) async {
final File? ffx = _fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
throwToolExit('Fuchsia ffx tool not found.');
}
final List<String> command = <String>[
ffx.path,
'session',
'add',
url,
];
final RunResult result = await _processUtils.run(command);
if (result.exitCode != 0) {
_logger.printError('ffx failed: ${result.stderr}');
return false;
}
return true;
}
}

View File

@ -1,136 +0,0 @@
// 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.
import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/logger.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../project.dart';
/// This is a simple wrapper around the custom kernel compiler from the Fuchsia
/// SDK.
class FuchsiaKernelCompiler {
/// Compiles the [fuchsiaProject] with entry point [target] to a collection of
/// .dilp files (consisting of the app split along package: boundaries, but
/// the Flutter tool should make no use of that fact), and a manifest that
/// refers to them.
Future<void> build({
required FuchsiaProject fuchsiaProject,
required String target, // E.g., lib/main.dart
BuildInfo buildInfo = BuildInfo.dummy,
}) async {
// TODO(zanderso): Use filesystem root and scheme information from buildInfo.
const String multiRootScheme = 'main-root';
final String outDir = getFuchsiaBuildDirectory();
final String appName = fuchsiaProject.project.manifest.appName;
final String fsRoot = fuchsiaProject.project.directory.path;
final String relativePackageConfigPath = globals.fs.path.relative(
buildInfo.packageConfigPath,
from: fsRoot,
);
final String manifestPath = globals.fs.path.join(outDir, '$appName.dilpmanifest');
final String? kernelCompiler = globals.artifacts?.getArtifactPath(
Artifact.fuchsiaKernelCompiler,
platform: TargetPlatform.fuchsia_arm64, // This file is not arch-specific.
mode: buildInfo.mode,
);
if (kernelCompiler == null || !globals.fs.isFileSync(kernelCompiler)) {
throwToolExit('Fuchsia kernel compiler not found at "$kernelCompiler"');
}
final String? platformDill = globals.artifacts?.getArtifactPath(
Artifact.platformKernelDill,
platform: TargetPlatform.fuchsia_arm64, // This file is not arch-specific.
mode: buildInfo.mode,
);
if (platformDill == null || !globals.fs.isFileSync(platformDill)) {
throwToolExit('Fuchsia platform file not found at "$platformDill"');
}
List<String> flags = <String>[
'--no-sound-null-safety',
'--target',
'flutter_runner',
'--platform',
platformDill,
'--filesystem-scheme',
'main-root',
'--filesystem-root',
fsRoot,
'--packages',
'$multiRootScheme:///$relativePackageConfigPath',
'--output',
globals.fs.path.join(outDir, '$appName.dil'),
'--component-name',
appName,
...getBuildInfoFlags(buildInfo: buildInfo, manifestPath: manifestPath),
];
flags += <String>[
'$multiRootScheme:///$target',
];
final String? engineDartBinaryPath = globals.artifacts?.getArtifactPath(Artifact.engineDartBinary);
if (engineDartBinaryPath == null) {
throwToolExit('Engine dart binary not found at "$engineDartBinaryPath"');
}
final List<String> command = <String>[
engineDartBinaryPath,
kernelCompiler,
...flags,
];
final Status status = globals.logger.startProgress(
'Building Fuchsia application...',
);
int result;
try {
result = await globals.processUtils.stream(command, trace: true);
} finally {
status.cancel();
}
if (result != 0) {
throwToolExit('Build process failed');
}
}
/// Provide flags that are affected by [BuildInfo]
@visibleForTesting
static List<String> getBuildInfoFlags({
required BuildInfo buildInfo,
required String manifestPath,
}) {
return <String>[
// AOT/JIT:
if (buildInfo.usesAot) ...<String>[
'--aot',
'--tfa',
] else ...<String>[
'--no-link-platform',
'--split-output-by-packages',
'--manifest',
manifestPath,
],
// debug, profile, jit release, release:
if (buildInfo.isDebug)
'--embed-sources'
else
'--no-embed-sources',
if (buildInfo.isProfile) ...<String>[
'-Ddart.vm.profile=true',
'-Ddart.vm.product=false',
],
if (buildInfo.mode.isRelease) ...<String>[
'-Ddart.vm.profile=false',
'-Ddart.vm.product=true',
],
for (final String dartDefine in buildInfo.dartDefines)
'-D$dartDefine',
];
}
}

View File

@ -1,241 +0,0 @@
// 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.
import 'dart:async';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/net.dart';
import '../base/process.dart';
import '../convert.dart';
import '../globals.dart' as globals;
/// 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
/// package for the app named [appName].
///
/// When successful, this creates a file under [buildPath] at `meta/package`.
///
/// 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) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-n',
appName,
'init',
]);
}
/// Updates, signs, and seals a Fuchsia package.
///
/// [buildPath] should be the same [buildPath] passed to [init].
/// [manifestPath] must be a file containing lines formatted as follows:
///
/// data/path/to/file/in/the/package=/path/to/file/on/the/host
///
/// which describe the contents of the Fuchsia package. It must also contain
/// two other entries:
///
/// meta/$APPNAME.cm=/path/to/cm/on/the/host/$APPNAME.cm
/// meta/package=/path/to/package/file/from/init/package
///
/// 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 manifestPath) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-m',
manifestPath,
'build',
]);
}
/// Constructs a .far representation of the Fuchsia package.
///
/// When successful, creates a file `app_name-0.far` under [buildPath], which
/// is the Fuchsia package.
///
/// [buildPath] should be the same path passed to [init], and [manifestPath]
/// should be the same manifest passed to [build].
Future<bool> archive(String buildPath, String manifestPath) {
return _runPMCommand(<String>[
'-o',
buildPath,
'-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 Fuchsia packages.
///
/// The argument [repoPath] should have previously been an argument to
/// [newrepo]. The [host] should be the host reported by
/// [FuchsiaFfx.resolve], and [port] should be an unused port for the
/// http server to bind.
Future<Process> serve(String repoPath, String host, int port) async {
final File? pm = globals.fuchsiaArtifacts?.pm;
if (pm == null) {
throwToolExit('Fuchsia pm tool not found');
}
if (isIPv6Address(host.split('%').first)) {
host = '[$host]';
}
final List<String> command = <String>[
pm.path,
'serve',
'-repo',
repoPath,
'-l',
'$host:$port',
'-c',
'2',
];
final Process process = await globals.processUtils.start(command);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(globals.printTrace);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(globals.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 {
final File? pm = globals.fuchsiaArtifacts?.pm;
if (pm == null) {
throwToolExit('Fuchsia pm tool not found');
}
final List<String> command = <String>[pm.path, ...args];
final RunResult result = await globals.processUtils.run(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
/// server.
///
/// Example usage:
/// var server = FuchsiaPackageServer(
/// '/path/to/repo',
/// 'server_name',
/// await FuchsiaFfx.resolve(deviceName),
/// await freshPort());
/// try {
/// await server.start();
/// await server.addPackage(farArchivePath);
/// ...
/// } finally {
/// server.stop();
/// }
class FuchsiaPackageServer {
FuchsiaPackageServer(String repo, this.name, String host, int port)
: _repo = repo, _host = host, _port = port;
static const String deviceHost = 'fuchsia.com';
static const String toolHost = 'flutter-tool';
final String _repo;
final String _host;
final int _port;
Process? _process;
// The name used to reference the server by fuchsia-pkg:// urls.
final String name;
int get port => _port;
/// Uses [FuchsiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia
/// package server.
///
/// Returns false if the repo could not be created or the server could not
/// be spawned, and true otherwise.
Future<bool> start() async {
if (_process != null) {
globals.printError('$this already started!');
return false;
}
// initialize a new repo.
final FuchsiaPM? fuchsiaPM = globals.fuchsiaSdk?.fuchsiaPM;
if (fuchsiaPM == null || !await fuchsiaPM.newrepo(_repo)) {
globals.printError('Failed to create a new package server repo');
return false;
}
_process = await 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) {
globals.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 globals.fuchsiaSdk?.fuchsiaPM.publish(_repo, package.path)) ??
false;
}
@override
String toString() {
final String p =
(_process == null) ? 'stopped' : 'running ${_process?.pid}';
return 'FuchsiaPackageServer at $_host:$_port ($p)';
}
}

View File

@ -1,150 +0,0 @@
// 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.
import 'dart:async';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import 'fuchsia_ffx.dart';
import 'fuchsia_kernel_compiler.dart';
import 'fuchsia_pm.dart';
/// Returns [true] if the current platform supports Fuchsia targets.
bool isFuchsiaSupportedPlatform(Platform platform) {
return false;
}
/// The Fuchsia SDK shell commands.
///
/// 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.
late final FuchsiaPM fuchsiaPM = FuchsiaPM();
/// Interface to the 'kernel_compiler' tool.
late final FuchsiaKernelCompiler fuchsiaKernelCompiler = FuchsiaKernelCompiler();
/// Interface to the 'ffx' tool.
late final FuchsiaFfx fuchsiaFfx = FuchsiaFfx();
/// Returns any attached devices is a newline-denominated String.
///
/// Example output: abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy
Future<String?> listDevices({Duration? timeout}) async {
final File? ffx = globals.fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
return null;
}
final List<String>? devices = await fuchsiaFfx.list(timeout: timeout);
if (devices == null) {
return null;
}
return devices.isNotEmpty ? devices.join('\n') : null;
}
/// Returns the fuchsia system logs for an attached device where
/// [id] is the IP address of the device.
Stream<String>? syslogs(String id) {
Process? process;
try {
final StreamController<String> controller = StreamController<String>(onCancel: () {
process?.kill();
});
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
if (sshConfig == null || !sshConfig.existsSync()) {
globals.printError('Cannot read device logs: No ssh config.');
globals.printError('Have you set FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR?');
return null;
}
const String remoteCommand = 'log_listener --clock Local';
final List<String> cmd = <String>[
'ssh',
'-F',
sshConfig.absolute.path,
id, // The device's IP.
remoteCommand,
];
globals.processManager.start(cmd).then((Process newProcess) {
if (controller.isClosed) {
return;
}
process = newProcess;
process?.exitCode.whenComplete(controller.close);
controller.addStream(process!.stdout
.transform(utf8.decoder)
.transform(const LineSplitter()));
});
return controller.stream;
} on Exception catch (exception) {
globals.printTrace('$exception');
}
return const Stream<String>.empty();
}
}
/// Fuchsia-specific artifacts used to interact with a device.
class FuchsiaArtifacts {
/// Creates a new [FuchsiaArtifacts].
FuchsiaArtifacts({
this.sshConfig,
this.ffx,
this.pm,
});
/// Creates a new [FuchsiaArtifacts] using the cached Fuchsia SDK.
///
/// Finds tools under bin/cache/artifacts/fuchsia/tools.
/// Queries environment variables (first FUCHSIA_BUILD_DIR, then
/// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
/// a device.
factory FuchsiaArtifacts.find() {
if (!isFuchsiaSupportedPlatform(globals.platform)) {
// Don't try to find the artifacts on platforms that are not supported.
return FuchsiaArtifacts();
}
// 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(zanderso): Consider passing the ssh config path in with a flag.
File? sshConfig;
if (globals.platform.environment.containsKey(_kFuchsiaBuildDir)) {
sshConfig = globals.fs.file(globals.fs.path.join(
globals.platform.environment[_kFuchsiaBuildDir]!, 'ssh-keys', 'ssh_config'));
} else if (globals.platform.environment.containsKey(_kFuchsiaSshConfig)) {
sshConfig = globals.fs.file(globals.platform.environment[_kFuchsiaSshConfig]);
}
final String fuchsia = globals.cache.getArtifactDirectory('fuchsia').path;
final String tools = globals.fs.path.join(fuchsia, 'tools');
final File ffx = globals.fs.file(globals.fs.path.join(tools, 'x64/ffx'));
final File pm = globals.fs.file(globals.fs.path.join(tools, 'pm'));
return FuchsiaArtifacts(
sshConfig: sshConfig,
ffx: ffx.existsSync() ? ffx : null,
pm: pm.existsSync() ? pm : null,
);
}
static const String _kFuchsiaSshConfig = 'FUCHSIA_SSH_CONFIG';
static const String _kFuchsiaBuildDir = 'FUCHSIA_BUILD_DIR';
/// The location of the SSH configuration file used to interact with a
/// Fuchsia device.
final File? sshConfig;
/// The location of the ffx tool used to locate connected
/// Fuchsia devices.
final File? ffx;
/// The pm tool.
final File? pm;
/// Returns true if the [sshConfig] file is not null and exists.
bool get hasSshConfig => sshConfig != null && sshConfig!.existsSync();
}

View File

@ -1,46 +0,0 @@
// 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.
import '../base/context.dart';
import '../base/platform.dart';
import '../doctor_validator.dart';
import '../features.dart';
import 'fuchsia_sdk.dart';
/// The [FuchsiaWorkflow] instance.
FuchsiaWorkflow? get fuchsiaWorkflow => context.get<FuchsiaWorkflow>();
/// The Fuchsia-specific implementation of a [Workflow].
///
/// This workflow assumes development within the fuchsia source tree,
/// including a working fx command-line tool in the user's PATH.
class FuchsiaWorkflow implements Workflow {
FuchsiaWorkflow({
required Platform platform,
required FeatureFlags featureFlags,
required FuchsiaArtifacts fuchsiaArtifacts,
}) : _platform = platform,
_featureFlags = featureFlags,
_fuchsiaArtifacts = fuchsiaArtifacts;
final Platform _platform;
final FeatureFlags _featureFlags;
final FuchsiaArtifacts _fuchsiaArtifacts;
@override
bool get appliesToHostPlatform => _featureFlags.isFuchsiaEnabled && (_platform.isLinux || _platform.isMacOS);
@override
bool get canListDevices {
return _fuchsiaArtifacts.ffx != null;
}
@override
bool get canLaunchDevices {
return _fuchsiaArtifacts.ffx != null && _fuchsiaArtifacts.sshConfig != null;
}
@override
bool get canListEmulators => false;
}

View File

@ -1,44 +0,0 @@
// 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.
import '../base/process.dart';
import 'fuchsia_device.dart';
import 'fuchsia_pm.dart';
/// Simple wrapper for interacting with the 'pkgctl' tool running on the
/// Fuchsia device.
class FuchsiaPkgctl {
/// Teaches pkgctl on [device] about the Fuchsia package server
Future<bool> addRepo(
FuchsiaDevice device, FuchsiaPackageServer server) async {
final String localIp = await device.hostAddress;
final String configUrl = 'http://[$localIp]:${server.port}/config.json';
final RunResult result =
await device.shell('pkgctl repo add url -n ${server.name} $configUrl');
return result.exitCode == 0;
}
/// Instructs pkgctl instance running on [device] to forget about the
/// Fuchsia package server with the given name
/// pkgctl repo rm fuchsia-pkg://mycorp.com
Future<bool> rmRepo(FuchsiaDevice device, FuchsiaPackageServer server) async {
final RunResult result = await device.shell(
'pkgctl repo rm fuchsia-pkg://${server.name}',
);
return result.exitCode == 0;
}
/// Instructs the pkgctl instance running on [device] to prefetch the package
/// with the given [packageUrl] hosted in the given [serverName].
Future<bool> resolve(
FuchsiaDevice device,
String serverName,
String packageName,
) async {
final String packageUrl = 'fuchsia-pkg://$serverName/$packageName';
final RunResult result = await device.shell('pkgctl resolve $packageUrl');
return result.exitCode == 0;
}
}

View File

@ -32,7 +32,6 @@ import 'cache.dart';
import 'custom_devices/custom_devices_config.dart';
import 'device.dart';
import 'doctor.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'ios/ios_workflow.dart';
import 'ios/plist_parser.dart';
import 'ios/simulators.dart';
@ -71,8 +70,6 @@ Signals get signals => context.get<Signals>() ?? LocalSignals.instance;
AndroidStudio? get androidStudio => context.get<AndroidStudio>();
AndroidSdk? get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>()!;
FuchsiaArtifacts? get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
FuchsiaSdk? get fuchsiaSdk => context.get<FuchsiaSdk>();
Usage get flutterUsage => context.get<Usage>()!;
XcodeProjectInterpreter? get xcodeProjectInterpreter => context.get<XcodeProjectInterpreter>();
XCDevice? get xcdevice => context.get<XCDevice>();

View File

@ -21,7 +21,6 @@ import 'package:flutter_tools/src/commands/daemon.dart';
import 'package:flutter_tools/src/daemon.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:flutter_tools/src/preview_device.dart';
@ -498,7 +497,6 @@ void main() {
}, overrides: <Type, Generator>{
AndroidWorkflow: () => FakeAndroidWorkflow(),
IOSWorkflow: () => FakeIOSWorkflow(),
FuchsiaWorkflow: () => FakeFuchsiaWorkflow(),
WindowsWorkflow: () => FakeWindowsWorkflow(),
});
@ -1109,13 +1107,6 @@ class FakeWindowsWorkflow extends Fake implements WindowsWorkflow {
final bool canListDevices;
}
class FakeFuchsiaWorkflow extends Fake implements FuchsiaWorkflow {
FakeFuchsiaWorkflow({ this.canListDevices = true });
@override
final bool canListDevices;
}
class FakeAndroidWorkflow extends Fake implements AndroidWorkflow {
FakeAndroidWorkflow({ this.canListDevices = true });