Tasks/Set-WhiskeyVersion.ps1
function Set-WhiskeyVersion { <# .SYNOPSIS Sets the version for the current build. .DESCRIPTION The `Version` task sets the version for the current build. Whiskey only supports [semantic versions](http://semver.org). You can set the version explicitly with the `Version` property. Whiskey can read the version from a .NET Core .csproj file, a PowerShell module manifest, or a Node package.json file. Set the `Path` property to the path to the file to read from. If it is unsupported or doesn't contain a version, the build will fail. You can set custom prerelease metadata with the `Prerelease` property and custom build metadata with the `Build` property. These properties ovewrite any existing prereleaes version of build metadata from the `Version` property or the version read from a file. Prerelease metadata must only consist of letters, numbers, periods, or hyphens. Build metadata has the same restriction, but Whiskey replaces all non letters, numbers, and periods with hyphens (since build metadata typically comes from systems that don't have these restrictions). When run by a developer, the version will never have any build metadata (i.e. build metadata is only available when running under/by a build server). ## Per-Branch Prerelease Metadata The `Prerelease` property also allows you to have different prerelease metadata on different branches. Set the `Prerelease` property to a list of key/value pairs. The key should be a wildcard pattern that matches branch names. The value should be the prerelease metadata to use on any branch that matches that wildcard. The `Version` task uses the first item that matches the current branch. So, if you wanted to publish alpha versions in branches that begin with "feature/" and beta versions on a branch named "develop", your "Prerelease" property would look like this: BuildTasks: - Version: Version: 5.6.3 Prerelease: - feature/*: alpha.$(WHISKEY_BUILD_NUMBER) - develop: beta.$(WHISKEY_BUILD_NUMBER) ## Reading Versions from Files Some frameworks and platforms already have well-known locations they expect you to put your version number. Whiskey can read your version from some of these files. ### PowerShell Module Manifest Your module manifest must be formatted correctly and contain the `ModuleVersion` property, e.g. @{ # ...snip... ModuleVersion = '0.31.0' # ...snip... } ### Node package.json Whiskey looks for a "version" property on the root object, e.g. { "name": "some node module name", "version": "0.31.0" "description": "some description", "// ...snip..." } ### .NET Core .csproj Whiskey looks for a "Version" element under a "PropertyGroup" element under the project's root "Project" element, e.g. <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- ...snip... --> <Version>0.31.0</Version> <!-- ...snip... --> </PropertyGroup> <!-- ...snip... --> </Project> # Properties * **Version**: The version for the current build. * **Path**: The path to a file from which Whiskey will read the current version. Whiskey supports .NET Core .csproj files, PowerShell module manifests, or Node package.json files. * **Prerelease**: Any custom pre-release metadata to add to the version. This will overwrite any existing prerelease metadata. You usually only use this property when reading a version from a file with the `Path` property since some platforms don't natively support prerelease metadata. * **Build**: Any custom build metadata to add to the version. This will overwrite any existing build metadata. You usually only use this property when reading a version from a file with the `Path` property since some platforms don't natively support prerelease metadata. # Examples ## Example 1 BuildTasks: - Version: 5.6.3 Demonstrates the simplest syntax to set the version for the current build. Do this if you want to completely control the version for every build. ## Example 2 BuildTasks: - Version: Version: 5.6.3 OnlyBy: BuildServer Demonstrates the standard syntax to set the version for the current build. Do this if you want to use other properties. ## Example 3 BuildTasks: - Version: $(WHISKEY_BUILD_STARTED_AT.ToString('yyyy.M.d'))+$(WHISKEY_BUILD_NUMBER).$(WHISKEY_SCM_BRANCH).$(WHISKEY_SCM_COMMIT_ID.Substring(0,7)) Demonstrates how to use a dynamic, date-based version number. In this example, if the current date is 6 Feb. 2018, the current build number is `43`, your current branch is `develop`, and your current commit ID is `c7d950be9982156d5d9aaa1a6c23594eaaba9b27`, your version number would be '2018.2.16+43.develop.c7d950b'. This is a useful strategy for versioning applications that don't expose an API so don't technically need to be semantically versioned. ### Example 4 BuildTasks: - Version: Path: Whiskey\Whiskey.psd1 Prerelease: alpha.$(WHISKEY_BUILD_NUMBER) Build: $(WHISKEY_SCM_BRANCH).$(WHISKEY_SCM_COMMIT_ID.Substring(0,7)) Demonstrates how to read the version from a file, in this case a PowerShell module manifest. Since PowerShell doesn't support semantic versions, it uses the `Prerelease` and `Build` properties to add that metadata to the version. This metadata may or may not be used by other tasks in your build. ### Example 5 BuildTasks: - Version: Path: Assembly\Whiskey.csproj Prerelease: - feature/*: alpha.$(WHISKEY_BUILD_NUMBER) - bugfix/*: beta.$(WHISKEY_BUILD_NUMBER) - develop: beta.$(WHISKEY_BUILD_NUMBER) - release: rc.$(WHISKEY_BUILD_NUMBER) - hotfix/*: rc.$(WHISKEY_BUILD_NUMBER) Demonstrates how to have custom prerelease metadata on different branches. Set the "Prerelease" element to a list of key/value pairs. The keys are wildcard patterns that match branch names in your source code repository. The value is the prerelease metadata to use. In this example, if the current build number is 43, all builds on branches that match the wildcard pattern "feature/*" would have prerelease be "alpha.43", branches that matched pattern "bugfix/*" would have prerelease "beta.43", etc. ### Example 6 BuildTasks: - Version: Version: 4.5.6 - Version: OnlyBy: BuildServer Prerelease: alpha.$(WHISKEY_BUILD_NUMBER) Build: $(WHISKEY_SCM_BRANCH).$(WHISKEY_SCM_COMMIT_ID.Substring(0,7)) Demonstrates that you can use the Version task to set just the Prerelease version and the Build metadata without affecting the version number. In this example, when being bun by a developer, the version number will be 4.5.6; when being run by the build server, the version number will be "4.5.6-alpha.6+develop.775e171" (assuming the current branch is "develop" and the current commit is "775e1711a1fe190f59f4c46ae3063f12e0040e58"). #> [CmdletBinding()] [Whiskey.Task("Version")] param( [Parameter(Mandatory=$true)] [Whiskey.Context] $TaskContext, [Parameter(Mandatory=$true)] [hashtable] $TaskParameter ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState function ConvertTo-SemVer { param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $InputObject, $PropertyName, $VersionSource ) process { [SemVersion.SemanticVersion]$semver = $null if( -not [SemVersion.SemanticVersion]::TryParse($rawVersion,[ref]$semver) ) { if( $VersionSource ) { $VersionSource = ' ({0})' -f $VersionSource } $optionalParam = @{ } if( $PropertyName ) { $optionalParam['PropertyName'] = $PropertyName } Stop-WhiskeyTask -TaskContext $TaskContext -Message ('''{0}''{1} is not a semantic version. See http://semver.org for documentation on semantic versions.' -f $rawVersion,$VersionSource) @optionalParam } return $semver } } [Whiskey.BuildVersion]$buildVersion = $TaskContext.Version [SemVersion.SemanticVersion]$semver = $buildVersion.SemVer2 if( $TaskParameter[''] ) { $rawVersion = $TaskParameter[''] $semVer = $rawVersion | ConvertTo-SemVer -PropertyName 'Version' } elseif( $TaskParameter['Version'] ) { $rawVersion = $TaskParameter['Version'] $semVer = $rawVersion | ConvertTo-SemVer -PropertyName 'Version' } elseif( $TaskParameter['Path'] ) { $path = $TaskParameter['Path'] | Resolve-WhiskeyTaskPath -TaskContext $TaskContext -PropertyName 'Path' if( -not $path ) { return } $fileInfo = Get-Item -Path $path if( $fileInfo.Extension -eq '.psd1' ) { $rawVersion = Test-ModuleManifest -Path $Path -ErrorAction Ignore | Select-Object -ExpandProperty 'Version' if( -not $rawVersion ) { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Unable to read version from PowerShell module manifest ''{0}'': the manifest is invalid or doesn''t contain a ''ModuleVersion'' property.' -f $path) } Write-WhiskeyVerbose -Context $TaskContext -Message ('Read version ''{0}'' from PowerShell module manifest ''{1}''.' -f $rawVersion,$path) $semver = $rawVersion | ConvertTo-SemVer -VersionSource ('from PowerShell module manifest ''{0}''' -f $path) } elseif( $fileInfo.Name -eq 'package.json' ) { try { $rawVersion = Get-Content -Path $path -Raw | ConvertFrom-Json | Select-Object -ExpandProperty 'Version' -ErrorAction Ignore } catch { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Node package.json file ''{0}'' contains invalid JSON.' -f $path) } if( -not $rawVersion ) { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Unable to read version from Node package.json ''{0}'': the ''Version'' property is missing.' -f $path) } Write-WhiskeyVerbose -Context $TaskContext -Message ('Read version ''{0}'' from Node package.json ''{1}''.' -f $rawVersion,$path) $semVer = $rawVersion | ConvertTo-SemVer -VersionSource ('from Node package.json file ''{0}''' -f $path) } elseif( $fileInfo.Extension -eq '.csproj' ) { [xml]$csprojXml = $null try { $csprojxml = Get-Content -Path $Path -Raw } catch { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('.NET .cspoj file ''{0}'' contains invalid XMl.' -f $path) } if( $csprojXml.DocumentElement.Attributes['xmlns'] ) { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('.NET .csproj file ''{0}'' has an "xmlns" attribute. .NET Core/Standard .csproj files should not have a default namespace anymore (see https://docs.microsoft.com/en-us/dotnet/core/migration/). Please remove the "xmlns" attribute from the root "Project" document element. If this is a .NET framework .csproj, it doesn''t support versioning. Use the Whiskey Version task''s Version property to version your assemblies.' -f $path) } $csprojVersionNode = $csprojXml.SelectSingleNode('/Project/PropertyGroup/Version') if( -not $csprojVersionNode ) { Stop-WhiskeyTask -TaskContext $TaskContext -Message ('Element ''/Project/PropertyGroup/Version'' does not exist in .NET .csproj file ''{0}''. Please create this element and set it to the MAJOR.MINOR.PATCH version of the next version of your assembly.' -f $path) } $rawVersion = $csprojVersionNode.InnerText Write-WhiskeyVerbose -Context $TaskContext -Message ('Read version ''{0}'' from .NET Core .csproj ''{1}''.' -f $rawVersion,$path) $semver = $rawVersion | ConvertTo-SemVer -VersionSource ('from .NET .csproj file ''{0}''' -f $path) } } $prerelease = $TaskParameter['Prerelease'] if( $prerelease -isnot [string] ) { $foundLabel = $false foreach( $object in $prerelease ) { foreach( $map in $object ) { if( -not ($map | Get-Member -Name 'Keys') ) { Stop-WhiskeyTask -TaskContext $TaskContext -PropertyName 'Prerelease' -Message ('Unable to find keys in ''[{1}]{0}''. It looks like you''re trying use the Prerelease property to map branches to prerelease versions. If you want a static prerelease version, the syntax should be: BuildTasks: - Version: Prerelease: {0} If you want certain branches to always have certain prerelease versions, set Prerelease to a list of key/value pairs: BuildTasks: - Version: Prerelease: - feature/*: alpha.$(WHISKEY_BUILD_NUMBER) - develop: beta.$(WHISKEY_BUILD_NUMBER) ' -f $map,$map.GetType().FullName) } foreach( $wildcardPattern in $map.Keys ) { if( $TaskContext.BuildMetadata.ScmBranch -like $wildcardPattern ) { Write-WhiskeyVerbose -Context $TaskContext -Message ('{0} -like {1}' -f $TaskContext.BuildMetadata.ScmBranch,$wildcardPattern) $foundLabel = $true $prerelease = $map[$wildcardPattern] break } else { Write-WhiskeyVerbose -Context $TaskContext -Message ('{0} -notlike {1}' -f $TaskContext.BuildMetadata.ScmBranch,$wildcardPattern) } } } } if( -not $foundLabel ) { $prerelease = '' } } if( $prerelease ) { $buildSuffix = '' if( $semver.Build ) { $buildSuffix = '+{0}' -f $semver.Build } $rawVersion = '{0}.{1}.{2}-{3}{4}' -f $semver.Major,$semver.Minor,$semver.Patch,$prerelease,$buildSuffix if( -not [SemVersion.SemanticVersion]::TryParse($rawVersion,[ref]$semver) ) { Stop-WhiskeyTask -TaskContext $TaskContext -PropertyName 'Prerelease' -Message ('''{0}'' is not a valid prerelease version. Only letters, numbers, hyphens, and periods are allowed. See http://semver.org for full documentation.' -f $prerelease) } } $build = $TaskParameter['Build'] if( $build ) { $prereleaseSuffix = '' if( $semver.Prerelease ) { $prereleaseSuffix = '-{0}' -f $semver.Prerelease } $build = $build -replace '[^A-Za-z0-9\.-]','-' $rawVersion = '{0}.{1}.{2}{3}+{4}' -f $semver.Major,$semver.Minor,$semver.Patch,$prereleaseSuffix,$build if( -not [SemVersion.SemanticVersion]::TryParse($rawVersion,[ref]$semver) ) { Stop-WhiskeyTask -TaskContext $TaskContext -PropertyName 'Build' -Message ('''{0}'' is not valid build metadata. Only letters, numbers, hyphens, and periods are allowed. See http://semver.org for full documentation.' -f $build) } } # Build metadata is only available when running under a build server. if( $TaskContext.ByDeveloper ) { $semver = New-Object -TypeName 'SemVersion.SemanticVersion' $semver.Major,$semVer.Minor,$semVer.Patch,$semver.Prerelease } $buildVersion.SemVer2 = $semver Write-WhiskeyInfo -Context $TaskContext -Message ('Building version {0}' -f $semver) $buildVersion.Version = [version]('{0}.{1}.{2}' -f $semver.Major,$semver.Minor,$semver.Patch) Write-WhiskeyVerbose -Context $TaskContext -Message ('Version {0}' -f $buildVersion.Version) $buildVersion.SemVer2NoBuildMetadata = New-Object 'SemVersion.SemanticVersion' ($semver.Major,$semver.Minor,$semver.Patch,$semver.Prerelease) Write-WhiskeyVerbose -Context $TaskContext -Message ('SemVer2NoBuildMetadata {0}' -f $buildVersion.SemVer2NoBuildMetadata) $semver1Prerelease = $semver.Prerelease if( $semver1Prerelease ) { $semver1Prerelease = $semver1Prerelease -replace '[^A-Za-z0-9]','' } $buildVersion.SemVer1 = New-Object 'SemVersion.SemanticVersion' ($semver.Major,$semver.Minor,$semver.Patch,$semver1Prerelease) Write-WhiskeyVerbose -Context $TaskContext -Message ('SemVer1 {0}' -f $buildVersion.SemVer1) } |