Commit a0f0c42f authored by Jakob Andersen's avatar Jakob Andersen Committed by GitHub

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
parent 4cace66d
...@@ -2,8 +2,8 @@ apply plugin: 'com.android.application' ...@@ -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 { ...@@ -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 { ...@@ -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'
} }
...@@ -2,10 +2,12 @@ ...@@ -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 ...@@ -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")
} }
project.dependencies {
compile project.files(flutterJar)
}
} else { } else {
// TODO(abarth): Support x64 and x86 in addition to arm. Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
String artifactType = "unknown"; File debugFlutterJar = baseEnginePath.resolve("android-arm").resolve("flutter.jar").toFile()
if (buildMode == "debug") { File profileFlutterJar = baseEnginePath.resolve("android-arm-profile").resolve("flutter.jar").toFile()
artifactType = "android-arm" File releaseFlutterJar = baseEnginePath.resolve("android-arm-release").resolve("flutter.jar").toFile()
} else if (buildMode == "profile") { if (!debugFlutterJar.isFile()) {
artifactType = "android-arm-profile"
} else if (buildMode == "release") {
artifactType = "android-arm-release"
}
flutterJar = new File(flutterRoot, Joiner.on(File.separatorChar).join(
"bin", "cache", "artifacts", "engine", artifactType, "flutter.jar"))
if (!flutterJar.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> { ...@@ -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
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 flutterRoot this.flutterRoot
buildMode this.buildMode buildMode variant.name
localEngine this.localEngine localEngine this.localEngine
targetPath target targetPath target
sourceDir project.file(project.flutter.source) sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter") intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
} }
project.android.applicationVariants.all { variant ->
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 { ...@@ -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 { ...@@ -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()) {
......
implementation-class=org.domokit.sky.gradle.FlutterPlugin implementation-class=io.flutter.gradle.FlutterPlugin
...@@ -274,6 +274,11 @@ class AndroidDevice extends Device { ...@@ -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);
......
...@@ -18,12 +18,44 @@ import '../globals.dart'; ...@@ -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,22 +123,7 @@ String locateProjectGradlew({ bool ensureExecutable: true }) { ...@@ -91,22 +123,7 @@ 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) {
...@@ -130,10 +147,13 @@ Future<Null> buildGradleProject(BuildMode buildMode) async { ...@@ -130,10 +147,13 @@ 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
); );
...@@ -149,10 +169,67 @@ Future<Null> buildGradleProject(BuildMode buildMode) async { ...@@ -149,10 +169,67 @@ 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',
allowReentrantFlutter: true
);
status.stop();
if (exitcode != 0)
throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
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', workingDirectory: 'android',
allowReentrantFlutter: true allowReentrantFlutter: true
); );
...@@ -161,8 +238,11 @@ Future<Null> buildGradleProject(BuildMode buildMode) async { ...@@ -161,8 +238,11 @@ 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); String apkFilename = 'app-$buildModeName.apk';
printStatus('Built $gradleAppOut (${getSizeAsMB(apkFile.lengthSync())}).'); 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 {
......
...@@ -227,21 +227,20 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -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( ...@@ -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.');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment