[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
This commit is contained in:
Rolf Bjarne Kvinge 2017-01-24 11:10:20 +01:00
Родитель 85f28fbd59
Коммит 7e28df59c4
7 изменённых файлов: 444 добавлений и 135 удалений

Просмотреть файл

@ -60,12 +60,18 @@ namespace Xamarin.Bundler {
public MarshalManagedExceptionMode MarshalManagedExceptions;
public bool IsDefaultMarshalManagedExceptionMode;
public string RootAssembly;
public List<Application> SharedCodeApps = new List<Application> (); // List of appexes we're sharing code with.
public string RegistrarOutputLibrary;
public static int Concurrency => Driver.Concurrency;
public Version DeploymentTarget;
public Version SdkVersion;
// This is just a name for this app to show in log/error messages, etc.
public string Name {
get { return Path.GetFileNameWithoutExtension (AppDirectory); }
}
public bool RequiresPInvokeWrappers {
get {
#if MTOUCH

Просмотреть файл

@ -545,12 +545,14 @@ namespace Xamarin.Bundler {
// new assembly
var asm = new Assembly (target, assembly);
Add (asm);
Driver.Log (1, "The linker added the assembly '{0}'.", asm.Identity);
Driver.Log (1, "The linker added the assembly '{0}' to '{1}'.", asm.Identity, target.App.Name);
} else {
this [identity].AssemblyDefinition = assembly;
}
}
foreach (var removed in current) {
Driver.Log (1, "The linker linked away the assembly '{0}'.", this [removed].Identity);
Driver.Log (1, "The linker linked away the assembly '{0}' from '{1}'.", this [removed].Identity, target.App.Name);
Remove (removed);
}
}

Просмотреть файл

@ -118,6 +118,7 @@ namespace Xamarin.Bundler {
public bool? PackageMonoFramework;
public bool NoFastSim;
public bool NoDevCodeShare;
// The list of assemblies that we do generate debugging info for.
public bool DebugAll;
@ -180,6 +181,20 @@ namespace Xamarin.Bundler {
}
}
public bool HasAnyDynamicLibraries {
get {
if (LibMonoLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibXamarinLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibPInvokesLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
if (LibProfilerLinkMode == AssemblyBuildTarget.DynamicLibrary)
return true;
return HasDynamicLibraries;
}
}
public bool HasFrameworks {
get {
return assembly_build_targets.Any ((abt) => abt.Value.Item1 == AssemblyBuildTarget.Framework);
@ -680,7 +695,20 @@ namespace Xamarin.Bundler {
return false;
}
public void Build ()
public void BuildAll ()
{
var allapps = new List<Application> ();
allapps.Add (this); // We need to build the main app first, so that any extensions sharing code can reference frameworks built in the main app.
allapps.AddRange (AppExtensions);
allapps.ForEach ((v) => v.BuildInitialize ());
DetectCodeSharing ();
allapps.ForEach ((v) => v.BuildManaged ());
allapps.ForEach ((v) => v.BuildNative ());
allapps.ForEach ((v) => v.BuildEnd ());
}
void BuildInitialize ()
{
if (Driver.Force) {
Driver.Log (3, "A full rebuild has been forced by the command line argument -f.");
@ -696,8 +724,15 @@ namespace Xamarin.Bundler {
SelectRegistrar ();
ExtractNativeLinkInfo ();
SelectNativeCompiler ();
ProcessAssemblies ();
}
void BuildManaged ()
{
ProcessAssemblies ();
}
void BuildNative ()
{
// Everything that can be parallelized is put into a list of tasks,
// which are then executed at the end.
build_tasks = new BuildTasks ();
@ -774,6 +809,154 @@ namespace Xamarin.Bundler {
}
}
void DetectCodeSharing ()
{
if (AppExtensions.Count == 0)
return;
if (!IsDeviceBuild)
return;
if (NoDevCodeShare) {
Driver.Log (2, "Native code sharing has been disabled in the main app, so no code sharing with extensions will occur.");
return;
}
// No I18N assemblies can be included
if (I18n != Mono.Linker.I18nAssemblies.None) {
Driver.Log (2, "Native code sharing has been disabled, because the container app includes I18N assemblies ({0}).", I18n);
return;
}
List<Application> candidates = new List<Application> ();
foreach (var appex in AppExtensions) {
if (appex.IsWatchExtension)
continue;
if (appex.NoDevCodeShare) {
Driver.Log (2, "Native code sharing has been disabled in the extension {0}, so no code sharing with the main will occur for this extension.", appex.Name);
continue;
}
bool applicable = true;
// The --assembly-build-target arguments must be identical.
// We can probably lift this requirement (at least partially) at some point,
// but for now it makes our code simpler.
if (assembly_build_targets.Count != appex.assembly_build_targets.Count) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
continue;
}
foreach (var key in assembly_build_targets.Keys) {
Tuple<AssemblyBuildTarget, string> appex_value;
if (!appex.assembly_build_targets.TryGetValue (key, out appex_value)) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
applicable = false;
break;
}
var value = assembly_build_targets [key];
if (value.Item1 != appex_value.Item1 || value.Item2 != appex_value.Item2) {
Driver.Log (2, "The extension '{0}' has different --assembly-build-target arguments, and can therefore not share code with the main app.", appex.Name);
applicable = false;
break;
}
}
if (!applicable)
continue;
// No I18N assemblies can be included
if (appex.I18n != Mono.Linker.I18nAssemblies.None) {
Driver.Log (2, "The extension '{0}' includes I18N assemblies ({1}), and can therefore not share native code with the main app.", appex.Name, appex.I18n);
continue;
}
// All arguments to the AOT compiler must be identical
if (AotArguments != appex.AotArguments) {
Driver.Log (2, "The extension '{0}' has different arguments to the AOT compiler (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.AotArguments, AotArguments);
continue;
}
if (AotOtherArguments != appex.AotOtherArguments) {
Driver.Log (2, "The extension '{0}' has different other arguments to the AOT compiler (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.AotOtherArguments, AotOtherArguments);
continue;
}
if (IsLLVM != appex.IsLLVM) {
Driver.Log (2, "The extension '{0}' does not have the same LLVM option as the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.IsLLVM, IsLLVM);
continue;
}
if (LinkMode != appex.LinkMode) {
Driver.Log (2, "The extension '{0}' does not have the same managed linker option as the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, appex.LinkMode, LinkMode);
continue;
}
if (LinkMode != LinkMode.None) {
var linkskipped_same = !LinkSkipped.Except (appex.LinkSkipped).Any () && !appex.LinkSkipped.Except (LinkSkipped).Any ();
if (!linkskipped_same) {
Driver.Log (2, "The extension '{0}' is asking the managed linker to skip different assemblies than the main app (extension: {1}, main app: {2}), and can therefore not share native code with the main app.", appex.Name, string.Join (", ", appex.LinkSkipped), string.Join (", ", LinkSkipped));
continue;
}
if (Definitions.Count > 0) {
Driver.Log (2, "The app '{0}' has xml definitions for the managed linker ({1}), and can therefore not share native code with any extension.", Name, string.Join (", ", Definitions));
continue;
}
if (appex.Definitions.Count > 0) {
Driver.Log (2, "The extension '{0}' has xml definitions for the managed linker ({1}), and can therefore not share native code with the main app.", appex.Name, string.Join (", ", appex.Definitions));
continue;
}
}
// Check that the Abis are matching
foreach (var abi in appex.Abis) {
var matching = abis.FirstOrDefault ((v) => (v & Abi.ArchMask) == (abi & Abi.ArchMask));
if (matching == Abi.None) {
// Example: extension has arm64+armv7, while the main app has only arm64.
Driver.Log (2, "The extension '{0}' is targeting the abi '{1}' (which the main app is not targeting), and can therefore not share native code with the main app.", appex.Name, abi);
applicable = false;
break;
} else if (matching != abi) {
// Example: extension has arm64+llvm, while the main app has only arm64.
Driver.Log (2, "The extension '{0}' is targeting the abi '{1}', which is not compatible with the main app's corresponding abi '{2}', and can therefore not share native code with the main app.", appex.Name, abi, matching);
applicable = false;
break;
}
}
// Check if there aren't referenced assemblies from different sources
foreach (var target in Targets) {
var appexTarget = appex.Targets.Single ((v) => v.Is32Build == target.Is32Build);
foreach (var kvp in appexTarget.Assemblies.Hashed) {
Assembly asm;
if (!target.Assemblies.TryGetValue (kvp.Key, out asm))
continue; // appex references an assembly the main app doesn't. This is fine.
if (asm.FullPath != kvp.Value.FullPath) {
applicable = false; // app references an assembly with the same name as the main app, but from a different location. This is not fine. Should we emit a real warning here, or just a log message?
Driver.Log (2, "The extension '{0}' is referencing the assembly '{1}' from '{2}', while the main app references it from '{3}', and can therefore not share native code with the main app.", appex.Name, asm.Identity, kvp.Value.FullPath, asm.FullPath);
break;
}
}
}
if (!applicable)
continue;
if (!applicable)
continue;
candidates.Add (appex);
Driver.Log (2, "The main app and the extension '{0}' will share code.", appex.Name);
}
if (candidates.Count > 0)
SharedCodeApps.AddRange (candidates);
}
void Initialize ()
{
if (EnableDebug && IsLLVM)
@ -1706,6 +1889,16 @@ namespace Xamarin.Bundler {
var build_target = assemblies [0].BuildTarget;
var size_specific = assemblies.Length > 1 && !Cache.CompareAssemblies (assemblies [0].FullPath, assemblies [1].FullPath, true, true);
if (IsExtension && !IsWatchExtension) {
var codeShared = assemblies.Count ((v) => v.IsCodeShared);
if (codeShared > 0) {
if (codeShared != assemblies.Length)
throw ErrorHelper.CreateError (99, $"Internal error: all assemblies in a joined build target must have the same code sharing options ({string.Join (", ", assemblies.Select ((v) => v.Identity + "=" + v.IsCodeShared))}). Please file a bug report with a test case (http://bugzilla.xamarin.com).");
continue; // These resources will be found in the main app.
}
}
// Determine where to put the assembly
switch (build_target) {
case AssemblyBuildTarget.StaticObject:

Просмотреть файл

@ -32,6 +32,7 @@ namespace Xamarin.Bundler {
{
public AssemblyBuildTarget BuildTarget;
public string BuildTargetName;
public bool IsCodeShared;
public Dictionary<Abi, AotInfo> AotInfos = new Dictionary<Abi, AotInfo> ();
@ -209,6 +210,10 @@ namespace Xamarin.Bundler {
*/
public void CreateAOTTask (Abi abi)
{
// Check if we've already created the AOT tasks.
if (AotInfos.ContainsKey (abi))
return;
var assembly_path = FullPath;
var build_dir = Path.GetDirectoryName (assembly_path);
var arch = abi.AsArchString ();

Просмотреть файл

@ -116,6 +116,11 @@ namespace MonoTouch.Tuner {
return assembly;
}
public void Add (AssemblyDefinition assembly)
{
cache [Path.GetFileNameWithoutExtension (assembly.MainModule.FileName)] = assembly;
}
public AssemblyDefinition Resolve (string fullName)
{
return Resolve (AssemblyNameReference.Parse (fullName), null);

Просмотреть файл

@ -126,6 +126,27 @@ namespace Xamarin.Bundler
info.Sources.Add (source);
}
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.IsExtension && assemblies.Any ((asm) => asm.IsCodeShared)))
AddToBundle (link_task.OutputFile);
LinkWithTaskOutput (link_task);
break;
case AssemblyBuildTarget.Framework:
if (!(App.IsExtension && assemblies.Any ((asm) => asm.IsCodeShared)))
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);
}
}
public void LinkWithTaskOutput (CompileTask task)
{
if (task.SharedLibrary) {
@ -481,21 +502,24 @@ namespace Xamarin.Bundler
return new Assembly (this, assembly);
}
public void LinkAssemblies (out List<AssemblyDefinition> assemblies, string output_dir)
public void LinkAssemblies (out List<AssemblyDefinition> assemblies, string output_dir, IEnumerable<Target> sharedCodeTargets)
{
if (Driver.Verbosity > 0)
Console.WriteLine ("Linking {0} into {1} using mode '{2}'", App.RootAssembly, output_dir, App.LinkMode);
var cache = Resolver.ToResolverCache ();
var resolver = cache != null
? new AssemblyResolver (cache)
: new AssemblyResolver ();
var resolver = new AssemblyResolver (cache);
resolver.AddSearchDirectory (Resolver.RootDirectory);
resolver.AddSearchDirectory (Resolver.FrameworkDirectory);
var main_assemblies = new List<AssemblyDefinition> ();
main_assemblies.Add (Resolver.Load (App.RootAssembly));
foreach (var appex in sharedCodeTargets)
main_assemblies.Add (Resolver.Load (appex.App.RootAssembly));
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 {
MainAssemblies = new [] { Resolver.Load (App.RootAssembly) },
MainAssemblies = main_assemblies,
OutputDirectory = output_dir,
LinkMode = App.LinkMode,
Resolver = resolver,
@ -522,116 +546,203 @@ namespace Xamarin.Bundler
Driver.Watch ("Link Assemblies", 1);
}
bool linked;
public void ManagedLink ()
{
if (linked)
return;
var cache_path = Path.Combine (ArchDirectory, "linked-assemblies.txt");
// 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;
}
}
foreach (var a in Assemblies)
a.CopyToDirectory (LinkDirectory, false, check_case: true);
// Check if we can use a previous link result.
var cached_output = new Dictionary<string, List<string>> ();
if (!Driver.Force) {
var input = new List<string> ();
var output = new List<string> ();
var cached_output = new List<string> ();
if (File.Exists (cache_path)) {
cached_output.AddRange (File.ReadAllLines (cache_path));
var cached_loaded = new HashSet<string> ();
// Only add the previously linked assemblies (and their satellites) as the input/output assemblies.
// Do not add assemblies which the linker process removed.
foreach (var a in Assemblies) {
if (!cached_output.Contains (a.FullPath))
continue;
cached_loaded.Add (a.FullPath);
input.Add (a.FullPath);
output.Add (Path.Combine (PreBuildDirectory, a.FileName));
if (File.Exists (a.FullPath + ".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.
input.Add (a.FullPath + ".mdb");
output.Add (Path.Combine (PreBuildDirectory, a.FileName) + ".mdb");
}
if (a.Satellites != null) {
foreach (var s in a.Satellites) {
input.Add (s);
output.Add (Path.Combine (PreBuildDirectory, Path.GetFileName (Path.GetDirectoryName (s)), Path.GetFileName (s)));
// No need to copy satellite mdb files, satellites are resource-only assemblies.
}
}
}
// The linker might have added assemblies that weren't specified/reachable
// from the command line arguments (such as I18N assemblies). Those are not
// in the Assemblies list at this point (since we haven't run the linker yet)
// so make sure we take those into account as well.
var not_loaded = cached_output.Except (cached_loaded);
foreach (var path in not_loaded) {
input.Add (path);
output.Add (Path.Combine (PreBuildDirectory, Path.GetFileName (path)));
}
// Include mtouch here too?
// input.Add (Path.Combine (MTouch.MonoTouchDirectory, "usr", "bin", "mtouch"));
if (Application.IsUptodate (input, output)) {
cached_link = true;
foreach (var a in Assemblies.ToList ()) {
if (!cached_output.Contains (a.FullPath)) {
Assemblies.Remove (a);
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);
}
}
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;
}
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");
}
// Load the cached assembly
a.LoadAssembly (Path.Combine (PreBuildDirectory, a.FileName));
Driver.Log (3, "Target '{0}' is up-to-date.", a.FullPath);
}
foreach (var path in not_loaded) {
var a = new Assembly (this, path);
a.LoadAssembly (Path.Combine (PreBuildDirectory, a.FileName));
Assemblies.Add (a);
if (!cache_valid)
break;
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;
}
}
Driver.Watch ("Cached assemblies reloaded", 1);
Driver.Log ("Cached assemblies reloaded.");
return;
if (cache_valid) {
// FIXME: this doesn't update related files (.config, satellite assemblies) if they were modified.
//allTargets.ForEach ((v) => v.linked = true);
this.cached_link = cache_valid;
}
}
}
// Load the assemblies into memory.
foreach (var a in Assemblies)
a.LoadAssembly (a.FullPath);
List<AssemblyDefinition> linked_assemblies_definitions;
LinkAssemblies (out linked_assemblies_definitions, PreBuildDirectory);
// Update (add/remove) the assemblies, since the linker may have both added and removed assemblies.
Assemblies.Update (this, linked_assemblies_definitions);
// Make the assemblies point to the right path.
foreach (var a in Assemblies) {
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').
Driver.Touch (a.GetRelatedFiles ());
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).Distinct ())
output_assemblies.Add (Resolver.Load (Path.Combine (PreBuildDirectory, Path.GetFileName (file))));
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);
}
List<string> linked_assemblies = linked_assemblies_definitions.Select ((v) => v.MainModule.FileName).ToList ();
File.WriteAllText (cache_path, string.Join ("\n", linked_assemblies));
// 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 rootAssembly = t.Assemblies [Assembly.GetIdentity (t.App.RootAssembly)];
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.
queue.Enqueue (rootAssembly.Identity);
do {
var next = queue.Dequeue ();
collectedNames.Add (next);
var ad = output_assemblies.Single ((AssemblyDefinition v) => v.Name.Name == next);
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);
}
}
// 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}");
}
}
}
// 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.GetRelatedFiles ());
// 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;
}
}
// 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);
}
public void ProcessAssemblies ()
@ -701,15 +812,6 @@ namespace Xamarin.Bundler
ManagedLink ();
// Now the assemblies are in PreBuildDirectory.
foreach (var a in Assemblies) {
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;
}
Driver.GatherFrameworks (this, Frameworks, WeakFrameworks);
// Make sure there are no duplicates between frameworks and weak frameworks.
@ -822,9 +924,20 @@ namespace Xamarin.Bundler
string compiler_output;
var compiler_flags = new CompilerFlags (this);
var link_dependencies = new List<CompileTask> ();
var infos = assemblies.Select ((asm) => asm.AotInfos [abi]);
var infos = assemblies.Select ((asm) => asm.AotInfos [abi]).ToList ();
var aottasks = infos.Select ((info) => info.Task);
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;
}
// We have to compile any source files to object files before we can link.
var sources = infos.SelectMany ((info) => info.AsmFiles);
if (sources.Count () > 0) {
@ -934,21 +1047,7 @@ namespace Xamarin.Bundler
link_task.AddDependency (link_dependencies);
link_task.AddDependency (aottasks);
switch (build_target) {
case AssemblyBuildTarget.StaticObject:
LinkWithTaskOutput (link_task);
break;
case AssemblyBuildTarget.DynamicLibrary:
AddToBundle (link_task.OutputFile);
LinkWithTaskOutput (link_task);
break;
case AssemblyBuildTarget.Framework:
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);
}
LinkWithBuildTarget (build_target, name, link_task, assemblies);
foreach (var info in infos)
info.LinkTask = link_task;
@ -1263,7 +1362,8 @@ namespace Xamarin.Bundler
case AssemblyBuildTarget.DynamicLibrary:
libprofiler = Path.Combine (libdir, "libmono-profiler-log.dylib");
linker_flags.AddLinkWith (libprofiler);
AddToBundle (libprofiler);
if (!App.IsExtension)
AddToBundle (libprofiler);
break;
case AssemblyBuildTarget.StaticObject:
libprofiler = Path.Combine (libdir, "libmono-profiler-log.a");

