xamarin-macios/tools/mtouch/Target.cs

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

2016-04-21 15:58:45 +03:00
// Copyright 2013--2014 Xamarin Inc. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
2016-04-21 15:58:45 +03:00
using MonoTouch.Tuner;
using Mono.Cecil;
using Mono.Tuner;
using Mono.Linker;
using Xamarin.Linker;
using Xamarin.Utils;
using XamCore.Registrar;
2016-04-21 15:58:45 +03:00
namespace Xamarin.Bundler
{
public class BundleFileInfo
{
public HashSet<string> Sources = new HashSet<string> ();
public bool DylibToFramework;
}
2016-04-21 15:58:45 +03:00
public partial class Target {
public string TargetDirectory;
public string AppTargetDirectory;
public MonoTouchManifestResolver ManifestResolver = new MonoTouchManifestResolver ();
public AssemblyDefinition ProductAssembly;
// directories used during the build process
public string ArchDirectory;
public string PreBuildDirectory;
2016-04-21 15:58:45 +03:00
public string BuildDirectory;
public string LinkDirectory;
// Note that each 'Target' can have multiple abis: armv7+armv7s for instance.
public List<Abi> Abis;
public Dictionary<string, BundleFileInfo> BundleFiles = new Dictionary<string, BundleFileInfo> ();
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
Dictionary<Abi, CompileTask> pinvoke_tasks = new Dictionary<Abi, CompileTask> ();
List<CompileTask> link_with_task_output = new List<CompileTask> ();
List<AOTTask> aot_dependencies = new List<AOTTask> ();
List<LinkTask> embeddinator_tasks = new List<LinkTask> ();
[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
CompilerFlags linker_flags;
NativeLinkTask link_task;
2016-04-21 15:58:45 +03:00
// If we didn't link because the existing (cached) assemblyes are up-to-date.
bool cached_link;
// If any assemblies were updated (only set to false if the linker is disabled and no assemblies were modified).
bool any_assembly_updated = true;
// If the assemblies were symlinked.
public bool Symlinked;
public bool Is32Build { get { return Application.IsArchEnabled (Abis, Abi.Arch32Mask); } } // If we're targetting a 32 bit arch for this target.
public bool Is64Build { get { return Application.IsArchEnabled (Abis, Abi.Arch64Mask); } } // If we're targetting a 64 bit arch for this target.
[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 we didn't link the final executable because the existing binary is up-to-date.
public bool CachedExecutable {
get {
if (link_task == null)
return false;
return !link_task.Rebuilt;
}
}
// This is a list of all the architectures we need to build, which may include any architectures
// in any extensions (but not the main app).
List<Abi> all_architectures;
public List<Abi> AllArchitectures {
get {
if (all_architectures == null) {
all_architectures = new List<Abi> ();
var mask = Is32Build ? Abi.Arch32Mask : Abi.Arch64Mask;
foreach (var abi in App.AllArchitectures) {
var a = abi & mask;
if (a != 0)
all_architectures.Add (abi);
}
}
return all_architectures;
}
}
List<Abi> GetArchitectures (AssemblyBuildTarget build_target)
{
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
case AssemblyBuildTarget.DynamicLibrary:
return Abis;
case AssemblyBuildTarget.Framework:
return AllArchitectures;
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);
}
}
public void AddToBundle (string source, string bundle_path = null, bool dylib_to_framework_conversion = false)
{
BundleFileInfo info;
if (bundle_path == null) {
if (source.EndsWith (".framework", StringComparison.Ordinal)) {
var bundle_name = Path.GetFileNameWithoutExtension (source);
bundle_path = $"Frameworks/{bundle_name}.framework";
} else {
bundle_path = Path.GetFileName (source);
}
}
if (!BundleFiles.TryGetValue (bundle_path, out info))
BundleFiles [bundle_path] = info = new BundleFileInfo () { DylibToFramework = dylib_to_framework_conversion };
if (info.DylibToFramework != dylib_to_framework_conversion)
throw ErrorHelper.CreateError (99, "Internal error: 'invalid value for framework conversion'. Please file a bug report with a test case (http://bugzilla.xamarin.com).");
info.Sources.Add (source);
}
[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 LinkWithBuildTarget (AssemblyBuildTarget build_target, string name, CompileTask link_task, IEnumerable<Assembly> assemblies)
{
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
LinkWithTaskOutput (link_task);
break;
case AssemblyBuildTarget.DynamicLibrary:
if (!(!App.HasFrameworksDirectory && assemblies.Any ((asm) => asm.IsCodeShared)))
[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
AddToBundle (link_task.OutputFile);
LinkWithTaskOutput (link_task);
break;
case AssemblyBuildTarget.Framework:
if (!(!App.HasFrameworksDirectory && assemblies.Any ((asm) => asm.IsCodeShared)))
[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
AddToBundle (link_task.OutputFile, $"Frameworks/{name}.framework/{name}", dylib_to_framework_conversion: true);
LinkWithTaskOutput (link_task);
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);
}
}
[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
public void LinkWithTaskOutput (CompileTask task)
{
if (task.SharedLibrary) {
LinkWithDynamicLibrary (task.OutputFile);
} else {
LinkWithStaticLibrary (task.OutputFile);
}
link_with_task_output.Add (task);
}
public void LinkWithTaskOutput (IEnumerable<CompileTask> tasks)
{
foreach (var t in tasks)
LinkWithTaskOutput (t);
}
public void LinkWithStaticLibrary (string path)
{
linker_flags.AddLinkWith (path);
}
public void LinkWithStaticLibrary (IEnumerable<string> paths)
{
linker_flags.AddLinkWith (paths);
}
public void LinkWithFramework (string path)
{
linker_flags.AddFramework (path);
}
[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
public void LinkWithDynamicLibrary (string path)
{
linker_flags.AddLinkWith (path);
}
PInvokeWrapperGenerator pinvoke_state;
PInvokeWrapperGenerator MarshalNativeExceptionsState {
get {
if (!App.RequiresPInvokeWrappers)
return null;
if (pinvoke_state == null) {
pinvoke_state = new PInvokeWrapperGenerator ()
{
App = App,
SourcePath = Path.Combine (ArchDirectory, "pinvokes.m"),
HeaderPath = Path.Combine (ArchDirectory, "pinvokes.h"),
Registrar = (StaticRegistrar) StaticRegistrar,
};
}
return pinvoke_state;
}
}
2016-04-21 15:58:45 +03:00
public string Executable {
get {
return Path.Combine (TargetDirectory, App.ExecutableName);
}
}
public void Initialize (bool show_warnings)
{
// we want to load our own mscorlib[-runtime].dll, not something else we're being feeded
// (e.g. bug #6612) since it might not match the libmono[-sgen].a library we'll link with,
// so load the corlib we want first.
var corlib_path = Path.Combine (Resolver.FrameworkDirectory, "mscorlib.dll");
var corlib = ManifestResolver.Load (corlib_path);
if (corlib == null)
throw new MonoTouchException (2006, true, "Can not load mscorlib.dll from: '{0}'. Please reinstall Xamarin.iOS.", corlib_path);
var roots = new List<AssemblyDefinition> ();
foreach (var root_assembly in App.RootAssemblies) {
var root = ManifestResolver.Load (root_assembly);
if (root == null) {
// We check elsewhere that the path exists, so I'm not sure how we can get into this.
throw ErrorHelper.CreateError (2019, "Can not load the root assembly '{0}'.", root_assembly);
}
roots.Add (root);
}
2016-04-21 15:58:45 +03:00
foreach (var reference in App.References) {
var ad = ManifestResolver.Load (reference);
if (ad == null)
throw new MonoTouchException (2002, true, "Can not resolve reference: {0}", reference);
var root_assembly = roots.FirstOrDefault ((v) => v.MainModule.FileName == ad.MainModule.FileName);
if (root_assembly != null) {
// If we asked the manifest resolver for assembly X and got back a root assembly, it means the requested assembly has the same identity as the root assembly, which is not allowed.
throw ErrorHelper.CreateError (23, "The root assembly {0} conflicts with another assembly ({1}).", root_assembly.MainModule.FileName, reference);
}
2016-04-21 15:58:45 +03:00
if (ad.MainModule.Runtime > TargetRuntime.Net_4_0)
ErrorHelper.Show (new MonoTouchException (11, false, "{0} was built against a more recent runtime ({1}) than Xamarin.iOS supports.", Path.GetFileName (reference), ad.MainModule.Runtime));
// Figure out if we're referencing Xamarin.iOS or monotouch.dll
if (Path.GetFileNameWithoutExtension (ad.MainModule.FileName) == Driver.GetProductAssembly (App))
2016-04-21 15:58:45 +03:00
ProductAssembly = ad;
if (ad != ProductAssembly && GetRealPath (ad.MainModule.FileName) != GetRealPath (reference) && !ad.MainModule.FileName.EndsWith (".resources.dll", StringComparison.Ordinal))
ErrorHelper.Show (ErrorHelper.CreateWarning (109, "The assembly '{0}' was loaded from a different path than the provided path (provided path: {1}, actual path: {2}).", Path.GetFileName (reference), reference, ad.MainModule.FileName));
2016-04-21 15:58:45 +03:00
}
ComputeListOfAssemblies ();
if (App.LinkMode == LinkMode.None && App.I18n != I18nAssemblies.None)
AddI18nAssemblies ();
[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
linker_flags = new CompilerFlags (this);
2016-04-21 15:58:45 +03:00
}
// This is to load the symbols for all assemblies, so that we can give better error messages
// (with file name / line number information).
public void LoadSymbols ()
{
foreach (var a in Assemblies)
a.LoadSymbols ();
}
IEnumerable<AssemblyDefinition> GetAssemblies ()
{
if (App.LinkMode == LinkMode.None)
return ManifestResolver.GetAssemblies ();
2016-04-21 15:58:45 +03:00
List<AssemblyDefinition> assemblies = new List<AssemblyDefinition> ();
if (LinkContext == null) {
// use data from cache
foreach (var assembly in Assemblies)
assemblies.Add (assembly.AssemblyDefinition);
} else {
foreach (var assembly in LinkContext.GetAssemblies ()) {
if (LinkContext.Annotations.GetAction (assembly) == AssemblyAction.Delete)
continue;
assemblies.Add (assembly);
}
}
return assemblies;
}
Dictionary<string, List<MemberReference>> entry_points;
public IDictionary<string, List<MemberReference>> GetEntryPoints ()
2016-04-21 15:58:45 +03:00
{
if (entry_points == null)
GetRequiredSymbols ();
return entry_points;
}
public IEnumerable<string> GetRequiredSymbols ()
{
if (entry_points != null)
return entry_points.Keys;
var cache_location = Path.Combine (App.Cache.Location, "entry-points.txt");
2016-04-21 15:58:45 +03:00
if (cached_link || !any_assembly_updated) {
entry_points = new Dictionary<string, List<MemberReference>> ();
2016-04-21 15:58:45 +03:00
foreach (var ep in File.ReadAllLines (cache_location))
entry_points.Add (ep, null);
} else {
if (LinkContext == null) {
// This happens when using the simlauncher and the msbuild tasks asked for a list
// of symbols (--symbollist). In that case just produce an empty list, since the
// binary shouldn't end up stripped anyway.
entry_points = new Dictionary<string, List<MemberReference>> ();
2016-04-21 15:58:45 +03:00
} else {
entry_points = LinkContext.RequiredSymbols;
if (App.IsCodeShared) {
// Remove symbols from assemblies from extensions that were linked together with the main app.
var filtered_entry_points = new Dictionary<string, List<MemberReference>> ();
foreach (var ep in entry_points) {
var filtered = ep.Value.Where ((v) => Assemblies.ContainsKey (Assembly.GetIdentity (v.Module.FileName))).ToList ();
if (filtered.Count > 0)
filtered_entry_points.Add (ep.Key, filtered);
}
entry_points = filtered_entry_points;
}
2016-04-21 15:58:45 +03:00
}
2016-04-21 15:58:45 +03:00
// keep the debugging helper in debugging binaries only
if (App.EnableDebug && !App.EnableBitCode)
entry_points.Add ("mono_pmip", null);
if (App.IsSimulatorBuild) {
entry_points.Add ("xamarin_dyn_objc_msgSend", null);
entry_points.Add ("xamarin_dyn_objc_msgSendSuper", null);
entry_points.Add ("xamarin_dyn_objc_msgSend_stret", null);
entry_points.Add ("xamarin_dyn_objc_msgSendSuper_stret", null);
}
2016-04-21 15:58:45 +03:00
File.WriteAllText (cache_location, string.Join ("\n", entry_points.Keys.ToArray ()));
}
return entry_points.Keys;
}
public IEnumerable<string> GetRequiredSymbols (Assembly assembly, bool includeObjectiveCClasses)
2016-04-21 15:58:45 +03:00
{
if (entry_points == null)
GetRequiredSymbols ();
foreach (var ep in entry_points) {
if (ep.Value == null)
continue;
foreach (var mr in ep.Value) {
if (mr.Module.Assembly == assembly.AssemblyDefinition)
yield return ep.Key;
}
}
[mtouch] Make sure native symbols from third-party libraries are preserved in dylibs. Fixes #51548. The native linker treats object files (.o) and static libraries (.a files, which are archives of .o files) differently. The native linker will always include object files into the executable: $ echo "void xxx () {}" > foo.m $ clang -c foo.m -o foo.o -arch x86_64 $ ld foo.o -dylib -o foo.dylib -macosx_version_min 10.12 -arch x86_64 $ nm foo.dylib 0000000000000fe0 T _xxx However, if the object file is inside a static library: $ echo "void xxx () {}" > foo.m $ clang -c foo.m -o foo.o -arch x86_64 $ ar cru foo.a foo.o $ ld foo.a -dylib -o foo.dylib -macosx_version_min 10.12 -arch x86_64 $ nm foo.dylib <no output> This means that our testing library (libtest.a) which is a fat library of _object files_, do not show the problems reported in bug #51548. So: a) I've fixed the creation of libtest.a to be a fat library of _static libraries_. This causes the `FastDev_LinkWithTest` test to fail exactly like in bug #51548. b) I've made mtouch pass `-u <native symbol>` to the native linker, for every native symbol referenced in a managed assembly, when creating a dylib. Amazingly this seems to work fine even with symbols to Objective-C classes (`_OBJC_CLASS_$_<class name>`). c) This also required adding support for collecting the Objective-C names of all managed types registered with Objective-C to the linker. The information is already available in the static registrar, but that would require us to make sure the static registrar is executed before compiling dylibs, which means those two tasks won't be able to run in parallel (also there's no guarantee we'll even run the static registrar). https://bugzilla.xamarin.com/show_bug.cgi?id=51548
2017-01-18 12:25:58 +03:00
if (includeObjectiveCClasses) {
foreach (var kvp in LinkContext.ObjectiveCClasses) {
if (kvp.Value.Module.Assembly == assembly.AssemblyDefinition)
yield return $"OBJC_CLASS_$_{kvp.Key}";
}
}
}
public List<MemberReference> GetMembersForSymbol (string symbol)
{
List<MemberReference> rv = null;
2016-04-21 15:58:45 +03:00
entry_points?.TryGetValue (symbol, out rv);
return rv;
}
//
// Gets a flattened list of all the assemblies pulled by the root assembly
//
public void ComputeListOfAssemblies ()
{
var exceptions = new List<Exception> ();
var assemblies = new HashSet<string> ();
var cache_file = Path.Combine (this.ArchDirectory, "assembly-references.txt");
if (File.Exists (cache_file)) {
assemblies.UnionWith (File.ReadAllLines (cache_file));
// Check if any of the referenced assemblies changed after we cached the complete set of references
if (Application.IsUptodate (assemblies, new string [] { cache_file })) {
// Load all the assemblies in the cached list of assemblies
foreach (var assembly in assemblies)
ManifestResolver.Load (assembly);
return;
}
// We must manually find all the references.
assemblies.Clear ();
}
2016-04-21 15:58:45 +03:00
try {
foreach (var root in App.RootAssemblies) {
var assembly = ManifestResolver.Load (root);
ComputeListOfAssemblies (assemblies, assembly, exceptions);
}
2016-04-21 15:58:45 +03:00
} catch (MonoTouchException mte) {
exceptions.Add (mte);
} catch (Exception e) {
exceptions.Add (new MonoTouchException (9, true, e, "Error while loading assemblies: {0}", e.Message));
2016-04-21 15:58:45 +03:00
}
if (App.LinkMode == LinkMode.None)
exceptions.AddRange (ManifestResolver.list);
if (exceptions.Count > 0)
throw new AggregateException (exceptions);
// Cache all the assemblies we found.
Directory.CreateDirectory (Path.GetDirectoryName (cache_file));
File.WriteAllLines (cache_file, assemblies);
2016-04-21 15:58:45 +03:00
}
void ComputeListOfAssemblies (HashSet<string> assemblies, AssemblyDefinition assembly, List<Exception> exceptions)
{
if (assembly == null)
return;
var fqname = assembly.MainModule.FileName;
2016-04-21 15:58:45 +03:00
if (assemblies.Contains (fqname))
return;
assemblies.Add (fqname);
var asm = new Assembly (this, assembly);
asm.ComputeSatellites ();
this.Assemblies.Add (asm);
var main = assembly.MainModule;
foreach (AssemblyNameReference reference in main.AssemblyReferences) {
// Verify that none of the references references an incorrect platform assembly.
switch (reference.Name) {
case "monotouch":
case "Xamarin.iOS":
case "Xamarin.TVOS":
case "Xamarin.WatchOS":
if (reference.Name != Driver.GetProductAssembly (App))
2016-04-21 15:58:45 +03:00
exceptions.Add (ErrorHelper.CreateError (34, "Cannot reference '{0}.dll' in a {1} project - it is implicitly referenced by '{2}'.", reference.Name, Driver.TargetFramework.Identifier, assembly.FullName));
break;
}
var reference_assembly = ManifestResolver.Resolve (reference);
ComputeListOfAssemblies (assemblies, reference_assembly, exceptions);
}
// Custom Attribute metadata can include references to other assemblies, e.g. [X (typeof (Y)],
// but it is not reflected in AssemblyReferences :-( ref: #37611
// so we must scan every custom attribute to look for System.Type
GetCustomAttributeReferences (assembly, assemblies, exceptions);
GetCustomAttributeReferences (main, assemblies, exceptions);
if (main.HasTypes) {
foreach (var ca in main.GetCustomAttributes ())
GetCustomAttributeReferences (ca, assemblies, exceptions);
2016-04-21 15:58:45 +03:00
}
}
void GetCustomAttributeReferences (ICustomAttributeProvider cap, HashSet<string> assemblies, List<Exception> exceptions)
{
if (!cap.HasCustomAttributes)
return;
foreach (var ca in cap.CustomAttributes)
GetCustomAttributeReferences (ca, assemblies, exceptions);
}
void GetCustomAttributeReferences (CustomAttribute ca, HashSet<string> assemblies, List<Exception> exceptions)
{
if (ca.HasConstructorArguments) {
foreach (var arg in ca.ConstructorArguments)
GetCustomAttributeArgumentReference (arg, assemblies, exceptions);
}
if (ca.HasFields) {
foreach (var arg in ca.Fields)
GetCustomAttributeArgumentReference (arg.Argument, assemblies, exceptions);
}
if (ca.HasProperties) {
foreach (var arg in ca.Properties)
GetCustomAttributeArgumentReference (arg.Argument, assemblies, exceptions);
2016-04-21 15:58:45 +03:00
}
}
void GetCustomAttributeArgumentReference (CustomAttributeArgument arg, HashSet<string> assemblies, List<Exception> exceptions)
{
if (!arg.Type.Is ("System", "Type"))
return;
var ar = (arg.Value as TypeReference)?.Scope as AssemblyNameReference;
if (ar == null)
return;
var reference_assembly = ManifestResolver.Resolve (ar);
ComputeListOfAssemblies (assemblies, reference_assembly, exceptions);
}
bool IncludeI18nAssembly (Mono.Linker.I18nAssemblies assembly)
{
return (App.I18n & assembly) != 0;
}
public void AddI18nAssemblies ()
{
Assemblies.Add (LoadI18nAssembly ("I18N"));
if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.CJK))
Assemblies.Add (LoadI18nAssembly ("I18N.CJK"));
if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.MidEast))
Assemblies.Add (LoadI18nAssembly ("I18N.MidEast"));
if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.Other))
Assemblies.Add (LoadI18nAssembly ("I18N.Other"));
if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.Rare))
Assemblies.Add (LoadI18nAssembly ("I18N.Rare"));
if (IncludeI18nAssembly (Mono.Linker.I18nAssemblies.West))
Assemblies.Add (LoadI18nAssembly ("I18N.West"));
}
Assembly LoadI18nAssembly (string name)
{
var assembly = ManifestResolver.Resolve (AssemblyNameReference.Parse (name));
return new Assembly (this, assembly);
}
[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 LinkAssemblies (out List<AssemblyDefinition> assemblies, string output_dir, IEnumerable<Target> sharedCodeTargets)
2016-04-21 15:58:45 +03:00
{
var cache = Resolver.ToResolverCache ();
[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
var resolver = new AssemblyResolver (cache);
2016-04-21 15:58:45 +03:00
resolver.AddSearchDirectory (Resolver.RootDirectory);
resolver.AddSearchDirectory (Resolver.FrameworkDirectory);
[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
var main_assemblies = new List<AssemblyDefinition> ();
foreach (var root in App.RootAssemblies)
main_assemblies.Add (Resolver.Load (root));
foreach (var appex in sharedCodeTargets) {
foreach (var root in appex.App.RootAssemblies)
main_assemblies.Add (Resolver.Load (root));
}
[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 (Driver.Verbosity > 0)
Console.WriteLine ("Linking {0} into {1} using mode '{2}'", string.Join (", ", main_assemblies.Select ((v) => v.MainModule.FileName)), output_dir, App.LinkMode);
LinkerOptions = new LinkerOptions {
[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
MainAssemblies = main_assemblies,
2016-04-21 15:58:45 +03:00
OutputDirectory = output_dir,
LinkMode = App.LinkMode,
Resolver = resolver,
SkippedAssemblies = App.LinkSkipped,
I18nAssemblies = App.I18n,
LinkSymbols = true,
LinkAway = App.LinkAway,
ExtraDefinitions = App.Definitions,
Device = App.IsDeviceBuild,
// by default we keep the code to ensure we're executing on the UI thread (for UI code) for debug builds
// but this can be overridden to either (a) remove it from debug builds or (b) keep it in release builds
EnsureUIThread = App.ThreadCheck.HasValue ? App.ThreadCheck.Value : App.EnableDebug,
DebugBuild = App.EnableDebug,
Arch = Is64Build ? 8 : 4,
IsDualBuild = App.IsDualBuild,
DumpDependencies = App.LinkerDumpDependencies,
RuntimeOptions = App.RuntimeOptions,
MarshalNativeExceptionsState = MarshalNativeExceptionsState,
[mtouch] Make sure native symbols from third-party libraries are preserved in dylibs. Fixes #51548. The native linker treats object files (.o) and static libraries (.a files, which are archives of .o files) differently. The native linker will always include object files into the executable: $ echo "void xxx () {}" > foo.m $ clang -c foo.m -o foo.o -arch x86_64 $ ld foo.o -dylib -o foo.dylib -macosx_version_min 10.12 -arch x86_64 $ nm foo.dylib 0000000000000fe0 T _xxx However, if the object file is inside a static library: $ echo "void xxx () {}" > foo.m $ clang -c foo.m -o foo.o -arch x86_64 $ ar cru foo.a foo.o $ ld foo.a -dylib -o foo.dylib -macosx_version_min 10.12 -arch x86_64 $ nm foo.dylib <no output> This means that our testing library (libtest.a) which is a fat library of _object files_, do not show the problems reported in bug #51548. So: a) I've fixed the creation of libtest.a to be a fat library of _static libraries_. This causes the `FastDev_LinkWithTest` test to fail exactly like in bug #51548. b) I've made mtouch pass `-u <native symbol>` to the native linker, for every native symbol referenced in a managed assembly, when creating a dylib. Amazingly this seems to work fine even with symbols to Objective-C classes (`_OBJC_CLASS_$_<class name>`). c) This also required adding support for collecting the Objective-C names of all managed types registered with Objective-C to the linker. The information is already available in the static registrar, but that would require us to make sure the static registrar is executed before compiling dylibs, which means those two tasks won't be able to run in parallel (also there's no guarantee we'll even run the static registrar). https://bugzilla.xamarin.com/show_bug.cgi?id=51548
2017-01-18 12:25:58 +03:00
Target = this,
2016-04-21 15:58:45 +03:00
};
MonoTouch.Tuner.Linker.Process (LinkerOptions, out LinkContext, out assemblies);
2016-04-21 15:58:45 +03:00
Driver.Watch ("Link Assemblies", 1);
}
[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
bool linked;
2016-04-21 15:58:45 +03:00
public void ManagedLink ()
{
[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 (linked)
return;
2016-04-21 15:58:45 +03:00
var cache_path = Path.Combine (ArchDirectory, "linked-assemblies.txt");
[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
// Get all the Target instances we're sharing code with. Make sure to only select targets with matching pointer size.
var sharingTargets = App.SharedCodeApps.SelectMany ((v) => v.Targets).Where ((v) => v.Is32Build == Is32Build).ToList ();
var allTargets = new List<Target> ();
allTargets.Add (this); // We want ourselves first in this list.
allTargets.AddRange (sharingTargets);
// Include any assemblies from appex's we're sharing code with.
foreach (var target in sharingTargets) {
var targetAssemblies = target.Assemblies.ToList (); // We need to clone the list of assemblies, since we'll be modifying the original
foreach (var asm in targetAssemblies) {
Assembly main_asm;
if (!Assemblies.TryGetValue (asm.Identity, out main_asm)) {
// The appex has an assembly that's not present in the main app.
// Re-load it into the main app.
main_asm = new Assembly (this, asm.FullPath);
main_asm.LoadAssembly (main_asm.FullPath);
Assemblies.Add (main_asm);
Driver.Log (1, "Added '{0}' from {1} to the set of assemblies to be linked.", main_asm.Identity, Path.GetFileNameWithoutExtension (target.App.AppDirectory));
} else {
asm.IsCodeShared = true;
}
// Use the same AOT information between both Assembly instances.
target.Assemblies [main_asm.Identity].AotInfos = main_asm.AotInfos;
main_asm.IsCodeShared = true;
}
}
2016-04-21 15:58:45 +03:00
foreach (var a in Assemblies)
a.CopyToDirectory (LinkDirectory, false, check_case: true);
// Check if we can use a previous link result.
[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
var cached_output = new Dictionary<string, List<string>> ();
2016-04-21 15:58:45 +03:00
if (!Driver.Force) {
if (File.Exists (cache_path)) {
[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
using (var reader = new StreamReader (cache_path)) {
string line;
while ((line = reader.ReadLine ()) != null) {
var colon = line.IndexOf (':');
if (colon == -1)
continue;
var appex = line.Substring (0, colon);
var asm = line.Substring (colon + 1);
List<string> asms;
if (!cached_output.TryGetValue (appex, out asms))
cached_output [appex] = asms = new List<string> ();
asms.Add (asm);
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
var cache_valid = true;
foreach (var target in allTargets) {
List<string> cached_files;
if (!cached_output.TryGetValue (target.App.AppDirectory, out cached_files)) {
cache_valid = false;
Driver.Log (2, $"The cached assemblies are not valid because there are no cached assemblies for {target.App.Name}.");
break;
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
var outputs = new List<string> ();
var inputs = new List<string> (cached_files);
foreach (var input in inputs.ToArray ()) {
var output = Path.Combine (PreBuildDirectory, Path.GetFileName (input));
outputs.Add (output);
if (File.Exists (input + ".mdb")) {
// Debug files can change without the assemblies themselves changing
// This should also invalidate the cached linker results, since the non-linked mdbs can't be copied.
inputs.Add (input + ".mdb");
outputs.Add (output + ".mdb");
2016-04-21 15:58:45 +03:00
}
var pdb = Path.ChangeExtension (input, "pdb");
if (File.Exists (pdb)) {
inputs.Add (pdb);
outputs.Add (Path.ChangeExtension (output, "pdb"));
}
if (File.Exists (input + ".config")) {
// If a config file changes, then the AOT-compiled output can be different,
// so make sure to take config files into account as well.
inputs.Add (input + ".config");
outputs.Add (output + ".config");
}
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
if (!cache_valid)
break;
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
if (!Application.IsUptodate (inputs, outputs)) {
Driver.Log (2, $"The cached assemblies are not valid because some of the assemblies in {target.App.Name} are out-of-date.");
cache_valid = false;
break;
}
2016-04-21 15:58:45 +03:00
}
2017-02-01 17:52:19 +03:00
cached_link = cache_valid;
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
List<AssemblyDefinition> output_assemblies;
if (cached_link) {
Driver.Log (2, $"Reloading cached assemblies.");
output_assemblies = new List<AssemblyDefinition> ();
foreach (var file in cached_output.Values.SelectMany ((v) => v).Select ((v) => Path.GetFileName (v)).Distinct ())
output_assemblies.Add (Resolver.Load (Path.Combine (PreBuildDirectory, file)));
[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
Driver.Watch ("Cached assemblies reloaded", 1);
Driver.Log ("Cached assemblies reloaded.");
} else {
// Load the assemblies into memory.
foreach (var a in Assemblies)
a.LoadAssembly (a.FullPath);
// Link!
LinkAssemblies (out output_assemblies, PreBuildDirectory, sharingTargets);
}
2016-04-21 15:58:45 +03:00
// Verify that we don't get multiple identical assemblies from the linker.
foreach (var group in output_assemblies.GroupBy ((v) => v.Name.Name)) {
if (group.Count () != 1)
throw ErrorHelper.CreateError (99, "Internal error {0}. Please file a bug report with a test case (http://bugzilla.xamarin.com).",
$"The linker output contains more than one assemblies named '{group.Key}':\n\t{string.Join ("\n\t", group.Select ((v) => v.MainModule.FileName).ToArray ())}");
}
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
// Update (add/remove) list of assemblies in each app, since the linker may have both added and removed assemblies.
// The logic for updating assemblies when doing code-sharing is not equivalent to when we're not code sharing
// (in particular code sharing is not supported when there are xml linker definitions), so we need
// to maintain two paths here.
if (sharingTargets.Count == 0) {
Assemblies.Update (this, output_assemblies);
} else {
// For added assemblies we have to determine exactly which apps need which assemblies.
// Code sharing is only allowed if there are no linker xml definitions, nor any I18N values, which means that
// we can limit ourselves to iterate over assembly references to create the updated list of assemblies.
foreach (var t in allTargets) {
// Find the root assembly
// Here we assume that 'AssemblyReference.Name' == 'Assembly.Identity'.
var rootAssemblies = new List<Assembly> ();
foreach (var root in t.App.RootAssemblies)
rootAssemblies.Add (t.Assemblies [Assembly.GetIdentity (root)]);
[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
var queue = new Queue<string> ();
var collectedNames = new HashSet<string> ();
// First collect the set of all assemblies in the app by walking the assembly references.
foreach (var root in rootAssemblies)
queue.Enqueue (root.Identity);
[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
do {
var next = queue.Dequeue ();
collectedNames.Add (next);
var ad = output_assemblies.SingleOrDefault ((AssemblyDefinition v) => v.Name.Name == next);
if (ad == null)
throw ErrorHelper.CreateError (99, "Internal error {0}. Please file a bug report with a test case (http://bugzilla.xamarin.com).", $"The assembly {next} was referenced by another assembly, but at the same time linked out by the linker.");
[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 (ad.MainModule.HasAssemblyReferences) {
foreach (var ar in ad.MainModule.AssemblyReferences) {
if (!collectedNames.Contains (ar.Name) && !queue.Contains (ar.Name))
queue.Enqueue (ar.Name);
}
}
} while (queue.Count > 0);
// Now update the assembly collection
var appexAssemblies = collectedNames.Select ((v) => output_assemblies.Single ((v2) => v2.Name.Name == v));
t.Assemblies.Update (t, appexAssemblies);
// And make sure every Target's assembly resolver knows about all the assemblies.
foreach (var asm in t.Assemblies)
t.Resolver.Add (asm.AssemblyDefinition);
}
2016-04-21 15:58:45 +03:00
// If any of the appex'es build to a grouped SDK framework, then we must ensure that all SDK assemblies
// in that appex are also in the container app.
foreach (var st in sharingTargets) {
if (!st.App.ContainsGroupedSdkAssemblyBuildTargets)
2016-04-21 15:58:45 +03:00
continue;
foreach (var asm in st.Assemblies.Where ((v) => Profile.IsSdkAssembly (v.AssemblyDefinition) || Profile.IsProductAssembly (v.AssemblyDefinition))) {
if (!Assemblies.ContainsKey (asm.Identity)) {
Driver.Log (2, $"The SDK assembly {asm.Identity} will be included in the app because it's referenced by the extension {st.App.Name}");
Assemblies.Add (asm);
}
}
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
// Write the input files to the cache
using (var writer = new StreamWriter (cache_path, false)) {
foreach (var target in allTargets) {
foreach (var asm in target.Assemblies) {
writer.WriteLine ($"{target.App.AppDirectory}:{asm.FullPath}");
}
}
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
// Now the assemblies are in PreBuildDirectory, and they need to be in the BuildDirectory for the AOT compiler.
foreach (var t in allTargets) {
foreach (var a in t.Assemblies) {
// All these assemblies are in the main app's PreBuildDirectory.
a.FullPath = Path.Combine (PreBuildDirectory, a.FileName);
// The linker can copy files (and not update timestamps), and then we run into this sequence:
// * We run the linker, nothing changes, so the linker copies
// all files to the PreBuild directory, with timestamps intact.
// * This means that for instance SDK assemblies will have the original
// timestamp from their installed location, and the exe will have the
// timestamp of when it was built.
// * mtouch is executed again for some reason, and none of the input assemblies changed.
// We'll still re-execute the linker, because at least one of the input assemblies
// (the .exe) has a newer timestamp than some of the assemblies in the PreBuild directory.
// So here we manually touch all the assemblies we have, to make sure their timestamps
// change (this is us saying 'we know these files are up-to-date at this point in time').
if (!cached_link) {
Driver.Touch (a.FullPath);
if (File.Exists (a.FullPath + ".mdb"))
Driver.Touch (a.FullPath + ".mdb");
var pdb = Path.ChangeExtension (a.FullPath, "pdb");
if (File.Exists (pdb))
Driver.Touch (pdb);
}
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
// Now copy to the build directory
var target = Path.Combine (BuildDirectory, a.FileName);
if (!a.CopyAssembly (a.FullPath, target))
Driver.Log (3, "Target '{0}' is up-to-date.", target);
a.FullPath = target;
}
}
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
// Set the 'linked' flag for the targets sharing code, so that this method can be called
// again, and it won't do anything for the appex's sharing code with the main app (but
// will still work for any appex's not sharing code).
allTargets.ForEach ((v) => v.linked = true);
2016-04-21 15:58:45 +03:00
}
public void ProcessAssemblies ()
{
//
// * Linking
// Copy assemblies to LinkDirectory
// Link and save to PreBuildDirectory
// If marshalling native exceptions:
// * Generate/calculate P/Invoke wrappers and save to PreBuildDirectory
2016-04-21 15:58:45 +03:00
// [AOT assemblies in BuildDirectory]
// Strip managed code save to TargetDirectory (or just copy the file if stripping is disabled).
//
// * No linking
// If marshalling native exceptions:
// Generate/calculate P/Invoke wrappers and save to PreBuildDirectory.
// If not marshalling native exceptions:
// Copy assemblies to PreBuildDirectory
// Copy unmodified assemblies to BuildDirectory
2016-04-21 15:58:45 +03:00
// [AOT assemblies in BuildDirectory]
// Strip managed code save to TargetDirectory (or just copy the file if stripping is disabled).
//
// Note that we end up copying assemblies around quite much,
// this is because we we're comparing contents instead of
// filestamps, so we need the previous file around to be
// able to do the actual comparison. For instance: in the
// 'No linking' case above, we copy the assembly to PreBuild
// before removing the resources and saving that result to Build.
// The copy in PreBuild is required for the next build iteration,
// to see if the original assembly has been modified or not (the
// file in the Build directory might be different due to resource
// removal even if the original assembly didn't change).
//
// This can probably be improved by storing digests/hashes instead
// of the entire files, but this turned out a bit messy when
// trying to make it work with the linker, so I decided to go for
// simple file copying for now.
//
//
// Other notes:
//
// * We need all assemblies in the same directory when doing AOT-compilation.
// * We cannot overwrite in-place, because it will mess up dependency tracking
// and besides if we overwrite in place we might not be able to ignore
// insignificant changes (such as only a GUID change - the code is identical,
// but we still need the original assembly since the AOT-ed image also stores
// the GUID, and we fail at runtime if the GUIDs in the assembly and the AOT-ed
// image don't match - if we overwrite in-place we lose the original assembly and
// its GUID).
//
LinkDirectory = Path.Combine (ArchDirectory, "Link");
if (!Directory.Exists (LinkDirectory))
Directory.CreateDirectory (LinkDirectory);
PreBuildDirectory = Path.Combine (ArchDirectory, "PreBuild");
if (!Directory.Exists (PreBuildDirectory))
Directory.CreateDirectory (PreBuildDirectory);
2016-04-21 15:58:45 +03:00
BuildDirectory = Path.Combine (ArchDirectory, "Build");
if (!Directory.Exists (BuildDirectory))
Directory.CreateDirectory (BuildDirectory);
if (!Directory.Exists (TargetDirectory))
Directory.CreateDirectory (TargetDirectory);
ManagedLink ();
GatherFrameworks ();
2016-04-21 15:58:45 +03:00
}
public void CompilePInvokeWrappers ()
{
if (!App.RequiresPInvokeWrappers)
return;
if (!App.HasFrameworksDirectory && App.IsCodeShared)
return;
// Write P/Invokes
var state = MarshalNativeExceptionsState;
if (state.Started) {
// The generator is 'started' by the linker, which means it may not
// be started if the linker was not executed due to re-using cached results.
state.End ();
}
2016-04-21 15:58:45 +03:00
var ifile = state.SourcePath;
var mode = App.LibPInvokesLinkMode;
foreach (var abi in GetArchitectures (mode)) {
var arch = abi.AsArchString ();
string ofile;
2016-04-21 15:58:45 +03:00
switch (mode) {
case AssemblyBuildTarget.StaticObject:
ofile = Path.Combine (App.Cache.Location, arch, "libpinvokes.a");
break;
case AssemblyBuildTarget.DynamicLibrary:
ofile = Path.Combine (App.Cache.Location, arch, "libpinvokes.dylib");
break;
case AssemblyBuildTarget.Framework:
ofile = Path.Combine (App.Cache.Location, arch, "Xamarin.PInvokes.framework", "Xamarin.PInvokes");
var plist_path = Path.Combine (Path.GetDirectoryName (ofile), "Info.plist");
var fw_name = Path.GetFileNameWithoutExtension (ofile);
App.CreateFrameworkInfoPList (plist_path, fw_name, App.BundleId + ".frameworks." + fw_name, fw_name);
break;
default:
throw ErrorHelper.CreateError (100, "Invalid assembly build target: '{0}'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", mode);
}
var pinvoke_task = new PinvokesTask
{
Target = this,
Abi = abi,
InputFile = ifile,
OutputFile = ofile,
SharedLibrary = mode != AssemblyBuildTarget.StaticObject,
Language = "objective-c++",
};
if (pinvoke_task.SharedLibrary) {
if (mode == AssemblyBuildTarget.Framework) {
var name = Path.GetFileNameWithoutExtension (ifile);
pinvoke_task.InstallName = $"@rpath/{name}.framework/{name}";
AddToBundle (pinvoke_task.OutputFile, $"Frameworks/{name}.framework/{name}", dylib_to_framework_conversion: true);
} else {
pinvoke_task.InstallName = $"@rpath/{Path.GetFileName (ofile)}";
AddToBundle (pinvoke_task.OutputFile);
}
pinvoke_task.CompilerFlags.AddFramework ("Foundation");
pinvoke_task.CompilerFlags.LinkWithXamarin ();
}
[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
pinvoke_tasks.Add (abi, pinvoke_task);
[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
LinkWithTaskOutput (pinvoke_task);
}
2016-04-21 15:58:45 +03:00
}
public void SelectStaticRegistrar ()
2016-04-21 15:58:45 +03:00
{
switch (App.Registrar) {
case RegistrarMode.Static:
case RegistrarMode.Dynamic:
case RegistrarMode.Default:
StaticRegistrar = new StaticRegistrar (this)
{
LinkContext = LinkContext,
};
break;
}
}
2016-04-21 15:58:45 +03:00
[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 AOTCompile ()
{
if (App.IsSimulatorBuild)
return;
// Here we create the tasks to run the AOT compiler.
foreach (var a in Assemblies) {
foreach (var abi in GetArchitectures (a.BuildTarget)) {
[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
a.CreateAOTTask (abi);
}
}
// Group the assemblies according to their target name, and link everything together accordingly.
var grouped = Assemblies.GroupBy ((arg) => arg.BuildTargetName);
foreach (var @group in grouped) {
var name = @group.Key;
var assemblies = @group.AsEnumerable ().ToArray ();
[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
// We ensure elsewhere that all assemblies in a group have the same build target.
var build_target = assemblies [0].BuildTarget;
foreach (var abi in GetArchitectures (build_target)) {
Driver.Log (2, "Building {0} from {1}", name, string.Join (", ", assemblies.Select ((arg1) => Path.GetFileName (arg1.FileName))));
[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
string install_name;
string compiler_output;
var compiler_flags = new CompilerFlags (this);
var link_dependencies = new List<CompileTask> ();
[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
var infos = assemblies.Select ((asm) => asm.AotInfos [abi]).ToList ();
var aottasks = infos.Select ((info) => info.Task);
[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
var existingLinkTask = infos.Where ((v) => v.LinkTask != null).Select ((v) => v.LinkTask).ToList ();
if (existingLinkTask.Count > 0) {
if (existingLinkTask.Count != infos.Count)
throw ErrorHelper.CreateError (99, "Internal error: {0}. Please file a bug report with a test case (http://bugzilla.xamarin.com).", $"Not all assemblies for {name} have link tasks");
if (!existingLinkTask.All ((v) => v == existingLinkTask [0]))
throw ErrorHelper.CreateError (99, "Internal error: {0}. Please file a bug report with a test case (http://bugzilla.xamarin.com).", $"Link tasks for {name} aren't all the same");
LinkWithBuildTarget (build_target, name, existingLinkTask [0], assemblies);
continue;
}
[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
// We have to compile any source files to object files before we can link.
var sources = infos.SelectMany ((info) => info.AsmFiles);
[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 (sources.Count () > 0) {
foreach (var src in sources) {
// We might have to convert .s to bitcode assembly (.ll) first
var assembly = src;
BitCodeifyTask bitcode_task = null;
if (App.EnableAsmOnlyBitCode) {
bitcode_task = new BitCodeifyTask ()
{
Input = assembly,
OutputFile = Path.ChangeExtension (assembly, ".ll"),
Platform = App.Platform,
Abi = abi,
DeploymentTarget = App.DeploymentTarget,
};
bitcode_task.AddDependency (aottasks);
[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
assembly = bitcode_task.OutputFile;
}
// Compile assembly code (either .s or .ll) to object file
var compile_task = new CompileTask
{
Target = this,
SharedLibrary = false,
InputFile = assembly,
OutputFile = Path.ChangeExtension (assembly, ".o"),
Abi = abi,
Language = bitcode_task != null ? null : "assembler",
};
compile_task.AddDependency (bitcode_task);
compile_task.AddDependency (aottasks);
[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
link_dependencies.Add (compile_task);
}
} else {
aot_dependencies.AddRange (aottasks);
[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
}
var arch = abi.AsArchString ();
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
LinkWithTaskOutput (link_dependencies); // Any .s or .ll files from the AOT compiler (compiled to object files)
foreach (var info in infos) {
LinkWithStaticLibrary (info.ObjectFiles);
LinkWithStaticLibrary (info.BitcodeFiles);
}
[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
continue; // no linking to do here.
case AssemblyBuildTarget.DynamicLibrary:
install_name = $"@rpath/lib{name}.dylib";
compiler_output = Path.Combine (App.Cache.Location, arch, $"lib{name}.dylib");
break;
case AssemblyBuildTarget.Framework:
install_name = $"@rpath/{name}.framework/{name}";
compiler_output = Path.Combine (App.Cache.Location, arch, name);
break;
[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
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);
}
CompileTask pinvoke_task;
if (pinvoke_tasks.TryGetValue (abi, out pinvoke_task))
link_dependencies.Add (pinvoke_task);
foreach (var info in infos) {
compiler_flags.AddLinkWith (info.ObjectFiles);
compiler_flags.AddLinkWith (info.BitcodeFiles);
}
[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
foreach (var task in link_dependencies)
compiler_flags.AddLinkWith (task.OutputFile);
foreach (var a in assemblies) {
compiler_flags.AddFrameworks (a.Frameworks, a.WeakFrameworks);
compiler_flags.AddLinkWith (a.LinkWith, a.ForceLoad);
compiler_flags.AddOtherFlags (a.LinkerFlags);
if (a.HasLinkWithAttributes && !App.EnableBitCode)
compiler_flags.ReferenceSymbols (GetRequiredSymbols (a, true));
}
if (App.Embeddinator) {
if (!string.IsNullOrEmpty (App.UserGccFlags))
compiler_flags.AddOtherFlag (App.UserGccFlags);
}
[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
compiler_flags.LinkWithMono ();
compiler_flags.LinkWithXamarin ();
if (GetEntryPoints ().ContainsKey ("UIApplicationMain"))
compiler_flags.AddFramework ("UIKit");
if (App.EnableLLVMOnlyBitCode) {
// The AOT compiler doesn't optimize the bitcode so clang will do it
compiler_flags.AddOtherFlag ("-fexceptions");
var optimizations = assemblies.Select ((a) => App.GetLLVMOptimizations (a)).Where ((opt) => opt != null).Distinct ().ToList ();
if (optimizations.Count == 0) {
[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
compiler_flags.AddOtherFlag ("-O2");
} else if (optimizations.Count == 1) {
compiler_flags.AddOtherFlag (optimizations [0]);
} else {
throw ErrorHelper.CreateError (107, "The assemblies '{0}' have different custom LLVM optimizations ('{1}'), which is not allowed when they are all compiled to a single binary.", string.Join (", ", assemblies.Select ((v) => v.Identity)), string.Join ("', '", optimizations));
[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
}
}
var link_task = new LinkTask ()
{
Target = this,
Abi = abi,
OutputFile = compiler_output,
InstallName = install_name,
CompilerFlags = compiler_flags,
Language = compiler_output.EndsWith (".s", StringComparison.Ordinal) ? "assembler" : null,
SharedLibrary = build_target != AssemblyBuildTarget.StaticObject,
};
link_task.AddDependency (link_dependencies);
link_task.AddDependency (aottasks);
[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 (App.Embeddinator) {
link_task.AddDependency (link_with_task_output);
link_task.CompilerFlags.AddLinkWith (link_with_task_output.Select ((v) => v.OutputFile));
embeddinator_tasks.Add (link_task);
}
[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
LinkWithBuildTarget (build_target, name, link_task, assemblies);
[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
foreach (var info in infos)
info.LinkTask = link_task;
[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
}
}
// Code in one assembly (either in a P/Invoke or a third-party library) can depend on a third-party library in another assembly.
// This means that we must always build assemblies only when all their dependent assemblies have been built, so that
// we can link (natively) with the frameworks/dylibs for those dependent assemblies.
// Fortunately we can cheat a bit, since this can (currently at least) only happen for assemblies that
// have third-party libraries. This means that we only enforce this order for any assemblies that depend
// on other assemblies that have third-party libraries.
// Example:
// * We can build System.dll and mscorlib.dll in parallel, even if System.dll depends on mscorlib.dll,
// because we know that mscorlib.dll does not have any third-party libraries.
if (Assemblies.All ((arg) => arg.HasDependencyMap)) {
var dict = Assemblies.ToDictionary ((arg) => Path.GetFileNameWithoutExtension (arg.FileName));
foreach (var asm in Assemblies) {
if (!asm.HasDependencyMap)
continue;
if (asm.BuildTarget == AssemblyBuildTarget.StaticObject)
continue;
if (Profile.IsSdkAssembly (asm.AssemblyDefinition) || Profile.IsProductAssembly (asm.AssemblyDefinition)) {
//Console.WriteLine ("SDK assembly, so skipping assembly dependency checks: {0}", Path.GetFileNameWithoutExtension (asm.FileName));
continue;
}
HashSet<Assembly> dependent_assemblies = new HashSet<Assembly> ();
foreach (var dep in asm.DependencyMap) {
Assembly dependentAssembly;
if (!dict.TryGetValue (Path.GetFileNameWithoutExtension (dep), out dependentAssembly)) {
//Console.WriteLine ("Could not find dependency '{0}' of '{1}'", dep, asm.Identity);
continue;
}
if (asm == dependentAssembly)
continue; // huh?
// Nothing can depend on anything in our SDK, nor does our SDK depend on anything else in our SDK
// So we can remove any SDK dependency
if (Profile.IsSdkAssembly (dependentAssembly.AssemblyDefinition) || Profile.IsProductAssembly (dependentAssembly.AssemblyDefinition)) {
//Console.WriteLine ("SDK assembly, so not a dependency of anything: {0}", Path.GetFileNameWithoutExtension (dependentAssembly.FileName));
continue;
}
if (!dependentAssembly.HasLinkWithAttributes) {
//Console.WriteLine ("Assembly {0} does not have LinkWith attributes, so there's nothing we can depend on.", dependentAssembly.Identity);
continue;
}
if (dependentAssembly.BuildTargetName == asm.BuildTargetName) {
//Console.WriteLine ("{0} is a dependency of {1}, but both are being built into the same target, so no dependency added.", Path.GetFileNameWithoutExtension (dep), Path.GetFileNameWithoutExtension (asm.FileName));
continue;
}
//Console.WriteLine ("Added {0} as a dependency of {1}", Path.GetFileNameWithoutExtension (dep), Path.GetFileNameWithoutExtension (asm.FileName));
dependent_assemblies.Add (dependentAssembly);
}
// Circular dependencies shouldn't happen, but still make sure, since it's technically possible
// for users to do it.
foreach (var abi in GetArchitectures (asm.BuildTarget)) {
var target_task = asm.AotInfos [abi].LinkTask;
var dependent_tasks = dependent_assemblies.Select ((v) => v.AotInfos [abi].LinkTask);
var stack = new Stack<BuildTask> ();
foreach (var dep in dependent_tasks) {
stack.Clear ();
stack.Push (target_task);
if (target_task == dep || IsCircularTask (target_task, stack, dep)) {
Driver.Log ("Found circular task.");
Driver.Log ("Task {0} (with output {1}) depends on:", target_task.GetType ().Name, target_task.Outputs.First ());
stack = new Stack<BuildTask> (stack.Reverse ());
while (stack.Count > 0) {
var node = stack.Pop ();
Driver.Log (" -> {0} (Output: {1})", node.GetType ().Name, node.Outputs.First ());
}
} else {
target_task.AddDependency (dep);
target_task.CompilerFlags.AddLinkWith (dep.OutputFile);
}
}
}
}
}
}
bool IsCircularTask (BuildTask root, Stack<BuildTask> stack, BuildTask task)
{
stack.Push (task);
foreach (var d in task?.Dependencies) {
if (stack.Contains (d))
return true;
if (IsCircularTask (root, stack, d))
return true;
}
stack.Pop ();
return false;
[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
}
public void Compile ()
{
// Compute the dependency map, and show warnings if there are any problems.
List<Exception> exceptions = new List<Exception> ();
foreach (var a in Assemblies)
a.ComputeDependencyMap (exceptions);
if (exceptions.Count > 0) {
ErrorHelper.Show (exceptions);
ErrorHelper.Warning (3006, "Could not compute a complete dependency map for the project. This will result in slower build times because Xamarin.iOS can't properly detect what needs to be rebuilt (and what does not need to be rebuilt). Please review previous warnings for more details.");
2016-04-21 15:58:45 +03:00
}
List<string> registration_methods = new List<string> ();
2016-04-21 15:58:45 +03:00
// The static registrar.
if (App.Registrar == RegistrarMode.Static) {
2016-04-21 15:58:45 +03:00
var registrar_m = Path.Combine (ArchDirectory, "registrar.m");
var registrar_h = Path.Combine (ArchDirectory, "registrar.h");
var run_registrar_task = new RunRegistrarTask
{
Target = this,
RegistrarCodePath = registrar_m,
RegistrarHeaderPath = registrar_h,
};
2016-04-21 15:58:45 +03:00
foreach (var abi in GetArchitectures (AssemblyBuildTarget.StaticObject)) {
var arch = abi.AsArchString ();
var ofile = Path.Combine (App.Cache.Location, arch, Path.GetFileNameWithoutExtension (registrar_m) + ".o");
var registrar_task = new CompileRegistrarTask
{
Target = this,
Abi = abi,
InputFile = registrar_m,
OutputFile = ofile,
RegistrarCodePath = registrar_m,
RegistrarHeaderPath = registrar_h,
SharedLibrary = false,
Language = "objective-c++",
};
[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
registrar_task.AddDependency (run_registrar_task);
// This is because iOS has a forward declaration of NSPortMessage, but no actual declaration.
// They still use NSPortMessage in other API though, so it can't just be removed from our bindings.
registrar_task.CompilerFlags.AddOtherFlag ("-Wno-receiver-forward-class");
[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
LinkWithTaskOutput (registrar_task);
2016-04-21 15:58:45 +03:00
}
registration_methods.Add ("xamarin_create_classes");
2016-04-21 15:58:45 +03:00
}
if (App.Registrar == RegistrarMode.Dynamic && App.IsSimulatorBuild && App.LinkMode == LinkMode.None) {
2016-04-21 15:58:45 +03:00
string method;
string library;
switch (App.Platform) {
case ApplePlatform.iOS:
method = "xamarin_create_classes_Xamarin_iOS";
library = "Xamarin.iOS.registrar.a";
break;
case ApplePlatform.WatchOS:
method = "xamarin_create_classes_Xamarin_WatchOS";
library = "Xamarin.WatchOS.registrar.a";
break;
case ApplePlatform.TVOS:
method = "xamarin_create_classes_Xamarin_TVOS";
library = "Xamarin.TVOS.registrar.a";
break;
default:
throw ErrorHelper.CreateError (71, "Unknown platform: {0}. This usually indicates a bug in Xamarin.iOS; please file a bug report at http://bugzilla.xamarin.com with a test case.", App.Platform);
}
registration_methods.Add (method);
[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
LinkWithStaticLibrary (Path.Combine (Driver.GetProductSdkDirectory (App), "usr", "lib", library));
2016-04-21 15:58:45 +03:00
}
// The main method.
foreach (var abi in GetArchitectures (AssemblyBuildTarget.StaticObject)) {
var arch = abi.AsArchString ();
var main_m = Path.Combine (App.Cache.Location, arch, "main.m");
2016-04-21 15:58:45 +03:00
var generate_main_task = new GenerateMainTask
{
Target = this,
Abi = abi,
MainM = main_m,
RegistrationMethods = registration_methods,
};
var main_o = Path.Combine (App.Cache.Location, arch, "main.o");
var main_task = new CompileMainTask
{
Target = this,
Abi = abi,
InputFile = main_m,
OutputFile = main_o,
SharedLibrary = false,
Language = "objective-c++",
};
[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
main_task.AddDependency (generate_main_task);
main_task.CompilerFlags.AddDefine ("MONOTOUCH");
[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
LinkWithTaskOutput (main_task);
2016-04-21 15:58:45 +03:00
}
// Compile the managed assemblies into object files, frameworks or shared libraries
AOTCompile ();
2016-04-21 15:58:45 +03:00
Driver.Watch ("Compile", 1);
}
[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
public void NativeLink (BuildTasks build_tasks)
2016-04-21 15:58:45 +03:00
{
if (App.Embeddinator && App.IsDeviceBuild) {
build_tasks.AddRange (embeddinator_tasks);
return;
}
2016-04-21 15:58:45 +03:00
if (!string.IsNullOrEmpty (App.UserGccFlags))
App.DeadStrip = false;
if (App.EnableLLVMOnlyBitCode)
App.DeadStrip = false;
// Get global frameworks
linker_flags.AddFrameworks (App.Frameworks, App.WeakFrameworks);
linker_flags.AddFrameworks (Frameworks, WeakFrameworks);
2016-04-21 15:58:45 +03:00
// Collect all LinkWith flags and frameworks from all assemblies.
foreach (var a in Assemblies) {
linker_flags.AddFrameworks (a.Frameworks, a.WeakFrameworks);
if (a.BuildTarget == AssemblyBuildTarget.StaticObject)
linker_flags.AddLinkWith (a.LinkWith, a.ForceLoad);
linker_flags.AddOtherFlags (a.LinkerFlags);
[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 (a.BuildTarget == AssemblyBuildTarget.StaticObject) {
foreach (var abi in GetArchitectures (a.BuildTarget)) {
[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
AotInfo info;
if (!a.AotInfos.TryGetValue (abi, out info))
continue;
linker_flags.AddLinkWith (info.BitcodeFiles);
linker_flags.AddLinkWith (info.ObjectFiles);
}
}
2016-04-21 15:58:45 +03:00
}
var bitcode = App.EnableBitCode;
if (bitcode)
linker_flags.AddOtherFlag (App.EnableMarkerOnlyBitCode ? "-fembed-bitcode-marker" : "-fembed-bitcode");
2016-04-21 15:58:45 +03:00
if (App.EnablePie.HasValue && App.EnablePie.Value && (App.DeploymentTarget < new Version (4, 2)))
ErrorHelper.Error (28, "Cannot enable PIE (-pie) when targeting iOS 4.1 or earlier. Please disable PIE (-pie:false) or set the deployment target to at least iOS 4.2");
if (!App.EnablePie.HasValue)
App.EnablePie = true;
if (App.Platform == ApplePlatform.iOS) {
if (App.EnablePie.Value && (App.DeploymentTarget >= new Version (4, 2))) {
linker_flags.AddOtherFlag ("-Wl,-pie");
2016-04-21 15:58:45 +03:00
} else {
linker_flags.AddOtherFlag ("-Wl,-no_pie");
2016-04-21 15:58:45 +03:00
}
}
CompileTask.GetArchFlags (linker_flags, Abis);
2016-04-21 15:58:45 +03:00
if (App.IsDeviceBuild) {
linker_flags.AddOtherFlag ($"-m{Driver.GetTargetMinSdkName (App)}-version-min={App.DeploymentTarget}");
linker_flags.AddOtherFlag ($"-isysroot {Driver.Quote (Driver.GetFrameworkDirectory (App))}");
2016-04-21 15:58:45 +03:00
} else {
CompileTask.GetSimulatorCompilerFlags (linker_flags, false, App);
2016-04-21 15:58:45 +03:00
}
linker_flags.LinkWithMono ();
if (App.LibMonoLinkMode != AssemblyBuildTarget.StaticObject)
AddToBundle (App.GetLibMono (App.LibMonoLinkMode));
linker_flags.LinkWithXamarin ();
if (App.LibXamarinLinkMode != AssemblyBuildTarget.StaticObject)
AddToBundle (App.GetLibXamarin (App.LibXamarinLinkMode));
2016-04-21 15:58:45 +03:00
linker_flags.AddOtherFlag ($"-o {Driver.Quote (Executable)}");
2016-04-21 15:58:45 +03:00
bool need_libcpp = false;
2016-04-21 15:58:45 +03:00
if (App.EnableBitCode)
need_libcpp = true;
#if ENABLE_BITCODE_ON_IOS
need_libcpp = true;
#endif
if (need_libcpp)
linker_flags.AddOtherFlag ("-lc++");
2016-04-21 15:58:45 +03:00
// allow the native linker to remove unused symbols (if the caller was removed by the managed linker)
if (!bitcode) {
// Note that we include *all* (__Internal) p/invoked symbols here
// We also include any fields from [Field] attributes.
linker_flags.ReferenceSymbols (GetRequiredSymbols ());
2016-04-21 15:58:45 +03:00
}
var libdir = Path.Combine (Driver.GetProductSdkDirectory (App), "usr", "lib");
if (App.Embeddinator) {
linker_flags.AddOtherFlag ("-shared");
linker_flags.AddOtherFlag ($"-install_name {Driver.Quote ($"@rpath/{App.ExecutableName}.framework/{App.ExecutableName}")}");
2016-04-21 15:58:45 +03:00
} else {
string mainlib;
if (App.IsWatchExtension) {
mainlib = "libwatchextension.a";
linker_flags.AddOtherFlag (" -e _xamarin_watchextension_main");
} else if (App.IsTVExtension) {
mainlib = "libtvextension.a";
} else if (App.IsExtension) {
mainlib = "libextension.a";
} else {
mainlib = "libapp.a";
}
var libmain = Path.Combine (libdir, mainlib);
linker_flags.AddLinkWith (libmain, true);
2016-04-21 15:58:45 +03:00
}
if (App.EnableProfiling) {
string libprofiler;
switch (App.LibProfilerLinkMode) {
case AssemblyBuildTarget.DynamicLibrary:
2016-04-21 15:58:45 +03:00
libprofiler = Path.Combine (libdir, "libmono-profiler-log.dylib");
linker_flags.AddLinkWith (libprofiler);
if (App.HasFrameworksDirectory)
[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
AddToBundle (libprofiler);
break;
case AssemblyBuildTarget.StaticObject:
2016-04-21 15:58:45 +03:00
libprofiler = Path.Combine (libdir, "libmono-profiler-log.a");
linker_flags.AddLinkWith (libprofiler);
2016-04-21 15:58:45 +03:00
if (!App.EnableBitCode)
linker_flags.ReferenceSymbol ("mono_profiler_startup_log");
break;
case AssemblyBuildTarget.Framework: // We don't ship the profiler as a framework, so this should be impossible.
default:
throw ErrorHelper.CreateError (100, "Invalid assembly build target: '{0}'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", App.LibProfilerLinkMode);
2016-04-21 15:58:45 +03:00
}
}
if (!string.IsNullOrEmpty (App.UserGccFlags))
linker_flags.AddOtherFlag (App.UserGccFlags);
2016-04-21 15:58:45 +03:00
if (App.DeadStrip)
linker_flags.AddOtherFlag ("-dead_strip");
2016-04-21 15:58:45 +03:00
if (App.IsExtension) {
if (App.Platform == ApplePlatform.iOS && Driver.XcodeVersion.Major < 7) {
linker_flags.AddOtherFlag ("-lpkstart");
linker_flags.AddOtherFlag ($"-F {Driver.Quote (Path.Combine (Driver.GetFrameworkDirectory (App), "System/Library/PrivateFrameworks"))} -framework PlugInKit");
2016-04-21 15:58:45 +03:00
}
linker_flags.AddOtherFlag ("-fapplication-extension");
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
link_task = new NativeLinkTask
{
Target = this,
OutputFile = Executable,
CompilerFlags = linker_flags,
};
[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
link_task.AddDependency (link_with_task_output);
link_task.AddDependency (aot_dependencies);
build_tasks.Add (link_task);
2016-04-21 15:58:45 +03:00
}
public void AdjustDylibs ()
2016-04-21 15:58:45 +03:00
{
var sb = new StringBuilder ();
foreach (var dependency in Xamarin.MachO.GetNativeDependencies (Executable)) {
if (!dependency.StartsWith ("/System/Library/PrivateFrameworks/", StringComparison.Ordinal))
continue;
var fixed_dep = dependency.Replace ("/PrivateFrameworks/", "/Frameworks/");
sb.Append (" -change ").Append (dependency).Append (' ').Append (fixed_dep);
}
if (sb.Length > 0) {
var quoted_name = Driver.Quote (Executable);
sb.Append (' ').Append (quoted_name);
Driver.XcodeRun ("install_name_tool", sb.ToString ());
sb.Clear ();
}
}
public bool CanWeSymlinkTheApplication ()
{
if (!Driver.CanWeSymlinkTheApplication (App))
2016-04-21 15:58:45 +03:00
return false;
foreach (var a in Assemblies)
if (!a.CanSymLinkForApplication ())
return false;
return true;
}
public void Symlink ()
{
foreach (var a in Assemblies)
a.Symlink ();
var targetExecutable = Executable;
Application.TryDelete (targetExecutable);
try {
var launcher = new StringBuilder ();
launcher.Append (Path.Combine (Driver.MonoTouchDirectory, "bin", "simlauncher"));
if (Is32Build)
launcher.Append ("32");
else if (Is64Build)
launcher.Append ("64");
2016-04-21 15:58:45 +03:00
launcher.Append ("-sgen");
File.Copy (launcher.ToString (), Executable);
File.SetLastWriteTime (Executable, DateTime.Now);
2016-04-21 15:58:45 +03:00
} catch (MonoTouchException) {
throw;
} catch (Exception ex) {
throw new MonoTouchException (1015, true, ex, "Failed to create the executable '{0}': {1}", targetExecutable, ex.Message);
}
Symlinked = true;
if (Driver.Verbosity > 0)
Console.WriteLine ("Application ({0}) was built using fast-path for simulator.", string.Join (", ", Abis.ToArray ()));
}
}
}