Skip to content

Commit dc50501

Browse files
Kangyan-ZhouclaudeFridge003
committed
[Fix] Fix setuptools-scm version resolution for rc tags (#22165)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Baizhou Zhang <sobereddiezhang@gmail.com>
1 parent 1519acf commit dc50501

9 files changed

Lines changed: 179 additions & 16 deletions

File tree

.github/workflows/release-pypi-nightly.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,7 @@ jobs:
5454
cd python
5555
cp ../README.md ../LICENSE .
5656
57-
# Parse git describe output to get latest tag
58-
# Use same command as pyproject.toml to ensure version consistency
59-
DESC=$(git tag --list --sort=-version:refname 'v*.*.*' | head -1 | xargs git describe --tags --long 2>/dev/null || echo 'v0.0.0-0-g0000000')
60-
TAG=$(echo "$DESC" | cut -d- -f1)
57+
TAG=$(python3 ../python/tools/get_version_tag.py)
6158
HASH="g$(git rev-parse --short HEAD)"
6259
BUILD_DATE=$(date -u +%Y%m%d)
6360

.github/workflows/release-pypi-pr.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,7 @@ jobs:
3434
- name: Generate PR wheel version
3535
id: gen_version
3636
run: |
37-
# Get base version from the latest v*.*.* git tag directly
38-
# Note: We cannot use setuptools_scm here because the [tool.setuptools_scm]
39-
# config (with custom git_describe_command) lives in python/pyproject.toml,
40-
# not at the repo root. Without that config, setuptools_scm falls back to
41-
# default git describe which finds gateway-* tags instead of v*.*.* release tags.
42-
LATEST_TAG=$(git tag --list --sort=-version:refname 'v*.*.*' | head -1)
37+
LATEST_TAG=$(python3 python/tools/get_version_tag.py)
4338
BASE_VERSION=${LATEST_TAG#v}
4439
echo "Latest release tag: ${LATEST_TAG}"
4540

3rdparty/amd/wheel/sglang/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,4 @@ exclude = [
210210
[tool.setuptools_scm]
211211
root = ".."
212212
version_file = "sglang/_version.py"
213-
git_describe_command = ["git", "describe", "--tags", "--long", "--match", "v*"]
213+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,6 @@ exclude = [
198198
[tool.setuptools_scm]
199199
root = ".."
200200
version_file = "sglang/_version.py"
201-
git_describe_command = ["bash", "-c", "git tag --list --sort=-version:refname 'v*.*.*' | head -1 | xargs git describe --tags --long"]
201+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]
202202
# Allow editable installs even when .git metadata is not available.
203203
fallback_version = "0.0.0.dev0"

python/pyproject_cpu.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ exclude = [
127127
[tool.setuptools_scm]
128128
root = ".."
129129
version_file = "sglang/_version.py"
130-
git_describe_command = ["git", "describe", "--tags", "--long", "--match", "v*"]
130+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]

python/pyproject_npu.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,4 @@ exclude = [
146146
[tool.setuptools_scm]
147147
root = ".."
148148
version_file = "sglang/_version.py"
149-
git_describe_command = ["git", "describe", "--tags", "--long", "--match", "v*"]
149+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]

python/pyproject_other.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,4 @@ exclude = [
209209
[tool.setuptools_scm]
210210
root = ".."
211211
version_file = "sglang/_version.py"
212-
git_describe_command = ["git", "describe", "--tags", "--long", "--match", "v*"]
212+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]

python/pyproject_xpu.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,6 @@ exclude = [
134134
[tool.setuptools_scm]
135135
root = ".."
136136
version_file = "sglang/_version.py"
137-
git_describe_command = ["bash", "-c", "git tag --list --sort=-version:refname 'v*.*.*' | head -1 | xargs git describe --tags --long"]
137+
git_describe_command = ["python3", "python/tools/get_version_tag.py", "--tag-only"]
138138
# Allow editable installs even when .git metadata is not available.
139139
fallback_version = "0.0.0.dev0"

python/tools/get_version_tag.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
"""Resolve the correct version tag for setuptools-scm.
3+
4+
Called by setuptools-scm via git_describe_command in pyproject.toml.
5+
Outputs either a bare tag (e.g., "v0.5.10") for exact-match commits,
6+
or a `git describe --long` string (e.g., "v0.5.10-2-gabcdef0") for
7+
untagged commits. Both formats are accepted by setuptools-scm.
8+
9+
This two-step approach avoids a strverscmp bug where
10+
`git tag --sort=-version:refname` sorts v0.5.10rc0 above v0.5.10,
11+
which would cause CI to build the wrong version.
12+
13+
Strategy:
14+
1. If the current commit has an exact version tag, use it directly.
15+
This handles CI release builds (both stable and rc).
16+
2. Otherwise, find the highest version tag across all branches
17+
and describe relative to it. This handles local dev installs
18+
from main where release tags only exist on release branches.
19+
"""
20+
21+
import re
22+
import subprocess
23+
import sys
24+
25+
26+
def parse_version_tuple(tag: str) -> tuple:
27+
"""Parse a version tag into a sortable tuple using PEP 440 ordering.
28+
29+
Returns a tuple where:
30+
- Base version parts are integers: (major, minor, patch)
31+
- Pre-release suffix gets a lower sort key than bare version:
32+
v0.5.10rc0 -> (0, 5, 10, 0, 0) # pre-release
33+
v0.5.10 -> (0, 5, 10, 1, 0) # stable (sorts higher)
34+
v0.5.10post1 -> (0, 5, 10, 2, 1) # post-release (sorts highest)
35+
"""
36+
v = tag.lstrip("v")
37+
# Split base version from suffix
38+
m = re.match(r"^(\d+)\.(\d+)\.(\d+)(?:(rc|post)(\d+))?$", v)
39+
if not m:
40+
return (0, 0, 0, 0, 0)
41+
major, minor, patch = int(m.group(1)), int(m.group(2)), int(m.group(3))
42+
suffix_type = m.group(4)
43+
suffix_num = int(m.group(5)) if m.group(5) else 0
44+
if suffix_type == "rc":
45+
return (major, minor, patch, 0, suffix_num)
46+
elif suffix_type == "post":
47+
return (major, minor, patch, 2, suffix_num)
48+
else:
49+
return (major, minor, patch, 1, 0)
50+
51+
52+
def run_git(*args: str, allow_failure: bool = False) -> str:
53+
"""Run a git command and return stripped stdout.
54+
55+
Args:
56+
allow_failure: If True, return "" on non-zero exit (expected for
57+
commands like --exact-match that legitimately fail).
58+
If False, log stderr on failure before returning "".
59+
"""
60+
try:
61+
result = subprocess.run(
62+
["git", *args],
63+
capture_output=True,
64+
text=True,
65+
)
66+
except OSError as exc:
67+
print(f"ERROR: Failed to run 'git {' '.join(args)}': {exc}", file=sys.stderr)
68+
sys.exit(1)
69+
70+
if result.returncode != 0:
71+
if not allow_failure:
72+
stderr_msg = result.stderr.strip()
73+
print(
74+
f"WARNING: git {' '.join(args)} failed "
75+
f"(exit {result.returncode}): {stderr_msg}",
76+
file=sys.stderr,
77+
)
78+
return ""
79+
80+
return result.stdout.strip()
81+
82+
83+
def get_exact_version_tag() -> str:
84+
"""Return the version tag name if HEAD has an exact version tag, or empty string."""
85+
return run_git(
86+
"describe", "--tags", "--exact-match", "--match", "v*", allow_failure=True
87+
)
88+
89+
90+
def get_latest_version_tag_describe() -> str:
91+
"""Find the highest version tag and build a describe string relative to it.
92+
93+
Uses PEP 440 version ordering so that stable releases sort above
94+
pre-release tags (e.g., v0.5.10 > v0.5.10rc0).
95+
96+
The highest tag may live on a release branch and not be a direct
97+
ancestor of HEAD (e.g., main diverged before the release tag was
98+
created). In that case, we compute the commit distance from the
99+
merge-base and build the describe string manually.
100+
"""
101+
tag = get_latest_version_tag()
102+
if not tag:
103+
print("WARNING: No version tags (v*.*.*) found in repo", file=sys.stderr)
104+
return ""
105+
106+
# Fast path: tag is an ancestor of HEAD, git describe works directly
107+
result = run_git(
108+
"describe", "--tags", "--long", "--match", tag, "HEAD", allow_failure=True
109+
)
110+
if result:
111+
return result
112+
113+
# Tag is not an ancestor (e.g., release branch diverged from main).
114+
# Build describe string manually: {tag}-{distance}-g{hash}
115+
merge_base = run_git("merge-base", tag, "HEAD", allow_failure=True)
116+
if not merge_base:
117+
print(
118+
f"WARNING: No common ancestor between {tag} and HEAD. "
119+
f"Is this a shallow clone? Try: git fetch --unshallow --tags",
120+
file=sys.stderr,
121+
)
122+
return ""
123+
distance = run_git("rev-list", "--count", f"{merge_base}..HEAD")
124+
short_hash = run_git("rev-parse", "--short", "HEAD")
125+
return f"{tag}-{distance}-g{short_hash}"
126+
127+
128+
def get_version_describe() -> str:
129+
"""Main entry point: resolve the version describe string."""
130+
# Prefer exact match — correct for both stable and pre-release tags
131+
exact = get_exact_version_tag()
132+
if exact:
133+
return exact
134+
135+
# Fallback for untagged commits (e.g., dev install from main)
136+
return get_latest_version_tag_describe()
137+
138+
139+
def get_latest_version_tag() -> str:
140+
"""Return just the highest version tag (PEP 440 ordered), or empty string."""
141+
tags_raw = run_git("tag", "--list", "v*.*.*")
142+
if not tags_raw:
143+
return ""
144+
tag_list = sorted(tags_raw.splitlines(), key=parse_version_tuple, reverse=True)
145+
return tag_list[0] if tag_list else ""
146+
147+
148+
def main() -> None:
149+
# --tag-only: print just the latest version tag (for CI scripts)
150+
tag_only = "--tag-only" in sys.argv
151+
if tag_only:
152+
result = get_latest_version_tag()
153+
else:
154+
result = get_version_describe()
155+
if not result:
156+
print(
157+
"ERROR: Could not determine version from git tags.\n"
158+
"Possible causes:\n"
159+
" - No version tags (v*.*.*) exist: run 'git fetch --tags'\n"
160+
" - Shallow clone without tags: run 'git fetch --unshallow --tags'\n"
161+
" - Git safe.directory issue: run 'git config --global --add safe.directory <repo>'\n"
162+
" - Not inside a git repository\n"
163+
"setuptools-scm will fall back to version 0.0.0.dev0",
164+
file=sys.stderr,
165+
)
166+
sys.exit(1)
167+
print(result)
168+
169+
170+
if __name__ == "__main__":
171+
main()

0 commit comments

Comments
 (0)