Skip to content

Commit a778817

Browse files
committed
[build] Reimplement NdkUtils
Context: #5996 Context: #5964 Context: #5964 (comment) The `NdkUtils` class used by Xamarin.Andrid.Build.Tasks to find tooling shipped with the Android NDK, has grown increasingly complicated over the years due to a number of incompatibilities between various versions of the NDK. The code became hard to follow and untidy. This commit attempts to address the issue by replacing the single static `NdkUtils` class with a hierarchy of dynamically instantiated classes rooted in a new base class, `NdkTools`. `NdkUtils` had to be initialized for each thread that needed to access its methods, which led to various issues with concurrency and lack of proper initialization since the initialization had to be done wherever `NdkUtils` was first accessed, meaning that any task using it had to do it. `NdkTools` doesn't require such initialization, instead it provides a factory method called `Create` which takes path to the NDK as its parameter and returns an instance of `NdkTools` child class (or `null` if an error occurs) which the can be safely used by the caller. Callers need not concern themselves with what is the actual type of the returned instance, they access only methods and properties defined in the `NdkTools` base abstract class. The hierarchy of `NdkTools` derivatives is structured and named after the breaking changes in the NDK. For instance, NDK versions before 16 used the GNU compilers, while release 16 and above use the clang compilers - this is reflected in existence of two classes derived from `NdkTools`, `NoClang` for NDKs older than r16 and `WithClang` for the newer ones. The other breaking changes are the addition of unified headers in r19, removal of the `platforms` directory in r22 and removal of GNU Binutils in r23. NDK r23 is recognized in this commit but it is NOT supported. Support for r23 is being worked on in PR #6073 which will be merged once r23 is out of beta.
1 parent e06d713 commit a778817

20 files changed

+1003
-863
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,6 @@ public class Aot : AndroidAsyncTask
8484
AotMode AotMode;
8585
SequencePointsMode sequencePointsMode;
8686

87-
public override bool RunTask ()
88-
{
89-
// NdkUtil must always be initialized - once per thread
90-
if (!NdkUtil.Init (LogCodedError, AndroidNdkDirectory))
91-
return false;
92-
93-
return base.RunTask ();
94-
}
95-
9687
public static bool GetAndroidAotMode(string androidAotMode, out AotMode aotMode)
9788
{
9889
aotMode = AotMode.Normal;
@@ -138,7 +129,7 @@ public static bool TryGetSequencePointsMode (string value, out SequencePointsMod
138129
return false;
139130
}
140131

141-
static string GetNdkToolchainLibraryDir(string binDir, string archDir = null)
132+
static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, string archDir = null)
142133
{
143134
var baseDir = Path.GetFullPath(Path.Combine(binDir, ".."));
144135

@@ -154,7 +145,7 @@ static string GetNdkToolchainLibraryDir(string binDir, string archDir = null)
154145
goto no_toolchain_error;
155146
}
156147

157-
if (NdkUtil.UsingClangNDK)
148+
if (ndk.UsesClang)
158149
return libPath;
159150

160151
gccLibDir = Directory.EnumerateDirectories(libPath).ToList();
@@ -171,9 +162,9 @@ static string GetNdkToolchainLibraryDir(string binDir, string archDir = null)
171162
throw new Exception("Could not find a valid NDK compiler toolchain library path");
172163
}
173164

174-
static string GetNdkToolchainLibraryDir (string binDir, AndroidTargetArch arch)
165+
static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, AndroidTargetArch arch)
175166
{
176-
return GetNdkToolchainLibraryDir (binDir, NdkUtil.GetArchDirName (arch));
167+
return GetNdkToolchainLibraryDir (ndk, binDir, ndk.GetArchDirName (arch));
177168
}
178169

179170
static string QuoteFileName(string fileName)
@@ -183,7 +174,7 @@ static string QuoteFileName(string fileName)
183174
return builder.ToString();
184175
}
185176

186-
int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetArch arch)
177+
int GetNdkApiLevel (NdkTools ndk, string androidApiLevel, AndroidTargetArch arch)
187178
{
188179
var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions);
189180

@@ -209,15 +200,15 @@ int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetA
209200
else if (level == 23) level = 21;
210201

