Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions dev/archery/archery/crossbow/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<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".
Expand All @@ -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:
Expand Down
88 changes: 87 additions & 1 deletion dev/archery/archery/crossbow/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,102 @@

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

import pytest
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")


@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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will fail if a git command is not available (which is unlikely on dev setups, for sure). Can we skip in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a pytest.mark.skipif guard using shutil.which("git").

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",
"user.email=archery@example.com",
"commit",
"--allow-empty",
"-m",
"release",
)
run_git("tag", "apache-arrow-4.0.0")

run_git(
"-c",
"user.name=Archery Test",
"-c",
"user.email=archery@example.com",
"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")
Expand Down
2 changes: 1 addition & 1 deletion dev/archery/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down