Skip to content

Instantly share code, notes, and snippets.

@TheRealPiotrP
Last active November 23, 2016 19:43
Show Gist options
  • Select an option

  • Save TheRealPiotrP/9ab355cd48a5539c9fc31bafd98df5aa to your computer and use it in GitHub Desktop.

Select an option

Save TheRealPiotrP/9ab355cd48a5539c9fc31bafd98df5aa to your computer and use it in GitHub Desktop.

Goals

  1. Remove SDK PackageRef
  2. Remove pre & post imports
  3. Remove nuget restore from sdk acquisition

Impact on Templates

With the change in place, the .NET Core Library csproj template will become:

<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk/1.0.0-rc2">
  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="**\*.cs" />
    <EmbeddedResource Include="**\*.resx" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="NETStandard.Library" Version="1.6" />
  </ItemGroup>
</Project>

Risks

It is imperative that implicit <Imports> do not get persisted to disk. Andy is tracking this concern.

Design

A new attribute is added to <Project> for representing SDKs: Sdk="Microsoft.NET.Sdk/1.0.0-RC2". In RC3 and beyond, the element is used to assist in the acquisition experience. In RC2 it is used to identify a pre-installed framework to be loaded. Specifically, when MSBuild discovers one of these elements it will inject implicit Imports before and after the current .proj file. An example:

This example .csproj file:

<Project Sdk="Microsoft.NET.Sdk/1.0.0-RC2" />
  <Target Name="SayHi">
    <Message Text="Hello, World" />
  </Target>
</Project>

Is interepreted by MSBuild as:

<Project Sdk="Microsoft.NET.Sdk/1.0.0-RC2" />
  <!-- Import all, in order listed -->
  <Import Project="$(MSBuildSDKsPath)\%(___MSBuildSDK.Name)\%(___MSBuildSDK.Version)\build\InitialImport.props" 
          Condition="Exists('$(MSBuildSDKsPath)\%(___MSBuildSDK.Name)\%(___MSBuildSDK.Version)\build\InitialImport.props')" />
  
  <Target Name="SayHi">
    <Message Text="Hello, World" />
  </Target>
  
  <!-- Import all, in order listed -->  
  <Import Project="$(MSBuildSDKsPath)\%(___MSBuildSDK.Name)\%(___MSBuildSDK.Version)\build\FinalImport.targets"
          Condition="Exists('$(MSBuildSDKsPath)\%(___MSBuildSDK.Name)\%(___MSBuildSDK.Version)\build\FinalImport.targets')" />
</Project>

Usage Examples

1. .NET Core Console App

<Project Sdk="Microsoft.NET.Sdk/1.0.0-RC2">
  ...
</Project>

2. .NET Core Web App

<Project Sdk="Microsoft.NET.Sdk/1.0.0-RC2;
              Microsoft.Web.Sdk/1.0.0-RC2">
  ...
</Project>

RC Work

MSBuild

  1. Enable <Project Sdk=""> attribute
  2. Enable InitialImport.props and FinalImport.targets injection based on the values in the SDK element
  3. Ensure we save the csproj correctly [the implicit imports remain implicit]

Core & Web SDKs

  1. Create InitialImport.props and FinalImport.targets
  2. Ship RC2 and beyond with 'clean' version numbers e.g: 1.0.0-RC2 vs. 1.0.0-alpha-20161104-2
  3. Add the SDK layouts to both VS and CLI

CLI & VS

  1. Add SDKs to installation layout
  2. Pass MSBuildSDKsPath Environment Variable, to be interpreted as $(MSBuildSDKsPath) for the sake of this document, to invocations of MSBuild as MSBuildSDKsPath

MSBuildSDKsPath Locations

  1. In Visual Studio: $(MSBuildExtensionsPath)\.dotnet\
  2. In CLI, [ProgramFiles]\dotnet\sdk\{cli_version}\Extensions\

Syntax Comparison

1. SDKs Attribute [Chosen]

