Skip to content

Commit 536120c

Browse files
committed
[Xamarin.Android.Build.Tasks] Better support for netstandard libraries.
Fixes dotnet#1154, dotnet#1162 Netstandard packages sometimes ship with both reference and implementation assemblies. The Nuget build task `ResolveNuGetPackageAssets` only resolves the `ref` version of the assemblies. There does not seem to be away way to FORCE Nuget to resolve the lib one. How .net Core manages to do this is still a mistery. That said the Nuget `ResolveNuGetPackageAssets` does give us a hint as to how to use the `project.assets.json` file to figure out what `lib` version of the package we should be including. This commit reworks `ResolveAssemblies` to attempt to map the `ref` to a `lib` if we find a Referenece Assembly. Historically we just issue a warning (which will probably be ignored), but now we will use the `project.assets.json` file to find the implementation version of the `ref` assembly. We need to be using `Nuget.ProjectModel` since it an API for querying the `project.assets.json`. We make use of the Nuget build properties `$(ProjectLockFile)` for the location of the `project.assets.json` , `$(NuGetPackageRoot)` for the root folder of the Nuget packages and `$(NuGetTargetMoniker)` for resolving which `TargetFrameworks` we are looking for. All of these properties should be set by Nuget. If they are not we should fallback to the default behaviour and just issue the warning. { "version": 3, "targets": { "MonoAndroid,Version=v8.1": { "System.IO.Packaging/4.4.0": { "type": "package", "compile": { "ref/netstandard1.3/System.IO.Packaging.dll": {} }, "runtime": { "lib/netstandard1.3/System.IO.Packaging.dll": {} } }, } } } The code above is a cut down sample of the `project.assets.json`. So our code will first resolve the `targets`. We use `$(NuGetTargetMoniker)` to do this. For an android project this should have a value of `MonoAndroid,Version=v8.1`. Note we do NOT need to worry about the version here. When Nuget restores the packages it creates the file so it will use the correct version. Next we try to find the `System.IO.Packaging`. We need to look at the `lockFile.Libraries` to get information about the install path in the Nuget folder, and then `target.Libraries` to pick out the `runtime` item. Once we have resolved the path we need to then combine that with the `$(NuGetPackageRoot)` to get the full path to the new library. If at any point during all of this code we don't get what we expect (i.e a null) we should abort and just issue the warning. The only real concern with this is if the format of the `project.assets.json` file changes. It is not ment to be edited by a human so there is the possibiltity that the Nuget team will decide to either change the schema or even migrate to a new file format. Hopefully we can just update the `Nuget` nuggets if that happens.
1 parent 1826ec2 commit 536120c

File tree

6 files changed

+149
-6
lines changed

6 files changed

+149
-6
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs

+53-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
using MonoDroid.Tuner;
1111
using System.IO;
1212
using Xamarin.Android.Tools;
13+
using NuGet.Common;
14+
using NuGet.Frameworks;
15+
using NuGet.ProjectModel;
1316

1417
using Java.Interop.Tools.Cecil;
1518

@@ -24,6 +27,12 @@ public class ResolveAssemblies : Task
2427
[Required]
2528
public string ReferenceAssembliesDirectory { get; set; }
2629

30+
public string ProjectAssetFile { get; set; }
31+
32+
public string TargetMoniker { get; set; }
33+
34+
public string NuGetPackageRoot { get; set; }
35+
2736
public string I18nAssemblies { get; set; }
2837
public string LinkMode { get; set; }
2938

@@ -57,6 +66,9 @@ bool Execute (DirectoryAssemblyResolver resolver)
5766
Log.LogDebugMessage (" I18nAssemblies: {0}", I18nAssemblies);
5867
Log.LogDebugMessage (" LinkMode: {0}", LinkMode);
5968
Log.LogDebugTaskItems (" Assemblies:", Assemblies);
69+
Log.LogDebugMessage (" ProjectAssetFile: {0}", ProjectAssetFile);
70+
Log.LogDebugMessage (" NuGetPackageRoot: {0}", NuGetPackageRoot);
71+
Log.LogDebugMessage (" TargetMoniker: {0}", TargetMoniker);
6072

