Visual Studio 2017 (.NET Core)中的自动版本控制

148

我花费了几个小时的时间,试图找到一种在.NETCoreApp 1.1 (Visual Studio 2017)中自动递增版本号的方法。

我知道AssemblyInfo.cs文件是在目录:obj/Debug/netcoreapp1.1/中动态创建的。

但这不再接受旧的方法: [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.*")]

如果我将项目设置为打包,则可以在那里设置版本,但这似乎用于构建AssemblyInfo.cs文件。

我的问题是,是否有人已经找到了如何控制.NET Core(或.NET Standard)项目版本的方法。


我不知道你在这方面做到了什么程度,但看起来我用了另一种方式几乎问了同样的问题(https://dev59.com/ElgQ5IYBdhLWcg3wIAWb#43280282) - 也许这个问题的被接受答案能帮到你; 你只需要在构建脚本中传递/p:标志给dotnet msbuild,并设置版本、公司、版权等所有好东西。 - Jay
2
谢谢提供这些信息。这刚好为我们打开了更多的选择。 - Jason H
以前 AssemblyVersion 支持 *,但 AssemblyFileVersion 不支持 - 参见 Can I automatically increment the file build version when using Visual Studio? - Michael Freidgeim
4
就这个新项目而言,汇编版本中的通配符不受支持,因为默认情况下编译器启用了"确定性"模式。由于自动增量会破坏确定性(相同的输入产生相同的输出),所以在该模式下不允许其使用。您可以在csproj中设置<Deterministic>False</Deterministic>来使用它(或使用任何其他的MSbuild逻辑来计算<VersionPrefix>/<Version>)。 - Martin Ullrich
21个回答

80
在 .csproj 的 <PropertyGroup> 部分中添加 <Deterministic>False</Deterministic> 可解决 AssemblyVersion 使用 * 时出现的问题。关于此问题的解决方案请参考“Confusing error message for wildcard in [AssemblyVersion] on .Net Core #22660”
使用通配符 * 的前提是构建过程不是确定性的,这也是 .Net Core 项目的默认设置。添加 <Deterministic>False</Deterministic> 到 csproj 中即可解决此问题。有关 .Net Core 开发人员认为确定性构建有益的原因,请参考http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.htmlCompilers should be deterministic: same inputs generate same outputs #372
然而,如果您使用 TeamCity、TFS 或其他 CI/CD 工具,最好由它们控制并通过参数传递版本号(正如其他回答所建议的那样),例如:
msbuild /t:build /p:Version=YourVersionNumber /p:AssemblyVersion=YourVersionNumber

NuGet包的软件包编号

msbuild /t:pack /p:Version=YourVersionNumber   

谢谢!我知道有一个隐藏的杠杆可以打开藏宝室!我正在将一个旧项目迁移到新的.NET SDK,我真的很想快速完成这个过程,而不必费心寻找自动版本增量解决方案。事实上,对于我的情况来说,越兼容旧方法越好。 - Ivaylo Slavov
1
在我看来,这是最好的答案。它可以让构建工具正常工作。至少现在我可以使用外部机制将数字输入到构建中了。 - Michael Yanni
还要确保您的 .csproj 文件中没有 <Version>1.0.0</Version>。 - College Code

68

如果您正在使用Visual Studio Team Services/TFS或其他CI构建过程来内置版本控制,您可以利用msbuild的Condition属性,例如:

如果您正在使用Visual Studio Team Services/TFS或其他CI构建过程来内置版本控制,您可以利用msbuild的Condition属性,例如:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">0.0.1-local</Version>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(BUILD_BUILDNUMBER)</Version>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
  </ItemGroup>

</Project>

这将告诉.NET Core编译器,如果存在BUILD_BUILDNUMBER环境变量,则使用其中的内容,否则在本地机器上构建时回退至0.0.1-local


很好,我喜欢这种方法,因为环境变量可以在构建服务器上设置,而这些条件语句则确定了二进制文件中的程序集。 - Judy007
似乎在TFS 2010上无法工作,但是希望我们很快能够升级! - Mark Adamson
不是一个坏的解决方案,但如果解决方案有很多项目,可能需要花费一些功夫。 - tofutim
好的解决方案。不过我遇到了一个构建异常。我不得不稍微更改一下配置来修复它。https://dev59.com/ZlgQ5IYBdhLWcg3wRR5j#59858009 - Stu Harper
这在.NET Core 2.1.2和TFS2017U3上运行良好。 - Dave Johnson
其中最简单的方法之一,没有脚本,(不幸的是)仍然适用于 dotnet 5。 - Remy

30

我一直在寻找一个能够为使用csproj配置格式的.NET Core应用程序在VS2017中进行版本增量的工具。

我找到了一个名为dotnet bump的项目,它适用于project.json格式,但是对于.csproj格式,我很难找到解决方案。实际上,dotnet bump的作者提出了.csproj格式的解决方案,它被称为MSBump。

你可以在GitHub上找到它的项目:

https://github.com/BalassaMarton/MSBump

在那里你可以看到代码,并且它也可以在NuGet上获取。只需在Nuget上搜索MSBump即可。


1
我建议使用最新的MSBump 2.1.0版本,它支持更好地切换配置,并为当前构建设置版本,而不是下一个版本(就像以前的版本)。 - Márton Balassa
我现在看到它也支持MSBuild,而以前需要使用Visual Studio。 - ravetroll
2
是的,它还支持多目标项目。 - Márton Balassa
4
考虑使用GitVersioning。它可能适合在您的CI环境中运行。 https://github.com/AArnott/Nerdbank.GitVersioning - Henrique
3
MSBump会在每次构建时增加版本号,即使您没有更改任何内容,这在长期使用中会导致许多问题。有时,版本号不同步,其中一个版本可能落后于另一个版本。 - Konrad
Visual Studio 16.5 刚刚害我出了问题。:( - Daniel Henry

22
您可以使用MSBuild属性函数根据当前日期设置版本后缀:
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
  <VersionSuffix>pre$([System.DateTime]::UtcNow.ToString(yyyyMMdd-HHmm))</VersionSuffix>
</PropertyGroup>

这将会输出一个名为: PackageName.1.0.0-pre20180807-1711.nupkg 的包。

有关 MSBuild 属性函数的更多详细信息: https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions

Version 是由 VersionPrefixVersionSuffix 组合形成的,如果 VersionSuffix 为空,则仅使用 VersionPrefix

<PropertyGroup>
  <VersionPrefix>1.0.0</VersionPrefix>
</PropertyGroup>

16

我提出了一个解决方案,它几乎与旧的AssemblyVersion属性带有星号(*)的用法相同-AssemblyVersion("1.0.*")

AssemblyVersionAssemblyFileVersion的值包含在MSBuild项目.csproj文件中(不在AssemblyInfo.cs文件中),作为属性FileVersion(生成AssemblyFileVersionAttribute)和AssemblyVersion(生成AssemblyVersionAttribute)。 在MSBuild过程中,我们使用自定义的MSBuild任务来生成版本号,然后我们使用来自任务的新值覆盖这些FileVersionAssemblyVersion属性的值。

因此,首先,我们创建自定义的MSBuild任务GetCurrentBuildVersion

public class GetCurrentBuildVersion : Task
{
    [Output]
    public string Version { get; set; }
 
    public string BaseVersion { get; set; }
 
    public override bool Execute()
    {
        var originalVersion = System.Version.Parse(this.BaseVersion ?? "1.0.0");
 
        this.Version = GetCurrentBuildVersionString(originalVersion);
 
        return true;
    }
 
    private static string GetCurrentBuildVersionString(Version baseVersion)
    {
        DateTime d = DateTime.Now;
        return new Version(baseVersion.Major, baseVersion.Minor,
            (DateTime.Today - new DateTime(2000, 1, 1)).Days,
            ((int)new TimeSpan(d.Hour, d.Minute, d.Second).TotalSeconds) / 2).ToString();
    }
}

Task 类从 Microsoft.Build.Utilities.Core NuGet 包中的 Microsoft.Build.Utilities.Task 类继承而来。

它接受输入的 BaseVersion 属性(可选),并在输出属性 Version 中返回生成的版本。获取版本号的逻辑与 .NET 自动版本控制相同(构建号是自 2000 年 1 月 1 日以来的天数计数,修订号是距午夜的半秒数)。

为了构建这个 MSBuild 任务,我们使用了 .NET Standard 1.3 类库项目类型,并使用此类。

.csproj 文件可能看起来像这样:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
    <AssemblyName>DC.Build.Tasks</AssemblyName>
    <RootNamespace>DC.Build.Tasks</RootNamespace>
    <PackageId>DC.Build.Tasks</PackageId>
    <AssemblyTitle>DC.Build.Tasks</AssemblyTitle>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="15.1.1012" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.1.1012" />
  </ItemGroup>
</Project>

这个任务项目也可以在我的GitHub中找到:holajan/DC.Build.Tasks

现在我们设置MSBuild使用这个任务并设置FileVersionAssemblyVersion属性。 在.csproj文件中,它看起来像这样:

<Project Sdk="Microsoft.NET.Sdk">
  <UsingTask TaskName="GetCurrentBuildVersion" AssemblyFile="$(MSBuildThisFileFullPath)\..\..\DC.Build.Tasks.dll" />
 
  <PropertyGroup>
    ...
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
  </PropertyGroup>
 
  ...
 
  <Target Name="BeforeBuildActionsProject1" BeforeTargets="BeforeBuild">
    <GetCurrentBuildVersion BaseVersion="$(FileVersion)">
      <Output TaskParameter="Version" PropertyName="FileVersion" />
    </GetCurrentBuildVersion>
    <PropertyGroup>
      <AssemblyVersion>$(FileVersion)</AssemblyVersion>
    </PropertyGroup>
  </Target>
 
</Project>

重要提示:

  • UsingTask提及了从DC.Build.Tasks.dll导入GetCurrentBuildVersion任务。它假定该dll文件位于.csproj文件的父目录中。
  • 我们调用任务的BeforeBuildActionsProject1目标必须每个项目具有唯一名称,以防我们在解决方案中有更多的项目调用GetCurrentBuildVersion任务。

这种解决方案的优点是它不仅适用于构建服务器上的构建,还适用于从dotnet build或Visual Studio进行的手动构建。


5
建议在方法GetCurrentBuildVersionString()中使用DateTime.UtcNow而不是DateTime.Now,特别是如果代码在自动构建机器上执行。这些机器可能在凌晨2点或3点运行,此时您的计算机会切换到/从夏令时。在该场景中使用DateTime.Now可能会导致版本回退。尽管这是一个边缘情况,但我承认我比较挑剔。 :-) 此外,如果在所有构建机器上配置相同的时区并禁用夏令时调整,则问题也会消失。 - Manfred
这个有 NuGet 包吗? - Jonathan Allen
@Jonathan Allen不是的,我没有打算创建NuGet包,因为每个项目的名称都不同。你可以在https://github.com/holajan/DC.Build.Tasks/tree/master/dist文件夹中下载已编译的构建任务程序集。 - HolaJan

15
我接受上面的答案,因为@Gigi目前是正确的,但我很烦恼,于是想出了以下PowerShell脚本。
首先,我在我的解决方案文件夹中有这个脚本(UpdateBuildVersion.ps1):
#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Revision
$avBuild = [Convert]::ToInt32($avBuild,10)+1
$fvBuild = [Convert]::ToInt32($fvBuild,10)+1

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)

我将以下代码添加到csproj文件中:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <AssemblyVersion>0.0.1</AssemblyVersion>
    <FileVersion>0.0.1</FileVersion>
    <PreBuildEvent>powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -command "& {$(SolutionDir)UpdateBuildVersion.ps1}"</PreBuildEvent>
  </PropertyGroup>
</Project>

即使设置为PreBuildEvent,事实上版本号直到文件被加载到内存后才会更新,因此版本号直到下一次构建才会反映出来。实际上,您可以将其更改为PostBuildEvent,其效果是相同的。

我还创建了以下两个脚本:(UpdateMinorVersion.ps1)

#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Minor Version - Will reset all sub nodes
$avMinor = [Convert]::ToInt32($avMinor,10)+1
$fvMinor = [Convert]::ToInt32($fvMinor,10)+1
$avBuild = 0
$fvBuild = 0

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)

