diff --git a/requirements.txt b/requirements.txt index c6ad300..5ae47d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cargo2rpm>=0.1.8 +cargo2rpm>=0.1.11 jinja2 jsonschema pyparsing diff --git a/rust2rpm/crate.py b/rust2rpm/crate.py index cba8d55..8d8fa3a 100644 --- a/rust2rpm/crate.py +++ b/rust2rpm/crate.py @@ -6,9 +6,9 @@ import tarfile import tempfile from typing import Optional -from cargo2rpm.metadata import Metadata +from cargo2rpm.metadata import Metadata, Version, VersionReq -from rust2rpm.cratesio import download_crate +from rust2rpm.cratesio import download_crate, query_available_versions from rust2rpm import log from rust2rpm.patching import make_patches @@ -196,6 +196,36 @@ def process_project_local( return name, version, diffs, metadata, doc_files, license_files +def resolve_version(crate: str, version: str) -> Optional[str]: + # try parsing version as actual version + try: + resolved_version = Version.parse(version) + return str(resolved_version) + except ValueError: + pass + + # try parsing version as partial version + try: + parsed_version = VersionReq.parse(version) + log.info("Resolving partial version ...") + + available_versions = query_available_versions(crate) + resolved_version = max(filter(lambda x: x in parsed_version, available_versions), default=None) + + if resolved_version is None: + log.warn("Partial version does not match any available version.") + log.info("Falling back to latest version.") + return None + + log.info(f"Partial version matched with available version: {resolved_version}") + return str(resolved_version) + + except ValueError: + log.error(f"Invalid version: {version}") + log.info("Falling back to latest version.") + return None + + def process_project( project: str, version: Optional[str], @@ -220,9 +250,15 @@ def process_project( else: # project is just a crate name name = project - # download .crate from crates.io; - # set version to the latest stable version if it was not specified - crate_file_path, version = download_crate(project, version) + + # download .crate from crates.io + if version: + # version or partial version was specified + resolved_version = resolve_version(project, version) + crate_file_path, version = download_crate(project, resolved_version) + else: + # no version was specified: download latest + crate_file_path, version = download_crate(project, version) if store_crate: copy_target = os.path.join(os.getcwd(), os.path.basename(crate_file_path)) diff --git a/rust2rpm/cratesio.py b/rust2rpm/cratesio.py index 82d79ad..9155153 100644 --- a/rust2rpm/cratesio.py +++ b/rust2rpm/cratesio.py @@ -6,6 +6,8 @@ from urllib.parse import urljoin import requests import tqdm +from cargo2rpm.semver import Version + from rust2rpm import log from rust2rpm.utils import remove_on_error @@ -18,6 +20,20 @@ class NoVersionsError(Exception): pass +def query_available_versions(crate: str, stable: bool = True) -> list[Version]: + url = urljoin(CRATES_IO_API_URL, f"crates/{crate}/versions") + req = requests.get(url, headers={"User-Agent": "rust2rpm"}) + req.raise_for_status() + versions = req.json()["versions"] + + parsed_versions = map(lambda x: Version.parse(x["num"]), filter(lambda x: not x["yanked"], versions)) + + if stable: + return list(filter(lambda x: x.pre is None, parsed_versions)) + else: + return list(parsed_versions) + + def query_newest_version(crate: str) -> str: url = urljoin(CRATES_IO_API_URL, f"crates/{crate}/versions") req = requests.get(url, headers={"User-Agent": "rust2rpm"}) diff --git a/setup.cfg b/setup.cfg index c99f7cf..34863eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ classifiers = include_package_data = True packages = rust2rpm install_requires = - cargo2rpm>=0.1.8 + cargo2rpm>=0.1.11 jinja2 jsonschema pyparsing