mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

I plan to extend the prepare_package.dart script to upload the flutter preview device ([design doc](https://docs.google.com/document/d/1AzI-_Uk2v1LA2kKKFJ7gVD4xcakXJ6yVZiS5Ek6RHtg/edit#heading=h.byp03plw7mg9)). However, given that that script is one large >1k line file, I decided to organize it into smaller libraries in this PR. There should be no behavioral change in this PR, this is a cleanup only. I made the following changes: 1. Created a //dev/bots/prepare_package/ directory to contain helper libraries 2. Moved everything but the `main()` function in //dev/bots/prepare_package.dart into one of 4 helper libraries under the new directory from step 1: a. archive_creator.dart which contains the code that creates archive directory locally on disk b. archive_publisher.dart which contains the code that uploads the archive to cloud storage c. common.dart for shared constants and definitions d. process_runner.dart for an abstraction over running sub-processes 3. Changed all definitions to `File` and `Directory` from `dart:io` to use the testable versions from `package:file`. This allowed me to use the `MemoryFileSystem` in the unit tests, rather than creating real temp file system directories.
117 lines
3.9 KiB
Dart
117 lines
3.9 KiB
Dart
// 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 'dart:convert';
|
|
import 'dart:io' hide Platform;
|
|
|
|
import 'package:platform/platform.dart' show LocalPlatform, Platform;
|
|
import 'package:process/process.dart';
|
|
|
|
import 'common.dart';
|
|
|
|
/// A helper class for classes that want to run a process.
|
|
///
|
|
/// The stderr and stdout can optionally be reported as the process runs, and
|
|
/// capture the stdout properly without dropping any.
|
|
class ProcessRunner {
|
|
ProcessRunner({
|
|
ProcessManager? processManager,
|
|
this.subprocessOutput = true,
|
|
this.defaultWorkingDirectory,
|
|
this.platform = const LocalPlatform(),
|
|
}) : processManager = processManager ?? const LocalProcessManager() {
|
|
environment = Map<String, String>.from(platform.environment);
|
|
}
|
|
|
|
/// The platform to use for a starting environment.
|
|
final Platform platform;
|
|
|
|
/// Set [subprocessOutput] to show output as processes run. Stdout from the
|
|
/// process will be printed to stdout, and stderr printed to stderr.
|
|
final bool subprocessOutput;
|
|
|
|
/// Set the [processManager] in order to inject a test instance to perform
|
|
/// testing.
|
|
final ProcessManager processManager;
|
|
|
|
/// Sets the default directory used when `workingDirectory` is not specified
|
|
/// to [runProcess].
|
|
final Directory? defaultWorkingDirectory;
|
|
|
|
/// The environment to run processes with.
|
|
late Map<String, String> environment;
|
|
|
|
/// Run the command and arguments in `commandLine` as a sub-process from
|
|
/// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses
|
|
/// [Directory.current] if [defaultWorkingDirectory] is not set.
|
|
///
|
|
/// Set `failOk` if [runProcess] should not throw an exception when the
|
|
/// command completes with a non-zero exit code.
|
|
Future<String> runProcess(
|
|
List<String> commandLine, {
|
|
Directory? workingDirectory,
|
|
bool failOk = false,
|
|
}) async {
|
|
workingDirectory ??= defaultWorkingDirectory ?? Directory.current;
|
|
if (subprocessOutput) {
|
|
stderr.write('Running "${commandLine.join(' ')}" in ${workingDirectory.path}.\n');
|
|
}
|
|
final List<int> output = <int>[];
|
|
final Completer<void> stdoutComplete = Completer<void>();
|
|
final Completer<void> stderrComplete = Completer<void>();
|
|
late Process process;
|
|
Future<int> allComplete() async {
|
|
await stderrComplete.future;
|
|
await stdoutComplete.future;
|
|
return process.exitCode;
|
|
}
|
|
|
|
try {
|
|
process = await processManager.start(
|
|
commandLine,
|
|
workingDirectory: workingDirectory.absolute.path,
|
|
environment: environment,
|
|
);
|
|
process.stdout.listen(
|
|
(List<int> event) {
|
|
output.addAll(event);
|
|
if (subprocessOutput) {
|
|
stdout.add(event);
|
|
}
|
|
},
|
|
onDone: () async => stdoutComplete.complete(),
|
|
);
|
|
if (subprocessOutput) {
|
|
process.stderr.listen(
|
|
(List<int> event) {
|
|
stderr.add(event);
|
|
},
|
|
onDone: () async => stderrComplete.complete(),
|
|
);
|
|
} else {
|
|
stderrComplete.complete();
|
|
}
|
|
} on ProcessException catch (e) {
|
|
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
|
|
'failed with:\n$e';
|
|
throw PreparePackageException(message);
|
|
} on ArgumentError catch (e) {
|
|
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
|
|
'failed with:\n$e';
|
|
throw PreparePackageException(message);
|
|
}
|
|
|
|
final int exitCode = await allComplete();
|
|
if (exitCode != 0 && !failOk) {
|
|
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed';
|
|
throw PreparePackageException(
|
|
message,
|
|
ProcessResult(0, exitCode, null, 'returned $exitCode'),
|
|
);
|
|
}
|
|
return utf8.decoder.convert(output).trim();
|
|
}
|
|
}
|