xamarin-macios/tools/common/Application.cs

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

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;
using Mono.Cecil.Cil;
2016-04-21 15:57:02 +03:00
using Xamarin.Utils;
using XamCore.ObjCRuntime;
#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,
}
public partial class Application
{
public Cache Cache;
public string AppDirectory = ".";
2016-04-21 15:57:02 +03:00
public bool DeadStrip = true;
public bool EnableDebug;
internal RuntimeOptions RuntimeOptions;
public RegistrarMode Registrar = RegistrarMode.Default;
public RegistrarOptions RegistrarOptions = RegistrarOptions.Default;
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;
public bool? EnableCoopGC;
public bool EnableSGenConc;
public MarshalObjectiveCExceptionMode MarshalObjectiveCExceptions;
public MarshalManagedExceptionMode MarshalManagedExceptions;
public bool IsDefaultMarshalManagedExceptionMode;
public List<string> RootAssemblies = new List<string> ();
[mtouch] Implement support for sharing code between app extensions and container apps. Implement support for sharing both code and resources between app extensions and their container app: * AOT-compiled code. Each shared assembly is only AOT-compiled once, and if the assembly is built to a framework or dynamic library, it will also only be included once in the final app (as a framework or dynamic library in the container app, referenced directly by the app extension). If the assemblies are built to static objects there won't be any size improvements in the app, but the build will be much faster, because the assemblies will only be AOT- compiled once. * Any resources related to managed assemblies (debug files, config files, satellite assemblies) will be put in the container app only. Since these improvements are significant, code sharing will be enabled by default. Test results ============ For an extreme test project with 7 extensions (embedded-frameworks)[1]: with code sharing cycle 9 difference build time 1m 47s 3m 33s -1m 46s = ~50% faster app size 26 MB 131 MB -105 MB = ~80% smaller For a more normal test project (MyTabbedApplication)[2] - this is a simple application with 1 extension: with code sharing cycle 9 difference build time 0m 44s 0m 48s -4s = ~ 8% faster app size 23 MB 37 MB -15 MB = ~40% smaller Another tvOS app with one extension also show similar gains (MyTVApp)[3]: with code sharing cycle 9 difference build time 0m 22s 0m 48s -26s = ~54% faster app size 22 MB 62 MB -40 MB = ~65% smaller [1]: https://github.com/rolfbjarne/embedded-frameworks [2]: https://github.com/xamarin/xamarin-macios/tree/cycle9/msbuild/tests/MyTabbedApplication [3]: https://github.com/xamarin/xamarin-macios/tree/cycle9/msbuild/tests/MyTVApp
2017-01-24 13:10:20 +03:00
public List<Application> SharedCodeApps = new List<Application> (); // List of appexes we're sharing code with.
public string RegistrarOutputLibrary;
public static int Concurrency => Driver.Concurrency;
public Version DeploymentTarget;
public Version SdkVersion;
public Application (string[] arguments)
{
Cache = new Cache (arguments);
}
[mtouch] Implement support for sharing code between app extensions and container apps. Implement support for sharing both code and resources between app extensions and their container app: * AOT-compiled code. Each shared assembly is only AOT-compiled once, and if the assembly is built to a framework or dynamic library, it will also only be included once in the final app (as a framework or dynamic library in the container app, referenced directly by the app extension). If the assemblies are built to static objects there won't be any size improvements in the app, but the build will be much faster, because the assemblies will only be AOT- compiled once. * Any resources related to managed assemblies (debug files, config files, satellite assemblies) will be put in the container app only. Since these improvements are significant, code sharing will be enabled by default. Test results ============ For an extreme test project with 7 extensions (embedded-frameworks)[1]: with code sharing cycle 9 difference build time 1m 47s 3m 33s -1m 46s = ~50% faster app size 26 MB 131 MB -105 MB = ~80% smaller For a more normal test project (MyTabbedApplication)[2] - this is a simple application with 1 extension: with code sharing cycle 9 difference build time 0m 44s 0m 48s -4s = ~ 8% faster app size 23 MB 37 MB -15 MB = ~40% smaller Another tvOS app with one extension also show similar gains (MyTVApp)[3]: with code sharing cycle 9 difference build time 0m 22s 0m 48s -26s = ~54% faster app size 22 MB 62 MB -40 MB = ~65% smaller [1]: https://github.com/rolfbjarne/embedded-frameworks [2]: https://github.com/xamarin/xamarin-macios/tree/cycle9/msbuild/tests/MyTabbedApplication [3]: https://github.com/xamarin/xamarin-macios/tree/cycle9/msbuild/tests/MyTVApp
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); }
}
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 ();
}
}
}
public static bool IsUptodate (string source, string target, bool check_contents = false)
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;
}
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;
}
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)
{
var main = assembly.MainModule;
bool symbols = main.HasSymbols;
2016-04-21 15:57:02 +03:00
if (symbols) {
var provider = new DefaultSymbolReaderProvider ();
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);
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;
}
}
public static void UpdateFile (string source, string target, bool check_contents = false)
2016-04-21 15:57:02 +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.
public static bool IsUptodate (IEnumerable<string> sources, IEnumerable<string> targets)
{
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;
}
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 {
}
}
public void InitializeCommon ()
{
if (Platform == ApplePlatform.WatchOS && EnableCoopGC.HasValue && !EnableCoopGC.Value)
throw ErrorHelper.CreateError (88, "The GC must be in cooperative mode for watchOS apps. Please remove the --coop:false argument to mtouch.");
if (!EnableCoopGC.HasValue)
EnableCoopGC = Platform == ApplePlatform.WatchOS;
if (EnableCoopGC.Value) {
switch (MarshalObjectiveCExceptions) {
case MarshalObjectiveCExceptionMode.UnwindManagedCode:
case MarshalObjectiveCExceptionMode.Disable:
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 ());
}
switch (MarshalManagedExceptions) {
case MarshalManagedExceptionMode.UnwindNativeCode:
case MarshalManagedExceptionMode.Disable:
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 ());
}
}
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;
}
IsDefaultMarshalManagedExceptionMode = true;
}
}
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
if (RootAssemblies.Count != 1)
throw ErrorHelper.CreateError (8, "You should provide one root assembly only, found {0} assemblies: '{1}'", RootAssemblies.Count, string.Join ("', '", RootAssemblies.ToArray ()));
var registrar_m = RegistrarOutputLibrary;
var RootAssembly = RootAssemblies [0];
var resolvedAssemblies = new List<AssemblyDefinition> ();
var resolver = new PlatformResolver () {
FrameworkDirectory = Driver.GetPlatformFrameworkDirectory (this),
RootDirectory = Path.GetDirectoryName (RootAssembly),
};
if (Platform == ApplePlatform.iOS || Platform == ApplePlatform.MacOSX) {
if (Is32Build) {
resolver.ArchDirectory = Driver.GetArch32Directory (this);
} else {
resolver.ArchDirectory = Driver.GetArch64Directory (this);
}
}
var ps = new ReaderParameters ();
ps.AssemblyResolver = resolver;
2016-12-23 20:03:34 +03:00
resolvedAssemblies.Add (ps.AssemblyResolver.Resolve (AssemblyNameReference.Parse ("mscorlib"), new ReaderParameters ()));
var rootName = Path.GetFileNameWithoutExtension (RootAssembly);
if (rootName != Driver.GetProductAssembly (this))
2017-01-16 18:22:08 +03:00
throw ErrorHelper.CreateError (66, "Invalid build registrar assembly: {0}", RootAssembly);
2016-12-23 20:03:34 +03:00
resolvedAssemblies.Add (ps.AssemblyResolver.Resolve (AssemblyNameReference.Parse (rootName), new ReaderParameters ()));
Driver.Log (3, "Loaded {0}", resolvedAssemblies [resolvedAssemblies.Count - 1].MainModule.FileName);
#if MONOTOUCH
BuildTarget = BuildTarget.Simulator;
#endif
var registrar = new XamCore.Registrar.StaticRegistrar (this);
registrar.GenerateSingleAssembly (resolvedAssemblies, Path.ChangeExtension (registrar_m, "h"), registrar_m, Path.GetFileNameWithoutExtension (RootAssembly));
}
2016-04-21 15:57:02 +03:00
}
}