Update gradle example to support x86 in debug mode. (#7606)

* Update gradle example to support x86 in debug mode.

Changed the Flutter Gradle plugin a bit to better fit in with the
Android build.

Fixes #6136
Fixes #6864
Fixes #7539
This commit is contained in:
Jakob Andersen 2017-01-31 14:48:48 -08:00 committed by GitHub
parent 4cace66dbc
commit a0f0c42fe3
7 changed files with 233 additions and 94 deletions

View file

@ -2,8 +2,8 @@ apply plugin: 'com.android.application'
apply plugin: 'flutter' apply plugin: 'flutter'
android { android {
compileSdkVersion 22 compileSdkVersion 25
buildToolsVersion '22.0.1' buildToolsVersion '24.0.2'
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
@ -12,6 +12,14 @@ android {
defaultConfig { defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
} }
flutter { flutter {
@ -19,7 +27,7 @@ flutter {
} }
dependencies { dependencies {
androidTestCompile 'com.android.support:support-annotations:22.0.0' androidTestCompile 'com.android.support:support-annotations:25.0.0'
androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.android.support.test:rules:0.5'
} }

View file

@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.domokit.sky.gradle package io.flutter.gradle
import java.nio.file.Path
import java.nio.file.Paths
import com.android.builder.model.AndroidProject import com.android.builder.model.AndroidProject
import com.google.common.base.Joiner
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.GradleException import org.gradle.api.GradleException
import org.gradle.api.Project import org.gradle.api.Project
@ -14,71 +16,102 @@ import org.gradle.api.Task
import org.gradle.api.file.CopySpec import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.bundling.Jar
class FlutterPlugin implements Plugin<Project> { class FlutterPlugin implements Plugin<Project> {
private File flutterRoot private File flutterRoot
private String buildMode
private String localEngine private String localEngine
private Properties localProperties
private String resolveProperty(Project project, String name, String defaultValue) {
if (localProperties == null) {
localProperties = new Properties()
def localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localProperties.load(localPropertiesFile.newDataInputStream())
}
}
String result;
if (project.hasProperty(name)) {
result = project.property(name)
}
if (result == null) {
result = localProperties.getProperty(name)
}
if (result == null) {
result = defaultValue
}
return result
}
@Override @Override
void apply(Project project) { void apply(Project project) {
Properties properties = new Properties() // Add a 'profile' build type
properties.load(project.rootProject.file("local.properties").newDataInputStream()) project.android.buildTypes {
profile {
initWith debug
}
}
String flutterRootPath = properties.getProperty("flutter.sdk") String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_HOME)
if (flutterRootPath == null) { if (flutterRootPath == null) {
throw new GradleException("flutter.sdk must be defined in local.properties") throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_HOME environment variable.")
} }
flutterRoot = project.file(flutterRootPath) flutterRoot = project.file(flutterRootPath)
if (!flutterRoot.isDirectory()) { if (!flutterRoot.isDirectory()) {
throw new GradleException("flutter.sdk must point to the Flutter SDK directory") throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
} }
buildMode = properties.getProperty("flutter.buildMode") String flutterJarPath = localProperties.getProperty("flutter.jar")
if (buildMode == null) {
buildMode = "release"
}
if (!["debug", "profile", "release"].contains(buildMode)) {
throw new GradleException("flutter.buildMode must be one of \"debug\", \"profile\", or \"release\" but was \"${buildMode}\"")
}
File flutterJar
String flutterJarPath = properties.getProperty("flutter.jar")
if (flutterJarPath != null) { if (flutterJarPath != null) {
flutterJar = project.file(flutterJarPath) File flutterJar = project.file(flutterJarPath)
if (!flutterJar.isFile()) { if (!flutterJar.isFile()) {
throw new GradleException("flutter.jar must point to a Flutter engine JAR") throw new GradleException("flutter.jar must point to a Flutter engine JAR")
} }
} else { project.dependencies {
// TODO(abarth): Support x64 and x86 in addition to arm. compile project.files(flutterJar)
String artifactType = "unknown";
if (buildMode == "debug") {
artifactType = "android-arm"
} else if (buildMode == "profile") {
artifactType = "android-arm-profile"
} else if (buildMode == "release") {
artifactType = "android-arm-release"
} }
flutterJar = new File(flutterRoot, Joiner.on(File.separatorChar).join( } else {
"bin", "cache", "artifacts", "engine", artifactType, "flutter.jar")) Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
if (!flutterJar.isFile()) { File debugFlutterJar = baseEnginePath.resolve("android-arm").resolve("flutter.jar").toFile()
File profileFlutterJar = baseEnginePath.resolve("android-arm-profile").resolve("flutter.jar").toFile()
File releaseFlutterJar = baseEnginePath.resolve("android-arm-release").resolve("flutter.jar").toFile()
if (!debugFlutterJar.isFile()) {
project.exec { project.exec {
executable "${flutterRoot}/bin/flutter" executable "${flutterRoot}/bin/flutter"
args "precache" args "precache"
} }
if (!flutterJar.isFile()) { if (!debugFlutterJar.isFile()) {
throw new GradleException("Unable to find flutter.jar in SDK: ${flutterJar}") throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}")
} }
} }
// Add x86/x86_64 native library. Debug mode only, for now.
File flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
project.tasks.create("flutterBuildX86Jar", Jar) {
destinationDir flutterX86Jar.parentFile
archiveName flutterX86Jar.name
from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libsky_shell.so") {
into "lib/x86"
}
from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libsky_shell.so") {
into "lib/x86_64"
}
}
project.dependencies {
debugCompile project.files(flutterX86Jar, debugFlutterJar)
profileCompile project.files(profileFlutterJar)
releaseCompile project.files(releaseFlutterJar)
}
} }
localEngine = properties.getProperty("flutter.localEngine") localEngine = localProperties.getProperty("flutter.localEngine")
project.extensions.create("flutter", FlutterExtension) project.extensions.create("flutter", FlutterExtension)
project.dependencies.add("compile", project.files(flutterJar))
project.afterEvaluate this.&addFlutterTask project.afterEvaluate this.&addFlutterTask
} }
@ -87,21 +120,27 @@ class FlutterPlugin implements Plugin<Project> {
throw new GradleException("Must provide Flutter source directory") throw new GradleException("Must provide Flutter source directory")
} }
String target = project.flutter.target; String target = project.flutter.target
if (target == null) { if (target == null) {
target = 'lib/main.dart' target = 'lib/main.dart'
} }
FlutterTask flutterTask = project.tasks.create("flutterBuild", FlutterTask) { project.compileDebugJavaWithJavac.dependsOn project.flutterBuildX86Jar
flutterRoot this.flutterRoot
buildMode this.buildMode
localEngine this.localEngine
targetPath target
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter")
}
project.android.applicationVariants.all { variant -> project.android.applicationVariants.all { variant ->
if (!["debug", "profile", "release"].contains(variant.name)) {
throw new GradleException("Build variant must be one of \"debug\", \"profile\", or \"release\" but was \"${variant.name}\"")
}
FlutterTask flutterTask = project.tasks.create("flutterBuild${variant.name.capitalize()}", FlutterTask) {
flutterRoot this.flutterRoot
buildMode variant.name
localEngine this.localEngine
targetPath target
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
}
Task copyFlxTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) { Task copyFlxTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
dependsOn flutterTask dependsOn flutterTask
dependsOn variant.mergeAssets dependsOn variant.mergeAssets
@ -124,7 +163,6 @@ class FlutterTask extends DefaultTask {
String localEngine String localEngine
String targetPath String targetPath
@InputDirectory
File sourceDir File sourceDir
@OutputDirectory @OutputDirectory
@ -142,6 +180,11 @@ class FlutterTask extends DefaultTask {
} }
} }
@InputFiles
FileCollection getSourceFiles() {
return project.fileTree(dir: sourceDir, exclude: ['android', 'ios'], include: ['**/*.dart', 'pubspec.yaml', 'flutter.yaml'])
}
@TaskAction @TaskAction
void build() { void build() {
if (!sourceDir.isDirectory()) { if (!sourceDir.isDirectory()) {
@ -151,34 +194,34 @@ class FlutterTask extends DefaultTask {
intermediateDir.mkdirs() intermediateDir.mkdirs()
if (buildMode != "debug") { if (buildMode != "debug") {
project.exec { project.exec {
executable "${flutterRoot}/bin/flutter" executable "${flutterRoot}/bin/flutter"
workingDir sourceDir workingDir sourceDir
if (localEngine != null) { if (localEngine != null) {
args "--local-engine", localEngine args "--local-engine", localEngine
}
args "build", "aot"
args "--target", targetPath
args "--target-platform", "android-arm"
args "--output-dir", "${intermediateDir}"
args "--${buildMode}"
} }
args "build", "aot"
args "--target", targetPath
args "--target-platform", "android-arm"
args "--output-dir", "${intermediateDir}"
args "--${buildMode}"
}
} }
project.exec { project.exec {
executable "${flutterRoot}/bin/flutter" executable "${flutterRoot}/bin/flutter"
workingDir sourceDir workingDir sourceDir
if (localEngine != null) { if (localEngine != null) {
args "--local-engine", localEngine args "--local-engine", localEngine
} }
args "build", "flx" args "build", "flx"
args "--target", targetPath args "--target", targetPath
args "--output-file", "${intermediateDir}/app.flx" args "--output-file", "${intermediateDir}/app.flx"
if (buildMode != "debug") { if (buildMode != "debug") {
args "--precompiled" args "--precompiled"
} else { } else {
args "--snapshot", "${intermediateDir}/snapshot_blob.bin" args "--snapshot", "${intermediateDir}/snapshot_blob.bin"
args "--depfile", "${intermediateDir}/snapshot_blob.bin.d" args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
} }
args "--working-dir", "${intermediateDir}/flx" args "--working-dir", "${intermediateDir}/flx"
} }

View file

@ -1 +1 @@
implementation-class=org.domokit.sky.gradle.FlutterPlugin implementation-class=io.flutter.gradle.FlutterPlugin

View file

@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536M

View file

@ -274,6 +274,11 @@ class AndroidDevice extends Device {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion()) if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
return new LaunchResult.failed(); return new LaunchResult.failed();
if (platform != TargetPlatform.android_arm && mode != BuildMode.debug) {
printError('Profile and release builds are only supported on ARM targets.');
return new LaunchResult.failed();
}
printTrace("Stopping app '${package.name}' on $name."); printTrace("Stopping app '${package.name}' on $name.");
await stopApp(package); await stopApp(package);

View file

@ -18,12 +18,44 @@ import '../globals.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml'; const String gradleManifestPath = 'android/app/src/main/AndroidManifest.xml';
const String gradleAppOut = 'android/app/build/outputs/apk/app-debug.apk'; const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
const String gradleAppOutV2 = 'android/app/build/outputs/apk/app.apk';
const String gradleAppOutDir = 'android/app/build/outputs/apk';
enum FlutterPluginVersion {
none,
v1,
v2,
}
bool isProjectUsingGradle() { bool isProjectUsingGradle() {
return fs.isFileSync('android/build.gradle'); return fs.isFileSync('android/build.gradle');
} }
FlutterPluginVersion get flutterPluginVersion {
File plugin = fs.file('android/buildSrc/src/main/groovy/FlutterPlugin.groovy');
if (!plugin.existsSync()) {
return FlutterPluginVersion.none;
}
String packageLine = plugin.readAsLinesSync().skip(4).first;
if (packageLine == "package io.flutter.gradle") {
return FlutterPluginVersion.v2;
}
return FlutterPluginVersion.v1;
}
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.v2:
return gradleAppOutV2;
}
return null;
}
String locateSystemGradle({ bool ensureExecutable: true }) { String locateSystemGradle({ bool ensureExecutable: true }) {
String gradle = _locateSystemGradle(); String gradle = _locateSystemGradle();
if (ensureExecutable && gradle != null) { if (ensureExecutable && gradle != null) {
@ -91,29 +123,14 @@ String locateProjectGradlew({ bool ensureExecutable: true }) {
} }
} }
Future<Null> buildGradleProject(BuildMode buildMode) async { Future<String> ensureGradlew() async {
// Create android/local.properties.
File localProperties = fs.file('android/local.properties');
if (!localProperties.existsSync()) {
localProperties.writeAsStringSync(
'sdk.dir=${androidSdk.directory}\n'
'flutter.sdk=${Cache.flutterRoot}\n'
);
}
// Update the local.settings file with the build mode.
// TODO(devoncarew): It would be nicer if we could pass this information in via a cli flag.
SettingsFile settings = new SettingsFile.parseFromFile(localProperties);
settings.values['flutter.buildMode'] = getModeName(buildMode);
settings.writeContents(localProperties);
String gradlew = locateProjectGradlew(); String gradlew = locateProjectGradlew();
if (gradlew == null) { if (gradlew == null) {
String gradle = locateSystemGradle(); String gradle = locateSystemGradle();
if (gradle == null) { if (gradle == null) {
throwToolExit( throwToolExit(
'Unable to locate gradle. Please configure the path to gradle using \'flutter config --gradle-dir\'.' 'Unable to locate gradle. Please configure the path to gradle using \'flutter config --gradle-dir\'.'
); );
} else { } else {
printTrace('Using gradle from $gradle.'); printTrace('Using gradle from $gradle.');
@ -130,12 +147,15 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
} }
// Run 'gradle wrapper'. // Run 'gradle wrapper'.
List<String> command = logger.isVerbose
? <String>[gradle, 'wrapper']
: <String>[gradle, '-q', 'wrapper'];
try { try {
Status status = logger.startProgress('Running \'gradle wrapper\'...'); Status status = logger.startProgress('Running \'gradle wrapper\'...');
int exitcode = await runCommandAndStreamOutput( int exitcode = await runCommandAndStreamOutput(
<String>[gradle, 'wrapper'], command,
workingDirectory: 'android', workingDirectory: 'android',
allowReentrantFlutter: true allowReentrantFlutter: true
); );
status.stop(); status.stop();
if (exitcode != 0) if (exitcode != 0)
@ -149,10 +169,44 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
throwToolExit('Unable to build android/gradlew.'); throwToolExit('Unable to build android/gradlew.');
} }
return gradlew;
}
Future<Null> buildGradleProject(BuildMode buildMode) async {
// Create android/local.properties.
File localProperties = fs.file('android/local.properties');
if (!localProperties.existsSync()) {
localProperties.writeAsStringSync(
'sdk.dir=${androidSdk.directory}\n'
'flutter.sdk=${Cache.flutterRoot}\n'
);
}
// 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.
String buildModeName = getModeName(buildMode);
SettingsFile settings = new SettingsFile.parseFromFile(localProperties);
settings.values['flutter.buildMode'] = buildModeName;
settings.writeContents(localProperties);
String gradlew = await ensureGradlew();
switch (flutterPluginVersion) {
case FlutterPluginVersion.none:
// Fall through. Pretend it's v1, and just go for it.
case FlutterPluginVersion.v1:
return buildGradleProjectV1(gradlew);
case FlutterPluginVersion.v2:
return buildGradleProjectV2(gradlew, buildModeName);
}
}
Future<Null> buildGradleProjectV1(String gradlew) async {
// Run 'gradlew build'. // Run 'gradlew build'.
Status status = logger.startProgress('Running \'gradlew build\'...'); Status status = logger.startProgress('Running \'gradlew build\'...');
int exitcode = await runCommandAndStreamOutput( int exitcode = await runCommandAndStreamOutput(
<String>[fs.file('android/gradlew').absolute.path, 'build'], <String>[fs.file(gradlew).absolute.path, 'build'],
workingDirectory: 'android', workingDirectory: 'android',
allowReentrantFlutter: true allowReentrantFlutter: true
); );
@ -161,8 +215,34 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
if (exitcode != 0) if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode); throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
File apkFile = fs.file(gradleAppOut); File apkFile = fs.file(gradleAppOutV1);
printStatus('Built $gradleAppOut (${getSizeAsMB(apkFile.lengthSync())}).'); printStatus('Built $gradleAppOutV1 (${getSizeAsMB(apkFile.lengthSync())}).');
}
Future<Null> buildGradleProjectV2(String gradlew, String buildModeName) async {
String assembleTask = "assemble${toTitleCase(buildModeName)}";
// Run 'gradlew assemble<BuildMode>'.
Status status = logger.startProgress('Running \'gradlew $assembleTask\'...');
String gradlewPath = fs.file(gradlew).absolute.path;
List<String> command = logger.isVerbose
? <String>[gradlewPath, assembleTask]
: <String>[gradlewPath, '-q', assembleTask];
int exitcode = await runCommandAndStreamOutput(
command,
workingDirectory: 'android',
allowReentrantFlutter: true
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
String apkFilename = 'app-$buildModeName.apk';
File apkFile = fs.file('$gradleAppOutDir/$apkFilename');
// Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
apkFile.copySync('$gradleAppOutDir/app.apk');
printStatus('Built $apkFilename (${getSizeAsMB(apkFile.lengthSync())}).');
} }
class _GradleFile { class _GradleFile {

View file

@ -227,21 +227,20 @@ class BuildApkCommand extends BuildSubCommand {
await super.runCommand(); await super.runCommand();
TargetPlatform targetPlatform = _getTargetPlatform(argResults['target-arch']); TargetPlatform targetPlatform = _getTargetPlatform(argResults['target-arch']);
if (targetPlatform != TargetPlatform.android_arm && getBuildMode() != BuildMode.debug) BuildMode buildMode = getBuildMode();
if (targetPlatform != TargetPlatform.android_arm && buildMode != BuildMode.debug)
throwToolExit('Profile and release builds are only supported on ARM targets.'); throwToolExit('Profile and release builds are only supported on ARM targets.');
if (isProjectUsingGradle()) { if (isProjectUsingGradle()) {
if (targetPlatform != TargetPlatform.android_arm)
throwToolExit('Gradle builds only support ARM targets.');
await buildAndroidWithGradle( await buildAndroidWithGradle(
TargetPlatform.android_arm, targetPlatform,
getBuildMode(), buildMode,
target: targetFile target: targetFile
); );
} else { } else {
await buildAndroid( await buildAndroid(
targetPlatform, targetPlatform,
getBuildMode(), buildMode,
force: true, force: true,
manifest: argResults['manifest'], manifest: argResults['manifest'],
resources: argResults['resources'], resources: argResults['resources'],
@ -595,6 +594,9 @@ Future<Null> buildAndroidWithGradle(
bool force: false, bool force: false,
String target String target
}) async { }) async {
if (platform != TargetPlatform.android_arm && buildMode != BuildMode.debug) {
throwToolExit('Profile and release builds are only supported on ARM targets.');
}
// Validate that we can find an android sdk. // Validate that we can find an android sdk.
if (androidSdk == null) if (androidSdk == null)
throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.'); throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');