Skip to content

Commit 4931b46

Browse files
authored
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;
1 parent f9bb428 commit 4931b46

6 files changed

Lines changed: 144 additions & 71 deletions

File tree

‎packages/flutter_tools/lib/src/android/android_sdk.dart‎

Lines changed: 124 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,122 @@ String getAvdPath() {
104104
);
105105
}
106106

107+
class AndroidNdkSearchError {
108+
AndroidNdkSearchError(this.reason);
109+
110+
/// The message explaining why NDK was not found.
111+
final String reason;
112+
}
113+
114+
class AndroidNdk {
115+
AndroidNdk._(this.directory, this.compiler, this.compilerArgs);
116+
117+
/// The path to the NDK.
118+
final String directory;
119+
120+
/// The path to the NDK compiler.
121+
final String compiler;
122+
123+
/// The mandatory arguments to the NDK compiler.
124+
final List<String> compilerArgs;
125+
126+
/// Locate NDK within the given SDK or throw [AndroidNdkSearchError].
127+
static AndroidNdk locateNdk(String androidHomeDir) {
128+
if (androidHomeDir == null) {
129+
throw new AndroidNdkSearchError('Can not locate NDK because no SDK is found');
130+
}
131+
132+
String findBundle(String androidHomeDir) {
133+
final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle');
134+
if (!fs.isDirectorySync(ndkDirectory)) {
135+
throw new AndroidNdkSearchError('Can not locate ndk-bundle, tried: $ndkDirectory');
136+
}
137+
return ndkDirectory;
138+
}
139+
140+
String findCompiler(String ndkDirectory) {
141+
String directory;
142+
if (platform.isLinux) {
143+
directory = 'linux-x86_64';
144+
} else if (platform.isMacOS) {
145+
directory = 'darwin-x86_64';
146+
} else {
147+
throw new AndroidNdkSearchError('Only Linux and macOS are supported');
148+
}
149+
150+
final String ndkCompiler = fs.path.join(ndkDirectory,
151+
'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
152+
'bin', 'arm-linux-androideabi-gcc');
153+
if (!fs.isFileSync(ndkCompiler)) {
154+
throw new AndroidNdkSearchError('Can not locate GCC binary, tried $ndkCompiler');
155+
}
156+
157+
return ndkCompiler;
158+
}
159+
160+
List<String> findSysroot(String ndkDirectory) {
161+
// If entity represents directory with name android-<version> that
162+
// contains arch-arm subdirectory then returns version, otherwise
163+
// returns null.
164+
int toPlatformVersion(FileSystemEntity entry) {
165+
if (entry is! Directory) {
166+
return null;
167+
}
168+
169+
if (!fs.isDirectorySync(fs.path.join(entry.path, 'arch-arm'))) {
170+
return null;
171+
}
172+
173+
final String name = fs.path.basename(entry.path);
174+
175+
const String platformPrefix = 'android-';
176+
if (!name.startsWith(platformPrefix)) {
177+
return null;
178+
}
179+
180+
return int.tryParse(name.substring(platformPrefix.length));
181+
}
182+
183+
final String platformsDir = fs.path.join(ndkDirectory, 'platforms');
184+
final List<int> versions = fs
185+
.directory(platformsDir)
186+
.listSync()
187+
.map(toPlatformVersion)
188+
.where((int version) => version != null)
189+
.toList(growable: false);
190+
versions.sort();
191+
192+
final int suitableVersion = versions
193+
.firstWhere((int version) => version >= 9, orElse: () => null);
194+
if (suitableVersion == null) {
195+
throw new AndroidNdkSearchError('Can not locate a suitable platform ARM sysroot (need android-9 or newer), tried to look in $platformsDir');
196+
}
197+
198+
final String armPlatform = fs.path.join(ndkDirectory, 'platforms',
199+
'android-$suitableVersion', 'arch-arm');
200+
return <String>['--sysroot', armPlatform];
201+
}
202+
203+
final String ndkDir = findBundle(androidHomeDir);
204+
final String ndkCompiler = findCompiler(ndkDir);
205+
final List<String> ndkCompilerArgs = findSysroot(ndkDir);
206+
return new AndroidNdk._(ndkDir, ndkCompiler, ndkCompilerArgs);
207+
}
208+
209+
/// Returns a descriptive message explaining why NDK can not be found within
210+
/// the given SDK.
211+
static String explainMissingNdk(String androidHomeDir) {
212+
try {
213+
locateNdk(androidHomeDir);
214+
return 'Unexpected error: found NDK on the second try';
215+
} on AndroidNdkSearchError catch (e) {
216+
return e.reason;
217+
}
218+
}
219+
}
220+
107221
class AndroidSdk {
108-
AndroidSdk(this.directory, [this.ndkDirectory, this.ndkCompiler,
109-
this.ndkCompilerArgs]) {
222+
AndroidSdk(this.directory, [this.ndk]) {
110223
_init();
111224
}
112225

@@ -116,14 +229,8 @@ class AndroidSdk {
116229
/// The path to the Android SDK.
117230
final String directory;
118231

119-
/// The path to the NDK (can be `null`).
120-
final String ndkDirectory;
121-
122-
/// The path to the NDK compiler (can be `null`).
123-
final String ndkCompiler;
124-
125-
/// The mandatory arguments to the NDK compiler (can be `null`).
126-
final List<String> ndkCompilerArgs;
232+
/// Android NDK (can be `null`).
233+
final AndroidNdk ndk;
127234

128235
List<AndroidSdkVersion> _sdkVersions;
129236
AndroidSdkVersion _latestVersion;
@@ -176,41 +283,6 @@ class AndroidSdk {
176283
return null;
177284
}
178285

179-
String findNdk(String androidHomeDir) {
180-
final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle');
181-
if (fs.isDirectorySync(ndkDirectory)) {
182-
return ndkDirectory;
183-
}
184-
return null;
185-
}
186-
187-
String findNdkCompiler(String ndkDirectory) {
188-
String directory;
189-
if (platform.isLinux) {
190-
directory = 'linux-x86_64';
191-
} else if (platform.isMacOS) {
192-
directory = 'darwin-x86_64';
193-
}
194-
if (directory != null) {
195-
final String ndkCompiler = fs.path.join(ndkDirectory,
196-
'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
197-
'bin', 'arm-linux-androideabi-gcc');
198-
if (fs.isFileSync(ndkCompiler)) {
199-
return ndkCompiler;
200-
}
201-
}
202-
return null;
203-
}
204-
205-
List<String> computeNdkCompilerArgs(String ndkDirectory) {
206-
final String armPlatform = fs.path.join(ndkDirectory, 'platforms',
207-
'android-9', 'arch-arm');
208-
if (fs.isDirectorySync(armPlatform)) {
209-
return <String>['--sysroot', armPlatform];
210-
}
211-
return null;
212-
}
213-
214286
final String androidHomeDir = findAndroidHomeDir();
215287
if (androidHomeDir == null) {
216288
// No dice.
@@ -219,20 +291,15 @@ class AndroidSdk {
219291
}
220292

221293
// Try to find the NDK compiler. If we can't find it, it's also ok.
222-
final String ndkDir = findNdk(androidHomeDir);
223-
String ndkCompiler;
224-
List<String> ndkCompilerArgs;
225-
if (ndkDir != null) {
226-
ndkCompiler = findNdkCompiler(ndkDir);
227-
if (ndkCompiler != null) {
228-
ndkCompilerArgs = computeNdkCompilerArgs(ndkDir);
229-
if (ndkCompilerArgs == null) {
230-
ndkCompiler = null;
231-
}
232-
}
294+
AndroidNdk ndk;
295+
try {
296+
ndk = AndroidNdk.locateNdk(androidHomeDir);
297+
} on AndroidNdkSearchError {
298+
// Ignore AndroidNdkSearchError's but don't ignore any other
299+
// exceptions.
233300
}
234301

235-
return new AndroidSdk(androidHomeDir, ndkDir, ndkCompiler, ndkCompilerArgs);
302+
return new AndroidSdk(androidHomeDir, ndk);
236303
}
237304

238305
static bool validSdkDirectory(String dir) {

‎packages/flutter_tools/lib/src/android/android_workflow.dart‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
100100

101101
messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
102102

103-
messages.add(new ValidationMessage(androidSdk.ndkDirectory == null
103+
messages.add(new ValidationMessage(androidSdk.ndk == null
104104
? 'Android NDK location not configured (optional; useful for native profiling support)'
105-
: 'Android NDK at ${androidSdk.ndkDirectory}'));
105+
: 'Android NDK at ${androidSdk.ndk.directory}'));
106106

107107
String sdkVersionText;
108108
if (androidSdk.latestVersion != null) {

‎packages/flutter_tools/lib/src/android/gradle.dart‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ Future<Null> _buildGradleProjectV2(String gradle, BuildInfo buildInfo, String ta
379379
if (buildInfo.fileSystemScheme != null)
380380
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
381381
}
382-
if (buildInfo.preferSharedLibrary && androidSdk.ndkCompiler != null) {
382+
if (buildInfo.preferSharedLibrary && androidSdk.ndk != null) {
383383
command.add('-Pprefer-shared-library=true');
384384
}
385385
if (buildInfo.targetPlatform == TargetPlatform.android_arm64)

‎packages/flutter_tools/lib/src/base/build.dart‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,13 @@ class AOTSnapshotter {
152152
if (platform == TargetPlatform.ios)
153153
buildSharedLibrary = false;
154154

155-
if (buildSharedLibrary && androidSdk.ndkCompiler == null) {
155+
if (buildSharedLibrary && androidSdk.ndk == null) {
156+
final String explanation = AndroidNdk.explainMissingNdk(androidSdk.directory);
156157
printError(
157-
'Could not find NDK in Android SDK at ${androidSdk.directory}.\n'
158+
'Could not find NDK in Android SDK at ${androidSdk.directory}:\n'
159+
'\n'
160+
' $explanation\n'
161+
'\n'
158162
'Unable to build with --build-shared-library\n'
159163
'To install the NDK, see instructions at https://developer.android.com/ndk/guides/'
160164
);
@@ -344,8 +348,8 @@ class AOTSnapshotter {
344348
// (which causes it to not look into the other section and therefore not
345349
// find the correct unwinding information).
346350
final String assemblySo = fs.path.join(outputPath, 'app.so');
347-
return await runCheckedAsync(<String>[androidSdk.ndkCompiler]
348-
..addAll(androidSdk.ndkCompilerArgs)
351+
return await runCheckedAsync(<String>[androidSdk.ndk.compiler]
352+
..addAll(androidSdk.ndk.compilerArgs)
349353
..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assemblyPath ]));
350354
}
351355

‎packages/flutter_tools/test/android/android_sdk_test.dart‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,11 @@ void main() {
132132

133133
final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
134134
expect(sdk.directory, realSdkDir);
135-
expect(sdk.ndkDirectory, realNdkDir);
136-
expect(sdk.ndkCompiler, realNdkCompiler);
137-
expect(sdk.ndkCompilerArgs, <String>['--sysroot', realNdkSysroot]);
135+
print(AndroidNdk.explainMissingNdk(sdk.directory));
136+
expect(sdk.ndk, isNotNull);
137+
expect(sdk.ndk.directory, realNdkDir);
138+
expect(sdk.ndk.compiler, realNdkCompiler);
139+
expect(sdk.ndk.compilerArgs, <String>['--sysroot', realNdkSysroot]);
138140
}, overrides: <Type, Generator>{
139141
FileSystem: () => fs,
140142
Platform: () => new FakePlatform(operatingSystem: os),
@@ -149,9 +151,9 @@ void main() {
149151
final String realSdkDir = sdkDir.path;
150152
final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
151153
expect(sdk.directory, realSdkDir);
152-
expect(sdk.ndkDirectory, null);
153-
expect(sdk.ndkCompiler, null);
154-
expect(sdk.ndkCompilerArgs, null);
154+
expect(sdk.ndk, isNull);
155+
final String explanation = AndroidNdk.explainMissingNdk(sdk.directory);
156+
expect(explanation, contains('Can not locate ndk-bundle'));
155157
}, overrides: <Type, Generator>{
156158
FileSystem: () => fs,
157159
Platform: () => new FakePlatform(operatingSystem: os),

‎packages/flutter_tools/test/base/build_test.dart‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ void main() {
659659
testUsingContext('returns failure if buildSharedLibrary is true but no NDK is found', () async {
660660
final String outputPath = fs.path.join('build', 'foo');
661661

662-
when(mockAndroidSdk.ndkCompiler).thenReturn(null);
662+
when(mockAndroidSdk.ndk).thenReturn(null);
663663

664664
final int genSnapshotExitCode = await snapshotter.build(
665665
platform: TargetPlatform.android_arm,

0 commit comments

Comments
 (0)