Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Handle Ref Assemblies from Nuget (#3938)
Browse files Browse the repository at this point in the history
Fixes: #3920

Context: https://github.com/mattleibow/NuGetFoldersTest

What is a [reference assembly][0]?

The short & glib answer is "an assembly with the
[`[assembly:ReferenceAssemblyAttribute]`][1] custom attribute".

Unfortunately, the short & glib answer is incomplete: NuGet also has
a convention in that assemblies within a [`/ref/` directory][2] are
treated as reference assemblies:

> …if you want to provide the corresponding compile time assembly
> as well then have `AnyCPU` assembly in `/ref/{tfm}` folder.
> 
> Please note, NuGet always picks these compile or runtime assets
> from one folder so if there are some compatible assets from `/ref`
> then `/lib` will be ignored to add compile-time assemblies.

Unfortunately, the `<ResolveAssemblies/>` task only treated
assemblies with `[assembly:ReferenceAssembly]` as reference
assemblies, not the "NuGet `/ref/` convention" assemblies.
Consequently, we would never switch out the "reference assembly"
for the `monodroid90`-profile assembly during build time. 

Update `<ResolveAssemblies/>` so that NuGet-style assemblies located
within a `/ref/` directory are treated as reference assemblies, even
if they do *not* have the `[assembly:ReferenceAssembly]` attribute.

[0]: https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies
[1]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.referenceassemblyattribute?view=netframework-4.8
[2]: https://docs.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks#architecture-specific-folders
  • Loading branch information
dellis1972 authored and jonpryor committed Dec 6, 2019
1 parent dd49af3 commit aa05045
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 6 deletions.
5 changes: 4 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs
Expand Up @@ -85,7 +85,8 @@ void Execute (MetadataResolver resolver)
foreach (var assembly in Assemblies) {
// Add each user assembly and all referenced assemblies (recursive)
string resolved_assembly = resolver.Resolve (assembly.ItemSpec);
if (MonoAndroidHelper.IsReferenceAssembly (resolved_assembly)) {
bool refAssembly = !string.IsNullOrEmpty (assembly.GetMetadata ("NuGetPackageId")) && resolved_assembly.Contains ($"{Path.DirectorySeparatorChar}ref{Path.DirectorySeparatorChar}");
if (refAssembly || MonoAndroidHelper.IsReferenceAssembly (resolved_assembly)) {
// Resolve "runtime" library
if (lockFile != null)
resolved_assembly = ResolveRuntimeAssemblyForReferenceAssembly (lockFile, assembly.ItemSpec);
Expand Down Expand Up @@ -176,6 +177,8 @@ string ResolveRuntimeAssemblyForReferenceAssembly (LockFile lockFile, string ass
}
foreach (var folder in lockFile.PackageFolders) {
var path = assemblyPath.Replace (folder.Path, string.Empty);
if (path.StartsWith ($"{Path.DirectorySeparatorChar}"))
path = path.Substring (1);
var libraryPath = lockFile.Libraries.FirstOrDefault (x => path.StartsWith (x.Path.Replace('/', Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase));
if (libraryPath == null)
continue;
Expand Down
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using NUnit.Framework;
using Xamarin.ProjectTools;
Expand Down Expand Up @@ -644,5 +644,89 @@ protected override void OnCreate (Bundle bundle)
}
}
}

[Test]
public void CheckTheCorrectRuntimeAssemblyIsUsedFromNuget ()
{
string monoandroidFramework;
using (var builder = new Builder ()) {
monoandroidFramework = builder.LatestMultiTargetFrameworkVersion ();
}
string path = Path.Combine (Root, "temp", TestName);
var ns = new DotNetStandard () {
ProjectName = "Dummy",
Sdk = "MSBuild.Sdk.Extras/2.0.54",
Sources = {
new BuildItem.Source ("Class1.cs") {
TextContent = () => @"public class Class1 {
#if __ANDROID__
public static string Library => ""Android"";
#else
public static string Library => "".NET Standard"";
#endif
}",
},
},
OtherBuildItems = {
new BuildItem.NoActionResource ("$(OutputPath)netstandard2.0\\$(AssemblyName).dll") {
TextContent = null,
BinaryContent = null,
Metadata = {
{ "PackagePath", "ref\\netstandard2.0" },
{ "Pack", "True" }
},
},
new BuildItem.NoActionResource ($"$(OutputPath){monoandroidFramework}\\$(AssemblyName).dll") {
TextContent = null,
BinaryContent = null,
Metadata = {
{ "PackagePath", $"lib\\{monoandroidFramework}" },
{ "Pack", "True" }
},
},
},
};
ns.SetProperty ("TargetFrameworks", $"netstandard2.0;{monoandroidFramework}");
ns.SetProperty ("PackageId", "dummy.package.foo");
ns.SetProperty ("PackageVersion", "1.0.0");
ns.SetProperty ("GeneratePackageOnBuild", "True");
ns.SetProperty ("IncludeBuildOutput", "False");
ns.SetProperty ("Summary", "Test");
ns.SetProperty ("Description", "Test");
ns.SetProperty ("PackageOutputPath", path);


var xa = new XamarinAndroidApplicationProject () {
ProjectName = "App",
PackageReferences = {
new Package () {
Id = "dummy.package.foo",
Version = "1.0.0",
},
},
OtherBuildItems = {
new BuildItem.NoActionResource ("NuGet.config") {
},
},
};
xa.SetProperty ("RestoreNoCache", "true");
xa.SetProperty ("RestorePackagesPath", "$(MSBuildThisFileDirectory)packages");
using (var nsb = CreateDllBuilder (Path.Combine (path, ns.ProjectName), cleanupAfterSuccessfulBuild: false, cleanupOnDispose: false))
using (var xab = CreateApkBuilder (Path.Combine (path, xa.ProjectName), cleanupAfterSuccessfulBuild: false, cleanupOnDispose: false)) {
nsb.ThrowOnBuildFailure = xab.ThrowOnBuildFailure = false;
Assert.IsTrue (nsb.Build (ns), "Build of NetStandard Library should have succeeded.");
Assert.IsFalse (xab.Build (xa, doNotCleanupOnUpdate: true), "Build of App Library should have failed.");
File.WriteAllText (Path.Combine (Root, xab.ProjectDirectory, "NuGet.config"), @"<?xml version='1.0' encoding='utf-8'?>
<configuration>
<packageSources>
<add key='nuget.org' value='https://api.nuget.org/v3/index.json' protocolVersion='3' />
<add key='bug-testing' value='..' />
</packageSources>
</configuration>");
Assert.IsTrue (xab.Build (xa, doNotCleanupOnUpdate: true), "Build of App Library should have succeeded.");
string expected = Path.Combine ("dummy.package.foo", "1.0.0", "lib", monoandroidFramework, "Dummy.dll");
Assert.IsTrue (xab.LastBuildOutput.ContainsText (expected), $"Build should be using {expected}");
}
}
}
}
Expand Up @@ -167,6 +167,15 @@ public string FirstTargetFrameworkVersion (out string apiLevel)
return lastFrameworkVersion;
}