211202
// API levels below level 21 do not provide support for 64-bit architectures.
212-
if (NdkUtil.IsNdk64BitArch(arch) && level < 21) {
203+
if (ndk.IsNdk64BitArch (arch) && level < 21) {
213204
level = 21;
214205
}
215206

216207
// We perform a downwards API level lookup search since we might not have hardcoded the correct API
217208
// mapping above and we do not want to crash needlessly.
218209
for (; level >= 5; level--) {
219210
try {
220-
NdkUtil.GetNdkPlatformLibPath (androidNdkPath, arch, level);
211+
ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level);
221212
break;
222213
} catch (InvalidOperationException ex) {
223214
// Path not found, continue searching...
@@ -230,10 +221,9 @@ int GetNdkApiLevel(string androidNdkPath, string androidApiLevel, AndroidTargetA
230221

231222
public async override System.Threading.Tasks.Task RunTaskAsync ()
232223
{
233-
// NdkUtil must always be initialized - once per thread
234-
if (!NdkUtil.Init (LogCodedError, AndroidNdkDirectory)) {
235-
LogDebugMessage ("Failed to initialize NdkUtil");
236-
return;
224+
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
225+
if (ndk == null) {
226+
return; // NdkTools.Create will log appropriate error
237227
}
238228

239229
bool hasValidAotMode = GetAndroidAotMode (AndroidAotMode, out AotMode);
@@ -251,7 +241,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
251241

252242
var nativeLibs = new List<string> ();
253243

254-
await this.WhenAllWithLock (GetAotConfigs (),
244+
await this.WhenAllWithLock (GetAotConfigs (ndk),
255245
(config, lockObject) => {
256246
if (!config.Valid) {
257247
Cancel ();
@@ -277,7 +267,7 @@ await this.WhenAllWithLock (GetAotConfigs (),
277267
LogDebugTaskItems (" NativeLibrariesReferences: ", NativeLibrariesReferences);
278268
}
279269

280-
IEnumerable<Config> GetAotConfigs ()
270+
IEnumerable<Config> GetAotConfigs (NdkTools ndk)
281271
{
282272
if (!Directory.Exists (AotOutputDirectory))
283273
Directory.CreateDirectory (AotOutputDirectory);
@@ -325,7 +315,7 @@ IEnumerable<Config> GetAotConfigs ()
325315
throw new Exception ("Unsupported Android target architecture ABI: " + abi);
326316
}
327317

328-
if (EnableLLVM && !NdkUtil.ValidateNdkPlatform (LogMessage, LogCodedError, AndroidNdkDirectory, arch, enableLLVM:EnableLLVM)) {
318+
if (EnableLLVM && !ndk.ValidateNdkPlatform (LogMessage, LogCodedError, arch, enableLLVM:EnableLLVM)) {
329319
yield return Config.Invalid;
330320
yield break;
331321
}
@@ -341,8 +331,8 @@ IEnumerable<Config> GetAotConfigs ()
341331

342332
int level = 0;
343333
string toolPrefix = EnableLLVM
344-
? NdkUtil.GetNdkToolPrefix (AndroidNdkDirectory, arch, level = GetNdkApiLevel (AndroidNdkDirectory, AndroidApiLevel, arch))
345-
: Path.Combine (AndroidBinUtilsDirectory, $"{NdkUtil.GetArchDirName (arch)}-");
334+
? ndk.GetNdkToolPrefixForAOT (arch, level = GetNdkApiLevel (ndk, AndroidApiLevel, arch))
335+
: Path.Combine (AndroidBinUtilsDirectory, $"{ndk.GetArchDirName (arch)}-");
346336
var toolchainPath = toolPrefix.Substring(0, toolPrefix.LastIndexOf(Path.DirectorySeparatorChar));
347337
var ldFlags = string.Empty;
348338
if (EnableLLVM) {
@@ -353,25 +343,25 @@ IEnumerable<Config> GetAotConfigs ()
353343

354344
string androidLibPath = string.Empty;
355345
try {
356-
androidLibPath = NdkUtil.GetNdkPlatformLibPath(AndroidNdkDirectory, arch, level);
346+
androidLibPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level);
357347
} catch (InvalidOperationException ex) {
358348
Diagnostic.Error (5101, ex.Message);
359349
}
360350

361351
string toolchainLibDir;
362-
if (NdkUtil.UsingClangNDK)
363-
toolchainLibDir = GetNdkToolchainLibraryDir (toolchainPath, arch);
352+
if (ndk.UsesClang)
353+
toolchainLibDir = GetNdkToolchainLibraryDir (ndk, toolchainPath, arch);
364354
else
365-
toolchainLibDir = GetNdkToolchainLibraryDir (toolchainPath);
355+
toolchainLibDir = GetNdkToolchainLibraryDir (ndk, toolchainPath);
366356

367357
var libs = new List<string>();
368-
if (NdkUtil.UsingClangNDK) {
358+
if (ndk.UsesClang) {
369359
libs.Add ($"-L{toolchainLibDir.TrimEnd ('\\')}");
370360
libs.Add ($"-L{androidLibPath.TrimEnd ('\\')}");
371361

372362
if (arch == AndroidTargetArch.Arm) {
373363
// Needed for -lunwind to work
374-
string compilerLibDir = Path.Combine (toolchainPath, "..", "sysroot", "usr", "lib", NdkUtil.GetArchDirName (arch));
364+
string compilerLibDir = Path.Combine (toolchainPath, "..", "sysroot", "usr", "lib", ndk.GetArchDirName (arch));
375365
libs.Add ($"-L{compilerLibDir.TrimEnd ('\\')}");
376366
}
377367
}
@@ -385,7 +375,7 @@ IEnumerable<Config> GetAotConfigs ()
385375

386376
string ldName = String.Empty;
387377
if (EnableLLVM) {
388-
ldName = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "ld", level);
378+
ldName = ndk.GetToolPath (NdkToolKind.Linker, arch, level);
389379
if (!String.IsNullOrEmpty (ldName)) {
390380
ldName = Path.GetFileName (ldName);
391381
if (ldName.IndexOf ('-') >= 0) {

src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,12 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
635635
return;
636636
}
637637

638-
NdkUtil.Init (AndroidNdkDirectory);
639-
string clangDir = NdkUtil.GetClangDeviceLibraryPath (AndroidNdkDirectory);
638+
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
639+
if (ndk == null) {
640+
return; // NdkTools.Create will log appropriate error
641+
}
642+
643+
string clangDir = ndk.GetClangDeviceLibraryPath ();
640644
if (String.IsNullOrEmpty (clangDir)) {
641645
LogSanitizerError ($"Unable to find the clang compiler directory. Is NDK installed?");
642646
return;

src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ IEnumerable<Config> GetLinkerConfigs ()
168168
linkerArgs.Add (QuoteFileName (file));
169169
}
170170

171-
string ld = MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, $"{NdkUtil.GetNdkToolchainPrefix (arch, false)}ld");
171+
string ld = MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, $"{NdkTools.GetBinutilsToolchainPrefix (arch)}ld");
172172
yield return new Config {
173173
LinkerPath = Path.Combine (AndroidBinUtilsDirectory, ld),
174174
LinkerOptions = String.Join (" ", linkerArgs),

src/Xamarin.Android.Build.Tasks/Tasks/MakeBundleNativeCodeExternal.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ public class MakeBundleNativeCodeExternal : AndroidTask
2828

2929
[Required]
3030
public ITaskItem[] Assemblies { get; set; }
31-
31+
3232
// Which ABIs to include native libs for
3333
[Required]
3434
public string [] SupportedAbis { get; set; }
35-
35+
3636
[Required]
3737
public string TempOutputPath { get; set; }
3838

@@ -57,11 +57,13 @@ public MakeBundleNativeCodeExternal ()
5757

5858
public override bool RunTask ()
5959
{
60-
if (!NdkUtil.Init (Log, AndroidNdkDirectory))
61-
return false;
60+
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
61+
if (ndk == null) {
62+
return false; // NdkTools.Create will log appropriate error
63+
}
6264

6365
try {
64-
return DoExecute ();
66+
return DoExecute (ndk);
6567
} catch (XamarinAndroidException e) {
6668
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
6769
if (MonoAndroidHelper.LogInternalExceptions)
@@ -72,7 +74,7 @@ public override bool RunTask ()
7274
return !Log.HasLoggedErrors;
7375
}
7476

75-
bool DoExecute ()
77+
bool DoExecute (NdkTools ndk)
7678
{
7779
var results = new List<ITaskItem> ();
7880
string bundlepath = Path.Combine (TempOutputPath, "bundles");
@@ -102,11 +104,11 @@ bool DoExecute ()
102104
break;
103105
}
104106

105-
if (!NdkUtil.ValidateNdkPlatform (Log, AndroidNdkDirectory, arch, enableLLVM: false)) {
107+
if (!ndk.ValidateNdkPlatform (arch, enableLLVM: false)) {
106108
return false;
107109
}
108110

109-
int level = NdkUtil.GetMinimumApiLevelFor (arch, AndroidNdkDirectory);
111+
int level = ndk.GetMinimumApiLevelFor (arch);
110112
var outpath = Path.Combine (bundlepath, abi);
111113
if (!Directory.Exists (outpath))
112114
Directory.CreateDirectory (outpath);
@@ -140,10 +142,10 @@ bool DoExecute ()
140142
CreateNoWindow = true,
141143
WindowStyle = ProcessWindowStyle.Hidden,
142144
};
143-
string windowsCompilerSwitches = NdkUtil.GetCompilerTargetParameters (AndroidNdkDirectory, arch, level);
144-
var compilerNoQuotes = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "gcc", level);
145+
string windowsCompilerSwitches = ndk.GetCompilerTargetParameters (arch, level);
146+
var compilerNoQuotes = ndk.GetToolPath (NdkToolKind.CompilerC, arch, level);
145147
var compiler = $"\"{compilerNoQuotes}\" {windowsCompilerSwitches}".Trim ();
146-
var gas = '"' + NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "as", level) + '"';
148+
var gas = '"' + ndk.GetToolPath (NdkToolKind.Assembler, arch, level) + '"';
147149
psi.EnvironmentVariables ["CC"] = compiler;
148150
psi.EnvironmentVariables ["AS"] = gas;
149151
Log.LogDebugMessage ("CC=" + compiler);
@@ -167,7 +169,7 @@ bool DoExecute ()
167169

168170
clb = new CommandLineBuilder ();
169171

170-
// See NdkUtils.GetNdkTool for reasons why
172+
// See NdkToolsWithClangWithPlatforms.ctor for reasons why
171173
if (!String.IsNullOrEmpty (windowsCompilerSwitches))
172174
clb.AppendTextUnquoted (windowsCompilerSwitches);
173175

@@ -188,14 +190,14 @@ bool DoExecute ()
188190
clb.AppendFileNameIfNotNull (IncludePath);
189191
}
190192

191-
string asmIncludePath = NdkUtil.GetNdkAsmIncludePath (AndroidNdkDirectory, arch, level);
193+
string asmIncludePath = ndk.GetDirectoryPath (NdkToolchainDir.AsmInclude, arch, level);
192194
if (!String.IsNullOrEmpty (asmIncludePath)) {
193195
clb.AppendSwitch ("-I");
194196
clb.AppendFileNameIfNotNull (asmIncludePath);
195197
}
196198

197199
clb.AppendSwitch ("-I");
198-
clb.AppendFileNameIfNotNull (NdkUtil.GetNdkPlatformIncludePath (AndroidNdkDirectory, arch, level));
200+
clb.AppendFileNameIfNotNull (ndk.GetDirectoryPath (NdkToolchainDir.PlatformInclude, arch, level));
199201
clb.AppendFileNameIfNotNull (Path.Combine (outpath, "temp.c"));
200202
Log.LogDebugMessage ("[CC] " + compiler + " " + clb);
201203
if (MonoAndroidHelper.RunProcess (compilerNoQuotes, clb.ToString (), OnCcOutputData, OnCcErrorData) != 0) {
@@ -216,13 +218,13 @@ bool DoExecute ()
216218
clb.AppendSwitch ("-o");
217219
clb.AppendFileNameIfNotNull (Path.Combine (outpath, BundleSharedLibraryName));
218220
clb.AppendSwitch ("-L");
219-
clb.AppendFileNameIfNotNull (NdkUtil.GetNdkPlatformLibPath (AndroidNdkDirectory, arch, level));
221+
clb.AppendFileNameIfNotNull (ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level));
220222
clb.AppendSwitch ("-lc");
221223
clb.AppendSwitch ("-lm");
222224
clb.AppendSwitch ("-ldl");
223225
clb.AppendSwitch ("-llog");
224226
clb.AppendSwitch ("-lz"); // Compress
225-
string ld = NdkUtil.GetNdkTool (AndroidNdkDirectory, arch, "ld", level);
227+
string ld = ndk.GetToolPath (NdkToolKind.Linker, arch, level);
226228
Log.LogMessage (MessageImportance.Normal, "[LD] " + ld + " " + clb);
227229
if (MonoAndroidHelper.RunProcess (ld, clb.ToString (), OnLdOutputData, OnLdErrorData) != 0) {
228230
Log.LogCodedError ("XA5201", Properties.Resources.XA5201, proc.ExitCode);

0 commit comments

Comments
 (0)