novotnyllc / MSBuildSdkExtras

Extra properties for MSBuild SDK projects
MIT License
348 stars 42 forks source link
android ios msbuild netcore pcl uwp xamarin

# MSBuild.Sdk.Extras

Summary

This package contains a few extra extensions to the SDK-style projects that are currently not available in Microsoft.NET.Sdk SDK. This feature is tracked in dotnet/sdk#491 and many of the scenarios are on the roadmap for .NET 6.

The primary goal of this project is to enable multi-targeting without you having to enter in tons of properties within your csproj, vbproj, fsproj, thus keeping it nice and clean.

See the blog post for more information.

Supported .NET Core SDK Versions

Important: 3.x of the Extras requires the .NET 5 SDK or later. The SDK can build previous targets, like netcoreapp3.1. The extras 2.x supports SDK 2.x and 3.x.

Advanced Scenarios

This package also enables advanced library scenarios, allowing you to create reference assemblies and per-RuntimeIdentifier targets.

Reference Assemblies

Reference Assemblies useful in a few scenarios. Please see my two blogs for more details.

Per-RuntimeIdentifier

In some cases involving native interop, it may be necessary to have different runtime versions. NuGet has supported this for a while if you use PackageReference by way of its runtimes folder in combination with a Reference Assembly. Creating and packing these were manual though.

See below for creating these using the Extras easily.

Package Name: MSBuild.Sdk.Extras

Stable: MSBuild.Sdk.Extras

CI Feed: MSBuild.Sdk.Extras package in MSBuildSdkExtras feed in Azure Artifacts https://pkgs.dev.azure.com/clairernovotny/GitBuilds/_packaging/MSBuildSdkExtras/nuget/v3/index.json

Getting started (VS 15.6+)

Visual Studio 2017 Update 6 (aka v15.6) includes support for SDK's resolved from NuGet, which is required for this to work. VS 2019 is recommended.

Using the SDK

  1. Create a new project

    • .NET Core console app or .NET Standard class library.
    • With your existing SDK-style project.
    • With the templates in the repo's TestProjects folder.
  2. Replace Microsoft.NET.Sdk with MSBuild.Sdk.Extras to the project's top-level Sdk attribute.

  3. You have to tell MSBuild that the Sdk should resolve from NuGet by

    • Adding a global.json containing the Sdk name and version.
    • Appending a version info to the Sdk attribute value.
  4. Then you can edit the TargetFramework to a different TFM, or you can rename TargetFramework to TargetFrameworks and specify multiple TFM's with a ; separator.

The final project should look like this:

<Project Sdk="MSBuild.Sdk.Extras">
  <PropertyGroup>
    <TargetFrameworks>net46;uap10.0.19041;tizen8.0</TargetFrameworks>
  </PropertyGroup>
</Project>

The .NET 5 SDK is the latest and has more support for desktop workloads. It's strongly recommended to use that SDK, even to build older targets. If you are using MsBuild.Sdk.Extras version 2 or above, use the .NET Core 3.1 SDK at a minimum. You can still target previous versions of .NET Core.

{
  "msbuild-sdks": {
    "MSBuild.Sdk.Extras": "3.0.22"
  }
}

Above the sdk section indicates use the .NET Core 3 preview to build, the msbuild-sdks indicates the NuGet package to include.

Then, all of your project files, from that directory forward, uses the version from the global.json file. This would be a preferred solution for all the projects in your solution.

Then again, you might want to override the version for just one project OR if you have only one project in your solution (without adding global.json), you can do so like this:

<Project Sdk="MSBuild.Sdk.Extras/3.0.22">
  <PropertyGroup>
    <TargetFrameworks>net46;uap10.0.19041;tizen8.0</TargetFrameworks>
  </PropertyGroup>
</Project>

That's it. You do not need to specify the UWP or Tizen meta-packages as they'll be automatically included. After that, you can use the Restore, Build, Pack targets to restore packages, build the project and create NuGet packages. E.g.: msbuild /t:Pack ...

Important to Note

More information on how SDK's are resolved can be found here.

Creating Per-RuntimeIdentifier packages

You'll need to perform a few steps:

  1. Make sure to use TargetFrameworks instead of TargetFramework, even if you're only building a single target framework. I am piggy-backing off of its looping capabilities.
  2. Set the RuntimeIdentifiers property to valid RID's (full list), separated by a semi-colon (<RuntimeIdentifiers>win;unix</RuntimeIdentifiers>).
  3. For the TFM's that you want want to build separately, set the ExtrasBuildEachRuntimeIdentifier property to true.

When you're done, you should be able to run build/pack and it'll produce a NuGet package.

Notes:

Reference Assemblies

You will likely need to create reference assemblies to simplify development and consumption of your libraries with complex flavor (TargetFramework × RuntimeIdentifier) matrix. Reference assemblies are packed into ref/<TargetFramework> folder. Please see my two blogs articles for details.

Packing additional contents

If you need to add native assets into runtimes, the easiest way is to use:

<None Include="path/to/native.dll" PackagePath="runtimes/<rid>/native" Pack="true" />

Overriding content paths in output package

Minimal example to pack output assemblies and symbols to tools (instead of runtimes) subfolders.

<PropertyGroup>
  <ExtrasIncludeDefaultProjectBuildOutputInPackTarget>IncludeDefaultProjectBuildOutputInPack</ExtrasIncludeDefaultProjectBuildOutputInPackTarget>
</PropertyGroup>

<Target Name="IncludeDefaultProjectBuildOutputInPack">
  <ItemGroup>
    <None Include="@(RidSpecificOutput)" PackagePath="tools/%(TargetFramework)/%(Rid)" Pack="true" />
  </ItemGroup>
</Target>

For advanced options, see ClasslibPack* SDK tests and RIDs.targets file.

Migrate from the old way (VS pre-15.6)

For those who are using in a PackageReference style, you can't do that with v2.0+ of this package. So update VS to 15.6+ and manually upgrade your projects as shown below:

  1. The same as above, replace the Sdk attribute's value.
  2. Remove the workaround import specified with the old way. The import property should be MSBuildSdkExtrasTargets.
  3. Do a trial build and then compare your project with the templates in the repo's TestProjects folder to troubleshoot any issues if you encounter them.
  4. Please file a issue.

Your project diff:

- <Project Sdk="Microsoft.NET.Sdk">
+ <Project Sdk="MSBuild.Sdk.Extras">
  <!-- OTHER PROPERTIES -->
  <PropertyGroup>
    <TargetFrameworks>net46;uap10.0.16299;tizen40</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
-    <PackageReference Include="MSBuild.Sdk.Extras" Version="1.6.0" PrivateAssets="All"/>
  <!-- OTHER PACKAGES/INCLUDES -->
  </ItemGroup>

-  <Import Project="$(MSBuildSdkExtrasTargets)" Condition="Exists('$(MSBuildSdkExtrasTargets)')"/>
  <!-- OTHER IMPORTS -->
</Project>
- PackageReference style
+ SDK style

Note: The SDK-style project now works on Visual Studio for Mac.

Release Notes

1.6.0

Single or multi-targeting

Once this package is configured, you can now use any supported TFM in your TargetFramework or TargetFrameworks element. The supported TFM families are: