使用 Directory.Build 来消除项目文件中的重复配置
Intro
如果解决方案里的项目比较多的话,往往会有很多重复的项目属性,通常我们可以使用独立的 props
属性文件来配置公用的属性,而一般的属性文件都需要手动的 Import
到项目文件中,而 MSBuild 支持自动导入我们要介绍的 Directory.Build.props
中的配置,所以通常我们可以使用 Directory.Build.props
来减少项目中重复的属性
DirectoryBuild
什么是 Directory.Build
呢?
Directory.Build
文件是放在某一个目录下,其中的配置针对这个目录下所有的项目都生效,并且 MSBuild 运行的时候会自动引入,不需要显式地在项目文件中引入,主要分成 Directory.Build.props
和 Directory.Build.targets
两类,在很多开源项目包括微软的开源项目都有用到这个来简化项目配置,.props
文件是属性文件,通常定义公用的属性,导入时机较早,项目中的定义可以覆盖掉其中的配置,.targets
是目标文件,通常定义一些自定义的 MSBuild Task,导入时间稍后,可以用来覆盖项目文件中的定义
通常我只是用到 Directory.Build.props
来配置公用的属性
下面是一个项目文件中的一部分:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net6.0</TargetFramework><LangVersion>latest</LangVersion><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings></PropertyGroup>
</Project>
上面 TargetFramwork
/Nullable
/ImplicitUsings
是一个 MSBuild 属性,需要声明在 PropertyGroup
内
如果一个解决方案中有很多个项目,那么这些可能就都是重复项了,这时候使用 Directory.Build.props
就会比较方便,比如说 ImplicitUsing
这一属性,这个属性是用来启用 .NET 6 里的隐式命名空间引用的,但是从 .NET 6 RC1 默认是禁用的,需要显式声明 <ImplicitUsings>enable</ImplicitUsings>
来启用,如果项目里的项目都是需要启用的,那就可以直接声明在 Directory.Build.props
中,放在项目根目录下,如下所示:
<Project><PropertyGroup><LangVersion>latest</LangVersion><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings></PropertyGroup>
</Project>
除了 PropertyGroup
之外,我们也可以定义 <ItemGroup>
,下面就是一个示例:
<Project><PropertyGroup><LangVersion>latest</LangVersion><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings></PropertyGroup><ItemGroup><Using Include="WeihanLi.Common"/><Using Include="WeihanLi.Common.Helpers"/><Using Include="WeihanLi.Extensions"/></ItemGroup>
</Project>
这样我们可以把公用的命名空间定义在 Directory.Build.props
上, MSBuild 会按目录结构依次寻找上层的 Directory.Build.props
,找到第一个之后就会停止再往上寻找,下面是一个示例
c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\
如果项目较多可以使用多层 Directory.Build.props
,下面是一个示例:
\MySolution.slnDirectory.Build.props (1)\srcDirectory.Build.props (2-src)\Project1\Project2\testDirectory.Build.props (2-test)\Project1Tests\Project2Tests
所有项目 (1) 的通用属性、src 项目 (2-src) 的通用属性,以及 test 项目 (2-test) 的通用属性
前面我们提到过,MSBuild 对于指定项目找到第一个 Directory.Build.props
就会停止再向上寻找 Directory.Build.props
如果要同时使用多级 Directory.Build.props
则需要显式导入上层的 Directory.Build.props
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Sample
下面我们来看一些使用示例吧
Sample1
在稍微复杂一些项目,往往会引用很多的 Nuget 包,而一些项目的版本可能是有关系的,有些包的版本是一致的,此时我们可以考虑定义一个属性,而在包版本的地方则引用这个属性
项目根目录下 Directory.Build.props
内容
<PropertyGroup><LangVersion>latest</LangVersion><Nullable>enable</Nullable><EFVersion>6.0.0-rc.1.*</EFVersion>
</PropertyGroup>
项目文件中内容:
<ItemGroup><PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EFVersion)" /><PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EFVersion)" /><PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(EFVersion)" />
</ItemGroup>
这样我们如果需要更新 EF 的版本,我们只需要更新 Directory.Build.props
文件中的 EFVersion
这个属性就可以了,详细配置可以参考:https://github.com/WeihanLi/WeihanLi.EntityFramework
Sample2
我们再来看一个多个层级使用的示例,项目结构如下所示:
Directory.Build.props
--src
--tests
--Directory.Build.props
----UnitTest
----IntegrationTest
项目根目录下 Directory.Build.props
,主要定义 package 相同的属性配置以及启用可控引用类型,隐式命名空间和公用的命名空间
<Project><PropertyGroup><LangVersion>latest</LangVersion><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><PackageIcon>icon.png</PackageIcon><PackageTags>HTTPie http https curl rest</PackageTags><GenerateDocumentationFile>false</GenerateDocumentationFile><PackageLicenseExpression>MIT</PackageLicenseExpression><RepositoryType>git</RepositoryType><RepositoryUrl>https://github.com/WeihanLi/dotnet-httpie</RepositoryUrl><PackageProjectUrl>https://github.com/WeihanLi/dotnet-httpie</PackageProjectUrl><Product>dotnet-HTTPie</Product><Authors>WeihanLi</Authors><PackageReleaseNotes>https://github.com/WeihanLi/dotnet-httpie/tree/main/docs/ReleaseNotes.md</PackageReleaseNotes><Copyright>Copyright 2021 (c) WeihanLi</Copyright></PropertyGroup><ItemGroup><Using Include="WeihanLi.Common"/><Using Include="WeihanLi.Common.Helpers"/><Using Include="WeihanLi.Extensions"/></ItemGroup>
</Project>
tests
目录下 Directory.Build.props
,定义了测试项目公用的命名空间,并引入了上一层 Directory.Build.props
中的属性
<Project><Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /><ItemGroup><Using Include="FluentAssertions"/><Using Include="Xunit"/></ItemGroup>
</Project>
IntegrationTest
项目文件,引入自己需要的命名空间引用
<ItemGroup><Using Include="HTTPie"/><Using Include="HTTPie.Implement"/><Using Include="HTTPie.Middleware"/><Using Include="HTTPie.Models"/><Using Include="HTTPie.Utilities"/>
</ItemGroup>
具体配置可以参考:https://github.com/WeihanLi/dotnet-httpie
More
使用好 Directory.Build.props
可以简化项目配置,使得项目更容易维护,如果还没用起来的可以尝试一下哈
References
https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?WT.mc_id=DT-MVP-5004222&view=vs-2019
https://github.com/WeihanLi/SparkTodo/blob/master/Directory.Build.props
https://github.com/WeihanLi/SamplesInPractice/tree/master/net6sample
https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/dev/Directory.Build.props
https://github.com/WeihanLi/dotnet-httpie/blob/dev/Directory.Build.props
https://github.com/WeihanLi/dotnet-httpie/blob/dev/tests/Directory.Build.props