如何自动递增软件包版本号?

24

我意识到可以通过更改程序集的构建/修订号码来自动递增它

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyVersion("1.0.*")]

在 AssemblyInfo.cs 文件中。

但我如何自动递增 Package.appxmanifest 中定义的版本号呢?也就是说,通过以下方式访问的版本号:

 Windows.ApplicationModel.Package.Current.Id.Version

我正在使用 Visual Studio 2013。


我认为这个问题已经在这里得到了回答:https://dev59.com/vXRC5IYBdhLWcg3wROtQ - RobP
3
@Sajeetharan 这不是那个问题的重复。这个问题是关于WinRT的appx包。 - dcastro
5个回答

42

三行代码,按日期版本管理

.csproj文件中只需三行代码,即可实现自动版本管理。通过大量研究,我解决了这个问题。

<Target Name="NugetPackAutoVersioning" AfterTargets="Build">
    <Exec Command="dotnet pack -p:PackageVersion=$([System.DateTime]::Now.ToString(&quot;yyyy.MM.dd.HHmm&quot;)) --no-build --configuration $(Configuration) --output &quot;$(SolutionDir)nuget&quot;" />
</Target>

这将在项目根目录的 "nuget" 文件夹中输出一个名为{ProjectName}.{Year}.{Month}.{Day}.{Hour}{Minute} 的 NuGet 包,确保后构建的包版本号是后续版本。


4
这应该是最佳答案。谢谢! - Zee
2
简单而非常有效 - DanielV
1
正是我正在寻找的!这应该是被接受的答案,给这个人一杯啤酒! - DARKGuy
这并不能回答问题,因为它不是关于如何按日期版本控制,而更多地是按照语义化版本控制进行构建/修订。 - Rodney S. Foley
2
使用这个可能会导致 NuGet 恢复循环。请参见 https://dev59.com/kFIH5IYBdhLWcg3wAHj2。删除版本中的 "ss" 部分即可解决此问题。 - Henrik Høyer
显示剩余4条评论

18
dotnet pack在我的包中没有包含运行时/构建目标,所以我更喜欢在项目中使用“生成构建时的包”选项。
我编辑了我的项目文件,并在中添加了以下属性。
<PackageVersion>$([System.DateTime]::Now.ToString("yyyy.MM.dd.HHmmss"))</PackageVersion>

“dotnet pack”是什么?是指字面上的命令行dotnet pack吗?还是其他什么东西? - Peter Mortensen
是的,命令行dotnet pack --在我的生成包中没有正确设置软件包版本。 - Jonathan Gilpin
在.NET Core 5上,使用VS2019完美运行,谢谢! - Obelix
3
关闭 PackageVersion 时你漏了 < 符号。这让我困惑了一分钟。 - Michael Cox
使用此功能可能会导致 NuGet 恢复循环。请参见 https://dev59.com/kFIH5IYBdhLWcg3wAHj2。删除版本中的 "ss" 部分可解决此问题。 - Henrik Høyer

13

在您的.csproj文件中,应添加一个名为AppxAutoIncrementPackageRevision的属性,并将值设置为True

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>

    ...

    <AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>

    ...
  </PropertyGroup>

每次通过Visual Studio构建应用程序包时,这将自动递增appx包版本。


3
尝试过了,对我没用。(添加了<import>和<AppxAutoIncrementPackageRevision>)。包号仍然相同。 - Allie

3
在Visual Studio 2017中,我创建了一个PowerShell脚本,通过查看一些地方来获取软件包ID和版本号信息,并在需要时更新.csproj文件。文件中的帮助注释描述如何在构建期间从您的.csproj中调用它(并作为构建的一部分构建NuGet包)。
<#
.SYNOPSIS

Update version information in the .csproj file in preparation for building a nuget
package.
.DESCRIPTION

Discovers the package name and latest version. If that package exists and is newer
than the target that goes into it, do nothing; otherwise, increment the version
information in the .csproj file (without updating that .csproj file last modified
time).

The latest version gets picked from the maximum of the package/file/assembly
versions in the .csproj file and the version found on the nuget server.
.PARAMETER csproj

The path to the .csproj file to check
.PARAMETER target

The path to the build target (the DLL) that goes into the package. Used to decide whether to
increment the version or not.
.PARAMETER packageDir

The location packages end up.
.PARAMETER nugetSite

