Bug 35884 - Libraries referenced only in XAML are not included in the final build
Summary: Libraries referenced only in XAML are not included in the final build
Status: RESOLVED ANSWERED
Alias: None
Product: iOS
Classification: Xamarin
Component: General ()
Version: XI 9.2
Hardware: PC Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
: 37238 ()
Depends on:
Blocks:
 
Reported: 2015-11-16 10:33 UTC by John Miller [MSFT]
Modified: 2015-12-23 11:12 UTC (History)
5 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample Project (18.17 KB, application/zip)
2015-11-16 10:33 UTC, John Miller [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description John Miller [MSFT] 2015-11-16 10:33:12 UTC
Created attachment 13821 [details]
Sample Project

**Overview:**

   In a Xamarin.Forms project, if a type is used in XAML only that is contained in a referenced library, that library is not included in the build and an exception is thrown. 

**Steps to Reproduce:**

   1. Run the attached project on a simulator. 

**Actual Results:**

   System.IO.FileNotFoundException: Could not load file or assembly 'Case233711Views' or one of its dependencies

**Expected Results:**

   The assembly should be included in the build since it's being used in XAML. 

**Build Date & Platform:**

=== Xamarin Studio ===

Version 5.10 (build 870)
Installation UUID: e01c3049-a2d2-4e0a-aad8-afe6fb627c4d
Runtime:
	Mono 4.2.1 (explicit/6dd2d0d)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 402010102

=== Xamarin.Profiler ===

Version: 0.20.0.0
Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

=== Xamarin.Android ===

Version: 6.0.0.33 (Business Edition)
Android SDK: /Users/johnmiller/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		4.0.3 (API level 15)
		4.1   (API level 16)
		4.2   (API level 17)
		4.4   (API level 19)
		5.0   (API level 21)
		5.1   (API level 22)
		6.0   (API level 23)

SDK Tools Version: 24.4.1
SDK Platform Tools Version: 23.0.1
SDK Build Tools Version: 23.0.1

Java SDK: /usr
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

=== Xamarin Android Player ===

Version: 0.6.2
Location: /Applications/Xamarin Android Player.app

=== Apple Developer Tools ===

Xcode 7.1 (9079)
Build 7B91b

=== Xamarin.iOS ===

Version: 9.2.1.50 (Business Edition)
Hash: 2edcbef
Branch: master
Build date: 2015-11-10 19:56:25-0500

=== Xamarin.Mac ===

Version: 2.4.0.109 (Business Edition)

=== Build Information ===

Release ID: 510000870
Git revision: 1212ecb5d4109f6c07c8a9f46b1972bb35556f38
Build date: 2015-11-10 11:07:48-05
Xamarin addins: 733a0b096c4f490368c49f43ae152a8f86e10433
Build lane: monodevelop-lion-cycle6

=== Operating System ===

Mac OS X 10.10.5

**Additional Information:**

   Uncomment line 15 of App.cs and now the DLL will be included because it's being referenced in C# and the compiler will include the assembly. 
   My guess is that since there is no C# reference at all of the types in the library, it's not being included in the build.
Comment 1 Sebastien Pouliot 2015-11-16 10:58:37 UTC
> My guess is that since there is no C# reference at all of the types in the
> library, it's not being included in the build.

Yes a static reference is *required* (in C#) so that the compiler will create an assembly reference into the project (binary) code.

Without this there's not way to know what's used or not (at runtime). Static analysis of the application's references solves this - but does not work with reflection only usage. It's even more complex for iOS as we cannot load code dynamically (AOT is required and bundling non-required BCL assemblies would have a huge impact).
Comment 2 Brian 2015-11-16 22:52:35 UTC
The documentation at http://developer.xamarin.com/guides/ios/advanced_topics/linker/ indicates that the PreserveAttribute attribute can be used to inform the linker that code is not directly referenced and should be kept. It also mentions use of the mtouch "--linkskip=" argument to keep assemblies that aren't directly referenced from code.

Unfortunately, this bug report didn't mention that I tried both of these techniques, neither of which were sufficient to keep the assembly in the final .app package.

So, is there something I don't understand, is the documentation incorrect, or is the linker misbehaving?
Comment 3 John Miller [MSFT] 2015-11-17 15:21:41 UTC
@Brian,

The Linker and compiler are separate. The linker can remove parts of an assembly (code) that is not used. The compiler needs at least *1* static reference of C# code to include it. It's not related to the linker so those techniques are not going to work. From what Sebastien says, my original thoughts are correct and that a XAML only use of the library means the >= 1 static reference requirement is not being satisfied so the entire assembly is not included. 

On the other hand, let's say you did include a C# reference to 1 type in the assembly, the compiler would be satisfied and include the assembly. Now the linker might come in and see that other types in the assembly are not used, so it will strip out those types, but not remove the entire assembly.
Comment 4 Sebastien Pouliot 2015-11-17 15:45:51 UTC
Sorry I got "distracted" from answering this morning. A few more notes beyond John comments.

First I did not mention the linker in my original answer because it's not the issue. In fact the linker is not enabled in the attached project (and it's not enabled, by default, for simulator builds).

Still the reason is similar to what I said earlier, mtouch (the tools doing the .app bundle) needs to know what's needed in the simulator - and static references are used (just like it would be for device builds).

> use of the mtouch "--linkskip=" argument to keep assemblies that aren't 
> directly referenced from code.

I do not see that in the linked document. It's also not how --linkskip works (it does not create a reference). However that behaviour is (for other historical reasons) how the -xml=FILE option works. In any case both are not good options as the linker is not enabled in your case.

A better way to ensure the reference exists would be to add:

> [assembly: Preserve (typeof (Case233711Views.MyView))]

in your AppDelegate.cs (and a reference to the assembly).

That will both create a reference (for simulator builds) and tell the linker (for device builds) that this type is needed at runtime (and must be kept even if not referenced directly).
Comment 5 John Miller [MSFT] 2015-11-25 17:49:58 UTC
I left out any details regarding the linker because it was not relevant to the issue. I tried to narrow it down to as simple of a sample project and directions as needed to reproduce the issue. Sorry if that left confusion. 

After further investigation, I noticed the following. 

Console Application:
References a library. 
Uses no code from that library.

Result => The DLL for that library is included in the build directory. 

iOS Application:
References a library.
Uses no code from that library.

Result => The DLL for that library *is* included in the build directory. However, it is not included in the .app file. 

@Brendan also brought up this comment:

Just to make sure we a little bit of extra perspective on this, I'm not 100% sure there's a difference between iOS and Console apps. It's the identical C# compiler in both cases, and `mcs` follows precisely the same rule as Microsoft's csc.exe:

"In order for the compiler to recognize a type in an assembly, and not in a module, it needs to be forced to resolve the type, which you can do by defining an instance of the type. There are other ways to resolve type names in an assembly for the compiler: for example, if you inherit from a type in an assembly, the type name will then be recognized by the compiler."

https://msdn.microsoft.com/en-us/library/yabyz3h4.aspx

So, the question for our team is why the DLL is not included in the .app. I've raised all these questions to our Doc team to also investigate how we can document this behavior and the proper strategies to handle it. i.e. XAML only references of types from a referenced assembly.
Comment 6 Sebastien Pouliot 2015-11-25 21:20:52 UTC
> the question for our team is why the DLL is not included in the .app.

Short answer: Your results are flawed because you are confusing the compiler(s) with the IDE(s) output. OTOH Brendan's comment and quote (about compilers) are exact.


Long answer:

The compiler read source code and assemblies and create a new output assembly - it does not copy assemblies.

The fact that the IDE copy a file (.dll or not) to a different directory is unrelated to assembly references (you can toggle properties in XS and VS). Notice how the build directory does not have a mscorlib.dll and yet you'll have one in your final .app (because it's referenced).

