Add support for NDK discovery and add --prefer-shared-library option (#12788)
* Add support for NDK discovery and add --prefer-shared-library option
We would like to be able to use native tools (e.g. simpleperf, gdb) with
precompiled flutter apps. The native tools work much better with *.so
files instead of the custom formats the Dart VM uses by default.
The reason for using blobs / instruction snapshots is that we do not
want to force flutter users to install the Android NDK.
This CL adds a `--prefer-shared-library` flag to e.g. `flutter build
apk` which will use the NDK compiler (if available) to turn the
precompiled app assembly file to an `*.so` file. If the NDK compiler is
not available it will default to the default behavior.
* Rebase, add test for NDK detection, augment flutter.gradle with @Input for flag
* Use InMemoryFileSystem for test
* Remove unused import
* Address some analyzer warnings
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 80eeff1..036da77 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -235,6 +235,10 @@
if (project.hasProperty('extra-gen-snapshot-options')) {
extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
}
+ Boolean preferSharedLibraryValue = false
+ if (project.hasProperty('prefer-shared-library')) {
+ preferSharedLibraryValue = project.property('prefer-shared-library')
+ }
project.android.applicationVariants.all { variant ->
String flutterBuildMode = buildModeFor(variant.buildType)
@@ -253,6 +257,7 @@
localEngineSrcPath this.localEngineSrcPath
targetPath target
previewDart2 previewDart2Value
+ preferSharedLibrary preferSharedLibraryValue
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
}
@@ -266,6 +271,7 @@
localEngineSrcPath this.localEngineSrcPath
targetPath target
previewDart2 previewDart2Value
+ preferSharedLibrary preferSharedLibraryValue
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
extraFrontEndOptions extraFrontEndOptionsValue
@@ -298,6 +304,8 @@
String targetPath
@Optional @Input
Boolean previewDart2
+ @Optional @Input
+ Boolean preferSharedLibrary
File sourceDir
File intermediateDir
@Optional @Input
@@ -338,10 +346,13 @@
args "--preview-dart-2"
}
if (extraFrontEndOptions != null) {
- args "--extra-front-end-options", "${extraFrontEndOptions}"
+ args "--extra-front-end-options", "${extraFrontEndOptions}"
}
if (extraGenSnapshotOptions != null) {
- args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
+ args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
+ }
+ if (preferSharedLibrary) {
+ args "--prefer-shared-library"
}
args "--${buildMode}"
}
@@ -392,15 +403,19 @@
CopySpec getAssets() {
return project.copySpec {
- from "${intermediateDir}/app.flx"
- from "${intermediateDir}/snapshot_blob.bin"
+ from "${intermediateDir}/app.flx"
+ from "${intermediateDir}/snapshot_blob.bin"
if (buildMode != 'debug') {
+ if (preferSharedLibrary) {
+ from "${intermediateDir}/app.so"
+ } else {
from "${intermediateDir}/vm_snapshot_data"
from "${intermediateDir}/vm_snapshot_instr"
from "${intermediateDir}/isolate_snapshot_data"
from "${intermediateDir}/isolate_snapshot_instr"
+ }
}
- }
+ }
}
FileCollection readDependencies(File dependenciesFile) {
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index fb88234..a3946d5 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -57,63 +57,131 @@
}
class AndroidSdk {
- AndroidSdk(this.directory) {
+ AndroidSdk(this.directory, [this.ndkDirectory, this.ndkCompiler,
+ this.ndkCompilerArgs]) {
_init();
}
+ /// The path to the Android SDK.
final String directory;
+ /// The path to the NDK (can be `null`).
+ final String ndkDirectory;
+
+ /// The path to the NDK compiler (can be `null`).
+ final String ndkCompiler;
+
+ /// The mandatory arguments to the NDK compiler (can be `null`).
+ final List<String> ndkCompilerArgs;
+
List<AndroidSdkVersion> _sdkVersions;
AndroidSdkVersion _latestVersion;
static AndroidSdk locateAndroidSdk() {
- String androidHomeDir;
+ String findAndroidHomeDir() {
+ String androidHomeDir;
+ if (config.containsKey('android-sdk')) {
+ androidHomeDir = config.getValue('android-sdk');
+ } else if (platform.environment.containsKey(kAndroidHome)) {
+ androidHomeDir = platform.environment[kAndroidHome];
+ } else if (platform.isLinux) {
+ if (homeDirPath != null)
+ androidHomeDir = fs.path.join(homeDirPath, 'Android', 'Sdk');
+ } else if (platform.isMacOS) {
+ if (homeDirPath != null)
+ androidHomeDir = fs.path.join(homeDirPath, 'Library', 'Android', 'sdk');
+ } else if (platform.isWindows) {
+ if (homeDirPath != null)
+ androidHomeDir = fs.path.join(homeDirPath, 'AppData', 'Local', 'Android', 'sdk');
+ }
- if (config.containsKey('android-sdk')) {
- androidHomeDir = config.getValue('android-sdk');
- } else if (platform.environment.containsKey(kAndroidHome)) {
- androidHomeDir = platform.environment[kAndroidHome];
- } else if (platform.isLinux) {
- if (homeDirPath != null)
- androidHomeDir = fs.path.join(homeDirPath, 'Android', 'Sdk');
- } else if (platform.isMacOS) {
- if (homeDirPath != null)
- androidHomeDir = fs.path.join(homeDirPath, 'Library', 'Android', 'sdk');
- } else if (platform.isWindows) {
- if (homeDirPath != null)
- androidHomeDir = fs.path.join(homeDirPath, 'AppData', 'Local', 'Android', 'sdk');
+ if (androidHomeDir != null) {
+ if (validSdkDirectory(androidHomeDir))
+ return androidHomeDir;
+ if (validSdkDirectory(fs.path.join(androidHomeDir, 'sdk')))
+ return fs.path.join(androidHomeDir, 'sdk');
+ }
+
+ // in build-tools/$version/aapt
+ final List<File> aaptBins = os.whichAll('aapt');
+ for (File aaptBin in aaptBins) {
+ // Make sure we're using the aapt from the SDK.
+ aaptBin = fs.file(aaptBin.resolveSymbolicLinksSync());
+ final String dir = aaptBin.parent.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return dir;
+ }
+
+ // in platform-tools/adb
+ final List<File> adbBins = os.whichAll('adb');
+ for (File adbBin in adbBins) {
+ // Make sure we're using the adb from the SDK.
+ adbBin = fs.file(adbBin.resolveSymbolicLinksSync());
+ final String dir = adbBin.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return dir;
+ }
+
+ return null;
}
- if (androidHomeDir != null) {
- if (validSdkDirectory(androidHomeDir))
- return new AndroidSdk(androidHomeDir);
- if (validSdkDirectory(fs.path.join(androidHomeDir, 'sdk')))
- return new AndroidSdk(fs.path.join(androidHomeDir, 'sdk'));
+ String findNdk(String androidHomeDir) {
+ final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle');
+ if (fs.isDirectorySync(ndkDirectory)) {
+ return ndkDirectory;
+ }
+ return null;
}
- // in build-tools/$version/aapt
- final List<File> aaptBins = os.whichAll('aapt');
- for (File aaptBin in aaptBins) {
- // Make sure we're using the aapt from the SDK.
- aaptBin = fs.file(aaptBin.resolveSymbolicLinksSync());
- final String dir = aaptBin.parent.parent.parent.path;
- if (validSdkDirectory(dir))
- return new AndroidSdk(dir);
+ String findNdkCompiler(String ndkDirectory) {
+ String directory;
+ if (platform.isLinux) {
+ directory = 'linux-x86_64';
+ } else if (platform.isMacOS) {
+ directory = 'darwin-x86_64';
+ }
+ if (directory != null) {
+ final String ndkCompiler = fs.path.join(ndkDirectory,
+ 'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
+ 'bin', 'arm-linux-androideabi-gcc');
+ if (fs.isFileSync(ndkCompiler)) {
+ return ndkCompiler;
+ }
+ }
+ return null;
}
- // in platform-tools/adb
- final List<File> adbBins = os.whichAll('adb');
- for (File adbBin in adbBins) {
- // Make sure we're using the adb from the SDK.
- adbBin = fs.file(adbBin.resolveSymbolicLinksSync());
- final String dir = adbBin.parent.parent.path;
- if (validSdkDirectory(dir))
- return new AndroidSdk(dir);
+ List<String> computeNdkCompilerArgs(String ndkDirectory) {
+ final String armPlatform = fs.path.join(ndkDirectory, 'platforms',
+ 'android-9', 'arch-arm');
+ if (fs.isDirectorySync(armPlatform)) {
+ return <String>['--sysroot', armPlatform];
+ }
+ return null;
}
- // No dice.
- printTrace('Unable to locate an Android SDK.');
- return null;
+ final String androidHomeDir = findAndroidHomeDir();
+ if (androidHomeDir == null) {
+ // No dice.
+ printTrace('Unable to locate an Android SDK.');
+ return null;
+ }
+
+ // Try to find the NDK compiler. If we can't find it, it's also ok.
+ final String ndkDir = findNdk(androidHomeDir);
+ String ndkCompiler;
+ List<String> ndkCompilerArgs;
+ if (ndkDir != null) {
+ ndkCompiler = findNdkCompiler(ndkDir);
+ if (ndkCompiler != null) {
+ ndkCompilerArgs = computeNdkCompilerArgs(ndkDir);
+ if (ndkCompilerArgs == null) {
+ ndkCompiler = null;
+ }
+ }
+ }
+
+ return new AndroidSdk(androidHomeDir, ndkDir, ndkCompiler, ndkCompilerArgs);
}
static bool validSdkDirectory(String dir) {
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 1fc76ae..99d1f6f 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -119,6 +119,14 @@
messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
+ messages.add(new ValidationMessage(androidSdk.ndkDirectory == null
+ ? 'Unable to locate Android NDK.\n'
+ : 'Android NDK at ${androidSdk.ndkDirectory}'));
+
+ messages.add(new ValidationMessage(androidSdk.ndkCompiler == null
+ ? 'Unable to locate compiler in Android NDK.\n'
+ : 'Compiler in Android NDK at ${androidSdk.ndkCompiler}'));
+
String sdkVersionText;
if (androidSdk.latestVersion != null) {
sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index bb930af..b3b5b74 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
@@ -289,12 +290,17 @@
if (target != null) {
command.add('-Ptarget=$target');
}
- if (buildInfo.previewDart2)
+ if (buildInfo.previewDart2) {
command.add('-Ppreview-dart-2=true');
if (buildInfo.extraFrontEndOptions != null)
command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
if (buildInfo.extraGenSnapshotOptions != null)
command.add('-Pextra-gen-snapshot-options=${buildInfo.extraGenSnapshotOptions}');
+ }
+ if (buildInfo.preferSharedLibrary && androidSdk.ndkCompiler != null) {
+ command.add('-Pprefer-shared-library=true');
+ }
+
command.add(assembleTask);
final int exitCode = await runCommandAndStreamOutput(
command,
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 3454552..154a2ec 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -13,7 +13,8 @@
const BuildInfo(this.mode, this.flavor,
{this.previewDart2,
this.extraFrontEndOptions,
- this.extraGenSnapshotOptions});
+ this.extraGenSnapshotOptions,
+ this.preferSharedLibrary});
final BuildMode mode;
/// Represents a custom Android product flavor or an Xcode scheme, null for
@@ -33,6 +34,9 @@
/// Extra command-line options for gen_snapshot.
final String extraGenSnapshotOptions;
+ // Whether to prefer AOT compiling to a *so file.
+ final bool preferSharedLibrary;
+
static const BuildInfo debug = const BuildInfo(BuildMode.debug, null);
static const BuildInfo profile = const BuildInfo(BuildMode.profile, null);
static const BuildInfo release = const BuildInfo(BuildMode.release, null);
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index d2b1510..4339f52 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../base/build.dart';
import '../base/common.dart';
@@ -48,7 +49,9 @@
allowMultiple: true,
splitCommas: true,
hide: true,
- );
+ )
+ ..addFlag('prefer-shared-library', negatable: false,
+ help: 'Whether to prefer compiling to a *.so file (android only).');
}
@override
@@ -80,6 +83,7 @@
previewDart2: argResults['preview-dart-2'],
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
+ preferSharedLibrary: argResults['prefer-shared-library'],
);
status?.stop();
@@ -110,6 +114,7 @@
bool previewDart2: false,
List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions,
+ bool preferSharedLibrary: false,
}) async {
outputPath ??= getAotBuildDirectory();
try {
@@ -122,6 +127,7 @@
previewDart2: previewDart2,
extraFrontEndOptions: extraFrontEndOptions,
extraGenSnapshotOptions: extraGenSnapshotOptions,
+ preferSharedLibrary: preferSharedLibrary,
);
} on String catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below.
@@ -140,6 +146,7 @@
bool previewDart2: false,
List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions,
+ bool preferSharedLibrary: false,
}) async {
outputPath ??= getAotBuildDirectory();
if (!isAotBuildMode(buildMode) && !interpreter) {
@@ -161,6 +168,16 @@
final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
final String dependencies = fs.path.join(outputDir.path, 'snapshot.d');
+ final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
+ final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
+ final String assemblySo = fs.path.join(outputDir.path, 'app.so');
+ final bool compileToSharedLibrary =
+ preferSharedLibrary && androidSdk.ndkCompiler != null;
+
+ if (preferSharedLibrary && !compileToSharedLibrary) {
+ printStatus(
+ 'Could not find NDK compiler. Not building in shared library mode');
+ }
final String vmEntryPoints = artifacts.getArtifactPath(
Artifact.dartVmEntryPointsTxt,
@@ -192,20 +209,22 @@
// These paths are used only on iOS.
String snapshotDartIOS;
- String assembly;
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
- outputPaths.addAll(<String>[
- vmSnapshotData,
- isolateSnapshotData,
- ]);
+ if (compileToSharedLibrary) {
+ outputPaths.add(assemblySo);
+ } else {
+ outputPaths.addAll(<String>[
+ vmSnapshotData,
+ isolateSnapshotData,
+ ]);
+ }
break;
case TargetPlatform.ios:
snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
- assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
inputPaths.add(snapshotDartIOS);
break;
case TargetPlatform.darwin_x64:
@@ -260,16 +279,23 @@
final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
- final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
+ if (compileToSharedLibrary) {
+ genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
+ genSnapshotCmd.add('--assembly=$assembly');
+ outputPaths.add(assemblySo);
+ } else {
+ genSnapshotCmd.addAll(<String>[
+ '--snapshot_kind=app-aot-blobs',
+ '--vm_snapshot_instructions=$vmSnapshotInstructions',
+ '--isolate_snapshot_instructions=$isolateSnapshotInstructions',
+ ]);
+ }
genSnapshotCmd.addAll(<String>[
- '--snapshot_kind=app-aot-blobs',
- '--vm_snapshot_instructions=$vmSnapshotInstructions',
- '--isolate_snapshot_instructions=$isolateSnapshotInstructions',
'--no-sim-use-hardfp', // Android uses the softfloat ABI.
'--no-use-integer-division', // Not supported by the Pixel in 32-bit mode.
]);
@@ -396,6 +422,19 @@
linkCommand.add(assemblyO);
}
await runCheckedAsync(linkCommand);
+ } else {
+ if (compileToSharedLibrary) {
+ // A word of warning: Instead of compiling via two steps, to a .o file and
+ // then to a .so file we use only one command. When using two commands
+ // gcc will end up putting a .eh_frame and a .debug_frame into the shared
+ // library. Without stripping .debug_frame afterwards, unwinding tools
+ // based upon libunwind use just one and ignore the contents of the other
+ // (which causes it to not look into the other section and therefore not
+ // find the correct unwinding information).
+ await runCheckedAsync(<String>[androidSdk.ndkCompiler]
+ ..addAll(androidSdk.ndkCompilerArgs)
+ ..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assembly ]));
+ }
}
// Compute and record build fingerprint.
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index fc9d97f..ff53ab7 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -11,9 +11,13 @@
BuildApkCommand() {
usesTargetOption();
addBuildModeFlags();
- argParser.addFlag('preview-dart-2', negatable: false);
usesFlavorOption();
usesPubOption();
+
+ argParser
+ ..addFlag('preview-dart-2', negatable: false)
+ ..addFlag('prefer-shared-library', negatable: false,
+ help: 'Whether to prefer compiling to a *.so file (android only).');
}
@override
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index a61beed..ca37fd7 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -161,7 +161,10 @@
: null,
extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions)
? argResults[FlutterOptions.kExtraGenSnapshotOptions]
- : null);
+ : null,
+ preferSharedLibrary: argParser.options.containsKey('prefer-shared-library')
+ ? argResults['prefer-shared-library']
+ : false);
}
void setupApplicationPackages() {
diff --git a/packages/flutter_tools/test/android/android_sdk_test.dart b/packages/flutter_tools/test/android/android_sdk_test.dart
index aa77b22..76f64e0 100644
--- a/packages/flutter_tools/test/android/android_sdk_test.dart
+++ b/packages/flutter_tools/test/android/android_sdk_test.dart
@@ -5,6 +5,8 @@
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/config.dart';
import 'package:test/test.dart';
import '../src/context.dart';
@@ -21,12 +23,14 @@
tearDown(() {
sdkDir?.deleteSync(recursive: true);
+ sdkDir = null;
});
testUsingContext('parse sdk', () {
sdkDir = _createSdkDirectory();
- final AndroidSdk sdk = new AndroidSdk(sdkDir.path);
+ Config.instance.setValue('android-sdk', sdkDir.path);
+ final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
expect(sdk.latestVersion, isNotNull);
expect(sdk.latestVersion.sdkLevel, 23);
}, overrides: <Type, Generator>{
@@ -35,17 +39,71 @@
testUsingContext('parse sdk N', () {
sdkDir = _createSdkDirectory(withAndroidN: true);
- final AndroidSdk sdk = new AndroidSdk(sdkDir.path);
+ Config.instance.setValue('android-sdk', sdkDir.path);
+ final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
expect(sdk.latestVersion, isNotNull);
expect(sdk.latestVersion.sdkLevel, 24);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
+
+ group('ndk', () {
+ const <String, String>{
+ 'linux': 'linux-x86_64',
+ 'macos': 'darwin-x86_64',
+ }.forEach((String os, String osDir) {
+ testUsingContext('detection on $os', () {
+ sdkDir = _createSdkDirectory(
+ withAndroidN: true, withNdkDir: osDir, withNdkSysroot: true);
+ Config.instance.setValue('android-sdk', sdkDir.path);
+
+ final String realSdkDir = sdkDir.path;
+ final String realNdkDir = fs.path.join(realSdkDir, 'ndk-bundle');
+ final String realNdkCompiler = fs.path.join(
+ realNdkDir,
+ 'toolchains',
+ 'arm-linux-androideabi-4.9',
+ 'prebuilt',
+ osDir,
+ 'bin',
+ 'arm-linux-androideabi-gcc');
+ final String realNdkSysroot =
+ fs.path.join(realNdkDir, 'platforms', 'android-9', 'arch-arm');
+
+ final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
+ expect(sdk.directory, realSdkDir);
+ expect(sdk.ndkDirectory, realNdkDir);
+ expect(sdk.ndkCompiler, realNdkCompiler);
+ expect(sdk.ndkCompilerArgs, <String>['--sysroot', realNdkSysroot]);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: () => new FakePlatform(operatingSystem: os),
+ });
+ });
+
+ for (String os in <String>['linux', 'macos']) {
+ testUsingContext('detection on $os (no ndk available)', () {
+ sdkDir = _createSdkDirectory(withAndroidN: true);
+ Config.instance.setValue('android-sdk', sdkDir.path);
+
+ final String realSdkDir = sdkDir.path;
+ final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
+ expect(sdk.directory, realSdkDir);
+ expect(sdk.ndkDirectory, null);
+ expect(sdk.ndkCompiler, null);
+ expect(sdk.ndkCompilerArgs, null);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: () => new FakePlatform(operatingSystem: os),
+ });
+ }
+ });
});
}
-Directory _createSdkDirectory({ bool withAndroidN: false }) {
+Directory _createSdkDirectory(
+ {bool withAndroidN: false, String withNdkDir, bool withNdkSysroot: false}) {
final Directory dir = fs.systemTempDirectory.createTempSync('android-sdk');
_createSdkFile(dir, 'platform-tools/adb');
@@ -63,6 +121,23 @@
_createSdkFile(dir, 'platforms/android-N/build.prop', contents: _buildProp);
}
+ if (withNdkDir != null) {
+ final String ndkCompiler = fs.path.join(
+ 'ndk-bundle',
+ 'toolchains',
+ 'arm-linux-androideabi-4.9',
+ 'prebuilt',
+ withNdkDir,
+ 'bin',
+ 'arm-linux-androideabi-gcc');
+ _createSdkFile(dir, ndkCompiler);
+ }
+ if (withNdkSysroot) {
+ final String armPlatform =
+ fs.path.join('ndk-bundle', 'platforms', 'android-9', 'arch-arm');
+ _createDir(dir, armPlatform);
+ }
+
return dir;
}
@@ -74,6 +149,11 @@
}
}
+void _createDir(Directory dir, String path) {
+ final Directory directory = fs.directory(fs.path.join(dir.path, path));
+ directory.createSync(recursive: true);
+}
+
const String _buildProp = r'''
ro.build.version.incremental=1624448
ro.build.version.sdk=24
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index 8913488..f75f101 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -70,6 +70,21 @@
ContextInitializer initializeContext: _defaultInitializeContext,
bool skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
}) {
+
+ // Ensure we don't rely on the default [Config] constructor which will
+ // leak a sticky $HOME/.flutter_settings behind!
+ Directory configDir;
+ tearDown(() {
+ configDir?.deleteSync(recursive: true);
+ configDir = null;
+ });
+ Config buildConfig(FileSystem fs) {
+ configDir = fs.systemTempDirectory.createTempSync('config-dir');
+ final File settingsFile = fs.file(
+ fs.path.join(configDir.path, '.flutter_settings'));
+ return new Config(settingsFile);
+ }
+
test(description, () async {
final AppContext testContext = new AppContext();
@@ -80,7 +95,7 @@
..putIfAbsent(FileSystem, () => const LocalFileSystem())
..putIfAbsent(ProcessManager, () => const LocalProcessManager())
..putIfAbsent(Logger, () => new BufferLogger())
- ..putIfAbsent(Config, () => new Config());
+ ..putIfAbsent(Config, () => buildConfig(testContext[FileSystem]));
// Apply the initializer after seeding the base value above.
initializeContext(testContext);