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.
This commit is contained in:
Jakob Andersen 2017-05-05 14:53:51 +02:00 committed by GitHub
parent 4bbf158b6a
commit 6b54137a15
4 changed files with 126 additions and 69 deletions

View file

@ -194,7 +194,20 @@ class FlutterPlugin implements Plugin<Project> {
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 @@ class FlutterExtension {
String target
}
class FlutterTask extends DefaultTask {
abstract class BaseFlutterTask extends DefaultTask {
File flutterRoot
File flutterExecutable
String buildMode
@ -232,67 +245,18 @@ class FlutterTask extends DefaultTask {
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 FlutterTask extends DefaultTask {
}
}
}
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()
}
}

View file

@ -78,7 +78,7 @@ String getGradleAppOutDirV2() {
// 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 @@ String ensureGradle() {
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();

View file

@ -151,6 +151,8 @@ class ItemListNotifier<T> {
}
class SettingsFile {
SettingsFile();
SettingsFile.parse(String contents) {
for (String line in contents.split('\n')) {
line = line.trim();

View file

@ -6,6 +6,7 @@ import 'dart:async';
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 @@ class CreateCommand extends FlutterCommand {
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 @@ class CreateCommand extends FlutterCommand {
injectPlugins(directory: appPath);
}
if (android_sdk.androidSdk != null)
gradle.updateLocalProperties(projectPath: appPath);
printStatus('');
// Run doctor; tell the user the next steps.