blob: 82cede97a5809fab745a197d8a14d129b297f7ca [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:fxutils/fxutils.dart';
/// Re-entry helper for running other fx subcommands from within any subcommand
/// written in Dart.
///
/// [Fx] instances are source-tree aware (via [RootLocator]), and launch
/// processes via a helper [startProcess].
///
/// Usage:
/// ```dart
/// final fx = Fx();
///
/// // Run prepared commands
/// final deviceName = await fx.getDeviceName();
///
/// // Run custom, specific commands.
/// // This runs `fx some-command`.
/// final output = await fx.getSubCommandOutput('some-command');
/// ```
class Fx {
final FxEnv? fxEnv;
final ProcessLauncher _processLauncher;
Fx({
required this.fxEnv,
StartProcess? processStarter,
}) : _processLauncher = ProcessLauncher(processStarter: processStarter);
factory Fx.mock(Process process, [FxEnv? fxEnv]) => Fx(
processStarter: returnGivenProcess(process),
fxEnv: fxEnv,
);
Future<String?> getDeviceName() async {
return await getSubCommandOutput('get-device');
}
Future<bool> isPackageServerRunning() async {
try {
await getSubCommandOutput('is-package-server-running');
} on FailedProcessException {
return false;
}
return true;
}
Future<bool> updateIfInBase(
Iterable<String> testNames, {
int batchSize = 50,
}) async {
final testNamesIterator = ListIterator<String>.from(testNames.toList());
while (testNamesIterator.isNotEmpty) {
final result = await runFxSubCommand(
'update-if-in-base',
args: testNamesIterator.take(batchSize),
);
if (result.exitCode != 0) {
return false;
}
}
return true;
}
/// Helper which invokes a command and returns the response.
///
/// Intentionally offers no realtime access to output, or to the stderr. This
/// is meant to simplify the task of getting output from an fx subcommand.
Future<String?> getSubCommandOutput(
/// fx subcommand to execute. For example, "device-name", "status", etc.
String cmd, {
/// Optional list of arguments to pass to the subcommand.
List<String>? args,
/// Accepted exit codes. Unexpected exit codes will throw a
/// [FailedProcessException]. Set this to [null] to never throw an
/// exception.
List<int> allowedExitCodes = const [0],
/// Convenience flag to remove trailing whitespace from the command, since
/// that is unlikely to be desired.
bool shouldTrimTrailing = true,
}) async {
final processResult = await runFxSubCommand(
cmd,
args: args,
);
if (!allowedExitCodes.contains(processResult.exitCode)) {
final fullCommand = [fxEnv!.fx, cmd, ...?args];
throw FailedProcessException(
command: fullCommand,
exitCode: processResult.exitCode,
stdout: processResult.stdout,
stderr: processResult.stderr,
);
}
return shouldTrimTrailing
// ignore: avoid_as
? (processResult.stdout as String).trimRight()
: processResult.stdout;
}
/// Basic wrapper around running an fx subcommand which exposes all necessary
/// controls to the caller.
Future<ProcessResult> runFxSubCommand(
/// Specific `fx` subcommand to execute.
String cmd, {
/// Argument and flags for the subcommand. Optional.
List<String>? args,
/// I/O mode of the spawned process. Defaults to
/// [ProcessStartMode.inheritStdio] if unset and if [stdoutSink] and
/// [stderrSink] are null.
ProcessStartMode mode = ProcessStartMode.normal,
// /// Handler for converting raw bytes into readable strings
SystemEncoding encoding = systemEncoding,
}) async {
return _processLauncher.run(
fxEnv!.fx,
[cmd, ...?args],
mode: mode,
outputEncoding: encoding,
);
}
}