Skip to content

Commit

Permalink
[dotnet] Add support for 'dotnet pack'. Fixes #12631. (#12900)
Browse files Browse the repository at this point in the history
Add support for 'dotnet pack', by:

1. Add a workaround for the fact that as soon as a project has a
   'NativeReference' item, .NET's MSBuild logic wants to include a
   'Native.$(AssemblyName).manifest' file in the NuGet. This obviously breaks,
   because we don't create such a file, so we work around it by removing the
   file in question from the corresponding item groups.

2. Add any binding resource packages to the NuGet.

3. Add tests.

Fixes #12631.
  • Loading branch information
rolfbjarne authored Oct 4, 2021
1 parent 44f570e commit 62bdd68
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 7 deletions.
64 changes: 63 additions & 1 deletion msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</Target>

<PropertyGroup>
<BindingResourcePath>$(ProjectDir)$(OutputPath)$(AssemblyName).resources</BindingResourcePath>
<BindingResourcePath>$(OutputPath)$(AssemblyName).resources</BindingResourcePath>
</PropertyGroup>

<Target Name="_CreateBindingResourcePackage" Condition="'$(DesignTimeBuild)' != 'true'"
Expand Down Expand Up @@ -194,6 +194,68 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</Touch>
</Target>

<!--
Binding projects may include NativeReference items, which makes the 'pack' target want to include a Native.$(AssemblyName).manifest into the NuGet,
which obviously fails because we don't create such a file. So let's remove that file.
Ref: https://github.com/dotnet/msbuild/blob/3a1e456fe227f3e2b190b434578844c31e8bcb4a/src/Tasks/Microsoft.Common.CurrentVersion.targets#L6111-L6118
Ref: https://github.com/dotnet/msbuild/issues/4584
-->
<PropertyGroup>
<GenerateNuspecDependsOn>
$(GenerateNuspecDependsOn);
_RemoveNativeManifestFromPack;
</GenerateNuspecDependsOn>
</PropertyGroup>
<Target Name="_RemoveNativeManifestFromPack">
<ItemGroup>
<_BuildOutputInPackage Remove="@(_BuildOutputInPackage)" Condition="'%(Filename)%(Extension)' == '$(_DeploymentTargetApplicationManifestFileName)'" />
</ItemGroup>
</Target>

<!--
Add any binding resource packages to the nupkg
Ref: https://github.com/dotnet/sdk/issues/14042#issuecomment-716868311
Ref: https://github.com/NuGet/Home/issues/10063#issuecomment-713083004
Ref: https://github.com/xamarin/xamarin-android/blob/681887ebdbd192ce7ce1cd02221d4939599ba762/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AndroidLibraries.targets#L86-L99
-->
<PropertyGroup>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_IncludeBindingResourcesInNuGetPackage</TargetsForTfmSpecificContentInPackage>
</PropertyGroup>
<Target Name="_IncludeBindingResourcesInNuGetPackage"
Condition="'$(IsMacEnabled)' == 'true' And '$(IncludeBuildOutput)' != 'false'"
>
<PropertyGroup>
<_HasOldStyleBindingItems Condition="@(ObjcBindingNativeLibrary->Count()) > 0">true</_HasOldStyleBindingItems>
</PropertyGroup>
<Error Condition="'$(_HasOldStyleBindingItems)' == 'true'" Text="Creating a NuGet package is not supported for projects that have ObjcBindingNativeLibrary items. Migrate to use NativeReference items instead." />

<!-- Figure out where to place the binding resources (see references above for more info) -->
<GetNuGetShortFolderName
TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
TargetPlatformMoniker="$(TargetPlatformMoniker)">
<Output TaskParameter="NuGetShortFolderName" PropertyName="_NuGetShortFolderName" />
</GetNuGetShortFolderName>

<!-- The binding project may either produce a 'MyBindingProject.resources' directory, or a compressed 'MyBindingProject.resources.zip' version -->
<PropertyGroup>
<_AppleBindingResourceBasePath>$(TargetDir)$(TargetName).resources</_AppleBindingResourceBasePath>
</PropertyGroup>

<ItemGroup>
<!-- Add all the files that might exist in any binding resource packages -->
<_AppleBindingResource Include="$(_AppleBindingResourceBasePath)\**\*" PackagePath="lib\$(_NuGetShortFolderName)\$(TargetName).resources" />
<!-- Add any compressed files that might exist as well -->
<_AppleBindingResource Include="$(_AppleBindingResourceBasePath).zip" PackagePath="lib\$(_NuGetShortFolderName)" Condition="Exists('$(_AppleBindingResourceBasePath).zip')" />
</ItemGroup>

