在MSBuild中使用Task实现自动引用指定版本的NuGet包
需求背景:
桌面应用程序NeverForm依赖于MoreFramework。MoreFramework在维护过程中会经常性地升级版本号 e.g. 从1.00.001.01升级到1.01.022.02 最后两位是build计数。
NeverForm使用CruiseControl.NET来实现持续集成。对MoreFramework的引用有一部分通过MSBuild脚本实现:
-使用NuGet从 指定目录 获取 package.config中指定版本 的 MoreFramework的nuget包 e.g. MoreFramework.1.01.022.02.nupkg
-NuGetInstall指令会把MoreFramework.1.01.022.nupkg编译成dll文件,并安装到packages目录 e.g. \NeverFormSource\packages\MoreFramework.1.01.022.02.nupkg\lib\net40\MoreFramework.dll
-NeverForm对MoreFramework的引用添加是在工程文件里实现的 e.g.NeverForm.csproj。也就是在visual studio里添加引用。这里没有使用MSbuild脚本。
当MoreFramework升级版本号时,目前的流程是:
1)修改package.config文件,将版本号从1.00.001.01改为1.01.022.02 。
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="MoreFramework" version="1.01.022.02" targetFramework="net471" /> </packages>
2)删除文件夹NeverFormSource\packages\MoreFramework.1.00.001.01。
3)新建文件夹NeverFormSource\packages\MoreFramework.1.01.022.02\lib\net40(直接修改文件夹名字也可,但是大多数情况我们都要上传代码,删除+新建更方便trace。)
4)将MoreFramework.dll拷贝到上述新建的文件夹中。
5)在打开NeverForm工程,visualstudio里重新添加对MoreFramework的引用,因为路径变了,所以引用要重新添加。
6)上传代码:package.config+新建的文件夹+修改引用后的工程文件。
7)在CruiseControl.NET中build。
上述流程中2,3,4步骤,完全是为了让重新指定MoreFramework的引用路径。packages文件夹的内容正常都是NuGet生成的。很明显这是一个笨拙的操作。
既然使用了CruiseControl.NET,那么像引用这种操作当然应该在MSBuild里添加,尽可能少的依赖csproj文件,甚至完全脱离它。
可是MoreFramework的路径是随着版本升级而变化的。不可能每次升级都去修改ReferencePath。
有一种解决办法是:
不使用NuGet,而是指定一个固定目录作为ReferencePath。将所有引用的dll都放到这个目录,然后在MSBuild中添加引用。每次升级直接用新的dll覆盖掉旧的就可以了。
这是一个比较常规的做法,很多引用的第三方API都可以这样处理。
但是对于有版本号管理的,公司内部开发的framework,这样简单的做法就会丢失版本号信息。开发者无法从代码层面trace framework的升级轨迹。
换句话说,如果能减少繁琐多余的操作,当然是使用NuGet来管理framework更好。
解决方案:
在NuGetInstall完成后,我们需要msbuild去读取package.config文件,根据读取到的版本号,返回引用路径,然后将该路径下的MoreFramework添加到引用中。
下面是NuGetInstall的MSBuild脚本:
<Target Name="NuGetPackages"> <CreateItem Include="$(SourcePath)\packages.config"> <Output TaskParameter="Include" ItemName="PackageConfigs"/> </CreateItem> <NuGetInstall Package="%(PackageConfigs.FullPath)" OutputDirectory="$(SourcePath)\Packages" ToolPath="D:\Build\Tools" Source="D:\Binaries\NuGet"/> </Target>
使用Task:
<Import Project="$(BuildTools)\Build.Tasks.Never.targets" /> <Target Name="BuildVersion"> <GetPackageVersionTask PackageConfigFileName="$(SourcePath)\packages.config" PackageID="MoreFramework"> <Output TaskParameter="Version" PropertyName="MoreFrameworkVersion"/> </GetPackageVersionTask> <PropertyGroup> <MoreFrameworkReferencePath>$(SourcePath)\packages\MoreFramework.$(MoreFrameworkVersion)\lib\net40</MoreFrameworkReferencePath>
</PropertyGroup>
</Target>
Task代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NuGet; namespace Build.Tasks.Never { public class GetPackageVersionTask : Task { [Required] public string PackageConfigFileName { get; set; } [Required] public string PackageID { get; set; } [Output] public string Version { get; private set; } public override bool Execute() { PackageReferenceFile packageFile = new PackageReferenceFile(PackageConfigFileName); foreach (PackageReference packageReference in packageFile.GetPackageReferences()) { if (packageReference.Id.Equals(PackageID)) { Version = packageReference.Version.ToString(); this.Log.LogMessage("Package ID = {0},version = {1}", new object[] { PackageID, Version }); return true; } } return false; } } }
targets文件:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask AssemblyFile="Build.Tasks.Never.dll" TaskName="Build.Tasks.Never.GetPackageVersionTask"/> </Project>
*.targets文件并不是build出来的,而是manual wirte的。
* GetPackageVersionTask生成的dll必须和.targets在同一个文件夹