blob: 8b4043ffcd0afb1773c37b679ffecfcda58b2b1c [file] [log] [blame]
// 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 '../plugins.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';
String _cachedGradleAppOutDirV2;
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 getGradleAppOut() {
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 '${getGradleAppOutDirV2()}/app.apk';
}
return null;
}
String getGradleAppOutDirV2() {
_cachedGradleAppOutDirV2 ??= _calculateGradleAppOutDirV2();
return _cachedGradleAppOutDirV2;
}
// Note: this call takes about a second to complete.
String _calculateGradleAppOutDirV2() {
final String gradle = ensureGradle();
updateLocalProperties();
try {
final String properties = runCheckedSync(
<String>[gradle, 'app:properties'],
workingDirectory: 'android',
hideStdout: true,
environment: _gradleEnv,
);
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 = fs.path.join(
'android', platform.isWindows ? 'gradlew.bat' : '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, and update Flutter settings.
void updateLocalProperties({String projectPath, String buildMode}) {
final File localProperties = (projectPath == null)
? fs.file(fs.path.join('android', 'local.properties'))
: fs.file(fs.path.join(projectPath, 'android', 'local.properties'));
bool changed = false;
SettingsFile settings;
if (localProperties.existsSync()) {
settings = new SettingsFile.parseFromFile(localProperties);
} else {
settings = new SettingsFile();
settings.values['sdk.dir'] = escapePath(androidSdk.directory);
changed = true;
}
final String escapedRoot = escapePath(Cache.flutterRoot);
if (changed || settings.values['flutter.sdk'] != escapedRoot) {
settings.values['flutter.sdk'] = escapedRoot;
changed = true;
}
if (buildMode != null && settings.values['flutter.buildMode'] != buildMode) {
settings.values['flutter.buildMode'] = buildMode;
changed = true;
}
if (changed)
settings.writeContents(localProperties);
}
Future<Null> buildGradleProject(BuildMode buildMode, String target, String kernelPath) async {
// 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);
updateLocalProperties(buildMode: buildModeName);
injectPlugins();
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);
}
}
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,
environment: _gradleEnv,
);
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,
environment: _gradleEnv,
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
final String buildDirectory = getGradleAppOutDirV2();
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())}).');
}
Map<String, String> get _gradleEnv {
final Map<String, String> env = new Map<String, String>.from(platform.environment);
if (javaPath != null) {
// Use java bundled with Android Studio.
env['JAVA_HOME'] = javaPath;
}
return env;
}