The domain name or IP address of the nuget server to query for package version information.
.EXAMPLE

To build a nuget package on every build, add this to the csproj file:

    <Project Sdk="Microsoft.NET.Sdk">
     <Target Name="PostcompileScript" AfterTargets="Build">
       <Exec Command="powershell.exe -NonInteractive -ExecutionPolicy Unrestricted -noexit -file &quot;$(SolutionDir)UpdateCsprojPackageVersion.ps1&quot; -csproj &quot;$(ProjectPath)&quot; -target &quot;$(TargetPath)&quot; -packageDir &quot;$(SolutionDir)nuget&quot;" />
       <Exec Command="dotnet pack --no-build --include-symbols --include-source --configuration $(Configuration) --output &quot;$(SolutionDir)nuget" />
     </Target>
    </Project>
#>
param (
    [Parameter(Mandatory=$true)][string]$csproj,
    [Parameter(Mandatory=$true)][string]$target,
    [Parameter(Mandatory=$true)][string]$packageDir,
    [string]$nugetSite = "local-nuget-server"
)


$csproj = $csproj.Trim()

Write-Output "Increment package/file/assembly version in $csproj"

function ParseVersion($version)
{
    $major = 0
    $minor = 1
    $build = 0
    $revisionType = 'alpha'
    $revision = 0
    $gotData = $false
    $m = [regex]::Match($version, '(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z]*)(\d*)|\.(\d+))?')
    if ($m.Success)
    {
        $major = $m.Groups[1].Value -as [int]
        $minor = $m.Groups[2].Value -as [int]
        $build = $m.Groups[3].Value -as [int]
        if ($m.Groups[4].Success)
        {
            $revisionType = $m.Groups[4].Value.ToLower()
            $revision = $m.Groups[5].Value -as [int]
        }
        else
        {
            $revisionType = ''
            if ($m.Groups[6].Success)
            {
                $revision = $m.Groups[6].Value
            }
        }
    }

    return [Convert]::ToInt32($major, 10), [Convert]::ToInt32($minor, 10), [Convert]::ToInt32($build, 10), $revisionType, [Convert]::ToInt32($revision, 10)
}

function VersionGreaterOrEqual($major1, $minor1, $build1, $revision1, $major2, $minor2, $build2, $revision2)
{
    return ($major1 -gt $major2 -or ($major1 -eq $major2 -and ($minor1 -gt $minor2 -or ($minor1 -eq $minor2 -and ($build1 -gt $build2 -or ($build1 -eq $build2 -and $revision1 -ge $revision2))))))
}

# Read csproj (XML)
$xml = New-Object -TypeName XML
$xml.Load($csproj)
$project = $xml.SelectSingleNode("/Project")
if ($project -eq $null)
{
    $project = $xml.CreateElement("Project")
    $xml.AppendChild($project)
}
$propertyGroup = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]")
if ($propertyGroup -eq $null)
{
    $propertyGroup = $project.AppendChild($xml.CreateElement("PropertyGroup"))
}

# Look for the package identifier in various places in the project file, as a last resort, use the project file name.
$packageId = $null
$packageidFrom = "PackageId in csproj"
$packageIdNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/PackageId")
if ($packageIdNode -ne $null)
{
    $packageId = $packageIdNode.'#text'
}

if ([String]::IsNullOrWhiteSpace($packageId))
{
    $assemblyTitle = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/AssemblyTitle")
    if ($assemblyTitle -ne $null)
    {
        $packageId = $assemblyTitle.'#text'
        $packageidFrom = "AssemblyTitle in csproj"
    }

    if ([String]::IsNullOrWhiteSpace($packageId))
    {
        $assemblyName = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/AssemblyName")
        if ($assemblyName -ne $null)
        {
            $packageId = $assemblyName.'#text'
            $packageidFrom = "AssemblyName in csproj"
        }

        if ([String]::IsNullOrWhiteSpace($packageId))
        {
            $title = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/Title")
            if ($title -ne $null)
            {
                $packageId = $title.'#text'
                $packageidFrom = "Title in csproj"
            }


            if ([String]::IsNullOrWhiteSpace($packageId))
            {
                $packageId = (New-Object System.IO.FileInfo($csproj)).BaseName
                $packageidFrom = "file name of csproj"
                if ($title -eq $null)
                {
                    $title = $propertyGroup.AppendChild($xml.CreateElement("Title"))
                }

                $title.'#text' = $packageId
            }

            if ($assemblyName -eq $null)
            {
                $assemblyName = $propertyGroup.AppendChild($xml.CreateElement("AssemblyName"))
            }

            $assemblyName.'#text' = $packageId
        }

        if ($assemblyTitle -eq $null)
        {
            $assemblyTitle = $propertyGroup.AppendChild($xml.CreateElement("AssemblyTitle"))
        }

        $assemblyTitle.'#text' = $packageId
    }

    if ($packageIdNode -eq $null)
    {
        $packageIdNode = $propertyGroup.AppendChild($xml.CreateElement("PackageId"))
    }

    $packageIdNode.'#text' = $packageId;
}

Write-Output "    Found Package Identifier ""$packageId"" from $packageIdFrom"

# Get the latest version from the nuget server.
# The query comes from running nuget.exe with the -Verbose option (and guessing that the search term can be a regular expression).
# The response comes back as XML
$nugetXml = New-Object -TypeName XML
$nugetXml.Load("http://$nugetSite/api/v2/Search()?`$filter=IsAbsoluteLatestVersion&searchTerm=%27^$packageId$%27&targetFramework=%27%27&includePrerelease=true")
$nugetVersionNode = $nugetXml.SelectSingleNode("feed.entry.properties.Version")
$nugetVersion = ''
if ($nugetVersionNode -ne $null)
{
    $nugetVersion = $nugetVersionNode.'#text'
}

# Retrieve Version Nodes
$packageVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/PackageVersion")
if ($packageVersionNode -eq $null) {
    $packageVersionNode = $propertyGroup.AppendChild($xml.CreateElement("PackageVersion"))
}
$assemblyVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/AssemblyVersion")
if ($assemblyVersionNode -eq $null) {
    $assemblyVersionNode = $propertyGroup.AppendChild($xml.CreateElement("AssemblyVersion"))
}
$fileVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(@*)]/FileVersion")
if ($fileVersionNode -eq $null) {
    $fileVersionNode = $propertyGroup.AppendChild($xml.CreateElement("FileVersion"))
}

$packageVersion = $packageVersionNode.'#text'
$assemblyVersion = $assemblyVersionNode.'#text'
$fileVersion = $fileVersionNode.'#text'

Write-Output "    Read versions: qat-nuget=""$nugetVersion"", package=""$packageVersion"", file=""$fileVersion"", assembly=""$assemblyVersion"""

# Split the Version Numbers
$major, $minor, $build, $revisionType, $revision = ParseVersion $nugetVersion
$paMajor, $paMinor, $paBuild, $paRevisionType, $paRevision = ParseVersion $packageVersion
$avMajor, $avMinor, $avBuild, $avRevisionType, $avRevision = ParseVersion $assemblyVersion
$fvMajor, $fvMinor, $fvBuild, $fvRevisionType, $fvRevision = ParseVersion $fileVersion

# choose between qat-nuget's package version and the package version found in the project file
if ((VersionGreaterOrEqual $paMajor $paMinor $paBuild 0 $major $minor $build 0) -and (($paRevisionType -eq '' -or $paRevisionType -gt $revisionType) -or ($paRevisionType -eq $revisionType -and $paRevision -gt $revision)))
{
    $major = $paMajor
    $minor = $paMinor
    $build = $paBuild
    $revisionType = $paRevisionType
    $revision = $paRevision
}

# Because of the way the build works, the file and assembly versions being set are for the
# _next_ build, the package version is for the _current_ build. We err on the side of the
# package version - that is, the file and assembly version may not increment at edge cases.
# If you want to be sure that all the versions are the same in a package, you must build
# twice.

# To keep revisions for file and assembly alpha/beta/rc/release builds going in order, we
# give the different releaseType values different base revision values.
switch($revisionType.ToLower())
{
   "rc" { $revisionDelta = 20001 }
   "beta" { $revisionDelta = 10001 }
   "alpha" { $revisionDelta = 1 }
   default { $revisionDelta = 40001 }  # for release revisions
}