Просмотреть файл

@ -582,7 +582,11 @@ namespace Xamarin.Bundler
var asm_name = asm_fw.Identity;
if (asm_fw.BuildTargetName == asm_name)
continue; // this is deduceable
assembly_location.AppendFormat ("\t{{ \"{0}\", \"Frameworks/{1}.framework/MonoBundle\" }},\n", asm_name, asm_fw.BuildTargetName);
if (app.IsExtension && asm_fw.IsCodeShared) {
assembly_location.AppendFormat ("\t{{ \"{0}\", \"../../Frameworks/{1}.framework/MonoBundle\" }},\n", asm_name, asm_fw.BuildTargetName);
} else {
assembly_location.AppendFormat ("\t{{ \"{0}\", \"Frameworks/{1}.framework/MonoBundle\" }},\n", asm_name, asm_fw.BuildTargetName);
}
assembly_location_count++;
}
}
@ -1052,6 +1056,7 @@ namespace Xamarin.Bundler
{ "time", v => watch_level++ },
{ "executable=", "Specifies the native executable name to output", v => app.ExecutableName = v },
{ "nofastsim", "Do not run the simulator fast-path build", v => app.NoFastSim = true },
{ "nodevcodeshare", "Do not share native code between extensions and main app.", v => app.NoDevCodeShare = true },
{ "nolink", "Do not link the assemblies", v => app.LinkMode = LinkMode.None },
{ "nodebugtrack", "Disable debug tracking of object resurrection bugs", v => app.DebugTrack = false },
{ "debugtrack:", "Enable debug tracking of object resurrection bugs (enabled by default for the simulator)", v => { app.DebugTrack = ParseBool (v, "--debugtrack"); } },
@ -1467,14 +1472,7 @@ namespace Xamarin.Bundler
throw ErrorHelper.CreateError (99, "Internal error: Extension build action is '{0}' when it should be 'Build'. Please file a bug report with a test case (http://bugzilla.xamarin.com).", app_action);
}
foreach (var appex in app.AppExtensions) {
Log ("Building {0}...", appex.BundleId);
appex.Build ();
}
if (app.AppExtensions.Count > 0)
Log ("Building {0}...", app.BundleId);
app.Build ();
app.BuildAll ();
}
} else {
throw ErrorHelper.CreateError (99, "Internal error: Invalid action: {0}. Please file a bug report with a test case (http://bugzilla.xamarin.com).", action);