(更新主版本.ps1)

#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Major Version - Will reset all sub nodes
$avMajor = [Convert]::ToInt32($avMajor,10)+1
$fvMajor = [Convert]::ToInt32($fvMajor,10)+1
$avMinor = 0
$fvMinor = 0
$avBuild = 0
$fvBuild = 0

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)

15

您可以在csproj文件中像下面这样做。我没有算出数学问题。我在Stack Overflow上找到了别的地方,但是这很有效,并将为您提供类似于版本1.0.*的东西。

<PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <FileVersion>1.0.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2000-01-01"))).TotalDays).$([System.Math]::Floor($([MSBuild]::Divide($([System.DateTime]::UtcNow.TimeOfDay.TotalSeconds), 1.32))))</FileVersion>
    <Version>1.0.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2000-01-01"))).TotalDays)</Version>
</PropertyGroup>

在我的环境中似乎不适用于 netstandard2.0。我正在使用 .NET Core SDK 3.1.403。还需要其他什么才能使其正常工作吗?当使用 dotnet build 运行时,版本号不会从默认值更改。 - Manfred
1
谢谢,短小精悍又好用。 @Manfred:也许可以使用AssemblyVersion或类似的东西。这是我的问题所在。FileVersion已经设置了,但是AssemblyVersion没有设置。此外:<Version>不应再使用(如果我没记错的话)。 - Martini Bianco
1
另外,我使用了<FileVersion>$(VersionPrefix). … </FileVersion>而不是<FileVersion>1.0. … </FileVersion>,然后我可以在VersionPefix属性中正确设置版本号。 - Martini Bianco
为了与.NET中的“1.0.*”匹配,我将使用“UtcNow”更改为“Now”,并从“1.32”更改为“2”,尽管对我来说使用UtcNow更有意义。 - Mafu Josh
这个会每天变化吗?还是只有在部署时改变一次? - Yzak

