We have a couple projects in DevOps that specify an incrementing build version as part of the release pipeline. The approach we have is working well and is deploying our projects with the proper version to our hosted environments. This approach, however, was causing some issues for local development and we needed a way to keep our local versions in sync with the releases. After looking into the behaviour I discovered we weren't the only ones. A quick search revealed many discussions trying to deal with this situation, and after some trial and error I was able to come up with a solution.
DevOps and PowerShell provide enough flexibility
The existing version update happens via a PowerShell script that runs as part of the release pipeline. First, a variable is set to the build version to be used. Then, the build command is called with that variable to specify the version. Because that process was working and in place prior to my involvement on the pipeline I didn't want to change it. Instead, I added a new pipeline step after the existing versioning step, and called a new custom PowerShell script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | trigger: - develop pool: vmImage : 'windows-latest' variables: # My project variables here steps: # This is important to allow scripts access to auth token - checkout : self persistCredentials : true # Set Build Number to variable - task : PowerShell@2 displayName : 'Set the Build Number' inputs : targetType : inline script : Write-Host "Setting Build Number" Write-Host " ###vso[task.setvariable variable=buildNumber ] $(VersionNumber).$(VersionRevision).$(($(Get-Date).ToUniversalTime()- [ datetime ] "01/01/2022 00: 00 ").Days.ToString()).$([System.Math]::Floor($(Get-Date).TimeOfDay.TotalMinutes).ToString())" # Extra step for debugging version - task : PowerShell@2 displayName : 'Confirm Build Number' inputs : targetType : inline script : Write-Host "Build Number $(buildNumber)" # Update version in code in repo - task : PowerShell@2 displayName : 'Update Project Version' inputs : targetType : filePath filePath : 'dev\_SolutionItems\Powershell Scripts\UpdateProjectVersions.ps1' arguments : '$(Build.SourceBranch) $(Build.SourcesDirectory) $(buildNumber)' # Extra steps for various build and configuration tasks go here # Build command specifying version previously set - task : DotNetCoreCLI@2 displayName : 'Build Code' inputs : command : build projects : '$(projects)' arguments : '--configuration $(buildConfiguration) --framework $(coreFramework) -p:Version=$(buildNumber)' # Code to publish and deploy package |
Pay attention to the first two lines under steps declaring a checkout step with the "persistCredentials" option set to "true".
This ensures that scripts executing can access the system token. This
is needed for proper permissions for the GIT operations in the
PowerShell script.
The custom PowerShell script updates the version information in the code base.
The important thing about updating the version in the codebase is it needs to make that update to the code in the repository. The script below is the solution to perform that task. It takes in 3 required parameters to specify the solution root directory, the version number to use, and the calling branch from our GIT repo. It then performs the following tasks:
- Create a temporary GIT branch on the pipeline agent
- Identify and loop through all CSProj files in the solution directories
- Set version information on all identified CSProj files (.NET Core+)
- Add the updates to the GIT repo for the temp branch
- Set the Username and Email for the commit details to reflect the Azure Agent
- Commit changes to the temp branch
- Merge and push the changes into the source branch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | param ( [Parameter(Position=0, Mandatory)] [ValidateNotNullOrEmpty()] [string] $sourceBranch , [Parameter(Position=1, Mandatory)] [ValidateNotNullOrEmpty()] [string] $rootDir , [Parameter(Position=2, Mandatory)] [ValidateNotNullOrEmpty()] [string] $version ) if([string]::IsNullOrWhiteSpace( $sourceBranch )){ throw "Source branch must be specified." } if([string]::IsNullOrWhiteSpace( $rootDir )){ throw "Please specify the root path containing the CSProj files to update." } if([string]::IsNullOrWhiteSpace( $version )){ throw "Please specify the version number to be used." } $sourceBranch = $sourceBranch .Replace( "refs/heads/" , "" ) $tempBranch = "temp/UpdateVersion_$($version)" # create a new branch for hosting update git branch $tempBranch - -quiet # switch to new branch for performing updates git checkout $tempBranch - -quiet # function to update or add and update XML nodes in project group Function SetNodeValue( $xmlRootNode , [string] $nodeName , [string] $nodeValue ) { $propGroup = $xmlRootNode .Node.SelectSingleNode( "PropertyGroup" ) $node = $propGroup .SelectSingleNode( "./$($nodeName)" ) if ( $node -eq $null ) { Write-Host "Adding node for $($nodeName)" $node = $propGroup .OwnerDocument.CreateElement( $nodeName ) $nodeAdded = $propGroup .AppendChild( $node ) # do this to avoid console output } else { Write-Host "Existing node found for $($nodeName)" } $node .InnerText = $nodeValue Write-Host "Set value $($nodeValue) for node $($nodeName)" } # loop all CSProj files so they are all on same version Get-ChildItem -Path $rootDir -Filter "*.csproj" -Recurse -File | ForEach-Object { Write-Host "Found project file $($_.FullName)" $projPath = $_.FullName $projXml = Select -Xml $projPath -XPath "//Project" SetNodeValue $projXml "AssemblyVersion" $version SetNodeValue $projXml "FileVersion" $version SetNodeValue $projXml "Version" $version $doc = $projXml .Node.OwnerDocument $doc .PreserveWhitespace = $true $doc .save( $projPath ) # add the updated CSProj to GIT git add $projPath } # update email and username to identify repo updates by automated pipe - not a real address git config user.email "azure.agent@mydomain.com" git config user.name "Azure Agent" - -replace -all # commit changes with a comment identifying pipeline mod git commit -m "[auto] Update Version: Set project versions to $($version)" # switch back to the original branch that triggered this git checkout $sourceBranch - -quiet # pull latest from git git pull - -quiet # merge the proj updates back into source branch, with comment to indicate operation git merge $tempBranch -m "[auto] Update Version: Complete version to $($version) for source $($sourceBranch)" - -quiet # push changes back into source branch git push -u origin - -quiet |
This process updates the version information in our repo
With
this script in place, each time a merge happens to our pipeline branch,
the develop branch in this case, the version will be updated in each
project file for our solution. After this, any pull of that branch will
include the latest version number in it. This does mean that any local
branches in progress when this happens need to pull down and merge the
latest version before committing to include the latest changes without
conflict. But that is a small price to pay for the automation this
provides.
No comments:
Post a Comment
Share your thoughts or ask questions...