Comment puis-je incrémenter automatiquement le numéro de version du package ?

Comment puis-je incrémenter automatiquement le numéro de version du package ?

Solution à trois lignes, gestion des versions par date

J'ai rencontré ce problème jusqu'à ce que je comprenne, après de nombreuses recherches, comment obtenir une gestion automatique des versions en seulement trois lignes dans le .csproj dossier. Le voici :

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

Cela produira un package NuGet nommé comme {ProjectName}.{Year}.{Month}.{Day}.{Hour}{Minute}{Second} dans un dossier "nuget" à la racine du projet, garantissant que les packages construits ultérieurement sont versionnés en tant que postérieurs.


Dans votre .csproj fichier, vous devez ajouter une propriété nommée AppxAutoIncrementPackageRevision avec la valeur définie sur 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>

Cela incrémentera automatiquement la version du package appx chaque fois que vous le créerez via Visual Studio.


Dans Visual Studio 2017, j'ai créé un script PowerShell pour extraire les informations d'ID de package et de numéro de version en regardant à quelques endroits et mettre à jour le fichier .csproj si nécessaire.

Le commentaire d'aide dans le fichier décrit comment l'appeler à partir de votre .csproj lors d'une génération (et générer le package NuGet dans le cadre de la génération) :

<#
.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)
}

Ce script applique la pratique douteuse consistant à synchroniser les numéros de version des fichiers/assemblages/paquets - nous avons trouvé cette pratique utile. Pour ce faire, les numéros de révision nécessitent un traitement spécial. Un delta est donné pour la version bêta, la version candidate et la version afin que les numéros de version ne diminuent pas lors du passage du package alpha → bêta, etc.

Il y a une astuce là-dedans car l'écriture du fichier projet vient après la construction. Cela signifie que les numéros de version du fichier et de l'assembly doivent être conservés un incrément après le numéro de version du package (le package est construit après l'incrément).

Ce script suppose que vous avez un serveur NuGet à interroger. Il ne devrait pas être difficile de couper ce code si vous n'en avez pas.