mirror of
https://github.com/flutter/packages.git
synced 2025-08-06 08:53:11 +08:00
[ci] Run analysis with older versions of Flutter (#5000)
This commit is contained in:
@ -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
|
||||
|
||||
|
@ -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']);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
]));
|
||||
|
@ -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', () {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user