<!-- Add what we found to the NuGet -->
<ItemGroup>
<TfmSpecificPackageFile Include="@(_AppleBindingResource)" />
</ItemGroup>
</Target>

<!-- Cleaning via FileWrites leaves empty framework directories on disk, so nuke via RemoveDir -->
<PropertyGroup>
<CleanDependsOn>
Expand Down
13 changes: 13 additions & 0 deletions tests/common/DotNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ public static string Executable {
}
}

public static ExecutionResult AssertPack (string project, Dictionary<string, string> properties = null)
{
return Execute ("pack", project, properties, true);
}

public static ExecutionResult AssertPackFailure (string project, Dictionary<string, string> properties = null)
{
var rv = Execute ("pack", project, properties, false);
Assert.AreNotEqual (0, rv.ExitCode, "Unexpected success");
return rv;
}

public static ExecutionResult AssertPublish (string project, Dictionary<string, string> properties = null)
{
return Execute ("publish", project, properties, true);
Expand Down Expand Up @@ -75,6 +87,7 @@ public static ExecutionResult Execute (string verb, string project, Dictionary<s
switch (verb) {
case "clean":
case "build":
case "pack":
case "publish":
var args = new List<string> ();
args.Add (verb);
Expand Down
6 changes: 0 additions & 6 deletions tests/dotnet/MyClassLibrary/MyClassLibrary.csproj

This file was deleted.

22 changes: 22 additions & 0 deletions tests/dotnet/UnitTests/ApplePlatformExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace Xamarin.Utils {
public static class ApplePlatformExtensionsWithVersions {
public static string ToFrameworkWithDefaultVersion (this ApplePlatform @this)
{
var netVersion = "net6.0";
switch (@this) {
case ApplePlatform.iOS:
return netVersion + "-ios" + SdkVersions.iOS;
case ApplePlatform.MacOSX:
return netVersion + "-macos" + SdkVersions.OSX;
case ApplePlatform.TVOS:
return netVersion + "-tvos" + SdkVersions.TVOS;
case ApplePlatform.MacCatalyst:
return netVersion + "-maccatalyst" + SdkVersions.MacCatalyst;
default:
return "Unknown";
}
}
}
}
3 changes: 3 additions & 0 deletions tests/dotnet/UnitTests/DotNetUnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<Compile Include="..\..\..\external\Xamarin.MacDev\Xamarin.MacDev\PListObject.cs">
<Link>external\PListObject.cs</Link>
</Compile>
<Compile Include="..\..\..\tools\common\SdkVersions.cs">
<Link>external\SdkVersions.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="external\" />
Expand Down
175 changes: 175 additions & 0 deletions tests/dotnet/UnitTests/PackTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#nullable enable

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;

using NUnit.Framework;

using Xamarin.Utils;
using Xamarin.MacDev;

namespace Xamarin.Tests {
public class PackTest : TestBaseClass {


[Test]
[TestCase (ApplePlatform.iOS)]
[TestCase (ApplePlatform.MacCatalyst)]
[TestCase (ApplePlatform.TVOS)]
[TestCase (ApplePlatform.MacOSX)]
public void BindingProject (ApplePlatform platform)
{
var project = "bindings-test";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = Path.Combine (Configuration.RootPath, "tests", project, "dotnet", platform.AsString (), $"{project}.csproj");
Clean (project_path);
Configuration.CopyDotNetSupportingFiles (Path.GetDirectoryName (project_path));

var tmpdir = Cache.CreateTemporaryDirectory ();
var outputPath = Path.Combine (tmpdir, "OutputPath");
var intermediateOutputPath = Path.Combine (tmpdir, "IntermediateOutputPath");
var properties = GetDefaultProperties ();
properties ["OutputPath"] = outputPath + Path.DirectorySeparatorChar;
properties ["IntermediateOutputPath"] = intermediateOutputPath + Path.DirectorySeparatorChar;

var rv =DotNet.AssertPackFailure (project_path, properties);
var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray ();
Assert.AreEqual (1, errors.Length, "Error count");
Assert.AreEqual ($"Creating a NuGet package is not supported for projects that have ObjcBindingNativeLibrary items. Migrate to use NativeReference items instead.", errors [0].Message, "Error message");
}

[Test]
[TestCase (ApplePlatform.iOS, true)]
[TestCase (ApplePlatform.iOS, false)]
[TestCase (ApplePlatform.MacCatalyst, true)]
[TestCase (ApplePlatform.MacCatalyst, false)]
[TestCase (ApplePlatform.TVOS, true)]
[TestCase (ApplePlatform.TVOS, false)]
[TestCase (ApplePlatform.MacOSX, true)]
[TestCase (ApplePlatform.MacOSX, false)]
public void BindingFrameworksProject (ApplePlatform platform, bool noBindingEmbedding)
{
var project = "bindings-framework-test";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = Path.Combine (Configuration.RootPath, "tests", project, "dotnet", platform.AsString (), $"{project}.csproj");
Clean (project_path);
Configuration.CopyDotNetSupportingFiles (Path.GetDirectoryName (project_path));

var tmpdir = Cache.CreateTemporaryDirectory ();
var outputPath = Path.Combine (tmpdir, "OutputPath");
var intermediateOutputPath = Path.Combine (tmpdir, "IntermediateOutputPath");
var properties = GetDefaultProperties ();
properties ["OutputPath"] = outputPath + Path.DirectorySeparatorChar;
properties ["IntermediateOutputPath"] = intermediateOutputPath + Path.DirectorySeparatorChar;
properties ["NoBindingEmbedding"] = noBindingEmbedding ? "true" : "false";

DotNet.AssertPack (project_path, properties);

var nupkg = Path.Combine (outputPath, project + ".1.0.0.nupkg");
Assert.That (nupkg, Does.Exist, "nupkg existence");

var archive = ZipFile.OpenRead (nupkg);
var files = archive.Entries.Select (v => v.FullName).ToHashSet ();
var hasSymlinks = noBindingEmbedding && (platform == ApplePlatform.MacCatalyst || platform == ApplePlatform.MacOSX);
if (noBindingEmbedding) {
Assert.That (archive.Entries.Count, Is.EqualTo (hasSymlinks ? 6 : 10), $"nupkg file count - {nupkg}");
} else {
Assert.That (archive.Entries.Count, Is.EqualTo (5), $"nupkg file count - {nupkg}");
}
Assert.That (files, Does.Contain (project + ".nuspec"), "nuspec");
Assert.That (files, Does.Contain ("_rels/.rels"), ".rels");
Assert.That (files, Does.Contain ("[Content_Types].xml"), "[Content_Types].xml");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.dll"), $"{project}.dll");
Assert.That (files, Has.Some.Matches<string> (v => v.StartsWith ("package/services/metadata/core-properties/", StringComparison.Ordinal) && v.EndsWith (".psmdcp", StringComparison.Ordinal)), "psmdcp");
if (noBindingEmbedding) {
if (hasSymlinks) {
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources.zip"), $"{project}.resources.zip");
} else {
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources/XStaticArTest.framework/XStaticArTest"), $"XStaticArTest.framework/XStaticArTest");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources/XStaticObjectTest.framework/XStaticObjectTest"), $"XStaticObjectTest.framework/XStaticObjectTest");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources/XTest.framework/XTest"), $"XTest.framework/XTest");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources/XTest.framework/Info.plist"), $"XTest.framework/Info.plist");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources/manifest"), $"manifest");
}
}
}

[Test]
[TestCase (ApplePlatform.iOS, true)]
[TestCase (ApplePlatform.iOS, false)]
[TestCase (ApplePlatform.MacCatalyst, true)]
[TestCase (ApplePlatform.MacCatalyst, false)]
[TestCase (ApplePlatform.TVOS, true)]
[TestCase (ApplePlatform.TVOS, false)]
[TestCase (ApplePlatform.MacOSX, true)]
[TestCase (ApplePlatform.MacOSX, false)]
public void BindingXcFrameworksProject (ApplePlatform platform, bool noBindingEmbedding)
{
var project = "bindings-xcframework-test";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = Path.Combine (Configuration.RootPath, "tests", project, "dotnet", platform.AsString (), $"{project}.csproj");
Clean (project_path);
Configuration.CopyDotNetSupportingFiles (Path.GetDirectoryName (project_path));

var tmpdir = Cache.CreateTemporaryDirectory ();
var outputPath = Path.Combine (tmpdir, "OutputPath");
var intermediateOutputPath = Path.Combine (tmpdir, "IntermediateOutputPath");
var properties = GetDefaultProperties ();
properties ["OutputPath"] = outputPath + Path.DirectorySeparatorChar;
properties ["IntermediateOutputPath"] = intermediateOutputPath + Path.DirectorySeparatorChar;
properties ["NoBindingEmbedding"] = noBindingEmbedding ? "true" : "false";
properties ["AssemblyName"] = project;

DotNet.AssertPack (project_path, properties);

var nupkg = Path.Combine (outputPath, project + ".1.0.0.nupkg");
Assert.That (nupkg, Does.Exist, "nupkg existence");

var archive = ZipFile.OpenRead (nupkg);
var files = archive.Entries.Select (v => v.FullName).ToHashSet ();
Assert.That (archive.Entries.Count, Is.EqualTo (noBindingEmbedding ? 6 : 5), $"nupkg file count - {nupkg}");
Assert.That (files, Does.Contain (project + ".nuspec"), "nuspec");
Assert.That (files, Does.Contain ("_rels/.rels"), ".rels");
Assert.That (files, Does.Contain ("[Content_Types].xml"), "[Content_Types].xml");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.dll"), $"{project}.dll");
Assert.That (files, Has.Some.Matches<string> (v => v.StartsWith ("package/services/metadata/core-properties/", StringComparison.Ordinal) && v.EndsWith (".psmdcp", StringComparison.Ordinal)), "psmdcp");
if (noBindingEmbedding) {
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.resources.zip"), $"{project}.resources.zip");
}
}

[Test]
[TestCase (ApplePlatform.iOS)]
[TestCase (ApplePlatform.MacCatalyst)]
[TestCase (ApplePlatform.TVOS)]
[TestCase (ApplePlatform.MacOSX)]
public void LibraryProject (ApplePlatform platform)
{
var project = "MyClassLibrary";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = GetProjectPath (project, runtimeIdentifiers: string.Empty, platform: platform, out var appPath);
Clean (project_path);
var properties = GetDefaultProperties ();

DotNet.AssertPack (project_path, properties);

var nupkg = Path.Combine (Path.GetDirectoryName (project_path)!, "bin", "Debug", project + ".1.0.0.nupkg");
Assert.That (nupkg, Does.Exist, "nupkg existence");

var archive = ZipFile.OpenRead (nupkg);
var files = archive.Entries.Select (v => v.FullName).ToHashSet ();
Assert.That (archive.Entries.Count, Is.EqualTo (5), "nupkg file count");
Assert.That (files, Does.Contain (project + ".nuspec"), "nuspec");
Assert.That (files, Does.Contain ("_rels/.rels"), ".rels");
Assert.That (files, Does.Contain ("[Content_Types].xml"), "[Content_Types].xml");
Assert.That (files, Does.Contain ($"lib/{platform.ToFrameworkWithDefaultVersion ()}/{project}.dll"), $"{project}.dll");
Assert.That (files, Has.Some.Matches<string> (v => v.StartsWith ("package/services/metadata/core-properties/", StringComparison.Ordinal) && v.EndsWith (".psmdcp", StringComparison.Ordinal)), "psmdcp");
}
}
}

4 comments on commit 62bdd68

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 Tests failed catastrophically on Build (no summary found). 🔥

Result file $(TEST_SUMMARY_PATH) not found.

Pipeline on Agent
[dotnet] Add support for 'dotnet pack'. Fixes #12631. (#12900)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ [CI Build] Tests passed on Build. ✅

Tests passed on Build.

API diff

✅ API Diff from stable

View API diff

API & Generator diff

API Diff (from PR only) (no change)
Generator Diff (only version changes)

Packages generated

View packages

🎉 All 218 tests passed 🎉

Pipeline on Agent XAMBOT-1038.BigSur'
[dotnet] Add support for 'dotnet pack'. Fixes #12631. (#12900)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Tests were not ran (VSTS: device tests tvOS). ⚠️

Results were skipped for this run due to provisioning problems Azure Devops. Please contact the bot administrator.

Pipeline on Agent
[dotnet] Add support for 'dotnet pack'. Fixes #12631. (#12900)

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Tests were not ran (VSTS: device tests iOS). ⚠️

Results were skipped for this run due to provisioning problems Azure Devops. Please contact the bot administrator.

Pipeline on Agent
[dotnet] Add support for 'dotnet pack'. Fixes #12631. (#12900)

Please sign in to comment.