blob: cab7fd1cfe9556b2a546cd052f3f01a8ed230b6b [file] [log] [blame]
// Copyright 2020 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:io';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:process/process.dart';
import 'dart:convert' show utf8;
const String kDeviceAccessCheckKey = 'device_access';
const String kAttachedDeviceHealthcheckKey = 'attached_device';
const String kAttachedDeviceHealthcheckValue = 'No device is available';
const String kAdbPowerServiceCheckKey = 'adb_power_service';
const String kDeveloperModeCheckKey = 'developer_mode';
const String kScreenOnCheckKey = 'screen_on';
const String kKillAdbServerCheckKey = 'kill_adb_server';
const String kKeychainUnlockCheckKey = 'keychain_unlock';
const String kDeviceProvisioningProfileCheckKey = 'device_provisioning_profile';
const String kUserAutoLoginCheckKey = 'swarming_user_auto_login';
const String kUnlockLoginKeychain = '/usr/local/bin/';
const String kCertCheckKey = 'codesigning_cert';
const String kDevicePairCheckKey = 'device_pair';
const String kScreenSaverCheckKey = 'screensaver';
const String kScreenRotationCheckKey = 'screen_rotation';
const String kBatteryLevelCheckKey = 'battery_level';
const String kBatteryTemperatureCheckKey = 'battery_temperature';
const String kM1BrewBinPath = '/opt/homebrew/bin';
void fail(String message) {
throw BuildFailedError(message);
class BuildFailedError extends Error {
final String message;
String toString() => message;
/// Creates a directory from the given path, or multiple path parts by joining
/// them using OS-specific file path separator.
Directory dir(String thePath,
[String? part2, String? part3, String? part4, String? part5, String? part6, String? part7, String? part8]) {
return Directory(path.join(thePath, part2, part3, part4, part5, part6, part7, part8));
Future<dynamic> inDirectory(dynamic directory, Future<dynamic> action()) async {
String previousCwd = path.current;
try {
return await action();
} finally {
void cd(dynamic directory) {
Directory d;
if (directory is String) {
d = dir(directory);
} else if (directory is Directory) {
d = directory;
} else {
throw 'Unsupported type ${directory.runtimeType} of $directory';
if (!d.existsSync()) throw 'Cannot cd into directory that does not exist: $directory';
/// Starts a process for an executable command, and returns the processes.
Future<Process> startProcess(String executable, List<String> arguments,
{Map<String, String>? env,
bool silent = false,
ProcessManager? processManager = const LocalProcessManager()}) async {
late Process proc;
try {
proc = await processManager!
.start(<String>[executable]..addAll(arguments), environment: env, workingDirectory: path.current);
} catch (error) {
return proc;
/// Executes a command and returns its standard output as a String.
/// Standard error is redirected to the current process' standard error stream.
Future<String> eval(String executable, List<String> arguments,
{Map<String, String>? env,
bool canFail = false,
bool silent = false,
ProcessManager? processManager = const LocalProcessManager()}) async {
Process proc = await startProcess(executable, arguments, env: env, silent: silent, processManager: processManager);
proc.stderr.listen((List<int> data) {
String output = await utf8.decodeStream(proc.stdout);
int exitCode = await proc.exitCode;
if (exitCode != 0 && !canFail) fail('Executable $executable failed with exit code $exitCode.');
return output.trimRight();
/// Splits [from] into lines and selects those that contain [pattern].
Iterable<String> grep(Pattern pattern, {@required String? from}) {
return from!.split('\n').where((String line) {
return line.contains(pattern);
/// Write [results] to [filePath].
void writeToFile(String results, File file) {
if (file.existsSync()) {
try {
} on FileSystemException catch (error) {
print('Failed to delete ${file.path}: $error');
/// Return Mac binary path.
/// For M1 bots, binaries like `ideviceinstaller` are installed under `kM1BrewBinPath`,
/// where they are not visible in `$PATH` by default.
Future<String> getMacBinaryPath(
String name, {
ProcessManager processManager = const LocalProcessManager(),
}) async {
String path = await eval('which', <String>[name], canFail: true, processManager: processManager);
if (path.isEmpty) {
path = await eval('which', <String>['$kM1BrewBinPath/$name'], canFail: true, processManager: processManager);
// Throws exception when the binary doesn't exist in either location.
if (path.isEmpty) {
fail('$name not found.');
return path;