2016-04-21 15:57:02 +03:00
using System ;
2017-01-24 12:48:40 +03:00
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 ;
2016-04-25 01:49:28 +03:00
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
{
2017-01-20 12:45:08 +03:00
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 ;
2016-10-14 21:14:27 +03:00
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 ) ; } }
2017-01-24 12:48:40 +03:00
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 ;
2017-01-16 20:35:28 +03:00
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
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 ;
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 ;
2016-08-24 16:43:35 +03:00
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
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.
2016-10-28 17:50:42 +03:00
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
2016-10-28 17:50:42 +03:00
if ( linkWith . Dlsym ! = DlsymOption . Default )
App . SetDlsymOption ( FullPath , linkWith . Dlsym = = DlsymOption . Required ) ;
2016-04-21 15:57:02 +03:00
#endif
2016-10-28 17:50:42 +03:00
if ( ! string . IsNullOrEmpty ( libraryName ) ) {
2017-01-03 17:14:47 +03:00
path = Path . Combine ( App . Cache . Location , libraryName ) ;
2016-10-28 17:50:42 +03:00
if ( path . EndsWith ( ".framework" , StringComparison . Ordinal ) ) {
#if MONOTOUCH
2016-12-05 20:34:41 +03:00
if ( App . Platform = = Xamarin . Utils . ApplePlatform . iOS & & App . DeploymentTarget . Major < 8 ) {
2016-10-28 17:50:42 +03:00
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
2016-10-28 17:50:42 +03:00
Frameworks . Add ( path ) ;
2016-04-21 15:57:02 +03:00
} else {
2016-10-28 17:50:42 +03:00
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" ) ;
}
}
2016-09-26 17:23:28 +03:00
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 ;
2016-10-28 17:50:42 +03:00
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 ;
2016-10-28 17:50:42 +03:00
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 ;
2016-10-14 13:34:51 +03:00
case "sqlite3" :
2016-12-08 17:42:30 +03:00
LinkerFlags . Add ( "-lsqlite3" ) ;
2016-10-14 13:34:51 +03:00
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
2016-12-08 17:42:30 +03:00
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
2017-01-16 20:35:28 +03:00
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
2017-01-16 20:35:28 +03:00
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
2017-01-16 20:35:28 +03:00
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" :
2017-01-16 20:35:28 +03:00
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 ) {
2017-01-16 20:35:28 +03:00
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 ;
}
2017-01-20 12:45:08 +03:00
// 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 ;
}
}
2017-02-01 03:59:08 +03:00
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
}
2017-01-24 12:48:40 +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 ; }
}
2017-01-25 09:52:38 +03:00
public void Update ( Target target , IEnumerable < AssemblyDefinition > assemblies )
{
// This function will remove any assemblies not in 'assemblies', and add any new assemblies.
var current = new HashSet < string > ( HashedAssemblies . Keys ) ;
foreach ( var assembly in assemblies ) {
var identity = Assembly . GetIdentity ( assembly ) ;
if ( ! current . Remove ( identity ) ) {
// new assembly
var asm = new Assembly ( target , assembly ) ;
Add ( asm ) ;
Driver . Log ( 1 , "The linker added the assembly '{0}'." , asm . Identity ) ;
}
}
foreach ( var removed in current ) {
Driver . Log ( 1 , "The linker linked away the assembly '{0}'." , this [ removed ] . Identity ) ;
Remove ( removed ) ;
}
}
2017-01-24 12:48:40 +03:00
#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
}