public string LatestMultiTargetFrameworkVersion ()
{
GetTargetFrameworkVersionRange (out string _, out string _, out string _, out string lastFrameworkVersion);
lastFrameworkVersion = lastFrameworkVersion.Replace ("v", string.Empty);
if (lastFrameworkVersion != "10.0")
lastFrameworkVersion = lastFrameworkVersion.Replace (".", string.Empty);
return $"monoandroid{lastFrameworkVersion}";
}

public string LatestTargetFrameworkVersion (out string apiLevel) {
GetTargetFrameworkVersionRange (out string _, out string _, out apiLevel, out string lastFrameworkVersion);
return lastFrameworkVersion;
Expand Down
Expand Up @@ -71,7 +71,7 @@ public override string SaveProject ()
if (bi.DependentUpon != null) sb.Append ($"DependentUpon=\"{bi.DependentUpon ()}\" ");
if (bi.Version != null) sb.Append ($"Version=\"{bi.Version ()}\" ");
if (bi.SubType != null) sb.Append ($"SubType=\"{bi.SubType ()}\" ");
if (bi.Metadata.Any ()) {
if (!bi.Metadata.Any ()) {
sb.AppendLine ($"\t\t/>");
} else {
sb.AppendLine ($">");
Expand Down
Expand Up @@ -56,8 +56,9 @@ public void Save (XamarinProject project, bool doNotCleanupOnUpdate = false, boo

// Copy our solution's NuGet.config
var nuget_config = Path.Combine (XABuildPaths.TopDirectory, "NuGet.config");
if (File.Exists (nuget_config)) {
File.Copy (nuget_config, Path.Combine (Root, ProjectDirectory, "NuGet.config"), overwrite: true);
var dest = Path.Combine (Root, ProjectDirectory, "NuGet.config");
if (File.Exists (nuget_config) && !File.Exists (dest)) {
File.Copy (nuget_config, dest, overwrite: true);
}
}
else
Expand Down
Expand Up @@ -270,7 +270,7 @@ public virtual void UpdateProjectFiles (string directory, IEnumerable<ProjectRes
string filedir = directory;
if (path.Contains (Path.DirectorySeparatorChar)) {
filedir = Path.GetDirectoryName (path);
if (!Directory.Exists (filedir))
if (!Directory.Exists (filedir) && (p.Content != null || p.BinaryContent != null))
Directory.CreateDirectory (filedir);
}

Expand Down

0 comments on commit aa05045

Please sign in to comment.