Skip to content

Commit a760968

Browse files
authored
Merge pull request #29 from PoshCode/add-semver-support
Add support for SemVer in builds to produce SemVer modules
2 parents 46d5269 + 8f9860a commit a760968

File tree

7 files changed

+201
-15
lines changed

7 files changed

+201
-15
lines changed

GitVersion.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mode: ContinuousDeployment
2+
next-version: 1.0.0
23
assembly-versioning-scheme: 'MajorMinorPatchTag'
34
assembly-informational-format: '{Major}.{Minor}.{Patch}{PreReleaseTagWithDash}+Sha.{Sha}.Date.{CommitDate}'
45
commit-message-incrementing: MergeMessageOnly

Source/ModuleBuilder.psd1

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
PrivateData = @{
77
# PrivateData.PSData is the PowerShell Gallery data
88
PSData = @{
9-
# Prerelease string of this module
10-
Prerelease = '-beta01'
9+
# Prerelease string should be here, so we can set it
10+
Prerelease = 'beta'
1111

12-
# ReleaseNotes of this module
12+
# Release Notes have to be here, so we can update them
1313
ReleaseNotes = '
14-
1.0.0-beta01: Pre-release version of Build-Module
14+
First release to the PowerShell gallery ...
1515
'
1616

1717
# Tags applied to this module. These help with module discovery in online galleries.
@@ -50,3 +50,4 @@
5050
PowerShellVersion = '5.1'
5151
CompatiblePSEditions = @('Core','Desktop')
5252
}
53+

Source/Private/InitializeBuild.ps1

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ function InitializeBuild {
1515
[CmdletBinding()]
1616
param(
1717
# The root folder where the module source is (including the Build.psd1 and the module Manifest.psd1)
18-
[string]$SourcePath
18+
[string]$SourcePath,
19+
20+
# Pass the invocation from the parent in, so InitializeBuild can read parameter values
21+
[Parameter(DontShow)]
22+
$Invocation = $(Get-Variable MyInvocation -Scope 1 -ValueOnly)
1923
)
20-
# Read the caller's parameter values
24+
# NOTE: This reads the parameter values from Build-Module!
2125
$ParameterValues = @{}
22-
foreach($parameter in (Get-Variable MyInvocation -Scope 1 -ValueOnly).MyCommand.Parameters.GetEnumerator()) {
26+
foreach($parameter in $Invocation.MyCommand.Parameters.GetEnumerator()) {
2327
$key = $parameter.Key
2428
if($null -ne ($value = Get-Variable -Name $key -ValueOnly -ErrorAction Ignore )) {
2529
if($value -ne ($null -as $parameter.Value.ParameterType)) {

Source/Public/Build-Module.ps1

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,18 @@ function Build-Module {
2929
Build-Module -Prefix "using namespace System.Management.Automation"
3030
3131
This example shows how to build a simple module from it's manifest, adding a using statement at the top as a prefix
32+
33+
.Example
34+
$gitVersion = gitversion | ConvertFrom-Json | Select -Expand InformationalVersion
35+
Build-Module -SemVer $gitVersion
36+
37+
This example shows how to use a semantic version from gitversion to version your build.
38+
Note, this is how we version ModuleBuilder, so if you want to see it in action, check out our azure-pipelines.yml
39+
https://github.com/PoshCode/ModuleBuilder/blob/master/azure-pipelines.yml
3240
#>
3341
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Build is approved now")]
3442
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCmdletCorrectly", "")]
43+
[CmdletBinding(DefaultParameterSetName="SemanticVersion")]
3544
param(
3645
# The path to the module folder, manifest or build.psd1
3746
[Parameter(Position = 0, ValueFromPipelineByPropertyName)]
@@ -50,8 +59,26 @@ function Build-Module {
5059
[Alias("Destination")]
5160
[string]$OutputDirectory,
5261

62+
# Semantic version, like 1.0.3-beta01+sha.22c35ffff166f34addc49a3b80e622b543199cc5
63+
# If the SemVer has metadata (after a +), then the full Semver will be added to the ReleaseNotes
64+
[Parameter(ParameterSetName="SemanticVersion")]
65+
[string]$SemVer,
66+
67+
# The module version (must be a valid System.Version such as PowerShell supports for modules)
5368
[Alias("ModuleVersion")]
54-
[version]$Version,
69+
[Parameter(ParameterSetName="ModuleVersion", Mandatory)]
70+
[version]$Version = $(if($V = $SemVer.Split("+")[0].Split("-")[0]){$V}),
71+
72+
# Setting pre-release forces the release to be a pre-release.
73+
# Must be valid pre-release tag like PowerShellGet supports
74+
[Parameter(ParameterSetName="ModuleVersion")]
75+
[string]$Prerelease = $($SemVer.Split("+")[0].Split("-")[1]),
76+
77+
# Build metadata (like the commit sha or the date).
78+
# If a value is provided here, then the full Semantic version will be inserted to the release notes:
79+
# Like: ModuleName v(Version(-Prerelease?)+BuildMetadata)
80+
[Parameter(ParameterSetName="ModuleVersion")]
81+
[string]$BuildMetadata = $($SemVer.Split("+")[1]),
5582

5683
# Folders which should be copied intact to the module output
5784
# Can be relative to the module folder
@@ -101,6 +128,18 @@ function Build-Module {
101128
}
102129
process {
103130
try {
131+
# BEFORE we InitializeBuild we need to "fix" the version
132+
if($PSCmdlet.ParameterSetName -ne "SemanticVersion") {
133+
Write-Verbose "Calculate the Semantic Version from the $Version - $Prerelease + $BuildMetadata"
134+
$SemVer = $Version
135+
if($Prerelease) {
136+
$SemVer = $Version + '-' + $Prerelease
137+
}
138+
if($BuildMetadata) {
139+
$SemVer = $SemVer + '+' + $BuildMetadata
140+
}
141+
}
142+
104143
# Push into the module source (it may be a subfolder)
105144
$ModuleInfo = InitializeBuild $SourcePath
106145
# Output file names
@@ -159,10 +198,43 @@ function Build-Module {
159198
}
160199
}
161200

162-
Write-Verbose "Update Manifest to $OutputManifest"
201+
try {
202+
if ($Version) {
203+
Write-Verbose "Update Manifest at $OutputManifest with version: $Version"
204+
Update-Metadata -Path $OutputManifest -PropertyName ModuleVersion -Value $Version
205+
}
206+
} catch {
207+
Write-Warning "Failed to update version to $Version. $_"
208+
}
209+
210+
if ($Prerelease) {
211+
Write-Verbose "Update Manifest at $OutputManifest with Prerelease: $Prerelease"
212+
Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.Prerelease -Value $Prerelease
213+
} else {
214+
Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.Prerelease -Value ""
215+
}
163216

164-
if ($Version) {
165-
Update-Metadata -Path $OutputManifest -PropertyName ModuleVersion -Value $Version
217+
if ($BuildMetadata) {
218+
Write-Verbose "Update Manifest at $OutputManifest with metadata: $BuildMetadata from $SemVer"
219+
$RelNote = Get-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -ErrorAction SilentlyContinue
220+
if ($null -ne $RelNote) {
221+
$Line = "$($ModuleInfo.Name) v$($SemVer)"
222+
if ([string]::IsNullOrWhiteSpace($RelNote)) {
223+
Write-Verbose "New ReleaseNotes:`n$Line"
224+
Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $Line
225+
} elseif ($RelNote -match "^\s*\n") {
226+
# Leading whitespace includes newlines
227+
Write-Verbose "Existing ReleaseNotes:$RelNote"
228+
$RelNote = $RelNote -replace "^(?s)(\s*)\S.*$|^$","`${1}$($Line)`$_"
229+
Write-Verbose "New ReleaseNotes:$RelNote"
230+
Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $RelNote
231+
} else {
232+
Write-Verbose "Existing ReleaseNotes:`n$RelNote"
233+
$RelNote = $RelNote -replace "^(?s)(\s*)\S.*$|^$","`${1}$($Line)`n`$_"
234+
Write-Verbose "New ReleaseNotes:`n$RelNote"
235+
Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $RelNote
236+
}
237+
}
166238
}
167239

168240
# This is mostly for testing ...

Tests/Public/Build-Module.Tests.ps1

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Describe "Build-Module" {
1919
It "has an optional parameter for setting the Version"{
2020
$parameters.ContainsKey("Version") | Should -Be $true
2121
$parameters["Version"].ParameterType | Should -Be ([version])
22-
$parameters["Version"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false
22+
$parameters["Version"].ParameterSets.Keys | Should -Not -Be "__AllParameterSets"
2323
}
2424

2525
It "has an optional parameter for setting the Encoding"{
@@ -185,4 +185,113 @@ Describe "Build-Module" {
185185
Assert-MockCalled SetModuleContent -ModuleName ModuleBuilder -Times 0
186186
}
187187
}
188+
189+
Context "Setting the version to a SemVer string" {
190+
$SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11"
191+
$global:ExpectedVersion = "1.0.0"
192+
Push-Location TestDrive:\ -StackName BuildModuleTest
193+
New-Item -ItemType Directory -Path TestDrive:\MyModule\ -Force
194+
New-Item -ItemType Directory -Path "TestDrive:\$ExpectedVersion\" -Force
195+
196+
Mock SetModuleContent -ModuleName ModuleBuilder {}
197+
Mock Update-Metadata -ModuleName ModuleBuilder {}
198+
Mock InitializeBuild -ModuleName ModuleBuilder {
199+
# These are actually all the values that we need
200+
@{
201+
OutputDirectory = "TestDrive:\$Version"
202+
Name = "MyModule"
203+
ModuleBase = "TestDrive:\MyModule\"
204+
CopyDirectories = @()
205+
Encoding = "UTF8"
206+
PublicFilter = "Public\*.ps1"
207+
}
208+
}
209+
210+
Mock Test-Path {$True} -Parameter {$Path -eq "TestDrive:\$ExpectedVersion"} -ModuleName ModuleBuilder
211+
Mock Remove-Item {} -Parameter {$Path -eq "TestDrive:\$ExpectedVersion"} -ModuleName ModuleBuilder
212+
Mock Set-Location {} -ModuleName ModuleBuilder
213+
Mock Copy-Item {} -ModuleName ModuleBuilder
214+
# Release notes
215+
Mock Get-Metadata { "First Release" } -ModuleName ModuleBuilder
216+
Mock Join-Path {
217+
[IO.Path]::Combine($Path, $ChildPath)
218+
} -ModuleName ModuleBuilder
219+
220+
Mock Get-ChildItem {
221+
[IO.FileInfo]$(Join-Path $(Convert-Path "TestDrive:\") "MyModule\Public\Get-MyInfo.ps1")
222+
} -ModuleName ModuleBuilder
223+
224+
Mock New-Item {} -Parameter {
225+
$Path -eq "TestDrive:\$ExpectedVersion" -and
226+
$ItemType -eq "Directory" -and
227+
$Force -eq $true
228+
} -ModuleName ModuleBuilder
229+
230+
try {
231+
Build-Module -SemVer $SemVer
232+
} catch {
233+
Pop-Location -StackName BuildModuleTest
234+
throw
235+
}
236+
237+
It "Should build to an output folder with the simple version." {
238+
Assert-MockCalled Remove-Item -ModuleName ModuleBuilder
239+
Assert-MockCalled New-Item -ModuleName ModuleBuilder
240+
}
241+
242+
It "Should update the module version to the simple version." {
243+
Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter {
244+
$PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion
245+
}
246+
}
247+
It "Should update the module pre-release version" {
248+
Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter {
249+
$PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03"
250+
}
251+
}
252+
It "When there are simple release notes, it should insert a line with the module name and full semver" {
253+
Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter {
254+
$PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$($SemVer)`nFirst Release"
255+
}
256+
}
257+
258+
It "When there's no release notes, it should insert the module name and full semver" {
259+
# If there's no release notes, but it was left uncommented
260+
Mock Get-Metadata { "" } -ModuleName ModuleBuilder
261+
262+
try {
263+
Build-Module -SemVer $SemVer
264+
} catch {
265+
Pop-Location -StackName BuildModuleTest
266+
throw
267+
}
268+
269+
Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter {
270+
$PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$SemVer"
271+
}
272+
}
273+
274+
It "When there's a prefix empty line, it should insert the module name and full semver the same way" {
275+
# If there's no release notes, but it was left uncommented
276+
Mock Get-Metadata { "
277+
Multi-line Release Notes
278+
With a prefix carriage return" } -ModuleName ModuleBuilder
279+
280+
try {
281+
Build-Module -SemVer $SemVer
282+
} catch {
283+
Pop-Location -StackName BuildModuleTest
284+
throw
285+
}
286+
287+
Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter {
288+
$PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "
289+
MyModule v$SemVer
290+
Multi-line Release Notes
291+
With a prefix carriage return"
292+
}
293+
}
294+
295+
Pop-Location -StackName BuildModuleTest
296+
}
188297
}

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ steps:
1414
- powershell: .\bootstrap.ps1
1515
displayName: 'Restore pre-requisites'
1616

17-
- powershell: .\build.ps1 -OutputDirectory $(Build.ArtifactStagingDirectory)\$(Build.DefinitionName) -Version $(GitVersion.AssemblySemVer) -Verbose
17+
- powershell: .\build.ps1 -OutputDirectory $(Build.ArtifactStagingDirectory)\$(Build.DefinitionName) -SemVer $(GitVersion.InformationalVersion) -Verbose
1818
displayName: 'Run build script'
1919

2020
- task: richardfennellBM.BM-VSTS-PesterRunner-Task.Pester-Task.Pester@8

build.ps1

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ param(
1010

1111
# The version of the output module
1212
[Alias("ModuleVersion")]
13-
[version]$Version
13+
[string]$SemVer
1414
)
1515

1616
# Sanitize parameters to pass to Build-Module
@@ -19,7 +19,6 @@ $null = $PSBoundParameters.Remove('Test')
1919
$ErrorActionPreference = "Stop"
2020
Push-Location $PSScriptRoot -StackName BuildBuildModule
2121
try {
22-
2322
# Build ModuleBuilder with ModuleBuilder:
2423
Write-Verbose "Compiling ModuleBuilderBootstrap module"
2524
$OFS = "`n`n"

0 commit comments

Comments
 (0)