[flutter_plugin_tools] Complete migration to NNBD (#4048)

This commit is contained in:
stuartmorgan
2021-06-12 12:44:04 -07:00
committed by GitHub
parent 038c1796b0
commit ce948ce3cb
5 changed files with 109 additions and 104 deletions

View File

@ -5,6 +5,7 @@
compatibility. compatibility.
- `xctest` now supports running macOS tests in addition to iOS - `xctest` now supports running macOS tests in addition to iOS
- **Breaking change**: it now requires an `--ios` and/or `--macos` flag. - **Breaking change**: it now requires an `--ios` and/or `--macos` flag.
- The tooling now runs in strong null-safe mode.
## 0.2.0 ## 0.2.0

View File

@ -2,6 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart=2.9
export 'package:flutter_plugin_tools/src/main.dart'; export 'package:flutter_plugin_tools/src/main.dart';

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart=2.9
import 'dart:io' as io; import 'dart:io' as io;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart=2.9
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io; import 'dart:io' as io;
@ -18,6 +16,17 @@ import 'package:yaml/yaml.dart';
import 'common.dart'; import 'common.dart';
@immutable
class _RemoteInfo {
const _RemoteInfo({required this.name, required this.url});
/// The git name for the remote.
final String name;
/// The remote's URL.
final String url;
}
/// Wraps pub publish with a few niceties used by the flutter/plugin team. /// Wraps pub publish with a few niceties used by the flutter/plugin team.
/// ///
/// 1. Checks for any modified files in git and refuses to publish if there's an /// 1. Checks for any modified files in git and refuses to publish if there's an
@ -35,8 +44,8 @@ class PublishPluginCommand extends PluginCommand {
Directory packagesDir, { Directory packagesDir, {
ProcessRunner processRunner = const ProcessRunner(), ProcessRunner processRunner = const ProcessRunner(),
Print print = print, Print print = print,
io.Stdin stdinput, io.Stdin? stdinput,
GitDir gitDir, GitDir? gitDir,
}) : _print = print, }) : _print = print,
_stdin = stdinput ?? io.stdin, _stdin = stdinput ?? io.stdin,
super(packagesDir, processRunner: processRunner, gitDir: gitDir) { super(packagesDir, processRunner: processRunner, gitDir: gitDir) {
@ -118,7 +127,7 @@ class PublishPluginCommand extends PluginCommand {
final Print _print; final Print _print;
final io.Stdin _stdin; final io.Stdin _stdin;
StreamSubscription<String> _stdinSubscription; StreamSubscription<String>? _stdinSubscription;
@override @override
Future<void> run() async { Future<void> run() async {
@ -138,14 +147,20 @@ class PublishPluginCommand extends PluginCommand {
_print('$packagesPath is not a valid Git repository.'); _print('$packagesPath is not a valid Git repository.');
throw ToolExit(1); throw ToolExit(1);
} }
final GitDir baseGitDir = final GitDir baseGitDir = gitDir ??
await GitDir.fromExisting(packagesPath, allowSubdirectory: true); await GitDir.fromExisting(packagesPath, allowSubdirectory: true);
final bool shouldPushTag = getBoolArg(_pushTagsOption); final bool shouldPushTag = getBoolArg(_pushTagsOption);
final String remote = getStringArg(_remoteOption); _RemoteInfo? remote;
String remoteUrl;
if (shouldPushTag) { if (shouldPushTag) {
remoteUrl = await _verifyRemote(remote); final String remoteName = getStringArg(_remoteOption);
final String? remoteUrl = await _verifyRemote(remoteName);
if (remoteUrl == null) {
printError(
'Unable to find URL for remote $remoteName; cannot push tags');
throw ToolExit(1);
}
remote = _RemoteInfo(name: remoteName, url: remoteUrl);
} }
_print('Local repo is ready!'); _print('Local repo is ready!');
if (getBoolArg(_dryRunFlag)) { if (getBoolArg(_dryRunFlag)) {
@ -155,27 +170,21 @@ class PublishPluginCommand extends PluginCommand {
bool successful; bool successful;
if (publishAllChanged) { if (publishAllChanged) {
successful = await _publishAllChangedPackages( successful = await _publishAllChangedPackages(
remote: remote,
remoteUrl: remoteUrl,
shouldPushTag: shouldPushTag,
baseGitDir: baseGitDir, baseGitDir: baseGitDir,
remoteForTagPush: remote,
); );
} else { } else {
successful = await _publishAndTagPackage( successful = await _publishAndTagPackage(
packageDir: _getPackageDir(package), packageDir: _getPackageDir(package),
remote: remote, remoteForTagPush: remote,
remoteUrl: remoteUrl,
shouldPushTag: shouldPushTag,
); );
} }
await _finish(successful); await _finish(successful);
} }
Future<bool> _publishAllChangedPackages({ Future<bool> _publishAllChangedPackages({
String remote, required GitDir baseGitDir,
String remoteUrl, _RemoteInfo? remoteForTagPush,
bool shouldPushTag,
GitDir baseGitDir,
}) async { }) async {
final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
final List<String> changedPubspecs = final List<String> changedPubspecs =
@ -215,9 +224,7 @@ class PublishPluginCommand extends PluginCommand {
_print('\n'); _print('\n');
if (await _publishAndTagPackage( if (await _publishAndTagPackage(
packageDir: pubspecFile.parent, packageDir: pubspecFile.parent,
remote: remote, remoteForTagPush: remoteForTagPush,
remoteUrl: remoteUrl,
shouldPushTag: shouldPushTag,
)) { )) {
packagesReleased.add(pubspecFile.parent.basename); packagesReleased.add(pubspecFile.parent.basename);
} else { } else {
@ -237,13 +244,11 @@ class PublishPluginCommand extends PluginCommand {
// Publish the package to pub with `pub publish`. // Publish the package to pub with `pub publish`.
// If `_tagReleaseOption` is on, git tag the release. // If `_tagReleaseOption` is on, git tag the release.
// If `shouldPushTag` is `true`, the tag will be pushed to `remote`. // If `remoteForTagPush` is non-null, the tag will be pushed to that remote.
// Returns `true` if publishing and tag are successful. // Returns `true` if publishing and tagging are successful.
Future<bool> _publishAndTagPackage({ Future<bool> _publishAndTagPackage({
@required Directory packageDir, required Directory packageDir,
@required String remote, _RemoteInfo? remoteForTagPush,
@required String remoteUrl,
@required bool shouldPushTag,
}) async { }) async {
if (!await _publishPlugin(packageDir: packageDir)) { if (!await _publishPlugin(packageDir: packageDir)) {
return false; return false;
@ -251,9 +256,7 @@ class PublishPluginCommand extends PluginCommand {
if (getBoolArg(_tagReleaseOption)) { if (getBoolArg(_tagReleaseOption)) {
if (!await _tagRelease( if (!await _tagRelease(
packageDir: packageDir, packageDir: packageDir,
remote: remote, remoteForPush: remoteForTagPush,
remoteUrl: remoteUrl,
shouldPushTag: shouldPushTag,
)) { )) {
return false; return false;
} }
@ -264,9 +267,9 @@ class PublishPluginCommand extends PluginCommand {
// Returns a [_CheckNeedsReleaseResult] that indicates the result. // Returns a [_CheckNeedsReleaseResult] that indicates the result.
Future<_CheckNeedsReleaseResult> _checkNeedsRelease({ Future<_CheckNeedsReleaseResult> _checkNeedsRelease({
@required File pubspecFile, required File pubspecFile,
@required GitVersionFinder gitVersionFinder, required GitVersionFinder gitVersionFinder,
@required List<String> existingTags, required List<String> existingTags,
}) async { }) async {
if (!pubspecFile.existsSync()) { if (!pubspecFile.existsSync()) {
_print(''' _print('''
@ -287,10 +290,6 @@ Safe to ignore if the package is deleted in this commit.
return _CheckNeedsReleaseResult.failure; return _CheckNeedsReleaseResult.failure;
} }
if (pubspec.name == null) {
_print('Fatal: Package name is null.');
return _CheckNeedsReleaseResult.failure;
}
// Get latest tagged version and compare with the current version. // Get latest tagged version and compare with the current version.
// TODO(cyanglaz): Check latest version of the package on pub instead of git // TODO(cyanglaz): Check latest version of the package on pub instead of git
// https://github.com/flutter/flutter/issues/81047 // https://github.com/flutter/flutter/issues/81047
@ -301,7 +300,7 @@ Safe to ignore if the package is deleted in this commit.
if (latestTag.isNotEmpty) { if (latestTag.isNotEmpty) {
final String latestTaggedVersion = latestTag.split('-v').last; final String latestTaggedVersion = latestTag.split('-v').last;
final Version latestVersion = Version.parse(latestTaggedVersion); final Version latestVersion = Version.parse(latestTaggedVersion);
if (pubspec.version < latestVersion) { if (pubspec.version! < latestVersion) {
_print( _print(
'The new version (${pubspec.version}) is lower than the current version ($latestVersion) for ${pubspec.name}.\nThis git commit is a revert, no release is tagged.'); 'The new version (${pubspec.version}) is lower than the current version ($latestVersion) for ${pubspec.name}.\nThis git commit is a revert, no release is tagged.');
return _CheckNeedsReleaseResult.noRelease; return _CheckNeedsReleaseResult.noRelease;
@ -313,7 +312,7 @@ Safe to ignore if the package is deleted in this commit.
// Publish the plugin. // Publish the plugin.
// //
// Returns `true` if successful, `false` otherwise. // Returns `true` if successful, `false` otherwise.
Future<bool> _publishPlugin({@required Directory packageDir}) async { Future<bool> _publishPlugin({required Directory packageDir}) async {
final bool gitStatusOK = await _checkGitStatus(packageDir); final bool gitStatusOK = await _checkGitStatus(packageDir);
if (!gitStatusOK) { if (!gitStatusOK) {
return false; return false;
@ -326,14 +325,13 @@ Safe to ignore if the package is deleted in this commit.
return true; return true;
} }
// Tag the release with <plugin-name>-v<version> // Tag the release with <plugin-name>-v<version>, and, if [remoteForTagPush]
// is provided, push it to that remote.
// //
// Return `true` if successful, `false` otherwise. // Return `true` if successful, `false` otherwise.
Future<bool> _tagRelease({ Future<bool> _tagRelease({
@required Directory packageDir, required Directory packageDir,
@required String remote, _RemoteInfo? remoteForPush,
@required String remoteUrl,
@required bool shouldPushTag,
}) async { }) async {
final String tag = _getTag(packageDir); final String tag = _getTag(packageDir);
_print('Tagging release $tag...'); _print('Tagging release $tag...');
@ -350,23 +348,20 @@ Safe to ignore if the package is deleted in this commit.
} }
} }
if (!shouldPushTag) { if (remoteForPush == null) {
return true; return true;
} }
_print('Pushing tag to $remote...'); _print('Pushing tag to ${remoteForPush.name}...');
return await _pushTagToRemote( return await _pushTagToRemote(
remote: remote,
tag: tag, tag: tag,
remoteUrl: remoteUrl, remote: remoteForPush,
); );
} }
Future<void> _finish(bool successful) async { Future<void> _finish(bool successful) async {
if (_stdinSubscription != null) { await _stdinSubscription?.cancel();
await _stdinSubscription.cancel();
_stdinSubscription = null; _stdinSubscription = null;
}
if (successful) { if (successful) {
_print('Done!'); _print('Done!');
} else { } else {
@ -408,15 +403,15 @@ Safe to ignore if the package is deleted in this commit.
return statusOutput.isEmpty; return statusOutput.isEmpty;
} }
Future<String> _verifyRemote(String remote) async { Future<String?> _verifyRemote(String remote) async {
final io.ProcessResult remoteInfo = await processRunner.run( final io.ProcessResult getRemoteUrlResult = await processRunner.run(
'git', 'git',
<String>['remote', 'get-url', remote], <String>['remote', 'get-url', remote],
workingDir: packagesDir, workingDir: packagesDir,
exitOnError: true, exitOnError: true,
logOnError: true, logOnError: true,
); );
return remoteInfo.stdout as String; return getRemoteUrlResult.stdout as String?;
} }
Future<bool> _publish(Directory packageDir) async { Future<bool> _publish(Directory packageDir) async {
@ -471,15 +466,14 @@ Safe to ignore if the package is deleted in this commit.
// //
// Return `true` if successful, `false` otherwise. // Return `true` if successful, `false` otherwise.
Future<bool> _pushTagToRemote({ Future<bool> _pushTagToRemote({
@required String remote, required String tag,
@required String tag, required _RemoteInfo remote,
@required String remoteUrl,
}) async { }) async {
assert(remote != null && tag != null && remoteUrl != null); assert(remote != null && tag != null);
if (!getBoolArg(_skipConfirmationFlag)) { if (!getBoolArg(_skipConfirmationFlag)) {
_print('Ready to push $tag to $remoteUrl (y/n)?'); _print('Ready to push $tag to ${remote.url} (y/n)?');
final String input = _stdin.readLineSync(); final String? input = _stdin.readLineSync();
if (input.toLowerCase() != 'y') { if (input?.toLowerCase() != 'y') {
_print('Tag push canceled.'); _print('Tag push canceled.');
return false; return false;
} }
@ -487,7 +481,7 @@ Safe to ignore if the package is deleted in this commit.
if (!getBoolArg(_dryRunFlag)) { if (!getBoolArg(_dryRunFlag)) {
final io.ProcessResult result = await processRunner.run( final io.ProcessResult result = await processRunner.run(
'git', 'git',
<String>['push', remote, tag], <String>['push', remote.name, tag],
workingDir: packagesDir, workingDir: packagesDir,
exitOnError: false, exitOnError: false,
logOnError: true, logOnError: true,
@ -500,15 +494,16 @@ Safe to ignore if the package is deleted in this commit.
} }
void _ensureValidPubCredential() { void _ensureValidPubCredential() {
final File credentialFile = packagesDir.fileSystem.file(_credentialsPath); final String credentialsPath = _credentialsPath;
final File credentialFile = packagesDir.fileSystem.file(credentialsPath);
if (credentialFile.existsSync() && if (credentialFile.existsSync() &&
credentialFile.readAsStringSync().isNotEmpty) { credentialFile.readAsStringSync().isNotEmpty) {
return; return;
} }
final String credential = io.Platform.environment[_pubCredentialName]; final String? credential = io.Platform.environment[_pubCredentialName];
if (credential == null) { if (credential == null) {
printError(''' printError('''
No pub credential available. Please check if `~/.pub-cache/credentials.json` is valid. No pub credential available. Please check if `$credentialsPath` is valid.
If running this command on CI, you can set the pub credential content in the $_pubCredentialName environment variable. If running this command on CI, you can set the pub credential content in the $_pubCredentialName environment variable.
'''); ''');
throw ToolExit(1); throw ToolExit(1);
@ -529,17 +524,32 @@ If running this command on CI, you can set the pub credential content in the $_p
final String _credentialsPath = () { final String _credentialsPath = () {
// This follows the same logic as pub: // This follows the same logic as pub:
// https://github.com/dart-lang/pub/blob/d99b0d58f4059d7bb4ac4616fd3d54ec00a2b5d4/lib/src/system_cache.dart#L34-L43 // https://github.com/dart-lang/pub/blob/d99b0d58f4059d7bb4ac4616fd3d54ec00a2b5d4/lib/src/system_cache.dart#L34-L43
String cacheDir; String? cacheDir;
final String pubCache = io.Platform.environment['PUB_CACHE']; final String? pubCache = io.Platform.environment['PUB_CACHE'];
print(pubCache); print(pubCache);
if (pubCache != null) { if (pubCache != null) {
cacheDir = pubCache; cacheDir = pubCache;
} else if (io.Platform.isWindows) { } else if (io.Platform.isWindows) {
final String appData = io.Platform.environment['APPDATA']; final String? appData = io.Platform.environment['APPDATA'];
cacheDir = p.join(appData, 'Pub', 'Cache'); if (appData == null) {
printError('"APPDATA" environment variable is not set.');
} else { } else {
cacheDir = p.join(io.Platform.environment['HOME'], '.pub-cache'); cacheDir = p.join(appData, 'Pub', 'Cache');
} }
} else {
final String? home = io.Platform.environment['HOME'];
if (home == null) {
printError('"HOME" environment variable is not set.');
} else {
cacheDir = p.join(home, '.pub-cache');
}
}
if (cacheDir == null) {
printError('Unable to determine pub cache location');
throw ToolExit(1);
}
return p.join(cacheDir, 'credentials.json'); return p.join(cacheDir, 'credentials.json');
}(); }();

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart=2.9
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io; import 'dart:io' as io;
@ -22,15 +20,15 @@ import 'util.dart';
void main() { void main() {
const String testPluginName = 'foo'; const String testPluginName = 'foo';
List<String> printedMessages; late List<String> printedMessages;
Directory testRoot; late Directory testRoot;
Directory packagesDir; late Directory packagesDir;
Directory pluginDir; late Directory pluginDir;
GitDir gitDir; late GitDir gitDir;
TestProcessRunner processRunner; late TestProcessRunner processRunner;
CommandRunner<void> commandRunner; late CommandRunner<void> commandRunner;
MockStdin mockStdin; late MockStdin mockStdin;
// This test uses a local file system instead of an in memory one throughout // This test uses a local file system instead of an in memory one throughout
// so that git actually works. In setup we initialize a mono repo of plugins // so that git actually works. In setup we initialize a mono repo of plugins
// with one package and commit everything to Git. // with one package and commit everything to Git.
@ -65,7 +63,7 @@ void main() {
commandRunner = CommandRunner<void>('tester', '') commandRunner = CommandRunner<void>('tester', '')
..addCommand(PublishPluginCommand(packagesDir, ..addCommand(PublishPluginCommand(packagesDir,
processRunner: processRunner, processRunner: processRunner,
print: (Object message) => printedMessages.add(message.toString()), print: (Object? message) => printedMessages.add(message.toString()),
stdinput: mockStdin, stdinput: mockStdin,
gitDir: gitDir)); gitDir: gitDir));
}); });
@ -285,9 +283,9 @@ void main() {
'--no-push-tags', '--no-push-tags',
]); ]);
final String tag = final String? tag =
(await gitDir.runCommand(<String>['show-ref', 'fake_package-v0.0.1'])) (await gitDir.runCommand(<String>['show-ref', 'fake_package-v0.0.1']))
.stdout as String; .stdout as String?;
expect(tag, isNotEmpty); expect(tag, isNotEmpty);
}); });
@ -303,10 +301,10 @@ void main() {
throwsA(const TypeMatcher<ToolExit>())); throwsA(const TypeMatcher<ToolExit>()));
expect(printedMessages, contains('Publish foo failed.')); expect(printedMessages, contains('Publish foo failed.'));
final String tag = (await gitDir.runCommand( final String? tag = (await gitDir.runCommand(
<String>['show-ref', 'fake_package-v0.0.1'], <String>['show-ref', 'fake_package-v0.0.1'],
throwOnError: false)) throwOnError: false))
.stdout as String; .stdout as String?;
expect(tag, isEmpty); expect(tag, isEmpty);
}); });
}); });
@ -838,20 +836,20 @@ void main() {
class TestProcessRunner extends ProcessRunner { class TestProcessRunner extends ProcessRunner {
final List<io.ProcessResult> results = <io.ProcessResult>[]; final List<io.ProcessResult> results = <io.ProcessResult>[];
// Most recent returned publish process. // Most recent returned publish process.
MockProcess mockPublishProcess; late MockProcess mockPublishProcess;
final List<String> mockPublishArgs = <String>[]; final List<String> mockPublishArgs = <String>[];
final MockProcessResult mockPushTagsResult = MockProcessResult(); final MockProcessResult mockPushTagsResult = MockProcessResult();
final List<String> pushTagsArgs = <String>[]; final List<String> pushTagsArgs = <String>[];
String mockPublishStdout; String? mockPublishStdout;
String mockPublishStderr; String? mockPublishStderr;
int mockPublishCompleteCode; int? mockPublishCompleteCode;
@override @override
Future<io.ProcessResult> run( Future<io.ProcessResult> run(
String executable, String executable,
List<String> args, { List<String> args, {
Directory workingDir, Directory? workingDir,
bool exitOnError = false, bool exitOnError = false,
bool logOnError = false, bool logOnError = false,
Encoding stdoutEncoding = io.systemEncoding, Encoding stdoutEncoding = io.systemEncoding,
@ -874,7 +872,7 @@ class TestProcessRunner extends ProcessRunner {
@override @override
Future<io.Process> start(String executable, List<String> args, Future<io.Process> start(String executable, List<String> args,
{Directory workingDirectory}) async { {Directory? workingDirectory}) async {
/// Never actually publish anything. Start is always and only used for this /// Never actually publish anything. Start is always and only used for this
/// since it returns something we can route stdin through. /// since it returns something we can route stdin through.
assert(executable == 'flutter' && assert(executable == 'flutter' &&
@ -884,10 +882,10 @@ class TestProcessRunner extends ProcessRunner {
mockPublishArgs.addAll(args); mockPublishArgs.addAll(args);
mockPublishProcess = MockProcess(); mockPublishProcess = MockProcess();
if (mockPublishStdout != null) { if (mockPublishStdout != null) {
mockPublishProcess.stdoutController.add(utf8.encode(mockPublishStdout)); mockPublishProcess.stdoutController.add(utf8.encode(mockPublishStdout!));
} }
if (mockPublishStderr != null) { if (mockPublishStderr != null) {
mockPublishProcess.stderrController.add(utf8.encode(mockPublishStderr)); mockPublishProcess.stderrController.add(utf8.encode(mockPublishStderr!));
} }
if (mockPublishCompleteCode != null) { if (mockPublishCompleteCode != null) {
mockPublishProcess.exitCodeCompleter.complete(mockPublishCompleteCode); mockPublishProcess.exitCodeCompleter.complete(mockPublishCompleteCode);
@ -899,8 +897,8 @@ class TestProcessRunner extends ProcessRunner {
class MockStdin extends Mock implements io.Stdin { class MockStdin extends Mock implements io.Stdin {
List<List<int>> mockUserInputs = <List<int>>[]; List<List<int>> mockUserInputs = <List<int>>[];
StreamController<List<int>> _controller; late StreamController<List<int>> _controller;
String readLineOutput; String? readLineOutput;
@override @override
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) { Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
@ -915,14 +913,14 @@ class MockStdin extends Mock implements io.Stdin {
} }
@override @override
StreamSubscription<List<int>> listen(void onData(List<int> event), StreamSubscription<List<int>> listen(void onData(List<int> event)?,
{Function onError, void onDone(), bool cancelOnError}) { {Function? onError, void onDone()?, bool? cancelOnError}) {
return _controller.stream.listen(onData, return _controller.stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError); onError: onError, onDone: onDone, cancelOnError: cancelOnError);
} }
@override @override
String readLineSync( String? readLineSync(
{Encoding encoding = io.systemEncoding, {Encoding encoding = io.systemEncoding,
bool retainNewlines = false}) => bool retainNewlines = false}) =>
readLineOutput; readLineOutput;