forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Tool] New tool to download android dependencies (flutter#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 flutter#120119
- Loading branch information
Showing
4 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.') | ||
], | ||
)); | ||
}); | ||
}); | ||
} |