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

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

Merged
merged 3 commits into from Apr 13, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function could just become:

Iterable<String> findApkFilesModule(
  FlutterProject project,
  AndroidBuildInfo androidBuildInfo,
) {
   final Iterable<String> apkFileNames = _apkFilesFor(androidBuildInfo);
   final Directory apkDirectory = getApkDirectory(project);
   return apkFileNames.map((File file) => fs.path.join(apkDirectory.path, file.path));
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nvm. This is for build apk in a module, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah

}

/// 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 @@ -1413,8 +1347,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 @@ -1453,7 +1386,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 @@ -1551,17 +1483,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 @@ -1579,8 +1500,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 @@ -1600,15 +1520,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