天天看點

解讀 Microsoft.NET.Sdk 的源碼,你能定制各種奇怪而富有創意的編譯過程

title: “解讀 Microsoft.NET.Sdk 的源碼,你能定制各種奇怪而富有創意的編譯過程”

publishDate: 2018-06-30 13:55:39 +0800

date: 2019-07-02 19:42:50 +0800

categories: dotnet visualstudio nuget msbuild

version:

current: 中文

versions:

  • 中文: #
  • English: /post/read-microsoft-net-sdk-en.html

在 csproj 中,

Project

中的

Sdk

屬性是 MSBuild 15.0 開始支援的,也就是 Visual Studio 2017 開始支援。有了 Sdk 屬性的存在,MSBuild 編譯過程能夠擴充得非常強大,而不止是過去

Import

的一個

props

targets

檔案。

本文将介紹 Microsoft.NET.Sdk 的源碼,以及利用源碼中的一些線索來完成官方文檔中沒有提及的功能擴充。

This post is written in multiple languages. Please select yours:

{% include post-version-selector.html %}

Microsoft.NET.Sdk 源碼的位置

在計算機上全局搜尋

Microsoft.NET.Sdk

可以找到不同版本的多個 Sdk 目錄,由于我安裝了 .NET Core 3.0,是以找到的目錄是:

C:\Program Files\dotnet\sdk\3.0.100-preview6-012264

。當然,按照官網 How to: Reference an MSBuild Project SDK 的描述,如果自己實作了一套 Sdk,也可以以 NuGet 包的形式釋出。

[外鍊圖檔轉存失敗(img-xTkPKfnx-1563186795926)(/static/posts/2018-06-30-21-06-06.png)]

▲ 搜尋 Microsoft.NET.Sdk

[外鍊圖檔轉存失敗(img-gVttPC8k-1563186795939)(/static/posts/2018-06-30-21-08-25.png)]

▲ 我計算機上的 Sdk 檔案夾

Sdk 中的 NuGet 部分在 GitHub 上的倉庫位址:

  • NuGet.Client/src/NuGet.Core at dev · NuGet/NuGet.Client

Microsoft.NET.Sdk 的目錄結構

在打開看

Microsoft.NET.Sdk

的目錄結構後,我們可以發現這幾乎就是 NuGet 包要求的目錄結構。

[外鍊圖檔轉存失敗(img-lB5oNtQi-1563186795940)(/static/posts/2018-06-30-21-09-29.png)]

關于 NuGet 包的目錄結構,我在下面兩篇文章中都有提到過:

  • 如何建立一個基于 MSBuild Task 的跨平台的 NuGet 工具包
  • 如何建立一個基于指令行工具的跨平台的 NuGet 工具包

官方對 NuGet 的目錄結構也有介紹:How to create a NuGet package from a convention-based working directory。

不過,Sdk 類型的 NuGet 包會多一個

Sdk

檔案夾。

[外鍊圖檔轉存失敗(img-YaHqfFoZ-1563186795943)(/static/posts/2018-06-30-21-10-19.png)]

Sdk

檔案夾中的

Sdk.props

Sdk.targets

是會被預設

Import

的,這一點在官方文檔 How to: Reference an MSBuild Project SDK - Visual Studio 中是有說明的,以下兩段代碼的含義相同:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net48</TargetFramework>
    </PropertyGroup>
</Project>
           
<Project>
    <!-- Implicit top import -->
    <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

    <PropertyGroup>
        <TargetFramework>net48</TargetFramework>
    </PropertyGroup>

    <!-- Implicit bottom import -->
    <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
           
<Project>
	<!-- Implicit top import -->
	<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
		<PropertyGroup>
			<TargetFramework>net48</TargetFramework>
		</PropertyGroup>
		
	 	<!-- Implicit bottom import -->
	 	<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
           

由于這兩個檔案的預設引入,Sdk 可以完成非常多的編譯任務。而且通常 Sdk 帶有擴充性,使得我們可以很友善地對項目的編譯過程進行擴充,這一點在我前面提到了兩篇制作 NuGet 工具包的文章中都有說明。

Microsoft.NET.Sdk 的主要任務

在 Sdk 檔案夾中搜尋

Target

節點的個數,我得到了 174 個(随 .NET Core 2.1 釋出);不過有一些是同名的,會被重寫(類似于 C#/.NET 中的繼承和重寫);核心的并沒有那麼多。

  • CollectPackageReferences

    用于收集

    PackageReference

    收集到的所有依賴(也就是 NuGet 包依賴)
  • CoreCompile

    核心的編譯過程
  • GenerateAssemblyInfo

    用于生成

    AssemblyInfo.cs

    檔案(以前可是手工寫的呢)
  • Pack

    用于将目前程式集打包成一個 NuGet 包
  • GenerateNuspec

    在打包之前生成 nuspec 檔案

定制富有創意的編譯過程

下面是 Microsoft.NET.Sdk 中發現的一些富有創意的編譯過程:

<Target Name="DontRestore" BeforeTargets="Restore">
  <Error Text="This project should not be restored" />
 </Target>
           

▲ 如果有

Restore

,那麼讓你編譯不通過

<Target Name="ReferenceStaticLegacyPackage" BeforeTargets="CollectPackageReferences">
  <ItemGroup>
    <PackageReference Remove="LiteDB" />
    <PackageReference Include="LiteDB" Version="2.0.2" />
  </ItemGroup>
</Target>
           

▲ 這是我另外寫的一篇文章:阻止某個 NuGet 包意外更新

參考資料

  • How to: Reference an MSBuild Project SDK - Visual Studio - Microsoft Docs