Skip to content

Commit faee3a7

Browse files
authored
build(release): check VERSION_NEXT_ markers during create-rc (#4188)
Checking `VERSION_NEXT_` markers during post-tag archive and release notes generation fails too late in the release process, after the RC tag is already created and pushed. Have `release.py create-rc` check the release branch for leftover `VERSION_NEXT_` markers before tagging or updating the tracking issue, and remove `check_version_markers.sh` from release archive generation.
1 parent 3ed114a commit faee3a7

6 files changed

Lines changed: 118 additions & 41 deletions

File tree

‎.github/workflows/check_version_markers.sh‎

Lines changed: 0 additions & 33 deletions
This file was deleted.

‎.github/workflows/create_archive_and_notes.sh‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ if [ -z "$TAG" ]; then
2626
fi
2727
# If the workflow checks out one commit, but is releasing another
2828
git fetch origin tag "$TAG"
29-
30-
# Update our local state so that check_version_markers searches what we expect
3129
git checkout "$TAG"
32-
$(dirname $0)/check_version_markers.sh
3330

3431
# A prefix is added to better match the GitHub generated archives.
3532
PREFIX="rules_python-${TAG}"

‎dev/release/create_rc.py‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717
from dev.release.utils import (
1818
REPO_URL,
19+
find_version_markers,
1920
get_latest_rc_tag,
2021
set_github_output,
2122
)
@@ -122,6 +123,21 @@ def _run_internal(self) -> int:
122123
next_rc_num = rc_num + 1
123124
next_rc = f"{version}-rc{next_rc_num}"
124125

126+
target_ref = f"{args.remote}/{branch_name}"
127+
commit_sha = self.git.get_commit_sha(target_ref)
128+
self.git.checkout(target_ref)
129+
130+
print("Checking for leftover VERSION_NEXT_ markers...")
131+
markers = find_version_markers()
132+
if markers:
133+
for marker in markers:
134+
print(marker)
135+
print(
136+
"Error: Found VERSION_NEXT markers indicating version needs to"
137+
" be specified."
138+
)
139+
return 1
140+
125141
# Precheck: next RC number must exist and be unchecked in the checklist
126142
rc_tags = state.get("rc_tags", {})
127143
if next_rc_num not in rc_tags:
@@ -137,9 +153,6 @@ def _run_internal(self) -> int:
137153
)
138154
return 1
139155

140-
target_ref = f"{args.remote}/{branch_name}"
141-
commit_sha = self.git.get_commit_sha(target_ref)
142-
143156
print(f"Tagging and pushing next RC: {next_rc}...")
144157
self.git.tag(next_rc, target_ref)
145158
self.git.push(args.remote, next_rc)

‎dev/release/utils.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,27 @@ def replace_version_next(version: str) -> list[pathlib.Path]:
211211
return replace_version_next_in_files(_iter_version_placeholder_files(), version)
212212

213213

