public bool Generate(bool skipJniAddNativeMethodRegistrationAttributeScan, List <TypeDefinition> javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState) { if (String.IsNullOrEmpty(outputDirectory)) { throw new ArgumentException("must not be null or empty", nameof(outputDirectory)); } if (!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); } int assemblyId = 0; int maxJavaNameLength = 0; int maxModuleFileNameLength = 0; var knownAssemblies = new Dictionary <string, int> (StringComparer.Ordinal); var tempModules = new Dictionary <byte[], ModuleData> (); Dictionary <AssemblyDefinition, int> moduleCounter = null; var mvidCache = new Dictionary <Guid, byte[]> (); appConfState = new ApplicationConfigTaskState { JniAddNativeMethodRegistrationAttributePresent = skipJniAddNativeMethodRegistrationAttributeScan }; foreach (TypeDefinition td in javaTypes) { UpdateApplicationConfig(td, appConfState); string assemblyName = td.Module.Assembly.FullName; if (!knownAssemblies.ContainsKey(assemblyName)) { assemblyId++; knownAssemblies.Add(assemblyName, assemblyId); } // We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding // byte array representation and on the runtime we need the latter in order to be able to binary search // through the module array. byte[] moduleUUID; if (!mvidCache.TryGetValue(td.Module.Mvid, out moduleUUID)) { moduleUUID = td.Module.Mvid.ToByteArray(); mvidCache.Add(td.Module.Mvid, moduleUUID); } ModuleData moduleData; if (!tempModules.TryGetValue(moduleUUID, out moduleData)) { if (moduleCounter == null) { moduleCounter = new Dictionary <AssemblyDefinition, int> (); } moduleData = new ModuleData { Mvid = td.Module.Mvid, MvidBytes = moduleUUID, Assembly = td.Module.Assembly, AssemblyName = td.Module.Assembly.Name.Name, TypesScratch = new Dictionary <string, TypeMapEntry> (StringComparer.Ordinal), DuplicateTypes = new Dictionary <uint, TypeMapEntry> (), }; tempModules.Add(moduleUUID, moduleData); if (!generateNativeAssembly) { int moduleNum; if (!moduleCounter.TryGetValue(moduleData.Assembly, out moduleNum)) { moduleNum = 0; moduleCounter [moduleData.Assembly] = 0; } else { moduleNum++; moduleCounter [moduleData.Assembly] = moduleNum; } string fileName = $"{moduleData.Assembly.Name.Name}.{moduleNum}.typemap"; moduleData.OutputFilePath = Path.Combine(outputDirectory, fileName); if (maxModuleFileNameLength < fileName.Length) { maxModuleFileNameLength = fileName.Length; } } } string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(td); var entry = new TypeMapEntry { JavaName = javaName, JavaNameLength = outputEncoding.GetByteCount(javaName), ManagedTypeName = td.FullName, Token = td.MetadataToken.ToUInt32(), AssemblyNameIndex = knownAssemblies [assemblyName] }; if (generateNativeAssembly) { if (entry.JavaNameLength > maxJavaNameLength) { maxJavaNameLength = entry.JavaNameLength; } } if (moduleData.TypesScratch.ContainsKey(entry.JavaName)) { // This is disabled because it costs a lot of time (around 150ms per standard XF Integration app // build) and has no value for the end user. The message is left here because it may be useful to us // in our devloop at some point. //logger ($"Warning: duplicate Java type name '{entry.JavaName}' in assembly '{moduleData.AssemblyName}' (new token: {entry.Token})."); moduleData.DuplicateTypes.Add(entry.Token, entry); } else { moduleData.TypesScratch.Add(entry.JavaName, entry); } } var modules = tempModules.Values.ToArray(); Array.Sort(modules, new ModuleUUIDArrayComparer()); var typeMapEntryComparer = new TypeMapEntryArrayComparer(); foreach (ModuleData module in modules) { if (module.TypesScratch.Count == 0) { module.Types = new TypeMapEntry[0]; continue; } module.Types = module.TypesScratch.Values.ToArray(); Array.Sort(module.Types, typeMapEntryComparer); } NativeTypeMappingData data; if (!generateNativeAssembly) { string typeMapIndexPath = Path.Combine(outputDirectory, "typemap.index"); using (var indexWriter = MemoryStreamPool.Shared.CreateBinaryWriter()) { OutputModules(modules, indexWriter, maxModuleFileNameLength + 1); indexWriter.Flush(); MonoAndroidHelper.CopyIfStreamChanged(indexWriter.BaseStream, typeMapIndexPath); } GeneratedBinaryTypeMaps.Add(typeMapIndexPath); data = new NativeTypeMappingData(logger, new ModuleData[0], 0); } else { data = new NativeTypeMappingData(logger, modules, maxJavaNameLength + 1); } NativeAssemblerTargetProvider asmTargetProvider; bool sharedBitsWritten = false; bool sharedIncludeUsesAbiPrefix; foreach (string abi in supportedAbis) { sharedIncludeUsesAbiPrefix = false; switch (abi.Trim()) { case "armeabi-v7a": asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: false); sharedIncludeUsesAbiPrefix = true; // ARMv7a is "special", it uses different directive prefix // than the others and the "shared" code won't build for it break; case "arm64-v8a": asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: true); break; case "x86": asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: false); break; case "x86_64": asmTargetProvider = new X86NativeAssemblerTargetProvider(is64Bit: true); break; default: throw new InvalidOperationException($"Unknown ABI {abi}"); } var generator = new TypeMappingNativeAssemblyGenerator(asmTargetProvider, data, Path.Combine(outputDirectory, "typemaps"), sharedBitsWritten, sharedIncludeUsesAbiPrefix); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter(outputEncoding)) { generator.Write(sw); sw.Flush(); MonoAndroidHelper.CopyIfStreamChanged(sw.BaseStream, generator.MainSourceFile); if (!sharedIncludeUsesAbiPrefix) { sharedBitsWritten = true; } } } return(true); }
bool GenerateRelease(bool skipJniAddNativeMethodRegistrationAttributeScan, List <TypeDefinition> javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) { int assemblyId = 0; int maxJavaNameLength = 0; var knownAssemblies = new Dictionary <string, int> (StringComparer.Ordinal); var tempModules = new Dictionary <byte[], ModuleReleaseData> (); Dictionary <AssemblyDefinition, int> moduleCounter = null; var mvidCache = new Dictionary <Guid, byte[]> (); foreach (TypeDefinition td in javaTypes) { UpdateApplicationConfig(td, appConfState); string assemblyName = td.Module.Assembly.FullName; if (!knownAssemblies.ContainsKey(assemblyName)) { assemblyId++; knownAssemblies.Add(assemblyName, assemblyId); } // We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding // byte array representation and on the runtime we need the latter in order to be able to binary search // through the module array. byte[] moduleUUID; if (!mvidCache.TryGetValue(td.Module.Mvid, out moduleUUID)) { moduleUUID = td.Module.Mvid.ToByteArray(); mvidCache.Add(td.Module.Mvid, moduleUUID); } ModuleReleaseData moduleData; if (!tempModules.TryGetValue(moduleUUID, out moduleData)) { if (moduleCounter == null) { moduleCounter = new Dictionary <AssemblyDefinition, int> (); } moduleData = new ModuleReleaseData { Mvid = td.Module.Mvid, MvidBytes = moduleUUID, Assembly = td.Module.Assembly, AssemblyName = td.Module.Assembly.Name.Name, TypesScratch = new Dictionary <string, TypeMapReleaseEntry> (StringComparer.Ordinal), DuplicateTypes = new Dictionary <uint, TypeMapReleaseEntry> (), }; tempModules.Add(moduleUUID, moduleData); } string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(td); // We will ignore generic types and interfaces when generating the Java to Managed map, but we must not // omit them from the table we output - we need the same number of entries in both java-to-managed and // managed-to-java tables. `SkipInJavaToManaged` set to `true` will cause the native assembly generator // to output `0` as the token id for the type, thus effectively causing the runtime unable to match such // a Java type name to a managed type. This fixes https://github.com/xamarin/xamarin-android/issues/4660 var entry = new TypeMapReleaseEntry { JavaName = javaName, JavaNameLength = outputEncoding.GetByteCount(javaName), ManagedTypeName = td.FullName, Token = td.MetadataToken.ToUInt32(), AssemblyNameIndex = knownAssemblies [assemblyName], SkipInJavaToManaged = ShouldSkipInJavaToManaged(td), }; if (entry.JavaNameLength > maxJavaNameLength) { maxJavaNameLength = entry.JavaNameLength; } if (moduleData.TypesScratch.ContainsKey(entry.JavaName)) { // This is disabled because it costs a lot of time (around 150ms per standard XF Integration app // build) and has no value for the end user. The message is left here because it may be useful to us // in our devloop at some point. //logger ($"Warning: duplicate Java type name '{entry.JavaName}' in assembly '{moduleData.AssemblyName}' (new token: {entry.Token})."); moduleData.DuplicateTypes.Add(entry.Token, entry); } else { moduleData.TypesScratch.Add(entry.JavaName, entry); } } var modules = tempModules.Values.ToArray(); Array.Sort(modules, new ModuleUUIDArrayComparer()); var typeMapEntryComparer = new TypeMapEntryArrayComparer(); foreach (ModuleReleaseData module in modules) { if (module.TypesScratch.Count == 0) { module.Types = new TypeMapReleaseEntry[0]; continue; } module.Types = module.TypesScratch.Values.ToArray(); Array.Sort(module.Types, typeMapEntryComparer); } NativeTypeMappingData data; data = new NativeTypeMappingData(logger, modules, maxJavaNameLength + 1); GenerateNativeAssembly( (NativeAssemblerTargetProvider asmTargetProvider, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { return(new TypeMappingReleaseNativeAssemblyGenerator(asmTargetProvider, data, outputDirectory, sharedBitsWritten, sharedIncludeUsesAbiPrefix)); } ); return(true); }