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'],

Reply via email to