xamarin-macios/tools/mtouch/Target.cs

1008 строки
35 KiB
C#
Исходник Обычный вид История

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