xamarin-macios/tools/common/Assembly.cs

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

2016-04-21 15:57:02 +03:00
using System;
using System.Collections;
2016-04-21 15:57:02 +03:00
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Mono.Cecil;
using MonoTouch.Tuner;
using XamCore.ObjCRuntime;
2016-04-21 15:57:02 +03:00
#if MONOTOUCH
using PlatformException = Xamarin.Bundler.MonoTouchException;
#else
using PlatformException = Xamarin.Bundler.MonoMacException;
#endif
namespace Xamarin.Bundler {
public partial class Assembly
{
public List<string> Satellites;
2016-04-21 15:57:02 +03:00
public Application App { get { return Target.App; } }
string full_path;
bool? is_framework_assembly;
public AssemblyDefinition AssemblyDefinition;
public Target Target;
public bool IsFrameworkAssembly { get { return is_framework_assembly.Value; } }
public string FullPath {
get {
return full_path;
}
set {
full_path = value;
if (!is_framework_assembly.HasValue) {
var real_full_path = Target.GetRealPath (full_path);
is_framework_assembly = real_full_path.StartsWith (Path.GetDirectoryName (Path.GetDirectoryName (Target.Resolver.FrameworkDirectory)), StringComparison.Ordinal);
}
2016-04-21 15:57:02 +03:00
}
}
public string FileName { get { return Path.GetFileName (FullPath); } }
public string Identity { get { return GetIdentity (FullPath); } }
public static string GetIdentity (AssemblyDefinition ad)
{
return Path.GetFileNameWithoutExtension (ad.MainModule.FileName);
}
public static string GetIdentity (string path)
{
return Path.GetFileNameWithoutExtension (path);
}
2016-04-21 15:57:02 +03:00
public bool EnableCxx;
public bool NeedsGccExceptionHandling;
public bool ForceLoad;
public HashSet<string> Frameworks = new HashSet<string> ();
public HashSet<string> WeakFrameworks = new HashSet<string> ();
public List<string> LinkerFlags = new List<string> (); // list of extra linker flags
[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
public List<string> LinkWith = new List<string> (); // list of paths to native libraries to link with, from LinkWith attributes
2016-04-21 15:57:02 +03:00
public HashSet<ModuleReference> UnresolvedModuleReferences;
[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
public bool HasLinkWithAttributes { get; private set; }
2016-04-21 15:57:02 +03:00
bool? symbols_loaded;
List<string> link_with_resources; // a list of resources that must be removed from the app
public Assembly (Target target, string path)
{
this.Target = target;
this.FullPath = path;
}
public Assembly (Target target, AssemblyDefinition definition)
{
this.Target = target;
this.AssemblyDefinition = definition;
this.FullPath = definition.MainModule.FileName;
2016-04-21 15:57:02 +03:00
}
public void LoadSymbols ()
{
if (symbols_loaded.HasValue)
return;
symbols_loaded = false;
try {
if (File.Exists (FullPath + ".mdb")) {
AssemblyDefinition.MainModule.ReadSymbols ();
symbols_loaded = true;
}
}
catch {
// do not let stale file crash us
Driver.Log (3, "Invalid debugging symbols for {0} ignored", FullPath);
}
}
void AddResourceToBeRemoved (string resource)
{
if (link_with_resources == null)
link_with_resources = new List<string> ();
link_with_resources.Add (resource);
}
public void ExtractNativeLinkInfo ()
{
// ignore framework assemblies, they won't have any LinkWith attributes
if (IsFrameworkAssembly)
return;
var assembly = AssemblyDefinition;
if (!assembly.HasCustomAttributes)
return;
var exceptions = new List<Exception> ();
string path;
//
// Tasks:
// * Remove LinkWith attribute: this is done in the linker.
// * Remove embedded resources related to LinkWith attribute from assembly: this is done at a later stage,
// here we just compile a list of resources to remove.
// * Extract embedded resources related to LinkWith attribute to a file
// * Modify the linker flags used to build/link the dylib (if fastdev) or the main binary (if !fastdev)
//
for (int i = 0; i < assembly.CustomAttributes.Count; i++) {
CustomAttribute attr = assembly.CustomAttributes[i];
if (attr.Constructor == null)
continue;
TypeReference type = attr.Constructor.DeclaringType;
if (!type.IsPlatformType ("ObjCRuntime", "LinkWithAttribute"))
continue;
// Let the linker remove it the attribute from the assembly
[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
HasLinkWithAttributes = true;
2016-04-21 15:57:02 +03:00
LinkWithAttribute linkWith = GetLinkWithAttribute (attr);
string libraryName = linkWith.LibraryName;
// Remove the resource from the assembly at a later stage.
if (!string.IsNullOrEmpty (libraryName))
AddResourceToBeRemoved (libraryName);
2016-04-21 15:57:02 +03:00
// We can't add -dead_strip if there are any LinkWith attributes where smart linking is disabled.
if (!linkWith.SmartLink)
App.DeadStrip = false;
// Don't add -force_load if the binding's SmartLink value is set and the static registrar is being used.
if (linkWith.ForceLoad && !(linkWith.SmartLink && App.Registrar == RegistrarMode.Static))
ForceLoad = true;
if (!string.IsNullOrEmpty (linkWith.LinkerFlags)) {
if (LinkerFlags == null)
LinkerFlags = new List<string> ();
LinkerFlags.Add (linkWith.LinkerFlags);
}
if (!string.IsNullOrEmpty (linkWith.Frameworks)) {
foreach (var f in linkWith.Frameworks.Split (new char[] { ' ' })) {
if (Frameworks == null)
Frameworks = new HashSet<string> ();
Frameworks.Add (f);
}
}
if (!string.IsNullOrEmpty (linkWith.WeakFrameworks)) {
foreach (var f in linkWith.WeakFrameworks.Split (new char[] { ' ' })) {
if (WeakFrameworks == null)
WeakFrameworks = new HashSet<string> ();
WeakFrameworks.Add (f);
}
}
if (linkWith.NeedsGccExceptionHandling)
NeedsGccExceptionHandling = true;
if (linkWith.IsCxx)
EnableCxx = true;
#if MONOTOUCH
if (linkWith.Dlsym != DlsymOption.Default)
App.SetDlsymOption (FullPath, linkWith.Dlsym == DlsymOption.Required);
2016-04-21 15:57:02 +03:00
#endif
if (!string.IsNullOrEmpty (libraryName)) {
path = Path.Combine (App.Cache.Location, libraryName);
if (path.EndsWith (".framework", StringComparison.Ordinal)) {
#if MONOTOUCH
if (App.Platform == Xamarin.Utils.ApplePlatform.iOS && App.DeploymentTarget.Major < 8) {
throw ErrorHelper.CreateError (1305, "The binding library '{0}' contains a user framework ({0}), but embedded user frameworks require iOS 8.0 (the deployment target is {1}). Please set the deployment target in the Info.plist file to at least 8.0.",
FileName, Path.GetFileName (path), App.DeploymentTarget);
}
#endif
var zipPath = path + ".zip";
if (!Application.IsUptodate (FullPath, zipPath)) {
Application.ExtractResource (assembly.MainModule, libraryName, zipPath, false);
Driver.Log (3, "Extracted third-party framework '{0}' from '{1}' to '{2}'", libraryName, FullPath, zipPath);
LogLinkWithAttribute (linkWith);
} else {
Driver.Log (3, "Target '{0}' is up-to-date.", path);
}
if (!File.Exists (zipPath)) {
ErrorHelper.Warning (1302, "Could not extract the native framework '{0}' from '{1}'. " +
"Please ensure the native framework was properly embedded in the managed assembly " +
"(if the assembly was built using a binding project, the native framework must be included in the project, and its Build Action must be 'ObjcBindingNativeFramework').",
libraryName, zipPath);
} else {
if (!Directory.Exists (path))
Directory.CreateDirectory (path);
if (Driver.RunCommand ("/usr/bin/unzip", string.Format ("-u -o -d {0} {1}", Driver.Quote (path), Driver.Quote (zipPath))) != 0)
throw ErrorHelper.CreateError (1303, "Could not decompress the native framework '{0}' from '{1}'. Please review the build log for more information from the native 'unzip' command.", libraryName, zipPath);
}
2016-04-21 15:57:02 +03:00
Frameworks.Add (path);
2016-04-21 15:57:02 +03:00
} else {
if (!Application.IsUptodate (FullPath, path)) {
Application.ExtractResource (assembly.MainModule, libraryName, path, false);
Driver.Log (3, "Extracted third-party binding '{0}' from '{1}' to '{2}'", libraryName, FullPath, path);
LogLinkWithAttribute (linkWith);
} else {
Driver.Log (3, "Target '{0}' is up-to-date.", path);
}
if (!File.Exists (path))
ErrorHelper.Warning (1302, "Could not extract the native library '{0}' from '{1}'. " +
"Please ensure the native library was properly embedded in the managed assembly " +
"(if the assembly was built using a binding project, the native library must be included in the project, and its Build Action must be 'ObjcBindingNativeLibrary').",
libraryName, path);
LinkWith.Add (path);
2016-04-21 15:57:02 +03:00
}
}
}
if (exceptions != null && exceptions.Count > 0)
throw new AggregateException (exceptions);
// Make sure there are no duplicates between frameworks and weak frameworks.
// Keep the weak ones.
if (Frameworks != null && WeakFrameworks != null)
Frameworks.ExceptWith (WeakFrameworks);
if (NeedsGccExceptionHandling) {
if (LinkerFlags == null)
LinkerFlags = new List<string> ();
LinkerFlags.Add ("-lgcc_eh");
}
}
static void LogLinkWithAttribute (LinkWithAttribute linkWith)
{
Driver.Log (3, " ForceLoad: {0}", linkWith.ForceLoad);
Driver.Log (3, " Frameworks: {0}", linkWith.Frameworks);
Driver.Log (3, " IsCxx: {0}", linkWith.IsCxx);
Driver.Log (3, " LinkerFlags: {0}", linkWith.LinkerFlags);
Driver.Log (3, " LinkTarget: {0}", linkWith.LinkTarget);
Driver.Log (3, " NeedsGccExceptionHandling: {0}", linkWith.NeedsGccExceptionHandling);
Driver.Log (3, " SmartLink: {0}", linkWith.SmartLink);
Driver.Log (3, " WeakFrameworks: {0}", linkWith.WeakFrameworks);
}
2016-04-21 15:57:02 +03:00
public static LinkWithAttribute GetLinkWithAttribute (CustomAttribute attr)
{
LinkWithAttribute linkWith;
var cargs = attr.ConstructorArguments;
switch (cargs.Count) {
case 3:
linkWith = new LinkWithAttribute ((string) cargs [0].Value, (LinkTarget) cargs [1].Value, (string) cargs [2].Value);
break;
case 2:
linkWith = new LinkWithAttribute ((string) cargs [0].Value, (LinkTarget) cargs [1].Value);
break;
case 0:
linkWith = new LinkWithAttribute ();
break;
2016-04-21 15:57:02 +03:00
default:
case 1:
linkWith = new LinkWithAttribute ((string) cargs [0].Value);
break;
}
foreach (var property in attr.Properties) {
switch (property.Name) {
case "NeedsGccExceptionHandling":
linkWith.NeedsGccExceptionHandling = (bool) property.Argument.Value;
break;
case "WeakFrameworks":
linkWith.WeakFrameworks = (string) property.Argument.Value;
break;
case "Frameworks":
linkWith.Frameworks = (string) property.Argument.Value;
break;
case "LinkerFlags":
linkWith.LinkerFlags = (string) property.Argument.Value;
break;
case "LinkTarget":
linkWith.LinkTarget = (LinkTarget) property.Argument.Value;
break;
case "ForceLoad":
linkWith.ForceLoad = (bool) property.Argument.Value;
break;
case "IsCxx":
linkWith.IsCxx = (bool) property.Argument.Value;
break;
case "SmartLink":
linkWith.SmartLink = (bool) property.Argument.Value;
break;
case "Dlsym":
linkWith.Dlsym = (DlsymOption) property.Argument.Value;
break;
2016-04-21 15:57:02 +03:00
default:
break;
}
}
return linkWith;
}
public void ComputeLinkerFlags ()
{
foreach (var m in AssemblyDefinition.Modules) {
if (!m.HasModuleReferences)
continue;
foreach (var mr in m.ModuleReferences) {
string name = mr.Name;
string file = Path.GetFileNameWithoutExtension (name);
switch (file) {
// special case
case "__Internal":
// well known libs
case "libc":
case "libSystem":
case "libobjc":
case "libdyld":
case "libsystem_kernel":
break;
case "sqlite3":
LinkerFlags.Add ("-lsqlite3");
Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName);
break;
2016-04-21 15:57:02 +03:00
case "libsqlite3":
// remove lib prefix
LinkerFlags.Add ("-l" + file.Substring (3));
2016-04-21 15:57:02 +03:00
Driver.Log (3, "Linking with {0} because it's referenced by a module reference in {1}", file, FileName);
break;
case "libGLES":
case "libGLESv2":
// special case for OpenGLES.framework
if (Frameworks.Add ("OpenGLES"))
2016-04-21 15:57:02 +03:00
Driver.Log (3, "Linking with the framework OpenGLES because {0} is referenced by a module reference in {1}", file, FileName);
break;
case "vImage":
case "vecLib":
// sub-frameworks
if (Frameworks.Add ("Accelerate"))
2016-04-21 15:57:02 +03:00
Driver.Log (3, "Linking with the framework Accelerate because {0} is referenced by a module reference in {1}", file, FileName);
break;
case "CoreAudioKit":
case "Metal":
case "MetalKit":
case "MetalPerformanceShaders":
// some frameworks do not exists on simulators and will result in linker errors if we include them
#if MTOUCH
if (!App.IsSimulatorBuild) {
#endif
if (Frameworks.Add (file))
2016-04-21 15:57:02 +03:00
Driver.Log (3, "Linking with the framework {0} because it's referenced by a module reference in {1}", file, FileName);
#if MTOUCH
}
#endif
break;
case "openal32":
if (Frameworks.Add ("OpenAL"))
Driver.Log (3, "Linking with the framework OpenAL because {0} is referenced by a module reference in {1}", file, FileName);
2016-04-21 15:57:02 +03:00
break;
default:
// detect frameworks
int f = name.IndexOf (".framework/", StringComparison.Ordinal);
if (f > 0) {
if (Frameworks.Add (file))
2016-04-21 15:57:02 +03:00
Driver.Log (3, "Linking with the framework {0} because it's referenced by a module reference in {1}", file, FileName);
} else {
if (UnresolvedModuleReferences == null)
UnresolvedModuleReferences = new HashSet<ModuleReference> ();
UnresolvedModuleReferences.Add (mr);
Driver.Log (3, "Could not resolve the module reference {0} in {1}", file, FileName);
}
break;
}
}
}
}
public override string ToString ()
{
return FileName;
}
// This returns the path to all related files:
// * The assembly itself
// * Any debug files (mdb/pdb)
// * Any config files
// * Any satellite assemblies
public IEnumerable<string> GetRelatedFiles ()
{
yield return FullPath;
var mdb = FullPath + ".mdb";
if (File.Exists (mdb))
yield return mdb;
var pdb = Path.ChangeExtension (FullPath, ".pdb");
if (File.Exists (pdb))
yield return pdb;
var config = FullPath + ".config";
if (File.Exists (config))
yield return config;
if (Satellites != null) {
foreach (var satellite in Satellites)
yield return satellite;
}
}
public void ComputeSatellites ()
{
var path = Path.GetDirectoryName (FullPath);
var satellite_name = Path.GetFileNameWithoutExtension (FullPath) + ".resources.dll";
foreach (var subdir in Directory.GetDirectories (path)) {
var culture_name = Path.GetFileName (subdir);
CultureInfo ci;
if (culture_name.IndexOf ('.') >= 0)
continue; // cultures can't have dots. This way we don't check every *.app directory
try {
ci = CultureInfo.GetCultureInfo (culture_name);
} catch {
// nope, not a resource language
continue;
}
if (ci == null)
continue;
var satellite = Path.Combine (subdir, satellite_name);
if (File.Exists (satellite)) {
if (Satellites == null)
Satellites = new List<string> ();
Satellites.Add (satellite);
}
}
}
public void CopySatellitesToDirectory (string directory)
{
if (Satellites == null)
return;
foreach (var a in Satellites) {
string target_dir = Path.Combine (directory, Path.GetFileName (Path.GetDirectoryName (a)));
string target_s = Path.Combine (target_dir, Path.GetFileName (a));
if (!Directory.Exists (target_dir))
Directory.CreateDirectory (target_dir);
CopyAssembly (a, target_s);
}
}
2016-04-21 15:57:02 +03:00
}
public class AssemblyCollection : IEnumerable<Assembly>
{
Dictionary<string, Assembly> HashedAssemblies = new Dictionary<string, Assembly> (StringComparer.OrdinalIgnoreCase);
public void Add (Assembly assembly)
{
Assembly other;
if (HashedAssemblies.TryGetValue (assembly.Identity, out other))
throw ErrorHelper.CreateError (2018, "The assembly '{0}' is referenced from two different locations: '{1}' and '{2}'.", assembly.Identity, other.FullPath, assembly.FullPath);
HashedAssemblies.Add (assembly.Identity, assembly);
}
public void AddRange (AssemblyCollection assemblies)
{
if (Count == 0) {
HashedAssemblies = new Dictionary<string, Assembly> (assemblies.HashedAssemblies);
} else {
foreach (var a in assemblies)
Add (a);
}
}
public int Count {
get {
return HashedAssemblies.Count;
}
}
public IDictionary<string, Assembly> Hashed {
get { return HashedAssemblies; }
}
public bool TryGetValue (string identity, out Assembly assembly)
{
return HashedAssemblies.TryGetValue (identity, out assembly);
}
public bool ContainsKey (string identity)
{
return HashedAssemblies.ContainsKey (identity);
}
public void Remove (string identity)
{
HashedAssemblies.Remove (identity);
}
public void Remove (Assembly assembly)
{
Remove (assembly.Identity);
}
public Assembly this [string key] {
get { return HashedAssemblies [key]; }
set { HashedAssemblies [key] = value; }
}
#region Interface implementations
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
public IEnumerator<Assembly> GetEnumerator ()
{
return HashedAssemblies.Values.GetEnumerator ();
}
#endregion
}
2016-04-21 15:57:02 +03:00
}