using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Xml; using Mono.Cecil; using Mono.Tuner; using MonoTouch.Tuner; using ObjCRuntime; using Xamarin; using Xamarin.Utils; namespace Xamarin.Bundler { struct NativeReferenceMetadata { public bool ForceLoad; public string Frameworks; public string WeakFrameworks; public string LibraryName; public string LinkerFlags; public LinkTarget LinkTarget; public bool NeedsGccExceptionHandling; public bool IsCxx; public bool SmartLink; public DlsymOption Dlsym; // Optional public LinkWithAttribute Attribute; public NativeReferenceMetadata (LinkWithAttribute attribute) { ForceLoad = attribute.ForceLoad; Frameworks = attribute.Frameworks; WeakFrameworks = attribute.WeakFrameworks; LibraryName = attribute.LibraryName; LinkerFlags = attribute.LinkerFlags; LinkTarget = attribute.LinkTarget; NeedsGccExceptionHandling = attribute.NeedsGccExceptionHandling; IsCxx = attribute.IsCxx; SmartLink = attribute.SmartLink; Dlsym = attribute.Dlsym; Attribute = attribute; } } public partial class Assembly { public AssemblyBuildTarget BuildTarget; public string BuildTargetName; public bool IsCodeShared; public List Satellites; public Application App { get { return Target.App; } } string full_path; bool? is_framework_assembly; public AssemblyDefinition AssemblyDefinition; public Target Target; public bool? IsFrameworkAssembly { get { return is_framework_assembly; } } public string FullPath { get { return full_path; } set { full_path = value; if (!is_framework_assembly.HasValue && !string.IsNullOrEmpty (full_path)) { #if NET is_framework_assembly = Target.App.Configuration.FrameworkAssemblies.Contains (GetIdentity (full_path)); #else var real_full_path = Target.GetRealPath (full_path); is_framework_assembly = real_full_path.StartsWith (Path.GetDirectoryName (Path.GetDirectoryName (Target.Resolver.FrameworkDirectory)), StringComparison.Ordinal); #endif } } } public string FileName { get { return Path.GetFileName (FullPath); } } public string Identity { get { return GetIdentity (AssemblyDefinition); } } public static string GetIdentity (AssemblyDefinition ad) { if (!string.IsNullOrEmpty (ad.MainModule.FileName)) return Path.GetFileNameWithoutExtension (ad.MainModule.FileName); return ad.Name.Name; } public static string GetIdentity (string path) { return Path.GetFileNameWithoutExtension (path); } public bool EnableCxx; public bool NeedsGccExceptionHandling; public bool ForceLoad; public HashSet Frameworks = new HashSet (); public HashSet WeakFrameworks = new HashSet (); public List LinkerFlags = new List (); // list of extra linker flags public List LinkWith = new List (); // list of paths to native libraries to link with, from LinkWith attributes public HashSet UnresolvedModuleReferences; public bool HasLinkWithAttributes { get; private set; } bool? symbols_loaded; List link_with_resources; // a list of resources that must be removed from the app public Assembly (Target target, string path) { this.Target = target; this.FullPath = path; } public Assembly (Target target, AssemblyDefinition definition) { this.Target = target; this.AssemblyDefinition = definition; this.FullPath = definition.MainModule.FileName; } public bool HasValidSymbols { get { return AssemblyDefinition.MainModule.HasSymbols; } } public void LoadSymbols () { if (symbols_loaded.HasValue) return; symbols_loaded = false; try { var pdb = Path.ChangeExtension (FullPath, ".pdb"); if (File.Exists (pdb) || File.Exists (FullPath + ".mdb")) { AssemblyDefinition.MainModule.ReadSymbols (); symbols_loaded = true; } } catch { // do not let stale file crash us Driver.Log (3, "Invalid debugging symbols for {0} ignored", FullPath); } } void AddResourceToBeRemoved (string resource) { if (link_with_resources == null) link_with_resources = new List (); link_with_resources.Add (resource); } public void ExtractNativeLinkInfo () { // ignore framework assemblies, they won't have any LinkWith attributes if (IsFrameworkAssembly == true) return; var assembly = AssemblyDefinition; if (!assembly.HasCustomAttributes) return; string resourceBundlePath = Path.ChangeExtension (FullPath, ".resources"); string manifestPath = Path.Combine (resourceBundlePath, "manifest"); if (File.Exists (manifestPath)) { foreach (NativeReferenceMetadata metadata in ReadManifest (manifestPath)) { LogNativeReference (metadata); ProcessNativeReferenceOptions (metadata); if (metadata.LibraryName.EndsWith (".framework", StringComparison.OrdinalIgnoreCase)) { AssertiOSVersionSupportsUserFrameworks (metadata.LibraryName); Frameworks.Add (metadata.LibraryName); #if MMP // HACK - MMP currently doesn't respect Frameworks on non-App - https://github.com/xamarin/xamarin-macios/issues/5203 App.Frameworks.Add (metadata.LibraryName); #endif } else { #if MMP // HACK - MMP currently doesn't respect LinkWith - https://github.com/xamarin/xamarin-macios/issues/5203 Driver.native_references.Add (metadata.LibraryName); #endif LinkWith.Add (metadata.LibraryName); } } } ProcessLinkWithAttributes (assembly); // Make sure there are no duplicates between frameworks and weak frameworks. // Keep the weak ones. if (Frameworks != null && WeakFrameworks != null) Frameworks.ExceptWith (WeakFrameworks); if (NeedsGccExceptionHandling) { if (LinkerFlags == null) LinkerFlags = new List (); LinkerFlags.Add ("-lgcc_eh"); } } IEnumerable ReadManifest (string manifestPath) { XmlDocument document = new XmlDocument (); document.LoadWithoutNetworkAccess (manifestPath); foreach (XmlNode referenceNode in document.GetElementsByTagName ("NativeReference")) { NativeReferenceMetadata metadata = new NativeReferenceMetadata (); metadata.LibraryName = Path.Combine (Path.GetDirectoryName (manifestPath), referenceNode.Attributes ["Name"].Value); var attributes = new Dictionary (); foreach (XmlNode attribute in referenceNode.ChildNodes) attributes [attribute.Name] = attribute.InnerText; metadata.ForceLoad = ParseAttributeWithDefault (attributes ["ForceLoad"], false); metadata.Frameworks = attributes ["Frameworks"]; metadata.WeakFrameworks = attributes ["WeakFrameworks"]; metadata.LinkerFlags = attributes ["LinkerFlags"]; metadata.NeedsGccExceptionHandling = ParseAttributeWithDefault (attributes ["NeedsGccExceptionHandling"], false); metadata.IsCxx = ParseAttributeWithDefault (attributes ["IsCxx"], false); metadata.SmartLink = ParseAttributeWithDefault (attributes ["SmartLink"], true); // TODO - The project attributes do not contain these bits, is that OK? //metadata.LinkTarget = (LinkTarget) Enum.Parse (typeof (LinkTarget), attributes ["LinkTarget"]); //metadata.Dlsym = (DlsymOption)Enum.Parse (typeof (DlsymOption), attributes ["Dlsym"]); yield return metadata; } } static bool ParseAttributeWithDefault (string attribute, bool defaultValue) => string.IsNullOrEmpty (attribute) ? defaultValue : bool.Parse (attribute); void ProcessLinkWithAttributes (AssemblyDefinition assembly) { // // Tasks: // * Remove LinkWith attribute: this is done in the linker. // * Remove embedded resources related to LinkWith attribute from assembly: this is done at a later stage, // here we just compile a list of resources to remove. // * Extract embedded resources related to LinkWith attribute to a file // * Modify the linker flags used to build/link the dylib (if fastdev) or the main binary (if !fastdev) // for (int i = 0; i < assembly.CustomAttributes.Count; i++) { CustomAttribute attr = assembly.CustomAttributes [i]; if (attr.Constructor == null) continue; TypeReference type = attr.Constructor.DeclaringType; if (!type.Is ("ObjCRuntime", "LinkWithAttribute")) continue; // Let the linker remove it the attribute from the assembly HasLinkWithAttributes = true; LinkWithAttribute linkWith = GetLinkWithAttribute (attr); NativeReferenceMetadata metadata = new NativeReferenceMetadata (linkWith); // If we've already processed this native library, skip it if (LinkWith.Any (x => Path.GetFileName (x) == metadata.LibraryName) || Frameworks.Any (x => Path.GetFileName (x) == metadata.LibraryName)) continue; // Remove the resource from the assembly at a later stage. if (!string.IsNullOrEmpty (metadata.LibraryName)) AddResourceToBeRemoved (metadata.LibraryName); ProcessNativeReferenceOptions (metadata); if (!string.IsNullOrEmpty (linkWith.LibraryName)) { if (linkWith.LibraryName.EndsWith (".framework", StringComparison.OrdinalIgnoreCase)) { AssertiOSVersionSupportsUserFrameworks (linkWith.LibraryName); Frameworks.Add (ExtractFramework (assembly, metadata)); } else { LinkWith.Add (ExtractNativeLibrary (assembly, metadata)); } } } } void AssertiOSVersionSupportsUserFrameworks (string path) { if (App.Platform == ApplePlatform.iOS && App.DeploymentTarget.Major < 8) { throw ErrorHelper.CreateError (1305, Errors.MT1305, FileName, Path.GetFileName (path), App.DeploymentTarget); } } void ProcessNativeReferenceOptions (NativeReferenceMetadata metadata) { // We can't add -dead_strip if there are any LinkWith attributes where smart linking is disabled. if (!metadata.SmartLink) App.DeadStrip = false; // Don't add -force_load if the binding's SmartLink value is set and the static registrar is being used. if (metadata.ForceLoad && !(metadata.SmartLink && App.Registrar == RegistrarMode.Static)) ForceLoad = true; if (!string.IsNullOrEmpty (metadata.LinkerFlags)) { if (LinkerFlags == null) LinkerFlags = new List (); if (!StringUtils.TryParseArguments (metadata.LinkerFlags, out string [] args, out var ex)) throw ErrorHelper.CreateError (148, ex, Errors.MX0148, metadata.LinkerFlags, metadata.LibraryName, FileName, ex.Message); LinkerFlags.AddRange (args); } if (!string.IsNullOrEmpty (metadata.Frameworks)) { foreach (var f in metadata.Frameworks.Split (new char [] { ' ' })) { if (Frameworks == null) Frameworks = new HashSet (); Frameworks.Add (f); } } if (!string.IsNullOrEmpty (metadata.WeakFrameworks)) { foreach (var f in metadata.WeakFrameworks.Split (new char [] { ' ' })) { if (WeakFrameworks == null) WeakFrameworks = new HashSet (); WeakFrameworks.Add (f); } } if (metadata.NeedsGccExceptionHandling) NeedsGccExceptionHandling = true; if (metadata.IsCxx) EnableCxx = true; #if MONOTOUCH if (metadata.Dlsym != DlsymOption.Default) App.SetDlsymOption (FullPath, metadata.Dlsym == DlsymOption.Required); #endif } string ExtractNativeLibrary (AssemblyDefinition assembly, NativeReferenceMetadata metadata) { string path = Path.Combine (App.Cache.Location, metadata.LibraryName); if (!Application.IsUptodate (FullPath, path)) { Application.ExtractResource (assembly.MainModule, metadata.LibraryName, path, false); Driver.Log (3, "Extracted third-party binding '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, path); LogNativeReference (metadata); } else { Driver.Log (3, "Target '{0}' is up-to-date.", path); } if (!File.Exists (path)) ErrorHelper.Warning (1302, Errors.MT1302, metadata.LibraryName, path); return path; } string ExtractFramework (AssemblyDefinition assembly, NativeReferenceMetadata metadata) { string path = Path.Combine (App.Cache.Location, metadata.LibraryName); var zipPath = path + ".zip"; if (!Application.IsUptodate (FullPath, zipPath)) { Application.ExtractResource (assembly.MainModule, metadata.LibraryName, zipPath, false); Driver.Log (3, "Extracted third-party framework '{0}' from '{1}' to '{2}'", metadata.LibraryName, FullPath, zipPath); LogNativeReference (metadata); } else { Driver.Log (3, "Target '{0}' is up-to-date.", path); } if (!File.Exists (zipPath)) { ErrorHelper.Warning (1302, Errors.MT1302, metadata.LibraryName, zipPath); } else { if (!Directory.Exists (path)) Directory.CreateDirectory (path); if (Driver.RunCommand ("/usr/bin/unzip", "-u", "-o", "-d", path, zipPath) != 0) throw ErrorHelper.CreateError (1303, Errors.MT1303, metadata.LibraryName, zipPath); } return path; } static void LogNativeReference (NativeReferenceMetadata metadata) { Driver.Log (3, " LibraryName: {0}", metadata.LibraryName); Driver.Log (3, " From: {0}", metadata.Attribute != null ? "LinkWith" : "Binding Manifest"); Driver.Log (3, " ForceLoad: {0}", metadata.ForceLoad); Driver.Log (3, " Frameworks: {0}", metadata.Frameworks); Driver.Log (3, " IsCxx: {0}", metadata.IsCxx); Driver.Log (3, " LinkerFlags: {0}", metadata.LinkerFlags); Driver.Log (3, " LinkTarget: {0}", metadata.LinkTarget); Driver.Log (3, " NeedsGccExceptionHandling: {0}", metadata.NeedsGccExceptionHandling); Driver.Log (3, " SmartLink: {0}", metadata.SmartLink); Driver.Log (3, " WeakFrameworks: {0}", metadata.WeakFrameworks); } public static LinkWithAttribute GetLinkWithAttribute (CustomAttribute attr) { LinkWithAttribute linkWith; var cargs = attr.ConstructorArguments; switch (cargs.Count) { case 3: linkWith = new LinkWithAttribute ((string) cargs [0].Value, (LinkTarget) cargs [1].Value, (string) cargs [2].Value); break; case 2: linkWith = new LinkWithAttribute ((string) cargs [0].Value, (LinkTarget) cargs [1].Value); break; case 0: linkWith = new LinkWithAttribute (); break; default: case 1: linkWith = new LinkWithAttribute ((string) cargs [0].Value); break; } foreach (var property in attr.Properties) { switch (property.Name) { case "NeedsGccExceptionHandling": linkWith.NeedsGccExceptionHandling = (bool) property.Argument.Value; break; case "WeakFrameworks": linkWith.WeakFrameworks = (string) property.Argument.Value; break; case "Frameworks": linkWith.Frameworks = (string) property.Argument.Value; break; case "LinkerFlags": linkWith.LinkerFlags = (string) property.Argument.Value; break; case "LinkTarget": linkWith.LinkTarget = (LinkTarget) property.Argument.Value; break; case "ForceLoad": linkWith.ForceLoad = (bool) property.Argument.Value; break; case "IsCxx": linkWith.IsCxx = (bool) property.Argument.Value; break; case "SmartLink": linkWith.SmartLink = (bool) property.Argument.Value; break; case "Dlsym": linkWith.Dlsym = (DlsymOption) property.Argument.Value; break; default: break; } } return linkWith; } void AddFramework (string file) { if (Driver.GetFrameworks (App).TryGetValue (file, out var framework) && framework.Version > App.SdkVersion) ErrorHelper.Warning (135, Errors.MX0135, file, FileName, App.PlatformName, framework.Version, App.SdkVersion); else { var strong = (framework == null) || (App.DeploymentTarget >= (App.IsSimulatorBuild ? framework.VersionAvailableInSimulator ?? framework.Version : framework.Version)); if (strong) { if (Frameworks.Add (file)) Driver.Log (3, "Linking with the framework {0} because it's referenced by a module reference in {1}", file, FileName); } else { if (WeakFrameworks.Add (file)) Driver.Log (3, "Linking (weakly) with the framework {0} because it's referenced by a module reference in {1}", file, FileName); } } } public string GetCompressionLinkingFlag () { switch(App.Platform) { case ApplePlatform.MacOSX: if (App.DeploymentTarget >= new Version (10, 11, 0)) return "-lcompression"; return "-weak-lcompression"; case ApplePlatform.iOS: if (App.DeploymentTarget >= new Version (9,0)) return "-lcompression"; return "-weak-lcompression"; case ApplePlatform.TVOS: case ApplePlatform.WatchOS: return "-lcompression"; default: throw ErrorHelper.CreateError (71, Errors.MX0071, App.Platform, App.SdkVersion); } } public void ComputeLinkerFlags () { foreach (var m in AssemblyDefinition.Modules) { if (!m.HasModuleReferences) continue; foreach (var mr in m.ModuleReferences) { string name = mr.Name; if (string.IsNullOrEmpty (name)) continue; // obfuscated assemblies. string file = Path.GetFileNameWithoutExtension (name); if (App.IsSimulatorBuild && !App.IsFrameworkAvailableInSimulator (file)) { Driver.Log (3, "Not linking with {0} (referenced by a module reference in {1}) because it's not available in the simulator.", file, FileName); continue; } switch (file) { // special case case "__Internal": case "System.Native": case "System.Security.Cryptography.Native.Apple": case "System.Net.Security.Native": // well known libs case "libc": case "libSystem": case "libobjc": case "libdyld": case "libsystem_kernel": break; case "sqlite3": LinkerFlags.Add ("-lsqlite3"); Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); break; case "libsqlite3": // remove lib prefix LinkerFlags.Add ("-l" + file.Substring (3)); Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName); break; case "libcompression": LinkerFlags.Add (GetCompressionLinkingFlag ()); break; case "libGLES": case "libGLESv2": // special case for OpenGLES.framework if (Frameworks.Add ("OpenGLES")) Driver.Log (3, "Linking with the framework OpenGLES because {0} is referenced by a module reference in {1}", file, FileName); break; case "vImage": case "vecLib": // sub-frameworks if (Frameworks.Add ("Accelerate")) Driver.Log (3, "Linking with the framework Accelerate because {0} is referenced by a module reference in {1}", file, FileName); break; case "openal32": if (Frameworks.Add ("OpenAL")) Driver.Log (3, "Linking with the framework OpenAL because {0} is referenced by a module reference in {1}", file, FileName); break; default: if (App.Platform == ApplePlatform.MacOSX) { string path = Path.GetDirectoryName (name); if (!path.StartsWith ("/System/Library/Frameworks", StringComparison.Ordinal)) continue; // CoreServices has multiple sub-frameworks that can be used by customer code if (path.StartsWith ("/System/Library/Frameworks/CoreServices.framework/", StringComparison.Ordinal)) { if (Frameworks.Add ("CoreServices")) Driver.Log (3, "Linking with the framework CoreServices because {0} is referenced by a module reference in {1}", file, FileName); break; } // ApplicationServices has multiple sub-frameworks that can be used by customer code if (path.StartsWith ("/System/Library/Frameworks/ApplicationServices.framework/", StringComparison.Ordinal)) { if (Frameworks.Add ("ApplicationServices")) Driver.Log (3, "Linking with the framework ApplicationServices because {0} is referenced by a module reference in {1}", file, FileName); break; } } // detect frameworks int f = name.IndexOf (".framework/", StringComparison.Ordinal); if (f > 0) { AddFramework (file); } else { if (UnresolvedModuleReferences == null) UnresolvedModuleReferences = new HashSet (); UnresolvedModuleReferences.Add (mr); Driver.Log (3, "Could not resolve the module reference {0} in {1}", file, FileName); } break; } } } } public override string ToString () { return FileName; } // This returns the path to all related files: // * The assembly itself // * Any debug files (mdb/pdb) // * Any config files // * Any satellite assemblies public IEnumerable GetRelatedFiles () { yield return FullPath; var mdb = FullPath + ".mdb"; if (File.Exists (mdb)) yield return mdb; var pdb = Path.ChangeExtension (FullPath, ".pdb"); if (File.Exists (pdb)) yield return pdb; var config = FullPath + ".config"; if (File.Exists (config)) yield return config; if (Satellites != null) { foreach (var satellite in Satellites) yield return satellite; } } public void ComputeSatellites () { var satellite_name = Path.GetFileNameWithoutExtension (FullPath) + ".resources.dll"; var path = Path.GetDirectoryName (FullPath); // first look if satellites are located in subdirectories of the current location of the assembly ComputeSatellites (satellite_name, path); if (Satellites == null) { // 2nd chance: satellite assemblies can come from different nugets (as dependencies) // they will be copied (at build time) into the destination directory (making them work at runtime) // but they won't be side-by-side the original assembly (which breaks our build time assumptions) path = Path.GetDirectoryName (App.RootAssemblies [0]); if (string.IsNullOrEmpty (path)) path = Environment.CurrentDirectory; ComputeSatellites (satellite_name, path); } } void ComputeSatellites (string satellite_name, string path) { foreach (var subdir in Directory.GetDirectories (path)) { var culture_name = Path.GetFileName (subdir); CultureInfo ci; if (culture_name.IndexOf ('.') >= 0) continue; // cultures can't have dots. This way we don't check every *.app directory // well-known subdirectories (that are not cultures) to avoid (slow) exceptions handling switch (culture_name) { case "Facades": case "repl": case "device-builds": case "Design": // XF continue; } try { ci = CultureInfo.GetCultureInfo (culture_name); } catch { // nope, not a resource language continue; } if (ci == null) continue; var satellite = Path.Combine (subdir, satellite_name); if (File.Exists (satellite)) { if (Satellites == null) Satellites = new List (); Satellites.Add (satellite); } } } public void CopySatellitesToDirectory (string directory) { if (Satellites == null) return; foreach (var a in Satellites) { string target_dir = Path.Combine (directory, Path.GetFileName (Path.GetDirectoryName (a))); string target_s = Path.Combine (target_dir, Path.GetFileName (a)); if (!Directory.Exists (target_dir)) Directory.CreateDirectory (target_dir); CopyAssembly (a, target_s); } } public delegate bool StripAssembly (string path); // returns false if the assembly was not copied (because it was already up-to-date). public bool CopyAssembly (string source, string target, bool copy_debug_symbols = true, StripAssembly strip = null) { var copied = false; try { var strip_assembly = strip != null && strip (source); if (!Application.IsUptodate (source, target) && (strip_assembly || !Cache.CompareAssemblies (source, target))) { copied = true; if (strip_assembly) { Driver.FileDelete (target); Directory.CreateDirectory (Path.GetDirectoryName (target)); MonoTouch.Tuner.Stripper.Process (source, target); } else { Application.CopyFile (source, target); } } else { Driver.Log (3, "Target '{0}' is up-to-date.", target); } // Update the debug symbols file even if the assembly didn't change. if (copy_debug_symbols && HasValidSymbols) { // Unfortunately Cecil won't tell us the path of the symbol file, so we have to try all we support (.pdb+.mdb) if (File.Exists (source + ".mdb")) Application.UpdateFile (source + ".mdb", target + ".mdb", true); var spdb = Path.ChangeExtension (source, "pdb"); if (File.Exists (spdb)) Application.UpdateFile (spdb, Path.ChangeExtension (target, "pdb"), true); } CopyConfigToDirectory (Path.GetDirectoryName (target)); } catch (Exception e) { throw new ProductException (1009, true, e, Errors.MX1009, source, target, e.Message); } return copied; } public void CopyConfigToDirectory (string directory) { string config_src = FullPath + ".config"; if (File.Exists (config_src)) { string config_target = Path.Combine (directory, FileName + ".config"); Application.UpdateFile (config_src, config_target, true); } } public bool IsAOTCompiled { get { return App.IsAOTCompiled (Identity); } } } public sealed class NormalizedStringComparer : IEqualityComparer { public static readonly NormalizedStringComparer OrdinalIgnoreCase = new NormalizedStringComparer (StringComparer.OrdinalIgnoreCase); StringComparer comparer; public NormalizedStringComparer (StringComparer comparer) { this.comparer = comparer; } public bool Equals (string x, string y) { // From what I gather it doesn't matter which normalization form // is used, but I chose Form D because HFS normalizes to Form D. if (x != null) x = x.Normalize (System.Text.NormalizationForm.FormD); if (y != null) y = y.Normalize (System.Text.NormalizationForm.FormD); return comparer.Equals (x, y); } public int GetHashCode (string obj) { return comparer.GetHashCode (obj?.Normalize (System.Text.NormalizationForm.FormD)); } } public class AssemblyCollection : IEnumerable { Dictionary HashedAssemblies = new Dictionary (NormalizedStringComparer.OrdinalIgnoreCase); public void Add (Assembly assembly) { Assembly other; if (HashedAssemblies.TryGetValue (assembly.Identity, out other)) throw ErrorHelper.CreateError (2018, Errors.MT2018, assembly.Identity, other.FullPath, assembly.FullPath); HashedAssemblies.Add (assembly.Identity, assembly); } public void AddRange (AssemblyCollection assemblies) { foreach (var a in assemblies) Add (a); } public int Count { get { return HashedAssemblies.Count; } } public IDictionary Hashed { get { return HashedAssemblies; } } public bool TryGetValue (string identity, out Assembly assembly) { return HashedAssemblies.TryGetValue (identity, out assembly); } public bool TryGetValue (AssemblyDefinition asm, out Assembly assembly) { return HashedAssemblies.TryGetValue (Assembly.GetIdentity (asm), out assembly); } public bool Contains (AssemblyDefinition asm) { return HashedAssemblies.ContainsKey (Assembly.GetIdentity (asm)); } public bool ContainsKey (string identity) { return HashedAssemblies.ContainsKey (identity); } public void Remove (string identity) { HashedAssemblies.Remove (identity); } public void Remove (Assembly assembly) { Remove (assembly.Identity); } public Assembly this [string key] { get { return HashedAssemblies [key]; } set { HashedAssemblies [key] = value; } } public void Update (Target target, IEnumerable assemblies) { // This function will remove any assemblies not in 'assemblies', and add any new assemblies. var current = new HashSet (HashedAssemblies.Keys, HashedAssemblies.Comparer); foreach (var assembly in assemblies) { var identity = Assembly.GetIdentity (assembly); if (!current.Remove (identity)) { // new assembly var asm = new Assembly (target, assembly); Add (asm); Driver.Log (1, "The linker added the assembly '{0}' to '{1}' to satisfy a reference.", asm.Identity, target.App.Name); } else { this [identity].AssemblyDefinition = assembly; } } foreach (var removed in current) { Driver.Log (1, "The linker removed the assembly '{0}' from '{1}' since there is no more reference to it.", this [removed].Identity, target.App.Name); Remove (removed); } } #region Interface implementations IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } public IEnumerator GetEnumerator () { return HashedAssemblies.Values.GetEnumerator (); } #endregion } }