Unverified Commit 9cb9bfbd authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] use new output location for the apk (#54328)

parent 650592b3
...@@ -39,7 +39,7 @@ Directory getApkDirectory(FlutterProject project) { ...@@ -39,7 +39,7 @@ Directory getApkDirectory(FlutterProject project) {
: project.android.buildDirectory : project.android.buildDirectory
.childDirectory('app') .childDirectory('app')
.childDirectory('outputs') .childDirectory('outputs')
.childDirectory('apk'); .childDirectory('flutter-apk');
} }
/// The directory where the app bundle artifact is generated. /// The directory where the app bundle artifact is generated.
...@@ -469,25 +469,33 @@ Future<void> buildGradleApp({ ...@@ -469,25 +469,33 @@ Future<void> buildGradleApp({
return; return;
} }
// Gradle produced an APK. // Gradle produced an APK.
final Iterable<File> apkFiles = findApkFiles(project, androidBuildInfo); final Iterable<String> apkFilesPaths = project.isModule
? findApkFilesModule(project, androidBuildInfo)
: listApkPaths(androidBuildInfo);
final Directory apkDirectory = getApkDirectory(project); final Directory apkDirectory = getApkDirectory(project);
final File apkFile = apkDirectory.childFile(apkFilesPaths.first);
if (!apkFile.existsSync()) {
_exitWithExpectedFileNotFound(
project: project,
fileExtension: '.apk',
);
}
// Copy the first APK to app.apk, so `flutter run` can find it. // Copy the first APK to app.apk, so `flutter run` can find it.
// TODO(egarciad): Handle multiple APKs. // TODO(egarciad): Handle multiple APKs.
apkFiles.first.copySync(apkDirectory.childFile('app.apk').path); apkFile.copySync(apkDirectory.childFile('app.apk').path);
globals.printTrace('calculateSha: $apkDirectory/app.apk'); globals.printTrace('calculateSha: $apkDirectory/app.apk');
final File apkShaFile = apkDirectory.childFile('app.apk.sha1'); final File apkShaFile = apkDirectory.childFile('app.apk.sha1');
apkShaFile.writeAsStringSync(_calculateSha(apkFiles.first)); apkShaFile.writeAsStringSync(_calculateSha(apkFile));
for (final File apkFile in apkFiles) { final String appSize = (buildInfo.mode == BuildMode.debug)
final String appSize = (buildInfo.mode == BuildMode.debug) ? '' // Don't display the size when building a debug variant.
? '' // Don't display the size when building a debug variant. : ' (${getSizeAsMB(apkFile.lengthSync())})';
: ' (${getSizeAsMB(apkFile.lengthSync())})'; globals.printStatus(
globals.printStatus( '$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.',
'$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.', color: TerminalColor.green,
color: TerminalColor.green, );
);
}
} }
/// Builds AAR and POM files. /// Builds AAR and POM files.
...@@ -778,7 +786,7 @@ Future<void> buildPluginsAsAar( ...@@ -778,7 +786,7 @@ Future<void> buildPluginsAsAar(
/// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo]. /// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo].
@visibleForTesting @visibleForTesting
Iterable<File> findApkFiles( Iterable<String> findApkFilesModule(
FlutterProject project, FlutterProject project,
AndroidBuildInfo androidBuildInfo, AndroidBuildInfo androidBuildInfo,
) { ) {
...@@ -815,7 +823,39 @@ Iterable<File> findApkFiles( ...@@ -815,7 +823,39 @@ Iterable<File> findApkFiles(
fileExtension: '.apk', fileExtension: '.apk',
); );
} }
return apks; return apks.map((File file) => file.path);
}
/// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo].
///
/// The flutter.gradle plugin will copy APK outputs into:
/// $buildDir/app/outputs/flutter-apk/app-<abi>-<flavor-flag>-<build-mode-flag>.apk
@visibleForTesting
Iterable<String> listApkPaths(
AndroidBuildInfo androidBuildInfo,
) {
final String buildType = camelCase(androidBuildInfo.buildInfo.modeName);
final List<String> apkPartialName = <String>[
if (androidBuildInfo.buildInfo.flavor?.isNotEmpty ?? false)
androidBuildInfo.buildInfo.flavor,
'$buildType.apk',
];
if (androidBuildInfo.splitPerAbi) {
return <String>[
for (AndroidArch androidArch in androidBuildInfo.targetArchs)
<String>[
'app',
getNameForAndroidArch(androidArch),
...apkPartialName
].join('-')
];
}
return <String>[
<String>[
'app',
...apkPartialName,
].join('-')
];
} }
@visibleForTesting @visibleForTesting
......
...@@ -44,7 +44,7 @@ void main() { ...@@ -44,7 +44,7 @@ void main() {
expect( expect(
getApkDirectory(project).path, getApkDirectory(project).path,
equals(globals.fs.path.join('foo', 'app', 'outputs', 'apk')), equals(globals.fs.path.join('foo', 'app', 'outputs', 'flutter-apk')),
); );
}); });
...@@ -312,107 +312,41 @@ void main() { ...@@ -312,107 +312,41 @@ void main() {
}); });
}); });
group('findApkFiles', () { group('listApkPaths', () {
final Usage mockUsage = MockUsage(); testWithoutContext('Finds APK without flavor in release', () {
final Iterable<String> apks = listApkPaths(
testUsingContext('Finds APK without flavor in release', () {
final FlutterProject project = MockFlutterProject();
final AndroidProject androidProject = MockAndroidProject();
when(project.android).thenReturn(androidProject);
when(project.isModule).thenReturn(false);
when(androidProject.buildDirectory).thenReturn(globals.fs.directory('irrelevant'));
final Directory apkDirectory = globals.fs.directory(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release'));
apkDirectory.createSync(recursive: true);
apkDirectory.childFile('app-release.apk').createSync();
final Iterable<File> apks = findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)), const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-release.apk')));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Finds APK with flavor in release mode', () { expect(apks, <String>['app-release.apk']);
final FlutterProject project = MockFlutterProject(); });
final AndroidProject androidProject = MockAndroidProject();
when(project.android).thenReturn(androidProject);
when(project.isModule).thenReturn(false);
when(androidProject.buildDirectory).thenReturn(globals.fs.directory('irrelevant'));
final Directory apkDirectory = globals.fs.directory(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release'));
apkDirectory.createSync(recursive: true);
apkDirectory.childFile('app-flavor1-release.apk').createSync();
final Iterable<File> apks = findApkFiles( testWithoutContext('Finds APK with flavor in release mode', () {
project, final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)), const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-flavor1-release.apk')));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Finds APK with flavor in release mode - AGP v3', () {
final FlutterProject project = MockFlutterProject();
final AndroidProject androidProject = MockAndroidProject();
when(project.android).thenReturn(androidProject); expect(apks, <String>['app-flavor1-release.apk']);
when(project.isModule).thenReturn(false); });
when(androidProject.buildDirectory).thenReturn(globals.fs.directory('irrelevant'));
final Directory apkDirectory = globals.fs.directory(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release'));
apkDirectory.createSync(recursive: true);
apkDirectory.childFile('app-flavor1-release.apk').createSync();
final Iterable<File> apks = findApkFiles( testWithoutContext('Finds APK with flavor in release mode - AGP v3', () {
project, final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)), const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
); );
expect(apks.isNotEmpty, isTrue);
expect(apks.first.path, equals(globals.fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release', 'app-flavor1-release.apk'))); expect(apks, <String>['app-flavor1-release.apk']);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('apk not found', () { testWithoutContext('Finds APK with split-per-abi', () {
final FlutterProject project = FlutterProject.current(); final Iterable<String> apks = listApkPaths(
expect( const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false), splitPerAbi: true),
() {
findApkFiles(
project,
const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'foo_bar', treeShakeIcons: false)),
);
},
throwsToolExit(
message:
"Gradle build failed to produce an .apk file. It's likely that this file "
"was generated under ${project.android.buildDirectory.path}, but the tool couldn't find it."
)
); );
verify(
mockUsage.sendEvent( expect(apks, unorderedEquals(<String>[
any, 'app-armeabi-v7a-flavor1-release.apk',
any, 'app-arm64-v8a-flavor1-release.apk',
label: 'gradle-expected-file-not-found', 'app-x86_64-flavor1-release.apk',
parameters: const <String, String> { ]));
'cd37': 'androidGradlePluginVersion: 5.6.2, fileExtension: .apk',
},
),
).called(1);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => mockUsage,
}); });
}); });
...@@ -1415,8 +1349,7 @@ plugin1=${plugin1.path} ...@@ -1415,8 +1349,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build') fileSystem.directory('build')
.childDirectory('app') .childDirectory('app')
.childDirectory('outputs') .childDirectory('outputs')
.childDirectory('apk') .childDirectory('flutter-apk')
.childDirectory('release')
.childFile('app-release.apk') .childFile('app-release.apk')
.createSync(recursive: true); .createSync(recursive: true);
...@@ -1455,7 +1388,6 @@ plugin1=${plugin1.path} ...@@ -1455,7 +1388,6 @@ plugin1=${plugin1.path}
label: 'gradle-random-event-label-success', label: 'gradle-random-event-label-success',
parameters: anyNamed('parameters'), parameters: anyNamed('parameters'),
)).called(1); )).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk, AndroidSdk: () => mockAndroidSdk,
Cache: () => cache, Cache: () => cache,
...@@ -1553,17 +1485,6 @@ plugin1=${plugin1.path} ...@@ -1553,17 +1485,6 @@ plugin1=${plugin1.path}
}); });
testUsingContext('indicates that an APK has been built successfully', () async { testUsingContext('indicates that an APK has been built successfully', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 0,
stdout: '',
));
});
fileSystem.directory('android') fileSystem.directory('android')
.childFile('build.gradle') .childFile('build.gradle')
.createSync(recursive: true); .createSync(recursive: true);
...@@ -1581,8 +1502,7 @@ plugin1=${plugin1.path} ...@@ -1581,8 +1502,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build') fileSystem.directory('build')
.childDirectory('app') .childDirectory('app')
.childDirectory('outputs') .childDirectory('outputs')
.childDirectory('apk') .childDirectory('flutter-apk')
.childDirectory('release')
.childFile('app-release.apk') .childFile('app-release.apk')
.createSync(recursive: true); .createSync(recursive: true);
...@@ -1602,7 +1522,7 @@ plugin1=${plugin1.path} ...@@ -1602,7 +1522,7 @@ plugin1=${plugin1.path}
expect( expect(
testLogger.statusText, testLogger.statusText,
contains('Built build/app/outputs/apk/release/app-release.apk (0.0MB)'), contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -1610,7 +1530,7 @@ plugin1=${plugin1.path} ...@@ -1610,7 +1530,7 @@ plugin1=${plugin1.path}
Cache: () => cache, Cache: () => cache,
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
Platform: () => android, Platform: () => android,
ProcessManager: () => mockProcessManager, ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async { testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment