在 Visual Studio 2017 中,我创建了一个 PowerShell 脚本,通过查看几个位置并在需要时更新 .csproj 文件来提取包 ID 和版本号信息。
文件中的帮助注释描述了如何在构建期间从您的 .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 "$(SolutionDir)UpdateCsprojPackageVersion.ps1" -csproj "$(ProjectPath)" -target "$(TargetPath)" -packageDir "$(SolutionDir)nuget"" />
<Exec Command="dotnet pack --no-build --include-symbols --include-source --configuration $(Configuration) --output "$(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、候选发布和发布提供了一个 delta,因此从包 alpha → beta 等移动时版本号不会减少。
其中有一个技巧,因为项目文件的写入是在构建之后进行的。这意味着文件和程序集的版本号必须在包版本号之后保持一个增量(包在增量之后构建)。
此脚本假定您有一个要查询的 NuGet 服务器。如果您没有代码,那么删除该代码应该不难。