<Project Sdk="Microsoft.NET.Sdk/1.0.0-RC2;
               FSharp/1.0.0-Beta;
               MySDK">
  <Target Name="SayHi">
    <Message Text="Hello, World" />
  </Target>
</Project>

Pros:

  • Because it is a project-level attribute, we get the immutability constraints desired.

Cons:

  • N/A

2. <SDK> element

<project>
  <SDK Name="Microsoft.NET.Sdk" Version="1.0.0-RC2" />
  <SDK Name="FSharp" Version="1.0.0-Beta" />
  
  <Message Text="Hello, World" />
</project>

Pros:

  • <SDK> elements include named Name and Version attributes
  • <SDK> elements can be listed separately, avoiding long value strings

Cons:

  • <SDK> cannot behave like other MSBuild elements. It must be statically evaluatable and so cannot accept $(Properties). We could put a bunch of special rules in place, but this would be counterintuitive for MSBuild developers.

3. Dynamic Attributes

<project Microsoft.NET.Sdk="1.0.0-RC2" 
         FSharp="1.0.0-Beta">
  <Message Text="Hello, World" />
</project>

Pros:

  • Not composing a single ; dilimited string

Cons:

  • Feels unnatural usage of XML
@nguerrera
Copy link

Nits:

  • <project> should be <Project> (capital P) in examples.
  • <Message Text="Hello, World"> is not valid outside a target
  • I think we should use Sdks as the casing and not SDKs. The former follows the .NET Design guidelines for acronyms that are 3 letters or longer and more importantly follows the same convention of Microsoft.NET.Sdk and Microsoft.Web.Sdk that will often appear on the same line.

@nguerrera
Copy link

I see that the initial and final imports are conditioned on existence.

We should make it a hard and clear error if the SDK is not found. I think in the current proposal, a bad VS/CLI installation or a simple typo in the project would just silently not import anything. In some common cases, it would leave no targets defined and we'd at least get error MSB4040: There is no target in the project, which is better than status quo when SDK has not been restored and silently defaults to net40. But in other cases (say only Web SDK is misspelled), then we'd still get silent bad behaviour.

@nguerrera
Copy link

nguerrera commented Nov 21, 2016

In Visual Studio: $(MSBuildExtensionsPath)\.dotnet\

Why the hidden folder and why the coupling in naming to .NET? This could be used to make SDKs for platforms with no affinity to .NET. I think something like $(MSBuildExtensionsPath)\Sdks would be better.

In CLI, [ProgramFiles]\dotnet\sdk\{cli_version}\Extensions\

Why would msbuild in CLI use a different path from msbuild in VS? $(MSBuildExtensionsPath) is a concept in both, can't we just use the same subdirectory off of it?

@AndyGerlicher
Copy link

Syntactic sugar:

  <Project Sdk="Microsoft.FSharp.Web/1.0.0.0" />  <!-- allows multiple SDK -->
  </Project>

Expanded form:

<Project>
  <prop here>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk/1.0.0.0" />
...
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk/1.0.0.0" />
  <target here>
</Project>

Change: Sdk everywhere. No SDKs
Added expanded form.

@TheRealPiotrP
Copy link
Author

changed SDKs => Sdk
changed project to Project
added 'Target' around 'Message'

@TheRealPiotrP
Copy link
Author

@nick

  • for the condition this was what we discussed on Friday. Final impl. is with the MSBuild folks. I think you're making good points.
  • for install location, the key in the design is that we not use MSBuildExtensionsPath in target resolution as it takes away flexibility from acquisition story. We may [and likely will] be putting new SDKs in a per-user location and the idea was that using a new env var lets us redirect without affecting existing behavior. Andy was quite concerned about using MSBuildExtensionsPath at all, but it seemed convenient in VS until acquisition story is solved.

@andy, when we start installing Sdk's per-user we'll need to think about how we unify the VS-installed stuff with the per-user stuff. Might need multiple root dirs + search order?

@cdmihai
Copy link

cdmihai commented Nov 23, 2016

Made the MSBuild part public: dotnet/msbuild#1392

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment