bool CreateJavaSources(IEnumerable <TypeDefinition> javaTypes, TypeDefinitionCache cache) { string outputPath = Path.Combine(OutputDirectory, "src"); string monoInit = GetMonoInitSource(AndroidSdkPlatform, UseSharedRuntime); bool hasExportReference = ResolvedAssemblies.Any(assembly => Path.GetFileName(assembly.ItemSpec) == "Mono.Android.Export.dll"); bool generateOnCreateOverrides = int.Parse(AndroidSdkPlatform) <= 10; bool ok = true; foreach (var t in javaTypes) { using (var writer = MemoryStreamPool.Shared.CreateStreamWriter()) { try { var jti = new JavaCallableWrapperGenerator(t, Log.LogWarning, cache) { GenerateOnCreateOverrides = generateOnCreateOverrides, ApplicationJavaClass = ApplicationJavaClass, MonoRuntimeInitialization = monoInit, }; jti.Generate(writer); writer.Flush(); var path = jti.GetDestinationPath(outputPath); MonoAndroidHelper.CopyIfStreamChanged(writer.BaseStream, path); if (jti.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: new object [0] ); } catch (DirectoryNotFoundException ex) { ok = false; if (OS.IsWindows) { Diagnostic.Error(5301, Properties.Resources.XA5301, t.FullName, ex); } else { Diagnostic.Error(4209, Properties.Resources.XA4209, t.FullName, ex); } } catch (Exception ex) { ok = false; Diagnostic.Error(4209, Properties.Resources.XA4209, t.FullName, ex); } } } return(ok); }
void AddEnvironment() { var environment = new StringWriter() { NewLine = "\n", }; if (EnableLLVM) { WriteEnvironment("mono.llvm", "true"); } AotMode aotMode; if (AndroidAotMode != null && Aot.GetAndroidAotMode(AndroidAotMode, out aotMode)) { WriteEnvironment("mono.aot", aotMode.ToString().ToLowerInvariant()); } bool haveLogLevel = false; bool haveMonoDebug = false; bool havebuildId = false; bool haveHttpMessageHandler = false; bool haveTlsProvider = false; bool haveMonoGCParams = false; SequencePointsMode sequencePointsMode; if (!Aot.TryGetSequencePointsMode(AndroidSequencePointsMode, out sequencePointsMode)) { sequencePointsMode = SequencePointsMode.None; } foreach (ITaskItem env in Environments ?? new TaskItem[0]) { environment.WriteLine("\t\t// Source File: {0}", env.ItemSpec); foreach (string line in File.ReadLines(env.ItemSpec)) { var lineToWrite = line; if (lineToWrite.StartsWith("MONO_LOG_LEVEL=", StringComparison.Ordinal)) { haveLogLevel = true; } if (lineToWrite.StartsWith("MONO_GC_PARAMS=", StringComparison.Ordinal)) { haveMonoGCParams = true; } if (lineToWrite.StartsWith("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) { havebuildId = true; } if (lineToWrite.StartsWith("MONO_DEBUG=", StringComparison.Ordinal)) { haveMonoDebug = true; if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains("gen-compact-seq-points")) { lineToWrite = line + ",gen-compact-seq-points"; } } if (lineToWrite.StartsWith("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) { haveHttpMessageHandler = true; } if (lineToWrite.StartsWith("XA_TLS_PROVIDER=", StringComparison.Ordinal)) { haveTlsProvider = true; } WriteEnvironmentLine(lineToWrite); } } if (_Debug && !haveLogLevel) { WriteEnvironment(defaultLogLevel[0], defaultLogLevel[1]); } if (sequencePointsMode != SequencePointsMode.None && !haveMonoDebug) { WriteEnvironment(defaultMonoDebug[0], defaultMonoDebug[1]); } if (!havebuildId) { WriteEnvironment("XAMARIN_BUILD_ID", buildId.ToString()); } if (!haveHttpMessageHandler) { if (HttpClientHandlerType == null) { WriteEnvironment(defaultHttpMessageHandler[0], defaultHttpMessageHandler[1]); } else { WriteEnvironment("XA_HTTP_CLIENT_HANDLER_TYPE", HttpClientHandlerType.Trim()); } } if (!haveTlsProvider) { if (TlsProvider == null) { WriteEnvironment(defaultTlsProvider[0], defaultTlsProvider[1]); } else { WriteEnvironment("XA_TLS_PROVIDER", TlsProvider.Trim()); } } if (!haveMonoGCParams) { if (EnableSGenConcurrent) { WriteEnvironment("MONO_GC_PARAMS", "major=marksweep-conc"); } else { WriteEnvironment("MONO_GC_PARAMS", "major=marksweep"); } } string environmentTemplate; using (var sr = new StreamReader(typeof(BuildApk).Assembly.GetManifestResourceStream(EnvironmentFileName))) { environmentTemplate = sr.ReadToEnd(); } using (var ms = new MemoryStream()) { using (var sw = new StreamWriter(ms)) { sw.Write(environmentTemplate.Replace("//@ENVVARS@", environment.ToString())); sw.Flush(); string dest = Path.GetFullPath(Path.Combine(EnvironmentOutputDirectory, EnvironmentFileName)); MonoAndroidHelper.CopyIfStreamChanged(ms, dest); } } void WriteEnvironment(string name, string value) { environment.WriteLine($"\t\t\"{ValidJavaString (name)}\", \"{ValidJavaString (value)}\","); } void WriteEnvironmentLine(string line) { if (String.IsNullOrEmpty(line)) { return; } string[] nv = line.Split(new char[] { '=' }, 2); WriteEnvironment(nv[0].Trim(), nv.Length < 2 ? String.Empty : nv[1].Trim()); } string ValidJavaString(string s) { return(s.Replace("\"", "\\\"")); } }
protected override void WriteSymbols(StreamWriter output) { bool haveJavaToManaged = data.JavaToManagedMap != null && data.JavaToManagedMap.Count > 0; bool haveManagedToJava = data.ManagedToJavaMap != null && data.ManagedToJavaMap.Count > 0; using (var sharedOutput = MemoryStreamPool.Shared.CreateStreamWriter(output.Encoding)) { WriteSharedBits(sharedOutput, haveJavaToManaged, haveManagedToJava); sharedOutput.Flush(); MonoAndroidHelper.CopyIfStreamChanged(sharedOutput.BaseStream, SharedIncludeFile); } if (haveJavaToManaged || haveManagedToJava) { output.Write(Indent); output.Write(".include"); output.Write(Indent); output.Write('"'); output.Write(Path.GetFileName(SharedIncludeFile)); output.WriteLine('"'); output.WriteLine(); } uint size = 0; WriteCommentLine(output, "Managed to java map: START", indent: false); WriteSection(output, $".data.rel.{ManagedToJavaSymbol}", hasStrings: false, writable: true); WriteStructureSymbol(output, ManagedToJavaSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: false); if (haveManagedToJava) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.ManagedToJavaMap) { size += WritePointer(output, entry.ManagedLabel); size += WritePointer(output, entry.JavaLabel); } } WriteStructureSize(output, ManagedToJavaSymbol, size, alwaysWriteSize: true); WriteCommentLine(output, "Managed to java map: END", indent: false); output.WriteLine(); size = 0; WriteCommentLine(output, "Java to managed map: START", indent: false); WriteSection(output, $".data.rel.{JavaToManagedSymbol}", hasStrings: false, writable: true); WriteStructureSymbol(output, JavaToManagedSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: false); if (haveJavaToManaged) { foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) { size += WritePointer(output, entry.JavaLabel); TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry; size += WritePointer(output, managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedLabel); } } WriteStructureSize(output, JavaToManagedSymbol, size, alwaysWriteSize: true); WriteCommentLine(output, "Java to managed map: END", indent: false); output.WriteLine(); // MUST match src/monodroid/xamarin-app.hh WriteCommentLine(output, "TypeMap structure"); WriteSection(output, $".data.rel.ro.{TypeMapSymbol}", hasStrings: false, writable: true); WriteStructureSymbol(output, TypeMapSymbol, alignBits: TargetProvider.DebugTypeMapAlignBits, isGlobal: true); size = WriteStructure(output, packed: false, structureWriter: () => WriteTypeMapStruct(output)); WriteStructureSize(output, TypeMapSymbol, size); }
void Run(DirectoryAssemblyResolver res) { PackageNamingPolicy pnp; JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse(PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; foreach (var dir in FrameworkDirectories) { if (Directory.Exists(dir.ItemSpec)) { res.SearchDirectories.Add(dir.ItemSpec); } } // Put every assembly we'll need in the resolver bool hasExportReference = false; bool haveMonoAndroid = false; var allTypemapAssemblies = new HashSet <string> (StringComparer.OrdinalIgnoreCase); var userAssemblies = new Dictionary <string, string> (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; } 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; } } if (addAssembly) { allTypemapAssemblies.Add(assembly.ItemSpec); } res.Load(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; } allTypemapAssemblies.Add(asm.ItemSpec); userAssemblies.Add(Path.GetFileNameWithoutExtension(asm.ItemSpec), asm.ItemSpec); } // Step 1 - Find all the JLO types var cache = new TypeDefinitionCache(); var scanner = new JavaTypeScanner(this.CreateTaskLogger(), cache) { ErrorOnCustomJavaObject = ErrorOnCustomJavaObject, }; List <TypeDefinition> allJavaTypes = scanner.GetJavaTypes(allTypemapAssemblies, res); // Step 2 - Generate type maps // Type mappings need to use all the assemblies, always. WriteTypeMappings(allJavaTypes); var javaTypes = new List <TypeDefinition> (); foreach (TypeDefinition td in allJavaTypes) { if (!userAssemblies.ContainsKey(td.Module.Assembly.Name.Name) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration(td, cache)) { continue; } javaTypes.Add(td); } // Step 3 - Generate Java stub code var success = CreateJavaSources(javaTypes, cache); if (!success) { return; } // We need to save a map of .NET type -> ACW type for resource file fixups var managed = new Dictionary <string, TypeDefinition> (javaTypes.Count, StringComparer.Ordinal); var java = new Dictionary <string, TypeDefinition> (javaTypes.Count, StringComparer.Ordinal); var managedConflicts = new Dictionary <string, List <string> > (0, StringComparer.Ordinal); var javaConflicts = new Dictionary <string, List <string> > (0, StringComparer.Ordinal); using (var acw_map = MemoryStreamPool.Shared.CreateStreamWriter(Encoding.Default)) { foreach (TypeDefinition type in javaTypes) { string managedKey = type.FullName.Replace('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName(type).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 (!managedConflicts.TryGetValue(managedKey, out var list)) { managedConflicts.Add(managedKey, list = new List <string> { conflict.GetPartialAssemblyName(cache) }); } list.Add(type.GetPartialAssemblyName(cache)); hasConflict = true; } if (java.TryGetValue(javaKey, out conflict)) { if (!javaConflicts.TryGetValue(javaKey, out var list)) { javaConflicts.Add(javaKey, list = new List <string> { conflict.GetAssemblyQualifiedName(cache) }); } list.Add(type.GetAssemblyQualifiedName(cache)); success = false; hasConflict = true; } 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(); } } acw_map.Flush(); MonoAndroidHelper.CopyIfStreamChanged(acw_map.BaseStream, AcwMapFile); } 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]); } 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); } } // Step 3 - Merge [Activity] and friends into AndroidManifest.xml var manifest = new ManifestDocument(ManifestTemplate); manifest.PackageName = PackageName; manifest.ApplicationName = ApplicationName ?? PackageName; manifest.Placeholders = ManifestPlaceholders; manifest.Assemblies.AddRange(userAssemblies.Values); manifest.Resolver = res; manifest.SdkDir = AndroidSdkDir; manifest.SdkVersion = AndroidSdkPlatform; manifest.Debug = Debug; manifest.MultiDex = MultiDex; manifest.NeedsInternet = NeedsInternet; manifest.InstantRunEnabled = InstantRunEnabled; var additionalProviders = manifest.Merge(Log, cache, 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 = UseSharedRuntime ? "MonoRuntimeProvider.Shared.java" : "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"); MonoAndroidHelper.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 (var type in javaTypes) { if (JavaNativeTypeManager.IsApplication(type, cache) || JavaNativeTypeManager.IsInstrumentation(type, cache)) { string javaKey = JavaNativeTypeManager.ToJniName(type).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())); }
public static bool CreateJavaSources(TaskLoggingHelper log, IEnumerable <TypeDefinition> javaTypes, string outputPath, string applicationJavaClass, string androidSdkPlatform, bool useSharedRuntime, bool generateOnCreateOverrides, bool hasExportReference) { string monoInit = GetMonoInitSource(androidSdkPlatform, useSharedRuntime); bool ok = true; using (var memoryStream = new MemoryStream()) using (var writer = new StreamWriter(memoryStream)) { foreach (var t in javaTypes) { //Reset for reuse memoryStream.SetLength(0); try { var jti = new JavaCallableWrapperGenerator(t, log.LogWarning) { GenerateOnCreateOverrides = generateOnCreateOverrides, ApplicationJavaClass = applicationJavaClass, MonoRuntimeInitialization = monoInit, }; jti.Generate(writer); writer.Flush(); var path = jti.GetDestinationPath(outputPath); MonoAndroidHelper.CopyIfStreamChanged(memoryStream, path); if (jti.HasExport && !hasExportReference) { Diagnostic.Error(4210, "You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute."); } } 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: new object [0] ); } catch (DirectoryNotFoundException ex) { ok = false; if (OS.IsWindows) { Diagnostic.Error(5301, "Failed to create JavaTypeInfo for class: {0} due to MAX_PATH: {1}", t.FullName, ex); } else { Diagnostic.Error(4209, "Failed to create JavaTypeInfo for class: {0} due to {1}", t.FullName, ex); } } catch (Exception ex) { ok = false; Diagnostic.Error(4209, "Failed to create JavaTypeInfo for class: {0} due to {1}", t.FullName, ex); } } } return(ok); }
// Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*] // Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*] void Extract( IDictionary <string, ITaskItem> jars, ICollection <ITaskItem> resolvedResourceDirectories, ICollection <ITaskItem> resolvedAssetDirectories, ICollection <ITaskItem> resolvedEnvironments) { // lets "upgrade" the old directory. string oldPath = Path.GetFullPath(Path.Combine(OutputImportDirectory, "..", "__library_projects__")); if (!OutputImportDirectory.Contains("__library_projects__") && Directory.Exists(oldPath)) { MonoAndroidHelper.SetDirectoryWriteable(Path.Combine(oldPath, "..")); Directory.Delete(oldPath, recursive: true); } var outdir = Path.GetFullPath(OutputImportDirectory); Directory.CreateDirectory(outdir); bool skip; foreach (var assemblyItem in Assemblies) { var assemblyPath = assemblyItem.ItemSpec; var fileName = Path.GetFileName(assemblyPath); if (MonoAndroidHelper.IsFrameworkAssembly(fileName) && !MonoAndroidHelper.FrameworkEmbeddedJarLookupTargets.Contains(fileName) && !MonoAndroidHelper.FrameworkEmbeddedNativeLibraryAssemblies.Contains(fileName)) { Log.LogDebugMessage($"Skipping framework assembly '{fileName}'."); continue; } if (!File.Exists(assemblyPath)) { Log.LogDebugMessage($"Skipping non-existent dependency '{assemblyPath}'."); continue; } if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceExtraction), out skip) && skip) { Log.LogDebugMessage("Skipping resource extraction for '{0}' .", assemblyPath); continue; } string assemblyFileName = Path.GetFileNameWithoutExtension(assemblyPath); string assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyFileName); string outDirForDll = Path.Combine(OutputImportDirectory, assemblyIdentName); string importsDir = Path.Combine(outDirForDll, ImportsDirectory); string nativeimportsDir = Path.Combine(outDirForDll, NativeImportsDirectory); string resDir = Path.Combine(importsDir, "res"); string assetsDir = Path.Combine(importsDir, "assets"); // Skip already-extracted resources. bool updated = false; string assemblyHash = MonoAndroidHelper.HashFile(assemblyPath); string stamp = Path.Combine(outdir, assemblyIdentName + ".stamp"); string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null; if (assemblyHash == stampHash) { Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath); if (Directory.Exists(importsDir)) { foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories)) { AddJar(jars, Path.GetFullPath(file)); } } if (Directory.Exists(resDir)) { var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, assemblyPath }, }); if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceProcessing), out skip) && skip) { taskItem.SetMetadata(AndroidSkipResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly)) { resolvedEnvironments.Add(new TaskItem(env, new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } continue; } Log.LogDebugMessage($"Refreshing {assemblyFileName}.dll"); using (var pe = new PEReader(File.OpenRead(assemblyPath))) { var reader = pe.GetMetadataReader(); foreach (var handle in reader.ManifestResources) { var resource = reader.GetManifestResource(handle); string name = reader.GetString(resource.Name); // android environment files if (name.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase)) { var outFile = Path.Combine(outDirForDll, name); using (var stream = pe.GetEmbeddedResourceStream(resource)) { updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, outFile); } resolvedEnvironments.Add(new TaskItem(Path.GetFullPath(outFile), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } // embedded jars (EmbeddedJar, EmbeddedReferenceJar) else if (name.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase)) { using (var stream = pe.GetEmbeddedResourceStream(resource)) { AddJar(jars, importsDir, name, assemblyPath); updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, Path.Combine(importsDir, name)); } } // embedded native libraries else if (name == "__AndroidNativeLibraries__.zip") { List <string> files = new List <string> (); using (var stream = pe.GetEmbeddedResourceStream(resource)) using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) { try { updated |= Files.ExtractAll(zip, nativeimportsDir, modifyCallback: (entryFullName) => { files.Add(Path.GetFullPath(Path.Combine(nativeimportsDir, entryFullName))); return(entryFullName .Replace("native_library_imports\\", "") .Replace("native_library_imports/", "")); }, deleteCallback: (fileToDelete) => { return(!files.Contains(fileToDelete)); }); } catch (PathTooLongException ex) { Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex); return; } catch (NotSupportedException ex) { Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex); return; } } } // embedded AndroidResourceLibrary archive else if (name == "__AndroidLibraryProjects__.zip") { // temporarily extracted directory will look like: // __library_projects__/[dllname]/[library_project_imports | jlibs]/bin using (var stream = pe.GetEmbeddedResourceStream(resource)) using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) { try { updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => { var path = entryFullName .Replace("library_project_imports\\", "") .Replace("library_project_imports/", ""); if (path.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { AddJar(jars, importsDir, path, assemblyPath); } return(path); }, deleteCallback: (fileToDelete) => { return(!jars.ContainsKey(fileToDelete)); }); } catch (PathTooLongException ex) { Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex); return; } catch (NotSupportedException ex) { Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex); return; } } // We used to *copy* the resources to overwrite other resources, // which resulted in missing resource issue. // Here we replaced copy with use of '-S' option and made it to work. if (Directory.Exists(resDir)) { var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } }); if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceProcessing), out skip) && skip) { taskItem.SetMetadata(AndroidSkipResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } } } } if (Directory.Exists(importsDir)) { // Delete unknown files in the top directory of importsDir foreach (var file in Directory.EnumerateFiles(importsDir, "*")) { var fullPath = Path.GetFullPath(file); if (file.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase) && !resolvedEnvironments.Any(x => x.ItemSpec == fullPath)) { Log.LogDebugMessage($"Deleting unknown AndroidEnvironment file: {Path.GetFileName (file)}"); File.Delete(fullPath); updated = true; } else if (file.EndsWith(".jar", StringComparison.OrdinalIgnoreCase) && !jars.ContainsKey(fullPath)) { Log.LogDebugMessage($"Deleting unknown jar: {Path.GetFileName (file)}"); File.Delete(fullPath); updated = true; } } if (assemblyHash != stampHash) { Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}"); //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes WriteAllText(stamp, assemblyHash, preserveTimestamp: !updated); } } } foreach (var aarFile in AarLibraries ?? new ITaskItem[0]) { if (!File.Exists(aarFile.ItemSpec)) { continue; } string aarIdentityName = Path.GetFileNameWithoutExtension(aarFile.ItemSpec); aarIdentityName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(aarIdentityName); string outDirForDll = Path.Combine(OutputImportDirectory, aarIdentityName); string importsDir = Path.Combine(outDirForDll, ImportsDirectory); string resDir = Path.Combine(importsDir, "res"); string assetsDir = Path.Combine(importsDir, "assets"); bool updated = false; string aarHash = MonoAndroidHelper.HashFile(aarFile.ItemSpec); string stamp = Path.Combine(outdir, aarIdentityName + ".stamp"); string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null; var aarFullPath = Path.GetFullPath(aarFile.ItemSpec); if (aarHash == stampHash) { Log.LogDebugMessage("Skipped {0}: extracted files are up to date", aarFile.ItemSpec); if (Directory.Exists(importsDir)) { foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories)) { AddJar(jars, Path.GetFullPath(file)); } } if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) }, { AndroidSkipResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, })); } continue; } Log.LogDebugMessage($"Refreshing {aarFile.ItemSpec}"); // temporarily extracted directory will look like: // _lp_/[aarFile] using (var zip = MonoAndroidHelper.ReadZipFile(aarFile.ItemSpec)) { try { updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => { var entryFileName = Path.GetFileName(entryFullName); var entryPath = Path.GetDirectoryName(entryFullName); if (entryFileName.StartsWith("internal_impl", StringComparison.InvariantCulture)) { var hash = Files.HashString(entryFileName); var jar = Path.Combine(entryPath, $"internal_impl-{hash}.jar"); AddJar(jars, importsDir, jar, aarFullPath); return(jar); } if (entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { AddJar(jars, importsDir, entryFullName, aarFullPath); } return(entryFullName); }, deleteCallback: (fileToDelete) => { return(!jars.ContainsKey(fileToDelete)); }, skipCallback: (entryFullName) => { // AAR files may contain other jars not needed for compilation // See: https://developer.android.com/studio/projects/android-library.html#aar-contents if (!entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { return(false); } if (entryFullName == "classes.jar" || entryFullName.StartsWith("libs/", StringComparison.OrdinalIgnoreCase) || entryFullName.StartsWith("libs\\", StringComparison.OrdinalIgnoreCase)) { return(false); } // This could be `lint.jar` or `api.jar`, etc. return(true); }); if (Directory.Exists(importsDir) && aarHash != stampHash) { Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}"); //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes WriteAllText(stamp, aarHash, preserveTimestamp: !updated); } } catch (PathTooLongException ex) { Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{aarFile.ItemSpec}\"", ex)); } } if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, { AndroidSkipResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, })); } } }
void GenerateCompressedAssemblySources() { if (Debug || !EnableCompression) { Generate(null); return; } var assemblies = new SortedDictionary <string, CompressedAssemblyInfo> (StringComparer.Ordinal); foreach (ITaskItem assembly in ResolvedAssemblies) { if (bool.TryParse(assembly.GetMetadata("AndroidSkipAddToPackage"), out bool value) && value) { continue; } if (assemblies.ContainsKey(assembly.ItemSpec)) { continue; } var fi = new FileInfo(assembly.ItemSpec); if (!fi.Exists) { Log.LogError($"Assembly {assembly.ItemSpec} does not exist"); continue; } assemblies.Add(CompressedAssemblyInfo.GetDictionaryKey(assembly), new CompressedAssemblyInfo(checked ((uint)fi.Length))); } uint index = 0; foreach (var kvp in assemblies) { kvp.Value.DescriptorIndex = index++; } string key = CompressedAssemblyInfo.GetKey(ProjectFullPath); Log.LogDebugMessage($"Storing compression assemblies info with key '{key}'"); BuildEngine4.RegisterTaskObjectAssemblyLocal(key, assemblies, RegisteredTaskObjectLifetime.Build); Generate(assemblies); void Generate(IDictionary <string, CompressedAssemblyInfo> dict) { foreach (string abi in SupportedAbis) { NativeAssemblerTargetProvider asmTargetProvider = GeneratePackageManagerJava.GetAssemblyTargetProvider(abi); string baseAsmFilePath = Path.Combine(EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}"); string asmFilePath = $"{baseAsmFilePath}.s"; var asmgen = new CompressedAssembliesNativeAssemblyGenerator(dict, asmTargetProvider, baseAsmFilePath); using (var sw = MemoryStreamPool.Shared.CreateStreamWriter()) { asmgen.Write(sw); sw.Flush(); if (MonoAndroidHelper.CopyIfStreamChanged(sw.BaseStream, asmFilePath)) { Log.LogDebugMessage($"File {asmFilePath} was regenerated"); } } } } }
void Run(DirectoryAssemblyResolver res) { PackageNamingPolicy pnp; JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse(PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; foreach (var dir in FrameworkDirectories) { if (Directory.Exists(dir.ItemSpec)) { res.SearchDirectories.Add(dir.ItemSpec); } } // Put every assembly we'll need in the resolver foreach (var assembly in ResolvedAssemblies) { if (bool.TryParse(assembly.GetMetadata(AndroidSkipJavaStubGeneration), out bool value) && value) { Log.LogDebugMessage($"Skipping Java Stub Generation for {assembly.ItemSpec}"); continue; } res.Load(assembly.ItemSpec); } // However we only want to look for JLO types in user code List <string> assemblies = new List <string> (); 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; } if (!assemblies.All(x => Path.GetFileName(x) != Path.GetFileName(asm.ItemSpec))) { continue; } Log.LogDebugMessage($"Adding {asm.ItemSpec} to assemblies."); assemblies.Add(asm.ItemSpec); } foreach (var asm in MonoAndroidHelper.GetFrameworkAssembliesToTreatAsUserAssemblies(ResolvedAssemblies)) { if (bool.TryParse(asm.GetMetadata(AndroidSkipJavaStubGeneration), out bool value) && value) { Log.LogDebugMessage($"Skipping Java Stub Generation for {asm.ItemSpec}"); continue; } if (!assemblies.All(x => Path.GetFileName(x) != Path.GetFileName(asm.ItemSpec))) { continue; } Log.LogDebugMessage($"Adding {asm.ItemSpec} to assemblies."); assemblies.Add(asm.ItemSpec); } // Step 1 - Find all the JLO types var scanner = new JavaTypeScanner(this.CreateTaskLogger()) { ErrorOnCustomJavaObject = ErrorOnCustomJavaObject, }; var all_java_types = scanner.GetJavaTypes(assemblies, res); WriteTypeMappings(all_java_types); var java_types = all_java_types .Where(t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration(t)) .ToArray(); // Step 2 - Generate Java stub code var success = Generator.CreateJavaSources( Log, java_types, Path.Combine(OutputDirectory, "src"), ApplicationJavaClass, AndroidSdkPlatform, UseSharedRuntime, int.Parse(AndroidSdkPlatform) <= 10, ResolvedAssemblies.Any(assembly => Path.GetFileName(assembly.ItemSpec) == "Mono.Android.Export.dll")); if (!success) { return; } // We need to save a map of .NET type -> ACW type for resource file fixups var managed = new Dictionary <string, TypeDefinition> (java_types.Length, StringComparer.Ordinal); var java = new Dictionary <string, TypeDefinition> (java_types.Length, StringComparer.Ordinal); var managedConflicts = new Dictionary <string, List <string> > (0, StringComparer.Ordinal); var javaConflicts = new Dictionary <string, List <string> > (0, StringComparer.Ordinal); // Allocate a MemoryStream with a reasonable guess at its capacity using (var stream = new MemoryStream(java_types.Length * 32)) using (var acw_map = new StreamWriter(stream)) { foreach (var type in java_types) { string managedKey = type.FullName.Replace('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName(type).Replace('/', '.'); acw_map.Write(type.GetPartialAssemblyQualifiedName()); acw_map.Write(';'); acw_map.Write(javaKey); acw_map.WriteLine(); TypeDefinition conflict; bool hasConflict = false; if (managed.TryGetValue(managedKey, out conflict)) { if (!managedConflicts.TryGetValue(managedKey, out var list)) { managedConflicts.Add(managedKey, list = new List <string> { conflict.GetPartialAssemblyName() }); } list.Add(type.GetPartialAssemblyName()); hasConflict = true; } if (java.TryGetValue(javaKey, out conflict)) { if (!javaConflicts.TryGetValue(javaKey, out var list)) { javaConflicts.Add(javaKey, list = new List <string> { conflict.GetAssemblyQualifiedName() }); } list.Add(type.GetAssemblyQualifiedName()); success = false; hasConflict = true; } 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).Replace('/', '.')); acw_map.Write(';'); acw_map.Write(javaKey); acw_map.WriteLine(); } } acw_map.Flush(); MonoAndroidHelper.CopyIfStreamChanged(stream, AcwMapFile); } foreach (var kvp in managedConflicts) { Log.LogCodedWarning( "XA4214", "The managed type `{0}` exists in multiple assemblies: {1}. " + "Please refactor the managed type names in these assemblies so that they are not identical.", kvp.Key, string.Join(", ", kvp.Value)); Log.LogCodedWarning("XA4214", "References to the type `{0}` will refer to `{0}, {1}`.", kvp.Key, kvp.Value [0]); } foreach (var kvp in javaConflicts) { Log.LogCodedError( "XA4215", "The Java type `{0}` is generated by more than one managed type. " + "Please change the [Register] attribute so that the same Java type is not emitted.", kvp.Key); foreach (var typeName in kvp.Value) { Log.LogCodedError("XA4215", " `{0}` generated by: {1}", kvp.Key, typeName); } } // Step 3 - Merge [Activity] and friends into AndroidManifest.xml var manifest = new ManifestDocument(ManifestTemplate, this.Log); manifest.PackageName = PackageName; manifest.ApplicationName = ApplicationName ?? PackageName; manifest.Placeholders = ManifestPlaceholders; manifest.Assemblies.AddRange(assemblies); manifest.Resolver = res; manifest.SdkDir = AndroidSdkDir; manifest.SdkVersion = AndroidSdkPlatform; manifest.Debug = Debug; manifest.MultiDex = MultiDex; manifest.NeedsInternet = NeedsInternet; manifest.InstantRunEnabled = InstantRunEnabled; var additionalProviders = manifest.Merge(all_java_types, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments); using (var stream = new MemoryStream()) { manifest.Save(stream); // Only write the new manifest if it actually changed MonoAndroidHelper.CopyIfStreamChanged(stream, MergedAndroidManifestOutput); } // Create additional runtime provider java sources. string providerTemplateFile = UseSharedRuntime ? "MonoRuntimeProvider.Shared.java" : "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"); MonoAndroidHelper.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 (var type in java_types) { if (JavaNativeTypeManager.IsApplication(type) || JavaNativeTypeManager.IsInstrumentation(type)) { string javaKey = JavaNativeTypeManager.ToJniName(type).Replace('/', '.'); regCallsWriter.WriteLine("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", type.GetAssemblyQualifiedName(), 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())); }
void AddEnvironment() { bool usesMonoAOT = false; bool usesAssemblyPreload = EnablePreloadAssembliesDefault; uint monoAOTMode = 0; string androidPackageName = null; var environmentVariables = new Dictionary <string, string> (StringComparer.Ordinal); var systemProperties = new Dictionary <string, string> (StringComparer.Ordinal); AotMode aotMode; if (AndroidAotMode != null && Aot.GetAndroidAotMode(AndroidAotMode, out aotMode)) { usesMonoAOT = true; monoAOTMode = (uint)aotMode; } bool haveLogLevel = false; bool haveMonoDebug = false; bool havebuildId = false; bool haveHttpMessageHandler = false; bool haveTlsProvider = false; bool haveMonoGCParams = false; SequencePointsMode sequencePointsMode; if (!Aot.TryGetSequencePointsMode(AndroidSequencePointsMode, out sequencePointsMode)) { sequencePointsMode = SequencePointsMode.None; } foreach (ITaskItem env in Environments ?? new TaskItem[0]) { foreach (string line in File.ReadLines(env.ItemSpec)) { var lineToWrite = line; if (lineToWrite.StartsWith("MONO_LOG_LEVEL=", StringComparison.Ordinal)) { haveLogLevel = true; } if (lineToWrite.StartsWith("MONO_GC_PARAMS=", StringComparison.Ordinal)) { haveMonoGCParams = true; } if (lineToWrite.StartsWith("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) { havebuildId = true; } if (lineToWrite.StartsWith("MONO_DEBUG=", StringComparison.Ordinal)) { haveMonoDebug = true; if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains("gen-compact-seq-points")) { lineToWrite = line + ",gen-compact-seq-points"; } } if (lineToWrite.StartsWith("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) { haveHttpMessageHandler = true; } if (lineToWrite.StartsWith("XA_TLS_PROVIDER=", StringComparison.Ordinal)) { haveTlsProvider = true; } if (lineToWrite.StartsWith("mono.enable_assembly_preload=", StringComparison.Ordinal)) { int idx = lineToWrite.IndexOf('='); uint val; if (idx < lineToWrite.Length - 1 && UInt32.TryParse(lineToWrite.Substring(idx + 1), out val)) { usesAssemblyPreload = idx == 1; } continue; } AddEnvironmentVariableLine(lineToWrite); } } if (_Debug && !haveLogLevel) { AddEnvironmentVariable(defaultLogLevel[0], defaultLogLevel[1]); } if (sequencePointsMode != SequencePointsMode.None && !haveMonoDebug) { AddEnvironmentVariable(defaultMonoDebug[0], defaultMonoDebug[1]); } if (!havebuildId) { AddEnvironmentVariable("XAMARIN_BUILD_ID", BuildId); } if (!haveHttpMessageHandler) { if (HttpClientHandlerType == null) { AddEnvironmentVariable(defaultHttpMessageHandler[0], defaultHttpMessageHandler[1]); } else { AddEnvironmentVariable("XA_HTTP_CLIENT_HANDLER_TYPE", HttpClientHandlerType.Trim()); } } if (!haveTlsProvider) { if (TlsProvider == null) { AddEnvironmentVariable(defaultTlsProvider[0], defaultTlsProvider[1]); } else { AddEnvironmentVariable("XA_TLS_PROVIDER", TlsProvider.Trim()); } } if (!haveMonoGCParams) { if (EnableSGenConcurrent) { AddEnvironmentVariable("MONO_GC_PARAMS", "major=marksweep-conc"); } else { AddEnvironmentVariable("MONO_GC_PARAMS", "major=marksweep"); } } using (var ms = new MemoryStream()) { var utf8Encoding = new UTF8Encoding(false); foreach (string abi in SupportedAbis) { ms.SetLength(0); NativeAssemblerTargetProvider asmTargetProvider; string asmFileName = Path.Combine(EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}.s"); switch (abi.Trim()) { case "armeabi-v7a": asmTargetProvider = new ARMNativeAssemblerTargetProvider(false); break; case "arm64-v8a": asmTargetProvider = new ARMNativeAssemblerTargetProvider(true); break; case "x86": asmTargetProvider = new X86NativeAssemblerTargetProvider(false); break; case "x86_64": asmTargetProvider = new X86NativeAssemblerTargetProvider(true); break; default: throw new InvalidOperationException($"Unknown ABI {abi}"); } var asmgen = new ApplicationConfigNativeAssemblyGenerator(asmTargetProvider, environmentVariables, systemProperties) { IsBundledApp = IsBundledApplication, UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, UsesAssemblyPreload = usesAssemblyPreload, MonoAOTMode = monoAOTMode.ToString().ToLowerInvariant(), AndroidPackageName = AndroidPackageName, }; using (var sw = new StreamWriter(ms, utf8Encoding, bufferSize: 8192, leaveOpen: true)) { asmgen.Write(sw, asmFileName); MonoAndroidHelper.CopyIfStreamChanged(ms, asmFileName); } } } void AddEnvironmentVariable(string name, string value) { if (Char.IsUpper(name [0]) || !Char.IsLetter(name [0])) { environmentVariables [ValidAssemblerString(name)] = ValidAssemblerString(value); } else { systemProperties [ValidAssemblerString(name)] = ValidAssemblerString(value); } } void AddEnvironmentVariableLine(string l) { string line = l?.Trim(); if (String.IsNullOrEmpty(line) || line [0] == '#') { return; } string[] nv = line.Split(new char[] { '=' }, 2); AddEnvironmentVariable(nv[0].Trim(), nv.Length < 2 ? String.Empty : nv[1].Trim()); } string ValidAssemblerString(string s) { return(s.Replace("\"", "\\\"")); } }
bool GenerateDebugFiles(bool skipJniAddNativeMethodRegistrationAttributeScan, List <TypeDefinition> javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState) { var modules = new Dictionary <string, ModuleDebugData> (StringComparer.Ordinal); int maxModuleFileNameWidth = 0; int maxModuleNameWidth = 0; foreach (TypeDefinition td in javaTypes) { UpdateApplicationConfig(td, appConfState); string moduleName = td.Module.Assembly.Name.Name; ModuleDebugData module; if (!modules.TryGetValue(moduleName, out module)) { string outputFileName = $"{moduleName}{TypemapExtension}"; module = new ModuleDebugData { EntryCount = 0, JavaNameWidth = 0, ManagedNameWidth = 0, JavaToManagedMap = new List <TypeMapDebugEntry> (), ManagedToJavaMap = new List <TypeMapDebugEntry> (), OutputFilePath = Path.Combine(outputDirectory, outputFileName), ModuleNameBytes = outputEncoding.GetBytes(moduleName) }; if (module.ModuleNameBytes.Length > maxModuleNameWidth) { maxModuleNameWidth = module.ModuleNameBytes.Length; } if (outputFileName.Length > maxModuleFileNameWidth) { maxModuleFileNameWidth = outputFileName.Length; } modules.Add(moduleName, module); } TypeMapDebugEntry entry = GetDebugEntry(td); if (entry.JavaName.Length > module.JavaNameWidth) { module.JavaNameWidth = (uint)entry.JavaName.Length + 1; } if (entry.ManagedName.Length > module.ManagedNameWidth) { module.ManagedNameWidth = (uint)entry.ManagedName.Length + 1; } module.JavaToManagedMap.Add(entry); module.ManagedToJavaMap.Add(entry); } foreach (ModuleDebugData module in modules.Values) { PrepareDebugMaps(module); } string typeMapIndexPath = Path.Combine(outputDirectory, "typemap.index"); using (var indexWriter = MemoryStreamPool.Shared.CreateBinaryWriter()) { OutputModules(modules, indexWriter, maxModuleFileNameWidth + 1); indexWriter.Flush(); MonoAndroidHelper.CopyIfStreamChanged(indexWriter.BaseStream, typeMapIndexPath); } GeneratedBinaryTypeMaps.Add(typeMapIndexPath); GenerateNativeAssembly( (NativeAssemblerTargetProvider asmTargetProvider, bool sharedBitsWritten, bool sharedIncludeUsesAbiPrefix) => { return(new TypeMappingDebugNativeAssemblyGenerator(asmTargetProvider, new ModuleDebugData(), outputDirectory, sharedBitsWritten)); } ); return(true); }
void AddEnvironment() { bool usesMonoAOT = false; bool usesAssemblyPreload = EnablePreloadAssembliesDefault; bool brokenExceptionTransitions = false; var environmentVariables = new Dictionary <string, string> (StringComparer.Ordinal); var systemProperties = new Dictionary <string, string> (StringComparer.Ordinal); if (!Enum.TryParse(PackageNamingPolicy, out PackageNamingPolicy pnp)) { pnp = PackageNamingPolicyEnum.LowercaseCrc64; } AotMode aotMode = AotMode.None; if (!string.IsNullOrEmpty(AndroidAotMode) && Aot.GetAndroidAotMode(AndroidAotMode, out aotMode) && aotMode != AotMode.None) { usesMonoAOT = true; } bool haveLogLevel = false; bool haveMonoDebug = false; bool havebuildId = false; bool haveHttpMessageHandler = false; bool haveTlsProvider = false; bool haveMonoGCParams = false; SequencePointsMode sequencePointsMode; if (!Aot.TryGetSequencePointsMode(AndroidSequencePointsMode, out sequencePointsMode)) { sequencePointsMode = SequencePointsMode.None; } foreach (ITaskItem env in Environments ?? new TaskItem[0]) { foreach (string line in File.ReadLines(env.ItemSpec)) { var lineToWrite = line; if (lineToWrite.StartsWith("MONO_LOG_LEVEL=", StringComparison.Ordinal)) { haveLogLevel = true; } if (lineToWrite.StartsWith("MONO_GC_PARAMS=", StringComparison.Ordinal)) { haveMonoGCParams = true; } if (lineToWrite.StartsWith("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) { havebuildId = true; } if (lineToWrite.StartsWith("MONO_DEBUG=", StringComparison.Ordinal)) { haveMonoDebug = true; if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains("gen-compact-seq-points")) { lineToWrite = line + ",gen-compact-seq-points"; } } if (lineToWrite.StartsWith("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) { haveHttpMessageHandler = true; } if (lineToWrite.StartsWith("XA_TLS_PROVIDER=", StringComparison.Ordinal)) { haveTlsProvider = true; } if (lineToWrite.StartsWith("mono.enable_assembly_preload=", StringComparison.Ordinal)) { int idx = lineToWrite.IndexOf('='); uint val; if (idx < lineToWrite.Length - 1 && UInt32.TryParse(lineToWrite.Substring(idx + 1), out val)) { usesAssemblyPreload = idx == 1; } continue; } if (lineToWrite.StartsWith("XA_BROKEN_EXCEPTION_TRANSITIONS=")) { brokenExceptionTransitions = true; continue; } AddEnvironmentVariableLine(lineToWrite); } } if (_Debug && !haveLogLevel) { AddEnvironmentVariable(defaultLogLevel[0], defaultLogLevel[1]); } if (sequencePointsMode != SequencePointsMode.None && !haveMonoDebug) { AddEnvironmentVariable(defaultMonoDebug[0], defaultMonoDebug[1]); } if (!havebuildId) { AddEnvironmentVariable("XAMARIN_BUILD_ID", BuildId); } if (!haveHttpMessageHandler) { if (HttpClientHandlerType == null) { AddEnvironmentVariable(defaultHttpMessageHandler[0], defaultHttpMessageHandler[1]); } else { AddEnvironmentVariable("XA_HTTP_CLIENT_HANDLER_TYPE", HttpClientHandlerType.Trim()); } } if (!haveTlsProvider) { if (TlsProvider == null) { AddEnvironmentVariable(defaultTlsProvider[0], defaultTlsProvider[1]); } else { AddEnvironmentVariable("XA_TLS_PROVIDER", TlsProvider.Trim()); } } if (!haveMonoGCParams) { if (EnableSGenConcurrent) { AddEnvironmentVariable("MONO_GC_PARAMS", "major=marksweep-conc"); } else { AddEnvironmentVariable("MONO_GC_PARAMS", "major=marksweep"); } } global::Android.Runtime.BoundExceptionType boundExceptionType; if (String.IsNullOrEmpty(BoundExceptionType) || String.Compare(BoundExceptionType, "System", StringComparison.OrdinalIgnoreCase) == 0) { boundExceptionType = global::Android.Runtime.BoundExceptionType.System; } else if (String.Compare(BoundExceptionType, "Java", StringComparison.OrdinalIgnoreCase) == 0) { boundExceptionType = global::Android.Runtime.BoundExceptionType.Java; } else { throw new InvalidOperationException($"Unsupported BoundExceptionType value '{BoundExceptionType}'"); } var appConfState = BuildEngine4.GetRegisteredTaskObject(ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build) as ApplicationConfigTaskState; foreach (string abi in SupportedAbis) { NativeAssemblerTargetProvider asmTargetProvider = GetAssemblyTargetProvider(abi); string baseAsmFilePath = Path.Combine(EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}"); string asmFilePath = $"{baseAsmFilePath}.s"; var asmgen = new ApplicationConfigNativeAssemblyGenerator(asmTargetProvider, baseAsmFilePath, environmentVariables, systemProperties) { IsBundledApp = IsBundledApplication, UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, UsesAssemblyPreload = usesAssemblyPreload, MonoAOTMode = aotMode.ToString().ToLowerInvariant(), AndroidPackageName = AndroidPackageName, BrokenExceptionTransitions = brokenExceptionTransitions, PackageNamingPolicy = pnp, BoundExceptionType = boundExceptionType, InstantRunEnabled = InstantRunEnabled, JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false, }; using (var sw = MemoryStreamPool.Shared.CreateStreamWriter()) { asmgen.Write(sw); sw.Flush(); MonoAndroidHelper.CopyIfStreamChanged(sw.BaseStream, asmFilePath); } } void AddEnvironmentVariable(string name, string value) { if (Char.IsUpper(name [0]) || !Char.IsLetter(name [0])) { environmentVariables [ValidAssemblerString(name)] = ValidAssemblerString(value); } else { systemProperties [ValidAssemblerString(name)] = ValidAssemblerString(value); } } void AddEnvironmentVariableLine(string l) { string line = l?.Trim(); if (String.IsNullOrEmpty(line) || line [0] == '#') { return; } string[] nv = line.Split(new char[] { '=' }, 2); AddEnvironmentVariable(nv[0].Trim(), nv.Length < 2 ? String.Empty : nv[1].Trim()); } string ValidAssemblerString(string s) { return(s.Replace("\"", "\\\"")); } }
protected override void WriteSymbols(StreamWriter output) { output.WriteLine(); WriteHeaderField(output, "map_module_count", mappingData.MapModuleCount); output.WriteLine(); WriteHeaderField(output, "java_type_count", mappingData.JavaTypeCount); output.WriteLine(); WriteHeaderField(output, "java_name_width", mappingData.JavaNameWidth); bool haveAssemblyNames = mappingData.AssemblyNames.Count > 0; bool haveModules = mappingData.Modules.Length > 0; output.WriteLine(); if (haveAssemblyNames) { output.Write(Indent); output.Write(".include"); output.Write(Indent); output.Write('"'); output.Write(Path.GetFileName(SharedIncludeFile)); output.WriteLine('"'); } else { WriteCommentLine(output, $"No shared data present, {Path.GetFileName (SharedIncludeFile)} not generated"); } if (haveModules) { output.Write(Indent); output.Write(".include"); output.Write(Indent); output.Write('"'); output.Write(Path.GetFileName(TypemapsIncludeFile)); output.WriteLine('"'); } else { WriteCommentLine(output, $"No modules defined, {Path.GetFileName (TypemapsIncludeFile)} not generated"); } output.WriteLine(); if (!sharedBitsWritten && haveAssemblyNames) { using (var sharedOutput = MemoryStreamPool.Shared.CreateStreamWriter(output.Encoding)) { WriteAssemblyNames(sharedOutput); sharedOutput.Flush(); MonoAndroidHelper.CopyIfStreamChanged(sharedOutput.BaseStream, SharedIncludeFile); } } if (haveModules) { using (var mapOutput = MemoryStreamPool.Shared.CreateStreamWriter(output.Encoding)) { WriteMapModules(output, mapOutput, "map_modules"); mapOutput.Flush(); MonoAndroidHelper.CopyIfStreamChanged(mapOutput.BaseStream, TypemapsIncludeFile); } } else { WriteMapModules(output, null, "map_modules"); } WriteJavaMap(output, "map_java"); }
void Run(DirectoryAssemblyResolver res) { PackageNamingPolicy pnp; JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse(PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseHash; foreach (var dir in FrameworkDirectories) { if (Directory.Exists(dir.ItemSpec)) { res.SearchDirectories.Add(dir.ItemSpec); } } var selectedWhitelistAssemblies = new List <string> (); // Put every assembly we'll need in the resolver foreach (var assembly in ResolvedAssemblies) { var assemblyFullPath = Path.GetFullPath(assembly.ItemSpec); res.Load(assemblyFullPath); if (MonoAndroidHelper.FrameworkAttributeLookupTargets.Any(a => Path.GetFileName(assembly.ItemSpec) == a)) { selectedWhitelistAssemblies.Add(assemblyFullPath); } } // However we only want to look for JLO types in user code var assemblies = ResolvedUserAssemblies.Select(p => p.ItemSpec).ToList(); var fxAdditions = MonoAndroidHelper.GetFrameworkAssembliesToTreatAsUserAssemblies(ResolvedAssemblies) .Where(a => assemblies.All(x => Path.GetFileName(x) != Path.GetFileName(a))); assemblies = assemblies.Concat(fxAdditions).ToList(); // Step 1 - Find all the JLO types var scanner = new JavaTypeScanner(this.CreateTaskLogger()) { ErrorOnCustomJavaObject = ErrorOnCustomJavaObject, }; var all_java_types = scanner.GetJavaTypes(assemblies, res); WriteTypeMappings(all_java_types); var java_types = all_java_types .Where(t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration(t)) .ToArray(); // Step 2 - Generate Java stub code var success = Generator.CreateJavaSources( Log, java_types, Path.Combine(OutputDirectory, "src"), ApplicationJavaClass, UseSharedRuntime, int.Parse(AndroidSdkPlatform) <= 10, ResolvedAssemblies.Any(assembly => Path.GetFileName(assembly.ItemSpec) == "Mono.Android.Export.dll")); if (!success) { return; } // We need to save a map of .NET type -> ACW type for resource file fixups var managed = new Dictionary <string, TypeDefinition> (java_types.Length, StringComparer.Ordinal); var java = new Dictionary <string, TypeDefinition> (java_types.Length, StringComparer.Ordinal); // Allocate a MemoryStream with a reasonable guess at its capacity using (var stream = new MemoryStream(java_types.Length * 32)) using (var acw_map = new StreamWriter(stream)) { foreach (var type in java_types) { string managedKey = type.FullName.Replace('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName(type).Replace('/', '.'); acw_map.Write(type.GetPartialAssemblyQualifiedName()); acw_map.Write(';'); acw_map.Write(javaKey); acw_map.WriteLine(); TypeDefinition conflict; if (managed.TryGetValue(managedKey, out conflict)) { Log.LogWarning( "Duplicate managed type found! Mappings between managed types and Java types must be unique. " + "First Type: '{0}'; Second Type: '{1}'.", conflict.GetAssemblyQualifiedName(), type.GetAssemblyQualifiedName()); Log.LogWarning( "References to the type '{0}' will refer to '{1}'.", managedKey, conflict.GetAssemblyQualifiedName()); continue; } if (java.TryGetValue(javaKey, out conflict)) { Log.LogError( "Duplicate Java type found! Mappings between managed types and Java types must be unique. " + "First Type: '{0}'; Second Type: '{1}'", conflict.GetAssemblyQualifiedName(), type.GetAssemblyQualifiedName()); success = false; continue; } 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).Replace('/', '.')); acw_map.Write(';'); acw_map.Write(javaKey); acw_map.WriteLine(); } acw_map.Flush(); MonoAndroidHelper.CopyIfStreamChanged(stream, AcwMapFile); } // Step 3 - Merge [Activity] and friends into AndroidManifest.xml var manifest = new ManifestDocument(ManifestTemplate, this.Log); manifest.PackageName = PackageName; manifest.ApplicationName = ApplicationName ?? PackageName; manifest.Placeholders = ManifestPlaceholders; manifest.Assemblies.AddRange(assemblies); manifest.Resolver = res; manifest.SdkDir = AndroidSdkDir; manifest.SdkVersion = AndroidSdkPlatform; manifest.Debug = Debug; manifest.MultiDex = MultiDex; manifest.NeedsInternet = NeedsInternet; manifest.InstantRunEnabled = InstantRunEnabled; var additionalProviders = manifest.Merge(all_java_types, selectedWhitelistAssemblies, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments); using (var stream = new MemoryStream()) { manifest.Save(stream); // Only write the new manifest if it actually changed MonoAndroidHelper.CopyIfStreamChanged(stream, MergedAndroidManifestOutput); } // Create additional runtime provider java sources. string providerTemplateFile = UseSharedRuntime ? "MonoRuntimeProvider.Shared.java" : "MonoRuntimeProvider.Bundled.java"; string providerTemplate = GetResource <JavaCallableWrapperGenerator> (providerTemplateFile); foreach (var provider in additionalProviders) { var contents = providerTemplate.Replace("MonoRuntimeProvider", provider); var real_provider = Path.Combine(OutputDirectory, "src", "mono", provider + ".java"); MonoAndroidHelper.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 (var type in java_types) { if (JavaNativeTypeManager.IsApplication(type) || JavaNativeTypeManager.IsInstrumentation(type)) { string javaKey = JavaNativeTypeManager.ToJniName(type).Replace('/', '.'); regCallsWriter.WriteLine("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", type.GetAssemblyQualifiedName(), 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())); // Create NotifyTimeZoneChanges java sources. string notifyTimeZoneChangesFile = "NotifyTimeZoneChanges.java"; SaveResource(notifyTimeZoneChangesFile, notifyTimeZoneChangesFile, real_app_dir, template => template); }
public override bool Execute() { BuildId = buildId.ToString(); Log.LogDebugMessage(" [Output] BuildId: {0}", BuildId); var shared_runtime = string.Compare(UseSharedRuntime, "true", true) == 0; var doc = AndroidAppManifest.Load(Manifest, MonoAndroidHelper.SupportedVersions); int minApiVersion = doc.MinSdkVersion == null ? 4 : (int)doc.MinSdkVersion; // We need to include any special assemblies in the Assemblies list var assemblies = ResolvedUserAssemblies.Select(p => p.ItemSpec) .Concat(MonoAndroidHelper.GetFrameworkAssembliesToTreatAsUserAssemblies(ResolvedAssemblies)) .ToList(); var mainFileName = Path.GetFileName(MainAssembly); Func <string, string, bool> fileNameEq = (a, b) => a.Equals(b, StringComparison.OrdinalIgnoreCase); assemblies = assemblies.Where(a => fileNameEq(a, mainFileName)).Concat(assemblies.Where(a => !fileNameEq(a, mainFileName))).ToList(); using (var stream = new MemoryStream()) using (var pkgmgr = new StreamWriter(stream)) { // Write the boilerplate from the MonoPackageManager.java resource var packageManagerResource = minApiVersion < 9 ? "MonoPackageManager.api4.java" : "MonoPackageManager.java"; using (var template = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(packageManagerResource))) { string line; while ((line = template.ReadLine()) != null) { pkgmgr.WriteLine(line); } } // Write all the user assemblies pkgmgr.WriteLine("class MonoPackageManager_Resources {"); pkgmgr.WriteLine("\tpublic static final String[] Assemblies = new String[]{"); pkgmgr.WriteLine("\t\t/* We need to ensure that \"{0}\" comes first in this list. */", mainFileName); foreach (var assembly in assemblies) { pkgmgr.WriteLine("\t\t\"" + Path.GetFileName(assembly) + "\","); } // Write the assembly dependencies pkgmgr.WriteLine("\t};"); pkgmgr.WriteLine("\tpublic static final String[] Dependencies = new String[]{"); //foreach (var assembly in assemblies.Except (args.Assemblies)) { // if (args.SharedRuntime && !Toolbox.IsInSharedRuntime (assembly)) // pkgmgr.WriteLine ("\t\t\"" + Path.GetFileName (assembly) + "\","); //} pkgmgr.WriteLine("\t};"); // Write the platform api apk we need pkgmgr.WriteLine("\tpublic static final String ApiPackageName = {0};", shared_runtime ? string.Format("\"Mono.Android.Platform.ApiLevel_{0}\"", MonoAndroidHelper.SupportedVersions.GetApiLevelFromFrameworkVersion(TargetFrameworkVersion)) : "null"); pkgmgr.WriteLine("}"); pkgmgr.Flush(); // Only copy to the real location if the contents actually changed var dest = Path.GetFullPath(Path.Combine(OutputDirectory, "MonoPackageManager.java")); MonoAndroidHelper.CopyIfStreamChanged(stream, dest); } AddEnvironment(); return(!Log.HasLoggedErrors); }
/// <summary> /// NOTE: all file paths used in this method should be full paths. (Or use AsyncTask.WorkingDirectory) /// </summary> void GenerateJava(Library library) { // In some cases (such as ancient support libraries), R.txt does not exist. // We can just use the main app's R.txt file and write *all fields* in this case. bool using_main_r_txt = false; var r_txt = library.TextFile; if (!File.Exists(r_txt)) { LogDebugMessage($"Using main R.txt, R.txt does not exist: {r_txt}"); using_main_r_txt = true; r_txt = main_r_txt; } var manifestFile = library.ManifestFile; if (!File.Exists(manifestFile)) { LogDebugMessage($"Skipping, AndroidManifest.xml does not exist: {manifestFile}"); return; } var manifest = AndroidAppManifest.Load(manifestFile, MonoAndroidHelper.SupportedVersions); using (var memory = new MemoryStream()) using (var writer = new StreamWriter(memory, Encoding)) { // This code is based on the Android gradle plugin // https://android.googlesource.com/platform/tools/base/+/908b391a9c006af569dfaff08b37f8fdd6c4da89/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java writer.WriteLine("/* AUTO-GENERATED FILE. DO NOT MODIFY."); writer.WriteLine(" *"); writer.WriteLine(" * This class was automatically generated by"); writer.WriteLine(" * Xamarin.Android from the resource data it found."); writer.WriteLine(" * It should not be modified by hand."); writer.WriteLine(" */"); writer.Write("package "); writer.Write(manifest.PackageName); writer.WriteLine(';'); writer.WriteLine(); writer.WriteLine("public final class R {"); using (var reader = File.OpenText(r_txt)) { string currentClass = null; foreach (var line in ParseFile(reader)) { var type = line [Index.Type]; var clazz = line [Index.Class]; var name = line [Index.Name]; if (GetValue(clazz, name, line, using_main_r_txt, out string value)) { if (clazz != currentClass) { // If not the first inner class if (currentClass != null) { writer.WriteLine("\t}"); } currentClass = clazz; writer.Write("\tpublic static final class "); writer.Write(currentClass); writer.WriteLine(" {"); } writer.Write("\t\tpublic static final "); writer.Write(type); writer.Write(' '); writer.Write(name); writer.Write(" = "); // It may be an int[] if (value.StartsWith("{", StringComparison.Ordinal)) { writer.Write("new "); writer.Write(type); writer.Write(' '); } writer.Write(value); writer.WriteLine(';'); } else { LogDebugMessage($"{r_txt}: `{type} {clazz} {name}` value not found"); } } // If we wrote at least one inner class if (currentClass != null) { writer.WriteLine("\t}"); } writer.WriteLine('}'); } writer.Flush(); var r_java = Path.Combine(output_directory, manifest.PackageName.Replace('.', Path.DirectorySeparatorChar), "R.java"); if (MonoAndroidHelper.CopyIfStreamChanged(memory, r_java)) { LogDebugMessage($"Writing: {r_java}"); } else { LogDebugMessage($"Up to date: {r_java}"); } } }
// Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*] // Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*] void Extract( DirectoryAssemblyResolver res, IDictionary <string, ITaskItem> jars, ICollection <ITaskItem> resolvedResourceDirectories, ICollection <ITaskItem> resolvedAssetDirectories, ICollection <ITaskItem> resolvedEnvironments) { // lets "upgrade" the old directory. string oldPath = Path.GetFullPath(Path.Combine(OutputImportDirectory, "..", "__library_projects__")); if (!OutputImportDirectory.Contains("__library_projects__") && Directory.Exists(oldPath)) { MonoAndroidHelper.SetDirectoryWriteable(Path.Combine(oldPath, "..")); Directory.Delete(oldPath, recursive: true); } var outdir = Path.GetFullPath(OutputImportDirectory); Directory.CreateDirectory(outdir); foreach (var assembly in Assemblies) { res.Load(assembly.ItemSpec); } // FIXME: reorder references by import priority (not sure how to do that yet) foreach (var assemblyPath in Assemblies .Select(a => a.ItemSpec) .Distinct()) { var fileName = Path.GetFileName(assemblyPath); if (MonoAndroidHelper.IsFrameworkAssembly(fileName) && !MonoAndroidHelper.FrameworkEmbeddedJarLookupTargets.Contains(fileName) && !MonoAndroidHelper.FrameworkEmbeddedNativeLibraryAssemblies.Contains(fileName)) { Log.LogDebugMessage($"Skipping framework assembly '{fileName}'."); continue; } if (DesignTimeBuild && !File.Exists(assemblyPath)) { Log.LogDebugMessage($"Skipping non-existent dependency '{assemblyPath}' during a design-time build."); continue; } if (assembliestoSkipExtraction.Contains(assemblyPath)) { Log.LogDebugMessage("Skipping resource extraction for '{0}' .", assemblyPath); continue; } string assemblyFileName = Path.GetFileNameWithoutExtension(assemblyPath); string assemblyIdentName = assemblyFileName; if (UseShortFileNames) { assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyFileName); } string outDirForDll = Path.Combine(OutputImportDirectory, assemblyIdentName); string importsDir = Path.Combine(outDirForDll, ImportsDirectory); string nativeimportsDir = Path.Combine(outDirForDll, NativeImportsDirectory); string resDir = Path.Combine(importsDir, "res"); string assetsDir = Path.Combine(importsDir, "assets"); // Skip already-extracted resources. bool updated = false; string assemblyHash = MonoAndroidHelper.HashFile(assemblyPath); string stamp = Path.Combine(outdir, assemblyIdentName + ".stamp"); string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null; if (assemblyHash == stampHash) { Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath); if (Directory.Exists(importsDir)) { foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories)) { AddJar(jars, Path.GetFullPath(file)); } } if (Directory.Exists(resDir)) { var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, assemblyPath }, }); if (assembliesToSkipCaseFixup.Contains(assemblyPath)) { taskItem.SetMetadata(AndroidSkipResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly)) { resolvedEnvironments.Add(new TaskItem(env, new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } continue; } Log.LogDebugMessage($"Refreshing {assemblyFileName}.dll"); var assembly = res.GetAssembly(assemblyPath); foreach (var mod in assembly.Modules) { // android environment files foreach (var envtxt in mod.Resources .Where(r => r.Name.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase)) .Where(r => r is EmbeddedResource) .Cast <EmbeddedResource> ()) { var outFile = Path.Combine(outDirForDll, envtxt.Name); using (var stream = envtxt.GetResourceStream()) { updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, outFile); } resolvedEnvironments.Add(new TaskItem(Path.GetFullPath(outFile), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } // embedded jars (EmbeddedJar, EmbeddedReferenceJar) var resjars = mod.Resources .Where(r => r.Name.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase)) .Select(r => (EmbeddedResource)r); foreach (var resjar in resjars) { using (var stream = resjar.GetResourceStream()) { AddJar(jars, importsDir, resjar.Name, assemblyPath); updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, Path.Combine(importsDir, resjar.Name)); } } var libzip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidNativeLibraries__.zip") as EmbeddedResource; if (libzip != null) { List <string> files = new List <string> (); using (var stream = libzip.GetResourceStream()) using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) { try { updated |= Files.ExtractAll(zip, nativeimportsDir, modifyCallback: (entryFullName) => { files.Add(Path.GetFullPath(Path.Combine(nativeimportsDir, entryFullName))); return(entryFullName .Replace("native_library_imports\\", "") .Replace("native_library_imports/", "")); }, deleteCallback: (fileToDelete) => { return(!files.Contains(fileToDelete)); }); } catch (PathTooLongException ex) { Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}"); return; } catch (NotSupportedException ex) { Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}"); return; } } } // embedded AndroidResourceLibrary archive var reszip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource; if (reszip != null) { // temporarily extracted directory will look like: // __library_projects__/[dllname]/[library_project_imports | jlibs]/bin using (var stream = reszip.GetResourceStream()) using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) { try { updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => { var path = entryFullName .Replace("library_project_imports\\", "") .Replace("library_project_imports/", ""); if (path.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { AddJar(jars, importsDir, path, assemblyPath); } return(path); }, deleteCallback: (fileToDelete) => { return(!jars.ContainsKey(fileToDelete)); }); } catch (PathTooLongException ex) { Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}"); return; } catch (NotSupportedException ex) { Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}"); return; } } // We used to *copy* the resources to overwrite other resources, // which resulted in missing resource issue. // Here we replaced copy with use of '-S' option and made it to work. if (Directory.Exists(resDir)) { var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } }); if (assembliesToSkipCaseFixup.Contains(assemblyPath)) { taskItem.SetMetadata(AndroidSkipResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } })); } } } if (Directory.Exists(importsDir)) { // Delete unknown files in the top directory of importsDir foreach (var file in Directory.EnumerateFiles(importsDir, "*")) { var fullPath = Path.GetFullPath(file); if (file.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase) && !resolvedEnvironments.Any(x => x.ItemSpec == fullPath)) { Log.LogDebugMessage($"Deleting unknown AndroidEnvironment file: {Path.GetFileName (file)}"); File.Delete(fullPath); updated = true; } else if (file.EndsWith(".jar", StringComparison.OrdinalIgnoreCase) && !jars.ContainsKey(fullPath)) { Log.LogDebugMessage($"Deleting unknown jar: {Path.GetFileName (file)}"); File.Delete(fullPath); updated = true; } } if (assemblyHash != stampHash) { Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}"); //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes WriteAllText(stamp, assemblyHash, preserveTimestamp: !updated); } } } foreach (var aarFile in AarLibraries ?? new ITaskItem[0]) { if (!File.Exists(aarFile.ItemSpec)) { continue; } string aarIdentityName = Path.GetFileNameWithoutExtension(aarFile.ItemSpec); if (UseShortFileNames) { aarIdentityName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(aarIdentityName); } string outDirForDll = Path.Combine(OutputImportDirectory, aarIdentityName); string importsDir = Path.Combine(outDirForDll, ImportsDirectory); string resDir = Path.Combine(importsDir, "res"); string assetsDir = Path.Combine(importsDir, "assets"); bool updated = false; string aarHash = MonoAndroidHelper.HashFile(aarFile.ItemSpec); string stamp = Path.Combine(outdir, aarIdentityName + ".stamp"); string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null; var aarFullPath = Path.GetFullPath(aarFile.ItemSpec); if (aarHash == stampHash) { Log.LogDebugMessage("Skipped {0}: extracted files are up to date", aarFile.ItemSpec); if (Directory.Exists(importsDir)) { foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories)) { AddJar(jars, Path.GetFullPath(file)); } } if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) }, { AndroidSkipResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, })); } continue; } Log.LogDebugMessage($"Refreshing {aarFile.ItemSpec}"); // temporarily extracted directory will look like: // _lp_/[aarFile] using (var zip = MonoAndroidHelper.ReadZipFile(aarFile.ItemSpec)) { try { updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => { var entryFileName = Path.GetFileName(entryFullName); var entryPath = Path.GetDirectoryName(entryFullName); if (entryFileName.StartsWith("internal_impl", StringComparison.InvariantCulture)) { var hash = Files.HashString(entryFileName); var jar = Path.Combine(entryPath, $"internal_impl-{hash}.jar"); AddJar(jars, importsDir, jar, aarFullPath); return(jar); } if (entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { AddJar(jars, importsDir, entryFullName, aarFullPath); } return(entryFullName); }, deleteCallback: (fileToDelete) => { return(!jars.ContainsKey(fileToDelete)); }); if (Directory.Exists(importsDir) && aarHash != stampHash) { Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}"); //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes WriteAllText(stamp, aarHash, preserveTimestamp: !updated); } } catch (PathTooLongException ex) { Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{aarFile.ItemSpec}\"", ex)); } } if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, { AndroidSkipResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, })); } } }
/// <summary> /// NOTE: all file paths used in this method should be full paths. (Or use AsyncTask.WorkingDirectory) /// </summary> void GenerateJava(Package package) { // In some cases (such as ancient support libraries), R.txt does not exist. // We can just use the main app's R.txt file and write *all fields* in this case. foreach (var r_txt in package.TextFiles) { if (!File.Exists(r_txt)) { LogDebugMessage($"Using main R.txt, R.txt does not exist: {r_txt}"); package.TextFiles.Clear(); package.TextFiles.Add(main_r_txt); break; } } var lines = LoadValues(package); using (var writer = MemoryStreamPool.Shared.CreateStreamWriter()) { // This code is based on the Android gradle plugin // https://android.googlesource.com/platform/tools/base/+/908b391a9c006af569dfaff08b37f8fdd6c4da89/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java writer.WriteLine("/* AUTO-GENERATED FILE. DO NOT MODIFY."); writer.WriteLine(" *"); writer.WriteLine(" * This class was automatically generated by"); writer.WriteLine(" * Xamarin.Android from the resource data it found."); writer.WriteLine(" * It should not be modified by hand."); writer.WriteLine(" */"); writer.Write("package "); writer.Write(package.Name); writer.WriteLine(';'); writer.WriteLine(); writer.WriteLine("public final class R {"); string currentClass = null; foreach (var line in lines) { var type = line [Index.Type]; var clazz = line [Index.Class]; var name = line [Index.Name]; var value = line [Index.Value]; if (clazz != currentClass) { // If not the first inner class if (currentClass != null) { writer.WriteLine("\t}"); } currentClass = clazz; writer.Write("\tpublic static final class "); writer.Write(currentClass); writer.WriteLine(" {"); } writer.Write("\t\tpublic static final "); writer.Write(type); writer.Write(' '); writer.Write(name); writer.Write(" = "); // It may be an int[] if (value.StartsWith("{", StringComparison.Ordinal)) { writer.Write("new "); writer.Write(type); writer.Write(' '); } writer.Write(value); writer.WriteLine(';'); } // If we wrote at least one inner class if (currentClass != null) { writer.WriteLine("\t}"); } writer.WriteLine('}'); writer.Flush(); var r_java = Path.Combine(output_directory, package.Name.Replace('.', Path.DirectorySeparatorChar), "R.java"); if (MonoAndroidHelper.CopyIfStreamChanged(writer.BaseStream, r_java)) { LogDebugMessage($"Writing: {r_java}"); } else { LogDebugMessage($"Up to date: {r_java}"); } } }
void UpdateWhenChanged(string path, string type, MemoryStream ms, Action <Stream> generator) { if (!EmbedAssemblies) { ms.SetLength(0); generator(ms); MonoAndroidHelper.CopyIfStreamChanged(ms, path); } string dataFilePath = $"{path}.inc"; using (var stream = new NativeAssemblyDataStream()) { if (EmbedAssemblies) { generator(stream); stream.EndOfFile(); MonoAndroidHelper.CopyIfStreamChanged(stream, dataFilePath); } else { stream.EmptyFile(); } var generatedFiles = new List <ITaskItem> (); string mappingFieldName = $"{type}_typemap"; string dataFileName = Path.GetFileName(dataFilePath); NativeAssemblerTargetProvider asmTargetProvider; var utf8Encoding = new UTF8Encoding(false); foreach (string abi in SupportedAbis) { ms.SetLength(0); switch (abi.Trim()) { case "armeabi-v7a": asmTargetProvider = new ARMNativeAssemblerTargetProvider(is64Bit: false); 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 asmgen = new TypeMappingNativeAssemblyGenerator(asmTargetProvider, stream, dataFileName, stream.MapByteCount, mappingFieldName); asmgen.EmbedAssemblies = EmbedAssemblies; string asmFileName = $"{path}.{abi.Trim ()}.s"; using (var sw = new StreamWriter(ms, utf8Encoding, bufferSize: 8192, leaveOpen: true)) { asmgen.Write(sw, dataFileName); MonoAndroidHelper.CopyIfStreamChanged(ms, asmFileName); } } } }
public bool Generate(List <TypeDefinition> javaTypes, string outputDirectory, bool generateNativeAssembly) { 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[]> (); foreach (TypeDefinition td in javaTypes) { 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"); // Try to approximate the index size: // 16 bytes for the header // 16 bytes (UUID) + filename length per each entry using (var ms = new MemoryStream(16 + (modules.Length * (16 + 128)))) { using (var indexWriter = new BinaryWriter(ms)) { OutputModules(outputDirectory, modules, indexWriter, maxModuleFileNameLength + 1); indexWriter.Flush(); MonoAndroidHelper.CopyIfStreamChanged(ms, 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); }