214+
def find_version_markers() -> list[str]:
215+
"""Finds any remaining VERSION_NEXT_ markers in non-excluded files.
216+
217+
Returns:
218+
A list of strings in the format 'filepath:line_number: line_content'
219+
for each line containing 'VERSION_NEXT_'.
220+
"""
221+
matches: list[str] = []
222+
for filepath in _iter_version_placeholder_files():
223+
try:
224+
content = filepath.read_text(encoding="utf-8")
225+
except (IOError, UnicodeDecodeError):
226+
continue
227+
228+
if "VERSION_NEXT_" in content:
229+
for line_num, line in enumerate(content.splitlines(), start=1):
230+
if "VERSION_NEXT_" in line:
231+
matches.append(f"{filepath}:{line_num}: {line}")
232+
return matches
233+
234+
214235
def parse_pr_list(value: str) -> list[str]:
215236
"""Parses a comma or space separated list of PR references.
216237

‎tests/tools/private/release/create_rc_test.py‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44
import tempfile
55
from unittest.mock import call
66

7+
import pytest
8+
79
from dev.release.create_rc import CreateRc
810

911
pytest_plugins = ["tests.tools.private.release.release_test_helper"]
1012

1113

14+
@pytest.fixture(name="isolate_cwd", autouse=True)
15+
def fixture_isolate_cwd(tmp_path, monkeypatch):
16+
monkeypatch.chdir(tmp_path)
17+
18+
1219
def test_create_rc_success_first_rc(mocker, mock_git, mock_gh):
1320
# Arrange
1421
args = argparse.Namespace(
@@ -41,7 +48,7 @@ def test_create_rc_success_first_rc(mocker, mock_git, mock_gh):
4148
mock_git.fetch.assert_has_calls(
4249
[call("my-remote"), call("my-remote", tags=True, force=True)]
4350
)
44-
mock_git.checkout.assert_not_called()
51+
mock_git.checkout.assert_called_once_with("my-remote/release/2.0")
4552
mock_git.tag.assert_called_once_with("2.0.0-rc0", "my-remote/release/2.0")
4653
mock_git.push.assert_called_once_with("my-remote", "2.0.0-rc0")
4754
mock_git.get_commit_sha.assert_called_once_with("my-remote/release/2.0")
@@ -138,7 +145,7 @@ def test_create_rc_success_next_rc(mock_git, mock_gh):
138145
mock_git.fetch.assert_has_calls(
139146
[call("my-remote"), call("my-remote", tags=True, force=True)]
140147
)
141-
mock_git.checkout.assert_not_called()
148+
mock_git.checkout.assert_called_once_with("my-remote/release/2.0")
142149
mock_git.tag.assert_called_once_with("2.0.0-rc1", "my-remote/release/2.0")
143150
mock_git.push.assert_called_once_with("my-remote", "2.0.0-rc1")
144151
mock_git.get_commit_sha.assert_called_once_with("my-remote/release/2.0")
@@ -418,3 +425,36 @@ def test_create_rc_precondition_failure_reacts_to_comment(mocker, mock_git, mock
418425
# Assert
419426
assert result == 1
420427
assert mock_gh.reactions.get(456) == ["-1"]
428+
429+
430+
def test_create_rc_fails_on_version_markers(tmp_path, mock_git, mock_gh):
431+
# Arrange
432+
args = argparse.Namespace(
433+
issue=123, remote="my-remote", triggering_comment=456, dry_run=False
434+
)
435+
initial_body = """
436+
## Checklist
437+
- [x] Prepare Release | status=done pr=#122 commit=abcdef12
438+
- [x] Create Release branch | status=done branch=release/2.0 commit=abcdef12
439+
- [ ] Tag RC0 | status=pending
440+
"""
441+
mock_gh.issues[123] = {
442+
"title": "Release 2.0.0",
443+
"body": initial_body,
444+
"labels": ["type: release"],
445+
}
446+
mock_git.get_remote_tags.return_value = []
447+
mock_git.get_commit_sha.return_value = "1234567890"
448+
(tmp_path / "dirty.bzl").write_text(":::{versionadded} VERSION_NEXT_FEATURE\n")
449+
450+
# Act
451+
result = CreateRc(args, mock_git, mock_gh).run()
452+
453+
# Assert
454+
assert result == 1
455+
mock_git.checkout.assert_called_once_with("my-remote/release/2.0")
456+
mock_git.tag.assert_not_called()
457+
mock_git.push.assert_not_called()
458+
assert mock_gh.get_issue_body(123) == initial_body
459+
assert 123 not in mock_gh.issue_comments
460+
assert mock_gh.reactions.get(456) == ["-1"]

‎tests/tools/private/release/utils_test.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,42 @@ def test_format_exception_empty_message_with_notes():
345345
e = Exception()
346346
e.add_note("Note only")
347347
assert utils.format_exception(e) == "Note only"
348+
349+
350+
def test_find_version_markers_none(release_tool_env):
351+
(release_tool_env.git_root / "clean.bzl").write_text(":::{versionadded} 1.2.0\n")
352+
assert utils.find_version_markers() == []
353+
354+
355+
def test_find_version_markers_found(release_tool_env):
356+
(release_tool_env.git_root / "dirty.bzl").write_text(
357+
"line 1\n"
358+
":::{versionadded} VERSION_NEXT_FEATURE\n"
359+
":::{versionchanged} VERSION_NEXT_PATCH\n"
360+
)
361+
markers = utils.find_version_markers()
362+
assert len(markers) == 2
363+
assert any(
364+
"dirty.bzl:2: :::{versionadded} VERSION_NEXT_FEATURE" in m for m in markers
365+
)
366+
assert any(
367+
"dirty.bzl:3: :::{versionchanged} VERSION_NEXT_PATCH" in m for m in markers
368+
)
369+
370+
371+
def test_find_version_markers_excludes_paths(release_tool_env):
372+
content = ":::{versionadded} VERSION_NEXT_FEATURE\n"
373+
for rel_path in [
374+
".agents/rule.md",
375+
"bazel-out/file.bzl",
376+
"dev/release/tool.py",
377+
"tests/tools/private/release/test.py",
378+
"docs/devguide.md",
379+
"CONTRIBUTING.md",
380+
"RELEASING.md",
381+
]:
382+
p = release_tool_env.git_root / rel_path
383+
p.parent.mkdir(parents=True, exist_ok=True)
384+
p.write_text(content)
385+
386+
assert utils.find_version_markers() == []

0 commit comments

Comments
 (0)