Fix --build-shared-library on newer NDKs (#26642)
Newer NDKs switched to clang which by default uses system linker, instead
we need to force it to use appropriate toolchain linker by passing
-fuse-ld= command line flag.
Fixes #23458
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index 7ffd46f..39f90f5 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -139,7 +139,8 @@
return ndkDirectory;
}
- String findCompiler(String ndkDirectory) {
+ // Returns list that contains toolchain bin folder and compiler binary name.
+ List<String> findToolchainAndCompiler(String ndkDirectory) {
String directory;
if (platform.isLinux) {
directory = 'linux-x86_64';
@@ -149,14 +150,16 @@
throw AndroidNdkSearchError('Only Linux and macOS are supported');
}
- final String ndkCompiler = fs.path.join(ndkDirectory,
+ final String toolchainBin = fs.path.join(ndkDirectory,
'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
- 'bin', 'arm-linux-androideabi-gcc');
+ 'bin');
+ final String ndkCompiler = fs.path.join(toolchainBin,
+ 'arm-linux-androideabi-gcc');
if (!fs.isFileSync(ndkCompiler)) {
throw AndroidNdkSearchError('Can not locate GCC binary, tried $ndkCompiler');
}
- return ndkCompiler;
+ return <String>[toolchainBin, ndkCompiler];
}
List<String> findSysroot(String ndkDirectory) {
@@ -202,9 +205,48 @@
return <String>['--sysroot', armPlatform];
}
+ int findNdkMajorVersion(String ndkDirectory) {
+ final String propertiesFile = fs.path.join(ndkDirectory, 'source.properties');
+ if (!fs.isFileSync(propertiesFile)) {
+ throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile not found');
+ }
+
+ // Parse source.properties: each line has Key = Value format.
+ final Iterable<String> propertiesFileLines = fs.file(propertiesFile)
+ .readAsStringSync()
+ .split('\n')
+ .map<String>((String line) => line.trim())
+ .where((String line) => line.isNotEmpty);
+ final Map<String, String> properties = Map<String, String>.fromIterable(
+ propertiesFileLines.map<List<String>>((String line) => line.split(' = ')),
+ key: (dynamic split) => split[0],
+ value: (dynamic split) => split[1]);
+
+ if (!properties.containsKey('Pkg.Revision')) {
+ throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile does not contain Pkg.Revision');
+ }
+
+ // Extract major version from Pkg.Revision property which looks like <ndk-version>.x.y.
+ return int.parse(properties['Pkg.Revision'].split('.').first);
+ }
+
final String ndkDir = findBundle(androidHomeDir);
- final String ndkCompiler = findCompiler(ndkDir);
+ final int ndkVersion = findNdkMajorVersion(ndkDir);
+ final List<String> ndkToolchainAndCompiler = findToolchainAndCompiler(ndkDir);
+ final String ndkToolchain = ndkToolchainAndCompiler[0];
+ final String ndkCompiler = ndkToolchainAndCompiler[1];
final List<String> ndkCompilerArgs = findSysroot(ndkDir);
+ if (ndkVersion >= 18) {
+ // Newer versions of NDK use clang instead of gcc, which falls back to
+ // system linker instead of using toolchain linker. Force clang to
+ // use appropriate linker by passing -fuse-ld=<path-to-ld> command line
+ // flag.
+ final String ndkLinker = fs.path.join(ndkToolchain, 'arm-linux-androideabi-ld');
+ if (!fs.isFileSync(ndkLinker)) {
+ throw AndroidNdkSearchError('Can not locate linker binary, tried $ndkLinker');
+ }
+ ndkCompilerArgs.add('-fuse-ld=$ndkLinker');
+ }
return AndroidNdk._(ndkDir, ndkCompiler, ndkCompilerArgs);
}
diff --git a/packages/flutter_tools/test/android/android_sdk_test.dart b/packages/flutter_tools/test/android/android_sdk_test.dart
index a84813b..9ef1711 100644
--- a/packages/flutter_tools/test/android/android_sdk_test.dart
+++ b/packages/flutter_tools/test/android/android_sdk_test.dart
@@ -160,6 +160,40 @@
FileSystem: () => fs,
Platform: () => FakePlatform(operatingSystem: os),
});
+
+ testUsingContext('newer NDK require explicit -fuse-ld on $os', () {
+ sdkDir = MockAndroidSdk.createSdkDirectory(
+ withAndroidN: true, withNdkDir: osDir, withNdkSysroot: true, ndkVersion: 18);
+ Config.instance.setValue('android-sdk', sdkDir.path);
+
+ final String realSdkDir = sdkDir.path;
+ final String realNdkDir = fs.path.join(realSdkDir, 'ndk-bundle');
+ final String realNdkToolchainBin = fs.path.join(
+ realNdkDir,
+ 'toolchains',
+ 'arm-linux-androideabi-4.9',
+ 'prebuilt',
+ osDir,
+ 'bin');
+ final String realNdkCompiler = fs.path.join(
+ realNdkToolchainBin,
+ 'arm-linux-androideabi-gcc');
+ final String realNdkLinker = fs.path.join(
+ realNdkToolchainBin,
+ 'arm-linux-androideabi-ld');
+ final String realNdkSysroot =
+ fs.path.join(realNdkDir, 'platforms', 'android-9', 'arch-arm');
+
+ final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
+ expect(sdk.directory, realSdkDir);
+ expect(sdk.ndk, isNotNull);
+ expect(sdk.ndk.directory, realNdkDir);
+ expect(sdk.ndk.compiler, realNdkCompiler);
+ expect(sdk.ndk.compilerArgs, <String>['--sysroot', realNdkSysroot, '-fuse-ld=$realNdkLinker']);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ Platform: () => FakePlatform(operatingSystem: os),
+ });
});
for (String os in <String>['linux', 'macos']) {
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 1ce3b7f..58ba611 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -41,6 +41,7 @@
static Directory createSdkDirectory({
bool withAndroidN = false,
String withNdkDir,
+ int ndkVersion = 16,
bool withNdkSysroot = false,
bool withSdkManager = true,
}) {
@@ -65,16 +66,29 @@
_createSdkFile(dir, 'tools/bin/sdkmanager');
if (withNdkDir != null) {
- final String ndkCompiler = fs.path.join(
+ final String ndkToolchainBin = fs.path.join(
'ndk-bundle',
'toolchains',
'arm-linux-androideabi-4.9',
'prebuilt',
withNdkDir,
'bin',
+ );
+ final String ndkCompiler = fs.path.join(
+ ndkToolchainBin,
'arm-linux-androideabi-gcc',
);
+ final String ndkLinker = fs.path.join(
+ ndkToolchainBin,
+ 'arm-linux-androideabi-ld',
+ );
_createSdkFile(dir, ndkCompiler);
+ _createSdkFile(dir, ndkLinker);
+ _createSdkFile(dir, fs.path.join('ndk-bundle', 'source.properties'), contents: '''
+Pkg.Desc = Android NDK[]
+Pkg.Revision = $ndkVersion.1.5063045
+
+''');
}
if (withNdkSysroot) {
final String armPlatform = fs.path.join(