diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000000..fb54dfd04f5 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,139 @@ +name: Prepare for a release + +on: + workflow_dispatch: + inputs: + tag-prefix: + type: choice + options: + - core- + - coreunstable- + description: 'Release tag prefix' + required: true + version: + type: string + description: 'Release version' + required: true + + pull_request: + types: + - closed + + issue_comment: + types: + - created + +permissions: + contents: write + pull-requests: write + +jobs: + prepare-release-pr: + if: github.event_name == 'workflow_dispatch' + + runs-on: windows-latest + + steps: + - name: check out code + uses: actions/checkout@v4 + + - name: Create GitHub Pull Request to prepare release + shell: pwsh + run: | + Import-Module .\build\scripts\prepare-release.psm1 + + CreatePullRequestToUpdateChangelogsAndPublicApis ` + -minVerTagPrefix '${{ inputs.tag-prefix }}' ` + -version '${{ inputs.version }}' ` + -targetBranch '${{ github.ref_name }}' + env: + GH_TOKEN: ${{ github.token }} + + lock-pr-and-post-notice-to-create-release-tag: + if: | + github.event_name == 'pull_request' + && github.event.action == 'closed' + && github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.merged == true + && startsWith(github.event.pull_request.title, '[repo] Prepare release ') + + runs-on: windows-latest + + steps: + - name: check out code + uses: actions/checkout@v4 + + - name: Lock GitHub Pull Request to prepare release + shell: pwsh + run: | + Import-Module .\build\scripts\prepare-release.psm1 + + LockPullRequestAndPostNoticeToCreateReleaseTag ` + -pullRequestNumber '${{ github.event.pull_request.number }}' + env: + GH_TOKEN: ${{ github.token }} + + create-release-tag-unlock-pr-post-notice: + if: | + github.event_name == 'issue_comment' + && github.event.issue.pull_request + && github.event.issue.locked == true + && contains(github.event.comment.body, '/CreateReleaseTag') + && startsWith(github.event.issue.title, '[repo] Prepare release ') + && github.event.issue.pull_request.merged_at + + runs-on: windows-latest + + outputs: + tag: ${{ steps.create-tag.outputs.tag }} + + steps: + - name: check out code + uses: actions/checkout@v4 + with: + # Note: By default GitHub only fetches 1 commit which fails the git tag operation below + fetch-depth: 0 + + - name: Create release tag + id: create-tag + shell: pwsh + run: | + Import-Module .\build\scripts\prepare-release.psm1 + + $tag = '' + + CreateReleaseTag ` + -pullRequestNumber '${{ github.event.issue.number }}' ` + -actionRunId '${{ github.run_id }}' ` + -tag ([ref]$tag) + + echo "tag=$tag" >> $env:GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + invoke-package-workflow: + needs: create-release-tag-unlock-pr-post-notice + uses: ./.github/workflows/publish-packages-1.0.yml + with: + tag: ${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }} + + post-packages-ready-notice: + needs: + - create-release-tag-unlock-pr-post-notice + - invoke-package-workflow + runs-on: windows-latest + steps: + - name: check out code + uses: actions/checkout@v4 + + - name: Post notice when packages are ready + shell: pwsh + run: | + Import-Module .\build\scripts\prepare-release.psm1 + + PostPackagesReadyNotice ` + -pullRequestNumber '${{ github.event.issue.number }}' ` + -tag '${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }}' ` + -packagesUrl '${{ needs.invoke-package-workflow.outputs.artifact-url }}' + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/publish-packages-1.0.yml b/.github/workflows/publish-packages-1.0.yml index a68f0efa82c..e93b6b282df 100644 --- a/.github/workflows/publish-packages-1.0.yml +++ b/.github/workflows/publish-packages-1.0.yml @@ -14,6 +14,16 @@ on: - 'core-*' - 'coreunstable-*' - 'Instrumentation.*-' + workflow_call: + inputs: + tag: + required: true + type: string + outputs: + artifact-id: + value: ${{ jobs.build-pack-publish.outputs.artifact-id }} + artifact-url: + value: ${{ jobs.build-pack-publish.outputs.artifact-url }} schedule: - cron: '0 0 * * *' # once in a day at 00:00 @@ -25,6 +35,10 @@ jobs: build-pack-publish: runs-on: windows-latest + outputs: + artifact-id: ${{ steps.upload-artifacts.outputs.artifact-id }} + artifact-url: ${{ steps.upload-artifacts.outputs.artifact-url }} + steps: - uses: actions/checkout@v4 with: @@ -32,7 +46,7 @@ jobs: # the version tag which is typically NOT on the first commit so we # retrieve them all. fetch-depth: 0 - ref: ${{ github.ref || 'main' }} + ref: ${{ inputs.tag || github.ref || 'main' }} - name: Setup dotnet uses: actions/setup-dotnet@v4 @@ -44,12 +58,13 @@ jobs: run: dotnet build OpenTelemetry.proj --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} -p:RunningDotNetPack=true - name: dotnet pack - run: dotnet pack OpenTelemetry.proj --configuration Release --no-restore --no-build -p:PackTag=${{ github.ref_type == 'tag' && github.ref_name || '' }} + run: dotnet pack OpenTelemetry.proj --configuration Release --no-restore --no-build -p:PackTag=${{ github.ref_type == 'tag' && github.ref_name || inputs.tag || '' }} - name: Publish Artifacts + id: upload-artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.ref_name }}-packages + name: ${{ inputs.tag || github.ref_name }}-packages path: '**/bin/**/*.*nupkg' - name: Publish MyGet @@ -61,7 +76,7 @@ jobs: nuget push **/bin/**/*.nupkg -Source https://www.myget.org/F/opentelemetry/api/v2/package - name: Create GitHub Release draft - if: github.ref_type == 'tag' + if: github.ref_type == 'tag' || inputs.tag shell: pwsh run: | $packages = (Get-ChildItem -Path src/*/bin/Release/*.nupkg).Name @@ -88,7 +103,7 @@ jobs: foreach ($line in $changelogContent) { - if ($line -like "## ${{ github.ref_name }}" -and $started -ne $true) + if ($line -like "## $packageVersion" -and $started -ne $true) { $started = $true } @@ -122,15 +137,15 @@ jobs: $content - See [CHANGELOG](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/src/$packageName/CHANGELOG.md) for details. + See [CHANGELOG](https://github.com/${{ github.repository }}/blob/${{ inputs.tag || github.ref_name }}/src/$packageName/CHANGELOG.md) for details. "@ } if ($firstPackageVersion -match '-alpha' -or $firstPackageVersion -match '-beta' -or $firstPackageVersion -match '-rc') { - gh release create ${{ github.ref_name }} ` - --title ${{ github.ref_name }} ` + gh release create ${{ inputs.tag || github.ref_name }} ` + --title ${{ inputs.tag || github.ref_name }} ` --verify-tag ` --notes "$notes" ` --prerelease ` @@ -138,8 +153,8 @@ jobs: } else { - gh release create ${{ github.ref_name }} ` - --title ${{ github.ref_name }} ` + gh release create ${{ inputs.tag || github.ref_name }} ` + --title ${{ inputs.tag || github.ref_name }} ` --verify-tag ` --notes "$notes" ` --latest ` @@ -149,20 +164,22 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Create GitHub draft Pull Request to update stable build version in props - if: github.ref_type == 'tag' && startsWith(github.ref_name, 'core-') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && !contains(github.ref_name, '-rc') + if: | + (github.ref_type == 'tag' && startsWith(github.ref_name, 'core-') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && !contains(github.ref_name, '-rc')) + || (inputs.tag && startsWith(inputs.tag, 'core-') && !contains(inputs.tag, '-alpha') && !contains(inputs.tag, '-beta') && !contains(inputs.tag, '-rc')) shell: pwsh run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git switch --create release/post-stable-${{ github.ref_name }}-update 2>&1 | % ToString + git switch --create release/post-stable-${{ inputs.tag || github.ref_name }}-update main 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { Write-Error 'git switch failure' Return } - $match = [regex]::Match('${{ github.ref_name }}', '.*?-(.*)') + $match = [regex]::Match('${{ inputs.tag || github.ref_name }}', '.*?-(.*)') $packageVersion = $match.Groups[1].Value (Get-Content Directory.Packages.props) ` @@ -183,7 +200,7 @@ jobs: Return } - git push -u origin release/post-stable-${{ github.ref_name }}-update 2>&1 | % ToString + git push -u origin release/post-stable-${{ inputs.tag || github.ref_name }}-update 2>&1 | % ToString if ($LASTEXITCODE -gt 0) { Write-Error 'git push failure' @@ -205,7 +222,7 @@ jobs: --title "[repo] Core stable release $packageVersion updates" ` --body $body ` --base main ` - --head release/post-stable-${{ github.ref_name }}-update ` + --head release/post-stable-${{ inputs.tag || github.ref_name }}-update ` --label infra ` --draft env: diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 30672f8e7fc..c388b69fd5f 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,7 +1,12 @@ # Default state for all rules default: true -# allow long lines for tables and code blocks +# MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md013.md MD013: code_blocks: false tables: false + +# MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md033.md +MD033: + # Allowed elements + allowed_elements: [ 'details', 'summary' ] diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 2808bd5f009..e60cbeaf667 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -34,7 +34,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E build\docker-compose.net6.0.yml = build\docker-compose.net6.0.yml build\docker-compose.net7.0.yml = build\docker-compose.net7.0.yml build\docker-compose.net8.0.yml = build\docker-compose.net8.0.yml - build\finalize-publicapi.ps1 = build\finalize-publicapi.ps1 build\GlobalAttrExclusions.txt = build\GlobalAttrExclusions.txt build\opentelemetry-icon-color.png = build\opentelemetry-icon-color.png build\OpenTelemetry.prod.loose.ruleset = build\OpenTelemetry.prod.loose.ruleset @@ -46,7 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E build\test-aot-compatibility.ps1 = build\test-aot-compatibility.ps1 build\test-threadSafety.ps1 = build\test-threadSafety.ps1 build\UnstableCoreLibraries.proj = build\UnstableCoreLibraries.proj - build\update-changelogs.ps1 = build\update-changelogs.ps1 build\xunit.runner.json = build\xunit.runner.json EndProjectSection EndProject @@ -99,6 +97,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\dotnet-format.yml = .github\workflows\dotnet-format.yml .github\workflows\markdownlint.yml = .github\workflows\markdownlint.yml .github\workflows\package-validation.yml = .github\workflows\package-validation.yml + .github\workflows\prepare-release.yml = .github\workflows\prepare-release.yml .github\workflows\publish-packages-1.0.yml = .github\workflows\publish-packages-1.0.yml .github\workflows\sanitycheck.yml = .github\workflows\sanitycheck.yml .github\workflows\stale.yml = .github\workflows\stale.yml @@ -341,6 +340,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{44982E0D-C8C6-42DC-9F8F-714981F27CE6}" ProjectSection(SolutionItems) = preProject build\scripts\add-labels.ps1 = build\scripts\add-labels.ps1 + build\scripts\finalize-publicapi.ps1 = build\scripts\finalize-publicapi.ps1 + build\scripts\prepare-release.psm1 = build\scripts\prepare-release.psm1 + build\scripts\update-changelogs.ps1 = build\scripts\update-changelogs.ps1 EndProjectSection EndProject Global diff --git a/build/RELEASING.md b/build/RELEASING.md index 5185af401de..035aad38d6b 100644 --- a/build/RELEASING.md +++ b/build/RELEASING.md @@ -2,7 +2,9 @@ **Only for Maintainers.** - 1. Decide the component(s) and tag name (version name) to be released. + 1. Decide the component(s) and tag name (version name) to be released. We use + [MinVer](https://github.com/adamralph/minver) to do versioning, which + produces version numbers based on git tags. Notes: @@ -44,25 +46,59 @@ `OTelLatestStableVer` property in `Directory.Packages.props` has been updated to the latest stable core version. - 2. Update CHANGELOG files - - Run the PowerShell script `.\build\update-changelogs.ps1 -minVerTagPrefix - [MinVerTagPrefix] -version [Version]`. Where `[MinVerTagPrefix]` is the tag - prefix (eg `core-`) for the components being released and `[Version]` is the - version being released (eg `1.9.0`). This will update `CHANGELOG.md` files - for the projects being released. - - 3. **Stable releases only**: Normalize PublicApi files - - Run the PowerShell script `.\build\finalize-publicapi.ps1 -minVerTagPrefix - [MinVerTagPrefix]`. Where `[MinVerTagPrefix]` is the tag prefix (eg `core-`) - for the components being released. This will merge the contents of any - detected `PublicAPI.Unshipped.txt` files in the `.publicApi` folder into the + 2. Prepare for release + + Run the [Prepare for a + release](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/prepare-release.yml) + workflow. Specify the `tag-prefix` and the `version` for the release. Make + sure to run the workflow on the branch being released. This is typically + `main` but could be some other branch for hotfix (eg `main-1.8.0`). The + workflow will open a PR to update `CHANGELOG.md` files for the projects + being released. If a stable version is specified as the `version` parameter, + the workflow will also merge the contents of any detected + `PublicAPI.Unshipped.txt` files in the `.publicApi` folder into the corresponding `PublicAPI.Shipped.txt` files for the projects being released. - 4. Tag Git with version to be released. We use - [MinVer](https://github.com/adamralph/minver) to do versioning, which - produces version numbers based on git tags. +
+ Instructions for preparing for a release manually + + * Update CHANGELOG files + + Run the PowerShell script `.\build\scripts\update-changelogs.ps1 + -minVerTagPrefix [MinVerTagPrefix] -version [Version]`. Where + `[MinVerTagPrefix]` is the tag prefix (eg `core-`) for the components + being released and `[Version]` is the version being released (eg + `1.9.0`). This will update `CHANGELOG.md` files for the projects being + released. + + * **Stable releases only**: Normalize PublicApi files + + Run the PowerShell script `.\build\scripts\finalize-publicapi.ps1 + -minVerTagPrefix [MinVerTagPrefix]`. Where `[MinVerTagPrefix]` is the tag + prefix (eg `core-`) for the components being released. This will merge + the contents of any detected `PublicAPI.Unshipped.txt` files in the + `.publicApi` folder into the corresponding `PublicAPI.Shipped.txt` files + for the projects being released. +
+ Instructions for pushing tags manually Note: In the below examples `git push origin` is used. If running in a fork, add the main repo as `upstream` and use `git push upstream` instead. Pushing @@ -87,6 +123,7 @@ Pushing the tag will kick off the [Build, pack, and publish to MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) workflow. + 5. :stop_sign: Wait for the [Build, pack, and publish to MyGet](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) diff --git a/build/finalize-publicapi.ps1 b/build/scripts/finalize-publicapi.ps1 similarity index 100% rename from build/finalize-publicapi.ps1 rename to build/scripts/finalize-publicapi.ps1 diff --git a/build/scripts/prepare-release.psm1 b/build/scripts/prepare-release.psm1 new file mode 100644 index 00000000000..ad2b0b53e77 --- /dev/null +++ b/build/scripts/prepare-release.psm1 @@ -0,0 +1,194 @@ +$gitHubBotUserName="github-actions[bot]" +$gitHubBotEmail="41898282+github-actions[bot]@users.noreply.github.com" + +$repoViewResponse = gh repo view --json nameWithOwner | ConvertFrom-Json + +$gitRepository = $repoViewResponse.nameWithOwner + +function CreatePullRequestToUpdateChangelogsAndPublicApis { + param( + [Parameter(Mandatory=$true)][string]$minVerTagPrefix, + [Parameter(Mandatory=$true)][string]$version, + [Parameter()][string]$gitUserName=$gitHubBotUserName, + [Parameter()][string]$gitUserEmail=$gitHubBotEmail, + [Parameter()][string]$targetBranch="main" + ) + + $tag="${minVerTagPrefix}${version}" + $branch="release/prepare-${tag}-release" + + git config user.name $gitUserName + git config user.email $gitUserEmail + + git switch --create $branch 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git switch failure' + } + + $body = +@" +Note: This PR was opened automatically by the [prepare release workflow](https://github.com/$gitRepository/actions/workflows/prepare-release.yml). + +## Changes + +* CHANGELOG files updated for projects being released. +"@ + + # Update CHANGELOGs + & ./build/scripts/update-changelogs.ps1 -minVerTagPrefix $minVerTagPrefix -version $version + + # Update publicApi files for stable releases + if ($version -notlike "*-alpha*" -and $version -notlike "*-beta*" -and $version -notlike "*-rc*") + { + & ./build/scripts/finalize-publicapi.ps1 -minVerTagPrefix $minVerTagPrefix + + $body += "`r`n* Public API files updated for projects being released (only performed for stable releases)." + } + + git commit -a -m "Prepare repo to release $tag." 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git commit failure' + } + + git push -u origin $branch 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git push failure' + } + + gh pr create ` + --title "[repo] Prepare release $tag" ` + --body $body ` + --base $targetBranch ` + --head $branch ` + --label infra +} + +Export-ModuleMember -Function CreatePullRequestToUpdateChangelogsAndPublicApis + +function LockPullRequestAndPostNoticeToCreateReleaseTag { + param( + [Parameter(Mandatory=$true)][string]$pullRequestNumber, + [Parameter()][string]$gitUserName=$gitHubBotUserName, + [Parameter()][string]$gitUserEmail=$gitHubBotEmail + ) + + git config user.name $gitUserName + git config user.email $gitUserEmail + + $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json + + if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions') + { + throw 'PR author was unexpected' + } + + $match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse tag from PR title' + } + + $tag = $match.Groups[1].Value + + $commit = $prViewResponse.mergeCommit.oid + if ([string]::IsNullOrEmpty($commit) -eq $true) + { + throw 'Could not find merge commit' + } + + $body = +@" +I noticed this PR was merged. + +Post a comment with "/CreateReleaseTag" in the body if you would like me to create the release tag ``$tag`` for [the merge commit](https://github.com/$gitRepository/commit/$commit) and then trigger the package workflow. +"@ + + gh pr comment $pullRequestNumber --body $body + + gh pr lock $pullRequestNumber +} + +Export-ModuleMember -Function LockPullRequestAndPostNoticeToCreateReleaseTag + +function CreateReleaseTag { + param( + [Parameter(Mandatory=$true)][string]$pullRequestNumber, + [Parameter(Mandatory=$true)][string]$actionRunId, + [Parameter()][string]$gitUserName=$gitHubBotUserName, + [Parameter()][string]$gitUserEmail=$gitHubBotEmail, + [Parameter()][ref]$tag + ) + + git config user.name $gitUserName + git config user.email $gitUserEmail + + $prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json + + if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions') + { + throw 'PR author was unexpected' + } + + $match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$') + if ($match.Success -eq $false) + { + throw 'Could not parse tag from PR title' + } + + $tagValue = $match.Groups[1].Value + + $commit = $prViewResponse.mergeCommit.oid + if ([string]::IsNullOrEmpty($commit) -eq $true) + { + throw 'Could not find merge commit' + } + + git tag -a $tagValue -m "$tagValue" $commit 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git tag failure' + } + + git push origin $tagValue 2>&1 | % ToString + if ($LASTEXITCODE -gt 0) + { + throw 'git push failure' + } + + gh pr unlock $pullRequestNumber + + $body = +@" +I just pushed the [$tagValue](https://github.com/$gitRepository/releases/tag/$tagValue) tag. + +The [package workflow](https://github.com/$gitRepository/actions/runs/$actionRunId) should begin momentarily. +"@ + + gh pr comment $pullRequestNumber --body $body + + $tag.value = $tagValue +} + +Export-ModuleMember -Function CreateReleaseTag + +function PostPackagesReadyNotice { + param( + [Parameter(Mandatory=$true)][string]$pullRequestNumber, + [Parameter(Mandatory=$true)][string]$tag, + [Parameter(Mandatory=$true)][string]$packagesUrl + ) + + $body = +@" +The packages for [$tag](https://github.com/$gitRepository/releases/tag/$tag) are now available: $packagesUrl. + +Have a nice day! +"@ + + gh pr comment $pullRequestNumber --body $body +} + +Export-ModuleMember -Function PostPackagesReadyNotice diff --git a/build/update-changelogs.ps1 b/build/scripts/update-changelogs.ps1 similarity index 100% rename from build/update-changelogs.ps1 rename to build/scripts/update-changelogs.ps1