// 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, ICollection <string> jars, ICollection <string> resolvedResourceDirectories, ICollection <string> resolvedAssetDirectories, ICollection <string> 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 = new DirectoryInfo(OutputImportDirectory); if (!outdir.Exists) { outdir.Create(); } 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 => GetTargetAssembly(a)) .Where(a => a != null) .Distinct()) { string assemblyIdentName = Path.GetFileNameWithoutExtension(assemblyPath); if (UseShortFileNames) { assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyIdentName); } string outDirForDll = Path.Combine(OutputImportDirectory, assemblyIdentName); string importsDir = Path.Combine(outDirForDll, ImportsDirectory); #if SEPARATE_CRUNCH // FIXME: review these binResDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. //string binResDir = Path.Combine (importsDir, "bin", "res"); //string binAssemblyDir = Path.Combine (importsDir, "bin", "assets"); #endif string resDir = Path.Combine(importsDir, "res"); string assemblyDir = Path.Combine(importsDir, "assets"); // Skip already-extracted resources. var stamp = new FileInfo(Path.Combine(outdir.FullName, assemblyIdentName + ".stamp")); if (stamp.Exists && stamp.LastWriteTimeUtc > new FileInfo(assemblyPath).LastWriteTimeUtc) { Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath); #if SEPARATE_CRUNCH // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. if (Directory.Exists(binResDir)) { resolvedResourceDirectories.Add(binResDir); } if (Directory.Exists(binAssemblyDir)) { resolvedAssetDirectories.Add(binAssemblyDir); } #endif if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(resDir); } if (Directory.Exists(assemblyDir)) { resolvedAssetDirectories.Add(assemblyDir); } foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly)) { resolvedEnvironments.Add(env); } continue; } Log.LogDebugMessage("Refreshing {0}", assemblyPath); Directory.CreateDirectory(importsDir); var assembly = res.GetAssembly(assemblyPath); var assemblyLastWrite = new FileInfo(assemblyPath).LastWriteTimeUtc; bool updated = false; 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> ()) { if (!Directory.Exists(outDirForDll)) { Directory.CreateDirectory(outDirForDll); } var finfo = new FileInfo(Path.Combine(outDirForDll, envtxt.Name)); if (!finfo.Exists || finfo.LastWriteTimeUtc > assemblyLastWrite) { using (var fs = finfo.Create()) { var data = envtxt.GetResourceData(); fs.Write(data, 0, data.Length); } updated = true; } resolvedEnvironments.Add(finfo.FullName); } // 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) { var outjarFile = Path.Combine(importsDir, resjar.Name); var fi = new FileInfo(outjarFile); if (!fi.Exists || fi.LastWriteTimeUtc > assemblyLastWrite) { var data = resjar.GetResourceData(); using (var outfs = File.Create(outjarFile)) outfs.Write(data, 0, data.Length); updated = true; } jars.Add(outjarFile); } // embedded AndroidResourceLibrary archive var reszip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource; if (reszip != null) { if (!Directory.Exists(outDirForDll)) { Directory.CreateDirectory(outDirForDll); } var finfo = new FileInfo(Path.Combine(outDirForDll, reszip.Name)); using (var fs = finfo.Create()) { var data = reszip.GetResourceData(); fs.Write(data, 0, data.Length); } // temporarily extracted directory will look like: // __library_projects__/[dllname]/[library_project_imports | jlibs]/bin using (var zip = MonoAndroidHelper.ReadZipFile(finfo.FullName)) { try { updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => { return(entryFullName .Replace("library_project_imports\\", "") .Replace("library_project_imports/", "")); }, deleteCallback: (fileToDelete) => { return(!jars.Contains(fileToDelete)); }, forceUpdate: false); } catch (PathTooLongException ex) { Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{assemblyPath}\"", ex)); } } // 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 SEPARATE_CRUNCH // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. if (Directory.Exists(binResDir)) { resolvedResourceDirectories.Add(binResDir); } if (Directory.Exists(binAssemblyDir)) { resolvedAssetDirectories.Add(binAssemblyDir); } #endif if (Directory.Exists(resDir)) { resolvedResourceDirectories.Add(resDir); } if (Directory.Exists(assemblyDir)) { resolvedAssetDirectories.Add(assemblyDir); } finfo.Delete(); } } if (Directory.Exists(importsDir) && (updated || !stamp.Exists)) { Log.LogDebugMessage("Touch {0}", stamp.FullName); stamp.Create().Close(); } } foreach (var f in outdir.GetFiles("*.jar", SearchOption.AllDirectories) .Select(fi => fi.FullName)) { if (jars.Contains(f)) { continue; } jars.Add(f); } }
// 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.GetFileName(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}"); 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.GetFileName(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)) { var skipProcessing = aarFile.GetMetadata(AndroidSkipResourceProcessing); if (string.IsNullOrEmpty(skipProcessing)) { skipProcessing = "True"; } resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) }, { AndroidSkipResourceProcessing, skipProcessing }, })); } 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); } else if (entryFullName.StartsWith(".netenv/", StringComparison.OrdinalIgnoreCase) || entryFullName.StartsWith(".netenv\\", StringComparison.OrdinalIgnoreCase)) { var fullPath = Path.GetFullPath(Path.Combine(importsDir, entryFullName)); resolvedEnvironments.Add(new TaskItem(fullPath, new Dictionary <string, string> { { OriginalFile, aarFile.ItemSpec } })); } return(entryFullName); }, deleteCallback: (fileToDelete) => { return(!jars.ContainsKey(fileToDelete)); }, skipCallback: Files.ShouldSkipEntryInAar); 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)) { var skipProcessing = aarFile.GetMetadata(AndroidSkipResourceProcessing); if (string.IsNullOrEmpty(skipProcessing)) { skipProcessing = "True"; } resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, { AndroidSkipResourceProcessing, skipProcessing }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> { { OriginalFile, aarFullPath }, })); } } }
// 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, ICollection <string> jars, ICollection <ITaskItem> resolvedResourceDirectories, ICollection <string> resolvedAssetDirectories, ICollection <string> 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 => GetTargetAssembly(a)) .Where(a => a != null) .Distinct()) { if (DesignTimeBuild && !File.Exists(assemblyPath)) { Log.LogDebugMessage("Skipping non existant dependancy '{0}' due to design time build.", 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); #if SEPARATE_CRUNCH // FIXME: review these binResDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. //string binResDir = Path.Combine (importsDir, "bin", "res"); //string binAssemblyDir = Path.Combine (importsDir, "bin", "assets"); #endif 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 SEPARATE_CRUNCH // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. if (Directory.Exists(binResDir)) { resolvedResourceDirectories.Add(binResDir); } if (Directory.Exists(binAssemblyDir)) { resolvedAssetDirectories.Add(binAssemblyDir); } #endif 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 (assembliesToSkip.Contains(assemblyFileName)) { taskItem.SetMetadata(SkipAndroidResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir)); } foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly)) { resolvedEnvironments.Add(env); } 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(Path.GetFullPath(outFile)); } // 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); 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); } return(path); }, deleteCallback: (fileToDelete) => { return(!jars.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; } } // 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 SEPARATE_CRUNCH // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this. // Enabling these blindly causes build failure on ActionBarSherlock. if (Directory.Exists(binResDir)) { resolvedResourceDirectories.Add(binResDir); } if (Directory.Exists(binAssemblyDir)) { resolvedAssetDirectories.Add(binAssemblyDir); } #endif if (Directory.Exists(resDir)) { var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> { { OriginalFile, assemblyPath } }); if (assembliesToSkip.Contains(assemblyFileName)) { taskItem.SetMetadata(SkipAndroidResourceProcessing, "True"); } resolvedResourceDirectories.Add(taskItem); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir)); } } } 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.Contains(fullPath)) { Log.LogDebugMessage($"Deleting unknown AndroidEnvironment file: {Path.GetFileName (file)}"); File.Delete(fullPath); updated = true; } else if (file.EndsWith(".jar", StringComparison.OrdinalIgnoreCase) && !jars.Contains(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, Path.GetFileNameWithoutExtension(aarFile.ItemSpec) + ".stamp"); string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null; 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) }, { SkipAndroidResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir)); } 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); return(jar); } if (entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase)) { AddJar(jars, importsDir, entryFullName); } return(entryFullName); }, deleteCallback: (fileToDelete) => { return(!jars.Contains(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, Path.GetFullPath(aarFile.ItemSpec) }, { SkipAndroidResourceProcessing, "True" }, })); } if (Directory.Exists(assetsDir)) { resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir)); } } }