Skip to content

Commit

Permalink
[flutter_tools] use new output location for the apk (#54328)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonahwilliams committed Apr 13, 2020
1 parent 650592b commit 9cb9bfb
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 122 deletions.
70 changes: 55 additions & 15 deletions packages/flutter_tools/lib/src/android/gradle.dart
Expand Up @@ -39,7 +39,7 @@ Directory getApkDirectory(FlutterProject project) {
: project.android.buildDirectory
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk');
.childDirectory('flutter-apk');
}

/// The directory where the app bundle artifact is generated.
Expand Down Expand Up @@ -469,25 +469,33 @@ Future<void> buildGradleApp({
return;
}
// 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 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.
// 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');

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)
? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(apkFile.lengthSync())})';
globals.printStatus(
'$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.',
color: TerminalColor.green,
);
}
final String appSize = (buildInfo.mode == BuildMode.debug)
? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(apkFile.lengthSync())})';
globals.printStatus(
'$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.',
color: TerminalColor.green,
);
}

/// Builds AAR and POM files.
Expand Down Expand Up @@ -778,7 +786,7 @@ Future<void> buildPluginsAsAar(

/// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo].
@visibleForTesting
Iterable<File> findApkFiles(
Iterable<String> findApkFilesModule(
FlutterProject project,
AndroidBuildInfo androidBuildInfo,
) {
Expand Down Expand Up @@ -815,7 +823,39 @@ Iterable<File> findApkFiles(
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
Expand Down
134 changes: 27 additions & 107 deletions packages/flutter_tools/test/general.shard/android/gradle_test.dart
Expand Up @@ -44,7 +44,7 @@ void main() {

expect(
getApkDirectory(project).path,
equals(globals.fs.path.join('foo', 'app', 'outputs', 'apk')),
equals(globals.fs.path.join('foo', 'app', 'outputs', 'flutter-apk')),
);
});

Expand Down Expand Up @@ -312,107 +312,41 @@ void main() {
});
});

group('findApkFiles', () {
final Usage mockUsage = MockUsage();

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,
group('listApkPaths', () {
testWithoutContext('Finds APK without flavor in release', () {
final Iterable<String> apks = listApkPaths(
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', () {
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();
expect(apks, <String>['app-release.apk']);
});

final Iterable<File> apks = findApkFiles(
project,
testWithoutContext('Finds APK with flavor in release mode', () {
final Iterable<String> apks = listApkPaths(
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);
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();
expect(apks, <String>['app-flavor1-release.apk']);
});

final Iterable<File> apks = findApkFiles(
project,
testWithoutContext('Finds APK with flavor in release mode - AGP v3', () {
final Iterable<String> apks = listApkPaths(
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')));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),

expect(apks, <String>['app-flavor1-release.apk']);
});

testUsingContext('apk not found', () {
final FlutterProject project = FlutterProject.current();
expect(
() {
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."
)
testWithoutContext('Finds APK with split-per-abi', () {
final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false), splitPerAbi: true),
);
verify(
mockUsage.sendEvent(
any,
any,
label: 'gradle-expected-file-not-found',
parameters: const <String, String> {
'cd37': 'androidGradlePluginVersion: 5.6.2, fileExtension: .apk',
},
),
).called(1);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => mockUsage,

expect(apks, unorderedEquals(<String>[
'app-armeabi-v7a-flavor1-release.apk',
'app-arm64-v8a-flavor1-release.apk',
'app-x86_64-flavor1-release.apk',
]));
});
});

Expand Down Expand Up @@ -1415,8 +1349,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk')
.childDirectory('release')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);

Expand Down Expand Up @@ -1455,7 +1388,6 @@ plugin1=${plugin1.path}
label: 'gradle-random-event-label-success',
parameters: anyNamed('parameters'),
)).called(1);

}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Expand Down Expand Up @@ -1553,17 +1485,6 @@ plugin1=${plugin1.path}
});

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')
.childFile('build.gradle')
.createSync(recursive: true);
Expand All @@ -1581,8 +1502,7 @@ plugin1=${plugin1.path}
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk')
.childDirectory('release')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);

Expand All @@ -1602,15 +1522,15 @@ plugin1=${plugin1.path}

expect(
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>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => mockProcessManager,
ProcessManager: () => FakeProcessManager.any(),
});

testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async {
Expand Down

0 comments on commit 9cb9bfb

Please sign in to comment.