Skip to content

Commit b0c0795

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 `NewtonSoft.Json` since it provides a decent API for querying 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)` and `$(_NuGetTargetFallbackMoniker)` 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/4.4.0`. My first throught was to use the version of the reference assembly we just loaded. But in this package even though the nuget version was `4.4.0` the assembly versioin was `4.0.2` .. so not helpful. So we need to just search for the items starting with `System.IO.Packaging`. Next is to look for the `runtime` item and then finally the value of that. 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.
1 parent edfa864 commit b0c0795

File tree

5 files changed

+65
-2
lines changed

5 files changed

+65
-2
lines changed

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

+57-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using MonoDroid.Tuner;
1111
using System.IO;
1212
using Xamarin.Android.Tools;
13+
using Newtonsoft.Json.Linq;
14+
using Newtonsoft.Json;
1315

1416
using Java.Interop.Tools.Cecil;
1517

@@ -24,6 +26,15 @@ public class ResolveAssemblies : Task
2426
[Required]
2527
public string ReferenceAssembliesDirectory { get; set; }
2628

29+
[Required]
30+
public string ProjectLockFile { get; set; }
31+
32+
[Required]
33+
public ITaskItem[] TargetMonikers { get; set; }
34+
35+
[Required]
36+
public string NuGetPackageRoot { get; set; }
37+
2738
public string I18nAssemblies { get; set; }
2839
public string LinkMode { get; set; }
2940

@@ -65,6 +76,12 @@ bool Execute (DirectoryAssemblyResolver resolver)
6576

6677
var topAssemblyReferences = new List<AssemblyDefinition> ();
6778

79+
JObject lockFile;
80+
using (var streamReader = new StreamReader(ProjectLockFile))
81+
{
82+
lockFile = JObject.Load(new JsonTextReader(streamReader));
83+
}
84+
6885
try {
6986
foreach (var assembly in Assemblies) {
7087
var assembly_path = Path.GetDirectoryName (assembly.ItemSpec);
@@ -77,8 +94,12 @@ 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+
assemblyDef = ResolveRuntimeAssemblyForReferenceAssembly (lockFile["targets"], resolver, assemblyDef.Name);
99+
if (assemblyDef == null) {
100+
Log.LogWarning ($"Ignoring {assembly_path} as it is a Reference Assembly");
101+
continue;
102+
}
82103
}
83104
topAssemblyReferences.Add (assemblyDef);
84105
assemblies.Add (Path.GetFullPath (assemblyDef.MainModule.FullyQualifiedName));
@@ -120,6 +141,40 @@ bool Execute (DirectoryAssemblyResolver resolver)
120141
readonly List<string> do_not_package_atts = new List<string> ();
121142
int indent = 2;
122143

144+
JToken GetTargetFrameworkMonikerForNuget (JToken targets)
145+
{
146+
foreach (var targetMoniker in TargetMonikers) {
147+
var targetMonikerWithoutRuntimeIdentifier = targetMoniker.ItemSpec;
148+
if (targets [targetMonikerWithoutRuntimeIdentifier] != null)
149+
return targets [targetMonikerWithoutRuntimeIdentifier];
150+
}
151+
var enumerableTargets = targets.Cast<KeyValuePair<string, JToken>> ();
152+
return enumerableTargets.FirstOrDefault ().Value ?? null;
153+
}
154+
155+
AssemblyDefinition ResolveRuntimeAssemblyForReferenceAssembly (JToken targets, DirectoryAssemblyResolver resolver, AssemblyNameDefinition assemblyNameDefinition)
156+
{
157+
var targetFramework = GetTargetFrameworkMonikerForNuget (targets);
158+
if (targetFramework == null)
159+
return null;
160+
var packageRoot = targetFramework.Cast<JProperty>().FirstOrDefault (x => x.Name.StartsWith (assemblyNameDefinition.Name));
161+
if (packageRoot == null)
162+
return null;
163+
var packageName = packageRoot.Name;
164+
var package = packageRoot.FirstOrDefault ();
165+
if (package == null)
166+
return null;
167+
var runtime = package ["runtime"];
168+
if (runtime == null)
169+
return null;
170+
var property = runtime.FirstOrDefault () as JProperty;
171+
if (property == null)
172+
return null;
173+
var path = Path.Combine (NuGetPackageRoot, packageName, property.Name);
174+
Log.LogDebugMessage ($"Attempting to load {path}");
175+
return resolver.Load (path, forceLoad: true);
176+
}
177+
123178
void AddAssemblyReferences (DirectoryAssemblyResolver resolver, ICollection<string> assemblies, AssemblyDefinition assembly, bool topLevel)
124179
{
125180
var fqname = assembly.MainModule.FullyQualifiedName;

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

+1
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ protected override void OnResume()
346346
"System.dll",
347347
"System.Runtime.Serialization.dll",
348348
"System.IO.Packaging.dll",
349+
"System.IO.Compression.dll",
349350
"Mono.Android.Export.dll",
350351
"App1.dll",
351352
"FormsViewGroup.dll",

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

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
<Reference Include="FSharp.Compiler.CodeDom">
4949
<HintPath>..\..\packages\FSharp.Compiler.CodeDom.1.0.0.1\lib\net40\FSharp.Compiler.CodeDom.dll</HintPath>
5050
</Reference>
51+
<Reference Include="Newtonsoft.Json">
52+
<HintPath>..\..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
53+
</Reference>
5154
</ItemGroup>
5255
<ItemGroup>
5356
<Compile Include="$(IntermediateOutputPath)Profile.g.cs" />

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

+3
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,9 @@ because xbuild doesn't support framework reference assemblies.
15811581
Assemblies="@(FilteredAssemblies)"
15821582
I18nAssemblies="$(MandroidI18n)"
15831583
LinkMode="$(AndroidLinkMode)"
1584+
ProjectLockFile="$(ProjectLockFile)"
1585+
NuGetPackageRoot="$(NuGetPackageRoot)"
1586+
TargetMonikers="$(NuGetTargetMoniker);$(_NuGetTargetFallbackMoniker)"
15841587
ReferenceAssembliesDirectory="$(TargetFrameworkDirectory)">
15851588
<Output TaskParameter="ResolvedAssemblies" ItemName="ResolvedAssemblies" />
15861589
<Output TaskParameter="ResolvedUserAssemblies" ItemName="ResolvedUserAssemblies" />

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

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
<packages>
33
<package id="FSharp.Compiler.CodeDom" version="1.0.0.1" targetFramework="net45" />
44
<package id="Irony" version="0.9.1" targetFramework="net45" />
5+
<package id="Newtonsoft.Json" version="11.0.1" targetFramework="net451" />
56
</packages>

0 commit comments

Comments
 (0)