Improve Android builds. (#9801)
Eagerly generate local.properties, and always update the flutter.sdk
setting in it, in case FLUTTER_ROOT has changed.
Fixes #8365.
Fixes #9716 - at least the specific issue reported. My Android Studio
still complains about Gradle versions - it ships with v3.2, but requires
v3.3...
Add a 'generate dependencies' task to the Gradle build, which checks if
the snapshot dependencies file exists, and runs an extra build before
the actual FlutterTask if it doesn't. This makes the first build slower,
but sub-sequent builds (without source changes) much faster.
Fixes #9717.
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 1fc3989..fdd4c3c 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -194,7 +194,20 @@
throw new GradleException("Build variant must be one of \"debug\", \"profile\", or \"release\" but was \"${variant.name}\"")
}
+ GenerateDependencies dependenciesTask = project.tasks.create("flutterDependencies${variant.name.capitalize()}", GenerateDependencies) {
+ flutterRoot this.flutterRoot
+ flutterExecutable this.flutterExecutable
+ buildMode variant.name
+ localEngine this.localEngine
+ localEngineSrcPath this.localEngineSrcPath
+ targetPath target
+ kernelFile kernel
+ sourceDir project.file(project.flutter.source)
+ intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
+ }
+
FlutterTask flutterTask = project.tasks.create("flutterBuild${variant.name.capitalize()}", FlutterTask) {
+ dependsOn dependenciesTask
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode variant.name
@@ -222,7 +235,7 @@
String target
}
-class FlutterTask extends DefaultTask {
+abstract class BaseFlutterTask extends DefaultTask {
File flutterRoot
File flutterExecutable
String buildMode
@@ -232,67 +245,18 @@
String targetPath
@Optional @InputFile
File kernelFile
-
File sourceDir
-
- @OutputDirectory
File intermediateDir
- CopySpec getAssets() {
- return project.copySpec {
- from "${intermediateDir}/app.flx"
- if (buildMode != 'debug') {
- from "${intermediateDir}/vm_snapshot_data"
- from "${intermediateDir}/vm_snapshot_instr"
- from "${intermediateDir}/isolate_snapshot_data"
- from "${intermediateDir}/isolate_snapshot_instr"
- }
- }
- }
-
- FileCollection readDependencies(File dependenciesFile) {
- if (dependenciesFile.exists()) {
- try {
- // Dependencies file has Makefile syntax:
- // <target> <files>: <source> <files> <separated> <by> <space>
- String depText = dependenciesFile.text
- return project.files(depText.split(': ')[1].split())
- } catch (Exception e) {
- logger.error("Error reading dependency file ${dependenciesFile}: ${e}")
- }
- }
- return null
- }
-
- @InputFiles
- FileCollection getSourceFiles() {
- File dependenciesFile
+ @OutputFile
+ File getDependenciesFile() {
if (buildMode != 'debug') {
- dependenciesFile = project.file("${intermediateDir}/snapshot.d")
- } else {
- dependenciesFile = project.file("${intermediateDir}/snapshot_blob.bin.d")
+ return project.file("${intermediateDir}/snapshot.d")
}
- FileCollection sources = readDependencies(dependenciesFile)
- if (sources != null) {
- // We have a dependencies file. Add a dependency on gen_snapshot as well, since the
- // snapshots have to be rebuilt if it changes.
- FileCollection snapshotter = readDependencies(project.file("${intermediateDir}/gen_snapshot.d"))
- if (snapshotter != null) {
- sources = sources.plus(snapshotter)
- }
- // Finally, add a dependency on pubspec.yaml as well.
- return sources.plus(project.files('pubspec.yaml'))
- }
- // No dependencies file (or problems parsing it). Fall back to source files.
- return project.fileTree(
- dir: sourceDir,
- exclude: ['android', 'ios'],
- include: ['**/*.dart', 'pubspec.yaml']
- )
+ return project.file("${intermediateDir}/snapshot_blob.bin.d")
}
- @TaskAction
- void build() {
+ void buildFlx() {
if (!sourceDir.isDirectory()) {
throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
}
@@ -339,3 +303,73 @@
}
}
}
+
+class GenerateDependencies extends BaseFlutterTask {
+ @TaskAction
+ void build() {
+ File dependenciesFile = getDependenciesFile();
+ if (!dependenciesFile.exists()) {
+ buildFlx()
+ }
+ }
+}
+
+class FlutterTask extends BaseFlutterTask {
+ @OutputDirectory
+ File getOutputDirectory() {
+ return intermediateDir
+ }
+
+ CopySpec getAssets() {
+ return project.copySpec {
+ from "${intermediateDir}/app.flx"
+ if (buildMode != 'debug') {
+ from "${intermediateDir}/vm_snapshot_data"
+ from "${intermediateDir}/vm_snapshot_instr"
+ from "${intermediateDir}/isolate_snapshot_data"
+ from "${intermediateDir}/isolate_snapshot_instr"
+ }
+ }
+ }
+
+ FileCollection readDependencies(File dependenciesFile) {
+ if (dependenciesFile.exists()) {
+ try {
+ // Dependencies file has Makefile syntax:
+ // <target> <files>: <source> <files> <separated> <by> <space>
+ String depText = dependenciesFile.text
+ return project.files(depText.split(': ')[1].split())
+ } catch (Exception e) {
+ logger.error("Error reading dependency file ${dependenciesFile}: ${e}")
+ }
+ }
+ return null
+ }
+
+ @InputFiles
+ FileCollection getSourceFiles() {
+ File dependenciesFile = getDependenciesFile()
+ FileCollection sources = readDependencies(dependenciesFile)
+ if (sources != null) {
+ // We have a dependencies file. Add a dependency on gen_snapshot as well, since the
+ // snapshots have to be rebuilt if it changes.
+ FileCollection snapshotter = readDependencies(project.file("${intermediateDir}/gen_snapshot.d"))
+ if (snapshotter != null) {
+ sources = sources.plus(snapshotter)
+ }
+ // Finally, add a dependency on pubspec.yaml as well.
+ return sources.plus(project.files('pubspec.yaml'))
+ }
+ // No dependencies file (or problems parsing it). Fall back to source files.
+ return project.fileTree(
+ dir: sourceDir,
+ exclude: ['android', 'ios'],
+ include: ['**/*.dart', 'pubspec.yaml']
+ )
+ }
+
+ @TaskAction
+ void build() {
+ buildFlx()
+ }
+}
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 3b17ed1..294b9e2 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -78,7 +78,7 @@
// Note: this call takes about a second to complete.
String _calculateGradleAppOutDirV2() {
final String gradle = ensureGradle();
- ensureLocalProperties();
+ updateLocalProperties();
try {
final String properties = runCheckedSync(
<String>[gradle, 'app:properties'],
@@ -141,28 +141,42 @@
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'
- );
+/// 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;
}
- return localProperties;
+ 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 {
- 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);
+ updateLocalProperties(buildMode: buildModeName);
injectPlugins();
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index 342c967..fa17fb3 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -151,6 +151,8 @@
}
class SettingsFile {
+ SettingsFile();
+
SettingsFile.parse(String contents) {
for (String line in contents.split('\n')) {
line = line.trim();
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 5d7e7a2..2e78dd3 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -6,6 +6,7 @@
import '../android/android.dart' as android;
import '../android/android_sdk.dart' as android_sdk;
+import '../android/gradle.dart' as gradle;
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
@@ -121,6 +122,9 @@
if (argResults['pub'])
await pubGet(directory: dirPath);
+ if (android_sdk.androidSdk != null)
+ gradle.updateLocalProperties(projectPath: dirPath);
+
appPath = fs.path.join(dirPath, 'example');
final String androidPluginIdentifier = templateContext['androidIdentifier'];
final String exampleProjectName = projectName + '_example';
@@ -153,6 +157,9 @@
injectPlugins(directory: appPath);
}
+ if (android_sdk.androidSdk != null)
+ gradle.updateLocalProperties(projectPath: appPath);
+
printStatus('');
// Run doctor; tell the user the next steps.