Automate Release Process #192
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }}" |