mirror of
https://github.com/flutter/packages.git
synced 2025-06-29 22:33:11 +08:00
[Tool] New tool to download android dependencies (#4408)
This pr is pushed for high level feedback/conversation. I will add tests before serious review. should be read in conjuction with https://flutter-review.googlesource.com/c/recipes/+/46980 - Create new top level command to run flutter dependencies on changed packages - when running android tests download dependencies before running tests https://github.com/flutter/flutter/issues/120119
This commit is contained in:
@ -1,6 +1,11 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- name: prepare tool
|
- name: prepare tool
|
||||||
script: .ci/scripts/prepare_tool.sh
|
script: .ci/scripts/prepare_tool.sh
|
||||||
|
infra_step: true # Note infra steps failing prevents "always" from running.
|
||||||
|
- name: download android deps
|
||||||
|
script: script/tool_runner.sh
|
||||||
|
infra_step: true
|
||||||
|
args: ["fetch-deps"]
|
||||||
- name: build examples
|
- name: build examples
|
||||||
script: script/tool_runner.sh
|
script: script/tool_runner.sh
|
||||||
args: ["build-examples", "--apk"]
|
args: ["build-examples", "--apk"]
|
||||||
|
74
script/tool/lib/src/fetch_deps_command.dart
Normal file
74
script/tool/lib/src/fetch_deps_command.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2013 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 'common/core.dart';
|
||||||
|
import 'common/gradle.dart';
|
||||||
|
import 'common/output_utils.dart';
|
||||||
|
import 'common/package_looping_command.dart';
|
||||||
|
import 'common/plugin_utils.dart';
|
||||||
|
import 'common/repository_package.dart';
|
||||||
|
|
||||||
|
/// Download dependencies for the following platforms {android}.
|
||||||
|
///
|
||||||
|
/// Specficially each platform runs:
|
||||||
|
/// Android: 'gradlew dependencies'.
|
||||||
|
/// Dart: TBD (flutter/flutter/issues/130279)
|
||||||
|
/// iOS: TBD (flutter/flutter/issues/130280)
|
||||||
|
///
|
||||||
|
/// See https://docs.gradle.org/6.4/userguide/core_dependency_management.html#sec:dependency-mgmt-in-gradle.
|
||||||
|
class FetchDepsCommand extends PackageLoopingCommand {
|
||||||
|
/// Creates an instance of the fetch-deps command.
|
||||||
|
FetchDepsCommand(
|
||||||
|
super.packagesDir, {
|
||||||
|
super.processRunner,
|
||||||
|
super.platform,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String name = 'fetch-deps';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String description = 'Fetches dependencies for plugins.\n'
|
||||||
|
'Runs "gradlew dependencies" on Android plugins.\n'
|
||||||
|
'Dart see flutter/flutter/issues/130279\n'
|
||||||
|
'iOS plugins see flutter/flutter/issues/130280\n'
|
||||||
|
'\n'
|
||||||
|
'Requires the examples to have been built at least once before running.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PackageResult> runForPackage(RepositoryPackage package) async {
|
||||||
|
if (!pluginSupportsPlatform(platformAndroid, package,
|
||||||
|
requiredMode: PlatformSupport.inline)) {
|
||||||
|
return PackageResult.skip(
|
||||||
|
'Plugin does not have an Android implementation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final RepositoryPackage example in package.getExamples()) {
|
||||||
|
final GradleProject gradleProject = GradleProject(example,
|
||||||
|
processRunner: processRunner, platform: platform);
|
||||||
|
|
||||||
|
if (!gradleProject.isConfigured()) {
|
||||||
|
final int exitCode = await processRunner.runAndStream(
|
||||||
|
flutterCommand,
|
||||||
|
<String>['build', 'apk', '--config-only'],
|
||||||
|
workingDir: example.directory,
|
||||||
|
);
|
||||||
|
if (exitCode != 0) {
|
||||||
|
printError('Unable to configure Gradle project.');
|
||||||
|
return PackageResult.fail(<String>['Unable to configure Gradle.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String packageName = package.directory.basename;
|
||||||
|
|
||||||
|
final int exitCode = await gradleProject.runCommand('$packageName:dependencies');
|
||||||
|
if (exitCode != 0) {
|
||||||
|
return PackageResult.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PackageResult.success();
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import 'dart_test_command.dart';
|
|||||||
import 'dependabot_check_command.dart';
|
import 'dependabot_check_command.dart';
|
||||||
import 'drive_examples_command.dart';
|
import 'drive_examples_command.dart';
|
||||||
import 'federation_safety_check_command.dart';
|
import 'federation_safety_check_command.dart';
|
||||||
|
import 'fetch_deps_command.dart';
|
||||||
import 'firebase_test_lab_command.dart';
|
import 'firebase_test_lab_command.dart';
|
||||||
import 'fix_command.dart';
|
import 'fix_command.dart';
|
||||||
import 'format_command.dart';
|
import 'format_command.dart';
|
||||||
@ -65,6 +66,7 @@ void main(List<String> args) {
|
|||||||
..addCommand(DependabotCheckCommand(packagesDir))
|
..addCommand(DependabotCheckCommand(packagesDir))
|
||||||
..addCommand(DriveExamplesCommand(packagesDir))
|
..addCommand(DriveExamplesCommand(packagesDir))
|
||||||
..addCommand(FederationSafetyCheckCommand(packagesDir))
|
..addCommand(FederationSafetyCheckCommand(packagesDir))
|
||||||
|
..addCommand(FetchDepsCommand(packagesDir))
|
||||||
..addCommand(FirebaseTestLabCommand(packagesDir))
|
..addCommand(FirebaseTestLabCommand(packagesDir))
|
||||||
..addCommand(FixCommand(packagesDir))
|
..addCommand(FixCommand(packagesDir))
|
||||||
..addCommand(FormatCommand(packagesDir))
|
..addCommand(FormatCommand(packagesDir))
|
||||||
|
252
script/tool/test/fetch_deps_command_test.dart
Normal file
252
script/tool/test/fetch_deps_command_test.dart
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
// Copyright 2013 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 'package:args/command_runner.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/fetch_deps_command.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'mocks.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('FetchDepsCommand', () {
|
||||||
|
FileSystem fileSystem;
|
||||||
|
late Directory packagesDir;
|
||||||
|
late CommandRunner<void> runner;
|
||||||
|
late MockPlatform mockPlatform;
|
||||||
|
late RecordingProcessRunner processRunner;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||||
|
mockPlatform = MockPlatform();
|
||||||
|
processRunner = RecordingProcessRunner();
|
||||||
|
final FetchDepsCommand command = FetchDepsCommand(
|
||||||
|
packagesDir,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: mockPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
|
runner =
|
||||||
|
CommandRunner<void>('fetch_deps_test', 'Test for $FetchDepsCommand');
|
||||||
|
runner.addCommand(command);
|
||||||
|
});
|
||||||
|
group('android', () {
|
||||||
|
test('runs gradlew dependencies', () async {
|
||||||
|
final RepositoryPackage plugin =
|
||||||
|
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
|
||||||
|
'example/android/gradlew',
|
||||||
|
], platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.inline)
|
||||||
|
});
|
||||||
|
|
||||||
|
final Directory androidDir = plugin
|
||||||
|
.getExamples()
|
||||||
|
.first
|
||||||
|
.platformDirectory(FlutterPlatform.android);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['fetch-deps']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
ProcessCall(
|
||||||
|
androidDir.childFile('gradlew').path,
|
||||||
|
const <String>['plugin1:dependencies'],
|
||||||
|
androidDir.path,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Running for plugin1'),
|
||||||
|
contains('No issues found!'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs on all examples', () async {
|
||||||
|
final List<String> examples = <String>['example1', 'example2'];
|
||||||
|
final RepositoryPackage plugin = createFakePlugin(
|
||||||
|
'plugin1', packagesDir,
|
||||||
|
examples: examples,
|
||||||
|
extraFiles: <String>[
|
||||||
|
'example/example1/android/gradlew',
|
||||||
|
'example/example2/android/gradlew',
|
||||||
|
],
|
||||||
|
platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.inline)
|
||||||
|
});
|
||||||
|
|
||||||
|
final Iterable<Directory> exampleAndroidDirs = plugin.getExamples().map(
|
||||||
|
(RepositoryPackage example) =>
|
||||||
|
example.platformDirectory(FlutterPlatform.android));
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['fetch-deps']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
for (final Directory directory in exampleAndroidDirs)
|
||||||
|
ProcessCall(
|
||||||
|
directory.childFile('gradlew').path,
|
||||||
|
const <String>['plugin1:dependencies'],
|
||||||
|
directory.path,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Running for plugin1'),
|
||||||
|
contains('No issues found!'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs --config-only build if gradlew is missing', () async {
|
||||||
|
final RepositoryPackage plugin = createFakePlugin(
|
||||||
|
'plugin1', packagesDir, platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.inline)
|
||||||
|
});
|
||||||
|
|
||||||
|
final Directory androidDir = plugin
|
||||||
|
.getExamples()
|
||||||
|
.first
|
||||||
|
.platformDirectory(FlutterPlatform.android);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['fetch-deps']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
ProcessCall(
|
||||||
|
getFlutterCommand(mockPlatform),
|
||||||
|
const <String>['build', 'apk', '--config-only'],
|
||||||
|
plugin.getExamples().first.directory.path,
|
||||||
|
),
|
||||||
|
ProcessCall(
|
||||||
|
androidDir.childFile('gradlew').path,
|
||||||
|
const <String>['plugin1:dependencies'],
|
||||||
|
androidDir.path,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Running for plugin1'),
|
||||||
|
contains('No issues found!'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if gradlew generation fails', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.inline)
|
||||||
|
});
|
||||||
|
|
||||||
|
processRunner
|
||||||
|
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
|
||||||
|
<FakeProcessInfo>[
|
||||||
|
FakeProcessInfo(MockProcess(exitCode: 1)),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['fetch-deps'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Unable to configure Gradle project'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if dependency download finds issues', () async {
|
||||||
|
final RepositoryPackage plugin =
|
||||||
|
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
|
||||||
|
'example/android/gradlew',
|
||||||
|
], platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.inline)
|
||||||
|
});
|
||||||
|
|
||||||
|
final String gradlewPath = plugin
|
||||||
|
.getExamples()
|
||||||
|
.first
|
||||||
|
.platformDirectory(FlutterPlatform.android)
|
||||||
|
.childFile('gradlew')
|
||||||
|
.path;
|
||||||
|
processRunner.mockProcessesForExecutable[gradlewPath] =
|
||||||
|
<FakeProcessInfo>[
|
||||||
|
FakeProcessInfo(MockProcess(exitCode: 1)),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['fetch-deps'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('The following packages had errors:'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips non-Android plugins', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['fetch-deps']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(
|
||||||
|
'SKIPPING: Plugin does not have an Android implementation.')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips non-inline plugins', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
platformSupport: <String, PlatformDetails>{
|
||||||
|
platformAndroid: const PlatformDetails(PlatformSupport.federated)
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['fetch-deps']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(
|
||||||
|
'SKIPPING: Plugin does not have an Android implementation.')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user