Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/reference/config-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Source Resolver
.. autopydantic_model:: PyPIGitResolver
:inherited-members: AbstractPyPIResolver, CooldownMixin

.. autopydantic_model:: VersionMapGitResolver

.. versionadded:: 0.79.0

.. autopydantic_model:: GitHubTagDownloadResolver
:inherited-members: AbstractGitSourceResolver, CooldownMixin

Expand Down
2 changes: 2 additions & 0 deletions src/fromager/packagesettings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
PyPIGitResolver,
PyPIPrebuiltResolver,
PyPISDistResolver,
VersionMapGitResolver,
pep440_tag_matcher,
)
from ._settings import Settings, SettingsFile
Expand Down Expand Up @@ -82,6 +83,7 @@
"Variant",
"VariantChangelog",
"VariantInfo",
"VersionMapGitResolver",
"default_update_extra_environ",
"get_extra_environ",
"pep440_tag_matcher",
Expand Down
58 changes: 58 additions & 0 deletions src/fromager/packagesettings/_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,63 @@ def resolver_provider(
)


class VersionMapGitResolver(AbstractResolver):
"""Resolve version from a version map, build sdist from git clone.

The ``versionmap-git`` provider maps known version numbers to known git
refs (commit SHAs or ref paths such as ``refs/tags/1.1``). It clones a
git repo at the configured ref and builds an sdist with PEP 517.

.. versionadded:: 0.79.0

Example::

provider: versionmap-git
clone_url: https://git.test/project/repo.git
build_sdist: pep517
versionmap:
'1.0': abad1dea
'1.1': refs/tags/1.1
"""

provider: typing.Literal["versionmap-git"]

clone_url: pydantic.AnyUrl
"""Git clone URL (``https`` or ``ssh`` scheme)."""

build_sdist: BuildSDist = BuildSDist.pep517
"""Source distribution build method."""

versionmap: dict[str, str]
"""Mapping of version strings to git refs."""

@pydantic.field_validator("clone_url", mode="after")
@classmethod
def validate_clone_url(cls, value: pydantic.AnyUrl) -> pydantic.AnyUrl:
if value.scheme not in {"https", "ssh"}:
raise ValueError(f"invalid scheme in url {value}")
if not value.path:
raise ValueError(f"url {value} has an empty path")
return value
Comment thread
jskladan marked this conversation as resolved.

def resolver_provider(
self, ctx: context.WorkContext, req_type: requirements_file.RequirementType
) -> resolver.VersionMapProvider:
from ..versionmap import VersionMap

clone_url = str(self.clone_url)
url_map = {
ver: f"git+{clone_url}@{ref}" for ver, ref in self.versionmap.items()
}
version_map = VersionMap(url_map) # type: ignore[arg-type]
return resolver.VersionMapProvider(
version_map=version_map,
package_name=None,
constraints=ctx.constraints,
req_type=req_type,
)


class NotAvailableResolver(AbstractResolver):
"""Prevent resolve and download"""

Expand Down Expand Up @@ -510,6 +567,7 @@ def resolver_provider(
| PyPIPrebuiltResolver
| PyPIDownloadResolver
| PyPIGitResolver
| VersionMapGitResolver
| GitHubTagCloneResolver
| GitHubTagDownloadResolver
| GitLabTagCloneResolver
Expand Down
45 changes: 45 additions & 0 deletions tests/test_packagesettings_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
PyPIPrebuiltResolver,
PyPISDistResolver,
SourceResolver,
VersionMapGitResolver,
)
from fromager.packagesettings._typedefs import MODEL_CONFIG
from fromager.requirements_file import RequirementType
Expand Down Expand Up @@ -426,6 +427,50 @@ def test_resolver_provider(self, tmp_context: WorkContext) -> None:
# -- Special resolvers --------------------------------------------------------


class TestVersionMapGitResolver:
YAML = """\
source:
provider: versionmap-git
clone_url: https://git.test/project/repo.git
build_sdist: pep517
versionmap:
'1.0': abad1dea
'1.1': refs/tags/1.1
"""

def test_parse(self) -> None:
r = _parse(self.YAML)
assert isinstance(r, VersionMapGitResolver)
assert r.provider == "versionmap-git"
assert str(r.clone_url) == "https://git.test/project/repo.git"
assert r.build_sdist == BuildSDist.pep517
assert r.versionmap == {"1.0": "abad1dea", "1.1": "refs/tags/1.1"}

def test_resolver_provider(self, tmp_context: WorkContext) -> None:
r = _parse(self.YAML)
p = r.resolver_provider(tmp_context, _REQ_TYPE)
assert isinstance(p, resolver.VersionMapProvider)
clone_url = "https://git.test/project/repo.git"
assert p.version_map["1.0"] == f"git+{clone_url}@abad1dea"
assert p.version_map["1.1"] == f"git+{clone_url}@refs/tags/1.1"

def test_clone_url_rejects_http(self) -> None:
with pytest.raises(pydantic.ValidationError):
VersionMapGitResolver(
provider="versionmap-git",
clone_url="http://git.test/project/repo.git", # type: ignore[arg-type]
versionmap={"1.0": "abc123"},
)

def test_clone_url_rejects_empty_path(self) -> None:
with pytest.raises(pydantic.ValidationError):
VersionMapGitResolver(
provider="versionmap-git",
clone_url="https://git.test", # type: ignore[arg-type]
versionmap={"1.0": "abc123"},
)


class TestNotAvailableResolver:
YAML = """\
source:
Expand Down
58 changes: 58 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,64 @@ def test_resolve_versionmap_no_match() -> None:
rslvr.resolve([Requirement("testpkg>=2.0")])


def test_resolve_versionmap_git() -> None:
from fromager.versionmap import VersionMap

clone_url = "https://git.test/project/repo.git"
version_map = VersionMap(
{
"1.0": f"git+{clone_url}@abad1dea",
"1.1": f"git+{clone_url}@refs/tags/1.1",
"1.2": f"git+{clone_url}@d3adb33f",
}
)

provider = resolver.VersionMapProvider(
version_map=version_map,
package_name="testpkg",
)
reporter: resolvelib.BaseReporter = resolvelib.BaseReporter()
rslvr = resolvelib.Resolver(provider, reporter)

result = rslvr.resolve([Requirement("testpkg")])
assert "testpkg" in result.mapping

candidate = result.mapping["testpkg"]
assert str(candidate.version) == "1.2"
assert candidate.url == f"git+{clone_url}@d3adb33f"


def test_resolve_versionmap_git_with_constraint() -> None:
from fromager.versionmap import VersionMap

clone_url = "https://git.test/project/repo.git"
version_map = VersionMap(
{
"1.0": f"git+{clone_url}@abad1dea",
"1.1": f"git+{clone_url}@refs/tags/1.1",
"1.2": f"git+{clone_url}@d3adb33f",
}
)

c = constraints.Constraints()
c.add_constraint("testpkg<1.2")

provider = resolver.VersionMapProvider(
version_map=version_map,
package_name="testpkg",
constraints=c,
)
reporter: resolvelib.BaseReporter = resolvelib.BaseReporter()
rslvr = resolvelib.Resolver(provider, reporter)

result = rslvr.resolve([Requirement("testpkg")])
assert "testpkg" in result.mapping

candidate = result.mapping["testpkg"]
assert str(candidate.version) == "1.1"
assert candidate.url == f"git+{clone_url}@refs/tags/1.1"


_gitlab_submodlib_repo_response = """
[
{
Expand Down
Loading