xamarin-macios/tools/mmp/driver.cs

1608 строки
61 KiB
C#

/*
* Copyright 2011-2014 Xamarin Inc. All rights reserved.
* Copyright 2010 Novell Inc.
*
* Authors:
* Sebastien Pouliot <sebastien@xamarin.com>
* Aaron Bockover <abock@xamarin.com>
* Rolf Bjarne Kvinge <rolf@xamarin.com>
* Geoff Norton <gnorton@novell.com>
*
* 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.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Xml;
using Mono.Cecil;
using Mono.Linker;
using Mono.Options;
using Mono.Tuner;
using MonoMac.Tuner;
using Xamarin.Utils;
using Xamarin.Linker;
using XamCore.Registrar;
using XamCore.ObjCRuntime;
namespace Xamarin.Bundler {
public enum RegistrarMode {
Default,
Dynamic,
Static,
IL,
}
public static partial class Driver {
internal static Application App = new Application ();
static Target BuildTarget = new Target (App);
static List<string> references = new List<string> ();
static List<string> resources = new List<string> ();
static List<string> resolved_assemblies = new List<string> ();
static List<string> ignored_assemblies = new List<string> ();
static List<string> native_references = new List<string> ();
static List<string> native_libraries_copied_in = new List<string> ();
static string output_dir;
static string app_name;
static bool show_help = false;
static bool show_version;
static bool generate_plist;
static RegistrarMode registrar = RegistrarMode.Default;
static bool no_executable;
static bool embed_mono = true;
static bool? profiling = false;
static bool? thread_check = null;
static string link_flags = null;
static LinkerOptions linker_options;
static bool arch_set = false;
static string arch = "i386";
static Version minos = new Version (10, 7);
static Version sdk_version;
static string contents_dir;
static string frameworks_dir;
static string macos_dir;
static string resources_dir;
static string mmp_dir;
static string mono_dir;
static string sdk_root;
static string custom_bundle_name;
static string tls_provider;
static string http_message_provider;
static string BundleName { get { return custom_bundle_name != null ? custom_bundle_name : "MonoBundle"; } }
static string AppPath { get { return Path.Combine (macos_dir, app_name); } }
static string icon;
static string certificate_name;
static int verbose = 0;
public static bool Force;
static bool is_extension;
static bool frameworks_copied_to_bundle_dir; // Have we copied any frameworks to Foo.app/Contents/Frameworks?
// This must be kept in sync with the system launcher's minimum mono version (in launcher/launcher-system.m)
static Version MinimumMonoVersion = new Version (4, 2, 0);
const string pkg_config = "/Library/Frameworks/Mono.framework/Commands/pkg-config";
static HashSet<string> xammac_reference_assemblies = new HashSet<string> {
"Xamarin.Mac.dll",
"Xamarin.Mac.CFNetwork.dll",
"OpenTK.dll"
};
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 IsUnifiedFullXamMacFramework { get; private set; }
public static bool IsUnifiedFullSystemFramework { get; private set; }
public static bool IsUnifiedMobile { get; private set; }
public static bool IsUnified { get { return IsUnifiedFullSystemFramework || IsUnifiedMobile || IsUnifiedFullXamMacFramework; } }
public static bool IsClassic { get { return !IsUnified; } }
public static bool Is64Bit {
get {
if (IsUnified && !arch_set)
return true;
return arch == "x86_64";
}
}
public static Version SDKVersion { get { return sdk_version; } }
public static Version MinOSVersion { get { return minos; } }
static int watch_level;
static Stopwatch watch;
static void Watch (string msg, int level)
{
if (watch != null && (watch_level > level))
Console.WriteLine ("{0}: {1} ms", msg, watch.ElapsedMilliseconds);
}
public static bool EnableDebug {
get { return App.EnableDebug; }
}
public static int Main (string [] args)
{
try {
Console.OutputEncoding = new UTF8Encoding (false, false);
Main2 (args);
}
catch (Exception e) {
ErrorHelper.Show (e);
}
finally {
Watch ("Total time", 0);
}
return 0;
}
static void Main2 (string [] args)
{
var os = new OptionSet () {
{ "h|?|help", "Displays the help", v => show_help = true },
{ "version", "Output version information and exit.", v => show_version = true },
{ "f|force", "Forces the recompilation of code, regardless of timestamps", v=> Force = true },
{ "cache=", "Specify the directory where temporary build files will be cached", v => Cache.Location = v },
{ "a|assembly=", "Add an assembly to be processed", v => references.Add (v) },
{ "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 },
{ "d|debug", "Build a debug bundle", v => App.EnableDebug = true },
{ "s|sgen:", "Use the SGen Garbage Collector",
v => {
if (!ParseBool (v, "sgen"))
ErrorHelper.Warning (43, "The Boehm garbage collector is not supported. The SGen garbage collector has been selected instead.");
},
true // do not show the option anymore
},
{ "boehm:", "Enable the Boehm garbage collector",
v => {
if (ParseBool (v, "boehm"))
ErrorHelper.Warning (43, "The Boehm garbage collector is not supported. The SGen garbage collector has been selected instead."); },
true // do not show the option anymore
},
{ "new-refcount:", "Enable new refcounting logic",
v => {
if (!ParseBool (v, "new-refcount"))
ErrorHelper.Warning (80, "Disabling the new refcount logic is deprecated.");
},
true // do not show this option anymore
},
{ "nolink", "Do not link the assemblies", v => App.LinkMode = LinkMode.None },
{ "mapinject", "Inject a fast method map [deprecated]", v => { ErrorHelper.Show (new MonoMacException (16, false, "The option '{0}' has been deprecated.", "--mapinject")); } },
{ "minos=", "Minimum supported version of Mac OS X",
v => {
try {
minos = Version.Parse (v);
} catch (Exception ex) {
ErrorHelper.Error (26, ex, "Could not parse the command line argument '{0}': {1}", "-minos", ex.Message);
}
}
},
{ "linksdkonly", "Link only the SDK assemblies", v => App.LinkMode = LinkMode.SDKOnly },
{ "linkskip=", "Skip linking of the specified assembly", v => App.LinkSkipped.Add (v) },
{ "i18n=", "List of i18n assemblies to copy to the output directory, separated by commas (none,all,cjk,mideast,other,rare,west)", v => App.I18n = LinkerOptions.ParseI18nAssemblies (v) },
{ "c|certificate=", "The Code Signing certificate for the application", v => { certificate_name = v; }},
{ "p", "Generate a plist for the application", v => { generate_plist = true; }},
{ "v|verbose", "Verbose output", v => { verbose++; }},
{ "q", "Quiet", v => verbose-- },
{ "i|icon=", "Use the specified file as the bundle icon", v => { icon = v; }},
{ "xml=", "Provide an extra XML definition file to the linker", v => App.Definitions.Add (v) },
{ "time", v => watch_level++ },
{ "sdkroot=", "Specify the location of Apple SDKs", v => sdk_root = v },
{ "arch=", "Specify the architecture ('i386' or 'x86_64') of the native runtime (default to 'i386')", v => { arch = v; arch_set = true; } },
{ "profile=", "(Obsoleted in favor of --target-framework) Specify the .NET profile to use (defaults to '" + Xamarin.Utils.TargetFramework.Default + "')", v => SetTargetFramework (v) },
{ "target-framework=", "Specify the .NET target framework to use (defaults to '" + Xamarin.Utils.TargetFramework.Default + "')", v => SetTargetFramework (v) },
{ "force-thread-check", "Keep UI thread checks inside (even release) builds", v => { thread_check = true; }},
{ "disable-thread-check", "Remove UI thread checks inside (even debug) builds", v => { thread_check = false; }},
{ "registrar:", "Specify the registrar to use (dynamic [default], IL or static)", v =>
{
switch (v) {
case "static":
registrar = RegistrarMode.Static;
break;
case "dynamic":
registrar = RegistrarMode.Dynamic;
break;
case "il":
registrar = RegistrarMode.IL;
break;
case "default":
registrar = RegistrarMode.Default;
break;
default:
throw new MonoMacException (20, true, "The valid options for '{0}' are '{1}'.", "--registrar", "il, dynamic, static or default");
}
}, true /* hidden for now */
},
{ "sdk=", "Specifies the SDK version to compile against (version, for example \"10.9\")",
v => {
try {
sdk_version = Version.Parse (v);
} catch (Exception ex) {
ErrorHelper.Error (26, ex, "Could not parse the command line argument '{0}': {1}", "-sdk", ex.Message);
}
}
},
{ "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 => { link_flags = v; }
},
{ "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);
}
},
{ "profiling:", "Enable profiling", v => profiling = ParseBool (v, "profiling") },
{ "custom_bundle_name=", "Specify a custom name for the MonoBundle folder.", v => custom_bundle_name = v, true }, // Hidden hack for "universal binaries"
{ "tls-provider=", "Specify the default TLS provider", v => { tls_provider = v; }},
{ "http-message-handler=", "Specify the default HTTP Message Handler", v => { http_message_provider = v; }},
{ "extension", "Specifies an app extension", v => is_extension = 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
};
AddSharedOptions (os);
IList<string> unprocessed;
try {
unprocessed = os.Parse (args);
}
catch (MonoMacException) {
throw;
}
catch (Exception e) {
throw new MonoMacException (10, true, "Could not parse the command line arguments: {0}", e.Message);
}
App.RuntimeOptions = RuntimeOptions.Create (http_message_provider, tls_provider);
ErrorHelper.Verbosity = verbose;
if (watch_level > 0) {
watch = new Stopwatch ();
watch.Start ();
}
if (show_help || (args.Length == 0)) {
ShowHelp (os);
return;
} else if (show_version) {
Console.Write ("mmp {0}.{1}", Constants.Version, Constants.Revision);
Console.WriteLine ();
return;
}
bool force45From40UnifiedSystemFull = false;
if (!targetFramework.HasValue)
targetFramework = TargetFramework.Default;
if (TargetFramework.Identifier == TargetFramework.Xamarin_Mac_2_0.Identifier) {
IsUnifiedMobile = true;
} else {
foreach (var asm in references) {
if (asm.EndsWith ("reference/full/Xamarin.Mac.dll")) {
IsUnifiedFullSystemFramework = true;
force45From40UnifiedSystemFull = targetFramework == TargetFramework.Net_4_0;
break;
}
if (asm.EndsWith ("mono/4.5/Xamarin.Mac.dll")) {
IsUnifiedFullXamMacFramework = true;
break;
}
}
}
if (IsUnifiedFullXamMacFramework) {
if (TargetFramework.Identifier != TargetFramework.Net_4_5.Identifier)
throw new MonoMacException (1405, true, "useFullXamMacFramework must always target framework .NET 4.5, not '{0}' which is invalid.", userTargetFramework);
}
if (IsUnifiedFullSystemFramework)
{
if (force45From40UnifiedSystemFull) {
Console.WriteLine ("Xamarin.Mac Unified Full System profile requires .NET 4.5, not .NET 4.0.");
FixReferences (x => x.Contains ("lib/mono/4.0"), x => x.Replace("lib/mono/4.0", "lib/mono/4.5"));
targetFramework = TargetFramework.Net_4_5;
}
}
if (IsUnifiedFullSystemFramework || IsClassic) {
// 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 (targetFramework == TargetFramework.Empty)
throw new MonoMacException (1404, true, "Target framework '{0}' is invalid.", userTargetFramework);
// 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;
int IsUnifiedCount = IsUnifiedMobile ? 1 : 0;
if (IsUnifiedFullSystemFramework)
IsUnifiedCount++;
if (IsUnifiedFullXamMacFramework)
IsUnifiedCount++;
if (IsUnified == IsClassic || (IsUnified && IsUnifiedCount != 1))
throw new Exception ("IsClassic/IsUnified/IsUnifiedMobile/IsUnifiedFullSystemFramework/IsUnifiedFullXamMacFramework logic regression");
if ((IsUnifiedFullSystemFramework || IsUnifiedFullXamMacFramework) && (App.LinkMode != LinkMode.None))
throw new MonoMacException (2007, true,
"Xamarin.Mac Unified API against a full .NET framework does not support linking. Pass the -nolink flag.");
if (App.LinkMode != LinkMode.None && is_extension) {
App.LinkMode = LinkMode.None;
ErrorHelper.Warning (2014, "Xamarin.Mac Extensions do not support linking. Request for linking will be ignored.");
}
if (!IsUnifiedMobile && tls_provider != null)
throw new MonoMacException (2011, true, "Selecting a TLS Provider is only supported in the Unified Mobile profile");
App.InitializeCommon ();
Log ("Xamarin.Mac {0}{1}", Constants.Version, verbose > 0 ? "." + Constants.Revision : string.Empty);
if (verbose > 0)
Console.WriteLine ("Selected target framework: {0}; API: {1}", targetFramework, IsClassic ? "Classic" : "Unified");
try {
Pack (unprocessed);
} finally {
if (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 (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.
Cache.ValidateCache ();
}
}
Log ("bundling complete");
}
static void FixReferences (Func<string, bool> match, Func<string, string> fix)
{
var assembliesToFix = references.Where (x => match(x)).ToList ();
references = references.Except (assembliesToFix).ToList ();
var fixedAssemblies = assembliesToFix.Select (x => fix(x));
references.AddRange (fixedAssemblies);
}
static bool ParseBool (string value, string name)
{
if (string.IsNullOrEmpty (value))
return true;
switch (value.ToLowerInvariant ()) {
case "1":
case "yes":
case "true":
case "enable":
return true;
case "0":
case "no":
case "false":
case "disable":
return false;
default:
try {
return bool.Parse (value);
} catch (Exception ex) {
throw ErrorHelper.CreateError (26, ex, "Could not parse the command line argument '-{0}:{1}': {2}", name, value, ex.Message);
}
}
}
static void SetSDKVersion ()
{
if (sdk_version != null)
return;
if (string.IsNullOrEmpty (DeveloperDirectory))
return;
var sdks = new List<Version> ();
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") && sdk.EndsWith (".sdk")) {
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.
sdk_version = sdks [sdks.Count - 1];
}
}
public static Frameworks Frameworks { get { return Frameworks.MacFrameworks; } }
static void CheckForUnknownCommandLineArguments (IList<Exception> exceptions, IList<string> arguments)
{
for (int i = arguments.Count - 1; i >= 0; i--) {
if (arguments [i].StartsWith ("-")) {
exceptions.Add (ErrorHelper.CreateError (18, "Unknown command line argument: '{0}'", arguments [i]));
arguments.RemoveAt (i);
}
}
}
static void Pack (IList<string> unprocessed)
{
string fx_dir = null;
string root_assembly = null;
var native_libs = new Dictionary<string, List<MethodDefinition>> ();
HashSet<string> internalSymbols = new HashSet<string> ();
if (registrar == RegistrarMode.Default)
registrar = RegistrarMode.Dynamic;
if (is_extension)
registrar = RegistrarMode.Static;
if (no_executable) {
if (unprocessed.Count != 0) {
var exceptions = new List<Exception> ();
CheckForUnknownCommandLineArguments (exceptions, unprocessed);
exceptions.Add (new MonoMacException (50, true, "You cannot provide a root assembly if --no-root-assembly is passed, found {0} assemblies: '{1}'", unprocessed.Count, string.Join ("', '", unprocessed.ToArray ())));
throw new AggregateException (exceptions);
}
if (string.IsNullOrEmpty (output_dir))
throw new MonoMacException (51, true, "An output directory (--output) is required if --no-root-assembly is passed.");
if (string.IsNullOrEmpty (app_name))
app_name = Path.GetFileNameWithoutExtension (output_dir);
} else {
if (unprocessed.Count != 1) {
var exceptions = new List<Exception> ();
CheckForUnknownCommandLineArguments (exceptions, unprocessed);
if (unprocessed.Count > 1) {
exceptions.Add (ErrorHelper.CreateError (8, "You should provide one root assembly only, found {0} assemblies: '{1}'", unprocessed.Count, string.Join ("', '", unprocessed.ToArray ())));
} else if (unprocessed.Count == 0) {
exceptions.Add (ErrorHelper.CreateError (17, "You should provide a root assembly."));
}
throw new AggregateException (exceptions);
}
root_assembly = unprocessed [0];
if (!File.Exists (root_assembly))
throw new MonoMacException (7, true, "The root assembly '{0}' does not exist", root_assembly);
string root_wo_ext = Path.GetFileNameWithoutExtension (root_assembly);
if (Profile.IsSdkAssembly (root_wo_ext) || Profile.IsProductAssembly (root_wo_ext))
throw new MonoMacException (3, true, "Application name '{0}.exe' conflicts with an SDK or product assembly (.dll) name.", root_wo_ext);
if (references.Exists (a => Path.GetFileNameWithoutExtension (a).Equals (root_wo_ext)))
throw new MonoMacException (23, true, "Application name '{0}.exe' conflicts with another user assembly.", root_wo_ext);
string monoFrameworkDirectory = TargetFramework.MonoFrameworkDirectory;
if (IsUnifiedFullSystemFramework || IsClassic)
monoFrameworkDirectory = "4.5";
fx_dir = Path.Combine (MonoDirectory, "lib", "mono", monoFrameworkDirectory);
if (!Directory.Exists (fx_dir))
throw new MonoMacException (1403, true, "{0} {1} could not be found. Target framework '{2}' is unusable to package the application.", "Directory", fx_dir, userTargetFramework);
references.Add (root_assembly);
BuildTarget.Resolver.CommandLineAssemblies = references;
if (string.IsNullOrEmpty (app_name))
app_name = root_wo_ext;
if (string.IsNullOrEmpty (output_dir))
output_dir = Environment.CurrentDirectory;
}
CreateDirectoriesIfNeeded ();
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"))
throw new MonoMacException (79, true, "No executable was copied into the app bundle. Please contact 'support@xamarin.com'", "");
// 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.StaticRegistrar = new StaticRegistrar (BuildTarget);
if (!no_executable) {
foreach (var nr in native_references) {
if (!native_libs.ContainsKey (nr))
native_libs.Add (nr, null);
}
// warn if we ask to remove thread checks but the linker is not enabled
if (App.LinkMode == LinkMode.None && thread_check.HasValue && !thread_check.Value)
ErrorHelper.Warning (2003, "Option '{0}' will be ignored since linking is disabled", "-disable-thread-check");
var linked_native_libs = Link ();
foreach (var kvp in linked_native_libs) {
List<MethodDefinition> methods;
if (native_libs.TryGetValue (kvp.Key, out methods))
methods.AddRange (kvp.Value);
else
native_libs.Add (kvp.Key, kvp.Value);
}
internalSymbols.UnionWith (BuildTarget.LinkContext.RequiredSymbols.Keys);
Watch (string.Format ("Linking (mode: '{0}')", App.LinkMode), 1);
}
if (App.MarshalObjectiveCExceptions != MarshalObjectiveCExceptionMode.Disable && !App.RequiresPInvokeWrappers && BuildTarget.Is64Build) {
internalSymbols.Add ("xamarin_dyn_objc_msgSend");
internalSymbols.Add ("xamarin_dyn_objc_msgSendSuper");
internalSymbols.Add ("xamarin_dyn_objc_msgSend_stret");
internalSymbols.Add ("xamarin_dyn_objc_msgSendSuper_stret");
}
CopyDependencies (native_libs);
Watch ("Copy Dependencies", 1);
// MDK check
var ret = Compile (internalSymbols);
Watch ("Compile", 1);
if (ret != 0) {
if (ret == 1)
throw new MonoMacException (5109, true, "Native linking failed with error code 1. Check build log for details.");
if (ret == 69)
throw new MonoMacException (5308, true, "Xcode license agreement may not have been accepted. Please launch Xcode.");
// if not then the compilation really failed
throw new MonoMacException (5103, true, String.Format ("Failed to compile. Error code - {0}. Please file a bug report at http://bugzilla.xamarin.com", ret));
}
if (frameworks_copied_to_bundle_dir) {
int install_ret = XcodeRun ("install_name_tool", string.Format ("{0} -add_rpath @loader_path/../Frameworks", Quote (AppPath)));
if (install_ret != 0)
throw new MonoMacException (5310, true, "install_name_tool failed with an error code '{0}'. Check build log for details.", ret);
}
if (generate_plist)
GeneratePList ();
if (App.LinkMode != LinkMode.All && App.RuntimeOptions != null)
App.RuntimeOptions.Write (App.AppDirectory);
if (!string.IsNullOrEmpty (certificate_name)) {
CodeSign ();
Watch ("Code Sign", 1);
}
}
static void ExtractNativeLinkInfo ()
{
var exceptions = new List<Exception> ();
BuildTarget.ExtractNativeLinkInfo (exceptions);
if (exceptions.Count > 0)
throw new AggregateException (exceptions);
Watch ("Extracted native link info", 1);
}
static string FindSystemXcode ()
{
var output = new StringBuilder ();
if (RunCommand ("xcode-select", "-p", output: output) != 0) {
ErrorHelper.Warning (59, "Could not find the currently selected Xcode on the system: {0}", output.ToString ());
return null;
}
return output.ToString ().Trim ();
}
static string DeveloperDirectory {
get {
if (sdk_root == null)
sdk_root = LocateXcode ();
var plist_path = Path.Combine (Path.GetDirectoryName (sdk_root), "version.plist");
if (xcode_version == null) {
if (File.Exists (plist_path)) {
bool nextElement = false;
XmlReaderSettings settings = new XmlReaderSettings ();
settings.DtdProcessing = DtdProcessing.Ignore;
using (XmlReader reader = XmlReader.Create (plist_path, settings)) {
while (reader.Read()) {
// We want the element after CFBundleShortVersionString
if (reader.NodeType == XmlNodeType.Element) {
if (reader.Name == "key") {
if (reader.ReadElementContentAsString() == "CFBundleShortVersionString")
nextElement = true;
}
if (nextElement && reader.Name == "string") {
nextElement = false;
xcode_version = new Version (reader.ReadElementContentAsString());
}
}
}
}
} else {
throw ErrorHelper.CreateError (58, "The Xcode.app '{0}' is invalid (the file '{1}' does not exist).", Path.GetDirectoryName (Path.GetDirectoryName (sdk_root)), plist_path);
}
}
return sdk_root;
}
}
static string LocateXcode ()
{
// DEVELOPER_DIR overrides `xcrun` so it should have priority
string user_developer_directory = Environment.GetEnvironmentVariable ("DEVELOPER_DIR");
if (!String.IsNullOrEmpty (user_developer_directory))
return user_developer_directory;
// Next let's respect xcode-select -p if it exists
string systemXCodePath = FindSystemXcode ();
if (!String.IsNullOrEmpty (systemXCodePath)) {
if (!Directory.Exists (systemXCodePath)) {
ErrorHelper.Warning (60, "Could not find the currently selected Xcode on the system. 'xcode-select --print-path' returned '{0}', but that directory does not exist.", systemXCodePath);
}
else {
return systemXCodePath;
}
}
// Now the fallback locaions we uses to use (for backwards compat)
const string Xcode43Default = "/Applications/Xcode.app/Contents/Developer";
const string XcrunMavericks = "/Library/Developer/CommandLineTools";
if (Directory.Exists (Xcode43Default))
return Xcode43Default;
if (Directory.Exists (XcrunMavericks))
return XcrunMavericks;
// And now we give up, but don't throw like mtouch, because we don't want to change behavior (this sometimes worked it appears)
ErrorHelper.Warning (56, "Cannot find Xcode in any of our default locations. Please install Xcode, or pass a custom path using --sdkroot=<path>.");
return string.Empty;
}
static string MonoDirectory {
get {
if (mono_dir == null) {
if (IsUnifiedFullXamMacFramework || IsUnifiedMobile) {
mono_dir = GetXamMacPrefix ();
} else {
var dir = new StringBuilder ();
RunCommand (pkg_config, "--variable=prefix mono-2", null, dir);
mono_dir = Path.GetFullPath (dir.ToString ().Replace (Environment.NewLine, String.Empty));
}
}
return mono_dir;
}
}
static void GeneratePList () {
var sr = new StreamReader (typeof (Driver).Assembly.GetManifestResourceStream ("Info.plist.tmpl"));
var all = sr.ReadToEnd ();
var icon_str = (icon != null) ? "\t<key>CFBundleIconFile</key>\n\t<string>" + icon + "</string>\n\t" : "";
using (var sw = new StreamWriter (Path.Combine (contents_dir, "Info.plist"))){
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@", 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));
}
public static string Quote (string f)
{
if (f.IndexOf (' ') == -1)
return f;
var s = new StringBuilder ();
s.Append ('"');
foreach (var c in f){
if (c == '\'' || c == '"' || c == '\\')
s.Append ('\\');
s.Append (c);
}
s.Append ('"');
return s.ToString ();
}
[DllImport (Constants.libSystemLibrary)]
static extern int symlink (string path1, string path2);
[DllImport (Constants.libSystemLibrary)]
static extern IntPtr realpath (string path, IntPtr buffer);
static string GetRealPath (string path)
{
if (path.StartsWith ("@executable_path/"))
path = Path.Combine (mmp_dir, path.Substring (17));
if (path.StartsWith ("@rpath/"))
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);
}
}
[DllImport ("/usr/lib/system/libdyld.dylib")]
static extern int _NSGetExecutablePath (byte[] buffer, ref uint bufsize);
static string GetXamMacPrefix ()
{
var envFrameworkPath = Environment.GetEnvironmentVariable ("XAMMAC_FRAMEWORK_PATH");
if (!String.IsNullOrEmpty (envFrameworkPath) && Directory.Exists (envFrameworkPath))
return envFrameworkPath;
var path = System.Reflection.Assembly.GetExecutingAssembly ().Location;
#if DEBUG
var localPath = Path.GetDirectoryName (path);
while (localPath.Length > 1) {
if (Directory.Exists (Path.Combine (localPath, "_mac-build")))
return Path.Combine (localPath, "_mac-build", "Library", "Frameworks", "Xamarin.Mac.framework", "Versions", "Current");
localPath = Path.GetDirectoryName (localPath);
}
#endif
path = GetRealPath (path);
return Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (path)));
}
public static string DriverBinDirectory {
get {
return MonoMacBinDirectory;
}
}
public static string MonoMacBinDirectory {
get {
return Path.Combine (GetXamMacPrefix (), "bin");
}
}
public static bool IsUptodate (string source, string target)
{
return Application.IsUptodate (source, target);
}
public static void Log (string format, params object[] args)
{
Log (0, format, args);
}
public static void Log (int min_verbosity, string format, params object[] args)
{
if (min_verbosity > verbose)
return;
Console.WriteLine (format, args);
}
static string GenerateMain ()
{
var sb = new StringBuilder ();
using (var sw = new StringWriter (sb)) {
sw.WriteLine ("#include <xamarin/xamarin.h>");
sw.WriteLine ("#import <AppKit/NSAlert.h>");
sw.WriteLine ("#import <Foundation/NSDate.h>"); // 10.7 wants this even if not needed on 10.9
sw.WriteLine ();
sw.WriteLine ();
sw.WriteLine ();
sw.WriteLine ("extern \"C\" int xammac_setup ()");
sw.WriteLine ("{");
if (custom_bundle_name != null) {
sw.WriteLine ("extern NSString* xamarin_custom_bundle_name;");
sw.WriteLine ("\txamarin_custom_bundle_name = @\"" + custom_bundle_name + "\";");
}
sw.WriteLine ("\txamarin_use_il_registrar = {0};", registrar == RegistrarMode.IL ? "true" : "false");
sw.WriteLine ("\txamarin_marshal_managed_exception_mode = MarshalManagedExceptionMode{0};", App.MarshalManagedExceptions);
sw.WriteLine ("\txamarin_marshal_objectivec_exception_mode = MarshalObjectiveCExceptionMode{0};", App.MarshalObjectiveCExceptions);
sw.WriteLine ();
if (Driver.registrar == RegistrarMode.Static)
sw.WriteLine ("\txamarin_create_classes ();");
sw.WriteLine ("\treturn 0;");
sw.WriteLine ("}");
sw.WriteLine ();
}
return sb.ToString ();
}
static void HandleFramework (StringBuilder args, string framework, bool weak)
{
string name = Path.GetFileName (framework);
if (name.Contains ('.'))
name = name.Remove (name.IndexOf("."));
string path = Path.GetDirectoryName (framework);
if (!string.IsNullOrEmpty (path))
args.Append ("-F ").Append (Quote (path)).Append (' ');
args.Append (weak ? "-weak_framework " : "-framework ").Append (Quote (name)).Append (' ');
if (!framework.EndsWith (".framework"))
return;
// TODO - There is a chunk of code in mtouch that calls Xamarin.MachO.IsDynamicFramework and doesn't cpoy if framework of static libs
// But IsDynamicFramework is not on XM yet
CreateDirectoryIfNeeded (frameworks_dir);
Application.UpdateDirectory (framework, frameworks_dir);
frameworks_copied_to_bundle_dir = true;
}
static int Compile (IEnumerable<string> internalSymbols)
{
int ret = 1;
string cflags;
string libdir;
StringBuilder cflagsb = new StringBuilder ();
StringBuilder libdirb = new StringBuilder ();
StringBuilder mono_version = new StringBuilder ();
string mainSource = GenerateMain ();
string registrarPath = null;
SetSDKVersion ();
if (registrar == RegistrarMode.Static) {
registrarPath = Path.Combine (Cache.Location, "registrar.m");
var registrarH = Path.Combine (Cache.Location, "registrar.h");
BuildTarget.StaticRegistrar.LinkContext = BuildTarget.LinkContext;
BuildTarget.StaticRegistrar.Generate (BuildTarget.Resolver.ResolverCache.Values, registrarH, registrarPath);
var platform_assembly = BuildTarget.Resolver.ResolverCache.First ((v) => v.Value.Name.Name == XamCore.Registrar.Registrar.PlatformAssembly).Value;
Frameworks.Gather (platform_assembly, BuildTarget.Frameworks, BuildTarget.WeakFrameworks);
}
try {
string [] env = null;
if (IsUnified && !IsUnifiedFullSystemFramework)
env = new [] { "PKG_CONFIG_PATH", Path.Combine (GetXamMacPrefix (), "lib", "pkgconfig") };
RunCommand (pkg_config, "--cflags mono-2", env, cflagsb);
RunCommand (pkg_config, "--variable=libdir mono-2", env, libdirb);
RunCommand (pkg_config, "--modversion mono-2", env, mono_version);
} catch (Win32Exception e) {
throw new MonoMacException (5301, true, e, "pkg-config could not be found. Please install the Mono.framework from http://mono-project.com/Downloads");
}
Version mono_ver;
if (Version.TryParse (mono_version.ToString ().TrimEnd (), out mono_ver) && mono_ver < MinimumMonoVersion)
throw new MonoMacException (1, true, "This version of Xamarin.Mac requires Mono {0} (the current Mono version is {1}). Please update the Mono.framework from http://mono-project.com/Downloads",
MinimumMonoVersion, mono_version.ToString ().TrimEnd ());
cflags = cflagsb.ToString ().Replace (Environment.NewLine, String.Empty);
libdir = libdirb.ToString ().Replace (Environment.NewLine, String.Empty);
var libmain = embed_mono ? "libxammac" : "libxammac-system";
var libxammac = Path.Combine (GetXamMacPrefix (), "lib", libmain + (App.EnableDebug ? "-debug" : "") + ".a");
if (!File.Exists (libxammac))
throw new MonoMacException (5203, true, "Can't find {0}, likely because of a corrupted Xamarin.Mac installation. Please reinstall Xamarin.Mac.", libxammac);
switch (arch) {
case "i386":
break;
case "x86_64":
if (IsClassic)
throw new MonoMacException (5204, true, "Invalid architecture. x86_64 is only supported with the mobile profile.");
break;
default:
throw new MonoMacException (5205, true, "Invalid architecture '{0}'. Valid architectures are i386 and x86_64 (when --profile=mobile).", arch);
}
if (IsUnified && !arch_set)
arch = "x86_64";
try {
var args = new StringBuilder ();
if (App.EnableDebug)
args.Append ("-g ");
args.Append ("-mmacosx-version-min=").Append (minos.ToString ()).Append (' ');
args.Append ("-arch ").Append (arch).Append (' ');
if (arch == "x86_64")
args.Append ("-fobjc-runtime=macosx ");
foreach (var assembly in BuildTarget.Assemblies) {
if (assembly.LinkWith != null) {
foreach (var linkWith in assembly.LinkWith) {
if (verbose > 1)
Console.WriteLine ("Found LinkWith on {0} for {1}", assembly.FileName, linkWith);
if (linkWith.EndsWith (".dylib")) {
// 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.Append (Quote (finalLibPath)).Append (' ');
}
else {
args.Append (Quote (linkWith)).Append (' ');
}
}
args.Append ("-ObjC").Append (' ');
}
if (assembly.LinkerFlags != null)
foreach (var linkFlag in assembly.LinkerFlags)
args.Append (linkFlag).Append (' ');
if (assembly.Frameworks != null)
foreach (var f in assembly.Frameworks)
HandleFramework (args, f, false);
if (assembly.WeakFrameworks != null)
foreach (var f in assembly.WeakFrameworks)
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.Append (Quote (finalLibPath)).Append (' ');
}
if (is_extension)
args.Append ("-e _xamarin_mac_extension_main -framework NotificationCenter").Append(' ');
foreach (var f in BuildTarget.Frameworks)
args.Append ("-framework ").Append (f).Append (' ');
foreach (var f in BuildTarget.WeakFrameworks)
args.Append ("-weak_framework ").Append (f).Append (' ');
Driver.WriteIfDifferent (Path.Combine (Cache.Location, "exported-symbols-list"), string.Join ("\n", internalSymbols.Select ((symbol) => "_" + symbol).ToArray ()));
foreach (var symbol in internalSymbols)
args.Append ("-u _").Append (symbol).Append (' ');
bool linkWithRequiresForceLoad = BuildTarget.Assemblies.Any (x => x.ForceLoad);
if (no_executable || linkWithRequiresForceLoad)
args.Append ("-force_load "); // make sure nothing is stripped away if we don't have a root assembly, since anything can end up being used.
args.Append (Quote (libxammac)).Append (' ');
args.Append ("-o ").Append (Quote (AppPath)).Append (' ');
args.Append (cflags).Append (' ');
if (embed_mono) {
var libmono = "libmonosgen-2.0.a";
var lib = Path.Combine (libdir, libmono);
if (!File.Exists (Path.Combine (lib)))
throw new MonoMacException (5202, true, "Mono.framework MDK is missing. Please install the MDK for your Mono.framework version from http://mono-project.com/Downloads");
args.Append (Quote (lib)).Append (' ');
if (profiling.HasValue && profiling.Value) {
args.Append (Quote (Path.Combine (libdir, "libmono-profiler-log.a"))).Append (' ');
args.Append ("-u _mono_profiler_startup_log -lz ");
}
}
args.Append ("-framework AppKit -liconv -x objective-c++ ");
args.Append ("-I").Append (Quote (Path.Combine (GetXamMacPrefix (), "include"))).Append (' ');
if (registrarPath != null)
args.Append (registrarPath).Append (' ');
args.Append ("-fno-caret-diagnostics -fno-diagnostics-fixit-info ");
if (link_flags != null)
args.Append (link_flags + " ");
if (!string.IsNullOrEmpty (DeveloperDirectory))
args.Append ("-isysroot ").Append (Quote (Path.Combine (DeveloperDirectory, "Platforms", "MacOSX.platform", "Developer", "SDKs", "MacOSX" + sdk_version + ".sdk"))).Append (' ');
if (App.RequiresPInvokeWrappers) {
var state = linker_options.MarshalNativeExceptionsState;
state.End ();
args.Append (Quote (state.SourcePath)).Append (' ');
}
var main = Path.Combine (Cache.Location, "main.m");
File.WriteAllText (main, mainSource);
args.Append (Quote (main));
ret = XcodeRun ("clang", args.ToString (), null);
} catch (Win32Exception e) {
throw new MonoMacException (5103, true, e, "Failed to compile the file '{0}'. Please file a bug report at http://bugzilla.xamarin.com", "driver");
}
return ret;
}
static int XcodeRun (string command, string args, StringBuilder output = null)
{
string [] env = DeveloperDirectory != string.Empty ? new string [] { "DEVELOPER_DIR", DeveloperDirectory } : null;
int ret = RunCommand ("xcrun", String.Concat ("-sdk macosx ", command, " ", args), env, output);
if (ret != 0 && verbose > 1) {
StringBuilder debug = new StringBuilder ();
RunCommand ("xcrun", String.Concat ("--find ", command), env, debug);
Console.WriteLine ("failed using `{0}` from: {1}", command, debug);
}
return ret;
}
// check that we have a reference to XamMac.dll and not to MonoMac.dll. Check various DRM license checks
static void CheckReferences ()
{
List<Exception> exceptions = new List<Exception> ();
var cache = BuildTarget.Resolver.ToResolverCache ();
var incompatibleReferences = new List<string> ();
var haveValidReference = false;
foreach (string entry in cache.Keys) {
switch (entry) {
case "Xamarin.Mac":
if (IsUnified)
haveValidReference = true;
else
incompatibleReferences.Add (entry);
break;
case "XamMac":
if (IsClassic)
haveValidReference = true;
else
incompatibleReferences.Add (entry);
break;
case "MonoMac":
incompatibleReferences.Add (entry);
break;
}
}
if (!haveValidReference)
exceptions.Add (new MonoMacException (1401, true,
"The required '{0}' assembly is missing from the references",
IsUnified ? "Xamarin.Mac.dll" : "XamMac.dll"));
foreach (var refName in incompatibleReferences)
exceptions.Add (new MonoMacException (1402, true,
"The assembly '{0}' is not compatible with this tool or profile",
refName + ".dll"));
if (exceptions.Count > 0)
throw new AggregateException (exceptions);
}
static IDictionary<string,List<MethodDefinition>> Link ()
{
var cache = BuildTarget.Resolver.ToResolverCache ();
var resolver = cache != null
? new Mono.Linker.AssemblyResolver (cache)
: new Mono.Linker.AssemblyResolver ();
resolver.AddSearchDirectory (BuildTarget.Resolver.RootDirectory);
resolver.AddSearchDirectory (BuildTarget.Resolver.FrameworkDirectory);
var options = new LinkerOptions {
MainAssembly = BuildTarget.Resolver.GetAssembly (references [references.Count - 1]),
OutputDirectory = mmp_dir,
LinkSymbols = App.EnableDebug,
LinkMode = App.LinkMode,
Resolver = resolver,
SkippedAssemblies = App.LinkSkipped,
I18nAssemblies = App.I18n,
ExtraDefinitions = App.Definitions,
TargetFramework = TargetFramework,
Architecture = arch,
RuntimeOptions = App.RuntimeOptions,
// 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 = thread_check.HasValue ? thread_check.Value : App.EnableDebug,
MarshalNativeExceptionsState = !App.RequiresPInvokeWrappers ? null : new PInvokeWrapperGenerator ()
{
SourcePath = Path.Combine (Cache.Location, "pinvokes.m"),
HeaderPath = Path.Combine (Cache.Location, "pinvokes.h"),
Registrar = (StaticRegistrar) BuildTarget.StaticRegistrar,
},
};
linker_options = options;
Mono.Linker.LinkContext context;
MonoMac.Tuner.Linker.Process (options, out context, out resolved_assemblies);
BuildTarget.LinkContext = (context as MonoMacLinkContext);
return BuildTarget.LinkContext.PInvokeModules;
}
static void ProcessDllImports (Dictionary<string, List<MethodDefinition>> pinvoke_modules, HashSet<string> internalSymbols)
{
foreach (string assembly_name in resolved_assemblies) {
AssemblyDefinition assembly = BuildTarget.Resolver.GetAssembly (assembly_name);
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<MethodDefinition> methods;
if (!pinvoke_modules.TryGetValue (module, out methods))
pinvoke_modules.Add (module, methods = new List<MethodDefinition> ());
methods.Add (method);
}
if (module == "__Internal")
internalSymbols.Add (method.PInvokeInfo.EntryPoint);
}
}
}
}
}
}
}
}
static void CopyDependencies (IDictionary<string, List<MethodDefinition>> 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"))) {
string libName = Path.GetFileName (linkWith);
string finalLibPath = Path.Combine (mmp_dir, libName);
Application.UpdateFile (linkWith, finalLibPath);
int ret = XcodeRun ("install_name_tool -id", string.Format ("{0} {1}", Quote("@executable_path/../" + BundleName + "/" + libName), Quote (finalLibPath)));
if (ret != 0)
throw new MonoMacException (5310, true, "install_name_tool failed with an error code '{0}'. Check build log for details.", ret);
native_libraries_copied_in.Add (libName);
}
}
var processed = new HashSet<string> ();
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)
StringBuilder sb = new StringBuilder ();
foreach (string library in Directory.GetFiles (mmp_dir, "*.dylib")) {
foreach (string lib in Xamarin.MachO.GetNativeDependencies (library)) {
if (lib.StartsWith ("/Library/Frameworks/Mono.framework/Versions/", 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.Append (" -change ").Append (lib).Append (" @executable_path/../" + BundleName + "/").Append (libname);
}
}
// if required update the paths inside the .dylib that was copied
if (sb.Length > 0) {
sb.Append (' ').Append (Quote (library));
int ret = XcodeRun ("install_name_tool", sb.ToString ());
if (ret != 0)
throw new MonoMacException (5310, true, "install_name_tool failed with an error code '{0}'. Check build log for details.", ret);
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 "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
return true;
}
// Shutup the warning until we decide on bug: 36478
if (shortendName.ToLowerInvariant () == "intl" && IsUnifiedFullXamMacFramework)
return true;
return false;
}
static void ProcessNativeLibrary (HashSet<string> processed, string library, List<MethodDefinition> 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 as passed in framework, ignore
if (App.Frameworks.Contains (library))
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 (MonoDirectory, "lib", libName);
if (src == null && File.Exists (monoDirPath))
src = monoDirPath;
// Now let's check in path with our libName
string path = Path.GetDirectoryName (library);
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 MonoMacException (2006, false, "Native library '{0}' was referenced but could not be found.", name));
if (used_by_methods != null && used_by_methods.Count > 0) {
const int referencedByLimit = 25;
bool limitReferencedByWarnings = used_by_methods.Count > referencedByLimit && verbose < 4;
foreach (var m in limitReferencedByWarnings ? used_by_methods.Take (referencedByLimit) : used_by_methods) {
ErrorHelper.Warning (2009, " Referenced by {0}.{1}", m.DeclaringType.FullName, m.Name);
}
if (limitReferencedByWarnings)
ErrorHelper.Warning (2012, " Only first {0} of {1} \"Referenced by\" warnings shown.", referencedByLimit, used_by_methods.Count);
}
return;
}
string real_src = GetRealPath (src);
string dest = Path.Combine (mmp_dir, Path.GetFileName (real_src));
if (verbose > 1)
Console.WriteLine ("Native library '{0}' copied to application bundle.", Path.GetFileName (real_src));
// FIXME: should we strip extra architectures (e.g. x64) ?
// that could break the library signature and cause issues on the appstore :(
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");
if (native_references.Contains (real_src)) {
if (!isStaticLib) {
int ret = XcodeRun ("install_name_tool -id", string.Format ("{0} {1}", Quote("@executable_path/../" + BundleName + "/" + name), Quote(dest)));
if (ret != 0)
throw new MonoMacException (5310, true, "install_name_tool failed with an error code '{0}'. Check build log for details.", ret);
}
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 CreateSymLink (string directory, string real, string link)
{
string cd = Environment.CurrentDirectory;
Environment.CurrentDirectory = directory;
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 () {
App.AppDirectory = Path.Combine (output_dir, string.Format("{0}.{1}", app_name, is_extension ? "appex" : "app"));
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, BundleName);
CreateDirectoryIfNeeded (App.AppDirectory);
CreateDirectoryIfNeeded (contents_dir);
CreateDirectoryIfNeeded (macos_dir);
CreateDirectoryIfNeeded (resources_dir);
CreateDirectoryIfNeeded (mmp_dir);
}
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 MonoMacException (1403, true, "{0} '{1}' could not be found. Target framework {2} is unusable to package the application.", "File", machine_config, userTargetFramework);
File.Copy (machine_config, Path.Combine (mmp_dir, "machine.config"), true);
}
CopyResourceFile ("config", "config");
}
}
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 mdbfile = string.Format ("{0}.mdb", asm);
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));
if (verbose > 0)
Console.WriteLine ("Added assembly {0}", asm);
if (App.EnableDebug && File.Exists (mdbfile))
File.Copy (mdbfile, Path.Combine (mmp_dir, Path.GetFileName (mdbfile)), true);
if (File.Exists (configfile))
File.Copy (configfile, Path.Combine (mmp_dir, Path.GetFileName (configfile)), true);
}
}
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 references) {
var assembly = BuildTarget.Resolver.AddAssembly (SwapOutReferenceAssembly (asm));
if (assembly == null)
ErrorHelper.Warning (1501, "Can not resolve reference: {0}", 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.FullyQualifiedName);
if (resolved_assemblies.Contains (fqname))
return;
BuildTarget.Assemblies.Add (new Assembly (BuildTarget, assembly));
resolved_assemblies.Add (fqname);
foreach (AssemblyNameReference reference in assembly.MainModule.AssemblyReferences) {
var reference_assembly = BuildTarget.Resolver.Resolve (SwapOutReferenceAssembly (reference.FullName));
if (verbose > 1)
Console.WriteLine ("Adding dependency {0} required by {1}", reference.Name, assembly.MainModule.Name);
ProcessAssemblyReferences (reference_assembly);
}
}
static string SwapOutReferenceAssembly (string assembly)
{
// Inject the correct Xamarin.Mac.dll - the one in the framework
// directory is a reference assembly only (stripped of IL, containing
// only API/metadata) and the correct one based on the target
// architecture needs to replace it
string fileName = Path.GetFileName (assembly);
if (assembly.Contains ("OpenTK.dll") && IsUnifiedFullXamMacFramework)
return assembly;
if (IsUnified &&
xammac_reference_assemblies.Contains (fileName)) {
switch (arch) {
case "i386":
case "x86_64":
return Path.Combine (GetXamMacPrefix (), "lib", arch, (IsUnifiedFullSystemFramework || IsUnifiedFullXamMacFramework) ? "full" : "mobile", fileName);
default:
throw new MonoMacException (5205, true, "Invalid architecture '{0}'. " +
"Valid architectures are i386 and x86_64 (when --profile=mobile).", arch);
}
}
return assembly;
}
}
}