11

这些值现在已经设置在.csproj文件中:

<PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <AssemblyVersion>1.0.6.0</AssemblyVersion>
    <FileVersion>1.0.6.0</FileVersion>
    <Version>1.0.1</Version>
</PropertyGroup>

如果您进入项目设置中的选项卡,您将看到这些相同的值。虽然我不认为您可以使用*来自动增加版本号,但您可以引入后处理步骤来替换版本号(例如作为您的持续集成的一部分)。


6
我害怕这会是答案。我会尝试做一个预构建步骤来递增它。 - Jason H
3
正如在另一个主题中指出的那样,新的csproj格式允许您关闭自动生成assemblyinfo文件并指定自己的文件。我遵循了natemcmaster在此处给出的答案建议,并使用了标准的AssemblyInfo.cs文件: https://dev59.com/o1gQ5IYBdhLWcg3w1nfm - James Eby
5
为什么他们移除了自动递增功能?这个功能在过去的几年里一直非常好用且简单。我只需要推送到主分支,CI将进行构建并递增版本号,然后使用一些 PowerShell 脚本从生成的 DLL 直接读取版本号,再将该版本号作为参数推送到 NuGet 中。如此简单。现在却出现了问题。 - Luke Puplett
@LukePuplett:请查看[“Confusing error message for wildcard in AssemblyVersion on .Net Core #22660”](https://github.com/dotnet/roslyn/issues/22660),他们认为确定性构建有益的原因在http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html中有描述,编译器应该是确定性的:相同的输入生成相同的输出#372 https://github.com/dotnet/roslyn/issues/372。 - Michael Freidgeim
1
这很棒,我同意这个想法...但是...为什么不支持一个*自动增量但不会在源实际更改时增加的功能呢?#反问句 - Luke Puplett

9
“有没有人在.NET Core(或.NETStandard)项目中想过如何控制版本呢?”使用方法:
dotnet build /p:AssemblyVersion=1.2.3.4

我发现这个问题是在CI构建的背景下解决的。我想把程序集版本设置为CI构建号。


2
标题是“Visual Studio 2017 (.NET Core)中的自动版本控制”。在哪里手动构建它符合“Visual Studio 2017”的要求? - JCKödel
4
我在回应以下问题:"有没有人想过如何在.NET Core(或.NET Standard)项目中控制版本?"我发现这个问题是在解决CI构建上的问题。我想将程序集版本设置为CI构建号。如果您认为这与问题无关,我很抱歉。 - Chris McKenzie
这对我来说是一个有用的组件,谢谢。我将把它作为 CI 解决方案的一部分使用。 - Mark Adamson
1
@ChrisMcKenzie:您的评论应包含在您的答案中,以使您的意图更清晰。 - Michael Freidgeim
当没有指定AssemblyInfo.cs时,且版本在csproj文件中时,此方法在NetStandard项目上无法正常工作。 - tofutim
尝试从 csproj 中移除版本?我认为这不是必要的。查看示例 - Chris McKenzie

8
总结以上内容:您可以通过以下方式恢复旧的AssemblyInfo.cs行为:
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Deterministic>false</Deterministic>

但是这种方法并不推荐,因为关闭GenerateAssemblyInfo可能会导致基础设施方面的问题,例如。更有针对性的方法:

<Deterministic>false</Deterministic>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<AssemblyVersion>1.2.*</AssemblyVersion>

你不再需要 AssemblyInfo.cs 文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接