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'
android {
compileSdkVersion 22
buildToolsVersion '22.0.1'
compileSdkVersion 25
buildToolsVersion '24.0.2'
lintOptions {
disable 'InvalidPackage'
@ -12,6 +12,14 @@ android {
defaultConfig {
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 {
@ -19,7 +27,7 @@ flutter {
}
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:rules:0.5'
}

View file

@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// 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.google.common.base.Joiner
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
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.FileCollection
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.TaskAction
import org.gradle.api.tasks.bundling.Jar
class FlutterPlugin implements Plugin<Project> {
private File flutterRoot
private String buildMode
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
void apply(Project project) {
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
// Add a 'profile' build type
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) {
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)
if (!flutterRoot.isDirectory()) {
throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
}
buildMode = properties.getProperty("flutter.buildMode")
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")
String flutterJarPath = localProperties.getProperty("flutter.jar")
if (flutterJarPath != null) {
flutterJar = project.file(flutterJarPath)
File flutterJar = project.file(flutterJarPath)
if (!flutterJar.isFile()) {
throw new GradleException("flutter.jar must point to a Flutter engine JAR")
}
} else {
// TODO(abarth): Support x64 and x86 in addition to arm.
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"
project.dependencies {
compile project.files(flutterJar)
}
flutterJar = new File(flutterRoot, Joiner.on(File.separatorChar).join(
"bin", "cache", "artifacts", "engine", artifactType, "flutter.jar"))
if (!flutterJar.isFile()) {
} else {
Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
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 {
executable "${flutterRoot}/bin/flutter"
args "precache"
}
if (!flutterJar.isFile()) {
throw new GradleException("Unable to find flutter.jar in SDK: ${flutterJar}")
if (!debugFlutterJar.isFile()) {
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.dependencies.add("compile", project.files(flutterJar))
project.afterEvaluate this.&addFlutterTask
}
@ -87,21 +120,27 @@ class FlutterPlugin implements Plugin<Project> {
throw new GradleException("Must provide Flutter source directory")
}
String target = project.flutter.target;
String target = project.flutter.target
if (target == null) {
target = 'lib/main.dart'
}
FlutterTask flutterTask = project.tasks.create("flutterBuild", FlutterTask) {
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.compileDebugJavaWithJavac.dependsOn project.flutterBuildX86Jar
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) {
dependsOn flutterTask
dependsOn variant.mergeAssets
@ -124,7 +163,6 @@ class FlutterTask extends DefaultTask {
String localEngine
String targetPath
@InputDirectory
File sourceDir
@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
void build() {
if (!sourceDir.isDirectory()) {
@ -151,34 +194,34 @@ class FlutterTask extends DefaultTask {
intermediateDir.mkdirs()
if (buildMode != "debug") {
project.exec {
executable "${flutterRoot}/bin/flutter"
workingDir sourceDir
if (localEngine != null) {
args "--local-engine", localEngine
project.exec {
executable "${flutterRoot}/bin/flutter"
workingDir sourceDir
if (localEngine != null) {
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 {
executable "${flutterRoot}/bin/flutter"
workingDir sourceDir
if (localEngine != null) {
args "--local-engine", localEngine
args "--local-engine", localEngine
}
args "build", "flx"
args "--target", targetPath
args "--output-file", "${intermediateDir}/app.flx"
if (buildMode != "debug") {
args "--precompiled"
args "--precompiled"
} else {
args "--snapshot", "${intermediateDir}/snapshot_blob.bin"
args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
args "--snapshot", "${intermediateDir}/snapshot_blob.bin"
args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
}
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())
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.");
await stopApp(package);

View file

@ -18,12 +18,44 @@ import '../globals.dart';
import 'android_sdk.dart';
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() {
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 gradle = _locateSystemGradle();
if (ensureExecutable && gradle != null) {
@ -91,29 +123,14 @@ String locateProjectGradlew({ bool ensureExecutable: true }) {
}
}
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.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);
Future<String> ensureGradlew() async {
String gradlew = locateProjectGradlew();
if (gradlew == null) {
String gradle = locateSystemGradle();
if (gradle == null) {
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 {
printTrace('Using gradle from $gradle.');
@ -130,12 +147,15 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
}
// Run 'gradle wrapper'.
List<String> command = logger.isVerbose
? <String>[gradle, 'wrapper']
: <String>[gradle, '-q', 'wrapper'];
try {
Status status = logger.startProgress('Running \'gradle wrapper\'...');
int exitcode = await runCommandAndStreamOutput(
<String>[gradle, 'wrapper'],
workingDirectory: 'android',
allowReentrantFlutter: true
command,
workingDirectory: 'android',
allowReentrantFlutter: true
);
status.stop();
if (exitcode != 0)
@ -149,10 +169,44 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
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'.
Status status = logger.startProgress('Running \'gradlew build\'...');
int exitcode = await runCommandAndStreamOutput(
<String>[fs.file('android/gradlew').absolute.path, 'build'],
<String>[fs.file(gradlew).absolute.path, 'build'],
workingDirectory: 'android',
allowReentrantFlutter: true
);
@ -161,8 +215,34 @@ Future<Null> buildGradleProject(BuildMode buildMode) async {
if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
File apkFile = fs.file(gradleAppOut);
printStatus('Built $gradleAppOut (${getSizeAsMB(apkFile.lengthSync())}).');
File apkFile = fs.file(gradleAppOutV1);
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 {

View file

@ -227,21 +227,20 @@ class BuildApkCommand extends BuildSubCommand {
await super.runCommand();
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.');
if (isProjectUsingGradle()) {
if (targetPlatform != TargetPlatform.android_arm)
throwToolExit('Gradle builds only support ARM targets.');
await buildAndroidWithGradle(
TargetPlatform.android_arm,
getBuildMode(),
targetPlatform,
buildMode,
target: targetFile
);
} else {
await buildAndroid(
targetPlatform,
getBuildMode(),
buildMode,
force: true,
manifest: argResults['manifest'],
resources: argResults['resources'],
@ -595,6 +594,9 @@ Future<Null> buildAndroidWithGradle(
bool force: false,
String target
}) 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.
if (androidSdk == null)
throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');