6173
foreach (var dir in ReferenceAssembliesDirectory.Split (new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
6274
resolver.SearchDirectories.Add (dir);
@@ -65,6 +77,11 @@ bool Execute (DirectoryAssemblyResolver resolver)
6577

6678
var topAssemblyReferences = new List<AssemblyDefinition> ();
6779

80+
LockFile lockFile = null;
81+
if (!string.IsNullOrEmpty (ProjectAssetFile) && File.Exists (ProjectAssetFile)) {
82+
lockFile = LockFileUtilities.GetLockFile (ProjectAssetFile, NullLogger.Instance);
83+
}
84+
6885
try {
6986
foreach (var assembly in Assemblies) {
7087
var assembly_path = Path.GetDirectoryName (assembly.ItemSpec);
@@ -77,8 +94,13 @@ bool Execute (DirectoryAssemblyResolver resolver)
7794
if (assemblyDef == null)
7895
throw new InvalidOperationException ("Failed to load assembly " + assembly.ItemSpec);
7996
if (MonoAndroidHelper.IsReferenceAssembly (assemblyDef)) {
80-
Log.LogWarning ($"Ignoring {assembly_path} as it is a Reference Assembly");
81-
continue;
97+
// Resolve "runtime" library
98+
if (lockFile != null)
99+
assemblyDef = ResolveRuntimeAssemblyForReferenceAssembly (lockFile, resolver, assemblyDef.Name);
100+
if (lockFile == null || assemblyDef == null) {
101+
Log.LogWarning ($"Ignoring {assembly_path} as it is a Reference Assembly");
102+
continue;
103+
}
82104
}
83105
topAssemblyReferences.Add (assemblyDef);
84106
assemblies.Add (Path.GetFullPath (assemblyDef.MainModule.FullyQualifiedName));
@@ -120,6 +142,35 @@ bool Execute (DirectoryAssemblyResolver resolver)
120142
readonly List<string> do_not_package_atts = new List<string> ();
121143
int indent = 2;
122144

145+
AssemblyDefinition ResolveRuntimeAssemblyForReferenceAssembly (LockFile lockFile, DirectoryAssemblyResolver resolver, AssemblyNameDefinition assemblyNameDefinition)
146+
{
147+
if (string.IsNullOrEmpty(TargetMoniker) || string.IsNullOrEmpty (NuGetPackageRoot) || !Directory.Exists (NuGetPackageRoot))
148+
return null;
149+
150+
var framework = NuGetFramework.Parse (TargetMoniker);
151+
if (framework == null) {
152+
Log.LogWarning ($"Could not parse '{TargetMoniker}'");
153+
return null;
154+
}
155+
var target = lockFile.GetTarget (framework, string.Empty);
156+
if (target == null) {
157+
Log.LogWarning ($"Could not resolve target for '{TargetMoniker}'");
158+
return null;
159+
}
160+
var libraryPath = lockFile.Libraries.FirstOrDefault (x => x.Name == assemblyNameDefinition.Name);
161+
if (libraryPath == null)
162+
return null;
163+
var library = target.Libraries.FirstOrDefault (x => x.Name == assemblyNameDefinition.Name);
164+
if (library == null)
165+
return null;
166+
var runtime = library.RuntimeAssemblies.FirstOrDefault ();
167+
if (runtime == null)
168+
return null;
169+
var path = Path.Combine (NuGetPackageRoot, libraryPath.Path, runtime.Path);
170+
Log.LogDebugMessage ($"Attempting to load {path}");
171+
return resolver.Load (path, forceLoad: true);
172+
}
173+
123174
void AddAssemblyReferences (DirectoryAssemblyResolver resolver, ICollection<string> assemblies, AssemblyDefinition assembly, bool topLevel)
124175
{
125176
var fqname = assembly.MainModule.FullyQualifiedName;

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

+32-2
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,14 @@ public partial class App : Application
280280
281281
public App()
282282
{
283-
JsonConvert.DeserializeObject<string>(""test"");
284-
package = Package.Open ("""");
283+
try {
284+
JsonConvert.DeserializeObject<string>(""test"");
285+
package = Package.Open ("""");
286+
} catch {
287+
}
285288
InitializeComponent();
289+
290+
MainPage = new ContentPage ();
286291
}
287292
288293
protected override void OnStart()
@@ -320,6 +325,7 @@ protected override void OnResume()
320325
IsRelease = true,
321326
UseLatestPlatformSdk = true,
322327
References = {
328+
new BuildItem.Reference ("Java.Interop.Export"),
323329
new BuildItem.Reference ("Mono.Android.Export"),
324330
new BuildItem.ProjectReference ($"..\\{netStandardProject.ProjectName}\\{netStandardProject.ProjectName}.csproj",
325331
netStandardProject.ProjectName, netStandardProject.ProjectGuid),
@@ -337,6 +343,29 @@ protected override void OnResume()
337343
KnownPackages.XamarinForms_2_3_4_231,
338344
}
339345
};
346+
app.MainActivity = @"using System;
347+
using Android.App;
348+
using Android.Content;
349+
using Android.Runtime;
350+
using Android.Views;
351+
using Android.Widget;
352+
using Android.OS;
353+
using XamFormsSample;
354+
355+
namespace App1
356+
{
357+
[Activity (Label = ""App1"", MainLauncher = true, Icon = ""@drawable/icon"")]
358+
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
359+
protected override void OnCreate (Bundle bundle)
360+
{
361+
base.OnCreate (bundle);
362+
363+
global::Xamarin.Forms.Forms.Init (this, bundle);
364+
365+
LoadApplication (new App ());
366+
}
367+
}
368+
}";
340369
app.SetProperty (KnownProperties.AndroidSupportedAbis, "x86;armeabi-v7a");
341370
var expectedFiles = new string [] {
342371
"Java.Interop.dll",
@@ -346,6 +375,7 @@ protected override void OnResume()
346375
"System.dll",
347376
"System.Runtime.Serialization.dll",
348377
"System.IO.Packaging.dll",
378+
"System.IO.Compression.dll",
349379
"Mono.Android.Export.dll",
350380
"App1.dll",
351381
"FormsViewGroup.dll",

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<OutputType>Library</OutputType>
88
<RootNamespace>Xamarin.Android.Build.Tests</RootNamespace>
99
<AssemblyName>Xamarin.Android.Build.Tests</AssemblyName>
10-
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
10+
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
1111
</PropertyGroup>
1212
<Import Project="Xamarin.Android.Build.Tests.Shared.projitems" Label="Shared" Condition="Exists('Xamarin.Android.Build.Tests.Shared.projitems')" />
1313
<Import Project="..\..\..\..\Configuration.props" />

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj

+47-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<RootNamespace>Xamarin.Android.Tasks</RootNamespace>
1010
<AssemblyName>Xamarin.Android.Build.Tasks</AssemblyName>
1111
<FileAlignment>512</FileAlignment>
12-
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
12+
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
1313
<SchemaVersion>2.0</SchemaVersion>
1414
</PropertyGroup>
1515
<Import Project="..\..\Configuration.props" />
@@ -50,6 +50,52 @@
5050
</Reference>
5151
<Reference Include="FSharp.Core">
5252
<HintPath>..\..\packages\FSharp.Core.3.1.2.5\lib\net40\FSharp.Core.dll</HintPath>
53+
<Reference Include="Newtonsoft.Json">
54+
<HintPath>..\..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
55+
</Reference>
56+
<Reference Include="NuGet.Frameworks">
57+
<HintPath>..\..\packages\NuGet.Frameworks.4.6.0\lib\net46\NuGet.Frameworks.dll</HintPath>
58+
</Reference>
59+
<Reference Include="NuGet.Common">
60+
<HintPath>..\..\packages\NuGet.Common.4.6.0\lib\net46\NuGet.Common.dll</HintPath>
61+
</Reference>
62+
<Reference Include="System.IO.Compression" />
63+
<Reference Include="NuGet.Configuration">
64+
<HintPath>..\..\packages\NuGet.Configuration.4.6.0\lib\net46\NuGet.Configuration.dll</HintPath>
65+
</Reference>
66+
<Reference Include="System.Security" />
67+
<Reference Include="NuGet.Versioning">
68+
<HintPath>..\..\packages\NuGet.Versioning.4.6.0\lib\net46\NuGet.Versioning.dll</HintPath>
69+
</Reference>
70+
<Reference Include="NuGet.LibraryModel">
71+
<HintPath>..\..\packages\NuGet.LibraryModel.4.6.0\lib\net46\NuGet.LibraryModel.dll</HintPath>
72+
</Reference>
73+
<Reference Include="NuGet.Packaging.Core">
74+
<HintPath>..\..\packages\NuGet.Packaging.Core.4.6.0\lib\net46\NuGet.Packaging.Core.dll</HintPath>
75+
</Reference>
76+
<Reference Include="NuGet.Packaging">
77+
<HintPath>..\..\packages\NuGet.Packaging.4.6.0\lib\net46\NuGet.Packaging.dll</HintPath>
78+
</Reference>
79+
<Reference Include="System.Runtime">
80+
<HintPath>..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
81+
</Reference>
82+
<Reference Include="mscorlib" />
83+
<Reference Include="System.ComponentModel.Composition" />
84+
<Reference Include="System.Runtime.InteropServices">
85+
<HintPath>..\..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll</HintPath>
86+
</Reference>
87+
<Reference Include="NuGet.Protocol">
88+
<HintPath>..\..\packages\NuGet.Protocol.4.6.0\lib\net46\NuGet.Protocol.dll</HintPath>
89+
</Reference>
90+
<Reference Include="System.IdentityModel" />
91+
<Reference Include="System.Net.Http" />
92+
<Reference Include="System.Net.Http.WebRequest" />
93+
<Reference Include="System.ServiceModel" />
94+
<Reference Include="NuGet.DependencyResolver.Core">
95+
<HintPath>..\..\packages\NuGet.DependencyResolver.Core.4.6.0\lib\net46\NuGet.DependencyResolver.Core.dll</HintPath>
96+
</Reference>
97+
<Reference Include="NuGet.ProjectModel">
98+
<HintPath>..\..\packages\NuGet.ProjectModel.4.6.0\lib\net46\NuGet.ProjectModel.dll</HintPath>
5399
</Reference>
54100
</ItemGroup>
55101
<ItemGroup>

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

+3
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,9 @@ because xbuild doesn't support framework reference assemblies.
15911591
Assemblies="@(FilteredAssemblies)"
15921592
I18nAssemblies="$(MandroidI18n)"
15931593
LinkMode="$(AndroidLinkMode)"
1594+
ProjectAssetFile="$(ProjectLockFile)"
1595+
NuGetPackageRoot="$(NuGetPackageRoot)"
1596+
TargetMoniker="$(NuGetTargetMoniker)"
15941597
ReferenceAssembliesDirectory="$(TargetFrameworkDirectory)">
15951598
<Output TaskParameter="ResolvedAssemblies" ItemName="ResolvedAssemblies" />
15961599
<Output TaskParameter="ResolvedUserAssemblies" ItemName="ResolvedUserAssemblies" />

src/Xamarin.Android.Build.Tasks/packages.config

+13
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,17 @@
33
<package id="FSharp.Compiler.CodeDom" version="1.0.0.1" targetFramework="net45" />
44
<package id="FSharp.Core" version="3.1.2.5" targetFramework="net451" />
55
<package id="Irony" version="0.9.1" targetFramework="net45" />
6+
<package id="Newtonsoft.Json" version="11.0.1" targetFramework="net451" />
7+
<package id="NuGet.Common" version="4.6.0" targetFramework="net462" />
8+
<package id="NuGet.Configuration" version="4.6.0" targetFramework="net462" />
9+
<package id="NuGet.DependencyResolver.Core" version="4.6.0" targetFramework="net462" />
10+
<package id="NuGet.Frameworks" version="4.6.0" targetFramework="net462" />
11+
<package id="NuGet.LibraryModel" version="4.6.0" targetFramework="net462" />
12+
<package id="NuGet.Packaging" version="4.6.0" targetFramework="net462" />
13+
<package id="NuGet.Packaging.Core" version="4.6.0" targetFramework="net462" />
14+
<package id="NuGet.ProjectModel" version="4.6.0" targetFramework="net462" />
15+
<package id="NuGet.Protocol" version="4.6.0" targetFramework="net462" />
16+
<package id="NuGet.Versioning" version="4.6.0" targetFramework="net462" />
17+
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
18+
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
619
</packages>

0 commit comments

Comments
 (0)