2016-04-21 15:57:02 +03:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices ;
using Mono.Cecil ;
2017-04-13 00:38:54 +03:00
using Mono.Cecil.Cil ;
2016-04-21 15:57:02 +03:00
2018-03-05 09:41:28 +03:00
using Xamarin.Linker ;
2016-04-21 15:57:02 +03:00
using Xamarin.Utils ;
2018-02-05 18:26:29 +03:00
using ObjCRuntime ;
2016-05-11 13:47:26 +03:00
2016-12-01 19:18:30 +03:00
#if MONOTOUCH
using PlatformException = Xamarin . Bundler . MonoTouchException ;
using PlatformResolver = MonoTouch . Tuner . MonoTouchResolver ;
#else
using PlatformException = Xamarin . Bundler . MonoMacException ;
using PlatformResolver = Xamarin . Bundler . MonoMacResolver ;
#endif
2016-04-21 15:57:02 +03:00
namespace Xamarin.Bundler {
[Flags]
public enum RegistrarOptions {
Default = 0 ,
Trace = 1 ,
}
public enum LinkMode {
None ,
SDKOnly ,
All ,
2017-05-17 01:05:26 +03:00
#if ! MONOTOUCH
Platform ,
#endif
2016-04-21 15:57:02 +03:00
}
public partial class Application
{
2017-01-31 12:18:45 +03:00
public Cache Cache ;
2016-12-23 20:50:35 +03:00
public string AppDirectory = "." ;
2016-04-21 15:57:02 +03:00
public bool DeadStrip = true ;
public bool EnableDebug ;
internal RuntimeOptions RuntimeOptions ;
[mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp. (#3242)
* [mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp.
1. Add an --optimize flag to mtouch/mmp that allows users to select which
optimizations to apply (or not). This makes it easier to add future
optimizations, and allow users to disable any optimization that causes
problems without having to disable many other features.
2. Share as much optimization code as possible between mtouch and mmp. This
immediately gives a benefit to mmp, which has three new optimizations only
mtouch had: NSObject.IsDirectBinding inlining, IntPtr.Size inlining and
dead code elimination.
This results in ~6kb of disk space saved for a linked Xamarin.Mac app:
* link sdk: [Debug][1], [Release][2]
* link all: [Debug][3], [Release][4]
Testing also verifies that monotouchtest ([Debug][5], [Release][6]) has not
changed size at all, which means that no default optimizations have changed
inadvertedly.
[1]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--debug
[2]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--release
[3]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--debug
[4]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--release
[5]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonedebug64
[6]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonerelease64
* [tools] Don't enable the IsDirectBinding optimization by default for Xamarin.Mac apps, it's not safe.
* Fix whitespace issues.
* [doc] Document optimizations.
* Officially support optimizations by adding them to the Versions.plist.
* [linker] Improve IntPtr.Size inliner + dead code eliminatior and add tests.
* Properly handle operands for the ldc_i4_s instruction (they're sbyte).
* Fix less-than condition to actually do a less-than comparison.
* Make sure to look up the bitness in the Target, not the Application, since
the Application's value will be incorrect when building fat apps (both
Is32Build and Is64Build will be true).
* Remove unnecessary checks for the IntPtr.Size inliner: this optimization
does not depend on other instructions than the IntPtr.get_Size call, so
remove the checks that verify surrounding instructions. This makes the
IntPtr.Size inliner kick in in more scenarios (such as the new tests).
* Add tests.
* [tests] Add mmp tests for optimizations.
* [tests] Fix XM optimization tests.
* [tests] Fix test build error.
2018-01-23 13:33:48 +03:00
public Optimizations Optimizations = new Optimizations ( ) ;
2016-04-21 15:57:02 +03:00
public RegistrarMode Registrar = RegistrarMode . Default ;
public RegistrarOptions RegistrarOptions = RegistrarOptions . Default ;
[mtouch] Improve how we make sure native symbols aren't stripped away. Fixes #51710 and #54417. (#2162)
* [mtouch] Improve how we make sure native symbols aren't stripped away. Fixes #51710 and #54417.
* Refactor required symbol collection to store more information about each
symbol (field, function, Objective-C class), and in general make the code
more straight forward.
* Implement support for generating source code that references these symbols,
and do this whenever we can't ask the native linker to keep these symbols
(when using bitcode). Additionally make it possible to do this manually, so
that the source code can be generated for non-bitcode platforms too (which
is useful if the number of symbols is enormous, in which case we might
surpass the maximum command-line length).
* Also make it possible to completely ignore native symbols, or ignore them on
a per-symbol basis. This provides a fallback for users if we get something
right and we try to preserve something that shouldn't be preserved (for
instance if it doesn't exist), and the user ends up with unfixable linker
errors.
* Don't collect Objective-C classes unless they're in an assembly with
LinkWith attributes. We don't need to preserve Objective-C classes in any
other circumstances.
* Implement everything for both Xamarin.iOS and Xamarin.Mac, and share the
code between them.
* Remove previous workaround for bug #51710, since it's no longer needed.
* Add tests.
https://bugzilla.xamarin.com/show_bug.cgi?id=54417
https://bugzilla.xamarin.com/show_bug.cgi?id=51710
* [mtouch] Make sure to only keep symbols from the current app when code sharing.
This fixes a build problem with the interdependent-binding-projects test when
testing in Today Extension mode.
2017-06-02 19:29:19 +03:00
public SymbolMode SymbolMode ;
public HashSet < string > IgnoredSymbols = new HashSet < string > ( ) ;
2016-04-21 15:57:02 +03:00
public HashSet < string > Frameworks = new HashSet < string > ( ) ;
public HashSet < string > WeakFrameworks = new HashSet < string > ( ) ;
public ApplePlatform Platform { get { return Driver . TargetFramework . Platform ; } }
// Linker config
public LinkMode LinkMode = LinkMode . All ;
public List < string > LinkSkipped = new List < string > ( ) ;
public List < string > Definitions = new List < string > ( ) ;
public Mono . Linker . I18nAssemblies I18n ;
2016-05-11 13:24:55 +03:00
public bool? EnableCoopGC ;
2017-01-24 09:28:15 +03:00
public bool EnableSGenConc ;
2016-05-11 13:47:26 +03:00
public MarshalObjectiveCExceptionMode MarshalObjectiveCExceptions ;
public MarshalManagedExceptionMode MarshalManagedExceptions ;
2018-02-13 19:19:31 +03:00
bool is_default_marshal_managed_exception_mode ;
public bool IsDefaultMarshalManagedExceptionMode {
get { return is_default_marshal_managed_exception_mode | | MarshalManagedExceptions = = MarshalManagedExceptionMode . Default ; }
set { is_default_marshal_managed_exception_mode = value ; }
}
2017-04-07 15:13:11 +03:00
public List < string > RootAssemblies = new List < string > ( ) ;
2017-01-24 13:10:20 +03:00
public List < Application > SharedCodeApps = new List < Application > ( ) ; // List of appexes we're sharing code with.
2016-12-01 19:18:30 +03:00
public string RegistrarOutputLibrary ;
2016-05-11 13:24:55 +03:00
2017-01-11 23:10:39 +03:00
public static int Concurrency = > Driver . Concurrency ;
2016-12-23 20:50:35 +03:00
public Version DeploymentTarget ;
public Version SdkVersion ;
2017-04-19 14:17:05 +03:00
public bool Embeddinator { get ; set ; }
2016-12-23 20:50:35 +03:00
2017-01-31 12:18:45 +03:00
public Application ( string [ ] arguments )
{
Cache = new Cache ( arguments ) ;
}
2018-02-12 18:59:39 +03:00
public bool DynamicRegistrationSupported {
get {
return Optimizations . RemoveDynamicRegistrar ! = true ;
}
}
2017-01-24 13:10:20 +03:00
// This is just a name for this app to show in log/error messages, etc.
public string Name {
get { return Path . GetFileNameWithoutExtension ( AppDirectory ) ; }
}
2016-05-11 14:27:51 +03:00
public bool RequiresPInvokeWrappers {
get {
#if MTOUCH
if ( IsSimulatorBuild )
return false ;
#else
if ( Driver . Is64Bit )
return false ;
#endif
return MarshalObjectiveCExceptions = = MarshalObjectiveCExceptionMode . ThrowManagedException | | MarshalObjectiveCExceptions = = MarshalObjectiveCExceptionMode . Abort ;
}
}
2016-04-21 15:57:02 +03:00
public string PlatformName {
get {
switch ( Platform ) {
case ApplePlatform . iOS :
return "iOS" ;
case ApplePlatform . TVOS :
return "tvOS" ;
case ApplePlatform . WatchOS :
return "watchOS" ;
case ApplePlatform . MacOSX :
return "macOS" ;
default :
throw new NotImplementedException ( ) ;
}
}
}
[mtouch/mmp] Fix tracking of whether the static registrar should run again or not. Fixes #641. (#3534) (#3536)
* [tests] Improve debug spew for the RebuildTest_WithExtensions test.
* [mtouch/mmp] Store/load if the dynamic registrar is removed or not into the cached link results.
Store/load if the dynamic registrar is removed or not into the cached link
results, so that we generate the correct main.m even if cached linker results
are used.
* [mtouch/mmp] The static registrar must not execute if we're loading cached results from the linker.
The static registrar must not execute if we're loading cached results from the
linker, because the static registrar needs information from the linker that's
not restored from the cache.
* [mtouch/mmp] Share Touch code.
* [mtouch/mmp] Make it possible to touch inexistent files (to create them).
* [mtouch/mmp] Fix tracking of whether the static registrar should run again or not.
The recent changes to support optimizing away the dynamic registrar caused the
Xamarin.MTouch.RebuildTest_WithExtensions test to regress.
The problem
-----------
* The linker now collects and stores information the static registrar needs.
* This information is not restored from disk when the linker realizes that it
can reload previously linked assemblies instead of executing again.
* The static registrar runs again (for another reason).
* The information the static registrar needs isn't available, and incorrect
output follows.
So fix 1: show an error if the static registrar runs when the linker loaded
cached results.
The exact scenario the test ran into is this:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk (this is an optimization to avoid
compiling the registrar.m file again unless needed).
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is newer than
registrar.m's timestamp and run again, but doesn't produce the right result
because it doesn't have the information it needs.
Considered solutions
--------------------
1. Only track timestamps, not file contents. This is not ideal, since it will
result in more work done: in particular for the case above, it would add a
registrar.m compilation in build #2, and linker rerun + static registrar
rerun + registrar.m compilation + final native link in build #3.
2. Always write the output of the static registrar, even if it hasn't changed.
This is not ideal either, since it will also result in more work done: for
the case above, it would add a registrar.m compilation + final native link
in build #3.
3. Always write the output of the static registrar, but track if it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it.
This only means the final native link in build #3 is added (see #5 for why
this is worse than it sounds).
4. Always write the output of the static registrar, but track it it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it,
and track that too, so that the final native link in build #3 isn't needed
anymore. Unfortunately this may result in incorrect behavior, because now
the msbuild tasks will detect that the executable has changed, and may run
dsymutil + strip again. The executable didn't actually change, which means
it would be the previously stripped executable, and thus we'd end up with
an empty .dSYM because we ran dsymtil on an already stripped executable.
5. Idea #4, but write the output of the final link into a temporary directory
instead of the .app, so that we could track whether we should update the
executable in the .app or not. This is not optimal either, because
executables can be *big* (I've seen multi-GB tvOS bitcode executables), and
extra copies of such files should not be taken lightly.
6. Idea #4, but tell the MSBuild tasks that dsymutil/strip doesn't need to be
rerun even if the timestamp of the executable changed. This might actually
work, but now the solution's become quite complex.
Implemented solution
--------------------
Use stamp files to detect whether a file is up-to-date or not.
In particular:
* When we don't write to a file because the new contents are identical to the
old contents, we now touch a .stamp file. This stamp file means "the
accompanying file was determined to be up-to-date when the stamp was
touched."
* When checking whether a file is up-to-date, also check for the presence of a
.stamp file, and if it exists, use the highest timestamp between the stamp
file and the actual file.
Now the test scenario becomes:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk, but it creates a registrar.m.stamp
file to indicate the point in time when registrar.m was considered up-to-
date.
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is *older* than
registrar.m.stamp's timestamp and doesn't run again.
We only use the stamp file for source code (registrar.[m|h], main.[m|h],
pinvokes.[m|h]), since using it every time has too much potential for running
into other problems (for instance we should never create .stamp files inside
the .app).
Fixes these test failures:
1) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single","",False,System.String[])
single
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory371/testApp.app/testApp is modified, timestamp: 2/15/2018 3:04:11 PM > 2/15/2018 3:04:09 PM" >
2) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("dual","armv7,arm64",False,System.String[])
dual
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory375/testApp.app/testApp is modified, timestamp: 2/15/2018 3:06:03 PM > 2/15/2018 3:06:00 PM" >
3) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("llvm","armv7+llvm",False,System.String[])
llvm
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory379/testApp.app/testApp is modified, timestamp: 2/15/2018 3:07:14 PM > 2/15/2018 3:07:12 PM" >
4) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("debug","",True,System.String[])
debug
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory383/testApp.app/testApp is modified, timestamp: 2/15/2018 3:08:16 PM > 2/15/2018 3:08:13 PM" >
5) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single-framework","",False,System.String[])
single-framework
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory387/testApp.app/testApp is modified, timestamp: 2/15/2018 3:09:18 PM > 2/15/2018 3:09:16 PM" >
Fixes https://github.com/xamarin/maccore/issues/641
2018-02-20 13:43:23 +03:00
// Checks if the source file has a time stamp later than the target file.
/ /
// Optionally check if the contents of the files are different after checking the timestamp.
/ /
// If check_stamp is true, the function will use the timestamp of a "target".stamp file
// if it's later than the timestamp of the "target" file itself.
public static bool IsUptodate ( string source , string target , bool check_contents = false , bool check_stamp = true )
2016-04-21 15:57:02 +03:00
{
if ( Driver . Force )
return false ;
var tfi = new FileInfo ( target ) ;
if ( ! tfi . Exists ) {
Driver . Log ( 3 , "Target '{0}' does not exist." , target ) ;
return false ;
}
[mtouch/mmp] Fix tracking of whether the static registrar should run again or not. Fixes #641. (#3534) (#3536)
* [tests] Improve debug spew for the RebuildTest_WithExtensions test.
* [mtouch/mmp] Store/load if the dynamic registrar is removed or not into the cached link results.
Store/load if the dynamic registrar is removed or not into the cached link
results, so that we generate the correct main.m even if cached linker results
are used.
* [mtouch/mmp] The static registrar must not execute if we're loading cached results from the linker.
The static registrar must not execute if we're loading cached results from the
linker, because the static registrar needs information from the linker that's
not restored from the cache.
* [mtouch/mmp] Share Touch code.
* [mtouch/mmp] Make it possible to touch inexistent files (to create them).
* [mtouch/mmp] Fix tracking of whether the static registrar should run again or not.
The recent changes to support optimizing away the dynamic registrar caused the
Xamarin.MTouch.RebuildTest_WithExtensions test to regress.
The problem
-----------
* The linker now collects and stores information the static registrar needs.
* This information is not restored from disk when the linker realizes that it
can reload previously linked assemblies instead of executing again.
* The static registrar runs again (for another reason).
* The information the static registrar needs isn't available, and incorrect
output follows.
So fix 1: show an error if the static registrar runs when the linker loaded
cached results.
The exact scenario the test ran into is this:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk (this is an optimization to avoid
compiling the registrar.m file again unless needed).
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is newer than
registrar.m's timestamp and run again, but doesn't produce the right result
because it doesn't have the information it needs.
Considered solutions
--------------------
1. Only track timestamps, not file contents. This is not ideal, since it will
result in more work done: in particular for the case above, it would add a
registrar.m compilation in build #2, and linker rerun + static registrar
rerun + registrar.m compilation + final native link in build #3.
2. Always write the output of the static registrar, even if it hasn't changed.
This is not ideal either, since it will also result in more work done: for
the case above, it would add a registrar.m compilation + final native link
in build #3.
3. Always write the output of the static registrar, but track if it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it.
This only means the final native link in build #3 is added (see #5 for why
this is worse than it sounds).
4. Always write the output of the static registrar, but track it it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it,
and track that too, so that the final native link in build #3 isn't needed
anymore. Unfortunately this may result in incorrect behavior, because now
the msbuild tasks will detect that the executable has changed, and may run
dsymutil + strip again. The executable didn't actually change, which means
it would be the previously stripped executable, and thus we'd end up with
an empty .dSYM because we ran dsymtil on an already stripped executable.
5. Idea #4, but write the output of the final link into a temporary directory
instead of the .app, so that we could track whether we should update the
executable in the .app or not. This is not optimal either, because
executables can be *big* (I've seen multi-GB tvOS bitcode executables), and
extra copies of such files should not be taken lightly.
6. Idea #4, but tell the MSBuild tasks that dsymutil/strip doesn't need to be
rerun even if the timestamp of the executable changed. This might actually
work, but now the solution's become quite complex.
Implemented solution
--------------------
Use stamp files to detect whether a file is up-to-date or not.
In particular:
* When we don't write to a file because the new contents are identical to the
old contents, we now touch a .stamp file. This stamp file means "the
accompanying file was determined to be up-to-date when the stamp was
touched."
* When checking whether a file is up-to-date, also check for the presence of a
.stamp file, and if it exists, use the highest timestamp between the stamp
file and the actual file.
Now the test scenario becomes:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk, but it creates a registrar.m.stamp
file to indicate the point in time when registrar.m was considered up-to-
date.
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is *older* than
registrar.m.stamp's timestamp and doesn't run again.
We only use the stamp file for source code (registrar.[m|h], main.[m|h],
pinvokes.[m|h]), since using it every time has too much potential for running
into other problems (for instance we should never create .stamp files inside
the .app).
Fixes these test failures:
1) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single","",False,System.String[])
single
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory371/testApp.app/testApp is modified, timestamp: 2/15/2018 3:04:11 PM > 2/15/2018 3:04:09 PM" >
2) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("dual","armv7,arm64",False,System.String[])
dual
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory375/testApp.app/testApp is modified, timestamp: 2/15/2018 3:06:03 PM > 2/15/2018 3:06:00 PM" >
3) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("llvm","armv7+llvm",False,System.String[])
llvm
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory379/testApp.app/testApp is modified, timestamp: 2/15/2018 3:07:14 PM > 2/15/2018 3:07:12 PM" >
4) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("debug","",True,System.String[])
debug
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory383/testApp.app/testApp is modified, timestamp: 2/15/2018 3:08:16 PM > 2/15/2018 3:08:13 PM" >
5) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single-framework","",False,System.String[])
single-framework
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory387/testApp.app/testApp is modified, timestamp: 2/15/2018 3:09:18 PM > 2/15/2018 3:09:16 PM" >
Fixes https://github.com/xamarin/maccore/issues/641
2018-02-20 13:43:23 +03:00
if ( check_stamp ) {
var tfi_stamp = new FileInfo ( target + ".stamp" ) ;
if ( tfi_stamp . Exists & & tfi_stamp . LastWriteTimeUtc > tfi . LastWriteTimeUtc ) {
Driver . Log ( 3 , "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp" , target , tfi_stamp . LastWriteTimeUtc , tfi . LastWriteTimeUtc ) ;
tfi = tfi_stamp ;
}
}
2016-04-21 15:57:02 +03:00
var sfi = new FileInfo ( source ) ;
if ( sfi . LastWriteTimeUtc < = tfi . LastWriteTimeUtc ) {
Driver . Log ( 3 , "Prerequisite '{0}' is older than the target '{1}'." , source , target ) ;
return true ;
}
2016-11-14 16:50:08 +03:00
if ( check_contents & & Cache . CompareFiles ( source , target ) ) {
Driver . Log ( 3 , "Prerequisite '{0}' is newer than the target '{1}', but the contents are identical." , source , target ) ;
return true ;
}
Driver . Log ( 3 , "Prerequisite '{0}' is newer than the target '{1}'." , source , target ) ;
return false ;
2016-04-21 15:57:02 +03:00
}
public static void RemoveResource ( ModuleDefinition module , string name )
{
for ( int i = 0 ; i < module . Resources . Count ; i + + ) {
EmbeddedResource embedded = module . Resources [ i ] as EmbeddedResource ;
if ( embedded = = null | | embedded . Name ! = name )
continue ;
module . Resources . RemoveAt ( i ) ;
break ;
}
}
public static void SaveAssembly ( AssemblyDefinition assembly , string destination )
{
2017-02-09 04:50:29 +03:00
var main = assembly . MainModule ;
bool symbols = main . HasSymbols ;
2016-04-21 15:57:02 +03:00
if ( symbols ) {
2017-04-13 00:38:54 +03:00
var provider = new DefaultSymbolReaderProvider ( ) ;
2017-02-09 04:50:29 +03:00
main . ReadSymbols ( provider . GetSymbolReader ( main , main . FileName ) ) ;
}
var wp = new WriterParameters ( ) { WriteSymbols = symbols } ;
// re-write symbols, if available, so the new tokens will match
assembly . Write ( destination , wp ) ;
if ( ! symbols ) {
2016-04-21 15:57:02 +03:00
// if we're not saving the symbols then we must not leave stale/old files to be used by other tools
string dest_mdb = destination + ".mdb" ;
if ( File . Exists ( dest_mdb ) )
File . Delete ( dest_mdb ) ;
2017-04-13 00:38:54 +03:00
string dest_pdb = Path . ChangeExtension ( destination , ".pdb" ) ;
if ( File . Exists ( dest_pdb ) )
File . Delete ( dest_pdb ) ;
2016-04-21 15:57:02 +03:00
}
}
public static void ExtractResource ( ModuleDefinition module , string name , string path , bool remove )
{
for ( int i = 0 ; i < module . Resources . Count ; i + + ) {
EmbeddedResource embedded = module . Resources [ i ] as EmbeddedResource ;
if ( embedded = = null | | embedded . Name ! = name )
continue ;
string dirname = Path . GetDirectoryName ( path ) ;
if ( ! Directory . Exists ( dirname ) )
Directory . CreateDirectory ( dirname ) ;
using ( Stream ostream = File . OpenWrite ( path ) ) {
embedded . GetResourceStream ( ) . CopyTo ( ostream ) ;
}
if ( remove )
module . Resources . RemoveAt ( i ) ;
break ;
}
}
enum CopyFileFlags : uint {
ACL = 1 < < 0 ,
Stat = 1 < < 1 ,
Xattr = 1 < < 2 ,
Data = 1 < < 3 ,
Security = Stat | ACL ,
Metadata = Security | Xattr ,
All = Metadata | Data ,
Recursive = 1 < < 15 ,
NoFollow_Src = 1 < < 18 ,
NoFollow_Dst = 1 < < 19 ,
Unlink = 1 < < 21 ,
Nofollow = NoFollow_Src | NoFollow_Dst ,
}
enum CopyFileState : uint {
StatusCB = 6 ,
}
enum CopyFileStep {
Start = 1 ,
Finish = 2 ,
Err = 3 ,
Progress = 4 ,
}
enum CopyFileResult {
Continue = 0 ,
Skip = 1 ,
Quit = 2 ,
}
enum CopyFileWhat {
Error = 0 ,
File = 1 ,
Dir = 2 ,
DirCleanup = 3 ,
CopyData = 4 ,
CopyXattr = 5 ,
}
[DllImport (Constants.libSystemLibrary)]
static extern IntPtr copyfile_state_alloc ( ) ;
[DllImport (Constants.libSystemLibrary)]
static extern int copyfile_state_free ( IntPtr state ) ;
[DllImport (Constants.libSystemLibrary)]
static extern int copyfile_state_set ( IntPtr state , CopyFileState flag , IntPtr value ) ;
delegate CopyFileResult CopyFileCallbackDelegate ( CopyFileWhat what , CopyFileStep stage , IntPtr state , string src , string dst , IntPtr ctx ) ;
[DllImport (Constants.libSystemLibrary, SetLastError = true)]
static extern int copyfile ( string @from , string @to , IntPtr state , CopyFileFlags flags ) ;
public static void UpdateDirectory ( string source , string target )
{
if ( ! Directory . Exists ( target ) )
Directory . CreateDirectory ( target ) ;
// Mono's File.Copy can't handle symlinks (the symlinks are followed instead of copied),
// so we need to use native functions directly. Luckily OSX provides exactly what we need.
IntPtr state = copyfile_state_alloc ( ) ;
try {
CopyFileCallbackDelegate del = CopyFileCallback ;
copyfile_state_set ( state , CopyFileState . StatusCB , Marshal . GetFunctionPointerForDelegate ( del ) ) ;
int rv = copyfile ( source , target , state , CopyFileFlags . Data | CopyFileFlags . Recursive | CopyFileFlags . Nofollow ) ;
if ( rv ! = 0 )
throw ErrorHelper . CreateError ( 1022 , "Could not copy the directory '{0}' to '{1}': {2}" , source , target , Target . strerror ( Marshal . GetLastWin32Error ( ) ) ) ;
} finally {
copyfile_state_free ( state ) ;
}
}
static CopyFileResult CopyFileCallback ( CopyFileWhat what , CopyFileStep stage , IntPtr state , string source , string target , IntPtr ctx )
{
// Console.WriteLine ("CopyFileCallback ({0}, {1}, 0x{2}, {3}, {4}, 0x{5})", what, stage, state.ToString ("x"), source, target, ctx.ToString ("x"));
switch ( what ) {
case CopyFileWhat . File :
if ( ! IsUptodate ( source , target ) ) {
if ( stage = = CopyFileStep . Finish )
Driver . Log ( 1 , "Copied {0} to {1}" , source , target ) ;
return CopyFileResult . Continue ;
} else {
Driver . Log ( 3 , "Target '{0}' is up-to-date" , target ) ;
return CopyFileResult . Skip ;
}
case CopyFileWhat . Dir :
case CopyFileWhat . DirCleanup :
case CopyFileWhat . CopyData :
case CopyFileWhat . CopyXattr :
return CopyFileResult . Continue ;
case CopyFileWhat . Error :
throw ErrorHelper . CreateError ( 1021 , "Could not copy the file '{0}' to '{1}': {2}" , source , target , Target . strerror ( Marshal . GetLastWin32Error ( ) ) ) ;
default :
return CopyFileResult . Continue ;
}
}
2016-11-14 16:50:08 +03:00
public static void UpdateFile ( string source , string target , bool check_contents = false )
2016-04-21 15:57:02 +03:00
{
2016-11-14 16:50:08 +03:00
if ( ! Application . IsUptodate ( source , target , check_contents ) )
2016-04-21 15:57:02 +03:00
CopyFile ( source , target ) ;
else
Driver . Log ( 3 , "Target '{0}' is up-to-date" , target ) ;
}
// Checks if any of the source files have a time stamp later than any of the target files.
[mtouch/mmp] Fix tracking of whether the static registrar should run again or not. Fixes #641. (#3534) (#3536)
* [tests] Improve debug spew for the RebuildTest_WithExtensions test.
* [mtouch/mmp] Store/load if the dynamic registrar is removed or not into the cached link results.
Store/load if the dynamic registrar is removed or not into the cached link
results, so that we generate the correct main.m even if cached linker results
are used.
* [mtouch/mmp] The static registrar must not execute if we're loading cached results from the linker.
The static registrar must not execute if we're loading cached results from the
linker, because the static registrar needs information from the linker that's
not restored from the cache.
* [mtouch/mmp] Share Touch code.
* [mtouch/mmp] Make it possible to touch inexistent files (to create them).
* [mtouch/mmp] Fix tracking of whether the static registrar should run again or not.
The recent changes to support optimizing away the dynamic registrar caused the
Xamarin.MTouch.RebuildTest_WithExtensions test to regress.
The problem
-----------
* The linker now collects and stores information the static registrar needs.
* This information is not restored from disk when the linker realizes that it
can reload previously linked assemblies instead of executing again.
* The static registrar runs again (for another reason).
* The information the static registrar needs isn't available, and incorrect
output follows.
So fix 1: show an error if the static registrar runs when the linker loaded
cached results.
The exact scenario the test ran into is this:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk (this is an optimization to avoid
compiling the registrar.m file again unless needed).
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is newer than
registrar.m's timestamp and run again, but doesn't produce the right result
because it doesn't have the information it needs.
Considered solutions
--------------------
1. Only track timestamps, not file contents. This is not ideal, since it will
result in more work done: in particular for the case above, it would add a
registrar.m compilation in build #2, and linker rerun + static registrar
rerun + registrar.m compilation + final native link in build #3.
2. Always write the output of the static registrar, even if it hasn't changed.
This is not ideal either, since it will also result in more work done: for
the case above, it would add a registrar.m compilation + final native link
in build #3.
3. Always write the output of the static registrar, but track if it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it.
This only means the final native link in build #3 is added (see #5 for why
this is worse than it sounds).
4. Always write the output of the static registrar, but track it it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it,
and track that too, so that the final native link in build #3 isn't needed
anymore. Unfortunately this may result in incorrect behavior, because now
the msbuild tasks will detect that the executable has changed, and may run
dsymutil + strip again. The executable didn't actually change, which means
it would be the previously stripped executable, and thus we'd end up with
an empty .dSYM because we ran dsymtil on an already stripped executable.
5. Idea #4, but write the output of the final link into a temporary directory
instead of the .app, so that we could track whether we should update the
executable in the .app or not. This is not optimal either, because
executables can be *big* (I've seen multi-GB tvOS bitcode executables), and
extra copies of such files should not be taken lightly.
6. Idea #4, but tell the MSBuild tasks that dsymutil/strip doesn't need to be
rerun even if the timestamp of the executable changed. This might actually
work, but now the solution's become quite complex.
Implemented solution
--------------------
Use stamp files to detect whether a file is up-to-date or not.
In particular:
* When we don't write to a file because the new contents are identical to the
old contents, we now touch a .stamp file. This stamp file means "the
accompanying file was determined to be up-to-date when the stamp was
touched."
* When checking whether a file is up-to-date, also check for the presence of a
.stamp file, and if it exists, use the highest timestamp between the stamp
file and the actual file.
Now the test scenario becomes:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk, but it creates a registrar.m.stamp
file to indicate the point in time when registrar.m was considered up-to-
date.
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is *older* than
registrar.m.stamp's timestamp and doesn't run again.
We only use the stamp file for source code (registrar.[m|h], main.[m|h],
pinvokes.[m|h]), since using it every time has too much potential for running
into other problems (for instance we should never create .stamp files inside
the .app).
Fixes these test failures:
1) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single","",False,System.String[])
single
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory371/testApp.app/testApp is modified, timestamp: 2/15/2018 3:04:11 PM > 2/15/2018 3:04:09 PM" >
2) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("dual","armv7,arm64",False,System.String[])
dual
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory375/testApp.app/testApp is modified, timestamp: 2/15/2018 3:06:03 PM > 2/15/2018 3:06:00 PM" >
3) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("llvm","armv7+llvm",False,System.String[])
llvm
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory379/testApp.app/testApp is modified, timestamp: 2/15/2018 3:07:14 PM > 2/15/2018 3:07:12 PM" >
4) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("debug","",True,System.String[])
debug
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory383/testApp.app/testApp is modified, timestamp: 2/15/2018 3:08:16 PM > 2/15/2018 3:08:13 PM" >
5) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single-framework","",False,System.String[])
single-framework
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory387/testApp.app/testApp is modified, timestamp: 2/15/2018 3:09:18 PM > 2/15/2018 3:09:16 PM" >
Fixes https://github.com/xamarin/maccore/issues/641
2018-02-20 13:43:23 +03:00
/ /
// If check_stamp is true, the function will use the timestamp of a "target".stamp file
// if it's later than the timestamp of the "target" file itself.
public static bool IsUptodate ( IEnumerable < string > sources , IEnumerable < string > targets , bool check_stamp = true )
2016-04-21 15:57:02 +03:00
{
if ( Driver . Force )
return false ;
DateTime max_source = DateTime . MinValue ;
string max_s = null ;
if ( sources . Count ( ) = = 0 | | targets . Count ( ) = = 0 )
ErrorHelper . Error ( 1013 , "Dependency tracking error: no files to compare. Please file a bug report at http://bugzilla.xamarin.com with a test case." ) ;
foreach ( var s in sources ) {
var sfi = new FileInfo ( s ) ;
if ( ! sfi . Exists ) {
Driver . Log ( 3 , "Prerequisite '{0}' does not exist." , s ) ;
return false ;
}
var st = sfi . LastWriteTimeUtc ;
if ( st > max_source ) {
max_source = st ;
max_s = s ;
}
}
foreach ( var t in targets ) {
var tfi = new FileInfo ( t ) ;
if ( ! tfi . Exists ) {
Driver . Log ( 3 , "Target '{0}' does not exist." , t ) ;
return false ;
}
[mtouch/mmp] Fix tracking of whether the static registrar should run again or not. Fixes #641. (#3534) (#3536)
* [tests] Improve debug spew for the RebuildTest_WithExtensions test.
* [mtouch/mmp] Store/load if the dynamic registrar is removed or not into the cached link results.
Store/load if the dynamic registrar is removed or not into the cached link
results, so that we generate the correct main.m even if cached linker results
are used.
* [mtouch/mmp] The static registrar must not execute if we're loading cached results from the linker.
The static registrar must not execute if we're loading cached results from the
linker, because the static registrar needs information from the linker that's
not restored from the cache.
* [mtouch/mmp] Share Touch code.
* [mtouch/mmp] Make it possible to touch inexistent files (to create them).
* [mtouch/mmp] Fix tracking of whether the static registrar should run again or not.
The recent changes to support optimizing away the dynamic registrar caused the
Xamarin.MTouch.RebuildTest_WithExtensions test to regress.
The problem
-----------
* The linker now collects and stores information the static registrar needs.
* This information is not restored from disk when the linker realizes that it
can reload previously linked assemblies instead of executing again.
* The static registrar runs again (for another reason).
* The information the static registrar needs isn't available, and incorrect
output follows.
So fix 1: show an error if the static registrar runs when the linker loaded
cached results.
The exact scenario the test ran into is this:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk (this is an optimization to avoid
compiling the registrar.m file again unless needed).
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is newer than
registrar.m's timestamp and run again, but doesn't produce the right result
because it doesn't have the information it needs.
Considered solutions
--------------------
1. Only track timestamps, not file contents. This is not ideal, since it will
result in more work done: in particular for the case above, it would add a
registrar.m compilation in build #2, and linker rerun + static registrar
rerun + registrar.m compilation + final native link in build #3.
2. Always write the output of the static registrar, even if it hasn't changed.
This is not ideal either, since it will also result in more work done: for
the case above, it would add a registrar.m compilation + final native link
in build #3.
3. Always write the output of the static registrar, but track if it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it.
This only means the final native link in build #3 is added (see #5 for why
this is worse than it sounds).
4. Always write the output of the static registrar, but track it it changed or
not, and if it didn't, just touch registrar.o instead of recompiling it,
and track that too, so that the final native link in build #3 isn't needed
anymore. Unfortunately this may result in incorrect behavior, because now
the msbuild tasks will detect that the executable has changed, and may run
dsymutil + strip again. The executable didn't actually change, which means
it would be the previously stripped executable, and thus we'd end up with
an empty .dSYM because we ran dsymtil on an already stripped executable.
5. Idea #4, but write the output of the final link into a temporary directory
instead of the .app, so that we could track whether we should update the
executable in the .app or not. This is not optimal either, because
executables can be *big* (I've seen multi-GB tvOS bitcode executables), and
extra copies of such files should not be taken lightly.
6. Idea #4, but tell the MSBuild tasks that dsymutil/strip doesn't need to be
rerun even if the timestamp of the executable changed. This might actually
work, but now the solution's become quite complex.
Implemented solution
--------------------
Use stamp files to detect whether a file is up-to-date or not.
In particular:
* When we don't write to a file because the new contents are identical to the
old contents, we now touch a .stamp file. This stamp file means "the
accompanying file was determined to be up-to-date when the stamp was
touched."
* When checking whether a file is up-to-date, also check for the presence of a
.stamp file, and if it exists, use the highest timestamp between the stamp
file and the actual file.
Now the test scenario becomes:
* 1st build: everything is new and everything is built.
* 2nd build: contents of .exe changes, the linker runs again, the static
registrar runs again, but sees that the generated output didn't change, so
it doesn't write the new content to disk, but it creates a registrar.m.stamp
file to indicate the point in time when registrar.m was considered up-to-
date.
* 3rd build: only the .exe timestamp changes, the linker sees nothing changes
in the contents of the .exe and loads the previously linked assemblies from
disk, the static registrar sees that the .exe's timestamp is *older* than
registrar.m.stamp's timestamp and doesn't run again.
We only use the stamp file for source code (registrar.[m|h], main.[m|h],
pinvokes.[m|h]), since using it every time has too much potential for running
into other problems (for instance we should never create .stamp files inside
the .app).
Fixes these test failures:
1) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single","",False,System.String[])
single
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory371/testApp.app/testApp is modified, timestamp: 2/15/2018 3:04:11 PM > 2/15/2018 3:04:09 PM" >
2) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("dual","armv7,arm64",False,System.String[])
dual
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory375/testApp.app/testApp is modified, timestamp: 2/15/2018 3:06:03 PM > 2/15/2018 3:06:00 PM" >
3) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("llvm","armv7+llvm",False,System.String[])
llvm
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory379/testApp.app/testApp is modified, timestamp: 2/15/2018 3:07:14 PM > 2/15/2018 3:07:12 PM" >
4) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("debug","",True,System.String[])
debug
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory383/testApp.app/testApp is modified, timestamp: 2/15/2018 3:08:16 PM > 2/15/2018 3:08:13 PM" >
5) Failed : Xamarin.MTouch.RebuildTest_WithExtensions("single-framework","",False,System.String[])
single-framework
Expected: <empty>
But was: < "/Users/builder/data/lanes/5746/4123bf7e/source/xamarin-macios/tests/mtouch/bin/Debug/tmp-test-dir/Xamarin.Tests.BundlerTool.CreateTemporaryDirectory387/testApp.app/testApp is modified, timestamp: 2/15/2018 3:09:18 PM > 2/15/2018 3:09:16 PM" >
Fixes https://github.com/xamarin/maccore/issues/641
2018-02-20 13:43:23 +03:00
if ( check_stamp ) {
var tfi_stamp = new FileInfo ( t + ".stamp" ) ;
if ( tfi_stamp . Exists & & tfi_stamp . LastWriteTimeUtc > tfi . LastWriteTimeUtc ) {
Driver . Log ( 3 , "Target '{0}' has a stamp file with newer timestamp ({1} > {2}), using the stamp file's timestamp" , t , tfi_stamp . LastWriteTimeUtc , tfi . LastWriteTimeUtc ) ;
tfi = tfi_stamp ;
}
}
2016-04-21 15:57:02 +03:00
var lwt = tfi . LastWriteTimeUtc ;
if ( max_source > lwt ) {
Driver . Log ( 3 , "Prerequisite '{0}' is newer than target '{1}' ({2} vs {3})." , max_s , t , max_source , lwt ) ;
return false ;
}
}
Driver . Log ( 3 , "Prerequisite(s) '{0}' are all older than the target(s) '{1}'." , string . Join ( "', '" , sources . ToArray ( ) ) , string . Join ( "', '" , targets . ToArray ( ) ) ) ;
return true ;
}
[DllImport (Constants.libSystemLibrary)]
static extern int readlink ( string path , IntPtr buf , int len ) ;
// A file copy that will replace symlinks with the source file
// File.Copy will copy the source to the target of the symlink instead
// of replacing the symlink.
public static void CopyFile ( string source , string target )
{
if ( readlink ( target , IntPtr . Zero , 0 ) ! = - 1 ) {
// Target is a symlink, delete it.
File . Delete ( target ) ;
} else if ( File . Exists ( target ) ) {
// Also delete the target file if it already exists,
// since it may not have write permissions.
File . Delete ( target ) ;
}
var dir = Path . GetDirectoryName ( target ) ;
if ( ! Directory . Exists ( dir ) )
Directory . CreateDirectory ( dir ) ;
File . Copy ( source , target , true ) ;
// Make sure the target file is r/w.
var attrs = File . GetAttributes ( target ) ;
if ( ( attrs & FileAttributes . ReadOnly ) = = FileAttributes . ReadOnly )
File . SetAttributes ( target , attrs & ~ FileAttributes . ReadOnly ) ;
Driver . Log ( 1 , "Copied {0} to {1}" , source , target ) ;
}
public static void TryDelete ( string path )
{
try {
if ( File . Exists ( path ) )
File . Delete ( path ) ;
} catch {
}
}
2016-05-11 13:24:55 +03:00
public void InitializeCommon ( )
{
2018-03-05 09:41:28 +03:00
Namespaces . Initialize ( ) ;
2018-02-12 16:42:19 +03:00
SelectRegistrar ( ) ;
2017-09-29 20:45:53 +03:00
if ( RequiresXcodeHeaders & & SdkVersion < SdkVersions . GetVersion ( Platform ) ) {
throw ErrorHelper . CreateError ( 91 , "This version of {0} requires the {1} {2} SDK (shipped with Xcode {3}). Either upgrade Xcode to get the required header files or {4} (to try to avoid the new APIs)." , ProductName , PlatformName , SdkVersions . GetVersion ( Platform ) , SdkVersions . Xcode , Error91LinkerSuggestion ) ;
}
2018-01-04 23:19:44 +03:00
if ( DeploymentTarget ! = null ) {
if ( DeploymentTarget < Xamarin . SdkVersions . GetMinVersion ( Platform ) )
throw new PlatformException ( 73 , true , "{4} {0} does not support a deployment target of {1} for {3} (the minimum is {2}). Please select a newer deployment target in your project's Info.plist." , Constants . Version , DeploymentTarget , Xamarin . SdkVersions . GetMinVersion ( Platform ) , PlatformName , ProductName ) ;
if ( DeploymentTarget > Xamarin . SdkVersions . GetVersion ( Platform ) )
throw new PlatformException ( 74 , true , "{4} {0} does not support a deployment target of {1} for {3} (the maximum is {2}). Please select an older deployment target in your project's Info.plist or upgrade to a newer version of {4}." , Constants . Version , DeploymentTarget , Xamarin . SdkVersions . GetVersion ( Platform ) , PlatformName , ProductName ) ;
}
2016-05-11 13:24:55 +03:00
if ( Platform = = ApplePlatform . WatchOS & & EnableCoopGC . HasValue & & ! EnableCoopGC . Value )
2016-05-17 12:17:47 +03:00
throw ErrorHelper . CreateError ( 88 , "The GC must be in cooperative mode for watchOS apps. Please remove the --coop:false argument to mtouch." ) ;
2016-05-11 13:24:55 +03:00
if ( ! EnableCoopGC . HasValue )
EnableCoopGC = Platform = = ApplePlatform . WatchOS ;
2016-05-11 13:47:26 +03:00
if ( EnableCoopGC . Value ) {
switch ( MarshalObjectiveCExceptions ) {
case MarshalObjectiveCExceptionMode . UnwindManagedCode :
case MarshalObjectiveCExceptionMode . Disable :
2016-05-17 12:17:47 +03:00
throw ErrorHelper . CreateError ( 89 , "The option '{0}' cannot take the value '{1}' when cooperative mode is enabled for the GC." , "--marshal-objectivec-exceptions" , MarshalObjectiveCExceptions . ToString ( ) . ToLowerInvariant ( ) ) ;
2016-05-11 13:47:26 +03:00
}
switch ( MarshalManagedExceptions ) {
case MarshalManagedExceptionMode . UnwindNativeCode :
case MarshalManagedExceptionMode . Disable :
2016-05-17 12:17:47 +03:00
throw ErrorHelper . CreateError ( 89 , "The option '{0}' cannot take the value '{1}' when cooperative mode is enabled for the GC." , "--marshal-managed-exceptions" , MarshalManagedExceptions . ToString ( ) . ToLowerInvariant ( ) ) ;
2016-05-11 13:47:26 +03:00
}
}
bool isSimulatorOrDesktopDebug = EnableDebug ;
#if MTOUCH
isSimulatorOrDesktopDebug & = IsSimulatorBuild ;
#endif
if ( MarshalObjectiveCExceptions = = MarshalObjectiveCExceptionMode . Default ) {
if ( EnableCoopGC . Value ) {
MarshalObjectiveCExceptions = MarshalObjectiveCExceptionMode . ThrowManagedException ;
} else {
MarshalObjectiveCExceptions = isSimulatorOrDesktopDebug ? MarshalObjectiveCExceptionMode . UnwindManagedCode : MarshalObjectiveCExceptionMode . Disable ;
}
}
if ( MarshalManagedExceptions = = MarshalManagedExceptionMode . Default ) {
if ( EnableCoopGC . Value ) {
MarshalManagedExceptions = MarshalManagedExceptionMode . ThrowObjectiveCException ;
} else {
MarshalManagedExceptions = isSimulatorOrDesktopDebug ? MarshalManagedExceptionMode . UnwindNativeCode : MarshalManagedExceptionMode . Disable ;
}
2016-10-28 20:07:01 +03:00
IsDefaultMarshalManagedExceptionMode = true ;
2016-05-11 13:47:26 +03:00
}
[mtouch] Improve how we make sure native symbols aren't stripped away. Fixes #51710 and #54417. (#2162)
* [mtouch] Improve how we make sure native symbols aren't stripped away. Fixes #51710 and #54417.
* Refactor required symbol collection to store more information about each
symbol (field, function, Objective-C class), and in general make the code
more straight forward.
* Implement support for generating source code that references these symbols,
and do this whenever we can't ask the native linker to keep these symbols
(when using bitcode). Additionally make it possible to do this manually, so
that the source code can be generated for non-bitcode platforms too (which
is useful if the number of symbols is enormous, in which case we might
surpass the maximum command-line length).
* Also make it possible to completely ignore native symbols, or ignore them on
a per-symbol basis. This provides a fallback for users if we get something
right and we try to preserve something that shouldn't be preserved (for
instance if it doesn't exist), and the user ends up with unfixable linker
errors.
* Don't collect Objective-C classes unless they're in an assembly with
LinkWith attributes. We don't need to preserve Objective-C classes in any
other circumstances.
* Implement everything for both Xamarin.iOS and Xamarin.Mac, and share the
code between them.
* Remove previous workaround for bug #51710, since it's no longer needed.
* Add tests.
https://bugzilla.xamarin.com/show_bug.cgi?id=54417
https://bugzilla.xamarin.com/show_bug.cgi?id=51710
* [mtouch] Make sure to only keep symbols from the current app when code sharing.
This fixes a build problem with the interdependent-binding-projects test when
testing in Today Extension mode.
2017-06-02 19:29:19 +03:00
if ( SymbolMode = = SymbolMode . Default ) {
#if MONOTOUCH
SymbolMode = EnableBitCode ? SymbolMode . Code : SymbolMode . Linker ;
#else
SymbolMode = SymbolMode . Linker ;
#endif
}
#if MONOTOUCH
if ( EnableBitCode & & SymbolMode ! = SymbolMode . Code ) {
// This is a warning because:
// * The user will get a linker error anyway if they do this.
// * I see it as quite unlikely that anybody will in fact try this (it must be manually set in the additional mtouch arguments).
// * I find it more probable that Apple will remove the -u restriction, in which case someone might actually want to try this, and if it's a warning, we won't prevent it.
ErrorHelper . Warning ( 115 , "It is recommended to reference dynamic symbols using code (--dynamic-symbol-mode=code) when bitcode is enabled." ) ;
}
#endif
[mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp. (#3242)
* [mtouch/mmp] Give users more control over optimizations, and share more code between mtouch and mmp.
1. Add an --optimize flag to mtouch/mmp that allows users to select which
optimizations to apply (or not). This makes it easier to add future
optimizations, and allow users to disable any optimization that causes
problems without having to disable many other features.
2. Share as much optimization code as possible between mtouch and mmp. This
immediately gives a benefit to mmp, which has three new optimizations only
mtouch had: NSObject.IsDirectBinding inlining, IntPtr.Size inlining and
dead code elimination.
This results in ~6kb of disk space saved for a linked Xamarin.Mac app:
* link sdk: [Debug][1], [Release][2]
* link all: [Debug][3], [Release][4]
Testing also verifies that monotouchtest ([Debug][5], [Release][6]) has not
changed size at all, which means that no default optimizations have changed
inadvertedly.
[1]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--debug
[2]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-sdk-mac--release
[3]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--debug
[4]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#link-all-mac--release
[5]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonedebug64
[6]: https://gist.github.com/rolfbjarne/6b731e3b5ca6170355662e6505c3d492#monotouchtest-iphonerelease64
* [tools] Don't enable the IsDirectBinding optimization by default for Xamarin.Mac apps, it's not safe.
* Fix whitespace issues.
* [doc] Document optimizations.
* Officially support optimizations by adding them to the Versions.plist.
* [linker] Improve IntPtr.Size inliner + dead code eliminatior and add tests.
* Properly handle operands for the ldc_i4_s instruction (they're sbyte).
* Fix less-than condition to actually do a less-than comparison.
* Make sure to look up the bitness in the Target, not the Application, since
the Application's value will be incorrect when building fat apps (both
Is32Build and Is64Build will be true).
* Remove unnecessary checks for the IntPtr.Size inliner: this optimization
does not depend on other instructions than the IntPtr.get_Size call, so
remove the checks that verify surrounding instructions. This makes the
IntPtr.Size inliner kick in in more scenarios (such as the new tests).
* Add tests.
* [tests] Add mmp tests for optimizations.
* [tests] Fix XM optimization tests.
* [tests] Fix test build error.
2018-01-23 13:33:48 +03:00
Optimizations . Initialize ( this ) ;
2016-05-11 13:24:55 +03:00
}
2016-12-01 19:18:30 +03:00
public void RunRegistrar ( )
{
// The static registrar.
if ( Registrar ! = RegistrarMode . Static )
throw new PlatformException ( 67 , "Invalid registrar: {0}" , Registrar ) ; // this is only called during our own build
2018-01-26 03:57:44 +03:00
if ( RootAssemblies . Count < 1 )
throw ErrorHelper . CreateError ( 130 , "No root assemblies found. You should provide at least one root assembly." ) ;
2016-12-01 19:18:30 +03:00
2017-04-07 15:13:11 +03:00
var registrar_m = RegistrarOutputLibrary ;
var RootAssembly = RootAssemblies [ 0 ] ;
2018-01-26 03:57:44 +03:00
var resolvedAssemblies = new Dictionary < string , AssemblyDefinition > ( ) ;
2016-12-01 19:18:30 +03:00
var resolver = new PlatformResolver ( ) {
2016-12-23 20:50:35 +03:00
FrameworkDirectory = Driver . GetPlatformFrameworkDirectory ( this ) ,
2016-12-01 19:18:30 +03:00
RootDirectory = Path . GetDirectoryName ( RootAssembly ) ,
2018-01-26 03:57:44 +03:00
#if MMP
2018-03-05 21:29:52 +03:00
CommandLineAssemblies = RootAssemblies ,
2018-01-26 03:57:44 +03:00
#endif
2018-03-05 21:29:52 +03:00
} ;
2016-12-01 19:18:30 +03:00
2016-12-23 20:50:35 +03:00
if ( Platform = = ApplePlatform . iOS | | Platform = = ApplePlatform . MacOSX ) {
if ( Is32Build ) {
resolver . ArchDirectory = Driver . GetArch32Directory ( this ) ;
2016-12-01 19:18:30 +03:00
} else {
2016-12-23 20:50:35 +03:00
resolver . ArchDirectory = Driver . GetArch64Directory ( this ) ;
2016-12-01 19:18:30 +03:00
}
}
var ps = new ReaderParameters ( ) ;
ps . AssemblyResolver = resolver ;
2018-01-26 03:57:44 +03:00
resolvedAssemblies . Add ( "mscorlib" , ps . AssemblyResolver . Resolve ( AssemblyNameReference . Parse ( "mscorlib" ) , new ReaderParameters ( ) ) ) ;
var productAssembly = Driver . GetProductAssembly ( this ) ;
bool foundProductAssembly = false ;
foreach ( var asm in RootAssemblies ) {
var rootName = Path . GetFileNameWithoutExtension ( asm ) ;
if ( rootName = = productAssembly )
foundProductAssembly = true ;
try {
AssemblyDefinition lastAssembly = ps . AssemblyResolver . Resolve ( AssemblyNameReference . Parse ( rootName ) , new ReaderParameters ( ) ) ;
if ( lastAssembly = = null ) {
ErrorHelper . CreateWarning ( 7 , "The root assembly '{0}' does not exist" , rootName ) ;
continue ;
}
if ( resolvedAssemblies . TryGetValue ( rootName , out var previousAssembly ) ) {
if ( lastAssembly . MainModule . RuntimeVersion ! = previousAssembly . MainModule . RuntimeVersion ) {
Driver . Log ( 2 , "Attemping to load an assembly another time {0} (previous {1})" , lastAssembly . FullName , previousAssembly . FullName ) ;
}
continue ;
}
resolvedAssemblies . Add ( rootName , lastAssembly ) ;
Driver . Log ( 3 , "Loaded {0}" , lastAssembly . MainModule . FileName ) ;
} catch ( Exception ex ) {
ErrorHelper . Warning ( 9 , ex , "Error while loading assemblies: {0}: {1}" , rootName , ex . Message ) ;
continue ;
}
}
2016-12-01 19:18:30 +03:00
2018-01-26 03:57:44 +03:00
if ( ! foundProductAssembly )
throw ErrorHelper . CreateError ( 131 , "Product assembly '{0}' not found in assembly list: '{1}'" , productAssembly , string . Join ( "', '" , RootAssemblies . ToArray ( ) ) ) ;
2016-12-01 19:18:30 +03:00
#if MONOTOUCH
BuildTarget = BuildTarget . Simulator ;
#endif
2018-02-05 18:26:29 +03:00
var registrar = new Registrar . StaticRegistrar ( this ) ;
2018-01-26 03:57:44 +03:00
if ( RootAssemblies . Count = = 1 )
registrar . GenerateSingleAssembly ( resolvedAssemblies . Values , Path . ChangeExtension ( registrar_m , "h" ) , registrar_m , Path . GetFileNameWithoutExtension ( RootAssembly ) ) ;
else
registrar . Generate ( resolvedAssemblies . Values , Path . ChangeExtension ( registrar_m , "h" ) , registrar_m ) ;
2016-12-01 19:18:30 +03:00
}
2016-04-21 15:57:02 +03:00
}
}