mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* Remove legacy .apk build. Print out an error message telling the user to upgrade the project if it's not Gradle-based. Removed all the obvious traces of the legacy build. Added support for Dart VM kernel snapshots in Gradle builds. Fixed Android installs to verify that the app is actually installed, and not just rely on the presence of the .sha1 file.
231 lines
7.6 KiB
Dart
231 lines
7.6 KiB
Dart
// Copyright 2016 The Chromium 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 'dart:async';
|
|
|
|
import '../artifacts.dart';
|
|
import '../base/common.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/logger.dart';
|
|
import '../base/os.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/process.dart';
|
|
import '../base/utils.dart';
|
|
import '../build_info.dart';
|
|
import '../cache.dart';
|
|
import '../globals.dart';
|
|
import 'android_sdk.dart';
|
|
import 'android_studio.dart';
|
|
|
|
const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml';
|
|
const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
|
|
const String gradleAppOutDirV1 = 'android/app/build/outputs/apk';
|
|
|
|
enum FlutterPluginVersion {
|
|
none,
|
|
v1,
|
|
v2,
|
|
managed,
|
|
}
|
|
|
|
bool isProjectUsingGradle() {
|
|
return fs.isFileSync('android/build.gradle');
|
|
}
|
|
|
|
FlutterPluginVersion get flutterPluginVersion {
|
|
final File plugin = fs.file('android/buildSrc/src/main/groovy/FlutterPlugin.groovy');
|
|
if (plugin.existsSync()) {
|
|
final String packageLine = plugin.readAsLinesSync().skip(4).first;
|
|
if (packageLine == "package io.flutter.gradle") {
|
|
return FlutterPluginVersion.v2;
|
|
}
|
|
return FlutterPluginVersion.v1;
|
|
}
|
|
final File appGradle = fs.file('android/app/build.gradle');
|
|
if (appGradle.existsSync()) {
|
|
for (String line in appGradle.readAsLinesSync()) {
|
|
if (line.contains(new RegExp(r"apply from: .*/flutter.gradle"))) {
|
|
return FlutterPluginVersion.managed;
|
|
}
|
|
}
|
|
}
|
|
return FlutterPluginVersion.none;
|
|
}
|
|
|
|
String get gradleAppOut {
|
|
switch (flutterPluginVersion) {
|
|
case FlutterPluginVersion.none:
|
|
// Fall through. Pretend we're v1, and just go with it.
|
|
case FlutterPluginVersion.v1:
|
|
return gradleAppOutV1;
|
|
case FlutterPluginVersion.managed:
|
|
// Fall through. The managed plugin matches plugin v2 for now.
|
|
case FlutterPluginVersion.v2:
|
|
return '$gradleAppOutDirV2/app.apk';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String get gradleAppOutDirV2 {
|
|
final String gradle = ensureGradle();
|
|
ensureLocalProperties();
|
|
try {
|
|
final String properties = runCheckedSync(
|
|
<String>[gradle, 'app:properties'],
|
|
workingDirectory: 'android',
|
|
hideStdout: true,
|
|
);
|
|
String buildDir = properties
|
|
.split('\n')
|
|
.firstWhere((String s) => s.startsWith('buildDir: '))
|
|
.substring('buildDir: '.length)
|
|
.trim();
|
|
final String currentDirectory = fs.currentDirectory.path;
|
|
if (buildDir.startsWith(currentDirectory)) {
|
|
// Relativize path, snip current directory + separating '/'.
|
|
buildDir = buildDir.substring(currentDirectory.length + 1);
|
|
}
|
|
return '$buildDir/outputs/apk';
|
|
} catch (e) {
|
|
printError('Error running gradle: $e');
|
|
}
|
|
// Fall back to the default
|
|
return gradleAppOutDirV1;
|
|
}
|
|
|
|
String locateSystemGradle({ bool ensureExecutable: true }) {
|
|
final String gradle = gradleExecutable;
|
|
if (ensureExecutable && gradle != null) {
|
|
final File file = fs.file(gradle);
|
|
if (file.existsSync())
|
|
os.makeExecutable(file);
|
|
}
|
|
return gradle;
|
|
}
|
|
|
|
String locateProjectGradlew({ bool ensureExecutable: true }) {
|
|
final String path = 'android/gradlew';
|
|
|
|
if (fs.isFileSync(path)) {
|
|
final File gradle = fs.file(path);
|
|
if (ensureExecutable)
|
|
os.makeExecutable(gradle);
|
|
return gradle.absolute.path;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
String ensureGradle() {
|
|
String gradle = locateProjectGradlew();
|
|
if (gradle == null) {
|
|
gradle = locateSystemGradle();
|
|
if (gradle == null) {
|
|
throwToolExit('Unable to locate gradle. Please install Android Studio.');
|
|
}
|
|
}
|
|
printTrace('Using gradle from $gradle.');
|
|
return gradle;
|
|
}
|
|
|
|
/// Create android/local.properties if needed.
|
|
File ensureLocalProperties() {
|
|
final File localProperties = fs.file('android/local.properties');
|
|
if (!localProperties.existsSync()) {
|
|
localProperties.writeAsStringSync(
|
|
'sdk.dir=${_escapePath(androidSdk.directory)}\n'
|
|
'flutter.sdk=${_escapePath(Cache.flutterRoot)}\n'
|
|
);
|
|
}
|
|
return localProperties;
|
|
}
|
|
|
|
Future<Null> buildGradleProject(BuildMode buildMode, String target, String kernelPath) async {
|
|
final File localProperties = ensureLocalProperties();
|
|
// Update the local.properties file with the build mode.
|
|
// FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2
|
|
// uses the standard Android way to determine what to build, but we still
|
|
// update local.properties, in case we want to use it in the future.
|
|
final String buildModeName = getModeName(buildMode);
|
|
final SettingsFile settings = new SettingsFile.parseFromFile(localProperties);
|
|
settings.values['flutter.buildMode'] = buildModeName;
|
|
settings.writeContents(localProperties);
|
|
|
|
final String gradle = ensureGradle();
|
|
|
|
switch (flutterPluginVersion) {
|
|
case FlutterPluginVersion.none:
|
|
// Fall through. Pretend it's v1, and just go for it.
|
|
case FlutterPluginVersion.v1:
|
|
return buildGradleProjectV1(gradle);
|
|
case FlutterPluginVersion.managed:
|
|
// Fall through. Managed plugin builds the same way as plugin v2.
|
|
case FlutterPluginVersion.v2:
|
|
return buildGradleProjectV2(gradle, buildModeName, target, kernelPath);
|
|
}
|
|
}
|
|
|
|
String _escapePath(String path) => platform.isWindows ? path.replaceAll('\\', '\\\\') : path;
|
|
|
|
Future<Null> buildGradleProjectV1(String gradle) async {
|
|
// Run 'gradle build'.
|
|
final Status status = logger.startProgress('Running \'gradle build\'...', expectSlowOperation: true);
|
|
final int exitcode = await runCommandAndStreamOutput(
|
|
<String>[fs.file(gradle).absolute.path, 'build'],
|
|
workingDirectory: 'android',
|
|
allowReentrantFlutter: true
|
|
);
|
|
status.stop();
|
|
|
|
if (exitcode != 0)
|
|
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
|
|
|
|
final File apkFile = fs.file(gradleAppOutV1);
|
|
printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).');
|
|
}
|
|
|
|
Future<Null> buildGradleProjectV2(String gradle, String buildModeName, String target, String kernelPath) async {
|
|
final String assembleTask = "assemble${toTitleCase(buildModeName)}";
|
|
|
|
// Run 'gradle assemble<BuildMode>'.
|
|
final Status status = logger.startProgress('Running \'gradle $assembleTask\'...', expectSlowOperation: true);
|
|
final String gradlePath = fs.file(gradle).absolute.path;
|
|
final List<String> command = <String>[gradlePath];
|
|
if (!logger.isVerbose) {
|
|
command.add('-q');
|
|
}
|
|
if (artifacts is LocalEngineArtifacts) {
|
|
final LocalEngineArtifacts localEngineArtifacts = artifacts;
|
|
printTrace('Using local engine: ${localEngineArtifacts.engineOutPath}');
|
|
command.add('-PlocalEngineOut=${localEngineArtifacts.engineOutPath}');
|
|
}
|
|
if (target != null) {
|
|
command.add('-Ptarget=$target');
|
|
}
|
|
if (kernelPath != null)
|
|
command.add('-Pkernel=$kernelPath');
|
|
command.add(assembleTask);
|
|
final int exitcode = await runCommandAndStreamOutput(
|
|
command,
|
|
workingDirectory: 'android',
|
|
allowReentrantFlutter: true
|
|
);
|
|
status.stop();
|
|
|
|
if (exitcode != 0)
|
|
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
|
|
|
|
final String buildDirectory = gradleAppOutDirV2;
|
|
final String apkFilename = 'app-$buildModeName.apk';
|
|
final File apkFile = fs.file('$buildDirectory/$apkFilename');
|
|
// Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
|
|
apkFile.copySync('$buildDirectory/app.apk');
|
|
|
|
printTrace('calculateSha: $buildDirectory/app.apk');
|
|
final File apkShaFile = fs.file('$buildDirectory/app.apk.sha1');
|
|
apkShaFile.writeAsStringSync(calculateSha(apkFile));
|
|
|
|
printStatus('Built ${apkFile.path} (${getSizeAsMB(apkFile.lengthSync())}).');
|
|
}
|