xamarin-macios/tools/mtouch/Application.cs

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

2016-04-21 15:58:45 +03:00
// Copyright 2013 Xamarin Inc. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
2016-04-21 15:58:45 +03:00
using System.IO;
using System.Text;
using MonoTouch.Tuner;
using Mono.Tuner;
using Xamarin.Linker;
using Xamarin.Utils;
using Xamarin.MacDev;
2016-04-21 15:58:45 +03:00
namespace Xamarin.Bundler {
public enum BitCodeMode {
None = 0,
ASMOnly = 1,
LLVMOnly = 2,
MarkerOnly = 3,
}
[Flags]
public enum Abi {
None = 0,
i386 = 1,
ARMv6 = 2,
ARMv7 = 4,
ARMv7s = 8,
ARM64 = 16,
x86_64 = 32,
Thumb = 64,
LLVM = 128,
ARMv7k = 256,
SimulatorArchMask = i386 | x86_64,
DeviceArchMask = ARMv6 | ARMv7 | ARMv7s | ARMv7k | ARM64,
ArchMask = SimulatorArchMask | DeviceArchMask,
Arch64Mask = x86_64 | ARM64,
Arch32Mask = i386 | ARMv6 | ARMv7 | ARMv7s | ARMv7k,
}
public static class AbiExtensions {
public static string AsString (this Abi self)
{
var rv = (self & Abi.ArchMask).ToString ();
if ((self & Abi.LLVM) == Abi.LLVM)
rv += "+LLVM";
if ((self & Abi.Thumb) == Abi.Thumb)
rv += "+Thumb";
return rv;
}
public static string AsArchString (this Abi self)
{
return (self & Abi.ArchMask).ToString ().ToLowerInvariant ();
}
}
public enum RegistrarMode {
Default,
Dynamic,
Static,
}
public enum BuildTarget {
Simulator,
Device,
}
public enum DlsymOptions
{
Default,
All,
None,
Custom,
}
public partial class Application
{
public string ExecutableName;
public BuildTarget BuildTarget;
public bool EnableCxx;
public bool EnableProfiling;
bool? package_mdb;
public bool PackageMdb {
get { return package_mdb.Value; }
set { package_mdb = value; }
}
bool? enable_msym;
public bool EnableMSym {
get { return enable_msym.Value; }
set { enable_msym = value; }
}
public bool EnableRepl;
public bool IsExtension;
public List<string> Extensions = new List<string> (); // A list of the extensions this app contains.
public List<Application> AppExtensions = new List<Application> ();
2016-04-21 15:58:45 +03:00
public bool FastDev;
public bool? EnablePie;
public bool NativeStrip = true;
public string SymbolList;
public bool ManagedStrip = true;
public List<string> NoSymbolStrip = new List<string> ();
public bool? ThreadCheck;
public DlsymOptions DlsymOptions;
public List<Tuple<string, bool>> DlsymAssemblies;
public bool? UseMonoFramework;
public bool? PackageMonoFramework;
public bool NoFastSim;
[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 bool NoDevCodeShare;
// The list of assemblies that we do generate debugging info for.
public bool DebugAll;
public List<string> DebugAssemblies = new List<string> ();
public bool? DebugTrack;
public string Compiler = string.Empty;
public string CompilerPath;
public string AotArguments = "static,asmonly,direct-icalls,";
public string AotOtherArguments = string.Empty;
public bool? LLVMAsmWriter;
public Dictionary<string, string> LLVMOptimizations = new Dictionary<string, string> ();
public Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string> ();
2016-04-21 15:58:45 +03:00
//
// Linker config
//
public bool LinkAway = true;
public bool LinkerDumpDependencies { get; set; }
public List<string> References = new List<string> ();
public bool? BuildDSym;
public bool Is32Build { get { return IsArchEnabled (Abi.Arch32Mask); } } // If we're targetting a 32 bit arch.
public bool Is64Build { get { return IsArchEnabled (Abi.Arch64Mask); } } // If we're targetting a 64 bit arch.
public bool IsDualBuild { get { return Is32Build && Is64Build; } } // if we're building both a 32 and a 64 bit version.
public bool IsLLVM { get { return IsArchEnabled (Abi.LLVM); } }
public List<Target> Targets = new List<Target> ();
public string UserGccFlags;
// If we didn't link the final executable because the existing binary is up-to-date.
bool cached_executable;
List<Abi> abis;
HashSet<Abi> all_architectures; // all Abis used in the app, including extensions.
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
BuildTasks build_tasks;
Dictionary<string, Tuple<AssemblyBuildTarget, string>> assembly_build_targets = new Dictionary<string, Tuple<AssemblyBuildTarget, string>> ();
public AssemblyBuildTarget LibMonoLinkMode = AssemblyBuildTarget.StaticObject;
public AssemblyBuildTarget LibXamarinLinkMode = AssemblyBuildTarget.StaticObject;
public AssemblyBuildTarget LibPInvokesLinkMode => LibXamarinLinkMode;
public AssemblyBuildTarget LibProfilerLinkMode => OnlyStaticLibraries ? AssemblyBuildTarget.StaticObject : AssemblyBuildTarget.DynamicLibrary;
Dictionary<string, BundleFileInfo> bundle_files = new Dictionary<string, BundleFileInfo> ();
public bool OnlyStaticLibraries {
get {
return assembly_build_targets.All ((abt) => abt.Value.Item1 == AssemblyBuildTarget.StaticObject);
}
}
public bool HasDynamicLibraries {
get {
return assembly_build_targets.Any ((abt) => abt.Value.Item1 == AssemblyBuildTarget.DynamicLibrary);
}
}
[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 bool HasAnyDynamicLibraries {
get {
if (LibMonoLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibXamarinLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibPInvokesLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibProfilerLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
return HasDynamicLibraries;
}
}
public bool HasFrameworks {
get {
return assembly_build_targets.Any ((abt) => abt.Value.Item1 == AssemblyBuildTarget.Framework);
}
}
public void AddAssemblyBuildTarget (string value)
{
var eq_index = value.IndexOf ('=');
if (eq_index == -1)
throw ErrorHelper.CreateError (10, "Could not parse the command line arguments: --assembly-build-target={0}", value);
var assembly_name = value.Substring (0, eq_index);
string target, name;
var eq_index2 = value.IndexOf ('=', eq_index + 1);
if (eq_index2 == -1) {
target = value.Substring (eq_index + 1);
if (assembly_name == "@all" || assembly_name == "@sdk") {
name = string.Empty;
} else {
name = assembly_name;
}
} else {
target = value.Substring (eq_index + 1, eq_index2 - eq_index - 1);
name = value.Substring (eq_index2 + 1);
}
int invalid_idx;
if ((invalid_idx = name.IndexOfAny (new char [] { '/', '\\' })) != -1)
throw ErrorHelper.CreateError (106, "The assembly build target name '{0}' is invalid: the character '{1}' is not allowed.", name, name [invalid_idx]);
if (assembly_build_targets.ContainsKey (assembly_name))
throw ErrorHelper.CreateError (101, "The assembly '{0}' is specified multiple times in --assembly-build-target arguments.", assembly_name);
AssemblyBuildTarget build_target;
switch (target) {
case "staticobject":
build_target = AssemblyBuildTarget.StaticObject;
break;
case "dynamiclibrary":
build_target = AssemblyBuildTarget.DynamicLibrary;
break;
case "framework":
build_target = AssemblyBuildTarget.Framework;
if (name.EndsWith (".framework", StringComparison.Ordinal))
name = name.Substring (0, name.Length - ".framework".Length);
break;
default:
throw ErrorHelper.CreateError (10, "Could not parse the command line arguments: --assembly-build-target={0}", value);
}
assembly_build_targets [assembly_name] = new Tuple<AssemblyBuildTarget, string> (build_target, name);
}
void SelectAssemblyBuildTargets ()
{
Tuple<AssemblyBuildTarget, string> all = null;
Tuple<AssemblyBuildTarget, string> sdk = null;
List<Exception> exceptions = null;
if (IsSimulatorBuild)
return;
// By default each assemblies is compiled to a static object.
if (assembly_build_targets.Count == 0)
assembly_build_targets.Add ("@all", new Tuple<AssemblyBuildTarget, string> (AssemblyBuildTarget.StaticObject, ""));
assembly_build_targets.TryGetValue ("@all", out all);
assembly_build_targets.TryGetValue ("@sdk", out sdk);
foreach (var target in Targets) {
var asm_build_targets = new Dictionary<string, Tuple<AssemblyBuildTarget, string>> (assembly_build_targets);
foreach (var assembly in target.Assemblies) {
Tuple<AssemblyBuildTarget, string> build_target;
var asm_name = assembly.Identity;
if (asm_build_targets.TryGetValue (asm_name, out build_target)) {
asm_build_targets.Remove (asm_name);
} else if (sdk != null && (Profile.IsSdkAssembly (asm_name) || Profile.IsProductAssembly (asm_name))) {
build_target = sdk;
} else {
build_target = all;
}
if (build_target == null) {
if (exceptions == null)
exceptions = new List<Exception> ();
exceptions.Add (ErrorHelper.CreateError (105, "No assembly build target was specified for '{0}'.", assembly.Identity));
continue;
}
assembly.BuildTarget = build_target.Item1;
// The default build target name is the assembly's filename, including the extension,
// so that for instance for System.dll, we'd end up with a System.dll.framework
// (this way it doesn't clash with the system's System.framework).
assembly.BuildTargetName = string.IsNullOrEmpty (build_target.Item2) ? Path.GetFileName (assembly.FileName) : build_target.Item2;
}
foreach (var abt in asm_build_targets) {
if (abt.Key == "@all" || abt.Key == "@sdk")
continue;
if (exceptions == null)
exceptions = new List<Exception> ();
exceptions.Add (ErrorHelper.CreateError (108, "The assembly build target '{0}' did not match any assemblies.", abt.Key));
}
if (exceptions != null)
continue;
var grouped = target.Assemblies.GroupBy ((a) => a.BuildTargetName);
foreach (var @group in grouped) {
var assemblies = @group.AsEnumerable ().ToArray ();
// Check that all assemblies in a group have the same build target
for (int i = 1; i < assemblies.Length; i++) {
if (assemblies [0].BuildTarget != assemblies [i].BuildTarget)
throw ErrorHelper.CreateError (102, "The assemblies '{0}' and '{1}' have the same target name ('{2}'), but different targets ('{3}' and '{4}').",
assemblies [0].Identity, assemblies [1].Identity, assemblies [0].BuildTargetName, assemblies [0].BuildTarget, assemblies [1].BuildTarget);
}
// Check that static objects must consist of only one assembly
if (assemblies.Length != 1 && assemblies [0].BuildTarget == AssemblyBuildTarget.StaticObject)
throw ErrorHelper.CreateError (103, "The static object '{0}' contains more than one assembly ('{1}'), but each static object must correspond with exactly one assembly.",
assemblies [0].BuildTargetName, string.Join ("', '", assemblies.Select ((a) => a.Identity).ToArray ()));
}
}
if (exceptions != null)
throw new AggregateException (exceptions);
}
public string GetLLVMOptimizations (Assembly assembly)
{
string opt;
if (LLVMOptimizations.TryGetValue (assembly.FileName, out opt))
return opt;
if (LLVMOptimizations.TryGetValue ("all", out opt))
return opt;
return null;
}
public void SetDlsymOption (string asm, bool dlsym)
{
if (DlsymAssemblies == null)
DlsymAssemblies = new List<Tuple<string, bool>> ();
DlsymAssemblies.Add (new Tuple<string, bool> (Path.GetFileNameWithoutExtension (asm), dlsym));
DlsymOptions = DlsymOptions.Custom;
}
2016-04-21 15:58:45 +03:00
public void ParseDlsymOptions (string options)
{
bool dlsym;
if (Driver.TryParseBool (options, out dlsym)) {
DlsymOptions = dlsym ? DlsymOptions.All : DlsymOptions.None;
} else {
DlsymAssemblies = new List<Tuple<string, bool>> ();
var assemblies = options.Split (',');
foreach (var assembly in assemblies) {
var asm = assembly;
if (assembly.StartsWith ("+", StringComparison.Ordinal)) {
2016-04-21 15:58:45 +03:00
dlsym = true;
asm = assembly.Substring (1);
} else if (assembly.StartsWith ("-", StringComparison.Ordinal)) {
2016-04-21 15:58:45 +03:00
dlsym = false;
asm = assembly.Substring (1);
} else {
dlsym = true;
}
DlsymAssemblies.Add (new Tuple<string, bool> (Path.GetFileNameWithoutExtension (asm), dlsym));
}
DlsymOptions = DlsymOptions.Custom;
}
}
public bool UseDlsym (string assembly)
{
string asm;
if (DlsymAssemblies != null) {
asm = Path.GetFileNameWithoutExtension (assembly);
foreach (var tuple in DlsymAssemblies) {
if (string.Equals (tuple.Item1, asm, StringComparison.Ordinal))
return tuple.Item2;
}
}
switch (DlsymOptions) {
case DlsymOptions.All:
return true;
case DlsymOptions.None:
return false;
}
2016-04-21 15:58:45 +03:00
if (EnableLLVMOnlyBitCode)
return false;
switch (Platform) {
case ApplePlatform.iOS:
return !Profile.IsSdkAssembly (Path.GetFileNameWithoutExtension (assembly));
2016-04-21 15:58:45 +03:00
case ApplePlatform.TVOS:
case ApplePlatform.WatchOS:
return false;
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.", Platform);
}
}
public string MonoGCParams {
get {
// Configure sgen to use a small nursery
string ret = "nursery-size=512k";
if (IsTodayExtension || Platform == ApplePlatform.WatchOS) {
// A bit test shows different behavior
// Sometimes apps are killed with ~100mb allocated,
// but I've seen apps allocate up to 240+mb as well
ret += ",soft-heap-limit=8m";
2016-04-21 15:58:45 +03:00
}
if (EnableSGenConc)
ret += ",major=marksweep-conc";
else
ret += ",major=marksweep";
return ret;
2016-04-21 15:58:45 +03:00
}
}
public bool IsDeviceBuild {
get { return BuildTarget == BuildTarget.Device; }
}
public bool IsSimulatorBuild {
get { return BuildTarget == BuildTarget.Simulator; }
}
public IEnumerable<Abi> Abis {
get { return abis; }
}
public BitCodeMode BitCodeMode { get; set; }
public bool EnableAsmOnlyBitCode { get { return BitCodeMode == BitCodeMode.ASMOnly; } }
public bool EnableLLVMOnlyBitCode { get { return BitCodeMode == BitCodeMode.LLVMOnly; } }
public bool EnableMarkerOnlyBitCode { get { return BitCodeMode == BitCodeMode.MarkerOnly; } }
public bool EnableBitCode { get { return BitCodeMode != BitCodeMode.None; } }
public ICollection<Abi> AllArchitectures {
get {
if (all_architectures == null) {
all_architectures = new HashSet<Abi> ();
foreach (var abi in abis)
all_architectures.Add (abi & Abi.ArchMask);
foreach (var ext in AppExtensions) {
foreach (var abi in ext.Abis)
all_architectures.Add (abi & Abi.ArchMask);
2016-04-21 15:58:45 +03:00
}
}
return all_architectures;
}
}
public bool IsTodayExtension {
get {
return ExtensionIdentifier == "com.apple.widget-extension";
}
}
public bool IsWatchExtension {
get {
return ExtensionIdentifier == "com.apple.watchkit";
}
}
public bool IsTVExtension {
get {
return ExtensionIdentifier == "com.apple.tv-services";
}
}
2016-04-21 15:58:45 +03:00
public string ExtensionIdentifier {
get {
if (!IsExtension)
return null;
var info_plist = Path.Combine (AppDirectory, "Info.plist");
var plist = Driver.FromPList (info_plist);
var dict = plist.Get<PDictionary> ("NSExtension");
if (dict == null)
return null;
return dict.GetString ("NSExtensionPointIdentifier");
2016-04-21 15:58:45 +03:00
}
}
public string BundleId {
get {
return GetStringFromInfoPList ("CFBundleIdentifier");
}
}
2016-04-21 15:58:45 +03:00
string GetStringFromInfoPList (string key)
{
return GetStringFromInfoPList (AppDirectory, key);
2016-04-21 15:58:45 +03:00
}
string GetStringFromInfoPList (string directory, string key)
{
var info_plist = Path.Combine (directory, "Info.plist");
if (!File.Exists (info_plist))
return null;
var plist = Driver.FromPList (info_plist);
if (!plist.ContainsKey (key))
return null;
return plist.GetString (key);
2016-04-21 15:58:45 +03:00
}
public void SetDefaultAbi ()
{
if (abis == null)
abis = new List<Abi> ();
switch (Platform) {
case ApplePlatform.iOS:
if (abis.Count == 0) {
abis.Add (IsDeviceBuild ? Abi.ARMv7 : Abi.i386);
}
break;
case ApplePlatform.WatchOS:
if (abis.Count == 0)
throw ErrorHelper.CreateError (76, "No architecture specified (using the --abi argument). An architecture is required for {0} projects.", "Xamarin.WatchOS");
break;
case ApplePlatform.TVOS:
if (abis.Count == 0)
throw ErrorHelper.CreateError (76, "No architecture specified (using the --abi argument). An architecture is required for {0} projects.", "Xamarin.TVOS");
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.", Platform);
}
}
public void ValidateAbi ()
{
var validAbis = new List<Abi> ();
switch (Platform) {
case ApplePlatform.iOS:
if (IsDeviceBuild) {
validAbis.Add (Abi.ARMv7);
validAbis.Add (Abi.ARMv7 | Abi.Thumb);
validAbis.Add (Abi.ARMv7 | Abi.LLVM);
validAbis.Add (Abi.ARMv7 | Abi.LLVM | Abi.Thumb);
validAbis.Add (Abi.ARMv7s);
validAbis.Add (Abi.ARMv7s | Abi.Thumb);
validAbis.Add (Abi.ARMv7s | Abi.LLVM);
validAbis.Add (Abi.ARMv7s | Abi.LLVM | Abi.Thumb);
} else {
validAbis.Add (Abi.i386);
}
if (IsDeviceBuild) {
validAbis.Add (Abi.ARM64);
validAbis.Add (Abi.ARM64 | Abi.LLVM);
} else {
validAbis.Add (Abi.x86_64);
2016-04-21 15:58:45 +03:00
}
break;
case ApplePlatform.WatchOS:
if (IsDeviceBuild) {
validAbis.Add (Abi.ARMv7k);
validAbis.Add (Abi.ARMv7k | Abi.LLVM);
} else {
validAbis.Add (Abi.i386);
}
2016-04-21 15:58:45 +03:00
break;
case ApplePlatform.TVOS:
if (IsDeviceBuild) {
validAbis.Add (Abi.ARM64);
validAbis.Add (Abi.ARM64 | Abi.LLVM);
} else {
validAbis.Add (Abi.x86_64);
}
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.", Platform);
}
foreach (var abi in abis) {
if (!validAbis.Contains (abi))
throw ErrorHelper.CreateError (75, "Invalid architecture '{0}' for {1} projects. Valid architectures are: {2}", abi, Platform, string.Join (", ", validAbis.Select ((v) => v.AsString ()).ToArray ()));
}
}
public void ClearAbi ()
{
abis = null;
}
// 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 t in Targets)
t.LoadSymbols ();
}
public void ParseAbi (string abi)
{
var res = new List<Abi> ();
foreach (var str in abi.Split (new char [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
Abi value;
switch (str) {
case "i386":
value = Abi.i386;
break;
case "x86_64":
value = Abi.x86_64;
break;
case "armv7":
value = Abi.ARMv7;
break;
case "armv7+llvm":
value = Abi.ARMv7 | Abi.LLVM;
break;
case "armv7+llvm+thumb2":
value = Abi.ARMv7 | Abi.LLVM | Abi.Thumb;
break;
case "armv7s":
value = Abi.ARMv7s;
break;
case "armv7s+llvm":
value = Abi.ARMv7s | Abi.LLVM;
break;
case "armv7s+llvm+thumb2":
value = Abi.ARMv7s | Abi.LLVM | Abi.Thumb;
break;
case "arm64":
value = Abi.ARM64;
break;
case "arm64+llvm":
value = Abi.ARM64 | Abi.LLVM;
break;
case "armv7k":
value = Abi.ARMv7k;
break;
case "armv7k+llvm":
value = Abi.ARMv7k | Abi.LLVM;
break;
2016-04-21 15:58:45 +03:00
default:
throw new MonoTouchException (15, true, "Invalid ABI: {0}. Supported ABIs are: i386, x86_64, armv7, armv7+llvm, armv7+llvm+thumb2, armv7s, armv7s+llvm, armv7s+llvm+thumb2, armv7k, armv7k+llvm, arm64 and arm64+llvm.", str);
2016-04-21 15:58:45 +03:00
}
// merge this value with any existing ARMv? already specified.
// this is so that things like '--armv7 --thumb' work correctly.
if (abis != null) {
for (int i = 0; i < abis.Count; i++) {
if ((abis [i] & Abi.ArchMask) == (value & Abi.ArchMask)) {
value |= abis [i];
break;
}
}
}
res.Add (value);
}
// We replace any existing abis, to keep the old behavior where '--armv6 --armv7' would
// enable only the last abi specified and disable the rest.
abis = res;
}
public static string GetArchitectures (IEnumerable<Abi> abis)
{
var res = new List<string> ();
foreach (var abi in abis)
res.Add (abi.AsArchString ());
return string.Join (", ", res.ToArray ());
}
public bool IsArchEnabled (Abi arch)
{
return IsArchEnabled (abis, arch);
}
public static bool IsArchEnabled (IEnumerable<Abi> abis, Abi arch)
{
foreach (var abi in abis) {
if ((abi & arch) != 0)
return true;
}
return false;
}
[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 void BuildAll ()
{
var allapps = new List<Application> ();
allapps.Add (this); // We need to build the main app first, so that any extensions sharing code can reference frameworks built in the main app.
allapps.AddRange (AppExtensions);
allapps.ForEach ((v) => v.BuildInitialize ());
DetectCodeSharing ();
allapps.ForEach ((v) => v.BuildManaged ());
allapps.ForEach ((v) => v.BuildNative ());
BuildBundle ();
[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
allapps.ForEach ((v) => v.BuildEnd ());
}
void BuildInitialize ()
2016-04-21 15:58:45 +03:00
{
if (Driver.Force) {
Driver.Log (3, "A full rebuild has been forced by the command line argument -f.");
Cache.Clean ();
[mtouch] Don't set Force when the cache is invalid (as it will be deleted) (#1016) If the cache is invalid we print a warning: > A full rebuild will be performed because the cache is either incomplete or entirely missing. and set `Driver.Force = true;` (in Application.cs). This later means that extracting the native code is done on each target: ``` public static bool IsUptodate (string source, string target) { if (Driver.Force) return false; ``` even if this is identical between 32 and 64 bits (targets). That's inefficient and, for large binding libraries (e.g. > 1GB), has a noticable impact on build time (see timestamps). Considering that the cache is cleaned (when detected as invalid) then this Force condition is not really needed. E.g. in `IsUptodate` * the first time (arch) it's called will have to extract the native library; * if a 2nd arch is built (fat) then it will be found as present and will not be extracted again Removing the `Driver.Force = true;` in this condition let the `-f` option continue to extract it twice, which can be useful in debugging and testing. As such the check is not removed from `IsUptodate` Timestamps (before) Setup: 25 ms Resolve References: 1605 ms Extracted native link info: 10465 ms ... Timestamps (after) Setup: 24 ms Resolve References: 1560 ms Extracted native link info: 5473 ms ... Total build times (from XS) was around 90-100 seconds so 5 seconds is about 10%. The actual savings will depend on how much native code needs to be extracted, but it should help most release builds (almost always fat builds).
2016-11-01 16:10:09 +03:00
} else {
// this will destroy the cache if invalid, which makes setting Driver.Force to true mostly unneeded
// in fact setting it means some actions (like extract native resource) gets duplicate for fat builds
Cache.VerifyCache ();
2016-04-21 15:58:45 +03:00
}
Initialize ();
ValidateAbi ();
SelectRegistrar ();
ExtractNativeLinkInfo ();
SelectNativeCompiler ();
[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
}
void BuildManaged ()
{
ProcessAssemblies ();
[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
}
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
[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
void BuildNative ()
{
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
// Everything that can be parallelized is put into a list of tasks,
// which are then executed at the end.
build_tasks = new BuildTasks ();
Driver.Watch ("Generating build tasks", 1);
CompilePInvokeWrappers ();
2016-04-21 15:58:45 +03:00
BuildApp ();
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
if (Driver.Dot)
build_tasks.Dot (Path.Combine (Cache.Location, "build.dot"));
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
Driver.Watch ("Building build tasks", 1);
build_tasks.Execute ();
}
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
void BuildEnd ()
{
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
// TODO: make more of the below actions parallelizable
2016-04-21 15:58:45 +03:00
BuildDsymDirectory ();
BuildMSymDirectory ();
StripNativeCode ();
BundleAssemblies ();
WriteNotice ();
2016-04-21 15:58:45 +03:00
GenerateRuntimeOptions ();
if (Cache.IsCacheTemporary) {
// If we used a temporary directory we created ourselves for the cache
// (in which case it's more a temporary location where we store the
// temporary build products than a cache), it will not be used again,
// so just delete it.
try {
Directory.Delete (Cache.Location, true);
} catch {
// Don't care.
}
} else {
// Write the cache data as the last step, so there is no half-done/incomplete (but yet detected as valid) cache.
Cache.ValidateCache ();
}
Console.WriteLine ("{0} built successfully.", AppDirectory);
}
bool no_framework;
2016-04-21 15:58:45 +03:00
public void SetDefaultFramework ()
{
// If no target framework was specified, check if we're referencing Xamarin.iOS.dll.
// It's an error if neither target framework nor Xamarin.iOS.dll is not specified
2016-04-21 15:58:45 +03:00
if (!Driver.HasTargetFramework) {
foreach (var reference in References) {
var name = Path.GetFileName (reference);
switch (name) {
case "Xamarin.iOS.dll":
Driver.TargetFramework = TargetFramework.Xamarin_iOS_1_0;
break;
case "Xamarin.TVOS.dll":
case "Xamarin.WatchOS.dll":
throw ErrorHelper.CreateError (86, "A target framework (--target-framework) must be specified when building for TVOS or WatchOS.");
}
if (Driver.HasTargetFramework)
break;
}
}
if (!Driver.HasTargetFramework) {
// Set a default target framework to show errors in the least confusing order.
Driver.TargetFramework = TargetFramework.Xamarin_iOS_1_0;
no_framework = true;
2016-04-21 15:58:45 +03:00
}
}
[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
void DetectCodeSharing ()
{
if (AppExtensions.Count == 0)
return;
if (!IsDeviceBuild)
return;
if (NoDevCodeShare) {
Driver.Log (2, "Native code sharing has been disabled in the main app, so no code sharing with extensions will occur.");
return;
}
// No I18N assemblies can be included
if (I18n != Mono.Linker.I18nAssemblies.None) {
Driver.Log (2, "Native code sharing has been disabled, because the container app includes I18N assemblies ({0}).", I18n);
return;
}
List<Application> candidates = new List<Application> ();
foreach (var appex in AppExtensions) {
if (appex.IsWatchExtension)
continue;
if (appex.NoDevCodeShare) {
Driver.Log (2, "Native code sharing has been disabled in the extension {0}, so no code sharing with the main will occur for this extension.", appex.Name);
continue;
}
bool applicable = true;
// The --assembly-build-target arguments must be identical.
// We can probably lift this requirement (at least partially) at some point,
// but for now it makes our code simpler.
if (assembly_build_targets.Count != appex.assembly_build_targets.Count) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
continue;
}
foreach (var key in assembly_build_targets.Keys) {
Tuple<AssemblyBuildTarget, string> appex_value;
if (!appex.assembly_build_targets.TryGetValue (key, out appex_value)) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
applicable = false;
break;
}
var value = assembly_build_targets [key];
if (value.Item1 != appex_value.Item1 || value.Item2 != appex_value.Item2) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
applicable = false;
break;
}
}
if (!applicable)
continue;
// No I18N assemblies can be included
if (appex.I18n != Mono.Linker.I18nAssemblies.None) {
Driver.Log (2, "The extension '{0}' includes I18N assemblies ({1}), and can therefore not share native code with the main app.", appex.Name, appex.I18n);
continue;
}
// All arguments to the AOT compiler must be identical
if (AotArguments != appex.AotArguments) {
Driver.Log (2, "The extension '{0}' has different arguments to the AOT compiler (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.AotArguments, AotArguments);
continue;
}
if (AotOtherArguments != appex.AotOtherArguments) {
Driver.Log (2, "The extension '{0}' has different other arguments to the AOT compiler (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.AotOtherArguments, AotOtherArguments);
continue;
}
if (IsLLVM != appex.IsLLVM) {
Driver.Log (2, "The extension '{0}' does not have the same LLVM option as the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.IsLLVM, IsLLVM);
continue;
}
if (LinkMode != appex.LinkMode) {
Driver.Log (2, "The extension '{0}' does not have the same managed linker option as the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.LinkMode, LinkMode);
continue;
}
if (LinkMode != LinkMode.None) {
var linkskipped_same = !LinkSkipped.Except (appex.LinkSkipped).Any () && !appex.LinkSkipped.Except (LinkSkipped).Any ();
if (!linkskipped_same) {
Driver.Log (2, "The extension '{0}' is asking the managed linker to skip different assemblies than the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, string.Join (", ", appex.LinkSkipped), string.Join (", ", LinkSkipped));
continue;
}
if (Definitions.Count > 0) {
Driver.Log (2, "The app '{0}' has xml definitions for the managed linker ({1}), and can therefore not share native code with any extension.", Name, string.Join (", ", Definitions));
continue;
}
if (appex.Definitions.Count > 0) {
Driver.Log (2, "The extension '{0}' has xml definitions for the managed linker ({1}), and can therefore not share native code with the main app.", appex.Name, string.Join (", ", appex.Definitions));
continue;
}
}
// Check that the Abis are matching
foreach (var abi in appex.Abis) {
var matching = abis.FirstOrDefault ((v) => (v & Abi.ArchMask) == (abi & Abi.ArchMask));
if (matching == Abi.None) {
// Example: extension has arm64+armv7, while the main app has only arm64.
Driver.Log (2, "The extension '{0}' is targeting the abi '{1}' (which the main app is not targeting), and can therefore not share native code with the main app.", appex.Name, abi);
applicable = false;
break;
} else if (matching != abi) {
// Example: extension has arm64+llvm, while the main app has only arm64.
Driver.Log (2, "The extension '{0}' is targeting the abi '{1}', which is not compatible with the main app's corresponding abi '{2}', and can therefore not share native code with the main app.", appex.Name, abi, matching);
applicable = false;
break;
}
}
// Check if there aren't referenced assemblies from different sources
foreach (var target in Targets) {
var appexTarget = appex.Targets.Single ((v) => v.Is32Build == target.Is32Build);
foreach (var kvp in appexTarget.Assemblies.Hashed) {
Assembly asm;
if (!target.Assemblies.TryGetValue (kvp.Key, out asm))
continue; // appex references an assembly the main app doesn't. This is fine.
if (asm.FullPath != kvp.Value.FullPath) {
applicable = false; // app references an assembly with the same name as the main app, but from a different location. This is not fine. Should we emit a real warning here, or just a log message?
Driver.Log (2, "The extension '{0}' is referencing the assembly '{1}' from '{2}', while the main app references it from '{3}', and can therefore not share native code with the main app.", appex.Name, asm.Identity, kvp.Value.FullPath, asm.FullPath);
break;
}
}
}
if (!applicable)
continue;
if (!applicable)
continue;
candidates.Add (appex);
Driver.Log (2, "The main app and the extension '{0}' will share code.", appex.Name);
}
if (candidates.Count > 0)
SharedCodeApps.AddRange (candidates);
}
2016-04-21 15:58:45 +03:00
void Initialize ()
{
if (EnableDebug && IsLLVM)
ErrorHelper.Warning (3003, "Debugging is not supported when building with LLVM. Debugging has been disabled.");
if (!IsLLVM && (EnableAsmOnlyBitCode || EnableLLVMOnlyBitCode))
throw ErrorHelper.CreateError (3008, "Bitcode support requires the use of LLVM (--abi=arm64+llvm etc.)");
if (IsLLVM && Platform == ApplePlatform.WatchOS && BitCodeMode != BitCodeMode.LLVMOnly) {
ErrorHelper.Warning (111, "Bitcode has been enabled because this version of Xamarin.iOS does not support building watchOS projects using LLVM without enabling bitcode.");
BitCodeMode = BitCodeMode.LLVMOnly;
}
if (EnableDebug) {
if (!DebugTrack.HasValue) {
DebugTrack = IsSimulatorBuild;
}
} else {
if (DebugTrack.HasValue) {
ErrorHelper.Warning (32, "The option '--debugtrack' is ignored unless '--debug' is also specified.");
}
DebugTrack = false;
}
if (EnableAsmOnlyBitCode)
LLVMAsmWriter = true;
2016-04-21 15:58:45 +03:00
if (!File.Exists (RootAssembly))
throw new MonoTouchException (7, true, "The root assembly '{0}' does not exist", RootAssembly);
if (no_framework)
throw ErrorHelper.CreateError (96, "No reference to Xamarin.iOS.dll was found.");
2016-04-21 15:58:45 +03:00
// Add a reference to the platform assembly if none has been added, and check that we're not referencing
// any platform assemblies from another platform.
var platformAssemblyReference = false;
foreach (var reference in References) {
var name = Path.GetFileNameWithoutExtension (reference);
if (name == Driver.GetProductAssembly (this)) {
2016-04-21 15:58:45 +03:00
platformAssemblyReference = true;
} else {
switch (name) {
case "Xamarin.iOS":
case "Xamarin.TVOS":
case "Xamarin.WatchOS":
throw ErrorHelper.CreateError (41, "Cannot reference '{0}' in a {1} app.", Path.GetFileName (reference), Driver.TargetFramework.Identifier);
}
}
}
if (!platformAssemblyReference) {
ErrorHelper.Warning (85, "No reference to '{0}' was found. It will be added automatically.", Driver.GetProductAssembly (this) + ".dll");
References.Add (Path.Combine (Driver.GetPlatformFrameworkDirectory (this), Driver.GetProductAssembly (this) + ".dll"));
2016-04-21 15:58:45 +03:00
}
var FrameworkDirectory = Driver.GetPlatformFrameworkDirectory (this);
2016-04-21 15:58:45 +03:00
var RootDirectory = Path.GetDirectoryName (Path.GetFullPath (RootAssembly));
((MonoTouchProfile) Profile.Current).SetProductAssembly (Driver.GetProductAssembly (this));
2016-04-21 15:58:45 +03:00
string root_wo_ext = Path.GetFileNameWithoutExtension (RootAssembly);
if (Profile.IsSdkAssembly (root_wo_ext) || Profile.IsProductAssembly (root_wo_ext))
throw new MonoTouchException (3, true, "Application name '{0}.exe' conflicts with an SDK or product assembly (.dll) name.", root_wo_ext);
if (IsDualBuild) {
var target32 = new Target (this);
var target64 = new Target (this);
target32.ArchDirectory = Path.Combine (Cache.Location, "32");
target32.TargetDirectory = IsSimulatorBuild ? Path.Combine (AppDirectory, ".monotouch-32") : Path.Combine (target32.ArchDirectory, "Output");
target32.AppTargetDirectory = Path.Combine (AppDirectory, ".monotouch-32");
target32.Resolver.ArchDirectory = Driver.GetArch32Directory (this);
2016-04-21 15:58:45 +03:00
target32.Abis = SelectAbis (abis, Abi.Arch32Mask);
target64.ArchDirectory = Path.Combine (Cache.Location, "64");
target64.TargetDirectory = IsSimulatorBuild ? Path.Combine (AppDirectory, ".monotouch-64") : Path.Combine (target64.ArchDirectory, "Output");
target64.AppTargetDirectory = Path.Combine (AppDirectory, ".monotouch-64");
target64.Resolver.ArchDirectory = Driver.GetArch64Directory (this);
2016-04-21 15:58:45 +03:00
target64.Abis = SelectAbis (abis, Abi.Arch64Mask);
Targets.Add (target64);
Targets.Add (target32);
} else {
var target = new Target (this);
target.TargetDirectory = AppDirectory;
target.AppTargetDirectory = IsSimulatorBuild ? AppDirectory : Path.Combine (AppDirectory, Is64Build ? ".monotouch-64" : ".monotouch-32");
2016-04-21 15:58:45 +03:00
target.ArchDirectory = Cache.Location;
target.Resolver.ArchDirectory = Path.Combine (FrameworkDirectory, "..", "..", Is32Build ? "32bits" : "64bits");
2016-04-21 15:58:45 +03:00
target.Abis = abis;
Targets.Add (target);
// Make sure there aren't any lingering .monotouch-* directories.
if (IsSimulatorBuild) {
var dir = Path.Combine (AppDirectory, ".monotouch-32");
if (Directory.Exists (dir))
Directory.Delete (dir, true);
dir = Path.Combine (AppDirectory, ".monotouch-64");
if (Directory.Exists (dir))
Directory.Delete (dir, true);
}
}
foreach (var target in Targets) {
target.Resolver.FrameworkDirectory = FrameworkDirectory;
2016-04-21 15:58:45 +03:00
target.Resolver.RootDirectory = RootDirectory;
target.Resolver.EnableRepl = EnableRepl;
target.ManifestResolver.EnableRepl = EnableRepl;
target.ManifestResolver.FrameworkDirectory = target.Resolver.FrameworkDirectory;
target.ManifestResolver.RootDirectory = target.Resolver.RootDirectory;
target.ManifestResolver.ArchDirectory = target.Resolver.ArchDirectory;
target.Initialize (target == Targets [0]);
if (!Directory.Exists (target.TargetDirectory))
Directory.CreateDirectory (target.TargetDirectory);
}
if (string.IsNullOrEmpty (ExecutableName)) {
var bundleExecutable = GetStringFromInfoPList ("CFBundleExecutable");
ExecutableName = bundleExecutable ?? Path.GetFileNameWithoutExtension (RootAssembly);
}
if (ExecutableName != Path.GetFileNameWithoutExtension (AppDirectory))
ErrorHelper.Warning (30, "The executable name ({0}) and the app name ({1}) are different, this may prevent crash logs from getting symbolicated properly.",
ExecutableName, Path.GetFileName (AppDirectory));
if (IsExtension && Platform == ApplePlatform.iOS && SdkVersion < new Version (8, 0))
2016-04-21 15:58:45 +03:00
throw new MonoTouchException (45, true, "--extension is only supported when using the iOS 8.0 (or later) SDK.");
if (IsExtension && Platform != ApplePlatform.iOS && Platform != ApplePlatform.WatchOS && Platform != ApplePlatform.TVOS)
2016-04-21 15:58:45 +03:00
throw new MonoTouchException (72, true, "Extensions are not supported for the platform '{0}'.", Platform);
if (!IsExtension && Platform == ApplePlatform.WatchOS)
throw new MonoTouchException (77, true, "WatchOS projects must be extensions.");
#if ENABLE_BITCODE_ON_IOS
if (Platform == ApplePlatform.iOS)
DeploymentTarget = new Version (9, 0);
#endif
2016-04-21 15:58:45 +03:00
if (DeploymentTarget == null) {
DeploymentTarget = Xamarin.SdkVersions.GetVersion (Platform);
} else if (DeploymentTarget < Xamarin.SdkVersions.GetMinVersion (Platform)) {
throw new MonoTouchException (73, true, "Xamarin.iOS {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);
2016-04-21 15:58:45 +03:00
} else if (DeploymentTarget > Xamarin.SdkVersions.GetVersion (Platform)) {
throw new MonoTouchException (74, true, "Xamarin.iOS {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 Xamarin.iOS.", Constants.Version, DeploymentTarget, Xamarin.SdkVersions.GetVersion (Platform), PlatformName);
2016-04-21 15:58:45 +03:00
}
if (Platform == ApplePlatform.iOS && (HasDynamicLibraries || HasFrameworks) && DeploymentTarget.Major < 8) {
[mtouch] Require deployment target to be 8+ for incremental builds. (#805) This is something that changed with 70f1346b: our libxamarin.dylib now requires iOS 8+, which means the app itself must require iOS 8+ when using libxamarin.dylib. This fixes an msbuild failure (Xamarin.iOS.Tasks.IBToolLinking("iPhone").BuildTest): Process exited with code 1, command: /Applications/Xcode73.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -framework Foundation -framework UIKit /Users/builder/data/lanes/1381/5f73edaa/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/SDKs/MonoTouch.iphoneos.sdk/usr/lib/libmonosgen-2.0.dylib /Users/builder/data/lanes/1381/5f73edaa/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/SDKs/MonoTouch.iphoneos.sdk/usr/lib/libxamarin-debug.dylib -lz -isysroot /Applications/Xcode73.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk -Qunused-arguments -miphoneos-version-min=7.0 -arch armv7 -shared -read_only_relocs suppress -install_name @executable_path/libXamarin.iOS.dll.dylib -fapplication-extension -o /Users/builder/data/lanes/1381/5f73edaa/source/xamarin-macios/msbuild/tests/MyIBToolLinkTest/obj/iPhone/Debug/mtouch-cache/Xamarin.iOS.dll.armv7.dylib -x assembler /Users/builder/data/lanes/1381/5f73edaa/source/xamarin-macios/msbuild/tests/MyIBToolLinkTest/obj/iPhone/Debug/mtouch-cache/Xamarin.iOS.dll.armv7.s -DDEBUG ld: warning: embedded dylibs/frameworks only run on iOS 8 or later ld: embedded dylibs/frameworks are only supported on iOS 8.0 and later (@executable_path/libxamarin-debug.dylib) for architecture armv7 clang: error: linker command failed with exit code 1 (use -v to see invocation)
2016-09-08 16:01:48 +03:00
ErrorHelper.Warning (78, "Incremental builds are enabled with a deployment target < 8.0 (currently {0}). This is not supported (the resulting application will not launch on iOS 9), so the deployment target will be set to 8.0.", DeploymentTarget);
DeploymentTarget = new Version (8, 0);
2016-04-21 15:58:45 +03:00
}
if (!package_mdb.HasValue) {
package_mdb = EnableDebug;
} else if (package_mdb.Value && IsLLVM) {
ErrorHelper.Warning (3007, "Debug info files (*.mdb) will not be loaded when llvm is enabled.");
}
if (!enable_msym.HasValue)
enable_msym = !EnableDebug && IsDeviceBuild;
2016-04-21 15:58:45 +03:00
if (!UseMonoFramework.HasValue && DeploymentTarget >= new Version (8, 0)) {
if (IsExtension) {
UseMonoFramework = true;
Driver.Log (2, "Automatically linking with Mono.framework because this is an extension");
} else if (Extensions.Count > 0) {
UseMonoFramework = true;
Driver.Log (2, "Automatically linking with Mono.framework because this is an app with extensions");
2016-04-21 15:58:45 +03:00
}
}
if (!UseMonoFramework.HasValue)
UseMonoFramework = false;
if (UseMonoFramework.Value)
Frameworks.Add (Path.Combine (Driver.GetProductFrameworksDirectory (this), "Mono.framework"));
2016-04-21 15:58:45 +03:00
if (!PackageMonoFramework.HasValue) {
if (!IsExtension && Extensions.Count > 0 && !UseMonoFramework.Value) {
// The main app must package the Mono framework if we have extensions, even if it's not linking with
// it. This happens when deployment target < 8.0 for the main app.
PackageMonoFramework = true;
} else {
// Package if we're not an extension and we're using the mono framework.
PackageMonoFramework = UseMonoFramework.Value && !IsExtension;
}
}
if (Frameworks.Count > 0) {
switch (Platform) {
case ApplePlatform.iOS:
if (DeploymentTarget < new Version (8, 0))
throw ErrorHelper.CreateError (65, "Xamarin.iOS only supports embedded frameworks when deployment target is at least 8.0 (current deployment target: '{0}'; embedded frameworks: '{1}')", DeploymentTarget, string.Join (", ", Frameworks.ToArray ()));
break;
case ApplePlatform.WatchOS:
if (DeploymentTarget < new Version (2, 0))
throw ErrorHelper.CreateError (65, "Xamarin.iOS only supports embedded frameworks when deployment target is at least 2.0 (current deployment target: '{0}'; embedded frameworks: '{1}')", DeploymentTarget, string.Join (", ", Frameworks.ToArray ()));
break;
case ApplePlatform.TVOS:
// All versions of tvOS support extensions
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.", Platform);
}
2016-04-21 15:58:45 +03:00
}
if (IsDeviceBuild) {
switch (BitCodeMode) {
case BitCodeMode.ASMOnly:
if (Platform == ApplePlatform.WatchOS)
throw ErrorHelper.CreateError (83, "asm-only bitcode is not supported on watchOS. Use either --bitcode:marker or --bitcode:full.");
break;
case BitCodeMode.LLVMOnly:
case BitCodeMode.MarkerOnly:
break;
case BitCodeMode.None:
// If neither llvmonly nor asmonly is enabled, enable markeronly.
if (Platform == ApplePlatform.TVOS || Platform == ApplePlatform.WatchOS)
BitCodeMode = BitCodeMode.MarkerOnly;
break;
}
}
if (EnableBitCode && IsSimulatorBuild)
throw ErrorHelper.CreateError (84, "Bitcode is not supported in the simulator. Do not pass --bitcode when building for the simulator.");
if (LinkMode == LinkMode.None && SdkVersion < SdkVersions.GetVersion (Platform))
throw ErrorHelper.CreateError (91, "This version of Xamarin.iOS requires the {0} {1} SDK (shipped with Xcode {2}) when the managed linker is disabled. Either upgrade Xcode, or enable the managed linker by changing the Linker behaviour to Link Framework SDKs Only.", PlatformName, SdkVersions.GetVersion (Platform), SdkVersions.Xcode);
2016-04-21 15:58:45 +03:00
if (HasFrameworks || UseMonoFramework.Value) {
LibMonoLinkMode = AssemblyBuildTarget.Framework;
} else if (HasDynamicLibraries) {
LibMonoLinkMode = AssemblyBuildTarget.DynamicLibrary;
}
if (HasFrameworks) {
LibXamarinLinkMode = AssemblyBuildTarget.Framework;
} else if (HasDynamicLibraries) {
LibXamarinLinkMode = AssemblyBuildTarget.DynamicLibrary;
}
2016-04-21 15:58:45 +03:00
Namespaces.Initialize ();
InitializeCommon ();
2016-04-21 15:58:45 +03:00
Driver.Watch ("Resolve References", 1);
}
void SelectRegistrar ()
{
// If the default values are changed, remember to update CanWeSymlinkTheApplication
// and main.m (default value for xamarin_use_old_dynamic_registrar must match).
if (Registrar == RegistrarMode.Default) {
if (IsDeviceBuild) {
Registrar = RegistrarMode.Static;
} else { /* if (app.IsSimulatorBuild) */
Registrar = RegistrarMode.Dynamic;
}
}
foreach (var target in Targets)
target.SelectStaticRegistrar ();
2016-04-21 15:58:45 +03:00
}
// Select all abi from the list matching the specified mask.
List<Abi> SelectAbis (IEnumerable<Abi> abis, Abi mask)
{
var rv = new List<Abi> ();
foreach (var abi in abis) {
if ((abi & mask) != 0)
rv.Add (abi);
}
return rv;
}
public string AssemblyName {
get {
return Path.GetFileName (RootAssembly);
}
}
public string Executable {
get {
return Path.Combine (AppDirectory, ExecutableName);
}
}
void ProcessAssemblies ()
2016-04-21 15:58:45 +03:00
{
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
// This can be parallelized once we determine the linker doesn't use any static state.
2016-04-21 15:58:45 +03:00
foreach (var target in Targets) {
if (target.CanWeSymlinkTheApplication ()) {
target.Symlink ();
} else {
target.ProcessAssemblies ();
}
}
// Deduplicate files from the Build directory. We need to do this before the AOT
// step, so that we can ignore timestamp/GUID in assemblies (the GUID is
// burned into the AOT assembly, so after that we'll need the original assembly.
if (IsDualBuild && IsDeviceBuild) {
2016-04-21 15:58:45 +03:00
// All the assemblies are now in BuildDirectory.
var t1 = Targets [0];
var t2 = Targets [1];
foreach (var f1 in Directory.GetFileSystemEntries (t1.BuildDirectory)) {
var f2 = Path.Combine (t2.BuildDirectory, Path.GetFileName (f1));
if (!File.Exists (f2))
continue;
var ext = Path.GetExtension (f1).ToUpperInvariant ();
var is_assembly = ext == ".EXE" || ext == ".DLL";
if (!is_assembly)
continue;
if (!Cache.CompareAssemblies (f1, f2, true))
continue;
2016-12-12 21:01:26 +03:00
Driver.Log (1, "Targets {0} and {1} found to be identical", f1, f2);
2016-04-21 15:58:45 +03:00
// Don't use symlinks, since it just gets more complicated
// For instance: on rebuild, when should the symlink be updated and when
// should the target of the symlink be updated? And all the usages
// must be audited to ensure the right thing is done...
Driver.CopyAssembly (f1, f2);
}
}
}
void CompilePInvokeWrappers ()
{
foreach (var target in Targets)
target.CompilePInvokeWrappers ();
}
2016-04-21 15:58:45 +03:00
void BuildApp ()
{
SelectAssemblyBuildTargets (); // This must be done after the linker has run, since the linker may bring in more assemblies than only those referenced explicitly.
2016-04-21 15:58:45 +03:00
foreach (var target in Targets) {
if (target.CanWeSymlinkTheApplication ())
continue;
target.ComputeLinkerFlags ();
target.Compile ();
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
target.NativeLink (build_tasks);
2016-04-21 15:58:45 +03:00
}
}
void WriteNotice ()
{
if (!IsDeviceBuild)
return;
if (Directory.Exists (Path.Combine (AppDirectory, "NOTICE")))
throw new MonoTouchException (1016, true, "Failed to create the NOTICE file because a directory already exists with the same name.");
try {
// write license information inside the .app
StringBuilder sb = new StringBuilder ();
sb.Append ("Xamarin built applications contain open source software. ");
sb.Append ("For detailed attribution and licensing notices, please visit...");
sb.AppendLine ().AppendLine ().Append ("http://xamarin.com/mobile-licensing").AppendLine ();
Driver.WriteIfDifferent (Path.Combine (AppDirectory, "NOTICE"), sb.ToString ());
2016-04-21 15:58:45 +03:00
} catch (Exception ex) {
throw new MonoTouchException (1017, true, ex, "Failed to create the NOTICE file: {0}", ex.Message);
}
}
public static void CopyMSymData (string src, string dest)
{
if (string.IsNullOrEmpty (src) || string.IsNullOrEmpty (dest))
return;
if (!Directory.Exists (src)) // got no aot data
return;
var p = new Process ();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.FileName = "mono-symbolicate";
p.StartInfo.Arguments = $"store-symbols \"{src}\" \"{dest}\"";
try {
if (p.Start ()) {
var error = p.StandardError.ReadToEnd();
p.WaitForExit ();
GC.Collect (); // Workaround for: https://bugzilla.xamarin.com/show_bug.cgi?id=43462#c14
if (p.ExitCode == 0)
return;
else {
ErrorHelper.Warning (95, $"Aot files could not be copied to the destination directory {dest}: {error}");
return;
}
}
ErrorHelper.Warning (95, $"Aot files could not be copied to the destination directory {dest}: Could not start process.");
return;
}
catch (Exception e) {
ErrorHelper.Warning (95, e, $"Aot files could not be copied to the destination directory {dest}: Could not start process.");
return;
}
}
void BuildBundle ()
2016-04-21 15:58:45 +03:00
{
Driver.Watch ($"Building app bundle for {Name}", 1);
2016-04-21 15:58:45 +03:00
// First we must build every appex's bundle, otherwise we won't be able
// to copy any frameworks the appex is using to the container app.
foreach (var appex in AppExtensions)
appex.BuildBundle ();
2016-04-21 15:58:45 +03:00
// Make sure we bundle Mono.framework if we need to.
if (PackageMonoFramework == true) {
BundleFileInfo info;
var name = "Frameworks/Mono.framework";
bundle_files [name] = info = new BundleFileInfo ();
info.Sources.Add (GetLibMono (AssemblyBuildTarget.Framework));
}
2016-04-21 15:58:45 +03:00
// Collect files to bundle from every target
if (Targets.Count == 1) {
bundle_files = Targets [0].BundleFiles;
} else {
foreach (var target in Targets) {
foreach (var kvp in target.BundleFiles) {
BundleFileInfo info;
if (!bundle_files.TryGetValue (kvp.Key, out info))
bundle_files [kvp.Key] = info = new BundleFileInfo () { DylibToFramework = kvp.Value.DylibToFramework };
info.Sources.UnionWith (kvp.Value.Sources);
}
2016-04-21 15:58:45 +03:00
}
}
// And from ourselves
var all_assemblies = Targets.SelectMany ((v) => v.Assemblies);
var all_frameworks = Frameworks.Concat (all_assemblies.SelectMany ((v) => v.Frameworks));
var all_weak_frameworks = WeakFrameworks.Concat (all_assemblies.SelectMany ((v) => v.WeakFrameworks));
foreach (var fw in all_frameworks.Concat (all_weak_frameworks)) {
BundleFileInfo info;
if (!Path.GetFileName (fw).EndsWith (".framework", StringComparison.Ordinal))
continue;
var key = $"Frameworks/{Path.GetFileName (fw)}";
if (!bundle_files.TryGetValue (key, out info))
bundle_files [key] = info = new BundleFileInfo ();
info.Sources.Add (fw);
}
// We also need to add any frameworks from extensions
foreach (var appex in AppExtensions) {
foreach (var bf in appex.bundle_files.ToList ()) {
if (!Path.GetFileName (bf.Key).EndsWith (".framework", StringComparison.Ordinal) && !bf.Value.DylibToFramework)
continue;
Driver.Log (3, "Copying {0} to the app's Frameworks directory because it's used by the extension {1}", bf.Key, Path.GetFileName (appex.Name));
var appex_info = bf.Value;
BundleFileInfo info;
if (!bundle_files.TryGetValue (bf.Key, out info))
bundle_files [bf.Key] = info = new BundleFileInfo ();
info.Sources.UnionWith (appex_info.Sources);
if (appex_info.DylibToFramework)
info.DylibToFramework = true;
}
}
// Finally copy all the files & directories
foreach (var kvp in bundle_files) {
var name = kvp.Key;
var info = kvp.Value;
var targetPath = Path.Combine (AppDirectory, name);
var files = info.Sources;
var isFramework = Directory.Exists (files.First ());
if (IsExtension && !IsWatchExtension && (isFramework || info.DylibToFramework))
continue; // Don't copy frameworks to app extensions (except watch extensions), they go into the container app.
if (isFramework) {
// This is a framework
if (files.Count != 1)
throw ErrorHelper.CreateError (99, "Internal error: 'can't lipo directories'. Please file a bug report with a test case (http://bugzilla.xamarin.com).");
if (info.DylibToFramework)
throw ErrorHelper.CreateError (99, "Internal error: 'can't convert frameworks to frameworks'. Please file a bug report with a test case (http://bugzilla.xamarin.com).");
var framework_src = files.First ();
var framework_filename = Path.Combine (framework_src, Path.GetFileNameWithoutExtension (framework_src));
if (!MachO.IsDynamicFramework (framework_filename)) {
Driver.Log (1, "The framework {0} is a framework of static libraries, and will not be copied to the app.", framework_src);
} else {
UpdateDirectory (framework_src, Path.GetDirectoryName (targetPath));
if (IsDeviceBuild) {
// Remove architectures we don't care about.
MachO.SelectArchitectures (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (framework_src)), AllArchitectures);
}
2016-04-21 15:58:45 +03:00
}
} else {
var targetDirectory = Path.GetDirectoryName (targetPath);
if (!IsUptodate (files, new string [] { targetPath })) {
Directory.CreateDirectory (targetDirectory);
if (files.Count == 1) {
CopyFile (files.First (), targetPath);
} else {
var sb = new StringBuilder ();
foreach (var lib in files) {
sb.Append (Driver.Quote (lib));
sb.Append (' ');
}
sb.Append ("-create -output ");
sb.Append (Driver.Quote (targetPath));
Driver.RunLipo (sb.ToString ());
}
if (LibMonoLinkMode == AssemblyBuildTarget.Framework)
Driver.XcodeRun ("install_name_tool", "-change @rpath/libmonosgen-2.0.dylib @rpath/Mono.framework/Mono " + Driver.Quote (targetPath));
} else {
Driver.Log (3, "Target '{0}' is up-to-date.", targetPath);
}
2016-04-21 15:58:45 +03:00
if (info.DylibToFramework) {
var bundleName = Path.GetFileName (name);
CreateFrameworkInfoPList (Path.Combine (targetDirectory, "Info.plist"), bundleName, BundleId + Path.GetFileNameWithoutExtension (bundleName), bundleName);
}
}
2016-04-21 15:58:45 +03:00
}
// If building a fat app, we need to lipo the two different executables we have together
if (IsDeviceBuild) {
if (IsDualBuild) {
if (IsUptodate (new string [] { Targets [0].Executable, Targets [1].Executable }, new string [] { Executable })) {
cached_executable = true;
Driver.Log (3, "Target '{0}' is up-to-date.", Executable);
} else {
var cmd = new StringBuilder ();
foreach (var target in Targets) {
cmd.Append (Driver.Quote (target.Executable));
cmd.Append (' ');
}
cmd.Append ("-create -output ");
cmd.Append (Driver.Quote (Executable));
Driver.RunLipo (cmd.ToString ());
}
} else {
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
cached_executable = Targets [0].CachedExecutable;
}
2016-04-21 15:58:45 +03:00
}
}
public void ExtractNativeLinkInfo ()
{
var exceptions = new List<Exception> ();
foreach (var target in Targets)
target.ExtractNativeLinkInfo (exceptions);
if (exceptions.Count > 0)
throw new AggregateException (exceptions);
Driver.Watch ("Extracted native link info", 1);
}
public void SelectNativeCompiler ()
{
foreach (var t in Targets) {
foreach (var a in t.Assemblies) {
if (a.EnableCxx) {
EnableCxx = true;
break;
}
}
}
Driver.CalculateCompilerPath (this);
2016-04-21 15:58:45 +03:00
}
public string GetLibMono (AssemblyBuildTarget build_target)
{
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
return Path.Combine (Driver.GetMonoTouchLibDirectory (this), "libmonosgen-2.0.a");
case AssemblyBuildTarget.DynamicLibrary:
return Path.Combine (Driver.GetMonoTouchLibDirectory (this), "libmonosgen-2.0.dylib");
case AssemblyBuildTarget.Framework:
return Path.Combine (Driver.GetProductSdkDirectory (this), "Frameworks", "Mono.framework");
default:
throw ErrorHelper.CreateError (100, "Invalid assembly build target: '{0}'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", build_target);
2016-04-21 15:58:45 +03:00
}
}
public string GetLibXamarin (AssemblyBuildTarget build_target)
{
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
return Path.Combine (Driver.GetMonoTouchLibDirectory (this), EnableDebug ? "libxamarin-debug.a" : "libxamarin.a");
case AssemblyBuildTarget.DynamicLibrary:
return Path.Combine (Driver.GetMonoTouchLibDirectory (this), EnableDebug ? "libxamarin-debug.dylib" : "libxamarin.dylib");
case AssemblyBuildTarget.Framework:
return Path.Combine (Driver.GetProductSdkDirectory (this), "Frameworks", EnableDebug ? "Xamarin-debug.framework" : "Xamarin.framework");
default:
throw ErrorHelper.CreateError (100, "Invalid assembly build target: '{0}'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", build_target);
2016-04-21 15:58:45 +03:00
}
}
// this will filter/remove warnings that are not helpful (e.g. complaining about non-matching armv6-6 then armv7-6 on fat binaries)
// and turn the remaining of the warnings into MT5203 that MonoDevelop will be able to report as real warnings (not just logs)
// it will also look for symbol-not-found errors and try to provide useful error messages.
public static void ProcessNativeLinkerOutput (Target target, string output, IEnumerable<string> inputs, List<Exception> errors, bool error)
2016-04-21 15:58:45 +03:00
{
List<string> lines = new List<string> (output.Split (new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
// filter
for (int i = 0; i < lines.Count; i++) {
string line = lines [i];
if (errors.Count > 100)
return;
if (line.Contains ("ld: warning: ignoring file ") &&
line.Contains ("file was built for") &&
line.Contains ("which is not the architecture being linked") &&
// Only ignore warnings related to the object files we've built ourselves (assemblies, main.m, registrar.m)
inputs.Any ((v) => line.Contains (v))) {
continue;
} else if (line.Contains ("ld: symbol(s) not found for architecture") && errors.Count > 0) {
continue;
} else if (line.Contains ("clang: error: linker command failed with exit code 1")) {
continue;
} else if (line.Contains ("was built for newer iOS version (5.1.1) than being linked (5.1)")) {
continue;
}
if (line.Contains ("Undefined symbols for architecture")) {
while (++i < lines.Count) {
line = lines [i];
if (!line.EndsWith (", referenced from:", StringComparison.Ordinal))
2016-04-21 15:58:45 +03:00
break;
var symbol = line.Replace (", referenced from:", "").Trim ('\"', ' ');
if (symbol.StartsWith ("_OBJC_CLASS_$_", StringComparison.Ordinal)) {
2016-04-21 15:58:45 +03:00
errors.Add (new MonoTouchException (5211, error,
"Native linking failed, undefined Objective-C class: {0}. The symbol '{1}' could not be found in any of the libraries or frameworks linked with your application.",
symbol.Replace ("_OBJC_CLASS_$_", ""), symbol));
} else {
var members = target.GetMembersForSymbol (symbol.Substring (1));
if (members != null && members.Count > 0) {
var member = members.First (); // Just report the first one.
2016-04-21 15:58:45 +03:00
// Neither P/Invokes nor fields have IL, so we can't find the source code location.
errors.Add (new MonoTouchException (5214, error,
"Native linking failed, undefined symbol: {0}. " +
"This symbol was referenced by the managed member {1}.{2}. " +
"Please verify that all the necessary frameworks have been referenced and native libraries linked.",
symbol, member.DeclaringType.FullName, member.Name));
} else {
errors.Add (new MonoTouchException (5210, error,
"Native linking failed, undefined symbol: {0}. " +
"Please verify that all the necessary frameworks have been referenced and native libraries are properly linked in.",
symbol));
}
}
// skip all subsequent lines related to the same error.
// we skip all subsequent lines with more indentation than the initial line.
var indent = GetIndentation (line);
while (i + 1 < lines.Count) {
line = lines [i + 1];
if (GetIndentation (lines [i + 1]) <= indent)
break;
i++;
}
}
} else if (line.StartsWith ("duplicate symbol", StringComparison.Ordinal) && line.EndsWith (" in:", StringComparison.Ordinal)) {
2016-04-21 15:58:45 +03:00
var symbol = line.Replace ("duplicate symbol ", "").Replace (" in:", "").Trim ();
errors.Add (new MonoTouchException (5212, error, "Native linking failed, duplicate symbol: '{0}'.", symbol));
var indent = GetIndentation (line);
while (i + 1 < lines.Count) {
line = lines [i + 1];
if (GetIndentation (lines [i + 1]) <= indent)
break;
i++;
errors.Add (new MonoTouchException (5213, error, "Duplicate symbol in: {0} (Location related to previous error)", line.Trim ()));
}
} else {
if (line.StartsWith ("ld: ", StringComparison.Ordinal))
2016-04-21 15:58:45 +03:00
line = line.Substring (4);
line = line.Trim ();
if (error) {
errors.Add (new MonoTouchException (5209, error, "Native linking error: {0}", line));
} else {
errors.Add (new MonoTouchException (5203, error, "Native linking warning: {0}", line));
}
}
}
}
static int GetIndentation (string line)
{
int rv = 0;
if (line.Length == 0)
return 0;
while (true) {
switch (line [rv]) {
case ' ':
case '\t':
rv++;
break;
default:
return rv;
}
};
}
// return the ids found in a macho file
List<Guid> GetUuids (MachOFile file)
{
var result = new List<Guid> ();
foreach (var cmd in file.load_commands) {
if (cmd is UuidCommand) {
var uuidCmd = cmd as UuidCommand;
result.Add (new Guid (uuidCmd.uuid));
}
}
return result;
}
// This method generates the manifest that is required by the symbolication in order to be able to debug the application,
// The following is an example of the manifest to be generated:
// <mono-debug version=”1”>
// <app-id>com.foo.bar</app-id>
// <build-date>datetime</build-date>
// <build-id>build-id</build-id>
// <build-id>build-id</build-id>
// </mono-debug>
// where:
//
// app-id: iOS/Android/Mac app/package ID. Currently for verification and user info only but in future may be used to find symbols automatically.
// build-date: Local time in DateTime “O” format. For user info only.
// build-id: The build UUID. Needed for HockeyApp to find the mSYM folder matching the app build. There may be more than one, as in the case of iOS multi-arch.
void GenerateMSymManifest (Target target, string target_directory)
{
var manifestPath = Path.Combine (target_directory, "manifest.xml");
if (String.IsNullOrEmpty (target_directory))
throw new ArgumentNullException (nameof (target_directory));
var root = new XElement ("mono-debug",
new XAttribute("version", 1),
new XElement ("app-id", BundleId),
new XElement ("build-date", DateTime.Now.ToString ("O")));
var file = MachO.Read (target.Executable);
if (file is MachO) {
var mfile = file as MachOFile;
var uuids = GetUuids (mfile);
foreach (var str in uuids) {
root.Add (new XElement ("build-id", str));
}
} else if (file is IEnumerable<MachOFile>) {
var ffile = file as IEnumerable<MachOFile>;
foreach (var fentry in ffile) {
var uuids = GetUuids (fentry);
foreach (var str in uuids) {
root.Add (new XElement ("build-id", str));
}
}
} else {
// do not write a manifest
return;
}
// Write only if we need to update the manifest
Driver.WriteIfDifferent (manifestPath, root.ToString ());
}
void CopyAotData (string src, string dest)
{
if (string.IsNullOrEmpty (src) || string.IsNullOrEmpty (dest)) {
ErrorHelper.Warning (95, $"Aot files could not be copied to the destination directory {dest}");
return;
}
var dir = new DirectoryInfo (src);
if (!dir.Exists) {
ErrorHelper.Warning (95, $"Aot files could not be copied to the destination directory {dest}");
return;
}
var dirs = dir.GetDirectories ();
if (!Directory.Exists (dest))
Directory.CreateDirectory (dest);
var files = dir.GetFiles ();
foreach (var file in files) {
var tmp = Path.Combine (dest, file.Name);
file.CopyTo (tmp, true);
}
foreach (var subdir in dirs) {
var tmp = Path.Combine (dest, subdir.Name);
CopyAotData (subdir.FullName, tmp);
}
}
2016-04-21 15:58:45 +03:00
public void BuildMSymDirectory ()
{
if (!EnableMSym)
return;
var target_directory = string.Format ("{0}.mSYM", AppDirectory);
if (!Directory.Exists (target_directory))
Directory.CreateDirectory (target_directory);
2016-04-21 15:58:45 +03:00
foreach (var target in Targets) {
GenerateMSymManifest (target, target_directory);
var msymdir = Path.Combine (target.BuildDirectory, "Msym");
// copy aot data must be done BEFORE we do copy the msym one
CopyAotData (msymdir, target_directory);
// copy all assemblies under mvid and with the dll and mdb
var tmpdir = Path.Combine (msymdir, "Msym", "tmp");
if (!Directory.Exists (tmpdir))
Directory.CreateDirectory (tmpdir);
2016-04-21 15:58:45 +03:00
foreach (var asm in target.Assemblies) {
asm.CopyToDirectory (tmpdir, reload: false, only_copy: true);
2016-04-21 15:58:45 +03:00
}
// mono-symbolicate knows best
CopyMSymData (target_directory, tmpdir);
2016-04-21 15:58:45 +03:00
}
}
public void BuildDsymDirectory ()
{
if (!BuildDSym.HasValue)
BuildDSym = IsDeviceBuild;
if (!BuildDSym.Value)
return;
string dsym_dir = string.Format ("{0}.dSYM", AppDirectory);
bool cached_dsym = false;
if (cached_executable)
cached_dsym = IsUptodate (new string [] { Executable }, Directory.EnumerateFiles (dsym_dir, "*", SearchOption.AllDirectories));
if (!cached_dsym) {
if (Directory.Exists (dsym_dir))
Directory.Delete (dsym_dir, true);
Driver.CreateDsym (AppDirectory, ExecutableName, dsym_dir);
} else {
Driver.Log (3, "Target '{0}' is up-to-date.", dsym_dir);
}
Driver.Watch ("Linking DWARF symbols", 1);
}
IEnumerable<string> GetRequiredSymbols ()
{
foreach (var target in Targets) {
foreach (var symbol in target.GetRequiredSymbols ())
yield return symbol;
}
}
bool WriteSymbolList (string filename)
{
var required_symbols = GetRequiredSymbols ().ToArray ();
using (StreamWriter writer = new StreamWriter (filename)) {
foreach (string symbol in required_symbols)
writer.WriteLine ("_{0}", symbol);
foreach (var symbol in NoSymbolStrip)
writer.WriteLine ("_{0}", symbol);
writer.Flush ();
return writer.BaseStream.Position > 0;
}
}
void StripNativeCode (string name)
{
if (NativeStrip && IsDeviceBuild && !EnableDebug && string.IsNullOrEmpty (SymbolList)) {
string symbol_file = Path.Combine (Cache.Location, "symbol-file");
if (WriteSymbolList (symbol_file)) {
Driver.RunStrip (String.Format ("-i -s \"{0}\" \"{1}\"", symbol_file, Executable));
} else {
Driver.RunStrip (String.Format ("\"{0}\"", Executable));
}
Driver.Watch ("Native Strip", 1);
}
if (!string.IsNullOrEmpty (SymbolList))
WriteSymbolList (SymbolList);
}
public void StripNativeCode ()
{
if (IsDualBuild) {
bool cached = true;
foreach (var target in Targets)
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
cached &= target.CachedExecutable;
2016-04-21 15:58:45 +03:00
if (!cached)
StripNativeCode (Executable);
} else {
foreach (var target in Targets) {
[mtouch] Rework how tasks are built. The previous build system kept a forward-pointing single linked list of tasks to execute: task X had a list of subsequent tasks to execute. If task X was up-to-date, it was not created (and the next tasks were directly added to the list of tasks to execute). In this world it became complicated to merge output from tasks (for instance if the output of task X and task Y should be a consumed by a single task producing a single output, since the corresponding task would end up in both X's and Y's list of subsequent tasks). Example: creating a single framework from the aot-compiled output of multiple assemblies. So I've reversed the logic: now we keep track of the final output, and then each task has a list of dependencies that must be built. This makes it trivial to create merging tasks (for the previous example, there could for instance be a CreateFrameworkTask, where its dependencies would be all the corresponding AotTasks). We also always create every task, and then each task decides when its executed whether it should do anything or not. This makes it unnecessary to 'forward- delete' files when creating tasks (say you have three tasks, A, B, C; B depends on A, and C depends on B; if A's output isn't up-to-date, it has to delete its own output if it exists, otherwise B would not detect that it would have to re-execute, because at task *creation* time, B's input hadn't changed). Additionally make it based on async/await, since much of the work happens in externel processes (and we don't need to spin up additional threads just to run external processes). This makes us have less code run on background threads, which makes any issues with thread-safety less likely.
2017-01-26 12:56:55 +03:00
if (!target.CachedExecutable)
2016-04-21 15:58:45 +03:00
StripNativeCode (target.Executable);
}
}
}
public void BundleAssemblies ()
2016-04-21 15:58:45 +03:00
{
var strip = ManagedStrip && IsDeviceBuild && !EnableDebug && !PackageMdb;
var grouped = Targets.SelectMany ((Target t) => t.Assemblies).GroupBy ((Assembly asm) => asm.Identity);
foreach (var @group in grouped) {
var filename = @group.Key;
var assemblies = @group.AsEnumerable ().ToArray ();
var build_target = assemblies [0].BuildTarget;
var size_specific = assemblies.Length > 1 && !Cache.CompareAssemblies (assemblies [0].FullPath, assemblies [1].FullPath, true, true);
[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
if (IsExtension && !IsWatchExtension) {
var codeShared = assemblies.Count ((v) => v.IsCodeShared);
if (codeShared > 0) {
if (codeShared != assemblies.Length)
throw ErrorHelper.CreateError (99, $"Internal error: all assemblies in a joined build target must have the same code sharing options ({string.Join (", ", assemblies.Select ((v) => v.Identity + "=" + v.IsCodeShared))}). Please file a bug report with a test case (http://bugzilla.xamarin.com).");
continue; // These resources will be found in the main app.
}
}
// Determine where to put the assembly
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
case AssemblyBuildTarget.DynamicLibrary:
if (size_specific) {
assemblies [0].CopyToDirectory (assemblies [0].Target.AppTargetDirectory, copy_mdb: PackageMdb, strip: strip, only_copy: true);
assemblies [1].CopyToDirectory (assemblies [1].Target.AppTargetDirectory, copy_mdb: PackageMdb, strip: strip, only_copy: true);
} else {
assemblies [0].CopyToDirectory (AppDirectory, copy_mdb: PackageMdb, strip: strip, only_copy: true);
}
break;
case AssemblyBuildTarget.Framework:
// Put our resources in a subdirectory in the framework
// But don't use 'Resources', because the app ends up being undeployable:
// "PackageInspectionFailed: Failed to load Info.plist from bundle at path /private/var/installd/Library/Caches/com.apple.mobile.installd.staging/temp.CR0vmK/extracted/testapp.app/Frameworks/TestApp.framework"
var target_name = assemblies [0].BuildTargetName;
var resource_directory = Path.Combine (AppDirectory, "Frameworks", $"{target_name}.framework", "MonoBundle");
if (size_specific) {
assemblies [0].CopyToDirectory (Path.Combine (resource_directory, Path.GetFileName (assemblies [0].Target.AppTargetDirectory)), copy_mdb: PackageMdb, strip: strip, only_copy: true);
assemblies [1].CopyToDirectory (Path.Combine (resource_directory, Path.GetFileName (assemblies [1].Target.AppTargetDirectory)), copy_mdb: PackageMdb, strip: strip, only_copy: true);
} else {
assemblies [0].CopyToDirectory (resource_directory, copy_mdb: PackageMdb, strip: strip, only_copy: true);
}
break;
default:
throw ErrorHelper.CreateError (100, "Invalid assembly build target: '{0}'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", build_target);
}
2016-04-21 15:58:45 +03:00
}
}
public void GenerateRuntimeOptions ()
{
// only if the linker is disabled
if (LinkMode != LinkMode.None)
return;
RuntimeOptions.Write (AppDirectory);
}
public void CreateFrameworkInfoPList (string output_path, string framework_name, string bundle_identifier, string bundle_name)
{
var sb = new StringBuilder ();
sb.AppendLine ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.AppendLine ("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
sb.AppendLine ("<plist version=\"1.0\">");
sb.AppendLine ("<dict>");
sb.AppendLine (" <key>CFBundleDevelopmentRegion</key>");
sb.AppendLine (" <string>en</string>");
sb.AppendLine (" <key>CFBundleIdentifier</key>");
sb.AppendLine ($" <string>{bundle_identifier}</string>");
sb.AppendLine (" <key>CFBundleInfoDictionaryVersion</key>");
sb.AppendLine (" <string>6.0</string>");
sb.AppendLine (" <key>CFBundleName</key>");
sb.AppendLine ($" <string>{bundle_name}</string>");
sb.AppendLine (" <key>CFBundlePackageType</key>");
sb.AppendLine (" <string>FMWK</string>");
sb.AppendLine (" <key>CFBundleShortVersionString</key>");
sb.AppendLine (" <string>1.0</string>");
sb.AppendLine (" <key>CFBundleSignature</key>");
sb.AppendLine (" <string>????</string>");
sb.AppendLine (" <key>CFBundleVersion</key>");
sb.AppendLine (" <string>1.0</string>");
sb.AppendLine (" <key>NSPrincipalClass</key>");
sb.AppendLine (" <string></string>");
sb.AppendLine (" <key>CFBundleExecutable</key>");
sb.AppendLine ($" <string>{framework_name}</string>");
sb.AppendLine (" <key>BuildMachineOSBuild</key>");
sb.AppendLine (" <string>13F34</string>");
sb.AppendLine (" <key>CFBundleSupportedPlatforms</key>");
sb.AppendLine (" <array>");
sb.AppendLine ($" <string>{Driver.GetPlatform (this)}</string>");
sb.AppendLine (" </array>");
sb.AppendLine (" <key>DTCompiler</key>");
sb.AppendLine (" <string>com.apple.compilers.llvm.clang.1_0</string>");
sb.AppendLine (" <key>DTPlatformBuild</key>");
sb.AppendLine (" <string>12D508</string>");
sb.AppendLine (" <key>DTPlatformName</key>");
sb.AppendLine ($" <string>{Driver.GetPlatform (this).ToLowerInvariant ()}</string>");
sb.AppendLine (" <key>DTPlatformVersion</key>");
sb.AppendLine ($" <string>{SdkVersions.GetVersion (Platform)}</string>");
sb.AppendLine (" <key>DTSDKBuild</key>");
sb.AppendLine (" <string>12D508</string>");
sb.AppendLine (" <key>DTSDKName</key>");
sb.AppendLine ($" <string>{Driver.GetPlatform (this)}{SdkVersion}</string>");
sb.AppendLine (" <key>DTXcode</key>");
sb.AppendLine (" <string>0620</string>");
sb.AppendLine (" <key>DTXcodeBuild</key>");
sb.AppendLine (" <string>6C131e</string>");
sb.AppendLine (" <key>MinimumOSVersion</key>");
sb.AppendLine ($" <string>{DeploymentTarget.ToString ()}</string>");
sb.AppendLine (" <key>UIDeviceFamily</key>");
sb.AppendLine (" <array>");
switch (Platform) {
case ApplePlatform.iOS:
sb.AppendLine (" <integer>1</integer>");
sb.AppendLine (" <integer>2</integer>");
break;
case ApplePlatform.TVOS:
sb.AppendLine (" <integer>3</integer>");
break;
case ApplePlatform.WatchOS:
sb.AppendLine (" <integer>4</integer>");
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.", Platform);
}
sb.AppendLine (" </array>");
sb.AppendLine ("</dict>");
sb.AppendLine ("</plist>");
Driver.WriteIfDifferent (output_path, sb.ToString ());
}
2016-04-21 15:58:45 +03:00
}
}