This is an automated email from the ASF dual-hosted git repository.
pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new b1af964af03 GH-40163: [Archery] Avoid setuptools_scm internal API
(#50669)
b1af964af03 is described below
commit b1af964af03e218f49ae1991526cc53de056cf60
Author: Liang Hu <[email protected]>
AuthorDate: Tue Jul 28 11:18:43 2026 -0400
GH-40163: [Archery] Avoid setuptools_scm internal API (#50669)
### Rationale for this change
Crossbow currently computes development versions through internal
`setuptools_scm` APIs. Those APIs are unstable and require Archery to pin an
otherwise unnecessary runtime dependency.
### What changes are included in this PR?
- Compute the development version from public `git describe` output.
- Preserve the existing behavior for released, development,
release-candidate, and dirty tags.
- Remove `setuptools_scm` from the Crossbow extra.
- Add unit coverage plus a real temporary Git repository test.
### Are these changes tested?
- `python -m pytest -q dev/archery/archery/crossbow/tests/test_core.py` (31
passed)
- `flake8 --max-line-length=88` on the changed Python files
- `autopep8 --diff --max-line-length=88` on the changed Python files
- Verified Archery imports when `setuptools_scm` is not installed
### Are there any user-facing changes?
No. This removes an internal dependency while preserving Crossbow version
behavior.
Closes #40163
* GitHub Issue: #40163
Authored-by: Liang Hu <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
dev/archery/archery/crossbow/core.py | 58 +++++++++-------
dev/archery/archery/crossbow/tests/test_core.py | 88 ++++++++++++++++++++++++-
dev/archery/setup.py | 2 +-
3 files changed, 124 insertions(+), 24 deletions(-)
diff --git a/dev/archery/archery/crossbow/core.py
b/dev/archery/archery/crossbow/core.py
index e88a08445cd..33bd5f6f88b 100644
--- a/dev/archery/archery/crossbow/core.py
+++ b/dev/archery/archery/crossbow/core.py
@@ -15,19 +15,20 @@
# specific language governing permissions and limitations
# under the License.
-import os
-import re
import fnmatch
import glob
-import time
import logging
import mimetypes
+import os
+import re
+import subprocess
import textwrap
+import time
import uuid
+import warnings
+from datetime import date
from io import StringIO
from pathlib import Path
-from datetime import date
-import warnings
import jinja2
from ruamel.yaml import YAML
@@ -705,26 +706,36 @@ class Queue(Repo):
return self.create_branch(job.branch, files=job.render_files())
-def get_version(root, **kwargs):
+def get_version(root):
"""
- Parse function for setuptools_scm that ignores tags for non-C++
- subprojects, e.g. apache-arrow-js-XXX tags.
+ Calculate a development version from the latest Arrow C++ release tag.
"""
- from setuptools_scm.git import parse as parse_git_version
- from setuptools_scm import Configuration
-
- # query the calculated version based on the git tags
- kwargs['describe_command'] = (
- 'git describe --dirty --tags --long --match "apache-arrow-[0-9]*.*"'
+ result = subprocess.run(
+ [
+ "git",
+ "describe",
+ "--dirty",
+ "--tags",
+ "--long",
+ "--match",
+ "apache-arrow-[0-9]*.*",
+ ],
+ cwd=root,
+ check=True,
+ stdout=subprocess.PIPE,
+ text=True,
)
-
- # Create a Configuration object with necessary parameters
- config = Configuration(
- git_describe_command=kwargs['describe_command']
+ description = result.stdout.strip()
+ describe_match = re.fullmatch(
+ r"apache-arrow-(?P<tag>.+)-(?P<distance>\d+)"
+ r"-g[0-9a-f]+(?:-dirty)?",
+ description,
)
-
- version = parse_git_version(root, config=config, **kwargs)
- tag = str(version.tag)
+ if describe_match is None:
+ raise CrossbowError(
+ f"Unable to parse git describe output: {description!r}"
+ )
+ tag = describe_match.group("tag")
# We may get a development tag for the next version, such as "5.0.0.dev0",
# or the tag of an already released version, such as "4.0.0".
@@ -733,11 +744,14 @@ def get_version(root, **kwargs):
# 4.0.0 is 5.0.0).
pattern = r"^(\d+)\.(\d+)\.(\d+)"
match = re.match(pattern, tag)
+ if match is None:
+ raise CrossbowError(f"Unable to parse Arrow version tag: {tag!r}")
major, minor, patch = map(int, match.groups())
if 'dev' not in tag:
major += 1
- return f"{major}.{minor}.{patch}.dev{version.distance or 0}"
+ distance = int(describe_match.group("distance"))
+ return f"{major}.{minor}.{patch}.dev{distance}"
class Serializable:
diff --git a/dev/archery/archery/crossbow/tests/test_core.py
b/dev/archery/archery/crossbow/tests/test_core.py
index 9a38ca75d7f..fb4d5fdc59e 100644
--- a/dev/archery/archery/crossbow/tests/test_core.py
+++ b/dev/archery/archery/crossbow/tests/test_core.py
@@ -17,9 +17,17 @@
from archery.utils.source import ArrowSources
from archery.crossbow import Config, Queue
-from archery.crossbow.core import CrossbowError, Repo, TaskAssets, TaskStatus
+from archery.crossbow.core import (
+ CrossbowError,
+ Repo,
+ TaskAssets,
+ TaskStatus,
+ get_version,
+)
import pathlib
+import shutil
+import subprocess
from datetime import date
from unittest import mock
@@ -27,6 +35,84 @@ import pytest
from github import GithubException
[email protected](
+ ("description", "expected"),
+ [
+ ("apache-arrow-4.0.0-0-gabcdef\n", "5.0.0.dev0"),
+ ("apache-arrow-4.0.0-12-gabcdef-dirty\n", "5.0.0.dev12"),
+ ("apache-arrow-5.0.0.dev-7-gabcdef\n", "5.0.0.dev7"),
+ ("apache-arrow-5.0.0-rc1-2-gabcdef\n", "6.0.0.dev2"),
+ ],
+)
+def test_get_version(description, expected):
+ with mock.patch("archery.crossbow.core.subprocess.run") as mocked_run:
+ mocked_run.return_value.stdout = description
+
+ assert get_version("/arrow") == expected
+
+ mocked_run.assert_called_once_with(
+ [
+ "git",
+ "describe",
+ "--dirty",
+ "--tags",
+ "--long",
+ "--match",
+ "apache-arrow-[0-9]*.*",
+ ],
+ cwd="/arrow",
+ check=True,
+ stdout=mock.ANY,
+ text=True,
+ )
+
+
+def test_get_version_rejects_unexpected_describe_output():
+ with mock.patch("archery.crossbow.core.subprocess.run") as mocked_run:
+ mocked_run.return_value.stdout = "not-an-arrow-version\n"
+
+ with pytest.raises(CrossbowError, match="git describe output"):
+ get_version("/arrow")
+
+
[email protected](shutil.which("git") is None, reason="git is required")
+def test_get_version_from_git_repository(tmp_path):
+ def run_git(*args):
+ subprocess.run(
+ ["git", *args],
+ cwd=tmp_path,
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+
+ run_git("init")
+ run_git(
+ "-c",
+ "user.name=Archery Test",
+ "-c",
+ "[email protected]",
+ "commit",
+ "--allow-empty",
+ "-m",
+ "release",
+ )
+ run_git("tag", "apache-arrow-4.0.0")
+
+ run_git(
+ "-c",
+ "user.name=Archery Test",
+ "-c",
+ "[email protected]",
+ "commit",
+ "--allow-empty",
+ "-m",
+ "development",
+ )
+
+ assert get_version(tmp_path) == "5.0.0.dev1"
+
+
def test_config():
src = ArrowSources.find()
conf = Config.load_yaml(src.dev / "tasks" / "tasks.yml")
diff --git a/dev/archery/setup.py b/dev/archery/setup.py
index 66d04d692fd..0b2bbfcb89c 100755
--- a/dev/archery/setup.py
+++ b/dev/archery/setup.py
@@ -30,7 +30,7 @@ jinja_req = 'jinja2>=2.11'
extras = {
'benchmark': ['pandas'],
'crossbow': [jinja_req, 'pygit2>=1.14.0', 'pygithub>=2.5.0', 'requests',
- 'ruamel.yaml', 'setuptools_scm>=8.0.0'],
+ 'ruamel.yaml'],
'docker': ['ruamel.yaml', 'python-dotenv'],
'integration': ['cffi', 'numpy'],
'integration-java': ['jpype1'],