If you want to know if a reference exists in an assembly you'll need to decompile it.

> I left out any details regarding the linker because it was not relevant to the
> issue. 

Wrong, it's very revelant :)

This is not done _by_ the linker (so modifying it's settings won't help) but _for_ the linker or, more accurately, for the application size (which is the biggest factor of the application build time too). So t's effectively done for the same goal.

Keep in mind that for iOS there is:
a. no code sharing across applications (e.g. a GAC);
b. a requirement to AOT all the code (no JIT);
c. executable size limitations;

So if an assembly is not reachable then we can either drop it or, in theory, keep all of it. There's no middle ground, like keeping only the "required" code as this information is missing (that's what references are used for).

e.g. most templates have references to System.Xml but it's not always used. When it's not used then you have 0% of the code (great), if you use only part of it then you get only that part of the code (great again). Now if we did not check references (and knew the assembly was part of the .csproj) then you would _always_ have 100% of that code when your application do not use it (a costly "just in case").

If you're curious about the impact of the above try to add: `Console.WriteLine (typeof (XmlDocument));` in a template app, add `--linkskip=System.Xml` and build for devices. That will essentially fake the behavior you're looking for. Next compare the size (and build time) you get with the original template app. Keep in mind that's it's the cost of just one, unreferenced, assembly (compare that to the list of PCL Facades we had to resolve).

Why the difference? because you're effectively killing the linker ability to remove code. Not just the extra code from System.Xml.dll - all the code that System.Xml.dll uses from System.dll and mscorlib.dll (references are recursive) is now part of your application too.

The downside of using references is that if you do reflection, like:

> Console.WriteLine (Type.GetType ("System.Xml.XmlDocument, System.Xml"));

then it will work only if you have (or add) a reference to `XmlDocument`, like adding a line (see comment #4) in your main app.

> [assembly: Preserve (typeof (System.Xml.XmlDocument))]

You can remove the `--linkskip=System.Xml` and rebuild again your sample to see the results.

and yes that line/reference is the price to pay to have self-contained, trimmed, .NET applications on iOS devices. We have to know what's used so we can remove what's not (used) because it quickly gets costly not to do so.
Comment 7 John Miller [MSFT] 2015-11-30 17:48:44 UTC
@Sebastien, 

Can you clarify why my results are flawed? 

I did not blame the compiler or linker for the assembly missing in comment #5, just raising the question of _why_ it's not included in the .app. 

> you can toggle properties in XS and VS

Please clarify which properties you are referring to. 

> Wrong, it's very relevant

Thanks for clarifying. I should have worded it as "I left this out because I could reproduce the issue with Don't Link". 

I am still unclear on exactly what the suggested best practice is for handling this scenario. Should users be adding a line like:

> [assembly: Preserve (typeof (Case233711Views))]
Comment 8 Rolf Bjarne Kvinge [MSFT] 2015-12-23 11:12:34 UTC
*** Bug 37238 has been marked as a duplicate of this bug. ***