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);