diff --git a/.github/scripts/draft-change-log-entries.sh b/.github/scripts/draft-change-log-entries.sh index 7ebb9448355d..885d14498ad0 100755 --- a/.github/scripts/draft-change-log-entries.sh +++ b/.github/scripts/draft-change-log-entries.sh @@ -28,6 +28,17 @@ echo echo "### Migration notes" echo echo + +# Add breaking changes and deprecations sections +if [[ -z $range ]]; then + labeled_range="HEAD" +else + labeled_range="$range" +fi + +"$(dirname "$0")/extract-labeled-prs.sh" "$labeled_range" +echo + echo "### 🌟 New javaagent instrumentation" echo echo diff --git a/.github/scripts/extract-labeled-prs.sh b/.github/scripts/extract-labeled-prs.sh new file mode 100755 index 000000000000..e01dbacb9eb9 --- /dev/null +++ b/.github/scripts/extract-labeled-prs.sh @@ -0,0 +1,78 @@ +#!/bin/bash -e + +# This script extracts PRs with "breaking change" and "deprecation" labels for the given version range +# Usage: extract-labeled-prs.sh [git-range] +# If no range is provided, it uses HEAD + +range="${1:-HEAD}" + +if [[ "$range" == "HEAD" ]]; then + # Get all commits from HEAD + commits=$(git log --reverse --pretty=format:"%H %s" HEAD) +else + # Get commits in the specified range + commits=$(git log --reverse --pretty=format:"%H %s" "$range") +fi + +# Initialize tracking variables +breaking_changes="" +deprecations="" +breaking_changes_found=false +deprecations_found=false + +# Process each commit to find PRs with specified labels +while IFS= read -r line; do + if [[ -z "$line" ]]; then + continue + fi + + # Extract PR number from commit message + if [[ $line =~ \(#([0-9]+)\)$ ]]; then + pr_number="${BASH_REMATCH[1]}" + commit_subject=$(echo "$line" | cut -d' ' -f2- | sed 's/ (#[0-9]*)$//') + + # Get PR labels using GitHub CLI + if pr_labels=$(gh pr view "$pr_number" --json labels --jq '.labels[].name' 2>/dev/null); then + # Check for breaking change label + if echo "$pr_labels" | grep -q "^breaking change$"; then + breaking_changes_found=true + breaking_changes+="- $commit_subject"$'\n'" ([#$pr_number](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/$pr_number))"$'\n'$'\n' + fi + + # Check for deprecation label + if echo "$pr_labels" | grep -q "^deprecation$"; then + deprecations_found=true + deprecations+="- $commit_subject"$'\n'" ([#$pr_number](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/$pr_number))"$'\n'$'\n' + fi + fi + fi +done <<< "$commits" + +# Output breaking changes section +if [[ "$breaking_changes_found" == "true" ]]; then + echo "### ⚠️ Breaking Changes" + echo + echo -n "$breaking_changes" +fi + +# Output deprecations section +if [[ "$deprecations_found" == "true" ]]; then + echo "### 🚫 Deprecations" + echo + echo -n "$deprecations" +fi + +# Output "no changes" messages if needed +if [[ "$breaking_changes_found" == "false" ]]; then + echo "### ⚠️ Breaking Changes" + echo + echo "*No breaking changes in this release.*" + echo +fi + +if [[ "$deprecations_found" == "false" ]]; then + echo "### 🚫 Deprecations" + echo + echo "*No deprecations in this release.*" + echo +fi diff --git a/.github/scripts/format-release-notes.sh b/.github/scripts/format-release-notes.sh new file mode 100755 index 000000000000..68a7cd08f8ef --- /dev/null +++ b/.github/scripts/format-release-notes.sh @@ -0,0 +1,57 @@ +#!/bin/bash -e + +# Format changelog section for GitHub release notes +# Usage: format-release-notes.sh + +changelog_section="$1" +output_file="$2" + +if [[ -z "$changelog_section" || -z "$output_file" ]]; then + echo "Usage: format-release-notes.sh " + exit 1 +fi + +if [[ ! -f "$changelog_section" ]]; then + echo "Error: Changelog section file '$changelog_section' not found" + exit 1 +fi + +{ + # Add breaking changes section if it exists + if grep -q "### ⚠️ Breaking Changes" "$changelog_section"; then + cat << 'EOF' + +## 🚨 IMPORTANT: Breaking Changes + +This release contains breaking changes. Please review the changes below: + +EOF + + # Extract breaking changes section, format for release notes + sed -n '/### ⚠️ Breaking Changes/,/^### /p' "$changelog_section" | sed '$d' | \ + perl -0pe 's/(? "$output_file" diff --git a/.github/workflows/pr-automation-comments.yml b/.github/workflows/pr-automation-comments.yml new file mode 100644 index 000000000000..d7f012bef972 --- /dev/null +++ b/.github/workflows/pr-automation-comments.yml @@ -0,0 +1,112 @@ +name: PR Automation Comments +on: + pull_request: + types: [labeled] + +permissions: + pull-requests: write + +jobs: + comment-on-breaking-change: + if: contains(github.event.label.name, 'breaking change') + runs-on: ubuntu-latest + steps: + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + // Check if we've already commented about breaking changes + const botComment = comments.find(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('⚠️ Breaking Change Documentation Required') + ); + + if (!botComment) { + const commentBody = [ + "## ⚠️ Breaking Change Documentation Required", + "", + "This PR has been labeled as a **breaking change**. Please ensure you provide the following information:", + "", + "### Migration Notes Required", + "Please add detailed migration notes to help users understand:", + "- What is changing and why", + "- How to update their code/configuration", + "- Any alternative approaches if applicable", + "- Code examples showing before/after usage", + "", + "### Checklist", + "- [ ] Migration notes added to the PR description", + "- [ ] Breaking change is documented in the changelog entry for the next release", + "- [ ] Consider if this change requires a major version bump", + "", + "Your migration notes will be included in the release notes to help users upgrade smoothly. The more detailed and helpful they are, the better the user experience will be.", + "", + "---", + "*This comment was automatically generated because the `breaking change` label was applied to this PR.*" + ].join("\n"); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } + + comment-on-deprecation: + if: contains(github.event.label.name, 'deprecation') + runs-on: ubuntu-latest + steps: + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + // Check if we've already commented about deprecation + const botComment = comments.find(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('📋 Deprecation Notice') + ); + + if (!botComment) { + const commentBody = [ + "## 📋 Deprecation Notice", + "", + "This PR has been labeled as a **deprecation**. Please ensure you provide the following information:", + "", + "### 📝 Deprecation Details Required", + "Please add details to help users understand:", + "- What is being deprecated and why", + "- What should be used instead (if applicable)", + "- Timeline for removal (if known)", + "- Any migration guidance", + "", + "### 📋 Checklist", + "- [ ] Deprecation details added to the PR description", + "- [ ] Deprecation is documented in the changelog entry for the next release", + "- [ ] Consider adding deprecation warnings in code/documentation", + "", + "Your deprecation notes will be included in the release notes to help users prepare for future changes.", + "", + "---", + "*This comment was automatically generated because the `deprecation` label was applied to this PR.*" + ].join("\n"); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } diff --git a/.github/workflows/pr-label-automation.yml b/.github/workflows/pr-label-automation.yml new file mode 100644 index 000000000000..341c95ba2668 --- /dev/null +++ b/.github/workflows/pr-label-automation.yml @@ -0,0 +1,173 @@ +name: PR Label Automation +on: + issue_comment: + types: [created] + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + auto-label: + # Only run on pull request comments + if: github.event.issue.pull_request != null + runs-on: ubuntu-latest + steps: + - name: Add breaking change label + if: github.event.comment.body == '/breaking-change' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + // Add the breaking change label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['breaking change'] + }); + + // React to the comment to show it was processed + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + // Add a reply comment to confirm the action + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '✅ Added `breaking change` label to this PR.' + }); + + - name: Add deprecation label + if: github.event.comment.body == '/deprecation' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + // Add the deprecation label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['deprecation'] + }); + + // React to the comment to show it was processed + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + // Add a reply comment to confirm the action + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '✅ Added `deprecation` label to this PR.' + }); + + - name: Remove breaking change label + if: github.event.comment.body == '/remove-breaking-change' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + try { + // Remove the breaking change label + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: 'breaking change' + }); + + // React to the comment to show it was processed + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + // Add a reply comment to confirm the action + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '✅ Removed `breaking change` label from this PR.' + }); + } catch (error) { + if (error.status === 404) { + // Label doesn't exist on the PR + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '⚠️ The `breaking change` label was not found on this PR.' + }); + } else { + throw error; + } + } + + - name: Remove deprecation label + if: github.event.comment.body == '/remove-deprecation' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + try { + // Remove the deprecation label + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: 'deprecation' + }); + + // React to the comment to show it was processed + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '+1' + }); + + // Add a reply comment to confirm the action + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '✅ Removed `deprecation` label from this PR.' + }); + } catch (error) { + if (error.status === 404) { + // Label doesn't exist on the PR + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '⚠️ The `deprecation` label was not found on this PR.' + }); + } else { + throw error; + } + } \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b83f119410f..426871ca98bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -153,10 +153,8 @@ jobs: sed -n "0,/^## Version $VERSION /d;/^## Version /q;p" CHANGELOG.md \ > /tmp/CHANGELOG_SECTION.md - # the complex perl regex is needed because markdown docs render newlines as soft wraps - # while release notes render them as line breaks - perl -0pe 's/(?> /tmp/release-notes.txt + # Format changelog section for release notes (includes breaking changes handling) + .github/scripts/format-release-notes.sh /tmp/CHANGELOG_SECTION.md /tmp/release-notes.txt # conditional block not indented because of the heredoc if [[ $VERSION == *.0 ]]; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da2e38058d2d..5f5936d92258 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,39 @@ Before submitting new features or changes to current functionality, it is recomm [open an issue](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/new) and discuss your ideas or propose the changes you wish to make. + +## Breaking Changes + +When your PR introduces a breaking change: + +* Add the `breaking change` label to your PR + - If you can't add labels directly, post a comment containing only `/breaking-change` and the label will be added automatically + - To remove the label, post a comment containing only `/remove-breaking-change` +* Provide migration notes in the PR description: + - What is changing and why + - How users should update their code/configuration + - Code examples showing before/after usage (if applicable) + +**When to Use:** + +* API changes that break backward compatibility +* Configuration changes that require user action +* Behavioral changes that might affect existing users +* Removal of deprecated features + +## Deprecations + +When your PR deprecates functionality: + +* Add the `deprecation` label to your PR + - If you can't add labels directly, post a comment containing only `/deprecation` and the label will be added automatically + - To remove the label, post a comment containing only `/remove-deprecation` +* Provide deprecation details in the PR description: + - What is being deprecated and why + - What should be used instead (if applicable) + - Timeline for removal (if known) + - Any migration guidance + ## Building This project requires Java 21 to build and run tests. Newer JDK's may work, but this version is used in CI. diff --git a/RELEASING.md b/RELEASING.md index ea6d6186705f..0cc432ae481b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -29,6 +29,8 @@ the second Monday of the month (roughly a few days after the monthly minor relea - Merge a pull request to `main` updating the `CHANGELOG.md`. - The heading for the unreleased entries should be `## Unreleased`. - Use `.github/scripts/draft-change-log-entries.sh` as a starting point for writing the change log. + - The script will automatically include a "Breaking Changes" section for PRs labeled with `breaking change`. + - The script will automatically include a "Deprecations" section for PRs labeled with `deprecation`. - Run the [Prepare release branch workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/prepare-release-branch.yml). - Press the "Run workflow" button, and leave the default branch `main` selected. - Review and merge the two pull requests that it creates