# Boost the version to the assembly version or the file version value if those are greater
if ((VersionGreaterOrEqual $avMajor $avMinor $avBuild $avRevision $major $minor $build ($revision + $revisionDelta)) -and (VersionGreaterOrEqual $avMajor $avMinor $avBuild $avRevision $fvMajor $fvMinor $fvBuild $fvRevision))
{
    $major = $avMajor
    $minor = $avMinor
    $build = $avBuild
    $revision = $avRevision - $revisionDelta
}
elseif (VersionGreaterOrEqual $fvMajor $fvMinor $fvBuild $fvRevision $major $minor $build ($revision + $revisionDelta))
{
    $major = $fvMajor
    $minor = $fvMinor
    $build = $fvBuild
    $revision = $fvRevision - $revisionDelta
}

if ($revision -lt 0)
{
    $revision -eq 0
}

$fileAssemblyRevision = $revision + $revisionDelta
$fileAssemblyBuild = $build


if ($revisionType -ne "")
{
    $oldPackageName = "$packageId.$major.$minor.$build-$revisionType$revision.nupkg"
}
else
{
    $oldPackageName = "$packageId.$major.$minor.$build.nupkg"
}

$oldPackage = [System.IO.Path]::Combine($packageDir, $oldPackageName)

if ([System.IO.File]::Exists($oldPackage) -and [System.IO.File]::GetLastWriteTime($oldPackage) -ge [System.IO.File]::GetLastWriteTime($target))
{
    $targetName = [System.IO.Path]::GetFileName($target)
    Write-Output "    * Not incrementing version - $oldPackageName newer than $targetName"
}
else
{
    # Increment revision or build
    if ($revisionType -ne "")
    {
        $fileAssemblyRevision = $fileAssemblyRevision + 1
        $revision = $revision + 1
    }
    else
    {
        $fileAssemblyBuild = $fileAssemblyBuild + 1
        $build = $build + 1
        $fileAssemblyRevision = 0
        $revision = $revision + 0
    }


    # Put the incremented version into the csproj file and save it
    $fileAssemblyVersion = "$major.$minor.$fileAssemblyBuild.$fileAssemblyRevision"
    $assemblyVersionNode.RemoveAll()
    $dummy = $assemblyVersionNode.AppendChild($xml.CreateTextNode($fileAssemblyVersion))
    $fileVersionNode.RemoveAll()
    $dummy = $fileVersionNode.AppendChild($xml.CreateTextNode($fileAssemblyVersion))
    $packageVersionNode.RemoveAll()
    if ($revisionType -eq '')
    {
        $packageVersion = "$major.$minor.$build"
    }
    else
    {
        $packageVersion = "$major.$minor.$build-$revisionType$revision"
    }

    $dummy = $packageVersionNode.AppendChild($xml.CreateTextNode($packageVersion))

    Write-Output "    Set file/assembly version to $fileAssemblyVersion, package version to $packageVersion"

    $lastWriteTime = [System.IO.File]::GetLastWriteTime($csproj)
    $xml.Save($csproj)
    [System.IO.File]::SetLastWriteTime($csproj, $lastWriteTime)
}

这个脚本强制执行保持文件/程序集/包版本号同步的可疑做法 - 我们发现这种做法很有用。为了实现这一点,修订号需要特殊处理。对于 beta、发布候选版和正式版,都会给出一个增量,以便在从 alpha 包 → beta 包等转移时,版本号不会下降。
其中有一个技巧,因为项目文件的编写是在构建之后进行的。这意味着文件和程序集版本号必须比包版本号少一个增量(包在增量之后构建)。
此脚本假设您有一个 NuGet 服务器可以查询。如果您没有,剪切该代码应该不难。

赋值 $assemblyTitle.'#text' = $packageId 的含义是什么? .'#text' 部分的作用是什么? - Peter Mortensen
稍后一点.. @PeterMortensen:在PowerShell的XML访问器中,元素的文本属性是#text。为了使PowerShell解释器能够解析带有特殊字符的对象属性,该属性被包含在单引号中。因此,.'#text'可以访问$assemblyTitle xmlElement的文本(标签之间的原始文本)属性。 - Christopher Eberle

2
每当您创建一个包时,都有一个选项可以实现此功能,下面是该选项的截图。您所要做的就是勾选自动增量选项。
从Visual Studio中,像这样导航菜单项目商店创建应用程序包自动增量

Enter image description here


2
我的 Visual Studio 中没有“Project => Store”,也没有我能看到的关于“创建应用程序包”的任何内容。 - WernerCD

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