Fix process running during package creation (#14508)
There were some problems I introduced with the last PR for this. It passed the test, but failed in practice.
This adds tests for those failure cases, adds a "--help" and fixes the test so that it doesn't try and actually download MinGit as part of the Windows test.
I added package:platform as a dependency, so I did a force upgrade on the packages.
Also, re-enabling 'create package' in the cache warming code, now that #14448 is fixed.
diff --git a/dev/bots/prepare_package.dart b/dev/bots/prepare_package.dart
index ad3c7fa..e5f66ee 100644
--- a/dev/bots/prepare_package.dart
+++ b/dev/bots/prepare_package.dart
@@ -13,11 +13,15 @@
import 'package:process/process.dart';
import 'package:platform/platform.dart' show Platform, LocalPlatform;
-const String CHROMIUM_REPO =
+const String chromiumRepo =
'https://chromium.googlesource.com/external/github.com/flutter/flutter';
-const String GITHUB_REPO = 'https://github.com/flutter/flutter.git';
-const String MINGIT_FOR_WINDOWS_URL = 'https://storage.googleapis.com/flutter_infra/mingit/'
+const String githubRepo = 'https://github.com/flutter/flutter.git';
+const String mingitForWindowsUrl = 'https://storage.googleapis.com/flutter_infra/mingit/'
'603511c649b00bbef0a6122a827ac419b656bc19/mingit.zip';
+const String gsBase = 'gs://flutter_infra';
+const String releaseFolder = '/releases';
+const String gsReleaseFolder = '$gsBase$releaseFolder';
+const String baseUrl = 'https://storage.googleapis.com/flutter_infra';
/// Exception class for when a process fails to run, so we can catch
/// it and provide something more readable than a stack trace.
@@ -74,11 +78,11 @@
/// properly without dropping any.
class ProcessRunner {
ProcessRunner({
- this.processManager: const LocalProcessManager(),
+ ProcessManager processManager,
this.subprocessOutput: true,
this.defaultWorkingDirectory,
this.platform: const LocalPlatform(),
- }) {
+ }) : processManager = processManager ?? const LocalProcessManager() {
environment = new Map<String, String>.from(platform.environment);
}
@@ -167,6 +171,8 @@
}
}
+typedef Future<Uint8List> HttpReader(Uri url, {Map<String, String> headers});
+
/// Creates a pre-populated Flutter archive from a git repo.
class ArchiveCreator {
/// [tempDir] is the directory to use for creating the archive. The script
@@ -185,8 +191,10 @@
ProcessManager processManager,
bool subprocessOutput: true,
this.platform: const LocalPlatform(),
+ HttpReader httpReader,
}) : assert(revision.length == 40),
flutterRoot = new Directory(path.join(tempDir.path, 'flutter')),
+ httpReader = httpReader ?? http.readBytes,
_processRunner = new ProcessRunner(
processManager: processManager,
subprocessOutput: subprocessOutput,
@@ -200,15 +208,36 @@
_processRunner.environment['PUB_CACHE'] = path.join(flutterRoot.absolute.path, '.pub-cache');
}
+ /// The platform to use for the environment and determining which
+ /// platform we're running on.
final Platform platform;
+
+ /// The branch to build the archive for. The branch must contain [revision].
final Branch branch;
+
+ /// The git revision hash to build the archive for. This revision has
+ /// to be available in the [branch], although it doesn't have to be
+ /// at HEAD, since we clone the branch and then reset to this revision
+ /// to create the archive.
final String revision;
+
+ /// The flutter root directory in the [tempDir].
final Directory flutterRoot;
+
+ /// The temporary directory used to build the archive in.
final Directory tempDir;
+
+ /// The directory to write the output file to.
final Directory outputDir;
- final Uri _minGitUri = Uri.parse(MINGIT_FOR_WINDOWS_URL);
+
+ final Uri _minGitUri = Uri.parse(mingitForWindowsUrl);
final ProcessRunner _processRunner;
+ /// Used to tell the [ArchiveCreator] which function to use for reading
+ /// bytes from a URL. Used in tests to inject a fake reader. Defaults to
+ /// [http.readBytes].
+ final HttpReader httpReader;
+
File _outputFile;
String _version;
String _flutter;
@@ -255,12 +284,12 @@
// We want the user to start out the in the specified branch instead of a
// detached head. To do that, we need to make sure the branch points at the
// desired revision.
- await _runGit(<String>['clone', '-b', branchName, CHROMIUM_REPO], workingDirectory: tempDir);
+ await _runGit(<String>['clone', '-b', branchName, chromiumRepo], workingDirectory: tempDir);
await _runGit(<String>['reset', '--hard', revision]);
// Make the origin point to github instead of the chromium mirror.
await _runGit(<String>['remote', 'remove', 'origin']);
- await _runGit(<String>['remote', 'add', 'origin', GITHUB_REPO]);
+ await _runGit(<String>['remote', 'add', 'origin', githubRepo]);
}
/// Retrieve the MinGit executable from storage and unpack it.
@@ -268,7 +297,7 @@
if (!platform.isWindows) {
return;
}
- final Uint8List data = await http.readBytes(_minGitUri);
+ final Uint8List data = await httpReader(_minGitUri);
final File gitFile = new File(path.join(tempDir.absolute.path, 'mingit.zip'));
await gitFile.writeAsBytes(data, flush: true);
@@ -289,10 +318,7 @@
// Create each of the templates, since they will call 'pub get' on
// themselves when created, and this will warm the cache with their
// dependencies too.
- // TODO(gspencer): 'package' is broken on dev branch right now!
- // Add it back in once the following is fixed:
- // https://github.com/flutter/flutter/issues/14448
- for (String template in <String>['app', 'plugin']) {
+ for (String template in <String>['app', 'package', 'plugin']) {
final String createName = path.join(tempDir.path, 'create_$template');
await _runFlutter(
<String>['create', '--template=$template', createName],
@@ -381,11 +407,6 @@
subprocessOutput: subprocessOutput,
);
- static String gsBase = 'gs://flutter_infra';
- static String releaseFolder = '/releases';
- static String gsReleaseFolder = '$gsBase$releaseFolder';
- static String baseUrl = 'https://storage.googleapis.com/flutter_infra';
-
final Platform platform;
final String platformName;
final String metadataGsPath;
@@ -498,14 +519,24 @@
argParser.addFlag(
'publish',
defaultsTo: false,
- help: 'The path to the directory where the output archive should be '
- 'written. If --output is not specified, the archive will be written to '
- "the current directory. If the output directory doesn't exist, it, and "
- 'the path to it, will be created.',
+ help: 'If set, will publish the archive to Google Cloud Storage upon '
+ 'successful creation of the archive. Will publish under this '
+ 'directory: $baseUrl$releaseFolder',
+ );
+ argParser.addFlag(
+ 'help',
+ defaultsTo: false,
+ negatable: false,
+ help: 'Print help for this command.',
);
final ArgResults args = argParser.parse(argList);
+ if (args['help']) {
+ print(argParser.usage);
+ exit(0);
+ }
+
void errorExit(String message, {int exitCode = -1}) {
stderr.write('Error: $message\n\n');
stderr.write('${argParser.usage}\n');
diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml
index 18cd046..6f9b64b 100644
--- a/dev/bots/pubspec.yaml
+++ b/dev/bots/pubspec.yaml
@@ -3,8 +3,9 @@
dependencies:
path: 1.5.1
- args: 1.2.0
+ args: 1.3.0
process: 2.0.7
+ platform: 2.1.1
dev_dependencies:
test: 0.12.30+1
@@ -26,7 +27,7 @@
http_multi_server: 2.0.4 # TRANSITIVE DEPENDENCY
http_parser: 3.1.1 # TRANSITIVE DEPENDENCY
intl: 0.15.2 # TRANSITIVE DEPENDENCY
- io: 0.3.1 # TRANSITIVE DEPENDENCY
+ io: 0.3.2+1 # TRANSITIVE DEPENDENCY
isolate: 1.1.0 # TRANSITIVE DEPENDENCY
js: 0.6.1 # TRANSITIVE DEPENDENCY
logging: 0.11.3+1 # TRANSITIVE DEPENDENCY
@@ -37,7 +38,6 @@
node_preamble: 1.4.0 # TRANSITIVE DEPENDENCY
package_config: 1.0.3 # TRANSITIVE DEPENDENCY
package_resolver: 1.0.2 # TRANSITIVE DEPENDENCY
- platform: 2.1.1 # TRANSITIVE DEPENDENCY
plugin: 0.2.0+2 # TRANSITIVE DEPENDENCY
pool: 1.3.4 # TRANSITIVE DEPENDENCY
pub_semver: 1.3.2 # TRANSITIVE DEPENDENCY
diff --git a/dev/bots/test/prepare_package_test.dart b/dev/bots/test/prepare_package_test.dart
index c517ae6..320feee 100644
--- a/dev/bots/test/prepare_package_test.dart
+++ b/dev/bots/test/prepare_package_test.dart
@@ -2,8 +2,10 @@
// 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 'dart:typed_data';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
@@ -20,6 +22,39 @@
operatingSystem: platformName,
environment: <String, String>{},
);
+ group('ProcessRunner for $platform', () {
+ test('Defaults to local process manager, can actually run a command', () async {
+ final ProcessRunner processRunner =
+ new ProcessRunner(subprocessOutput: false, platform: platform);
+ // We want to test that we can actually run a process and obtain stdout.
+ // The command 'echo test' works on all platforms.
+ final String output = await processRunner.runProcess(<String>['echo', 'test']);
+ expect(output, equals('test'));
+ });
+ test('Returns stdout', () async {
+ final FakeProcessManager fakeProcessManager = new FakeProcessManager();
+ fakeProcessManager.fakeResults = <String, List<ProcessResult>>{
+ 'echo test': <ProcessResult>[new ProcessResult(0, 0, 'output', 'error')],
+ };
+ final ProcessRunner processRunner = new ProcessRunner(
+ subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
+ final String output = await processRunner.runProcess(<String>['echo', 'test']);
+ expect(output, equals('output'));
+ });
+ test('Throws on process failure', () async {
+ final FakeProcessManager fakeProcessManager = new FakeProcessManager();
+ fakeProcessManager.fakeResults = <String, List<ProcessResult>>{
+ 'echo test': <ProcessResult>[new ProcessResult(0, -1, 'output', 'error')],
+ };
+ final ProcessRunner processRunner = new ProcessRunner(
+ subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
+ expect(
+ expectAsync1((List<String> commandLine) async {
+ return processRunner.runProcess(commandLine);
+ })(<String>['echo', 'test']),
+ throwsA(const isInstanceOf<ProcessRunnerException>()));
+ });
+ });
group('ArchiveCreator for $platformName', () {
ArchiveCreator creator;
Directory tmpDir;
@@ -29,6 +64,10 @@
final List<Map<Symbol, dynamic>> namedArgs = <Map<Symbol, dynamic>>[];
String flutter;
+ Future<Uint8List> fakeHttpReader(Uri url, {Map<String, String> headers}) {
+ return new Future<Uint8List>.value(new Uint8List(0));
+ }
+
setUp(() async {
processManager = new FakeProcessManager();
args.clear();
@@ -44,6 +83,7 @@
processManager: processManager,
subprocessOutput: false,
platform: platform,
+ httpReader: fakeHttpReader,
);
flutter = path.join(creator.flutterRoot.absolute.path, 'bin', 'flutter');
});
@@ -119,9 +159,7 @@
'$flutter precache': null,
'$flutter ide-config': null,
'$flutter create --template=app ${createBase}app': null,
- // TODO(gspencer): Re-enable this when package works again:
- // https://github.com/flutter/flutter/issues/14448
- // '$flutter create --template=package ${createBase}package': null,
+ '$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'git clean -f -X **/.packages': null,
});
@@ -141,6 +179,7 @@
processManager: processManager,
subprocessOutput: false,
platform: platform,
+ httpReader: fakeHttpReader,
);
await creator.initializeRepo();
await creator.createArchive();