From fbc7eacd0c9e1cf023fb2ece6d1904e2e0ebe18f Mon Sep 17 00:00:00 2001 From: Liang Hu Date: Mon, 27 Jul 2026 21:39:10 -0400 Subject: [PATCH 1/2] GH-40163: [Archery] Avoid setuptools_scm internal API --- dev/archery/archery/crossbow/core.py | 48 ++++++---- .../archery/crossbow/tests/test_core.py | 89 ++++++++++++++++++- dev/archery/setup.py | 2 +- 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/dev/archery/archery/crossbow/core.py b/dev/archery/archery/crossbow/core.py index e88a08445cdf..0248cbf4d60a 100644 --- a/dev/archery/archery/crossbow/core.py +++ b/dev/archery/archery/crossbow/core.py @@ -17,6 +17,7 @@ import os import re +import subprocess import fnmatch import glob import time @@ -705,26 +706,36 @@ def put(self, job, prefix='build', increment_job_id=True): 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.+)-(?P\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 9a38ca75d7f5..a69cbca74ab2 100644 --- a/dev/archery/archery/crossbow/tests/test_core.py +++ b/dev/archery/archery/crossbow/tests/test_core.py @@ -17,9 +17,16 @@ 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 subprocess from datetime import date from unittest import mock @@ -27,6 +34,86 @@ from github import GithubException +@pytest.mark.parametrize( + ("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") + + +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") + version_file = tmp_path / "version.txt" + version_file.write_text("released\n") + run_git("add", "version.txt") + run_git( + "-c", + "user.name=Archery Test", + "-c", + "user.email=archery@example.com", + "commit", + "-m", + "release", + ) + run_git("tag", "apache-arrow-4.0.0") + + version_file.write_text("development\n") + run_git("add", "version.txt") + run_git( + "-c", + "user.name=Archery Test", + "-c", + "user.email=archery@example.com", + "commit", + "-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 66d04d692fd9..0b2bbfcb89c6 100755 --- a/dev/archery/setup.py +++ b/dev/archery/setup.py @@ -30,7 +30,7 @@ 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'], From 214039319fd0a489edb4d9a68284ecc34b1a6605 Mon Sep 17 00:00:00 2001 From: Liang Hu Date: Tue, 28 Jul 2026 08:46:14 -0400 Subject: [PATCH 2/2] GH-40163: Address Archery version review --- dev/archery/archery/crossbow/core.py | 12 ++++++------ dev/archery/archery/crossbow/tests/test_core.py | 9 ++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/dev/archery/archery/crossbow/core.py b/dev/archery/archery/crossbow/core.py index 0248cbf4d60a..33bd5f6f88b2 100644 --- a/dev/archery/archery/crossbow/core.py +++ b/dev/archery/archery/crossbow/core.py @@ -15,20 +15,20 @@ # specific language governing permissions and limitations # under the License. -import os -import re -import subprocess 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 diff --git a/dev/archery/archery/crossbow/tests/test_core.py b/dev/archery/archery/crossbow/tests/test_core.py index a69cbca74ab2..fb4d5fdc59e7 100644 --- a/dev/archery/archery/crossbow/tests/test_core.py +++ b/dev/archery/archery/crossbow/tests/test_core.py @@ -26,6 +26,7 @@ ) import pathlib +import shutil import subprocess from datetime import date from unittest import mock @@ -74,6 +75,7 @@ def test_get_version_rejects_unexpected_describe_output(): get_version("/arrow") +@pytest.mark.skipif(shutil.which("git") is None, reason="git is required") def test_get_version_from_git_repository(tmp_path): def run_git(*args): subprocess.run( @@ -85,28 +87,25 @@ def run_git(*args): ) run_git("init") - version_file = tmp_path / "version.txt" - version_file.write_text("released\n") - run_git("add", "version.txt") run_git( "-c", "user.name=Archery Test", "-c", "user.email=archery@example.com", "commit", + "--allow-empty", "-m", "release", ) run_git("tag", "apache-arrow-4.0.0") - version_file.write_text("development\n") - run_git("add", "version.txt") run_git( "-c", "user.name=Archery Test", "-c", "user.email=archery@example.com", "commit", + "--allow-empty", "-m", "development", )