From 4931b46772a260eeeb3a956b367d66b6f3fb2e43 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Mon, 14 May 2018 16:36:54 +0200 Subject: [PATCH] Make --build-shared-library more robust. (#17420) * Search for a suitable ARM sysroot instead of hardcoding it; * Add facility to explain why NDK was not found; --- .../lib/src/android/android_sdk.dart | 181 ++++++++++++------ .../lib/src/android/android_workflow.dart | 4 +- .../flutter_tools/lib/src/android/gradle.dart | 2 +- .../flutter_tools/lib/src/base/build.dart | 12 +- .../test/android/android_sdk_test.dart | 14 +- .../flutter_tools/test/base/build_test.dart | 2 +- 6 files changed, 144 insertions(+), 71 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart index a70503fd7c..cb8399c0ce 100644 --- a/packages/flutter_tools/lib/src/android/android_sdk.dart +++ b/packages/flutter_tools/lib/src/android/android_sdk.dart @@ -104,9 +104,122 @@ String getAvdPath() { ); } +class AndroidNdkSearchError { + AndroidNdkSearchError(this.reason); + + /// The message explaining why NDK was not found. + final String reason; +} + +class AndroidNdk { + AndroidNdk._(this.directory, this.compiler, this.compilerArgs); + + /// The path to the NDK. + final String directory; + + /// The path to the NDK compiler. + final String compiler; + + /// The mandatory arguments to the NDK compiler. + final List compilerArgs; + + /// Locate NDK within the given SDK or throw [AndroidNdkSearchError]. + static AndroidNdk locateNdk(String androidHomeDir) { + if (androidHomeDir == null) { + throw new AndroidNdkSearchError('Can not locate NDK because no SDK is found'); + } + + String findBundle(String androidHomeDir) { + final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle'); + if (!fs.isDirectorySync(ndkDirectory)) { + throw new AndroidNdkSearchError('Can not locate ndk-bundle, tried: $ndkDirectory'); + } + return ndkDirectory; + } + + String findCompiler(String ndkDirectory) { + String directory; + if (platform.isLinux) { + directory = 'linux-x86_64'; + } else if (platform.isMacOS) { + directory = 'darwin-x86_64'; + } else { + throw new AndroidNdkSearchError('Only Linux and macOS are supported'); + } + + final String ndkCompiler = fs.path.join(ndkDirectory, + 'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory, + 'bin', 'arm-linux-androideabi-gcc'); + if (!fs.isFileSync(ndkCompiler)) { + throw new AndroidNdkSearchError('Can not locate GCC binary, tried $ndkCompiler'); + } + + return ndkCompiler; + } + + List findSysroot(String ndkDirectory) { + // If entity represents directory with name android- that + // contains arch-arm subdirectory then returns version, otherwise + // returns null. + int toPlatformVersion(FileSystemEntity entry) { + if (entry is! Directory) { + return null; + } + + if (!fs.isDirectorySync(fs.path.join(entry.path, 'arch-arm'))) { + return null; + } + + final String name = fs.path.basename(entry.path); + + const String platformPrefix = 'android-'; + if (!name.startsWith(platformPrefix)) { + return null; + } + + return int.tryParse(name.substring(platformPrefix.length)); + } + + final String platformsDir = fs.path.join(ndkDirectory, 'platforms'); + final List versions = fs + .directory(platformsDir) + .listSync() + .map(toPlatformVersion) + .where((int version) => version != null) + .toList(growable: false); + versions.sort(); + + final int suitableVersion = versions + .firstWhere((int version) => version >= 9, orElse: () => null); + if (suitableVersion == null) { + throw new AndroidNdkSearchError('Can not locate a suitable platform ARM sysroot (need android-9 or newer), tried to look in $platformsDir'); + } + + final String armPlatform = fs.path.join(ndkDirectory, 'platforms', + 'android-$suitableVersion', 'arch-arm'); + return ['--sysroot', armPlatform]; + } + + final String ndkDir = findBundle(androidHomeDir); + final String ndkCompiler = findCompiler(ndkDir); + final List ndkCompilerArgs = findSysroot(ndkDir); + return new AndroidNdk._(ndkDir, ndkCompiler, ndkCompilerArgs); + } + + /// Returns a descriptive message explaining why NDK can not be found within + /// the given SDK. + static String explainMissingNdk(String androidHomeDir) { + try { + locateNdk(androidHomeDir); + return 'Unexpected error: found NDK on the second try'; + } on AndroidNdkSearchError catch (e) { + return e.reason; + } + } +} + class AndroidSdk { - AndroidSdk(this.directory, [this.ndkDirectory, this.ndkCompiler, - this.ndkCompilerArgs]) { + AndroidSdk(this.directory, [this.ndk]) { _init(); } @@ -116,14 +229,8 @@ class AndroidSdk { /// 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 ndkCompilerArgs; + /// Android NDK (can be `null`). + final AndroidNdk ndk; List _sdkVersions; AndroidSdkVersion _latestVersion; @@ -176,41 +283,6 @@ class AndroidSdk { return null; } - String findNdk(String androidHomeDir) { - final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle'); - if (fs.isDirectorySync(ndkDirectory)) { - return ndkDirectory; - } - return null; - } - - 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; - } - - List computeNdkCompilerArgs(String ndkDirectory) { - final String armPlatform = fs.path.join(ndkDirectory, 'platforms', - 'android-9', 'arch-arm'); - if (fs.isDirectorySync(armPlatform)) { - return ['--sysroot', armPlatform]; - } - return null; - } - final String androidHomeDir = findAndroidHomeDir(); if (androidHomeDir == null) { // No dice. @@ -219,20 +291,15 @@ class AndroidSdk { } // Try to find the NDK compiler. If we can't find it, it's also ok. - final String ndkDir = findNdk(androidHomeDir); - String ndkCompiler; - List ndkCompilerArgs; - if (ndkDir != null) { - ndkCompiler = findNdkCompiler(ndkDir); - if (ndkCompiler != null) { - ndkCompilerArgs = computeNdkCompilerArgs(ndkDir); - if (ndkCompilerArgs == null) { - ndkCompiler = null; - } - } + AndroidNdk ndk; + try { + ndk = AndroidNdk.locateNdk(androidHomeDir); + } on AndroidNdkSearchError { + // Ignore AndroidNdkSearchError's but don't ignore any other + // exceptions. } - return new AndroidSdk(androidHomeDir, ndkDir, ndkCompiler, ndkCompilerArgs); + return new AndroidSdk(androidHomeDir, ndk); } 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 737eb1f8bd..0fa2ea3c31 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -100,9 +100,9 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}')); - messages.add(new ValidationMessage(androidSdk.ndkDirectory == null + messages.add(new ValidationMessage(androidSdk.ndk == null ? 'Android NDK location not configured (optional; useful for native profiling support)' - : 'Android NDK at ${androidSdk.ndkDirectory}')); + : 'Android NDK at ${androidSdk.ndk.directory}')); String sdkVersionText; if (androidSdk.latestVersion != null) { diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index d3f2e56662..c7084e35f0 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -379,7 +379,7 @@ Future _buildGradleProjectV2(String gradle, BuildInfo buildInfo, String ta if (buildInfo.fileSystemScheme != null) command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}'); } - if (buildInfo.preferSharedLibrary && androidSdk.ndkCompiler != null) { + if (buildInfo.preferSharedLibrary && androidSdk.ndk != null) { command.add('-Pprefer-shared-library=true'); } if (buildInfo.targetPlatform == TargetPlatform.android_arm64) diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index ec3919b73b..a5394c2a87 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -152,9 +152,13 @@ class AOTSnapshotter { if (platform == TargetPlatform.ios) buildSharedLibrary = false; - if (buildSharedLibrary && androidSdk.ndkCompiler == null) { + if (buildSharedLibrary && androidSdk.ndk == null) { + final String explanation = AndroidNdk.explainMissingNdk(androidSdk.directory); printError( - 'Could not find NDK in Android SDK at ${androidSdk.directory}.\n' + 'Could not find NDK in Android SDK at ${androidSdk.directory}:\n' + '\n' + ' $explanation\n' + '\n' 'Unable to build with --build-shared-library\n' 'To install the NDK, see instructions at https://developer.android.com/ndk/guides/' ); @@ -344,8 +348,8 @@ class AOTSnapshotter { // (which causes it to not look into the other section and therefore not // find the correct unwinding information). final String assemblySo = fs.path.join(outputPath, 'app.so'); - return await runCheckedAsync([androidSdk.ndkCompiler] - ..addAll(androidSdk.ndkCompilerArgs) + return await runCheckedAsync([androidSdk.ndk.compiler] + ..addAll(androidSdk.ndk.compilerArgs) ..addAll([ '-shared', '-nostdlib', '-o', assemblySo, assemblyPath ])); } diff --git a/packages/flutter_tools/test/android/android_sdk_test.dart b/packages/flutter_tools/test/android/android_sdk_test.dart index 9fb3016919..34a7769459 100644 --- a/packages/flutter_tools/test/android/android_sdk_test.dart +++ b/packages/flutter_tools/test/android/android_sdk_test.dart @@ -132,9 +132,11 @@ void main() { final AndroidSdk sdk = AndroidSdk.locateAndroidSdk(); expect(sdk.directory, realSdkDir); - expect(sdk.ndkDirectory, realNdkDir); - expect(sdk.ndkCompiler, realNdkCompiler); - expect(sdk.ndkCompilerArgs, ['--sysroot', realNdkSysroot]); + print(AndroidNdk.explainMissingNdk(sdk.directory)); + expect(sdk.ndk, isNotNull); + expect(sdk.ndk.directory, realNdkDir); + expect(sdk.ndk.compiler, realNdkCompiler); + expect(sdk.ndk.compilerArgs, ['--sysroot', realNdkSysroot]); }, overrides: { FileSystem: () => fs, Platform: () => new FakePlatform(operatingSystem: os), @@ -149,9 +151,9 @@ void main() { 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); + expect(sdk.ndk, isNull); + final String explanation = AndroidNdk.explainMissingNdk(sdk.directory); + expect(explanation, contains('Can not locate ndk-bundle')); }, overrides: { FileSystem: () => fs, Platform: () => new FakePlatform(operatingSystem: os), diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart index e2c6dd185a..7012112e62 100644 --- a/packages/flutter_tools/test/base/build_test.dart +++ b/packages/flutter_tools/test/base/build_test.dart @@ -659,7 +659,7 @@ void main() { testUsingContext('returns failure if buildSharedLibrary is true but no NDK is found', () async { final String outputPath = fs.path.join('build', 'foo'); - when(mockAndroidSdk.ndkCompiler).thenReturn(null); + when(mockAndroidSdk.ndk).thenReturn(null); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm,