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 ;
2016-05-11 14:27:51 +03:00
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 ;
2016-05-11 14:10:35 +03:00
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 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 ;
2016-05-11 14:10:35 +03:00
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.
2016-10-06 15:16:55 +03:00
List < string > link_with_and_ship = new List < string > ( ) ;
public IEnumerable < string > LibrariesToShip { get { return link_with_and_ship ; } }
2016-10-06 17:13:01 +03:00
PInvokeWrapperGenerator pinvoke_state ;
PInvokeWrapperGenerator MarshalNativeExceptionsState {
get {
if ( ! App . RequiresPInvokeWrappers )
return null ;
if ( pinvoke_state = = null ) {
pinvoke_state = new PInvokeWrapperGenerator ( )
{
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
2016-08-24 16:43:35 +03:00
if ( Path . GetFileNameWithoutExtension ( ad . MainModule . FileName ) = = Driver . ProductAssembly )
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 )
2016-05-11 14:10:35 +03:00
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 , MemberReference > entry_points ;
public IDictionary < string , MemberReference > GetEntryPoints ( )
{
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 ( Cache . Location , "entry-points.txt" ) ;
if ( cached_link | | ! any_assembly_updated ) {
entry_points = new Dictionary < string , MemberReference > ( ) ;
foreach ( var ep in File . ReadAllLines ( cache_location ) )
entry_points . Add ( ep , null ) ;
} else {
2016-05-11 14:27:51 +03:00
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 , MemberReference > ( ) ;
2016-05-11 14:27:51 +03:00
marshal_exception_pinvokes = new List < MethodDefinition > ( ) ;
2016-04-21 15:58:45 +03:00
} else {
entry_points = LinkContext . RequiredSymbols ;
2016-05-11 14:27:51 +03:00
marshal_exception_pinvokes = LinkContext . MarshalExceptionPInvokes ;
2016-04-21 15:58:45 +03:00
}
2016-05-11 14:27:51 +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 ) ;
2016-05-11 14:27:51 +03:00
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 MemberReference GetMemberForSymbol ( string symbol )
{
MemberReference rv = null ;
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 ) {
2016-10-28 00:45:33 +03:00
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 ;
2016-08-24 16:43:35 +03:00
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 . ProductAssembly )
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 t in main . Types ) {
GetTypeReferences ( t , assemblies , exceptions ) ;
}
}
}
void GetTypeReferences ( TypeDefinition type , HashSet < string > assemblies , List < Exception > exceptions )
{
GetCustomAttributeReferences ( type , assemblies , exceptions ) ;
if ( type . HasEvents ) {
foreach ( var e in type . Events )
GetCustomAttributeReferences ( e , assemblies , exceptions ) ;
}
if ( type . HasFields ) {
foreach ( var f in type . Fields )
GetCustomAttributeReferences ( f , assemblies , exceptions ) ;
}
if ( type . HasMethods ) {
foreach ( var m in type . Methods )
GetCustomAttributeReferences ( m , assemblies , exceptions ) ;
}
if ( type . HasProperties ) {
foreach ( var p in type . Properties )
GetCustomAttributeReferences ( p , assemblies , exceptions ) ;
}
if ( type . HasNestedTypes ) {
foreach ( var nt in type . NestedTypes )
GetTypeReferences ( nt , assemblies , exceptions ) ;
}
}
void GetCustomAttributeReferences ( ICustomAttributeProvider cap , HashSet < string > assemblies , List < Exception > exceptions )
{
if ( ! cap . HasCustomAttributes )
return ;
foreach ( var ca in cap . CustomAttributes ) {
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 ) ;
}
}
}
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 ) ;
2016-05-11 14:10:35 +03:00
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 ,
2016-05-11 14:27:51 +03:00
RuntimeOptions = App . RuntimeOptions ,
2016-10-06 17:13:01 +03:00
MarshalNativeExceptionsState = MarshalNativeExceptionsState ,
2016-04-21 15:58:45 +03:00
} ;
2016-05-11 14:10:35 +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 ) ;
2016-11-01 20:11:25 +03:00
output . Add ( Path . Combine ( BuildDirectory , a . FileName ) ) ;
2016-11-01 17:37:37 +03:00
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" ) ;
2016-11-01 20:11:25 +03:00
output . Add ( Path . Combine ( BuildDirectory , a . FileName ) + ".mdb" ) ;
2016-11-01 17:37:37 +03:00
}
2016-04-21 15:58:45 +03:00
if ( a . Satellites ! = null ) {
foreach ( var s in a . Satellites ) {
input . Add ( s ) ;
2016-11-01 20:11:25 +03:00
output . Add ( Path . Combine ( BuildDirectory , Path . GetFileName ( Path . GetDirectoryName ( s ) ) , Path . GetFileName ( s ) ) ) ;
2016-11-01 17:37:37 +03:00
// 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 ) ;
2016-11-01 20:11:25 +03:00
output . Add ( Path . Combine ( BuildDirectory , 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
2016-11-01 20:11:25 +03:00
a . LoadAssembly ( Path . Combine ( BuildDirectory , 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 ) ;
2016-11-01 20:11:25 +03:00
a . LoadAssembly ( Path . Combine ( BuildDirectory , a . FileName ) ) ;
2016-04-21 15:58:45 +03:00
Assemblies . Add ( a ) ;
}
Driver . Watch ( "Cached assemblies reloaded" , 1 ) ;
2016-10-31 23:53:02 +03:00
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 ) ;
2016-11-01 20:11:25 +03:00
LinkAssemblies ( App . RootAssembly , ref linked_assemblies , BuildDirectory , 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
2016-11-01 20:11:25 +03:00
string path = Path . Combine ( BuildDirectory , assembly ) ;
2016-04-21 15:58:45 +03:00
var ad = ManifestResolver . Load ( path ) ;
var a = new Assembly ( this , ad ) ;
2016-11-01 20:11:25 +03:00
a . CopyToDirectory ( BuildDirectory ) ;
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 )
2016-11-01 20:11:25 +03:00
a . FullPath = Path . Combine ( BuildDirectory , 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
2016-11-01 20:11:25 +03:00
// Link and save to BuildDirectory
2016-05-11 14:27:51 +03:00
// If marshalling native exceptions:
2016-11-01 20:11:25 +03:00
// * Generate/calculate P/Invoke wrappers and save 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).
/ /
// * No linking
2016-05-11 14:27:51 +03:00
// If marshalling native exceptions:
2016-11-01 20:11:25 +03:00
// Generate/calculate P/Invoke wrappers and save to BuildDirectory.
2016-05-11 14:27:51 +03:00
// If not marshalling native exceptions:
2016-11-01 20:11:25 +03:00
// Copy 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 ) ;
BuildDirectory = Path . Combine ( ArchDirectory , "Build" ) ;
if ( ! Directory . Exists ( BuildDirectory ) )
Directory . CreateDirectory ( BuildDirectory ) ;
if ( ! Directory . Exists ( TargetDirectory ) )
Directory . CreateDirectory ( TargetDirectory ) ;
ManagedLink ( ) ;
2016-05-11 14:27:51 +03:00
if ( App . RequiresPInvokeWrappers ) {
// Write P/Invokes
2016-10-06 17:13:01 +03:00
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 ( ) ;
}
2016-09-07 18:22:34 +03:00
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 ( ) ;
}
2016-05-11 14:27:51 +03:00
}
2016-11-01 20:11:25 +03:00
// Now the assemblies are in BuildDirectory.
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 ) ;
}
2016-05-11 14:10:35 +03:00
public void SelectStaticRegistrar ( )
2016-04-21 15:58:45 +03:00
{
2016-05-11 14:10:35 +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
2016-05-11 14:10:35 +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 )
2016-05-11 14:10:35 +03:00
a . CreateCompilationTasks ( compile_tasks , BuildDirectory , Abis ) ;
2016-04-21 15:58:45 +03:00
}
// The static registrar.
List < string > registration_methods = null ;
2016-09-30 22:02:17 +03:00
if ( App . Registrar = = RegistrarMode . Static ) {
2016-04-21 15:58:45 +03:00
var registrar_m = Path . Combine ( ArchDirectory , "registrar.m" ) ;
2016-05-11 14:10:35 +03:00
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 ) ;
}
2016-05-11 14:10:35 +03:00
RegistrarTask . Create ( compile_tasks , Abis , this , registrar_m ) ;
2016-04-21 15:58:45 +03:00
}
2016-09-30 22:02:17 +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 . ProductSdkDirectory , "usr" , "lib" , library ) ) ;
}
// The main method.
foreach ( var abi in Abis )
2016-05-11 14:10:35 +03:00
MainTask . Create ( compile_tasks , this , abi , Assemblies , App . AssemblyName , registration_methods ) ;
2016-04-21 15:58:45 +03:00
// Start compiling.
2016-05-11 14:10:35 +03:00
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 ) ;
2016-11-01 18:31:10 +03:00
if ( ! App . FastDev | | App . IsSimulatorBuild )
2016-10-31 23:55:45 +03:00
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.TargetMinSdkName}-version-min={App.DeploymentTarget}" ) ;
compiler_flags . AddOtherFlag ( $"-isysroot {Driver.Quote (Driver.FrameworkDirectory)}" ) ;
} 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" ) ;
2016-05-20 22:03:36 +03:00
bool need_libcpp = false ;
2016-04-21 15:58:45 +03:00
if ( App . EnableBitCode )
2016-05-20 22:03:36 +03:00
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 ) {
foreach ( var entry in GetRequiredSymbols ( ) ) {
// Note that we include *all* (__Internal) p/invoked symbols here
// We also include any fields from [Field] attributes.
compiler_flags . ReferenceSymbol ( entry ) ;
}
}
string mainlib ;
if ( App . IsWatchExtension ) {
mainlib = "libwatchextension.a" ;
compiler_flags . AddOtherFlag ( " -e _xamarin_watchextension_main" ) ;
2016-04-26 00:27:10 +03:00
} 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 . ProductSdkDirectory , "usr" , "lib" ) ;
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.FrameworkDirectory, " System / Library / PrivateFrameworks "))} -framework PlugInKit" ) ;
}
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 ( Driver . CompilerPath , flags , null , output ) ;
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 ( ) )
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" ) ) ;
2016-09-30 22:02:17 +03:00
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 ) ;
2016-11-03 16:35:46 +03:00
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 ) ;
}
2016-10-06 15:16:55 +03:00
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
2016-09-22 16:21:20 +03:00
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 ) ;
}
}
}
}