Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tool] New tool to download android dependencies #4408

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .ci/targets/android_platform_tests.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
tasks:
- name: prepare tool
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
script: script/tool_runner.sh
args: ["build-examples", "--apk"]
Expand Down
74 changes: 74 additions & 0 deletions script/tool/lib/src/fetch_deps_command.dart
Original file line number Diff line number Diff line change
@@ -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();
}
}
2 changes: 2 additions & 0 deletions script/tool/lib/src/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'dart_test_command.dart';
import 'dependabot_check_command.dart';
import 'drive_examples_command.dart';
import 'federation_safety_check_command.dart';
import 'fetch_deps_command.dart';
import 'firebase_test_lab_command.dart';
import 'fix_command.dart';
import 'format_command.dart';
Expand Down Expand Up @@ -65,6 +66,7 @@ void main(List<String> args) {
..addCommand(DependabotCheckCommand(packagesDir))
..addCommand(DriveExamplesCommand(packagesDir))
..addCommand(FederationSafetyCheckCommand(packagesDir))
..addCommand(FetchDepsCommand(packagesDir))
..addCommand(FirebaseTestLabCommand(packagesDir))
..addCommand(FixCommand(packagesDir))
..addCommand(FormatCommand(packagesDir))
Expand Down
252 changes: 252 additions & 0 deletions script/tool/test/fetch_deps_command_test.dart
Original file line number Diff line number Diff line change
@@ -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.')
],
));
});
});
}