// Copyright 2013--2014 Xamarin Inc. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Text; using System.Threading.Tasks; using System.Xml; using MonoTouch.Tuner; using Mono.Cecil; using Mono.Tuner; using Mono.Linker; using Xamarin.Linker; using Xamarin.Utils; using XamCore.Registrar; namespace Xamarin.Bundler { public partial class Target { public string TargetDirectory; public string AppTargetDirectory; public MonoTouchManifestResolver ManifestResolver = new MonoTouchManifestResolver (); public AssemblyDefinition ProductAssembly; // directories used during the build process public string ArchDirectory; public string PreBuildDirectory; public string BuildDirectory; public string LinkDirectory; public string NoResDirectory; // Note that each 'Target' can have multiple abis: armv7+armv7s for instance. public List Abis; // This is a list of native libraries to into the final executable. // All the native libraries are included here (this means all the libraries // that were AOTed from managed code, main, registrar, extra static libraries, etc). List link_with = new List (); // If we didn't link because the existing (cached) assemblyes are up-to-date. bool cached_link; // If any assemblies were updated (only set to false if the linker is disabled and no assemblies were modified). bool any_assembly_updated = true; BuildTasks compile_tasks = new BuildTasks (); // If we didn't link the final executable because the existing binary is up-to-date. public bool cached_executable; // If the assemblies were symlinked. public bool Symlinked; public bool Is32Build { get { return Application.IsArchEnabled (Abis, Abi.Arch32Mask); } } // If we're targetting a 32 bit arch for this target. public bool Is64Build { get { return Application.IsArchEnabled (Abis, Abi.Arch64Mask); } } // If we're targetting a 64 bit arch for this target. List link_with_and_ship = new List (); public IEnumerable LibrariesToShip { get { return link_with_and_ship; } } PInvokeWrapperGenerator pinvoke_state; PInvokeWrapperGenerator MarshalNativeExceptionsState { get { if (!App.RequiresPInvokeWrappers) return null; if (pinvoke_state == null) { pinvoke_state = new PInvokeWrapperGenerator () { SourcePath = Path.Combine (ArchDirectory, "pinvokes.m"), HeaderPath = Path.Combine (ArchDirectory, "pinvokes.h"), Registrar = (StaticRegistrar) StaticRegistrar, }; } return pinvoke_state; } } public string Executable { get { return Path.Combine (TargetDirectory, App.ExecutableName); } } public void Initialize (bool show_warnings) { // we want to load our own mscorlib[-runtime].dll, not something else we're being feeded // (e.g. bug #6612) since it might not match the libmono[-sgen].a library we'll link with, // so load the corlib we want first. var corlib_path = Path.Combine (Resolver.FrameworkDirectory, "mscorlib.dll"); var corlib = ManifestResolver.Load (corlib_path); if (corlib == null) throw new MonoTouchException (2006, true, "Can not load mscorlib.dll from: '{0}'. Please reinstall Xamarin.iOS.", corlib_path); foreach (var reference in App.References) { var ad = ManifestResolver.Load (reference); if (ad == null) throw new MonoTouchException (2002, true, "Can not resolve reference: {0}", reference); if (ad.MainModule.Runtime > TargetRuntime.Net_4_0) ErrorHelper.Show (new MonoTouchException (11, false, "{0} was built against a more recent runtime ({1}) than Xamarin.iOS supports.", Path.GetFileName (reference), ad.MainModule.Runtime)); // Figure out if we're referencing Xamarin.iOS or monotouch.dll if (Path.GetFileNameWithoutExtension (ad.MainModule.FileName) == Driver.ProductAssembly) ProductAssembly = ad; } ComputeListOfAssemblies (); if (App.LinkMode == LinkMode.None && App.I18n != I18nAssemblies.None) AddI18nAssemblies (); // an extension is a .dll and it would match itself if (App.IsExtension) return; var root_wo_ext = Path.GetFileNameWithoutExtension (App.RootAssembly); foreach (var assembly in Assemblies) { if (!assembly.FullPath.EndsWith (".exe", StringComparison.OrdinalIgnoreCase)) { if (root_wo_ext == Path.GetFileNameWithoutExtension (assembly.FullPath)) throw new MonoTouchException (23, true, "Application name '{0}.exe' conflicts with another user assembly.", root_wo_ext); } } } // This is to load the symbols for all assemblies, so that we can give better error messages // (with file name / line number information). public void LoadSymbols () { foreach (var a in Assemblies) a.LoadSymbols (); } IEnumerable GetAssemblies () { if (App.LinkMode == LinkMode.None) return ManifestResolver.GetAssemblies (); List assemblies = new List (); if (LinkContext == null) { // use data from cache foreach (var assembly in Assemblies) assemblies.Add (assembly.AssemblyDefinition); } else { foreach (var assembly in LinkContext.GetAssemblies ()) { if (LinkContext.Annotations.GetAction (assembly) == AssemblyAction.Delete) continue; assemblies.Add (assembly); } } return assemblies; } public void ComputeLinkerFlags () { foreach (var a in Assemblies) a.ComputeLinkerFlags (); if (App.Platform != ApplePlatform.WatchOS && App.Platform != ApplePlatform.TVOS) Frameworks.Add ("CFNetwork"); // required by xamarin_start_wwan } Dictionary entry_points; public IDictionary GetEntryPoints () { if (entry_points == null) GetRequiredSymbols (); return entry_points; } public IEnumerable GetRequiredSymbols () { if (entry_points != null) return entry_points.Keys; var cache_location = Path.Combine (Cache.Location, "entry-points.txt"); if (cached_link || !any_assembly_updated) { entry_points = new Dictionary (); foreach (var ep in File.ReadAllLines (cache_location)) entry_points.Add (ep, null); } else { List marshal_exception_pinvokes; if (LinkContext == null) { // This happens when using the simlauncher and the msbuild tasks asked for a list // of symbols (--symbollist). In that case just produce an empty list, since the // binary shouldn't end up stripped anyway. entry_points = new Dictionary (); marshal_exception_pinvokes = new List (); } else { entry_points = LinkContext.RequiredSymbols; marshal_exception_pinvokes = LinkContext.MarshalExceptionPInvokes; } // keep the debugging helper in debugging binaries only if (App.EnableDebug && !App.EnableBitCode) entry_points.Add ("mono_pmip", null); if (App.IsSimulatorBuild) { entry_points.Add ("xamarin_dyn_objc_msgSend", null); entry_points.Add ("xamarin_dyn_objc_msgSendSuper", null); entry_points.Add ("xamarin_dyn_objc_msgSend_stret", null); entry_points.Add ("xamarin_dyn_objc_msgSendSuper_stret", null); } File.WriteAllText (cache_location, string.Join ("\n", entry_points.Keys.ToArray ())); } return entry_points.Keys; } public MemberReference GetMemberForSymbol (string symbol) { MemberReference rv = null; entry_points?.TryGetValue (symbol, out rv); return rv; } // // Gets a flattened list of all the assemblies pulled by the root assembly // public void ComputeListOfAssemblies () { var exceptions = new List (); var assemblies = new HashSet (); try { var assembly = ManifestResolver.Load (App.RootAssembly); ComputeListOfAssemblies (assemblies, assembly, exceptions); } catch (MonoTouchException mte) { exceptions.Add (mte); } catch (Exception e) { exceptions.Add (new MonoTouchException (9, true, e, "Error while loading assemblies: {0}", e.Message)); } if (App.LinkMode == LinkMode.None) exceptions.AddRange (ManifestResolver.list); if (exceptions.Count > 0) throw new AggregateException (exceptions); } void ComputeListOfAssemblies (HashSet assemblies, AssemblyDefinition assembly, List exceptions) { if (assembly == null) return; var fqname = assembly.MainModule.FileName; if (assemblies.Contains (fqname)) return; assemblies.Add (fqname); var asm = new Assembly (this, assembly); asm.ComputeSatellites (); this.Assemblies.Add (asm); var main = assembly.MainModule; foreach (AssemblyNameReference reference in main.AssemblyReferences) { // Verify that none of the references references an incorrect platform assembly. switch (reference.Name) { case "monotouch": case "Xamarin.iOS": case "Xamarin.TVOS": case "Xamarin.WatchOS": if (reference.Name != Driver.ProductAssembly) exceptions.Add (ErrorHelper.CreateError (34, "Cannot reference '{0}.dll' in a {1} project - it is implicitly referenced by '{2}'.", reference.Name, Driver.TargetFramework.Identifier, assembly.FullName)); break; } var reference_assembly = ManifestResolver.Resolve (reference); ComputeListOfAssemblies (assemblies, reference_assembly, exceptions); } // Custom Attribute metadata can include references to other assemblies, e.g. [X (typeof (Y)], // but it is not reflected in AssemblyReferences :-( ref: #37611 // so we must scan every custom attribute to look for System.Type GetCustomAttributeReferences (assembly, assemblies, exceptions); GetCustomAttributeReferences (main, assemblies, exceptions); if (main.HasTypes) { foreach (var t in main.Types) { GetTypeReferences (t, assemblies, exceptions); } } } void GetTypeReferences (TypeDefinition type, HashSet assemblies, List exceptions) { GetCustomAttributeReferences (type, assemblies, exceptions); if (type.HasEvents) { foreach (var e in type.Events) GetCustomAttributeReferences (e, assemblies, exceptions); } if (type.HasFields) { foreach (var f in type.Fields) GetCustomAttributeReferences (f, assemblies, exceptions); } if (type.HasMethods) { foreach (var m in type.Methods) GetCustomAttributeReferences (m, assemblies, exceptions); } if (type.HasProperties) { foreach (var p in type.Properties) GetCustomAttributeReferences (p, assemblies, exceptions); } if (type.HasNestedTypes) { foreach (var nt in type.NestedTypes) GetTypeReferences (nt, assemblies, exceptions); } } void GetCustomAttributeReferences (ICustomAttributeProvider cap, HashSet assemblies, List exceptions) { if (!cap.HasCustomAttributes) return; foreach (var ca in cap.CustomAttributes) { if (ca.HasConstructorArguments) { foreach (var arg in ca.ConstructorArguments) GetCustomAttributeArgumentReference (arg, assemblies, exceptions); } if (ca.HasFields) { foreach (var arg in ca.Fields) GetCustomAttributeArgumentReference (arg.Argument, assemblies, exceptions); } if (ca.HasProperties) { foreach (var arg in ca.Properties) GetCustomAttributeArgumentReference (arg.Argument, assemblies, exceptions); } } } void GetCustomAttributeArgumentReference (CustomAttributeArgument arg, HashSet assemblies, List exceptions) { if (!arg.Type.Is ("System", "Type")) return; var ar = (arg.Value as TypeReference)?.Scope as AssemblyNameReference; if (ar == null) return; var reference_assembly = ManifestResolver.Resolve (ar); ComputeListOfAssemblies (assemblies, reference_assembly, exceptions); } bool IncludeI18nAssembly (Mono.Linker.I18nAssemblies assembly) { return (App.I18n & assembly) != 0; } public void AddI18nAssemblies () { Assemblies.Add (LoadI18nAssembly ("I18N")); if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.CJK)) Assemblies.Add (LoadI18nAssembly ("I18N.CJK")); if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.MidEast)) Assemblies.Add (LoadI18nAssembly ("I18N.MidEast")); if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.Other)) Assemblies.Add (LoadI18nAssembly ("I18N.Other")); if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.Rare)) Assemblies.Add (LoadI18nAssembly ("I18N.Rare")); if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.West)) Assemblies.Add (LoadI18nAssembly ("I18N.West")); } Assembly LoadI18nAssembly (string name) { var assembly = ManifestResolver.Resolve (AssemblyNameReference.Parse (name)); return new Assembly (this, assembly); } public void LinkAssemblies (string main, ref List assemblies, string output_dir, out MonoTouchLinkContext link_context) { if (Driver.Verbosity > 0) Console.WriteLine ("Linking {0} into {1} using mode '{2}'", main, output_dir, App.LinkMode); var cache = Resolver.ToResolverCache (); var resolver = cache != null ? new AssemblyResolver (cache) : new AssemblyResolver (); resolver.AddSearchDirectory (Resolver.RootDirectory); resolver.AddSearchDirectory (Resolver.FrameworkDirectory); LinkerOptions = new LinkerOptions { MainAssembly = Resolver.Load (main), OutputDirectory = output_dir, LinkMode = App.LinkMode, Resolver = resolver, SkippedAssemblies = App.LinkSkipped, I18nAssemblies = App.I18n, LinkSymbols = true, LinkAway = App.LinkAway, ExtraDefinitions = App.Definitions, Device = App.IsDeviceBuild, // by default we keep the code to ensure we're executing on the UI thread (for UI code) for debug builds // but this can be overridden to either (a) remove it from debug builds or (b) keep it in release builds EnsureUIThread = App.ThreadCheck.HasValue ? App.ThreadCheck.Value : App.EnableDebug, DebugBuild = App.EnableDebug, Arch = Is64Build ? 8 : 4, IsDualBuild = App.IsDualBuild, DumpDependencies = App.LinkerDumpDependencies, RuntimeOptions = App.RuntimeOptions, MarshalNativeExceptionsState = MarshalNativeExceptionsState, }; MonoTouch.Tuner.Linker.Process (LinkerOptions, out link_context, out assemblies); Driver.Watch ("Link Assemblies", 1); } public void ManagedLink () { var cache_path = Path.Combine (ArchDirectory, "linked-assemblies.txt"); foreach (var a in Assemblies) a.CopyToDirectory (LinkDirectory, false, check_case: true); // Check if we can use a previous link result. if (!Driver.Force) { var input = new List (); var output = new List (); var cached_output = new List (); if (File.Exists (cache_path)) { cached_output.AddRange (File.ReadAllLines (cache_path)); var cached_loaded = new HashSet (); // Only add the previously linked assemblies (and their satellites) as the input/output assemblies. // Do not add assemblies which the linker process removed. foreach (var a in Assemblies) { if (!cached_output.Contains (a.FullPath)) continue; cached_loaded.Add (a.FullPath); input.Add (a.FullPath); output.Add (Path.Combine (PreBuildDirectory, a.FileName)); if (File.Exists (a.FullPath + ".mdb")) { // Debug files can change without the assemblies themselves changing // This should also invalidate the cached linker results, since the non-linked mdbs can't be copied. input.Add (a.FullPath + ".mdb"); output.Add (Path.Combine (PreBuildDirectory, a.FileName) + ".mdb"); } if (a.Satellites != null) { foreach (var s in a.Satellites) { input.Add (s); output.Add (Path.Combine (PreBuildDirectory, Path.GetFileName (Path.GetDirectoryName (s)), Path.GetFileName (s))); // No need to copy satellite mdb files, satellites are resource-only assemblies. } } } // The linker might have added assemblies that weren't specified/reachable // from the command line arguments (such as I18N assemblies). Those are not // in the Assemblies list at this point (since we haven't run the linker yet) // so make sure we take those into account as well. var not_loaded = cached_output.Except (cached_loaded); foreach (var path in not_loaded) { input.Add (path); output.Add (Path.Combine (PreBuildDirectory, Path.GetFileName (path))); } // Include mtouch here too? // input.Add (Path.Combine (MTouch.MonoTouchDirectory, "usr", "bin", "mtouch")); if (Application.IsUptodate (input, output)) { cached_link = true; for (int i = Assemblies.Count - 1; i >= 0; i--) { var a = Assemblies [i]; if (!cached_output.Contains (a.FullPath)) { Assemblies.RemoveAt (i); continue; } // Load the cached assembly a.LoadAssembly (Path.Combine (PreBuildDirectory, a.FileName)); Driver.Log (3, "Target '{0}' is up-to-date.", a.FullPath); } foreach (var path in not_loaded) { var a = new Assembly (this, path); a.LoadAssembly (Path.Combine (PreBuildDirectory, a.FileName)); Assemblies.Add (a); } Driver.Watch ("Cached assemblies reloaded", 1); Driver.Log ("Cached assemblies reloaded."); return; } } } // Load the assemblies into memory. foreach (var a in Assemblies) a.LoadAssembly (a.FullPath); var assemblies = new List (); foreach (var a in Assemblies) assemblies.Add (a.FullPath); var linked_assemblies = new List (assemblies); LinkAssemblies (App.RootAssembly, ref linked_assemblies, PreBuildDirectory, out LinkContext); // Remove assemblies that were linked away var removed = new HashSet (assemblies); removed.ExceptWith (linked_assemblies); foreach (var assembly in removed) { for (int i = Assemblies.Count - 1; i >= 0; i--) { var ad = Assemblies [i]; if (assembly != ad.FullPath) continue; Assemblies.RemoveAt (i); } } // anything added by the linker will have it's original path var added = new HashSet (); foreach (var assembly in linked_assemblies) added.Add (Path.GetFileName (assembly)); var original = new HashSet (); foreach (var assembly in assemblies) original.Add (Path.GetFileName (assembly)); added.ExceptWith (original); foreach (var assembly in added) { // the linker already copied the assemblies (linked or not) into the output directory // and we must NOT overwrite the linker work with an original (unlinked) assembly string path = Path.Combine (PreBuildDirectory, assembly); var ad = ManifestResolver.Load (path); var a = new Assembly (this, ad); a.CopyToDirectory (PreBuildDirectory); Assemblies.Add (a); } assemblies = linked_assemblies; // Make the assemblies point to the right path. foreach (var a in Assemblies) a.FullPath = Path.Combine (PreBuildDirectory, a.FileName); File.WriteAllText (cache_path, string.Join ("\n", linked_assemblies)); } public void ProcessAssemblies () { // // * Linking // Copy assemblies to LinkDirectory // Link and save to PreBuildDirectory // If marshalling native exceptions: // * Generate/calculate P/Invoke wrappers and save to PreBuildDirectory // * Has resourced to be removed: // Remove resource and save to NoResDirectory // Copy to BuildDirectory. [Why not save directly to BuildDirectory? Because otherwise if we're rebuilding // and the only thing that changed is the resources we're removing in this step, // we won't be able to detect that the remaining parts of the assembly // haven't changed.] // * No resources to be removed: // Copy to BuildDirectory. // [AOT assemblies in BuildDirectory] // Strip managed code save to TargetDirectory (or just copy the file if stripping is disabled). // // * No linking // If marshalling native exceptions: // Generate/calculate P/Invoke wrappers and save to PreBuildDirectory. // If not marshalling native exceptions: // Copy assemblies to PreBuildDirectory // * Has resourced to be removed: // Remove resource and save to NoResDirectory // Copy to BuildDirectory. // * No resources to be removed: // Copy to BuildDirectory. // [AOT assemblies in BuildDirectory] // Strip managed code save to TargetDirectory (or just copy the file if stripping is disabled). // // Note that we end up copying assemblies around quite much, // this is because we we're comparing contents instead of // filestamps, so we need the previous file around to be // able to do the actual comparison. For instance: in the // 'No linking' case above, we copy the assembly to PreBuild // before removing the resources and saving that result to Build. // The copy in PreBuild is required for the next build iteration, // to see if the original assembly has been modified or not (the // file in the Build directory might be different due to resource // removal even if the original assembly didn't change). // // This can probably be improved by storing digests/hashes instead // of the entire files, but this turned out a bit messy when // trying to make it work with the linker, so I decided to go for // simple file copying for now. // // // Other notes: // // * We need all assemblies in the same directory when doing AOT-compilation. // * We cannot overwrite in-place, because it will mess up dependency tracking // and besides if we overwrite in place we might not be able to ignore // insignificant changes (such as only a GUID change - the code is identical, // but we still need the original assembly since the AOT-ed image also stores // the GUID, and we fail at runtime if the GUIDs in the assembly and the AOT-ed // image don't match - if we overwrite in-place we lose the original assembly and // its GUID). // LinkDirectory = Path.Combine (ArchDirectory, "Link"); if (!Directory.Exists (LinkDirectory)) Directory.CreateDirectory (LinkDirectory); PreBuildDirectory = Path.Combine (ArchDirectory, "PreBuild"); if (!Directory.Exists (PreBuildDirectory)) Directory.CreateDirectory (PreBuildDirectory); BuildDirectory = Path.Combine (ArchDirectory, "Build"); if (!Directory.Exists (BuildDirectory)) Directory.CreateDirectory (BuildDirectory); NoResDirectory = Path.Combine (ArchDirectory, "NoRes"); if (!Directory.Exists (NoResDirectory)) Directory.CreateDirectory (NoResDirectory); if (!Directory.Exists (TargetDirectory)) Directory.CreateDirectory (TargetDirectory); ManagedLink (); if (App.RequiresPInvokeWrappers) { // Write P/Invokes var state = MarshalNativeExceptionsState; if (state.Started) { // The generator is 'started' by the linker, which means it may not // be started if the linker was not executed due to re-using cached results. state.End (); } PinvokesTask.Create (compile_tasks, Abis, this, state.SourcePath); if (App.FastDev) { // In this case assemblies must link with the resulting dylib, // so we can't compile the pinvoke dylib in parallel with later // stuff. compile_tasks.ExecuteInParallel (); } } // Now the assemblies are in PreBuildDirectory. // // PreBuildDirectory -> BuildDirectory [resource removal] // // Resource removal // * Remove any __monotouch_content_* or __monotouch_page_* resources // - This is already done in the linker, so there is no need to do it here if the linker is enabled (for user assemblies). // - It is only done for device-builds. Xamarin Studio requires the resources to be present (to extract // them) - which is not a problem for device-builds since we operate on a copied assembly, not the // original like Xamarin Studio does). // * Remove any LinkWith-related resources // var remove_res = App.IsDeviceBuild && App.LinkMode != LinkMode.All; foreach (var a in Assemblies) { var target = Path.Combine (BuildDirectory, a.FileName); if (!Application.IsUptodate (a.FullPath, target)) { a.RemoveResources (remove_res, BuildDirectory, NoResDirectory); } else { a.FullPath = target; Driver.Log (3, "Target '{0}' is up-to-date.", target); } } Driver.Watch ("Removing Resources", 1); Driver.GatherFrameworks (this, Frameworks, WeakFrameworks); // Make sure there are no duplicates between frameworks and weak frameworks. // Keep the weak ones. Frameworks.ExceptWith (WeakFrameworks); } public void SelectStaticRegistrar () { switch (App.Registrar) { case RegistrarMode.Static: case RegistrarMode.Dynamic: case RegistrarMode.Default: StaticRegistrar = new StaticRegistrar (this) { LinkContext = LinkContext, }; break; } } public void Compile () { // Compile the managed assemblies into object files or shared libraries if (App.IsDeviceBuild) { foreach (var a in Assemblies) a.CreateCompilationTasks (compile_tasks, BuildDirectory, Abis); } // The static registrar. List registration_methods = null; if (App.Registrar == RegistrarMode.Static) { var registrar_m = Path.Combine (ArchDirectory, "registrar.m"); var registrar_h = Path.Combine (ArchDirectory, "registrar.h"); if (!Application.IsUptodate (Assemblies.Select (v => v.FullPath), new string[] { registrar_m, registrar_h })) { StaticRegistrar.Generate (Assemblies.Select ((a) => a.AssemblyDefinition), registrar_h, registrar_m); registration_methods = new List (); registration_methods.Add ("xamarin_create_classes"); Driver.Watch ("Registrar", 1); } else { Driver.Log (3, "Target '{0}' is up-to-date.", registrar_m); } RegistrarTask.Create (compile_tasks, Abis, this, registrar_m); } if (App.Registrar == RegistrarMode.Dynamic && App.IsSimulatorBuild && App.LinkMode == LinkMode.None) { if (registration_methods == null) registration_methods = new List (); string method; string library; switch (App.Platform) { case ApplePlatform.iOS: method = "xamarin_create_classes_Xamarin_iOS"; library = "Xamarin.iOS.registrar.a"; break; case ApplePlatform.WatchOS: method = "xamarin_create_classes_Xamarin_WatchOS"; library = "Xamarin.WatchOS.registrar.a"; break; case ApplePlatform.TVOS: method = "xamarin_create_classes_Xamarin_TVOS"; library = "Xamarin.TVOS.registrar.a"; break; default: throw ErrorHelper.CreateError (71, "Unknown platform: {0}. This usually indicates a bug in Xamarin.iOS; please file a bug report at http://bugzilla.xamarin.com with a test case.", App.Platform); } registration_methods.Add (method); link_with.Add (Path.Combine (Driver.ProductSdkDirectory, "usr", "lib", library)); } // The main method. foreach (var abi in Abis) MainTask.Create (compile_tasks, this, abi, Assemblies, App.AssemblyName, registration_methods); // Start compiling. compile_tasks.ExecuteInParallel (); if (App.FastDev) { foreach (var a in Assemblies) { if (a.Dylibs == null) continue; foreach (var dylib in a.Dylibs) LinkWith (dylib); } } Driver.Watch ("Compile", 1); } public void NativeLink () { if (!string.IsNullOrEmpty (App.UserGccFlags)) App.DeadStrip = false; if (App.EnableLLVMOnlyBitCode) App.DeadStrip = false; var compiler_flags = new CompilerFlags () { Target = this }; // Get global frameworks compiler_flags.AddFrameworks (App.Frameworks, App.WeakFrameworks); compiler_flags.AddFrameworks (Frameworks, WeakFrameworks); // Collect all LinkWith flags and frameworks from all assemblies. foreach (var a in Assemblies) { compiler_flags.AddFrameworks (a.Frameworks, a.WeakFrameworks); if (!App.FastDev || App.IsSimulatorBuild) compiler_flags.AddLinkWith (a.LinkWith, a.ForceLoad); compiler_flags.AddOtherFlags (a.LinkerFlags); } var bitcode = App.EnableBitCode; if (bitcode) compiler_flags.AddOtherFlag (App.EnableMarkerOnlyBitCode ? "-fembed-bitcode-marker" : "-fembed-bitcode"); if (App.EnablePie.HasValue && App.EnablePie.Value && (App.DeploymentTarget < new Version (4, 2))) ErrorHelper.Error (28, "Cannot enable PIE (-pie) when targeting iOS 4.1 or earlier. Please disable PIE (-pie:false) or set the deployment target to at least iOS 4.2"); if (!App.EnablePie.HasValue) App.EnablePie = true; if (App.Platform == ApplePlatform.iOS) { if (App.EnablePie.Value && (App.DeploymentTarget >= new Version (4, 2))) { compiler_flags.AddOtherFlag ("-Wl,-pie"); } else { compiler_flags.AddOtherFlag ("-Wl,-no_pie"); } } CompileTask.GetArchFlags (compiler_flags, Abis); if (App.IsDeviceBuild) { compiler_flags.AddOtherFlag ($"-m{Driver.TargetMinSdkName}-version-min={App.DeploymentTarget}"); compiler_flags.AddOtherFlag ($"-isysroot {Driver.Quote (Driver.FrameworkDirectory)}"); } else { CompileTask.GetSimulatorCompilerFlags (compiler_flags, null, App); } compiler_flags.LinkWithMono (); compiler_flags.LinkWithXamarin (); compiler_flags.AddLinkWith (link_with); compiler_flags.AddOtherFlag ($"-o {Driver.Quote (Executable)}"); compiler_flags.AddOtherFlag ("-lz"); compiler_flags.AddOtherFlag ("-liconv"); bool need_libcpp = false; if (App.EnableBitCode) need_libcpp = true; #if ENABLE_BITCODE_ON_IOS need_libcpp = true; #endif if (need_libcpp) compiler_flags.AddOtherFlag ("-lc++"); // allow the native linker to remove unused symbols (if the caller was removed by the managed linker) if (!bitcode) { foreach (var entry in GetRequiredSymbols ()) { // Note that we include *all* (__Internal) p/invoked symbols here // We also include any fields from [Field] attributes. compiler_flags.ReferenceSymbol (entry); } } string mainlib; if (App.IsWatchExtension) { mainlib = "libwatchextension.a"; compiler_flags.AddOtherFlag (" -e _xamarin_watchextension_main"); } else if (App.IsTVExtension) { mainlib = "libtvextension.a"; } else if (App.IsExtension) { mainlib = "libextension.a"; } else { mainlib = "libapp.a"; } var libdir = Path.Combine (Driver.ProductSdkDirectory, "usr", "lib"); var libmain = Path.Combine (libdir, mainlib); compiler_flags.AddLinkWith (libmain, true); if (App.EnableProfiling) { string libprofiler; if (App.FastDev) { libprofiler = Path.Combine (libdir, "libmono-profiler-log.dylib"); compiler_flags.AddLinkWith (libprofiler); } else { libprofiler = Path.Combine (libdir, "libmono-profiler-log.a"); compiler_flags.AddLinkWith (libprofiler); if (!App.EnableBitCode) compiler_flags.ReferenceSymbol ("mono_profiler_startup_log"); } } if (!string.IsNullOrEmpty (App.UserGccFlags)) compiler_flags.AddOtherFlag (App.UserGccFlags); if (App.DeadStrip) compiler_flags.AddOtherFlag ("-dead_strip"); if (App.IsExtension) { if (App.Platform == ApplePlatform.iOS && Driver.XcodeVersion.Major < 7) { compiler_flags.AddOtherFlag ("-lpkstart"); compiler_flags.AddOtherFlag ($"-F {Driver.Quote (Path.Combine (Driver.FrameworkDirectory, "System/Library/PrivateFrameworks"))} -framework PlugInKit"); } compiler_flags.AddOtherFlag ("-fapplication-extension"); } compiler_flags.Inputs = new List (); var flags = compiler_flags.ToString (); // This will populate Inputs. if (!Application.IsUptodate (compiler_flags.Inputs, new string [] { Executable } )) { // always show the native linker warnings since many of them turn out to be very important // and very hard to diagnose otherwise when hidden from the build output. Ref: bug #2430 var linker_errors = new List (); var output = new StringBuilder (); var code = Driver.RunCommand (Driver.CompilerPath, flags, null, output); Application.ProcessNativeLinkerOutput (this, output.ToString (), link_with, linker_errors, code != 0); if (code != 0) { // if the build failed - it could be because of missing frameworks / libraries we identified earlier foreach (var assembly in Assemblies) { if (assembly.UnresolvedModuleReferences == null) continue; foreach (var mr in assembly.UnresolvedModuleReferences) { // TODO: add more diagnose information on the warnings var name = Path.GetFileNameWithoutExtension (mr.Name); linker_errors.Add (new MonoTouchException (5215, false, "References to '{0}' might require additional -framework=XXX or -lXXX instructions to the native linker", name)); } } // mtouch does not validate extra parameters given to GCC when linking (--gcc_flags) if (!String.IsNullOrEmpty (App.UserGccFlags)) linker_errors.Add (new MonoTouchException (5201, true, "Native linking failed. Please review the build log and the user flags provided to gcc: {0}", App.UserGccFlags)); linker_errors.Add (new MonoTouchException (5202, true, "Native linking failed. Please review the build log.", App.UserGccFlags)); } ErrorHelper.Show (linker_errors); } else { cached_executable = true; Driver.Log (3, "Target '{0}' is up-to-date.", Executable); } // the native linker can prefer private (and existing) over public (but non-existing) framework when weak_framework are used // on an iOS target version where the framework does not exists, e.g. targeting iOS6 for JavaScriptCore added in iOS7 results in // /System/Library/PrivateFrameworks/JavaScriptCore.framework/JavaScriptCore instead of // /System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore // more details in https://bugzilla.xamarin.com/show_bug.cgi?id=31036 if (WeakFrameworks.Count > 0) AdjustDylibs (); Driver.Watch ("Native Link", 1); } void AdjustDylibs () { var sb = new StringBuilder (); foreach (var dependency in Xamarin.MachO.GetNativeDependencies (Executable)) { if (!dependency.StartsWith ("/System/Library/PrivateFrameworks/", StringComparison.Ordinal)) continue; var fixed_dep = dependency.Replace ("/PrivateFrameworks/", "/Frameworks/"); sb.Append (" -change ").Append (dependency).Append (' ').Append (fixed_dep); } if (sb.Length > 0) { var quoted_name = Driver.Quote (Executable); sb.Append (' ').Append (quoted_name); Driver.XcodeRun ("install_name_tool", sb.ToString ()); sb.Clear (); } } public bool CanWeSymlinkTheApplication () { if (!Driver.CanWeSymlinkTheApplication ()) return false; foreach (var a in Assemblies) if (!a.CanSymLinkForApplication ()) return false; return true; } public void Symlink () { foreach (var a in Assemblies) a.Symlink (); var targetExecutable = Executable; Application.TryDelete (targetExecutable); try { var launcher = new StringBuilder (); launcher.Append (Path.Combine (Driver.MonoTouchDirectory, "bin", "simlauncher")); if (Is32Build) launcher.Append ("32"); else if (Is64Build) launcher.Append ("64"); launcher.Append ("-sgen"); File.Copy (launcher.ToString (), Executable); } catch (MonoTouchException) { throw; } catch (Exception ex) { throw new MonoTouchException (1015, true, ex, "Failed to create the executable '{0}': {1}", targetExecutable, ex.Message); } Symlinked = true; if (Driver.Verbosity > 0) Console.WriteLine ("Application ({0}) was built using fast-path for simulator.", string.Join (", ", Abis.ToArray ())); } // Thread-safe public void LinkWith (string native_library) { lock (link_with) link_with.Add (native_library); } public void LinkWithAndShip (string dylib) { link_with_and_ship.Add (dylib); } public void StripManagedCode () { var strip = false; strip = App.ManagedStrip && App.IsDeviceBuild && !App.EnableDebug && !App.PackageMdb; if (!Directory.Exists (AppTargetDirectory)) Directory.CreateDirectory (AppTargetDirectory); if (strip) { // note: this is much slower when Parallel.ForEach is used Parallel.ForEach (Assemblies, new ParallelOptions () { MaxDegreeOfParallelism = Driver.Concurrency }, (assembly) => { var file = assembly.FullPath; var output = Path.Combine (AppTargetDirectory, Path.GetFileName (assembly.FullPath)); if (Application.IsUptodate (file, output)) { Driver.Log (3, "Target '{0}' is up-to-date", output); } else { Driver.Log (1, "Stripping assembly {0}", file); Driver.FileDelete (output); Stripper.Process (file, output); } // The stripper will only copy the main assembly. // We need to copy .config files and satellite assemblies too if (App.PackageMdb) assembly.CopyMdbToDirectory (AppTargetDirectory); assembly.CopyConfigToDirectory (AppTargetDirectory); assembly.CopySatellitesToDirectory (AppTargetDirectory); }); Driver.Watch ("Strip Assemblies", 1); } else if (!Symlinked) { foreach (var assembly in Assemblies) assembly.CopyToDirectory (AppTargetDirectory, reload: false, copy_mdb: App.PackageMdb); } } } }