/* * Copyright 2011-2014 Xamarin Inc. All rights reserved. * Copyright 2010 Novell Inc. * * Authors: * Sebastien Pouliot * Aaron Bockover * Rolf Bjarne Kvinge * Geoff Norton * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using System; using System.Collections; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.ComponentModel; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Xml; using System.Threading.Tasks; using Mono.Cecil; using Mono.Linker; using Mono.Options; using Mono.Tuner; using MonoMac.Tuner; using Xamarin.Utils; using Xamarin.Linker; using Registrar; using ObjCRuntime; namespace Xamarin.Bundler { enum Action { None, Help, Version, RunRegistrar, } public static partial class Driver { internal const string NAME = "mmp"; internal static Application App = new Application (Environment.GetCommandLineArgs ()); static Target BuildTarget; static List resources = new List (); static List resolved_assemblies = new List (); static List ignored_assemblies = new List (); public static List native_references = new List (); static List native_libraries_copied_in = new List (); static Action action; static string output_dir; static string app_name; static bool generate_plist; static bool no_executable; static bool embed_mono = true; static string machine_config_path = null; static bool bypass_linking_checks = false; static string contents_dir; static string frameworks_dir; static string macos_dir; static string resources_dir; static string mmp_dir; static string AppPath { get { return Path.Combine (macos_dir, app_name); } } static string icon; static string certificate_name; static bool is_extension, is_xpc_service; static bool frameworks_copied_to_bundle_dir; // Have we copied any frameworks to Foo.app/Contents/Frameworks? static bool dylibs_copied_to_bundle_dir => native_libraries_copied_in.Count > 0; static void ShowHelp (OptionSet os) { Console.WriteLine ("mmp - Xamarin.Mac Packer"); Console.WriteLine ("Copyright 2010 Novell Inc."); Console.WriteLine ("Copyright 2011-2016 Xamarin Inc."); Console.WriteLine ("Usage: mmp [options] application-exe"); os.WriteOptionDescriptions (Console.Out); } public static bool LinkProhibitedFrameworks { get; private set; } public static bool UseLegacyAssemblyResolution { get; private set; } static string mono_prefix; static string MonoPrefix { get { if (mono_prefix == null) { mono_prefix = Environment.GetEnvironmentVariable ("MONO_PREFIX"); if (string.IsNullOrEmpty (mono_prefix)) mono_prefix = "/Library/Frameworks/Mono.framework/Versions/Current"; } return mono_prefix; } } static string PkgConfig { get { var pkg_config = Path.Combine (MonoPrefix, "bin", "pkg-config"); if (!File.Exists (pkg_config)) throw ErrorHelper.CreateError (5313, Errors.MX5313, pkg_config); return pkg_config; } } public static string GetArch32Directory (Application app) { throw new InvalidOperationException ("Arch32Directory when not Mobile or Full?"); } public static string GetArch64Directory (Application app) { if (IsUnifiedMobile) return Path.Combine (GetFrameworkLibDirectory (app), "x86_64", "mobile"); else if (IsUnifiedFullXamMacFramework) return Path.Combine (GetFrameworkLibDirectory (app), "x86_64", "full"); throw new InvalidOperationException ("Arch64Directory when not Mobile or Full?"); } public static bool EnableDebug { get { return App.EnableDebug; } } static int Main2 (string [] args) { var os = new OptionSet () { { "a|assembly=", "Add an assembly to be processed [DEPRECATED, use --reference instead]", v => App.References.Add (v), true /* Hide: this option is deprecated in favor of the shared --reference option instead */ }, { "r|resource=", "Add a resource to be included", v => resources.Add (v) }, { "o|output=", "Specify the output path", v => output_dir = v }, { "n|name=", "Specify the application name", v => app_name = v }, { "s|sgen:", "Use the SGen Garbage Collector", v => { if (!ParseBool (v, "sgen")) ErrorHelper.Warning (43, Errors.MX0043); }, true // do not show the option anymore }, { "boehm:", "Enable the Boehm garbage collector", v => { if (ParseBool (v, "boehm")) ErrorHelper.Warning (43, Errors.MX0043); }, true // do not show the option anymore }, { "new-refcount:", "Enable new refcounting logic", v => { if (!ParseBool (v, "new-refcount")) ErrorHelper.Warning (80, Errors.MX0080); }, true // do not show this option anymore }, { "mapinject", "Inject a fast method map [deprecated]", v => { ErrorHelper.Show (new ProductException (16, false, Errors.MX0016, "--mapinject")); } }, { "minos=", "Minimum supported version of Mac OS X [DEPRECATED, use --targetver instead]", v => { try { App.DeploymentTarget = StringUtils.ParseVersion (v); } catch (Exception ex) { throw ErrorHelper.CreateError (26, ex, Errors.MX0026, $"minos:{v}", ex.Message); } }, true /* Hide: this option is deprecated in favor of the shared --targetver option instead */ }, { "c|certificate=", "The Code Signing certificate for the application", v => { certificate_name = v; }}, { "p", "Generate a plist for the application", v => { generate_plist = true; }}, { "i|icon=", "Use the specified file as the bundle icon", v => { icon = v; }}, { "time", v => WatchLevel++ }, { "arch=", "Specify the architecture ('x86_64') of the native runtime (default to 'x86_64', which is the only valid value) [DEPRECATED, use --abi instead]", v => { App.ParseAbi (v); }, true}, { "profile=", "(Obsoleted in favor of --target-framework) Specify the .NET profile to use", v => SetTargetFramework (v), true }, { "force-thread-check", "Keep UI thread checks inside (even release) builds [DEPRECATED, use --optimize=-remove-uithread-checks instead]", v => { App.Optimizations.RemoveUIThreadChecks = false; }, true}, { "disable-thread-check", "Remove UI thread checks inside (even debug) builds [DEPRECATED, use --optimize=remove-uithread-checks instead]", v => { App.Optimizations.RemoveUIThreadChecks = true; }, true}, { "no-root-assembly", "Specifies that mmp will not process a root assembly. This is if the app needs to be packaged with a different directory structure than what mmp supports.", v => no_executable = true }, { "embed-mono:", "Specifies whether the app will embed the Mono runtime, or if it will use the system Mono found at runtime (default: true).", v => { embed_mono = ParseBool (v, "embed-mono"); } }, { "link_flags=", "Specifies additional arguments to the native linker.", v => { App.ParseCustomLinkFlags (v, "link_flags"); } }, { "ignore-native-library=", "Add a native library to be ignored during assembly scanning and packaging", v => ignored_assemblies.Add (v) }, { "native-reference=", "Add a native (static, dynamic, or framework) library to be included in the bundle. Can be specified multiple times.", v => { native_references.Add (v); if (v.EndsWith (".framework", true, CultureInfo.InvariantCulture)) App.Frameworks.Add (v); else { // allow specifying the library inside the framework directory var p = Path.GetDirectoryName (v); if (p.EndsWith (".framework", true, CultureInfo.InvariantCulture)) App.Frameworks.Add (p); } } }, { "custom_bundle_name=", "Specify a custom name for the MonoBundle folder.", v => App.CustomBundleName = v, true }, // Hidden hack for "universal binaries" { "extension", "Specifies an app extension", v => is_extension = true }, { "xpc", "Specifies an XPC service", v => { is_extension = true; is_xpc_service = true; }}, { "allow-unsafe-gac-resolution", "Allow MSBuild to resolve from the System GAC", v => {} , true }, // Used in Xamarin.Mac.XM45.targets and must be ignored here. Hidden since it is a total hack. If you can use it, you don't need support { "force-unsupported-linker", "Bypass safety checkes preventing unsupported linking options.", v => bypass_linking_checks = true , true }, // Undocumented option for a reason, You get to keep the pieces when it breaks { "disable-lldb-attach=", "Disable automatic lldb attach on crash", v => App.DisableLldbAttach = ParseBool (v, "disable-lldb-attach")}, { "disable-omit-fp=", "Disable a JIT optimization where the frame pointer is omitted from the stack. This is optimization is disabled by default for debug builds.", v => App.DisableOmitFramePointer = ParseBool (v, "disable-omit-fp") }, { "machine-config=", "Custom machine.config file to copy into MonoBundle/mono/4.5/machine.config. Pass \"\" to copy in a valid \"empty\" config file.", v => machine_config_path = v }, { "xamarin-framework-directory=", "The framework directory", v => { framework_dir = v; }, true }, { "xamarin-full-framework", "Used with --target-framework=4.5 to select XM Full Target Framework. Deprecated, use --target-framework=Xamarin.Mac,Version=v4.0,Profile=Full instead.", v => { TargetFramework = TargetFramework.Xamarin_Mac_4_5_Full; }, true }, { "xamarin-system-framework", "Used with --target-framework=4.5 to select XM Full Target Framework. Deprecated, use --target-framework=Xamarin.Mac,Version=v4.0,Profile=System instead.", v => { TargetFramework = TargetFramework.Xamarin_Mac_4_5_System; }, true }, { "aot:", "Specify assemblies that should be AOT compiled\n- none - No AOT (default)\n- all - Every assembly in MonoBundle\n- core - Xamarin.Mac, System, mscorlib\n- sdk - Xamarin.Mac.dll and BCL assemblies\n- |hybrid after option enables hybrid AOT which allows IL stripping but is slower (only valid for 'all')\n - Individual files can be included for AOT via +FileName.dll and excluded via -FileName.dll\n\nExamples:\n --aot:all,-MyAssembly.dll\n --aot:core,+MyOtherAssembly.dll,-mscorlib.dll", v => { App.AOTOptions = new AOTOptions (v); } }, { "link-prohibited-frameworks", "Natively link against prohibited (rejected by AppStore) frameworks", v => { LinkProhibitedFrameworks = true; } }, { "legacy-assembly-resolution", "Use a legacy assembly resolution logic when using the Xamarin.Mac Full framework.", v => { UseLegacyAssemblyResolution = true; }, false /* hidden until we know if it's needed */ }, /* Any new options that are identical between mtouch and mmp should be added to common/Driver.cs */ }; var extra_args = Environment.GetEnvironmentVariable ("MMP_ENV_OPTIONS"); if (!string.IsNullOrEmpty (extra_args)) { var l = new List (args); l.AddRange (extra_args.Split (new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); args = l.ToArray (); } if (ParseOptions (App, os, args, ref action)) return 0; if (App.AOTOptions == null) { string forceAotVariable = Environment.GetEnvironmentVariable ("XM_FORCE_AOT"); if (forceAotVariable != null) App.AOTOptions = new AOTOptions (forceAotVariable); } if (IsUnifiedFullSystemFramework) { // With newer Mono builds, the system assemblies passed to us by msbuild are // no longer safe to copy into the bundle. They are stripped "fake" BCL // copies. So we redirect to the "real" ones. Thanks TargetFrameworkDirectories :( Regex monoAPIRegex = new Regex("lib/mono/.*-api/", RegexOptions.IgnoreCase); Regex monoAPIFacadesRegex = new Regex("lib/mono/.*-api/Facades/", RegexOptions.IgnoreCase); FixReferences (x => monoAPIRegex.IsMatch (x) && !monoAPIFacadesRegex.IsMatch (x), x => x.Replace(monoAPIRegex.Match(x).Value, "lib/mono/4.5/")); } if (App.Registrar == RegistrarMode.PartialStatic && App.LinkMode != LinkMode.None) throw new ProductException (2110, true, Errors.MM2110); // sanity check as this should never happen: we start out by not setting any // Unified/Classic properties, and only IsUnifiedMobile if we are are on the // XM framework. If we are not, we set IsUnifiedFull to true iff we detect // an explicit reference to the full unified Xamarin.Mac assembly; that is // only one of IsUnifiedMobile or IsUnifiedFull should ever be true. IsUnified // is true if one of IsUnifiedMobile or IsUnifiedFull is true; IsClassic is // implied if IsUnified is not true; // Skip this check if we're in .NET 5 mode (which happens when generating the partial static registrar code). if (!TargetFramework.IsDotNet) { int IsUnifiedCount = IsUnifiedMobile ? 1 : 0; if (IsUnifiedFullSystemFramework) IsUnifiedCount++; if (IsUnifiedFullXamMacFramework) IsUnifiedCount++; if (IsUnifiedCount != 1) throw ErrorHelper.CreateError (99, Errors.MX0099, "IsClassic/IsUnified/IsUnifiedMobile/IsUnifiedFullSystemFramework/IsUnifiedFullXamMacFramework logic regression"); } ValidateXamarinMacReference (); if (!bypass_linking_checks && (IsUnifiedFullSystemFramework || IsUnifiedFullXamMacFramework)) { switch (App.LinkMode) { case LinkMode.None: case LinkMode.Platform: break; default: throw new ProductException (2007, true, Errors.MM2007); } } ValidateXcode (App, false, true); App.Initialize (); // InitializeCommon needs SdkVersion set to something valid ValidateSDKVersion (); // InitializeCommon needs the current profile if (IsUnifiedFullXamMacFramework || IsUnifiedFullSystemFramework) Profile.Current = new XamarinMacProfile (); else Profile.Current = new MacMobileProfile (); BuildTarget = new Target (App); App.Targets.Add (BuildTarget); App.InitializeCommon (); Log ("Xamarin.Mac {0}.{1}", Constants.Version, Constants.Revision); Log (1, "Selected target framework: {0}; API: Unified", targetFramework); Log (1, $"Selected Linking: '{App.LinkMode}'"); if (action == Action.RunRegistrar) { App.Registrar = RegistrarMode.Static; App.RunRegistrar (); return 0; } try { Pack (App.RootAssemblies); } finally { if (App.Cache.IsCacheTemporary) { // If we used a temporary directory we created ourselves for the cache // (in which case it's more a temporary location where we store the // temporary build products than a cache), it will not be used again, // so just delete it. try { Directory.Delete (App.Cache.Location, true); } catch { // Don't care. } } else { // Write the cache data as the last step, so there is no half-done/incomplete (but yet detected as valid) cache. App.Cache.ValidateCache (App); } } Log ("bundling complete"); return 0; } static void ValidateXamarinMacReference () { // Many Xamarin.Mac references are technically valid, so whitelisting risks breaking working project // However, passing in Mobile / Xamarin.Mac folders and resolving full/4.5 or vice versa is // far from expected. So catch the common cases if we can string reference = App.References.FirstOrDefault (x => x.EndsWith ("Xamarin.Mac.dll", StringComparison.Ordinal)); if (reference != null) { bool valid = true; if (IsUnifiedMobile) valid = !reference.Contains ("full/") && !reference.Contains ("4.5/"); else if (IsUnifiedFullXamMacFramework || IsUnifiedFullSystemFramework) valid = !reference.Contains ("mobile/") && !reference.Contains ("Xamarin.Mac/"); if (!valid) throw ErrorHelper.CreateError (1407, Errors.MM1407, reference, TargetFramework); } } static void FixReferences (Func match, Func fix) { for (var i = 0; i < App.References.Count; i++) { if (match (App.References [i])) App.References [i] = fix (App.References [i]); } } // SDK versions are only passed in as X.Y but some frameworks/APIs require X.Y.Z // Mutate them if we have a new enough Xcode static Version MutateSDKVersionToPointRelease (Version rv) { if (rv.Major == 10 && (rv.Revision == 0 || rv.Revision == -1)) { if (rv.Minor == 15 && XcodeVersion >= new Version (11, 4)) return new Version (rv.Major, rv.Minor, 4); if (rv.Minor == 13 && XcodeVersion >= new Version (9, 3)) return new Version (rv.Major, rv.Minor, 4); if (rv.Minor == 13 && XcodeVersion >= new Version (9, 2)) return new Version (rv.Major, rv.Minor, 2); if (rv.Minor == 13 && XcodeVersion >= new Version (9, 1)) return new Version (rv.Major, rv.Minor, 1); if (rv.Minor == 12 && XcodeVersion >= new Version (8, 3)) return new Version (rv.Major, rv.Minor, 4); if (rv.Minor == 12 && XcodeVersion >= new Version (8, 2)) return new Version (rv.Major, rv.Minor, 2); if (rv.Minor == 12 && XcodeVersion >= new Version (8, 1)) return new Version (rv.Major, rv.Minor, 1); if (rv.Minor == 11 && XcodeVersion >= new Version (7, 3)) return new Version (rv.Major, rv.Minor, 4); } // Since Version has wrong behavior: // new Version (10, 14) < new Version (10, 14, 0) => true // Force any unset revision to 0 instead of -1 if (rv.Revision == -1) return new Version (rv.Major, rv.Minor, 0); return rv; } // Validates that sdk_version is set to a reasonable value before compile static void ValidateSDKVersion () { if (App.SdkVersion != null) { // We can't do mutation while parsing command line args as XcodeVersion isn't set yet App.SdkVersion = MutateSDKVersionToPointRelease (App.SdkVersion); return; } if (string.IsNullOrEmpty (DeveloperDirectory)) return; var sdks = new List (); var sdkdir = Path.Combine (DeveloperDirectory, "Platforms", "MacOSX.platform", "Developer", "SDKs"); foreach (var sdkpath in Directory.GetDirectories (sdkdir)) { var sdk = Path.GetFileName (sdkpath); if (sdk.StartsWith ("MacOSX", StringComparison.Ordinal) && sdk.EndsWith (".sdk", StringComparison.Ordinal)) { Version sdkVersion; if (Version.TryParse (sdk.Substring (6, sdk.Length - 10), out sdkVersion)) sdks.Add (sdkVersion); } } if (sdks.Count > 0) { sdks.Sort (); // select the highest. App.SdkVersion = MutateSDKVersionToPointRelease (sdks [sdks.Count - 1]); } } static void CheckForUnknownCommandLineArguments (IList exceptions, IList arguments) { for (int i = arguments.Count - 1; i >= 0; i--) { if (arguments [i].StartsWith ("-", StringComparison.Ordinal)) { exceptions.Add (ErrorHelper.CreateError (18, Errors.MX0018, arguments [i])); arguments.RemoveAt (i); } } } public static void SelectRegistrar () { if (App.Registrar == RegistrarMode.Default) { if (!App.EnableDebug) App.Registrar = RegistrarMode.Static; else if (App.LinkMode == LinkMode.None && embed_mono && App.IsDefaultMarshalManagedExceptionMode && File.Exists (PartialStaticLibrary)) App.Registrar = RegistrarMode.PartialStatic; else App.Registrar = RegistrarMode.Dynamic; Log (1, $"Defaulting registrar to '{App.Registrar}'"); } } static void Pack (IList unprocessed) { string fx_dir = null; string root_assembly = null; var native_libs = new Dictionary> (); if (no_executable) { if (unprocessed.Count != 0) { var exceptions = new List (); CheckForUnknownCommandLineArguments (exceptions, unprocessed); exceptions.Add (new ProductException (50, true, Errors.MM0050, unprocessed.Count, string.Join ("', '", unprocessed.ToArray ()))); throw new AggregateException (exceptions); } if (string.IsNullOrEmpty (output_dir)) throw new ProductException (51, true, Errors.MM0051); if (string.IsNullOrEmpty (app_name)) app_name = Path.GetFileNameWithoutExtension (output_dir); } else { if (unprocessed.Count != 1) { var exceptions = new List (); CheckForUnknownCommandLineArguments (exceptions, unprocessed); if (unprocessed.Count > 1) { exceptions.Add (ErrorHelper.CreateError (8, Errors.MM0008, unprocessed.Count, string.Join ("', '", unprocessed.ToArray ()))); } else if (unprocessed.Count == 0) { exceptions.Add (ErrorHelper.CreateError (17, Errors.MX0017)); } throw new AggregateException (exceptions); } root_assembly = unprocessed [0]; if (!File.Exists (root_assembly)) throw new ProductException (7, true, Errors.MX0007, root_assembly); string root_wo_ext = Path.GetFileNameWithoutExtension (root_assembly); if (Profile.IsSdkAssembly (root_wo_ext) || Profile.IsProductAssembly (root_wo_ext)) throw new ProductException (3, true, Errors.MX0003, root_wo_ext); if (App.References.Exists (a => Path.GetFileNameWithoutExtension (a).Equals (root_wo_ext))) throw new ProductException (23, true, Errors.MM0023, root_wo_ext); fx_dir = GetPlatformFrameworkDirectory (App); if (!Directory.Exists (fx_dir)) throw new ProductException (1403, true, Errors.MM1403, "Directory", fx_dir, TargetFramework); App.References.Add (root_assembly); BuildTarget.Resolver.CommandLineAssemblies = App.References; BuildTarget.Resolver.Configure (); if (string.IsNullOrEmpty (app_name)) app_name = root_wo_ext; if (string.IsNullOrEmpty (output_dir)) output_dir = Environment.CurrentDirectory; } CreateDirectoriesIfNeeded (); App.SetDefaultAbi (); App.ValidateAbi (); BuildTarget.Abis = App.Abis.ToList (); Watch ("Setup", 1); if (!no_executable) { BuildTarget.Resolver.FrameworkDirectory = fx_dir; BuildTarget.Resolver.RootDirectory = Path.GetDirectoryName (Path.GetFullPath (root_assembly)); GatherAssemblies (); CheckReferences (); if (!is_extension && !resolved_assemblies.Exists (f => Path.GetExtension (f).ToLower () == ".exe") && !App.Embeddinator) throw new ProductException (79, true, Errors.MM0079, ""); // i18n must be dealed outside linking too (e.g. bug 11448) if (App.LinkMode == LinkMode.None) CopyI18nAssemblies (App.I18n); CopyAssemblies (); Watch ("Copy Assemblies", 1); } CopyResources (); Watch ("Copy Resources", 1); CopyConfiguration (); Watch ("Copy Configuration", 1); ExtractNativeLinkInfo (); BuildTarget.ValidateAssembliesBeforeLink (); if (!no_executable) { foreach (var nr in native_references) { if (!native_libs.ContainsKey (nr)) native_libs.Add (nr, null); } var linked_native_libs = Link (); foreach (var kvp in linked_native_libs) { List methods; if (native_libs.TryGetValue (kvp.Key, out methods)) { if (methods == null) { methods = new List (); native_libs [kvp.Key] = methods; } methods.AddRange (kvp.Value); } else { native_libs.Add (kvp.Key, kvp.Value); } } Watch (string.Format ("Linking (mode: '{0}')", App.LinkMode), 1); } // These must occur _after_ Linking BuildTarget.CollectAllSymbols (); BuildTarget.ComputeLinkerFlags (); BuildTarget.GatherFrameworks (); CopyMonoNative (); CopyDependencies (native_libs); Watch ("Copy Dependencies", 1); // MDK check Compile (); Watch ("Compile", 1); if (generate_plist) GeneratePList (); if (App.LinkMode != LinkMode.Full && App.RuntimeOptions != null) App.RuntimeOptions.Write (resources_dir); if (App.AOTOptions != null && App.AOTOptions.IsAOT) { AOTCompilerType compilerType; if (IsUnifiedMobile || IsUnifiedFullXamMacFramework) compilerType = AOTCompilerType.Bundled64; else if (IsUnifiedFullSystemFramework) compilerType = AOTCompilerType.System64; else throw ErrorHelper.CreateError (0099, Errors.MX0099, "\"AOT with unexpected profile.\""); AOTCompiler compiler = new AOTCompiler (App.AOTOptions, App.Abis, compilerType, IsUnifiedMobile, !EnableDebug); compiler.Compile (mmp_dir); Watch ("AOT Compile", 1); } if (!string.IsNullOrEmpty (certificate_name)) { CodeSign (); Watch ("Code Sign", 1); } } static void CopyMonoNative () { string name; if (File.Exists (Path.Combine (GetMonoLibraryDirectory (App), "libmono-system-native.dylib"))) { // legacy libmono-system-native.a needs to be included if it exists in the mono in question name = "libmono-system-native"; } else { // use modern libmono-native name = App.GetLibNativeName (); } var src = Path.Combine (GetMonoLibraryDirectory (App), name + ".dylib"); var dest = Path.Combine (mmp_dir, "libmono-native.dylib"); Watch ($"Adding mono-native library {name} for {App.MonoNativeMode}.", 1); if (App.Optimizations.TrimArchitectures == true) { // copy to temp directory and lipo there to avoid touching the final dest file if it's up to date var temp_dest = Path.Combine (App.Cache.Location, "libmono-native.dylib"); if (Application.UpdateFile (src, temp_dest)) LipoLibrary (name, temp_dest); Application.UpdateFile (temp_dest, dest); } else { // we can directly update the dest Application.UpdateFile (src, dest); } } static void ExtractNativeLinkInfo () { var exceptions = new List (); BuildTarget.ExtractNativeLinkInfo (exceptions); if (exceptions.Count > 0) throw new AggregateException (exceptions); Watch ("Extracted native link info", 1); } static string system_mono_directory; public static string SystemMonoDirectory { get { if (system_mono_directory == null) system_mono_directory = RunPkgConfig ("--variable=prefix", force_system_mono: true); return system_mono_directory; } } static string MonoDirectory { get { if (IsUnifiedFullXamMacFramework || IsUnifiedMobile) return GetFrameworkCurrentDirectory (App); return SystemMonoDirectory; } } static void GeneratePList () { var sr = new StreamReader (typeof (Driver).Assembly.GetManifestResourceStream (App.Embeddinator ? "Info-framework.plist.tmpl" : "Info.plist.tmpl")); var all = sr.ReadToEnd (); var icon_str = (icon != null) ? "\tCFBundleIconFile\n\t" + icon + "\n\t" : ""; var path = Path.Combine (App.Embeddinator ? resources_dir : contents_dir, "Info.plist"); using (var sw = new StreamWriter (path)){ sw.WriteLine ( all.Replace ("@BUNDLEDISPLAYNAME@", app_name). Replace ("@EXECUTABLE@", app_name). Replace ("@BUNDLEID@", string.Format ("org.mono.bundler.{0}", app_name)). Replace ("@BUNDLEICON@", icon_str). Replace ("@BUNDLENAME@", app_name). Replace ("@ASSEMBLY@", App.References.Where (e => Path.GetExtension (e) == ".exe").FirstOrDefault ())); } } // the 'codesign' is provided with OSX, not with Xcode (no need to use xcrun) // note: by default the monodevelop addin does the signing (not mmp) static void CodeSign () { RunCommand ("codesign", String.Format ("-v -s \"{0}\" \"{1}\"", certificate_name, App.AppDirectory)); } [DllImport (Constants.libSystemLibrary)] static extern IntPtr realpath (string path, IntPtr buffer); static string GetRealPath (string path) { if (path.StartsWith ("@executable_path/", StringComparison.Ordinal)) path = Path.Combine (mmp_dir, path.Substring (17)); if (path.StartsWith ("@rpath/", StringComparison.Ordinal)) path = Path.Combine (mmp_dir, path.Substring (7)); const int PATHMAX = 1024 + 1; IntPtr buffer = IntPtr.Zero; try { buffer = Marshal.AllocHGlobal (PATHMAX); var result = realpath (path, buffer); if (result == IntPtr.Zero) return path; else return Marshal.PtrToStringAuto (buffer); } finally { if (buffer != IntPtr.Zero) Marshal.FreeHGlobal (buffer); } } static string PartialStaticLibrary { get { return Path.Combine (GetFrameworkLibDirectory (App), string.Format ("mmp/Xamarin.Mac.registrar.{0}.a", IsUnifiedMobile ? "mobile" : "full")); } } static void HandleFramework (IList args, string framework, bool weak) { string name = Path.GetFileName (framework); if (name.Contains ('.')) name = name.Remove (name.IndexOf (".", StringComparison.Ordinal)); string path = Path.GetDirectoryName (framework); if (!string.IsNullOrEmpty (path)) { args.Add ("-F"); args.Add (path); } if (weak) BuildTarget.WeakFrameworks.Add (name); else BuildTarget.Frameworks.Add (name); if (!framework.EndsWith (".framework", StringComparison.Ordinal)) return; // TODO - There is a chunk of code in mtouch that calls Xamarin.MachO.IsDynamicFramework and doesn't copy if framework of static libs // But IsDynamicFramework is not on XM yet CreateDirectoryIfNeeded (frameworks_dir); Application.UpdateDirectory (framework, frameworks_dir); var fxdir = Path.Combine (frameworks_dir, name + ".framework"); Application.ExcludeNonEssentialFrameworkFiles (fxdir); frameworks_copied_to_bundle_dir = true; if (App.Optimizations.TrimArchitectures == true) LipoLibrary (framework, Path.Combine (name, Path.Combine (fxdir, name))); } static void CheckSystemMonoVersion () { string mono_version; var versionFile = Path.Combine (SystemMonoDirectory, "VERSION"); if (File.Exists (versionFile)) { mono_version = File.ReadAllText (versionFile); } else { mono_version = RunPkgConfig ("--modversion", force_system_mono: true); } mono_version = mono_version.Trim (); if (!Version.TryParse (mono_version, out var mono_ver)) return; if (mono_ver < MonoVersions.MinimumMonoVersion) throw ErrorHelper.CreateError (1, Errors.MM0001, MonoVersions.MinimumMonoVersion, mono_version); } static void Compile () { string [] cflags = Array.Empty (); string registrarPath = null; CheckSystemMonoVersion (); if (App.Registrar == RegistrarMode.Static) { registrarPath = Path.Combine (App.Cache.Location, "registrar.m"); var registrarH = Path.Combine (App.Cache.Location, "registrar.h"); BuildTarget.StaticRegistrar.Generate (BuildTarget.Resolver.ResolverCache.Values, registrarH, registrarPath); var platform_assembly = BuildTarget.Resolver.ResolverCache.First ((v) => v.Value.Name.Name == BuildTarget.StaticRegistrar.PlatformAssembly).Value; Frameworks.Gather (App, platform_assembly, BuildTarget.Frameworks, BuildTarget.WeakFrameworks); } var str_cflags = RunPkgConfig ("--cflags"); var libdir = GetMonoLibraryDirectory (App); if (!StringUtils.TryParseArguments (str_cflags, out cflags, out var ex)) throw ErrorHelper.CreateError (147, ex, Errors.MM0147, str_cflags, ex.Message); var libmain = embed_mono ? "libxammac" : "libxammac-system"; var libxammac = Path.Combine (GetXamarinLibraryDirectory (App), libmain + (App.EnableDebug ? "-debug" : "") + ".a"); if (!File.Exists (libxammac)) throw new ProductException (5203, true, Errors.MM5203, libxammac); try { List compiledExecutables = new List (); foreach (var abi in App.Abis) { var abiDir = Path.Combine (App.Cache.Location, "main", abi.AsArchString ()); // Delete previous content (so we don't have any remaining dsym if we switch from having one to not having one) if (Directory.Exists (abiDir)) Directory.Delete (abiDir, true); var main = Path.Combine (abiDir, $"main.m"); BuildTarget.GenerateMain (ApplePlatform.MacOSX, abi, main, null); var args = new List (); if (App.EnableDebug) args.Add ("-g"); if (App.Embeddinator) { args.Add ("-shared"); args.Add ("-install_name"); args.Add ($"@loader_path/../Frameworks/{App.Name}.framework/{App.Name}"); } args.Add ($"-mmacosx-version-min={App.DeploymentTarget.ToString ()}"); args.Add ("-arch"); args.Add (abi.AsArchString ()); args.Add ($"-fobjc-runtime=macosx-{App.DeploymentTarget.ToString ()}"); if (!embed_mono) args.Add ("-DDYNAMIC_MONO_RUNTIME"); if (XcodeVersion >= new Version (9, 0)) args.Add ("-Wno-unguarded-availability-new"); if (XcodeVersion.Major >= 11) args.Add ("-std=c++14"); bool appendedObjc = false; var sourceFiles = new List (); foreach (var assembly in BuildTarget.Assemblies) { if (assembly.LinkWith != null) { foreach (var linkWith in assembly.LinkWith) { Log (2, "Found LinkWith on {0} for {1}", assembly.FileName, linkWith); if (linkWith.EndsWith (".dylib", StringComparison.Ordinal)) { // Link against the version copied into MonoBudle, since we install_name_tool'd it already string libName = Path.GetFileName (linkWith); string finalLibPath = Path.Combine (mmp_dir, libName); args.Add (finalLibPath); } else { args.Add (linkWith); } } if (!appendedObjc) { appendedObjc = true; args.Add ("-ObjC"); } } if (assembly.LinkerFlags != null) foreach (var linkFlag in assembly.LinkerFlags) args.Add (linkFlag); if (assembly.Frameworks != null) { foreach (var f in assembly.Frameworks) { Log (2, $"Adding Framework {f} for {assembly.FileName}"); HandleFramework (args, f, false); } } if (assembly.WeakFrameworks != null) { foreach (var f in assembly.WeakFrameworks) { Log (2, $"Adding Weak Framework {f} for {assembly.FileName}"); HandleFramework (args, f, true); } } } foreach (var framework in App.Frameworks) HandleFramework (args, framework, false); foreach (var lib in native_libraries_copied_in) { // Link against the version copied into MonoBudle, since we install_name_tool'd it already string libName = Path.GetFileName (lib); string finalLibPath = Path.Combine (mmp_dir, libName); args.Add (finalLibPath); } if (frameworks_copied_to_bundle_dir) { args.Add ("-rpath"); args.Add ("@loader_path/../Frameworks"); } if (dylibs_copied_to_bundle_dir) { args.Add ("-rpath"); args.Add ($"@loader_path/../{App.CustomBundleName}"); } if (is_extension && !is_xpc_service) { args.Add ("-e"); args.Add ("_xamarin_mac_extension_main"); args.Add ("-framework"); args.Add ("NotificationCenter"); } foreach (var f in BuildTarget.Frameworks) { args.Add ("-framework"); args.Add (f); } foreach (var f in BuildTarget.WeakFrameworks) { args.Add ("-weak_framework"); args.Add (f); } var requiredSymbols = BuildTarget.GetRequiredSymbols (target_abis: abi); Driver.WriteIfDifferent (Path.Combine (App.Cache.Location, "exported-symbols-list"), string.Join ("\n", requiredSymbols.Select ((symbol) => symbol.Prefix + symbol.Name).ToArray ())); switch (App.SymbolMode) { case SymbolMode.Ignore: break; case SymbolMode.Code: string reference_m = Path.Combine (App.Cache.Location, "reference.m"); reference_m = BuildTarget.GenerateReferencingSource (reference_m, requiredSymbols); if (!string.IsNullOrEmpty (reference_m)) sourceFiles.Add (reference_m); break; case SymbolMode.Linker: case SymbolMode.Default: foreach (var symbol in requiredSymbols) { args.Add ("-u"); args.Add (symbol.Prefix + symbol.Name); } break; default: throw ErrorHelper.CreateError (99, Errors.MX0099, $"invalid symbol mode: {App.SymbolMode}"); } bool linkWithRequiresForceLoad = BuildTarget.Assemblies.Any (x => x.ForceLoad); if (no_executable || linkWithRequiresForceLoad) args.Add ("-force_load"); // make sure nothing is stripped away if we don't have a root assembly, since anything can end up being used. args.Add (libxammac); args.Add ("-o"); var outputPath = Path.Combine (abiDir, Path.GetFileName (AppPath)); compiledExecutables.Add (outputPath); args.Add (outputPath); args.AddRange (cflags); if (embed_mono) { string libmono = Path.Combine (libdir, "libmonosgen-2.0.a"); if (!File.Exists (libmono)) throw new ProductException (5202, true, Errors.MM5202); args.Add (libmono); string libmonoSystemNative = Path.Combine (libdir, "libmono-system-native.a"); if (File.Exists (libmonoSystemNative)) { // legacy libmono-system-native.a needs to be included if it exists in the mono in question args.Add (libmonoSystemNative); args.Add ("-u"); args.Add ("_SystemNative_RealPath"); // This keeps libmono_system_native_la-pal_io.o symbols } else { // add modern libmono-native args.Add (Path.Combine (libdir, App.GetLibNativeName () + ".a")); args.Add ("-framework"); args.Add ("GSS"); } if (App.EnableProfiling) { args.Add (Path.Combine (libdir, "libmono-profiler-log.a")); args.Add ("-u"); args.Add ("_mono_profiler_init_log"); } args.Add ("-lz"); } if (App.Registrar == RegistrarMode.PartialStatic) { args.Add (PartialStaticLibrary); args.Add ("-framework"); args.Add ("Quartz"); } args.Add ("-liconv"); args.Add ("-lc++"); args.Add ("-x"); args.Add ("objective-c++"); if (XcodeVersion.Major >= 10) { // Xcode 10 doesn't ship with libstdc++ args.Add ("-stdlib=libc++"); } args.Add ($"-I{GetProductSdkIncludeDirectory (App)}"); if (registrarPath != null) args.Add (registrarPath); args.Add ("-fno-caret-diagnostics"); args.Add ("-fno-diagnostics-fixit-info"); if (App.CustomLinkFlags is not null) args.AddRange (App.CustomLinkFlags); if (!string.IsNullOrEmpty (DeveloperDirectory)) { var sysRootSDKVersion = new Version (App.SdkVersion.Major, App.SdkVersion.Minor); // Sys Root SDKs do not have X.Y.Z, just X.Y args.Add ("-isysroot"); args.Add (Path.Combine (DeveloperDirectory, "Platforms", "MacOSX.platform", "Developer", "SDKs", "MacOSX" + sysRootSDKVersion + ".sdk")); } if (App.RequiresPInvokeWrappers) { var state = BuildTarget.LinkerOptions.MarshalNativeExceptionsState; state.End (); args.Add (state.SourcePath); } sourceFiles.Add (main); args.AddRange (sourceFiles); RunClang (App, args); } RunLipoAndCreateDsym (App, AppPath, compiledExecutables); } catch (Win32Exception e) { throw new ProductException (5103, true, e, Errors.MM5103, "driver"); } } static string RunPkgConfig (string option, bool force_system_mono = false) { Dictionary env = null; if (!IsUnifiedFullSystemFramework && !force_system_mono) env = new Dictionary { { "PKG_CONFIG_PATH", Path.Combine (GetProductSdkLibDirectory (App), "pkgconfig") } }; var sb = new StringBuilder (); int rv; try { rv = RunCommand (PkgConfig, new [] { option, "mono-2" }, env, sb); } catch (Exception e) { throw ErrorHelper.CreateError (5314, e, Errors.MX5314, e.Message); } if (rv != 0) throw ErrorHelper.CreateError (5312, Errors.MX5312, rv); return sb.ToString ().Trim (); } // check that we have a reference to Xamarin.Mac.dll and not to MonoMac.dll. static void CheckReferences () { List exceptions = new List (); var cache = BuildTarget.Resolver.ToResolverCache (); var incompatibleReferences = new List (); var haveValidReference = false; foreach (string entry in cache.Keys) { switch (entry) { case "Xamarin.Mac": haveValidReference = true; break; case "XamMac": incompatibleReferences.Add (entry); break; case "MonoMac": incompatibleReferences.Add (entry); break; } } if (!haveValidReference) exceptions.Add (new ProductException (1401, true, Errors.MM1401)); foreach (var refName in incompatibleReferences) exceptions.Add (new ProductException (1402, true, Errors.MM1402, refName + ".dll")); if (exceptions.Count > 0) throw new AggregateException (exceptions); } static IDictionary> Link () { var cache = (Dictionary) BuildTarget.Resolver.ResolverCache; AssemblyResolver resolver; if (UseLegacyAssemblyResolution) { if (cache != null) { resolver = new Mono.Linker.AssemblyResolver (cache); } else { resolver = new Mono.Linker.AssemblyResolver (); } } else { resolver = new MonoMacAssemblyResolver (BuildTarget.Resolver); } resolver.AddSearchDirectory (BuildTarget.Resolver.RootDirectory); resolver.AddSearchDirectory (BuildTarget.Resolver.FrameworkDirectory); var options = new LinkerOptions { MainAssembly = BuildTarget.Resolver.GetAssembly (App.References [App.References.Count - 1]), OutputDirectory = mmp_dir, LinkSymbols = App.PackageManagedDebugSymbols, LinkMode = App.LinkMode, Resolver = resolver, SkippedAssemblies = App.LinkSkipped, I18nAssemblies = App.I18n, ExtraDefinitions = App.Definitions, RuntimeOptions = App.RuntimeOptions, MarshalNativeExceptionsState = !App.RequiresPInvokeWrappers ? null : new PInvokeWrapperGenerator () { App = App, SourcePath = Path.Combine (App.Cache.Location, "pinvokes.m"), HeaderPath = Path.Combine (App.Cache.Location, "pinvokes.h"), Registrar = (StaticRegistrar) BuildTarget.StaticRegistrar, }, SkipExportedSymbolsInSdkAssemblies = !embed_mono, Target = BuildTarget, WarnOnTypeRef = App.WarnOnTypeRef, }; BuildTarget.LinkerOptions = options; MonoMac.Tuner.Linker.Process (options, out var context, out resolved_assemblies); ErrorHelper.Show (context.Exceptions); // Idealy, this would be handled by Linker.Process above. However in the non-linking case // we do not run MobileMarkStep which generates the pinvoke list. Hack around this for now // https://bugzilla.xamarin.com/show_bug.cgi?id=43419 if (App.LinkMode == LinkMode.None) return ProcessDllImports (); else return BuildTarget.LinkContext.PInvokeModules; } static Dictionary> ProcessDllImports () { var pinvoke_modules = new Dictionary> (); foreach (string assembly_name in resolved_assemblies) { AssemblyDefinition assembly = BuildTarget.Resolver.GetAssembly (assembly_name); if (assembly != null) { foreach (ModuleDefinition md in assembly.Modules) { if (md.HasTypes) { foreach (TypeDefinition type in md.Types) { if (type.HasMethods) { foreach (MethodDefinition method in type.Methods) { if ((method != null) && !method.HasBody && method.IsPInvokeImpl) { // this happens for c++ assemblies (ref #11448) if (method.PInvokeInfo == null) continue; string module = method.PInvokeInfo.Module.Name; if (!String.IsNullOrEmpty (module)) { List methods; if (!pinvoke_modules.TryGetValue (module, out methods)) pinvoke_modules.Add (module, methods = new List ()); } } } } } } } } } return pinvoke_modules; } static void CopyDependencies (IDictionary> libraries) { // Process LinkWith first so we don't have unnecessary warnings foreach (var assembly in BuildTarget.Assemblies.Where (a => a.LinkWith != null)) { foreach (var linkWith in assembly.LinkWith.Where (l => l.EndsWith (".dylib", StringComparison.Ordinal))) { string libName = Path.GetFileName (linkWith); string finalLibPath = Path.Combine (mmp_dir, libName); Application.UpdateFile (linkWith, finalLibPath); RunInstallNameTool (App, new [] { "-id", "@executable_path/../" + App.CustomBundleName + "/" + libName, finalLibPath }); native_libraries_copied_in.Add (libName); } } var processed = new HashSet (); foreach (var kvp in libraries.Where (l => !native_libraries_copied_in.Contains (Path.GetFileName (l.Key)))) ProcessNativeLibrary (processed, kvp.Key, kvp.Value); // .dylibs might refers to specific paths (e.g. inside Mono framework directory) and/or // refer to a symlink (pointing to a more specific version of the library) var sb = new List (); foreach (string library in Directory.GetFiles (mmp_dir, "*.dylib")) { foreach (string lib in Xamarin.MachO.GetNativeDependencies (library)) { if (lib.StartsWith (Path.GetDirectoryName (MonoPrefix), StringComparison.Ordinal)) { string libname = Path.GetFileName (lib); string real_lib = GetRealPath (lib); string real_libname = Path.GetFileName (real_lib); // if a symlink was specified then re-create it inside the .app if (libname != real_libname) CreateSymLink (mmp_dir, real_libname, libname); sb.Add ("-change"); sb.Add (lib); sb.Add ("@executable_path/../" + App.CustomBundleName + "/" + libname); } } // if required update the paths inside the .dylib that was copied if (sb.Count > 0) { sb.Add (library); RunInstallNameTool (App, sb); sb.Clear (); } } } static string GetLibraryShortenedName (string name) { // GetFileNameWithoutExtension only removes one extension, e.g. libx.so.2 won't work int start = name.StartsWith ("lib", StringComparison.Ordinal) ? 3 : 0; int end = name.Length; if (name.EndsWith (".dylib", StringComparison.Ordinal)) end -= 6; else if (name.EndsWith (".so", StringComparison.Ordinal)) end -= 3; else if (name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) end -= 4; return name.Substring (start, end - start); } static bool ShouldSkipNativeLibrary (string fileName) { string shortendName = GetLibraryShortenedName (fileName); // well known libraries we do not bundle or warn about switch (shortendName.ToLowerInvariant ()) { case "xammac": // we have a p/invoke to this library in Runtime.mac.cs, for users that don't bundle with mmp. case "__internal": // mono runtime case "kernel32": // windows specific case "gdi32": // windows specific case "ole32": // windows specific case "user32": // windows specific case "advapi32": // windows specific case "crypt32": // windows specific case "msvcrt": // windows specific case "iphlpapi": // windows specific case "winmm": // windows specific case "winspool": // windows specific case "c": // system provided case "objc": // system provided case "objc.a": // found in swift core libraries case "system.b": // found in swift core libraries case "system": // system provided, libSystem.dylib -> CommonCrypto case "x11": // msvcrt pulled in case "winspool.drv": // msvcrt pulled in case "cups": // msvcrt pulled in case "fam.so.0": // msvcrt pulled in case "gamin-1.so.0": // msvcrt pulled in case "asound.so.2": // msvcrt pulled in case "oleaut32": // referenced by System.Runtime.InteropServices.Marshal._[S|G]etErrorInfo case "system.native": // handled by CopyMonoNative() case "system.security.cryptography.native.apple": // same case "system.net.security.native": // same return true; } // Shutup the warning until we decide on bug: 36478 if (shortendName.ToLowerInvariant () == "intl" && !native_references.Any (x => x.Contains ("libintl.dylib")) && IsUnifiedFullXamMacFramework) return true; return false; } static void ProcessNativeLibrary (HashSet processed, string library, List used_by_methods) { // We do not bundle system libraries, ever if (library.StartsWith ("/usr/lib/", StringComparison.Ordinal) || library.StartsWith ("/System/Library/", StringComparison.Ordinal)) return; // If we've been required to ignore this library, skip it if (ignored_assemblies.Contains (library)) return; // If we're passed in a framework, ignore if (App.Frameworks.Contains (library)) return; // Frameworks don't include the lib name, e.g. `../foo.framework` not `../foo.framework/foo` so check again string path = Path.GetDirectoryName (library); if (App.Frameworks.Contains (path)) return; // We need to check both the name and the shortened name, since we might get passed: // full path - /foo/bar/libFoo.dylib // just name - libFoo string name = Path.GetFileName (library); string libName = "lib" + GetLibraryShortenedName (name) + ".dylib"; // There is a list of libraries we always skip, honor that if (ShouldSkipNativeLibrary (name)) return; string src = null; // If we've been passed in a full path and it is there, let's just go with that if (File.Exists (library)) src = library; // Now let's check inside mono/lib string monoDirPath = Path.Combine (GetMonoLibraryDirectory (App), libName); if (src == null && File.Exists (monoDirPath)) src = monoDirPath; // Now let's check in path with our libName if (src == null && !String.IsNullOrEmpty (path)) { string pathWithLibName = Path.Combine (path, name); if (File.Exists (pathWithLibName)) src = pathWithLibName; } // If we can't find it at this point, scream if (src == null) { ErrorHelper.Show (new ProductException (2006, false, Errors.MM2006, name)); if (used_by_methods != null && used_by_methods.Count > 0) { const int referencedByLimit = 25; bool limitReferencedByWarnings = used_by_methods.Count > referencedByLimit && Verbosity < 4; foreach (var m in limitReferencedByWarnings ? used_by_methods.Take (referencedByLimit) : used_by_methods) { ErrorHelper.Warning (2009, Errors.MM2009, m.DeclaringType.FullName, m.Name); } if (limitReferencedByWarnings) ErrorHelper.Warning (2012, Errors.MM2012, referencedByLimit, used_by_methods.Count); } return; } string real_src = GetRealPath (src); string dest = Path.Combine (mmp_dir, Path.GetFileName (real_src)); Log (2, "Native library '{0}' copied to application bundle.", Path.GetFileName (real_src)); if (GetRealPath (dest) == real_src) { Console.WriteLine ("Dependency {0} was already at destination, skipping.", Path.GetFileName (real_src)); } else { // install_name_tool gets angry if you copy in a read only native library CopyFileAndRemoveReadOnly (real_src, dest); } bool isStaticLib = real_src.EndsWith (".a", StringComparison.Ordinal); bool isDynamicLib = real_src.EndsWith (".dylib", StringComparison.Ordinal); if (isDynamicLib && App.Optimizations.TrimArchitectures == true) LipoLibrary (name, dest); if (native_references.Contains (real_src)) { if (!isStaticLib) RunInstallNameTool (App, new [] { "-id", "@executable_path/../" + App.CustomBundleName + "/" + name, dest }); native_libraries_copied_in.Add (name); } // if a symlink was used then it might still be needed at runtime if (src != real_src) CreateSymLink (mmp_dir, real_src, src); // We add both the resolved location and the initial request. // @executable_path subtitution and other resolving can have these be different // and we'll loop forever otherwise processed.Add (real_src); processed.Add (library); // process native dependencies if (!isStaticLib) { foreach (string dependency in Xamarin.MachO.GetNativeDependencies (real_src)) { string lib = GetRealPath (dependency); if (!processed.Contains (lib)) ProcessNativeLibrary (processed, lib, null); } } } static void LipoLibrary (string name, string dest) { var existingArchs = Xamarin.MachO.GetArchitectures (dest); // If we have less than two, it will either match by default or // we'll fail a link/launch time with a better error so bail if (existingArchs.Count () < 2) return; // macOS frameworks often uses symlinks and we do not // want to replace the symlink with the thin binary // while leaving the fat binary inside the framework dest = GetRealPath (dest); var prefix = new [] { dest }; var suffix = new [] { "-output", dest }; List archArgs = new List (); foreach (var abi in App.Abis) { archArgs.Add ("-extract_family"); archArgs.Add (abi.ToString ().ToLowerInvariant ()); } RunLipo (App, prefix.Concat (archArgs).Concat (suffix).ToArray ()); if (existingArchs.Except (App.Abis).Count () > 0 && name != "MonoPosixHelper" && name != "libmono-native-unified" && name != "libmono-native-compat") ErrorHelper.Warning (2108, Errors.MM2108, name, string.Join (", ", App.Abis)); } static void CreateSymLink (string directory, string real, string link) { string cd = Environment.CurrentDirectory; Environment.CurrentDirectory = directory; PathUtils.Symlink (Path.GetFileName (real), "./" + Path.GetFileName (link)); Environment.CurrentDirectory = cd; } /* Currently we clobber any existing files, perhaps we should error and have a -force flag */ static void CreateDirectoriesIfNeeded () { if (App.Embeddinator) { App.AppDirectory = Path.Combine (output_dir, app_name + ".framework"); contents_dir = Path.Combine (App.AppDirectory, "Versions", "A"); macos_dir = contents_dir; } else { string bundle_ext; if (is_extension && is_xpc_service) bundle_ext = "xpc"; else if (is_extension) bundle_ext = "appex"; else bundle_ext = "app"; App.AppDirectory = Path.Combine (output_dir, string.Format ("{0}.{1}", app_name, bundle_ext)); contents_dir = Path.Combine (App.AppDirectory, "Contents"); macos_dir = Path.Combine (contents_dir, "MacOS"); } frameworks_dir = Path.Combine (contents_dir, "Frameworks"); resources_dir = Path.Combine (contents_dir, "Resources"); mmp_dir = Path.Combine (contents_dir, App.CustomBundleName); CreateDirectoryIfNeeded (App.AppDirectory); CreateDirectoryIfNeeded (contents_dir); CreateDirectoryIfNeeded (macos_dir); CreateDirectoryIfNeeded (resources_dir); CreateDirectoryIfNeeded (mmp_dir); if (App.Embeddinator) { CreateSymlink (Path.Combine (App.AppDirectory, "Versions", "Current"), "A"); CreateSymlink (Path.Combine (App.AppDirectory, app_name), $"Versions/Current/{app_name}"); CreateSymlink (Path.Combine (App.AppDirectory, "Resources"), "Versions/Current/Resources"); } } // Mono.Unix can't create symlinks to relative paths, it insists on the target to a full path before creating the symlink. static void CreateSymlink (string file, string target) { PathUtils.CreateSymlink (file, target); } static void CreateDirectoryIfNeeded (string dir) { if (!Directory.Exists (dir)) Directory.CreateDirectory (dir); } static void CopyConfiguration () { if (IsUnifiedMobile) { CopyResourceFile ("config_mobile", "config"); } else { if (IsUnifiedFullXamMacFramework) { CopyResourceFile ("machine.4_5.config", "machine.config"); } else { string machine_config = Path.Combine (MonoDirectory, "etc", "mono", "4.5", "machine.config"); if (!File.Exists (machine_config)) throw new ProductException (1403, true, Errors.MM1403, "File", machine_config, TargetFramework); File.Copy (machine_config, Path.Combine (mmp_dir, "machine.config"), true); } CopyResourceFile ("config", "config"); } if (machine_config_path != null) { string machineConfigDestDir = Path.Combine (mmp_dir, "mono/4.5/"); string machineConfigDestFile = Path.Combine (machineConfigDestDir, "machine.config"); CreateDirectoryIfNeeded (machineConfigDestDir); if (machine_config_path == String.Empty) { File.WriteAllLines (machineConfigDestFile, new string [] { "", "", "" }); } else { if (!File.Exists (machine_config_path)) throw new ProductException (97, true, Errors.MM0097, machine_config_path); File.Copy (machine_config_path, machineConfigDestFile); } } } static void CopyResourceFile (string streamName, string outputFileName) { var sr = new StreamReader (typeof (Driver).Assembly.GetManifestResourceStream (streamName)); var all = sr.ReadToEnd (); var config = Path.Combine (mmp_dir, outputFileName); using (var sw = new StreamWriter (config)) { sw.WriteLine (all); } } static void CopyI18nAssemblies (I18nAssemblies i18n) { if (i18n == I18nAssemblies.None) return; string fx_dir = BuildTarget.Resolver.FrameworkDirectory; // always needed (if any is specified) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.dll")); // optionally needed if ((i18n & I18nAssemblies.CJK) != 0) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.CJK.dll")); if ((i18n & I18nAssemblies.MidEast) != 0) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.MidEast.dll")); if ((i18n & I18nAssemblies.Other) != 0) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.Other.dll")); if ((i18n & I18nAssemblies.Rare) != 0) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.Rare.dll")); if ((i18n & I18nAssemblies.West) != 0) resolved_assemblies.Add (Path.Combine (fx_dir, "I18N.West.dll")); } static void CopyFileAndRemoveReadOnly (string src, string dest) { File.Copy (src, dest, true); FileAttributes attrs = File.GetAttributes (dest); if ((attrs & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) File.SetAttributes (dest, attrs & ~FileAttributes.ReadOnly); } static void CopyAssemblies () { foreach (string asm in resolved_assemblies) { var configfile = string.Format ("{0}.config", asm); string filename = Path.GetFileName (asm); // The linker later gets angry if you copy in a read only assembly CopyFileAndRemoveReadOnly (asm, Path.Combine (mmp_dir, filename)); Log (1, "Added assembly {0}", asm); if (App.PackageManagedDebugSymbols) { var mdbfile = asm + ".mdb"; if (File.Exists (mdbfile)) CopyFileAndRemoveReadOnly (mdbfile, Path.Combine (mmp_dir, Path.GetFileName (mdbfile))); var pdbfile = Path.ChangeExtension (asm, ".pdb"); if (File.Exists (pdbfile)) CopyFileAndRemoveReadOnly (pdbfile, Path.Combine (mmp_dir, Path.GetFileName (pdbfile))); } if (File.Exists (configfile)) File.Copy (configfile, Path.Combine (mmp_dir, Path.GetFileName (configfile)), true); } foreach (var assembly in BuildTarget.Assemblies) assembly.CopySatellitesToDirectory (mmp_dir); } static void CopyResources () { foreach (string res in resources) { File.Copy (res, Path.Combine (resources_dir, Path.GetFileName (res)), true); } } static void GatherAssemblies () { foreach (string asm in App.References) { AssemblyDefinition assembly = AddAssemblyPathToResolver (asm); ProcessAssemblyReferences (assembly); } if (BuildTarget.Resolver.Exceptions.Count > 0) throw new AggregateException (BuildTarget.Resolver.Exceptions); } static void ProcessAssemblyReferences (AssemblyDefinition assembly) { if (assembly == null) return; var fqname = GetRealPath (assembly.MainModule.FileName); if (resolved_assemblies.Contains (fqname)) return; Target.PrintAssemblyReferences (assembly); var asm = BuildTarget.AddAssembly (assembly); asm.ComputeSatellites (); resolved_assemblies.Add (fqname); foreach (AssemblyNameReference reference in assembly.MainModule.AssemblyReferences) { AssemblyDefinition reference_assembly = AddAssemblyReferenceToResolver (reference); ProcessAssemblyReferences (reference_assembly); } } static AssemblyDefinition AddAssemblyPathToResolver (string path) { if (AssemblySwapInfo.AssemblyNeedsSwappedOut (path)) path = AssemblySwapInfo.GetSwappedAssemblyPath (path); var assembly = BuildTarget.Resolver.Load (path); if (assembly == null) ErrorHelper.Warning (1501, Errors.MM1501, path); return assembly; } static AssemblyDefinition AddAssemblyReferenceToResolver (AssemblyNameReference reference) { if (AssemblySwapInfo.ReferencedNeedsSwappedOut (reference.Name)) return BuildTarget.Resolver.Load (AssemblySwapInfo.GetSwappedReference (reference.Name)); return BuildTarget.Resolver.Resolve (reference); } } public static class AssemblySwapInfo { static HashSet xammac_reference_assemblies_names = new HashSet { "Xamarin.Mac", "Xamarin.Mac.CFNetwork", "OpenTK" }; public static bool AssemblyNeedsSwappedOut (string path) => NeedsSwappedCore (Path.GetFileNameWithoutExtension (path)); public static bool ReferencedNeedsSwappedOut (string reference) => NeedsSwappedCore (reference); static bool NeedsSwappedCore (string name) { if (name.Contains ("OpenTK") && Driver.IsUnifiedFullXamMacFramework) return false; return xammac_reference_assemblies_names.Contains (name); } public static string GetSwappedAssemblyPath (string path) => GetSwappedPathCore (Path.GetFileNameWithoutExtension (path)); public static string GetSwappedReference (string reference) => GetSwappedPathCore (reference); static string GetSwappedPathCore (string name) { string flavor = (Driver.IsUnifiedFullSystemFramework || Driver.IsUnifiedFullXamMacFramework) ? "full" : "mobile"; var abi = Driver.App.Abi; var arch = abi.AsArchString (); switch (abi) { case Abi.x86_64: case Abi.ARM64: return Path.Combine (Driver.GetFrameworkLibDirectory (Driver.App), "64bits", flavor, name + ".dll"); default: throw new ProductException (5205, true, Errors.MM5205, arch); } } } }