[ci] Run analysis with older versions of Flutter (#5000)

This commit is contained in:
stuartmorgan
2022-03-09 11:35:23 -05:00
committed by GitHub
parent 199afd96f1
commit 159f6d87b7
8 changed files with 172 additions and 61 deletions

View File

@ -19,6 +19,9 @@
`flutter` behavior.
- Validates `default_package` entries in plugins.
- Removes `allow-warnings` from the `podspecs` command.
- Adds `skip-if-not-supporting-flutter-version` to allow running tests using a
version of Flutter that not all packages support. (E.g., to allow for running
some tests against old versions of Flutter to help avoid accidental breakage.)
## 0.7.3

View File

@ -10,12 +10,9 @@ import 'package:yaml/yaml.dart';
import 'common/core.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_command.dart';
import 'common/process_runner.dart';
import 'common/repository_package.dart';
const int _exitPackagesGetFailed = 3;
/// A command to run Dart analysis on packages.
class AnalyzeCommand extends PackageLoopingCommand {
/// Creates a analysis command instance.
@ -84,41 +81,8 @@ class AnalyzeCommand extends PackageLoopingCommand {
return false;
}
/// Ensures that the dependent packages have been fetched for all packages
/// (including their sub-packages) that will be analyzed.
Future<bool> _runPackagesGetOnTargetPackages() async {
final List<Directory> packageDirectories =
await getTargetPackagesAndSubpackages()
.map((PackageEnumerationEntry entry) => entry.package.directory)
.toList();
final Set<String> packagePaths =
packageDirectories.map((Directory dir) => dir.path).toSet();
packageDirectories.removeWhere((Directory directory) {
// Remove the 'example' subdirectories; 'flutter packages get'
// automatically runs 'pub get' there as part of handling the parent
// directory.
return directory.basename == 'example' &&
packagePaths.contains(directory.parent.path);
});
for (final Directory package in packageDirectories) {
final int exitCode = await processRunner.runAndStream(
flutterCommand, <String>['packages', 'get'],
workingDir: package);
if (exitCode != 0) {
return false;
}
}
return true;
}
@override
Future<void> initializeRun() async {
print('Fetching dependencies...');
if (!await _runPackagesGetOnTargetPackages()) {
printError('Unable to get dependencies.');
throw ToolExit(_exitPackagesGetFailed);
}
_allowedCustomAnalysisDirectories =
getStringListArg(_customAnalysisFlag).expand<String>((String item) {
if (item.endsWith('.yaml')) {
@ -138,6 +102,19 @@ class AnalyzeCommand extends PackageLoopingCommand {
@override
Future<PackageResult> runForPackage(RepositoryPackage package) async {
// For non-example packages, fetch dependencies. 'flutter packages get'
// automatically runs 'pub get' in examples as part of handling the parent
// directory, which is guaranteed to come first in the package enumeration.
if (package.directory.basename != 'example' ||
!RepositoryPackage(package.directory.parent).pubspecFile.existsSync()) {
final int exitCode = await processRunner.runAndStream(
flutterCommand, <String>['packages', 'get'],
workingDir: package.directory);
if (exitCode != 0) {
return PackageResult.fail(<String>['Unable to get dependencies']);
}
}
if (_hasUnexpecetdAnalysisOptions(package)) {
return PackageResult.fail(<String>['Unexpected local analysis options']);
}

View File

@ -9,6 +9,8 @@ import 'package:file/file.dart';
import 'package:git/git.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'core.dart';
import 'plugin_command.dart';
@ -75,7 +77,16 @@ abstract class PackageLoopingCommand extends PluginCommand {
Platform platform = const LocalPlatform(),
GitDir? gitDir,
}) : super(packagesDir,
processRunner: processRunner, platform: platform, gitDir: gitDir);
processRunner: processRunner, platform: platform, gitDir: gitDir) {
argParser.addOption(
_skipByFlutterVersionArg,
help: 'Skip any packages that require a Flutter version newer than '
'the provided version.',
);
}
static const String _skipByFlutterVersionArg =
'skip-if-not-supporting-flutter-version';
/// Packages that had at least one [logWarning] call.
final Set<PackageEnumerationEntry> _packagesWithWarnings =
@ -219,6 +230,11 @@ abstract class PackageLoopingCommand extends PluginCommand {
_otherWarningCount = 0;
_currentPackageEntry = null;
final String minFlutterVersionArg = getStringArg(_skipByFlutterVersionArg);
final Version? minFlutterVersion = minFlutterVersionArg.isEmpty
? null
: Version.parse(minFlutterVersionArg);
final DateTime runStart = DateTime.now();
await initializeRun();
@ -242,7 +258,8 @@ abstract class PackageLoopingCommand extends PluginCommand {
PackageResult result;
try {
result = await runForPackage(entry.package);
result = await _runForPackageIfSupported(entry.package,
minFlutterVersion: minFlutterVersion);
} catch (e, stack) {
printError(e.toString());
printError(stack.toString());
@ -285,6 +302,26 @@ abstract class PackageLoopingCommand extends PluginCommand {
return true;
}
/// Returns the result of running [runForPackage] if the package is supported
/// by any run constraints, or a skip result if it is not.
Future<PackageResult> _runForPackageIfSupported(
RepositoryPackage package, {
Version? minFlutterVersion,
}) async {
if (minFlutterVersion != null) {
final Pubspec pubspec = package.parsePubspec();
final VersionConstraint? flutterConstraint =
pubspec.environment?['flutter'];
if (flutterConstraint != null &&
!flutterConstraint.allows(minFlutterVersion)) {
return PackageResult.skip(
'Does not support Flutter ${minFlutterVersion.toString()}');
}
}
return await runForPackage(package);
}
void _printSuccess(String message) {
captureOutput ? print(message) : printSuccess(message);
}

View File

@ -409,6 +409,9 @@ abstract class PluginCommand extends Command<void> {
///
/// By default, packages excluded via --exclude will not be in the stream, but
/// they can be included by passing false for [filterExcluded].
///
/// Subpackages are guaranteed to be after the containing package in the
/// stream.
Stream<PackageEnumerationEntry> getTargetPackagesAndSubpackages(
{bool filterExcluded = true}) async* {
await for (final PackageEnumerationEntry plugin

View File

@ -47,10 +47,10 @@ void main() {
orderedEquals(<ProcessCall>[
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin1Dir.path),
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin2Dir.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'],
plugin1Dir.path),
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin2Dir.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'],
plugin2Dir.path),
]));
@ -82,10 +82,10 @@ void main() {
orderedEquals(<ProcessCall>[
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin1Dir.path),
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin2Dir.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'],
plugin1Dir.path),
ProcessCall(
'flutter', const <String>['packages', 'get'], plugin2Dir.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'],
plugin2Dir.path),
]));

View File

@ -236,6 +236,34 @@ void main() {
expect(command.checkedPackages,
isNot(contains(excluded.childDirectory('example2').path)));
});
test('skips unsupported versions when requested', () async {
final Directory excluded = createFakePlugin('a_plugin', packagesDir,
flutterConstraint: '>=2.10.0');
final Directory included = createFakePackage('a_package', packagesDir);
final TestPackageLoopingCommand command =
createTestCommand(includeSubpackages: true, hasLongOutput: false);
final List<String> output = await runCommand(command, arguments: <String>[
'--skip-if-not-supporting-flutter-version=2.5.0'
]);
expect(
command.checkedPackages,
unorderedEquals(<String>[
included.path,
included.childDirectory('example').path,
]));
expect(command.checkedPackages, isNot(contains(excluded.path)));
expect(
output,
containsAllInOrder(<String>[
'${_startHeadingColor}Running for a_package...$_endColor',
'${_startHeadingColor}Running for a_plugin...$_endColor',
'$_startSkipColor SKIPPING: Does not support Flutter 2.5.0$_endColor',
]));
});
});
group('output', () {

View File

@ -253,6 +253,29 @@ packages/plugin1/plugin1/plugin1.dart
unorderedEquals(<String>[platformInterfacePackage.path]));
});
test('returns subpackages after the enclosing package', () async {
final SamplePluginCommand localCommand = SamplePluginCommand(
packagesDir,
processRunner: processRunner,
platform: mockPlatform,
gitDir: MockGitDir(),
includeSubpackages: true,
);
final CommandRunner<void> localRunner =
CommandRunner<void>('common_command', 'subpackage testing');
localRunner.addCommand(localCommand);
final Directory package = createFakePackage('apackage', packagesDir);
await runCapturingPrint(localRunner, <String>['sample']);
expect(
localCommand.plugins,
containsAllInOrder(<String>[
package.path,
package.childDirectory('example').path,
]));
});
group('conflicting package selection', () {
test('does not allow --packages with --run-on-changed-packages',
() async {
@ -893,11 +916,14 @@ class SamplePluginCommand extends PluginCommand {
ProcessRunner processRunner = const ProcessRunner(),
Platform platform = const LocalPlatform(),
GitDir? gitDir,
this.includeSubpackages = false,
}) : super(packagesDir,
processRunner: processRunner, platform: platform, gitDir: gitDir);
final List<String> plugins = <String>[];
final bool includeSubpackages;
@override
final String name = 'sample';
@ -906,7 +932,10 @@ class SamplePluginCommand extends PluginCommand {
@override
Future<void> run() async {
await for (final PackageEnumerationEntry entry in getTargetPackages()) {
final Stream<PackageEnumerationEntry> packages = includeSubpackages
? getTargetPackagesAndSubpackages()
: getTargetPackages();
await for (final PackageEnumerationEntry entry in packages) {
plugins.add(entry.package.path);
}
}

View File

@ -85,12 +85,14 @@ Directory createFakePlugin(
Map<String, PlatformDetails> platformSupport =
const <String, PlatformDetails>{},
String? version = '0.0.1',
String flutterConstraint = '>=2.5.0',
}) {
final Directory pluginDirectory = createFakePackage(name, parentDirectory,
isFlutter: true,
examples: examples,
extraFiles: extraFiles,
version: version);
version: version,
flutterConstraint: flutterConstraint);
createFakePubspec(
pluginDirectory,
@ -99,6 +101,7 @@ Directory createFakePlugin(
isPlugin: true,
platformSupport: platformSupport,
version: version,
flutterConstraint: flutterConstraint,
);
return pluginDirectory;
@ -116,12 +119,16 @@ Directory createFakePackage(
List<String> extraFiles = const <String>[],
bool isFlutter = false,
String? version = '0.0.1',
String flutterConstraint = '>=2.5.0',
}) {
final Directory packageDirectory = parentDirectory.childDirectory(name);
packageDirectory.createSync(recursive: true);
createFakePubspec(packageDirectory,
name: name, isFlutter: isFlutter, version: version);
name: name,
isFlutter: isFlutter,
version: version,
flutterConstraint: flutterConstraint);
createFakeCHANGELOG(packageDirectory, '''
## $version
* Some changes.
@ -132,7 +139,10 @@ Directory createFakePackage(
final Directory exampleDir = packageDirectory.childDirectory(examples.first)
..createSync();
createFakePubspec(exampleDir,
name: '${name}_example', isFlutter: isFlutter, publishTo: 'none');
name: '${name}_example',
isFlutter: isFlutter,
publishTo: 'none',
flutterConstraint: flutterConstraint);
} else if (examples.isNotEmpty) {
final Directory exampleDir = packageDirectory.childDirectory('example')
..createSync();
@ -140,7 +150,10 @@ Directory createFakePackage(
final Directory currentExample = exampleDir.childDirectory(example)
..createSync();
createFakePubspec(currentExample,
name: example, isFlutter: isFlutter, publishTo: 'none');
name: example,
isFlutter: isFlutter,
publishTo: 'none',
flutterConstraint: flutterConstraint);
}
}
@ -172,40 +185,61 @@ void createFakePubspec(
const <String, PlatformDetails>{},
String publishTo = 'http://no_pub_server.com',
String? version,
String dartConstraint = '>=2.0.0 <3.0.0',
String flutterConstraint = '>=2.5.0',
}) {
isPlugin |= platformSupport.isNotEmpty;
parent.childFile('pubspec.yaml').createSync();
String yaml = '''
name: $name
String environmentSection = '''
environment:
sdk: "$dartConstraint"
''';
String dependenciesSection = '''
dependencies:
''';
String pluginSection = '';
// Add Flutter-specific entries if requested.
if (isFlutter) {
environmentSection += '''
flutter: "$flutterConstraint"
''';
dependenciesSection += '''
flutter:
sdk: flutter
''';
if (isPlugin) {
yaml += '''
pluginSection += '''
flutter:
plugin:
platforms:
''';
for (final MapEntry<String, PlatformDetails> platform
in platformSupport.entries) {
yaml += _pluginPlatformSection(platform.key, platform.value, name);
pluginSection +=
_pluginPlatformSection(platform.key, platform.value, name);
}
}
yaml += '''
dependencies:
flutter:
sdk: flutter
''';
}
if (version != null) {
yaml += '''
version: $version
String yaml = '''
name: $name
${(version != null) ? 'version: $version' : ''}
$environmentSection
$dependenciesSection
$pluginSection
''';
}
if (publishTo.isNotEmpty) {
yaml += '''
publish_to: $publishTo # Hardcoded safeguard to prevent this from somehow being published by a broken test.
''';
}
parent.childFile('pubspec.yaml').createSync();
parent.childFile('pubspec.yaml').writeAsStringSync(yaml);
}