diff --git a/Directory.Build.props b/Directory.Build.props
index 3f855aae763..d01d6648f8e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -48,7 +48,7 @@
5.4.0
1.1.11
6.12.0.148
- 6.0.0
+ 8.0.0
6.0.0
2.13.1
2.14.1
diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln
index 50918d1e4e3..c85d197a5aa 100644
--- a/Xamarin.Android.sln
+++ b/Xamarin.Android.sln
@@ -109,7 +109,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "decompress-assemblies", "to
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tmt", "tools\tmt\tmt.csproj", "{1A273ED2-AE84-48E9-9C23-E978C2D0CB34}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "assembly-store-reader", "tools\assembly-store-reader\assembly-store-reader.csproj", "{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "assembly-store-reader", "tools\assembly-store-reader-mk2\assembly-store-reader.csproj", "{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.JavaTypeSystem", "external\Java.Interop\src\Java.Interop.Tools.JavaTypeSystem\Java.Interop.Tools.JavaTypeSystem.csproj", "{4EFCED6E-9A6B-453A-94E4-CE4B736EC684}"
EndProject
diff --git a/build-tools/scripts/TestApks.targets b/build-tools/scripts/TestApks.targets
index 95ba391d170..1110f32453c 100644
--- a/build-tools/scripts/TestApks.targets
+++ b/build-tools/scripts/TestApks.targets
@@ -111,7 +111,14 @@
Timeout="60000"
/>
+
<_ResolvedJavaLibraries Include="@(ResolvedFileToPublish)" Condition=" '%(ResolvedFileToPublish.Extension)' == '.jar' " />
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
index c99ce0db5c5..45778d60204 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
@@ -23,6 +23,9 @@ namespace Xamarin.Android.Tasks
{
public class BuildApk : AndroidTask
{
+ const string ArchiveAssembliesPath = "lib";
+ const string ArchiveLibPath = "lib";
+
public override string TaskPrefix => "BLD";
public string AndroidNdkDirectory { get; set; }
@@ -33,6 +36,7 @@ public class BuildApk : AndroidTask
[Required]
public string ApkOutputPath { get; set; }
+ [Required]
public string AppSharedLibrariesDir { get; set; }
[Required]
@@ -118,12 +122,11 @@ bool _Debug {
SequencePointsMode sequencePointsMode = SequencePointsMode.None;
public ITaskItem[] LibraryProjectJars { get; set; }
- string [] uncompressedFileExtensions;
+ HashSet uncompressedFileExtensions;
+ // Do not use trailing / in the path
protected virtual string RootPath => "";
- protected virtual string AssembliesPath => RootPath + "assemblies/";
-
protected virtual string DalvikPath => "";
protected virtual CompressionMethod UncompressedMethod => CompressionMethod.Store;
@@ -136,7 +139,7 @@ protected virtual void FixupArchive (ZipArchiveEx zip) { }
List includePatterns = new List ();
- void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName)
+ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary> compressedAssembliesInfo, string assemblyStoreApkName)
{
ArchiveFileList files = new ArchiveFileList ();
bool refresh = true;
@@ -207,6 +210,7 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
apk.Flush ();
}
+ AddRuntimeConfigBlob (apk);
AddRuntimeLibraries (apk, supportedAbis);
apk.Flush();
AddNativeLibraries (files, supportedAbis);
@@ -218,10 +222,6 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
}
}
- if (!String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath)) {
- AddFileToArchiveIfNewer (apk, RuntimeConfigBinFilePath, $"{AssembliesPath}rc.bin", compressionMethod: UncompressedMethod);
- }
-
foreach (var file in files) {
var item = Path.Combine (file.archivePath.Replace (Path.DirectorySeparatorChar, '/'));
existingEntries.Remove (item);
@@ -318,7 +318,18 @@ public override bool RunTask ()
Aot.TryGetSequencePointsMode (AndroidSequencePointsMode, out sequencePointsMode);
var outputFiles = new List ();
- uncompressedFileExtensions = UncompressedFileExtensions?.Split (new char [] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty ();
+ uncompressedFileExtensions = new HashSet (StringComparer.OrdinalIgnoreCase);
+ foreach (string? e in UncompressedFileExtensions?.Split (new char [] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty ()) {
+ string? ext = e?.Trim ();
+ if (String.IsNullOrEmpty (ext)) {
+ continue;
+ }
+
+ if (ext[0] != '.') {
+ ext = $".{ext}";
+ }
+ uncompressedFileExtensions.Add (ext);
+ }
existingEntries.Clear ();
@@ -331,12 +342,12 @@ public override bool RunTask ()
bool debug = _Debug;
bool compress = !debug && EnableCompression;
- IDictionary compressedAssembliesInfo = null;
+ IDictionary> compressedAssembliesInfo = null;
if (compress) {
string key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
Log.LogDebugMessage ($"Retrieving assembly compression info with key '{key}'");
- compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal> (key, RegisteredTaskObjectLifetime.Build);
+ compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal>> (key, RegisteredTaskObjectLifetime.Build);
if (compressedAssembliesInfo == null)
throw new InvalidOperationException ($"Assembly compression info not found for key '{key}'. Compression will not be performed.");
}
@@ -380,34 +391,36 @@ static Regex FileGlobToRegEx (string fileGlob, RegexOptions options)
return new Regex (sb.ToString (), options);
}
- void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName)
+ void AddRuntimeConfigBlob (ZipArchiveEx apk)
+ {
+ // We will place rc.bin in the `lib` directory next to the blob, to make startup slightly faster, as we will find the config file right after we encounter
+ // our assembly store. Not only that, but also we'll be able to skip scanning the `base.apk` archive when split configs are enabled (which they are in 99%
+ // of cases these days, since AAB enforces that split). `base.apk` contains only ABI-agnostic file, while one of the split config files contains only
+ // ABI-specific data+code.
+ if (!String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath)) {
+ foreach (string abi in SupportedAbis) {
+ // Prefix it with `a` because bundletool sorts entries alphabetically, and this will place it right next to `assemblies.*.blob.so`, which is what we
+ // like since we can finish scanning the zip central directory earlier at startup.
+ string inArchivePath = MakeArchiveLibPath (abi, "libarc.bin.so");
+ AddFileToArchiveIfNewer (apk, RuntimeConfigBinFilePath, inArchivePath, compressionMethod: GetCompressionMethod (inArchivePath));
+ }
+ }
+ }
+
+ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary> compressedAssembliesInfo, string assemblyStoreApkName)
{
string sourcePath;
AssemblyCompression.AssemblyData compressedAssembly = null;
string compressedOutputDir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4"));
- AssemblyStoreGenerator storeGenerator;
+ AssemblyStoreGenerator? storeGenerator;
if (UseAssemblyStore) {
- storeGenerator = new AssemblyStoreGenerator (AssembliesPath, Log);
+ storeGenerator = new AssemblyStoreGenerator (Log);
} else {
storeGenerator = null;
}
- AssemblyStoreAssemblyInfo storeAssembly = null;
-
- //
- // NOTE
- //
- // The very first store (ID 0) **must** contain an index of all the assemblies included in the application, even if they
- // are included in other APKs than the base one. The ID 0 store **must** be placed in the base assembly
- //
-
- // Currently, all the assembly stores end up in the "base" apk (the APK name is the key in the dictionary below) but the code is ready for the time when we
- // partition assemblies into "feature" APKs
- const string DefaultBaseApkName = "base";
- if (String.IsNullOrEmpty (assemblyStoreApkName)) {
- assemblyStoreApkName = DefaultBaseApkName;
- }
+ AssemblyStoreAssemblyInfo? storeAssemblyInfo = null;
// Add user assemblies
AddAssembliesFromCollection (ResolvedUserAssemblies);
@@ -419,41 +432,53 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary> assemblyStorePaths = storeGenerator.Generate (Path.GetDirectoryName (ApkOutputPath));
- if (assemblyStorePaths == null) {
+ Dictionary assemblyStorePaths = storeGenerator.Generate (AppSharedLibrariesDir);
+
+ if (assemblyStorePaths.Count == 0) {
throw new InvalidOperationException ("Assembly store generator did not generate any stores");
}
- if (!assemblyStorePaths.TryGetValue (assemblyStoreApkName, out List baseAssemblyStores) || baseAssemblyStores == null || baseAssemblyStores.Count == 0) {
- throw new InvalidOperationException ("Assembly store generator didn't generate the required base stores");
+ if (assemblyStorePaths.Count != SupportedAbis.Length) {
+ throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI");
}
- string assemblyStorePrefix = $"{assemblyStoreApkName}_";
- foreach (string assemblyStorePath in baseAssemblyStores) {
- string inArchiveName = Path.GetFileName (assemblyStorePath);
-
- if (inArchiveName.StartsWith (assemblyStorePrefix, StringComparison.Ordinal)) {
- inArchiveName = inArchiveName.Substring (assemblyStorePrefix.Length);
- }
-
- CompressionMethod compressionMethod;
- if (inArchiveName.EndsWith (".manifest", StringComparison.Ordinal)) {
- compressionMethod = CompressionMethod.Default;
- } else {
- compressionMethod = UncompressedMethod;
- }
-
- AddFileToArchiveIfNewer (apk, assemblyStorePath, AssembliesPath + inArchiveName, compressionMethod);
+ string inArchivePath;
+ foreach (var kvp in assemblyStorePaths) {
+ string abi = MonoAndroidHelper.ArchToAbi (kvp.Key);
+ inArchivePath = MakeArchiveLibPath (abi, "lib" + Path.GetFileName (kvp.Value));
+ AddFileToArchiveIfNewer (apk, kvp.Value, inArchivePath, GetCompressionMethod (inArchivePath));
}
void AddAssembliesFromCollection (ITaskItem[] assemblies)
{
- foreach (ITaskItem assembly in assemblies) {
- if (bool.TryParse (assembly.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
- Log.LogDebugMessage ($"Skipping {assembly.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
- continue;
+ Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (
+ assemblies,
+ SupportedAbis,
+ validate: true,
+ shouldSkip: (ITaskItem asm) => {
+ if (bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
+ Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
+ return true;
+ }
+
+ return false;
}
+ );
+
+ foreach (var kvp in perArchAssemblies) {
+ Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'");
+ DoAddAssembliesFromArchCollection (kvp.Value);
+ }
+ }
+ void DoAddAssembliesFromArchCollection (Dictionary assemblies)
+ {
+ // In the "all assemblies are per-RID" world, assemblies, pdb and config are disguised as shared libraries (that is,
+ // their names end with the .so extension) so that Android allows us to put them in the `lib/{ARCH}` directory.
+ // For this reason, they have to be treated just like other .so files, as far as compression rules are concerned.
+ // Thus, we no longer just store them in the apk but we call the `GetCompressionMethod` method to find out whether
+ // or not we're supposed to compress .so files.
+ foreach (ITaskItem assembly in assemblies.Values) {
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec)) {
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec);
}
@@ -461,25 +486,27 @@ void AddAssembliesFromCollection (ITaskItem[] assemblies)
sourcePath = CompressAssembly (assembly);
// Add assembly
- var assemblyPath = GetAssemblyPath (assembly, frameworkAssembly: false);
+ (string assemblyPath, string assemblyDirectory) = GetInArchiveAssemblyPath (assembly);
if (UseAssemblyStore) {
- storeAssembly = new AssemblyStoreAssemblyInfo (sourcePath, assemblyPath, assembly.GetMetadata ("Abi"));
+ storeAssemblyInfo = new AssemblyStoreAssemblyInfo (sourcePath, assembly);
} else {
- AddFileToArchiveIfNewer (apk, sourcePath, assemblyPath + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
+ AddFileToArchiveIfNewer (apk, sourcePath, assemblyPath, compressionMethod: GetCompressionMethod (assemblyPath));
}
// Try to add config if exists
var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config");
if (UseAssemblyStore) {
- storeAssembly.SetConfigPath (config);
+ if (File.Exists (config)) {
+ storeAssemblyInfo.ConfigFile = new FileInfo (config);
+ }
} else {
- AddAssemblyConfigEntry (apk, assemblyPath, config);
+ AddAssemblyConfigEntry (apk, assemblyDirectory, config);
}
// Try to add symbols if Debug
if (debug) {
var symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb");
- string symbolsPath = null;
+ string? symbolsPath = null;
if (File.Exists (symbols)) {
symbolsPath = symbols;
@@ -487,15 +514,21 @@ void AddAssembliesFromCollection (ITaskItem[] assemblies)
if (!String.IsNullOrEmpty (symbolsPath)) {
if (UseAssemblyStore) {
- storeAssembly.SetDebugInfoPath (symbolsPath);
+ storeAssemblyInfo.SymbolsFile = new FileInfo (symbolsPath);
} else {
- AddFileToArchiveIfNewer (apk, symbolsPath, assemblyPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
+ string archiveSymbolsPath = assemblyDirectory + MonoAndroidHelper.MakeDiscreteAssembliesEntryName (Path.GetFileName (symbols));
+ AddFileToArchiveIfNewer (
+ apk,
+ symbolsPath,
+ archiveSymbolsPath,
+ compressionMethod: GetCompressionMethod (archiveSymbolsPath)
+ );
}
}
}
if (UseAssemblyStore) {
- storeGenerator.Add (assemblyStoreApkName, storeAssembly);
+ storeGenerator.Add (storeAssemblyInfo);
}
}
}
@@ -519,38 +552,44 @@ string CompressAssembly (ITaskItem assembly)
return assembly.ItemSpec;
}
- var key = CompressedAssemblyInfo.GetDictionaryKey (assembly);
- if (compressedAssembliesInfo.TryGetValue (key, out CompressedAssemblyInfo info) && info != null) {
- EnsureCompressedAssemblyData (assembly.ItemSpec, info.DescriptorIndex);
- string assemblyOutputDir;
- string subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
- if (!String.IsNullOrEmpty (subDirectory))
- assemblyOutputDir = Path.Combine (compressedOutputDir, subDirectory);
- else
- assemblyOutputDir = compressedOutputDir;
- AssemblyCompression.CompressionResult result = AssemblyCompression.Compress (compressedAssembly, assemblyOutputDir);
- if (result != AssemblyCompression.CompressionResult.Success) {
- switch (result) {
- case AssemblyCompression.CompressionResult.EncodingFailed:
- Log.LogMessage ($"Failed to compress {assembly.ItemSpec}");
- break;
-
- case AssemblyCompression.CompressionResult.InputTooBig:
- Log.LogMessage ($"Input assembly {assembly.ItemSpec} exceeds maximum input size");
- break;
-
- default:
- Log.LogMessage ($"Unknown error compressing {assembly.ItemSpec}");
- break;
- }
- return assembly.ItemSpec;
- }
- return compressedAssembly.DestinationPath;
- } else {
+ string key = CompressedAssemblyInfo.GetDictionaryKey (assembly);
+ AndroidTargetArch arch = MonoAndroidHelper.GetTargetArch (assembly);
+ if (!compressedAssembliesInfo.TryGetValue (arch, out Dictionary assembliesInfo)) {
+ throw new InvalidOperationException ($"Internal error: compression assembly info for architecture {arch} not available");
+ }
+
+ if (!assembliesInfo.TryGetValue (key, out CompressedAssemblyInfo info) || info == null) {
Log.LogDebugMessage ($"Assembly missing from {nameof (CompressedAssemblyInfo)}: {key}");
+ return assembly.ItemSpec;
}
- return assembly.ItemSpec;
+ EnsureCompressedAssemblyData (assembly.ItemSpec, info.DescriptorIndex);
+ string assemblyOutputDir;
+ string subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
+ string abi = MonoAndroidHelper.GetAssemblyAbi (assembly);
+ if (!String.IsNullOrEmpty (subDirectory)) {
+ assemblyOutputDir = Path.Combine (compressedOutputDir, abi, subDirectory);
+ } else {
+ assemblyOutputDir = Path.Combine (compressedOutputDir, abi);
+ }
+ AssemblyCompression.CompressionResult result = AssemblyCompression.Compress (compressedAssembly, assemblyOutputDir);
+ if (result != AssemblyCompression.CompressionResult.Success) {
+ switch (result) {
+ case AssemblyCompression.CompressionResult.EncodingFailed:
+ Log.LogMessage ($"Failed to compress {assembly.ItemSpec}");
+ break;
+
+ case AssemblyCompression.CompressionResult.InputTooBig:
+ Log.LogMessage ($"Input assembly {assembly.ItemSpec} exceeds maximum input size");
+ break;
+
+ default:
+ Log.LogMessage ($"Unknown error compressing {assembly.ItemSpec}");
+ break;
+ }
+ return assembly.ItemSpec;
+ }
+ return compressedAssembly.DestinationPath;
}
}
@@ -568,13 +607,14 @@ bool AddFileToArchiveIfNewer (ZipArchiveEx apk, string file, string inArchivePat
void AddAssemblyConfigEntry (ZipArchiveEx apk, string assemblyPath, string configFile)
{
- string inArchivePath = assemblyPath + Path.GetFileName (configFile);
+ string inArchivePath = MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyPath + Path.GetFileName (configFile));
existingEntries.Remove (inArchivePath);
- if (!File.Exists (configFile))
+ if (!File.Exists (configFile)) {
return;
+ }
- CompressionMethod compressionMethod = UncompressedMethod;
+ CompressionMethod compressionMethod = GetCompressionMethod (inArchivePath);
if (apk.SkipExistingFile (configFile, inArchivePath, compressionMethod)) {
Log.LogDebugMessage ($"Skipping {configFile} as the archive file is up to date.");
return;
@@ -593,19 +633,48 @@ void AddAssemblyConfigEntry (ZipArchiveEx apk, string assemblyPath, string confi
///
/// Returns the in-archive path for an assembly
///
- string GetAssemblyPath (ITaskItem assembly, bool frameworkAssembly)
+ (string assemblyFilePath, string assemblyDirectoryPath) GetInArchiveAssemblyPath (ITaskItem assembly)
{
- var assembliesPath = AssembliesPath;
- var subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
- if (!string.IsNullOrEmpty (subDirectory)) {
- assembliesPath += subDirectory.Replace ('\\', '/');
- if (!assembliesPath.EndsWith ("/", StringComparison.Ordinal)) {
- assembliesPath += "/";
+ var parts = new List ();
+
+ // The PrepareSatelliteAssemblies task takes care of properly setting `DestinationSubDirectory`, so we can just use it here.
+ string? subDirectory = assembly.GetMetadata ("DestinationSubDirectory")?.Replace ('\\', '/');
+ if (string.IsNullOrEmpty (subDirectory)) {
+ throw new InvalidOperationException ($"Internal error: assembly '{assembly}' lacks the required `DestinationSubDirectory` metadata");
+ }
+
+ string assemblyName = Path.GetFileName (assembly.ItemSpec);
+ if (UseAssemblyStore) {
+ parts.Add (subDirectory);
+ parts.Add (assemblyName);
+ } else {
+ // For discrete assembly entries we need to treat assemblies specially.
+ // All of the assemblies have their names mangled so that the possibility to clash with "real" shared
+ // library names is minimized. All of the assembly entries will start with a special character:
+ //
+ // `_` - for regular assemblies (e.g. `_Mono.Android.dll.so`)
+ // `-` - for satellite assemblies (e.g. `-es-Mono.Android.dll.so`)
+ //
+ // Second of all, we need to treat satellite assemblies with even more care.
+ // If we encounter one of them, we will return the culture as part of the path transformed
+ // so that it forms a `-culture-` assembly file name prefix, not a `culture/` subdirectory.
+ // This is necessary because Android doesn't allow subdirectories in `lib/{ABI}/`
+ //
+ string[] subdirParts = subDirectory.TrimEnd ('/').Split ('/');
+ if (subdirParts.Length == 1) {
+ // Not a satellite assembly
+ parts.Add (subDirectory);
+ parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName));
+ } else if (subdirParts.Length == 2) {
+ parts.Add (subdirParts[0]);
+ parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName, subdirParts[1]));
+ } else {
+ throw new InvalidOperationException ($"Internal error: '{assembly}' `DestinationSubDirectory` metadata has too many components ({parts.Count} instead of 1 or 2)");
}
- } else if (!frameworkAssembly && SatelliteAssembly.TryGetSatelliteCultureAndFileName (assembly.ItemSpec, out var culture, out _)) {
- assembliesPath += culture + "/";
}
- return assembliesPath;
+
+ string assemblyFilePath = MonoAndroidHelper.MakeZipArchivePath (ArchiveAssembliesPath, parts);
+ return (assemblyFilePath, Path.GetDirectoryName (assemblyFilePath) + "/");
}
sealed class LibInfo
@@ -618,14 +687,12 @@ sealed class LibInfo
CompressionMethod GetCompressionMethod (string fileName)
{
- if (uncompressedFileExtensions.Any (x => string.Compare (x.StartsWith (".", StringComparison.OrdinalIgnoreCase) ? x : $".{x}", Path.GetExtension (fileName), StringComparison.OrdinalIgnoreCase) == 0))
- return UncompressedMethod;
- return CompressionMethod.Default;
+ return uncompressedFileExtensions.Contains (Path.GetExtension (fileName)) ? UncompressedMethod : CompressionMethod.Default;
}
void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName)
{
- string archivePath = $"lib/{abi}/{inArchiveFileName}";
+ string archivePath = MakeArchiveLibPath (abi, inArchiveFileName);
existingEntries.Remove (archivePath);
CompressionMethod compressionMethod = GetCompressionMethod (archivePath);
if (apk.SkipExistingFile (filesystemPath, archivePath, compressionMethod)) {
@@ -809,7 +876,7 @@ private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supp
void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName)
{
string fileName = string.IsNullOrEmpty (archiveFileName) ? Path.GetFileName (path) : archiveFileName;
- var item = (filePath: path, archivePath: $"lib/{abi}/{fileName}");
+ var item = (filePath: path, archivePath: MakeArchiveLibPath (abi, fileName));
if (files.Any (x => x.archivePath == item.archivePath)) {
Log.LogCodedWarning ("XA4301", path, 0, Properties.Resources.XA4301, item.archivePath);
return;
@@ -833,5 +900,7 @@ void LogSanitizerError (string message)
{
Log.LogError (message);
}
+
+ static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName);
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
index 441299c527d..81a11497da4 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
@@ -4,6 +4,8 @@
using Microsoft.Build.Framework;
using Microsoft.Android.Build.Tasks;
+using Xamarin.Android.Tools;
+
namespace Xamarin.Android.Tasks
{
public class GenerateCompressedAssembliesNativeSourceFiles : AndroidTask
@@ -41,38 +43,54 @@ void GenerateCompressedAssemblySources ()
return;
}
- var assemblies = new SortedDictionary (StringComparer.Ordinal);
- foreach (ITaskItem assembly in ResolvedAssemblies) {
- if (bool.TryParse (assembly.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
- continue;
- }
+ Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (
+ ResolvedAssemblies,
+ SupportedAbis,
+ validate: true,
+ shouldSkip: (ITaskItem asm) => bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value
+ );
+ var archAssemblies = new Dictionary> ();
+ var counters = new Dictionary ();
+
+ foreach (var kvpPerArch in perArchAssemblies) {
+ AndroidTargetArch arch = kvpPerArch.Key;
+ Dictionary resolvedArchAssemblies = kvpPerArch.Value;
+
+ foreach (var kvp in resolvedArchAssemblies) {
+ ITaskItem assembly = kvp.Value;
+
+ if (!archAssemblies.TryGetValue (arch, out Dictionary assemblies)) {
+ assemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ archAssemblies.Add (arch, assemblies);
+ }
- var assemblyKey = CompressedAssemblyInfo.GetDictionaryKey (assembly);
- if (assemblies.ContainsKey (assemblyKey)) {
- Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec}");
- continue;
- }
+ var assemblyKey = CompressedAssemblyInfo.GetDictionaryKey (assembly);
+ if (assemblies.ContainsKey (assemblyKey)) {
+ Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec} (arch {MonoAndroidHelper.GetAssemblyAbi(assembly)})");
+ continue;
+ }
- var fi = new FileInfo (assembly.ItemSpec);
- if (!fi.Exists) {
- Log.LogError ($"Assembly {assembly.ItemSpec} does not exist");
- continue;
- }
+ var fi = new FileInfo (assembly.ItemSpec);
+ if (!fi.Exists) {
+ Log.LogError ($"Assembly {assembly.ItemSpec} does not exist");
+ continue;
+ }
- assemblies.Add (assemblyKey, new CompressedAssemblyInfo (checked((uint)fi.Length)));
- }
- uint index = 0;
- foreach (var kvp in assemblies) {
- kvp.Value.DescriptorIndex = index++;
+ if (!counters.TryGetValue (arch, out uint counter)) {
+ counter = 0;
+ }
+ assemblies.Add (assemblyKey, new CompressedAssemblyInfo (checked((uint)fi.Length), counter++, arch, Path.GetFileNameWithoutExtension (assembly.ItemSpec)));
+ counters[arch] = counter;
+ }
}
string key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
Log.LogDebugMessage ($"Storing compression assemblies info with key '{key}'");
- BuildEngine4.RegisterTaskObjectAssemblyLocal (key, assemblies, RegisteredTaskObjectLifetime.Build);
- Generate (assemblies);
+ BuildEngine4.RegisterTaskObjectAssemblyLocal (key, archAssemblies, RegisteredTaskObjectLifetime.Build);
+ Generate (archAssemblies);
- void Generate (IDictionary dict)
+ void Generate (Dictionary> dict)
{
var composer = new CompressedAssembliesNativeAssemblyGenerator (Log, dict);
LLVMIR.LlvmIrModule compressedAssemblies = composer.Construct ();
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 453885d1758..8bd49bfcd99 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -2,16 +2,11 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
-using System.IO.MemoryMappedFiles;
using System.Linq;
-using System.Reflection;
-using System.Text;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
using Mono.Cecil;
-
+using Microsoft.Build.Utilities;
using Java.Interop.Tools.Cecil;
using Java.Interop.Tools.Diagnostics;
@@ -28,7 +23,7 @@ namespace Xamarin.Android.Tasks
public class GenerateJavaStubs : AndroidTask
{
- public const string MarshalMethodsRegisterTaskKey = ".:!MarshalMethods!:.";
+ public const string NativeCodeGenStateRegisterTaskKey = ".:!MarshalMethods!:.";
public override string TaskPrefix => "GJS";
@@ -95,7 +90,7 @@ public class GenerateJavaStubs : AndroidTask
public ITaskItem[] Environments { get; set; }
[Output]
- public string [] GeneratedBinaryTypeMaps { get; set; }
+ public ITaskItem[] GeneratedBinaryTypeMaps { get; set; }
internal const string AndroidSkipJavaStubGeneration = "AndroidSkipJavaStubGeneration";
@@ -103,11 +98,7 @@ public override bool RunTask ()
{
try {
bool useMarshalMethods = !Debug && EnableMarshalMethods;
- // We're going to do 3 steps here instead of separate tasks so
- // we can share the list of JLO TypeDefinitions between them
- using (XAAssemblyResolver res = MakeResolver (useMarshalMethods)) {
- Run (res, useMarshalMethods);
- }
+ Run (useMarshalMethods);
} catch (XamarinAndroidException e) {
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
if (MonoAndroidHelper.LogInternalExceptions)
@@ -124,210 +115,207 @@ public override bool RunTask ()
return !Log.HasLoggedErrors;
}
- XAAssemblyResolver MakeResolver (bool useMarshalMethods)
+ XAAssemblyResolver MakeResolver (bool useMarshalMethods, AndroidTargetArch targetArch, Dictionary assemblies)
{
- var readerParams = new ReaderParameters();
+ var readerParams = new ReaderParameters ();
if (useMarshalMethods) {
readerParams.ReadWrite = true;
readerParams.InMemory = true;
}
- var res = new XAAssemblyResolver (Log, loadDebugSymbols: true, loadReaderParameters: readerParams);
- foreach (var dir in FrameworkDirectories) {
- if (Directory.Exists (dir.ItemSpec)) {
- res.FrameworkSearchDirectories.Add (dir.ItemSpec);
+ var res = new XAAssemblyResolver (targetArch, Log, loadDebugSymbols: true, loadReaderParameters: readerParams);
+ var uniqueDirs = new HashSet (StringComparer.OrdinalIgnoreCase);
+
+ Log.LogDebugMessage ($"Adding search directories to new architecture {targetArch} resolver:");
+ foreach (var kvp in assemblies) {
+ string assemblyDir = Path.GetDirectoryName (kvp.Value.ItemSpec);
+ if (uniqueDirs.Contains (assemblyDir)) {
+ continue;
}
+
+ uniqueDirs.Add (assemblyDir);
+ res.SearchDirectories.Add (assemblyDir);
+ Log.LogDebugMessage ($" {assemblyDir}");
}
return res;
}
- void Run (XAAssemblyResolver res, bool useMarshalMethods)
+ void Run (bool useMarshalMethods)
{
PackageNamingPolicy pnp;
JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
- Dictionary>? abiSpecificAssembliesByPath = null;
- if (useMarshalMethods) {
- abiSpecificAssembliesByPath = new Dictionary> (StringComparer.Ordinal);
+ // We will process each architecture completely separately as both type maps and marshal methods are strictly per-architecture and
+ // the assemblies should be processed strictly per architecture. Generation of JCWs, and the manifest are ABI-agnostic.
+ // We will generate them only for the first architecture, whichever it is.
+ Dictionary> allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
+
+ // Should "never" happen...
+ if (allAssembliesPerArch.Count != SupportedAbis.Length) {
+ // ...but it happens at least in our `BuildAMassiveApp` test, where `SupportedAbis` mentions only the `x86` and `armeabi-v7a` ABIs, but `ResolvedAssemblies` contains
+ // entries for all the ABIs we support, so let's be flexible and ignore the extra architectures but still error out if there are less architectures than supported ABIs.
+ if (allAssembliesPerArch.Count < SupportedAbis.Length) {
+ throw new InvalidOperationException ($"Internal error: number of architectures ({allAssembliesPerArch.Count}) must equal the number of target ABIs ({SupportedAbis.Length})");
+ }
}
- // Put every assembly we'll need in the resolver
- bool hasExportReference = false;
- bool haveMonoAndroid = false;
- var allTypemapAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
- var userAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
-
- foreach (var assembly in ResolvedAssemblies) {
- bool value;
- if (bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out value) && value) {
- Log.LogDebugMessage ($"Skipping Java Stub Generation for {assembly.ItemSpec}");
- continue;
+ // ...or this...
+ foreach (string abi in SupportedAbis) {
+ AndroidTargetArch arch = MonoAndroidHelper.AbiToTargetArch (abi);
+ if (!allAssembliesPerArch.ContainsKey (arch)) {
+ throw new InvalidOperationException ($"Internal error: no assemblies for architecture '{arch}', which corresponds to target abi '{abi}'");
}
+ }
- bool addAssembly = false;
- string fileName = Path.GetFileName (assembly.ItemSpec);
- if (!hasExportReference && String.Compare ("Mono.Android.Export.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
- hasExportReference = true;
- addAssembly = true;
- } else if (!haveMonoAndroid && String.Compare ("Mono.Android.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
- haveMonoAndroid = true;
- addAssembly = true;
- } else if (MonoAndroidHelper.FrameworkAssembliesToTreatAsUserAssemblies.Contains (fileName)) {
- if (!bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out value) || !value) {
- string name = Path.GetFileNameWithoutExtension (fileName);
- if (!userAssemblies.ContainsKey (name))
- userAssemblies.Add (name, assembly.ItemSpec);
- addAssembly = true;
- }
+ // ...as well as this
+ Dictionary> userAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedUserAssemblies, SupportedAbis, validate: true);
+ foreach (var kvp in userAssembliesPerArch) {
+ if (!allAssembliesPerArch.TryGetValue (kvp.Key, out Dictionary allAssemblies)) {
+ throw new InvalidOperationException ($"Internal error: found user assemblies for architecture '{kvp.Key}' which isn't found in ResolvedAssemblies");
}
- if (addAssembly) {
- MaybeAddAbiSpecifcAssembly (assembly, fileName);
- if (!allTypemapAssemblies.ContainsKey (assembly.ItemSpec)) {
- allTypemapAssemblies.Add (assembly.ItemSpec, assembly);
+ foreach (var asmKvp in kvp.Value) {
+ if (!allAssemblies.ContainsKey (asmKvp.Key)) {
+ throw new InvalidOperationException ($"Internal error: user assembly '{asmKvp.Value}' not found in ResolvedAssemblies");
}
}
-
- res.Load (MonoAndroidHelper.GetTargetArch (assembly), assembly.ItemSpec);
}
- // However we only want to look for JLO types in user code for Java stub code generation
- foreach (var asm in ResolvedUserAssemblies) {
- if (bool.TryParse (asm.GetMetadata (AndroidSkipJavaStubGeneration), out bool value) && value) {
- Log.LogDebugMessage ($"Skipping Java Stub Generation for {asm.ItemSpec}");
- continue;
- }
- res.Load (MonoAndroidHelper.GetTargetArch (asm), asm.ItemSpec);
- MaybeAddAbiSpecifcAssembly (asm, Path.GetFileName (asm.ItemSpec));
- if (!allTypemapAssemblies.ContainsKey (asm.ItemSpec)) {
- allTypemapAssemblies.Add (asm.ItemSpec, asm);
- }
+ // Now that "never" never happened, we can proceed knowing that at least the assembly sets are the same for each architecture
+ var nativeCodeGenStates = new Dictionary ();
+ bool generateJavaCode = true;
+ NativeCodeGenState? templateCodeGenState = null;
- string name = Path.GetFileNameWithoutExtension (asm.ItemSpec);
- if (!userAssemblies.ContainsKey (name))
- userAssemblies.Add (name, asm.ItemSpec);
- }
+ foreach (var kvp in allAssembliesPerArch) {
+ AndroidTargetArch arch = kvp.Key;
+ Dictionary archAssemblies = kvp.Value;
+ (bool success, NativeCodeGenState? state) = GenerateJavaSourcesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods, generateJavaCode);
- // Step 1 - Find all the JLO types
- var cache = new TypeDefinitionCache ();
- var scanner = new XAJavaTypeScanner (Log, cache) {
- ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
- };
- List allJavaTypes = scanner.GetJavaTypes (allTypemapAssemblies.Values, res);
- var javaTypes = new List ();
+ if (!success) {
+ return;
+ }
- foreach (JavaType jt in allJavaTypes) {
- // Whem marshal methods are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
- // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
- // build and stored in a jar file.
- if ((!useMarshalMethods && !userAssemblies.ContainsKey (jt.Type.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (jt.Type, cache)) {
- continue;
+ if (generateJavaCode) {
+ templateCodeGenState = state;
+ generateJavaCode = false;
}
- javaTypes.Add (jt);
+
+ nativeCodeGenStates.Add (arch, state);
}
- MarshalMethodsClassifier classifier = null;
- if (useMarshalMethods) {
- classifier = new MarshalMethodsClassifier (cache, res, Log);
+ if (templateCodeGenState == null) {
+ throw new InvalidOperationException ($"Internal error: no native code generator state defined");
}
+ JCWGenerator.EnsureAllArchitecturesAreIdentical (Log, nativeCodeGenStates);
- // Step 2 - Generate Java stub code
- var success = CreateJavaSources (javaTypes, cache, classifier, useMarshalMethods);
- if (!success)
- return;
+ NativeCodeGenState.Template = templateCodeGenState;
+ BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
if (useMarshalMethods) {
// We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed
// in order to properly generate wrapper methods in the marshal methods assembly rewriter.
// We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for.
var environmentParser = new EnvironmentFilesParser ();
+ bool brokenExceptionTransitionsEnabled = environmentParser.AreBrokenExceptionTransitionsEnabled (Environments);
- Dictionary assemblyPaths = AddMethodsFromAbiSpecificAssemblies (classifier, res, abiSpecificAssembliesByPath);
+ foreach (var kvp in nativeCodeGenStates) {
+ NativeCodeGenState state = kvp.Value;
+ RewriteMarshalMethods (state, brokenExceptionTransitionsEnabled);
+ state.Classifier.AddSpecialCaseMethods ();
- var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, assemblyPaths, Log);
- rewriter.Rewrite (res, environmentParser.AreBrokenExceptionTransitionsEnabled (Environments));
- }
-
- // Step 3 - Generate type maps
- // Type mappings need to use all the assemblies, always.
- WriteTypeMappings (allJavaTypes, cache);
-
- // We need to save a map of .NET type -> ACW type for resource file fixups
- var managed = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
- var java = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
-
- var managedConflicts = new Dictionary> (0, StringComparer.Ordinal);
- var javaConflicts = new Dictionary> (0, StringComparer.Ordinal);
-
- using (var acw_map = MemoryStreamPool.Shared.CreateStreamWriter ()) {
- foreach (JavaType jt in javaTypes) {
- TypeDefinition type = jt.Type;
- string managedKey = type.FullName.Replace ('/', '.');
- string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
-
- acw_map.Write (type.GetPartialAssemblyQualifiedName (cache));
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
-
- TypeDefinition conflict;
- bool hasConflict = false;
- if (managed.TryGetValue (managedKey, out conflict)) {
- if (!conflict.Module.Name.Equals (type.Module.Name)) {
- if (!managedConflicts.TryGetValue (managedKey, out var list))
- managedConflicts.Add (managedKey, list = new List { conflict.GetPartialAssemblyName (cache) });
- list.Add (type.GetPartialAssemblyName (cache));
- }
- hasConflict = true;
- }
- if (java.TryGetValue (javaKey, out conflict)) {
- if (!conflict.Module.Name.Equals (type.Module.Name)) {
- if (!javaConflicts.TryGetValue (javaKey, out var list))
- javaConflicts.Add (javaKey, list = new List { conflict.GetAssemblyQualifiedName (cache) });
- list.Add (type.GetAssemblyQualifiedName (cache));
- success = false;
- }
- hasConflict = true;
+ Log.LogDebugMessage ($"[{state.TargetArch}] Number of generated marshal methods: {state.Classifier.MarshalMethods.Count}");
+ if (state.Classifier.RejectedMethodCount > 0) {
+ Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.RejectedMethodCount}");
}
- if (!hasConflict) {
- managed.Add (managedKey, type);
- java.Add (javaKey, type);
-
- acw_map.Write (managedKey);
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
-
- acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type, cache).Replace ('/', '.'));
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
+
+ if (state.Classifier.WrappedMethodCount > 0) {
+ // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
+ Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {state.Classifier.WrappedMethodCount}");
}
}
+ }
+
+ bool typemapsAreAbiAgnostic = Debug && !GenerateNativeAssembly;
+ bool first = true;
+ foreach (var kvp in nativeCodeGenStates) {
+ if (!first && typemapsAreAbiAgnostic) {
+ Log.LogDebugMessage ("Typemaps: it's a debug build and type maps are ABI-agnostic, not processing more ABIs");
+ break;
+ }
+
+ NativeCodeGenState state = kvp.Value;
+ first = false;
+ WriteTypeMappings (state);
+ }
- acw_map.Flush ();
- Files.CopyIfStreamChanged (acw_map.BaseStream, AcwMapFile);
+ var acwMapGen = new ACWMapGenerator (Log);
+ if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
+ Log.LogDebugMessage ("ACW map generation failed");
}
- foreach (var kvp in managedConflicts) {
- Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214, kvp.Key, string.Join (", ", kvp.Value));
- Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214_Result, kvp.Key, kvp.Value [0]);
+ IList additionalProviders = MergeManifest (templateCodeGenState, MaybeGetArchAssemblies (userAssembliesPerArch, templateCodeGenState.TargetArch));
+ GenerateAdditionalProviderSources (templateCodeGenState, additionalProviders);
+
+ Dictionary MaybeGetArchAssemblies (Dictionary> dict, AndroidTargetArch arch)
+ {
+ if (!dict.TryGetValue (arch, out Dictionary archDict)) {
+ return new Dictionary (StringComparer.OrdinalIgnoreCase);
+ }
+
+ return archDict;
+ }
+ }
+
+ void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList additionalProviders)
+ {
+ // Create additional runtime provider java sources.
+ string providerTemplateFile = "MonoRuntimeProvider.Bundled.java";
+ string providerTemplate = GetResource (providerTemplateFile);
+
+ foreach (var provider in additionalProviders) {
+ var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider);
+ var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java");
+ Files.CopyIfStringChanged (contents, real_provider);
}
- foreach (var kvp in javaConflicts) {
- Log.LogCodedError ("XA4215", Properties.Resources.XA4215, kvp.Key);
- foreach (var typeName in kvp.Value)
- Log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, kvp.Key, typeName);
+ // Create additional application java sources.
+ StringWriter regCallsWriter = new StringWriter ();
+ regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
+ foreach (TypeDefinition type in codeGenState.JavaTypesForJCW) {
+ if (JavaNativeTypeManager.IsApplication (type, codeGenState.TypeCache) || JavaNativeTypeManager.IsInstrumentation (type, codeGenState.TypeCache)) {
+ if (codeGenState.Classifier != null && !codeGenState.Classifier.FoundDynamicallyRegisteredMethods (type)) {
+ continue;
+ }
+
+ string javaKey = JavaNativeTypeManager.ToJniName (type, codeGenState.TypeCache).Replace ('/', '.');
+ regCallsWriter.WriteLine (
+ "\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
+ type.GetAssemblyQualifiedName (codeGenState.TypeCache),
+ javaKey
+ );
+ }
}
+ regCallsWriter.Close ();
- // Step 3 - Merge [Activity] and friends into AndroidManifest.xml
+ var real_app_dir = Path.Combine (OutputDirectory, "src", "mono", "android", "app");
+ string applicationTemplateFile = "ApplicationRegistration.java";
+ SaveResource (
+ applicationTemplateFile,
+ applicationTemplateFile,
+ real_app_dir,
+ template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())
+ );
+ }
+
+ IList MergeManifest (NativeCodeGenState codeGenState, Dictionary userAssemblies)
+ {
var manifest = new ManifestDocument (ManifestTemplate) {
PackageName = PackageName,
VersionName = VersionName,
ApplicationLabel = ApplicationLabel ?? PackageName,
Placeholders = ManifestPlaceholders,
- Resolver = res,
+ Resolver = codeGenState.Resolver,
SdkDir = AndroidSdkDir,
TargetSdkVersion = AndroidSdkPlatform,
MinSdkVersion = MonoAndroidHelper.ConvertSupportedOSPlatformVersionToApiLevel (SupportedOSPlatformVersion).ToString (),
@@ -342,7 +330,7 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods)
} else if (!string.IsNullOrEmpty (VersionCode)) {
manifest.VersionCode = VersionCode;
}
- manifest.Assemblies.AddRange (userAssemblies.Values);
+ manifest.Assemblies.AddRange (userAssemblies.Values.Select (item => item.ItemSpec));
if (!String.IsNullOrWhiteSpace (CheckedBuild)) {
// We don't validate CheckedBuild value here, this will be done in BuildApk. We just know that if it's
@@ -351,216 +339,67 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods)
manifest.ForceExtractNativeLibs = true;
}
- var additionalProviders = manifest.Merge (Log, cache, allJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
+ IList additionalProviders = manifest.Merge (Log, codeGenState.TypeCache, codeGenState.AllJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
// Only write the new manifest if it actually changed
if (manifest.SaveIfChanged (Log, MergedAndroidManifestOutput)) {
Log.LogDebugMessage ($"Saving: {MergedAndroidManifestOutput}");
}
- // Create additional runtime provider java sources.
- string providerTemplateFile = "MonoRuntimeProvider.Bundled.java";
- string providerTemplate = GetResource (providerTemplateFile);
-
- foreach (var provider in additionalProviders) {
- var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider);
- var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java");
- Files.CopyIfStringChanged (contents, real_provider);
- }
-
- // Create additional application java sources.
- StringWriter regCallsWriter = new StringWriter ();
- regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
- foreach (JavaType jt in javaTypes) {
- TypeDefinition type = jt.Type;
- if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
- if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) {
- continue;
- }
-
- string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
- regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
- type.GetAssemblyQualifiedName (cache), javaKey);
- }
- }
- regCallsWriter.Close ();
-
- var real_app_dir = Path.Combine (OutputDirectory, "src", "mono", "android", "app");
- string applicationTemplateFile = "ApplicationRegistration.java";
- SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir,
- template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));
-
- if (useMarshalMethods) {
- classifier.AddSpecialCaseMethods ();
-
- Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}");
-
- if (classifier.RejectedMethodCount > 0) {
- Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}");
- }
-
- if (classifier.WrappedMethodCount > 0) {
- // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
- Log.LogDebugMessage ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}");
- }
- }
-
- void MaybeAddAbiSpecifcAssembly (ITaskItem assembly, string fileName)
- {
- if (abiSpecificAssembliesByPath == null) {
- return;
- }
-
- string? abi = assembly.GetMetadata ("Abi");
- if (!String.IsNullOrEmpty (abi)) {
- if (!abiSpecificAssembliesByPath.TryGetValue (fileName, out List? items)) {
- items = new List ();
- abiSpecificAssembliesByPath.Add (fileName, items);
- }
-
- items.Add (assembly);
- }
- }
+ return additionalProviders;
}
- AssemblyDefinition LoadAssembly (string path, XAAssemblyResolver? resolver = null)
+ (bool success, NativeCodeGenState? stubsState) GenerateJavaSourcesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods, bool generateJavaCode)
{
- string pdbPath = Path.ChangeExtension (path, ".pdb");
- var readerParameters = new ReaderParameters {
- AssemblyResolver = resolver,
- InMemory = false,
- ReadingMode = ReadingMode.Immediate,
- ReadSymbols = File.Exists (pdbPath),
- ReadWrite = false,
- };
-
- MemoryMappedViewStream? viewStream = null;
- try {
- // Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
- using var fileStream = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
- using var mappedFile = MemoryMappedFile.CreateFromFile (
- fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
- viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
+ XAAssemblyResolver resolver = MakeResolver (useMarshalMethods, arch, assemblies);
+ var tdCache = new TypeDefinitionCache ();
+ (List allJavaTypes, List javaTypesForJCW) = ScanForJavaTypes (resolver, tdCache, assemblies, userAssemblies, useMarshalMethods);
+ var jcwContext = new JCWGeneratorContext (arch, resolver, assemblies.Values, javaTypesForJCW, tdCache, useMarshalMethods);
+ var jcwGenerator = new JCWGenerator (Log, jcwContext);
+ bool success;
- AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, readerParameters).Assembly;
-
- // We transferred the ownership of the viewStream to the collection.
- viewStream = null;
-
- return result;
- } finally {
- viewStream?.Dispose ();
+ if (generateJavaCode) {
+ success = jcwGenerator.GenerateAndClassify (AndroidSdkPlatform, outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass);
+ } else {
+ success = jcwGenerator.Classify (AndroidSdkPlatform);
}
- }
- bool CreateJavaSources (IEnumerable newJavaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods)
- {
- if (useMarshalMethods && classifier == null) {
- throw new ArgumentNullException (nameof (classifier));
+ if (!success) {
+ return (false, null);
}
- string outputPath = Path.Combine (OutputDirectory, "src");
- string monoInit = GetMonoInitSource (AndroidSdkPlatform);
- bool hasExportReference = ResolvedAssemblies.Any (assembly => Path.GetFileName (assembly.ItemSpec) == "Mono.Android.Export.dll");
- bool generateOnCreateOverrides = int.Parse (AndroidSdkPlatform) <= 10;
+ return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, jcwGenerator.Classifier));
+ }
- var reader_options = new CallableWrapperReaderOptions {
- DefaultApplicationJavaClass = ApplicationJavaClass,
- DefaultGenerateOnCreateOverrides = generateOnCreateOverrides,
- DefaultMonoRuntimeInitialization = monoInit,
- MethodClassifier = classifier,
- };
- var writer_options = new CallableWrapperWriterOptions {
- CodeGenerationTarget = JavaPeerStyle.XAJavaInterop1
+ (List allJavaTypes, List javaTypesForJCW) ScanForJavaTypes (XAAssemblyResolver res, TypeDefinitionCache cache, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods)
+ {
+ var scanner = new XAJavaTypeScanner (res.TargetArch, Log, cache) {
+ ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
};
+ List allJavaTypes = scanner.GetJavaTypes (assemblies.Values, res);
+ var javaTypesForJCW = new List ();
- bool ok = true;
- foreach (JavaType jt in newJavaTypes) {
- TypeDefinition t = jt.Type; // JCW generator doesn't care about ABI-specific types or token ids
- if (t.IsInterface) {
- // Interfaces are in typemap but they shouldn't have JCW generated for them
+ foreach (TypeDefinition type in allJavaTypes) {
+ // When marshal methods are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
+ // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
+ // build and stored in a jar file.
+ if ((!useMarshalMethods && !userAssemblies.ContainsKey (type.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (type, cache)) {
continue;
}
-
- using (var writer = MemoryStreamPool.Shared.CreateStreamWriter ()) {
- try {
- var jcw_type = CecilImporter.CreateType (t, cache, reader_options);
-
- jcw_type.Generate (writer, writer_options);
-
- if (useMarshalMethods) {
- if (classifier.FoundDynamicallyRegisteredMethods (t)) {
- Log.LogWarning ($"Type '{t.GetAssemblyQualifiedName (cache)}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods.");
- }
- }
-
- writer.Flush ();
-
- var path = jcw_type.GetDestinationPath (outputPath);
- Files.CopyIfStreamChanged (writer.BaseStream, path);
- if (jcw_type.HasExport && !hasExportReference)
- Diagnostic.Error (4210, Properties.Resources.XA4210);
- } catch (XamarinAndroidException xae) {
- ok = false;
- Log.LogError (
- subcategory: "",
- errorCode: "XA" + xae.Code,
- helpKeyword: string.Empty,
- file: xae.SourceFile,
- lineNumber: xae.SourceLine,
- columnNumber: 0,
- endLineNumber: 0,
- endColumnNumber: 0,
- message: xae.MessageWithoutCode,
- messageArgs: Array.Empty