2016-04-21 15:57:02 +03:00
/ *
* Copyright 2014 Xamarin Inc . All rights reserved .
*
* Authors :
* Rolf Bjarne Kvinge < rolf @xamarin . com >
*
* /
using System ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices ;
using System.Text ;
using Xamarin.Utils ;
2016-05-11 13:47:26 +03:00
using XamCore.ObjCRuntime ;
2016-04-21 15:57:02 +03:00
namespace Xamarin.Bundler {
public partial class Driver {
2016-12-23 20:50:35 +03:00
static void AddSharedOptions ( Application app , Mono . Options . OptionSet options )
2016-05-11 13:24:55 +03:00
{
2016-12-23 20:50:35 +03:00
options . Add ( "coop:" , "If the GC should run in cooperative mode." , v = > { app . EnableCoopGC = ParseBool ( v , "coop" ) ; } , hidden : true ) ;
2017-01-24 09:28:15 +03:00
options . Add ( "sgen-conc" , "Enable the concurrent garbage collector." , v = > { app . EnableSGenConc = true ; } ) ;
2016-12-22 22:11:50 +03:00
options . Add ( "marshal-objectivec-exceptions:" , "Specify how Objective-C exceptions should be marshalled. Valid values: default, unwindmanagedcode, throwmanagedexception, abort and disable. The default depends on the target platform (on watchOS the default is 'throwmanagedexception', while on all other platforms it's 'disable')." , v = > {
2016-05-11 13:47:26 +03:00
switch ( v ) {
case "default" :
2016-12-23 20:50:35 +03:00
app . MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . Default ;
2016-05-11 13:47:26 +03:00
break ;
case "unwindmanaged" :
case "unwindmanagedcode" :
2016-12-23 20:50:35 +03:00
app . MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . UnwindManagedCode ;
2016-05-11 13:47:26 +03:00
break ;
case "throwmanaged" :
case "throwmanagedexception" :
2016-12-23 20:50:35 +03:00
app . MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . ThrowManagedException ;
2016-05-11 13:47:26 +03:00
break ;
case "abort" :
2016-12-23 20:50:35 +03:00
app . MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . Abort ;
2016-05-11 13:47:26 +03:00
break ;
case "disable" :
2016-12-23 20:50:35 +03:00
app . MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . Disable ;
2016-05-11 13:47:26 +03:00
break ;
default :
2016-12-22 22:11:50 +03:00
throw ErrorHelper . CreateError ( 26 , "Could not parse the command line argument '{0}': {1}" , "--marshal-objective-exceptions" , $"Invalid value: {v}. Valid values are: default, unwindmanagedcode, throwmanagedexception, abort and disable." ) ;
2016-05-11 13:47:26 +03:00
}
} ) ;
2016-12-22 22:11:50 +03:00
options . Add ( "marshal-managed-exceptions:" , "Specify how managed exceptions should be marshalled. Valid values: default, unwindnativecode, throwobjectivecexception, abort and disable. The default depends on the target platform (on watchOS the default is 'throwobjectivecexception', while on all other platform it's 'disable')." , v = > {
2016-05-11 13:47:26 +03:00
switch ( v ) {
case "default" :
2016-12-23 20:50:35 +03:00
app . MarshalManagedExceptions = MarshalManagedExceptionMode . Default ;
2016-05-11 13:47:26 +03:00
break ;
case "unwindnative" :
case "unwindnativecode" :
2016-12-23 20:50:35 +03:00
app . MarshalManagedExceptions = MarshalManagedExceptionMode . UnwindNativeCode ;
2016-05-11 13:47:26 +03:00
break ;
case "throwobjectivec" :
case "throwobjectivecexception" :
2016-12-23 20:50:35 +03:00
app . MarshalManagedExceptions = MarshalManagedExceptionMode . ThrowObjectiveCException ;
2016-05-11 13:47:26 +03:00
break ;
case "abort" :
2016-12-23 20:50:35 +03:00
app . MarshalManagedExceptions = MarshalManagedExceptionMode . Abort ;
2016-05-11 13:47:26 +03:00
break ;
case "disable" :
2016-12-23 20:50:35 +03:00
app . MarshalManagedExceptions = MarshalManagedExceptionMode . Disable ;
2016-05-11 13:47:26 +03:00
break ;
default :
2016-12-22 22:11:50 +03:00
throw ErrorHelper . CreateError ( 26 , "Could not parse the command line argument '{0}': {1}" , "--marshal-managed-exceptions" , $"Invalid value: {v}. Valid values are: default, unwindnativecode, throwobjectivecexception, abort and disable." ) ;
2016-05-11 13:47:26 +03:00
}
} ) ;
2017-01-11 23:10:39 +03:00
options . Add ( "j|jobs=" , "The level of concurrency. Default is the number of processors." , v = > {
Jobs = int . Parse ( v ) ;
} ) ;
}
static int Jobs ;
public static int Concurrency {
get {
return Jobs = = 0 ? Environment . ProcessorCount : Jobs ;
}
2016-05-11 13:24:55 +03:00
}
2016-04-21 15:57:02 +03:00
#if MONOMAC
#pragma warning disable 0414
static string userTargetFramework = TargetFramework . Default . ToString ( ) ;
#pragma warning restore 0414
#endif
static TargetFramework ? targetFramework ;
public static bool HasTargetFramework {
get { return targetFramework . HasValue ; }
}
public static TargetFramework TargetFramework {
get { return targetFramework . Value ; }
set { targetFramework = value ; }
}
static void SetTargetFramework ( string fx )
{
#if MONOMAC
userTargetFramework = fx ;
#endif
switch ( fx . Trim ( ) . ToLowerInvariant ( ) ) {
#if MONOMAC
case "xammac" :
case "mobile" :
case "xamarin.mac" :
targetFramework = TargetFramework . Xamarin_Mac_2_0 ;
break ;
#endif
default :
TargetFramework parsedFramework ;
if ( ! Xamarin . Utils . TargetFramework . TryParse ( fx , out parsedFramework ) )
throw ErrorHelper . CreateError ( 68 , "Invalid value for target framework: {0}." , fx ) ;
#if MONOMAC
if ( parsedFramework = = TargetFramework . Net_3_0 | | parsedFramework = = TargetFramework . Net_3_5 )
parsedFramework = TargetFramework . Net_2_0 ;
#endif
targetFramework = parsedFramework ;
break ;
}
#if MTOUCH
if ( Array . IndexOf ( TargetFramework . ValidFrameworks , targetFramework . Value ) = = - 1 )
throw ErrorHelper . CreateError ( 70 , "Invalid target framework: {0}. Valid target frameworks are: {1}." , targetFramework . Value , string . Join ( " " , TargetFramework . ValidFrameworks . Select ( ( v ) = > v . ToString ( ) ) . ToArray ( ) ) ) ;
#endif
}
public static int RunCommand ( string path , string args , string [ ] env = null , StringBuilder output = null , bool suppressPrintOnErrors = false )
{
Exception stdin_exc = null ;
var info = new ProcessStartInfo ( path , args ) ;
info . UseShellExecute = false ;
info . RedirectStandardInput = false ;
info . RedirectStandardOutput = true ;
info . RedirectStandardError = true ;
System . Threading . ManualResetEvent stdout_completed = new System . Threading . ManualResetEvent ( false ) ;
System . Threading . ManualResetEvent stderr_completed = new System . Threading . ManualResetEvent ( false ) ;
if ( output = = null )
output = new StringBuilder ( ) ;
if ( env ! = null ) {
if ( env . Length % 2 ! = 0 )
throw new Exception ( "You passed an environment key without a value" ) ;
for ( int i = 0 ; i < env . Length ; i + = 2 )
info . EnvironmentVariables [ env [ i ] ] = env [ i + 1 ] ;
}
if ( verbose > 0 )
Console . WriteLine ( "{0} {1}" , path , args ) ;
using ( var p = Process . Start ( info ) ) {
p . OutputDataReceived + = ( s , e ) = > {
if ( e . Data ! = null ) {
lock ( output )
output . AppendLine ( e . Data ) ;
} else {
stdout_completed . Set ( ) ;
}
} ;
p . ErrorDataReceived + = ( s , e ) = > {
if ( e . Data ! = null ) {
lock ( output )
output . AppendLine ( e . Data ) ;
} else {
stderr_completed . Set ( ) ;
}
} ;
p . BeginOutputReadLine ( ) ;
p . BeginErrorReadLine ( ) ;
p . WaitForExit ( ) ;
stderr_completed . WaitOne ( TimeSpan . FromSeconds ( 1 ) ) ;
stdout_completed . WaitOne ( TimeSpan . FromSeconds ( 1 ) ) ;
2016-10-03 21:02:28 +03:00
GC . Collect ( ) ; // Workaround for: https://bugzilla.xamarin.com/show_bug.cgi?id=43462#c14
2016-04-21 15:57:02 +03:00
if ( p . ExitCode ! = 0 ) {
// note: this repeat the failing command line. However we can't avoid this since we're often
// running commands in parallel (so the last one printed might not be the one failing)
if ( ! suppressPrintOnErrors )
Console . Error . WriteLine ( "Process exited with code {0}, command:\n{1} {2}{3}" , p . ExitCode , path , args , output . Length > 0 ? "\n" + output . ToString ( ) : string . Empty ) ;
return p . ExitCode ;
} else if ( verbose > 0 & & output . Length > 0 & & ! suppressPrintOnErrors ) {
Console . WriteLine ( output . ToString ( ) ) ;
}
if ( stdin_exc ! = null )
throw stdin_exc ;
}
return 0 ;
}
#if ! MMP_TEST
static void FileMove ( string source , string target )
{
Application . TryDelete ( target ) ;
File . Move ( source , target ) ;
}
2016-05-11 14:00:20 +03:00
static void MoveIfDifferent ( string path , string tmp )
{
// Don't read the entire file into memory, it can be quite big in certain cases.
bool move = false ;
using ( var fs1 = new FileStream ( path , FileMode . Open , FileAccess . Read ) ) {
using ( var fs2 = new FileStream ( tmp , FileMode . Open , FileAccess . Read ) ) {
if ( fs1 . Length ! = fs2 . Length ) {
Log ( 3 , "New file '{0}' has different length, writing new file." , path ) ;
move = true ;
} else {
move = ! Cache . CompareStreams ( fs1 , fs2 ) ;
}
}
}
if ( move ) {
FileMove ( tmp , path ) ;
} else {
Log ( 3 , "Target {0} is up-to-date." , path ) ;
}
}
2016-04-21 15:57:02 +03:00
public static void WriteIfDifferent ( string path , string contents )
{
var tmp = path + ".tmp" ;
try {
if ( ! File . Exists ( path ) ) {
2016-11-21 15:30:46 +03:00
Directory . CreateDirectory ( Path . GetDirectoryName ( path ) ) ;
2016-04-21 15:57:02 +03:00
File . WriteAllText ( path , contents ) ;
Log ( 3 , "File '{0}' does not exist, creating it." , path ) ;
return ;
}
File . WriteAllText ( tmp , contents ) ;
2016-05-11 14:00:20 +03:00
MoveIfDifferent ( path , tmp ) ;
} catch ( Exception e ) {
File . WriteAllText ( path , contents ) ;
ErrorHelper . Warning ( 1014 , e , "Failed to re-use cached version of '{0}': {1}." , path , e . Message ) ;
} finally {
Application . TryDelete ( tmp ) ;
}
}
2016-04-21 15:57:02 +03:00
2016-05-11 14:00:20 +03:00
public static void WriteIfDifferent ( string path , byte [ ] contents )
{
var tmp = path + ".tmp" ;
2016-04-21 15:57:02 +03:00
2016-05-11 14:00:20 +03:00
try {
if ( ! File . Exists ( path ) ) {
File . WriteAllBytes ( path , contents ) ;
Log ( 3 , "File '{0}' does not exist, creating it." , path ) ;
return ;
2016-04-21 15:57:02 +03:00
}
2016-05-11 14:00:20 +03:00
File . WriteAllBytes ( tmp , contents ) ;
MoveIfDifferent ( path , tmp ) ;
2016-04-21 15:57:02 +03:00
} catch ( Exception e ) {
2016-05-11 14:00:20 +03:00
File . WriteAllBytes ( path , contents ) ;
2016-04-21 15:57:02 +03:00
ErrorHelper . Warning ( 1014 , e , "Failed to re-use cached version of '{0}': {1}." , path , e . Message ) ;
} finally {
Application . TryDelete ( tmp ) ;
}
}
#endif
internal static string GetFullPath ( )
{
return System . Reflection . Assembly . GetExecutingAssembly ( ) . Location ;
}
static Version xcode_version ;
public static Version XcodeVersion {
get {
return xcode_version ;
}
}
}
}