Skip to content

Automate Release Process (#45) #194

Automate Release Process (#45)

Automate Release Process (#45) #194

Workflow file for this run

name: CI
on:
push:
branches:
- 'main'
paths-ignore:
- 'docs/**'
- README.md
- CHANGELOG.md
pull_request:
branches: [ "main" ]
workflow_dispatch:
inputs:
deploy_packages:
description: 'deploy_packages: If the created package should be deployed (additional manual approval required by a release admin)'
type: boolean
default: false
required: true
is_production_release:
description: 'is_production_release: Whether the release is a production release and not a pre-release (enabling this will update change log, increases version and tags commit)'
type: boolean
default: false
required: true
custom_version_suffix:
description: 'custom_version_suffix: Custom suffix for the NuGet packages (without leading -) for non-production releases. Default: empty for production release, "ci<DATE>" for other runs. The build ID is always appended.'
required: false
custom_configuration:
description: 'custom_configuration: Custom build configuration. Default: "Debug" for CI builds, "Release" for deployments.'
required: false
default: 'Default'
type: choice
options:
- Default
- Debug
- Release
permissions:
checks: write
jobs:
build:
runs-on: windows-latest
outputs:
product_version_prefix: ${{ steps.versions.outputs.product_version_prefix }}
product_version_suffix: ${{ steps.versions.outputs.product_version_suffix }}
product_main_version: ${{ steps.versions.outputs.product_main_version }}
product_patch_version: ${{ steps.versions.outputs.product_patch_version }}
product_full_version: ${{ steps.versions.outputs.product_full_version }}
product_configuration: ${{ steps.versions.outputs.product_configuration }}
deploy_packages: ${{ steps.versions.outputs.deploy_packages }}
is_production_release: ${{ steps.versions.outputs.is_production_release }}
build_params: ${{ steps.build_params.outputs.build_params }}
test_params: ${{ steps.build_params.outputs.test_params }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- id: versions
name: Calculate versions
shell: pwsh
run: |
$deployPackages = $false
if ("${{ inputs.deploy_packages }}" -eq 'true') {
$deployPackages = $true
}
Write-Output "deploy_packages=$($deployPackages.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
Write-Output "Deploy packages: $deployPackages"
$isProductionRelease = $false
if ("${{ inputs.is_production_release }}" -eq 'true') {
$isProductionRelease = $true
}
Write-Output "is_production_release=$($isProductionRelease.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
Write-Output "Is production release: $isProductionRelease"
$versionSuffix = "${{ inputs.custom_version_suffix }}"
if ($isProductionRelease){
if ($versionSuffix -ne "") {
throw "The 'custom_version_suffix' setting cannot be used for production releases."
}
}
else {
if ($versionSuffix -eq "") {
$date = [datetime]::Today
$dateString = $date.ToString('yyyyMMdd')
$versionSuffix = "ci$dateString-${env:GITHUB_RUN_NUMBER}"
}
else {
$versionSuffix = "$versionSuffix-${env:GITHUB_RUN_NUMBER}"
}
}
Write-Output "product_version_suffix=$versionSuffix" >> $env:GITHUB_OUTPUT
Write-Output "Product Version Suffix: $versionSuffix"
$productConfig = "${{ inputs.custom_configuration }}"
if (($productConfig -eq "Default") -or ($productConfig -eq "")) {
if ($deployPackages){
$productConfig = "Release"
}
else {
$productConfig = "Debug"
}
}
Write-Output "product_configuration=$productConfig" >> $env:GITHUB_OUTPUT
Write-Output "Product Configuration: $productConfig"
# Load Version
$buildPropsXml = [xml](Get-Content Directory.Build.props)
$mainVersion = $buildPropsXml.SelectSingleNode('//Project/PropertyGroup/ReqnrollMainVersion/text()').Value.Trim()
Write-Output "product_main_version=$mainVersion" >> $env:GITHUB_OUTPUT
Write-Output "Product Main Version: $mainVersion"
# we use the GH build number as patch number
$patchVersion = ${env:GITHUB_RUN_NUMBER}
Write-Output "product_patch_version=$patchVersion" >> $env:GITHUB_OUTPUT
Write-Output "Product Patch Version: $patchVersion"
$versionPrefix = "$mainVersion.$patchVersion"
Write-Output "product_version_prefix=$versionPrefix" >> $env:GITHUB_OUTPUT
Write-Output "Product Version Prefix: $versionPrefix"
$fullVersion = $versionPrefix
if ($versionSuffix -ne "") {
$fullVersion = "$fullVersion-$versionSuffix"
}
Write-Output "product_full_version=$fullVersion" >> $env:GITHUB_OUTPUT
Write-Output "Product Full Version: $fullVersion"
- id: build_params
name: Calculate build parameters
shell: pwsh
env:
APPINSIGHTS_KEY: ${{ secrets.APPINSIGHTS_KEY }}
run: |
# Load version fields to variables
$versionSuffix = '${{ steps.versions.outputs.product_version_suffix }}'
$productConfig = '${{ steps.versions.outputs.product_configuration }}'
$patchVersion = '${{ steps.versions.outputs.product_patch_version }}'
$deployPackages = '${{ steps.versions.outputs.deploy_packages }}' -eq 'true'
# Calculate 'build_params'
# configuration is handled in a special way during build, hence not included here
$buildParams = "-p:VersionSuffix=$versionSuffix -property:ReqnrollBuildNumber=$patchVersion"
Write-Output "build_params=$buildParams" >> $env:GITHUB_OUTPUT
Write-Output "Build Params: $buildParams"
# Calculate 'main_build_params'
$mainBuildParams = $buildParams
if ($deployPackages) {
$mainBuildParams = "$mainBuildParams -p:AppInsightsInstrumentationKey=$env:APPINSIGHTS_KEY"
Write-Output "Main Build Params Updated for Deployment"
}
Write-Output "main_build_params=$mainBuildParams" >> $env:GITHUB_OUTPUT
# Calculate 'test_params'
$gitHubActionsLoggerSettings = '"GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.titleFormat=[@traits.Category] @test;annotations.messageFormat=@error\n@trace"'
$testParams = "--no-build --verbosity normal -c $productConfig --logger trx --logger $gitHubActionsLoggerSettings -- RunConfiguration.CollectSourceInformation=true RunConfiguration.TreatNoTestsAsError=true"
Write-Output "test_params=$testParams" >> $env:GITHUB_OUTPUT
Write-Output "Test Params: $testParams"
- name: Update Changelog
shell: pwsh
run: |
$releaseDate = [System.DateTime]::Today.ToString("yyyy-MM-dd")
$newHeading = "# v${{ steps.versions.outputs.product_full_version }} - $releaseDate"
$content = [System.IO.File]::ReadAllText("CHANGELOG.md").Replace("# [vNext]",$newHeading)
[System.IO.File]::WriteAllText("CHANGELOG.md", $content)
- name: Restore dependencies
run: dotnet restore
- name: Build Connectors
shell: pwsh
run: |
cd Connectors
.\build.ps1 -configuration ${{ steps.versions.outputs.product_configuration }} -additionalArgs "${{ steps.build_params.outputs.main_build_params }}"
- name: Install Test Report Dependencies
run: |
dotnet add ./Tests/Reqnroll.VisualStudio.Specs/Reqnroll.VisualStudio.Specs.csproj package GitHubActionsTestLogger
dotnet add ./Tests/Reqnroll.VisualStudio.Tests/Reqnroll.VisualStudio.Tests.csproj package GitHubActionsTestLogger
dotnet add ./Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/Reqnroll.VisualStudio.ReqnrollConnector.Tests.csproj package GitHubActionsTestLogger
dotnet add ./Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.V1.Tests/Reqnroll.VisualStudio.ReqnrollConnector.V1.Tests.csproj package GitHubActionsTestLogger
- name: Add MsBuild to PATH
uses: microsoft/setup-msbuild@v1.3
- name: Build
run: msbuild -restore -target:Rebuild -property:DeployExtension=false -property:Configuration=${{ steps.versions.outputs.product_configuration }} ${{ steps.build_params.outputs.main_build_params }}
- name: Unit Tests
run: dotnet test ./Tests/Reqnroll.VisualStudio.Tests/Reqnroll.VisualStudio.Tests.csproj ${{ steps.build_params.outputs.test_params }}
- name: Review modified files
run: git status
- name: Upload VSIX
uses: actions/upload-artifact@v4
with:
name: vsix-v${{ steps.versions.outputs.product_full_version }}
if-no-files-found: error
path: "Reqnroll.VisualStudio.Package/bin/${{ steps.versions.outputs.product_configuration }}/net481/*.vsix"
- uses: nuget/setup-nuget@v1.2
- name: Prepare Connector Tests
shell: pwsh
run: |
cd Tests/ExternalPackages
./buildExternalPackages.ps1
cd PackagesForTests
nuget install packages.config
- name: Connector Tests
run: dotnet test ./Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/Reqnroll.VisualStudio.ReqnrollConnector.Tests.csproj ${{ steps.build_params.outputs.test_params }}
- name: Connector V1 Tests
run: dotnet test ./Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.V1.Tests/Reqnroll.VisualStudio.ReqnrollConnector.V1.Tests.csproj ${{ steps.build_params.outputs.test_params }}
- name: Specs Tests
run: >
dotnet test ./Tests/Reqnroll.VisualStudio.Specs/Reqnroll.VisualStudio.Specs.csproj
--filter "Category!=quarantine"
${{ steps.build_params.outputs.test_params }}
- name: Upload Test Result TRX Files
uses: actions/upload-artifact@v4
if: always()
with:
name: build-trx-v${{ steps.versions.outputs.product_full_version }}
if-no-files-found: error
path: "**/*.trx"
release:
runs-on: windows-latest
needs: [build]
environment: production_environment
if: github.ref == 'refs/heads/main' && needs.build.outputs.deploy_packages == 'true'
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # avoid shallow clone git commit
ref: ${{ github.head_ref }}
ssh-key: ${{secrets.RELEASE_GIT_SSH_KEY}}
- uses: actions/download-artifact@v4
with:
name: vsix-v${{ needs.build.outputs.product_full_version }}
path: release_packages
- name: Verify VSIX Deploy settings
shell: pwsh
run: |
Write-Output "Deploying v${{ needs.build.outputs.product_full_version }} (v${{ needs.build.outputs.product_main_version }}) package to Visual Studio Marketplace"
ls ${{ github.workspace }}/release_packages
$isProductionRelease = '${{ needs.build.outputs.is_production_release }}' -eq 'true'
if (-not $isProductionRelease) {
throw "Preview releases are currently not supported. (The 'is_production_release' must be set.)"
}
- name: Deploy VSIX packages
uses: CodingWithCalvin/GHA-VSMarketplacePublisher@v2.0.0
with:
marketplace-pat: ${{ secrets.MARKETPLACE_PUBLISH_PAT }}
publish-manifest-path: ${{ github.workspace }}/.marketplace/publishManifest.json
vsix-path: ${{ github.workspace }}/release_packages/Reqnroll.VisualStudio.Package.v${{ needs.build.outputs.product_version_prefix }}.vsix
- name: Calculate Next Version
if: needs.build.outputs.is_production_release == 'true'
id: next_version
shell: pwsh
run: |
$mainVersion = "${{ needs.build.outputs.product_main_version }}"
$majorVersion = &{$mainVersion -match '^\d+' > $null; $matches[0]}
$minorVersion = &{$mainVersion -match '\d+$' > $null; $matches[0]}
$nextMinor = [int]$minorVersion + 1
$nextMainVersion = "$majorVersion.$nextMinor"
Write-Output "product_next_main_version=$nextMainVersion" >> $env:GITHUB_OUTPUT
Write-Output "Product Next Main Version: $nextMainVersion"
- name: Bump Version
if: needs.build.outputs.is_production_release == 'true'
shell: pwsh
run: |
[System.IO.File]::WriteAllText("Directory.Build.props", [System.IO.File]::ReadAllText("Directory.Build.props").Replace("<ReqnrollMainVersion>${{ needs.build.outputs.product_main_version }}</ReqnrollMainVersion>", "<ReqnrollMainVersion>${{ steps.next_version.outputs.product_next_main_version }}</ReqnrollMainVersion>"))
$vsTemplates = Get-ChildItem -Path . -File -Recurse -Filter '*.vstemplate'
foreach ($vsTemplate in $vsTemplates) {
[System.IO.File]::WriteAllText($vsTemplate.FullName, [System.IO.File]::ReadAllText($vsTemplate.FullName).Replace("Version=${{ needs.build.outputs.product_main_version }}.0.0", "Version=${{ steps.next_version.outputs.product_next_main_version }}.0.0"))
}
- name: Update Changelog
if: needs.build.outputs.is_production_release == 'true'
id: changelog
shell: pwsh
run: |
$newHeading = "# [vNext]$([Environment]::NewLine)$([Environment]::NewLine)## Improvements:$([Environment]::NewLine)$([Environment]::NewLine)## Bug fixes:$([Environment]::NewLine)$([Environment]::NewLine)*Contributors of this release (in alphabetical order):* $([Environment]::NewLine)$([Environment]::NewLine)"
$releaseDate = [System.DateTime]::Today.ToString("yyyy-MM-dd")
$releaseTitle = "v${{ needs.build.outputs.product_full_version }} - $releaseDate"
$newHeading = $newHeading + "# $releaseTitle"
$content = [System.IO.File]::ReadAllText("CHANGELOG.md").Replace("# [vNext]",$newHeading)
[System.IO.File]::WriteAllText("CHANGELOG.md", $content)
Write-Output "New Heading:"
Write-Output $newHeading
# calculate release notes
$match = [System.Text.RegularExpressions.Regex]::Match($content, "(?ms)^# .*?^# (?<title>[^\r\n]*?)\s*$\s*(?<notes>.*?)\s*(?:^# |\Z)")
$releaseNotes = $(if ($match.Success) { $match.Groups["notes"].Value } else { "N/A" })
[System.IO.File]::WriteAllText("release_notes.txt", $releaseNotes)
Write-Output "release_title=$releaseTitle" >> $env:GITHUB_OUTPUT
Write-Output "release_notes_file=release_notes.txt" >> $env:GITHUB_OUTPUT
- name: Update changes in GitHub repository
if: needs.build.outputs.is_production_release == 'true'
run: |
git status
git config --global user.name 'Reqnroll CI'
git config --global user.email 'ci@reqnroll.net'
git tag v${{ needs.build.outputs.product_full_version }}
# git push origin tag v${{ needs.build.outputs.product_full_version }}
git add -u
git commit -m '[automated commit] bump version after release of ${{ needs.build.outputs.product_full_version }}'
# git push
- name: Create GitHub Release
if: needs.build.outputs.is_production_release == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "v${{ needs.build.outputs.product_full_version }}" \
--verify-tag \
--title="${{ steps.changelog.outputs.release_title }}" \
--notes-file="${{ steps.changelog.outputs.release_notes_file }}"