blob: 22b6c181debaca5a3e94f323bafc98d89a36c2f2 [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';
import 'package:meta/meta.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);
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 != null &&
!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,
);
}
}