crate: improve determining project name/version from directory name
If the heuristics fail because the project uses a weird naming or versioning scheme or if the directory name does not match the "{project}-{version}" pattern. This is mostly the case for "workspace" proejcts. In the case the heuristics fail, the version can be overriden on the command line.
This commit is contained in:
parent
323dbea4b6
commit
228efa58fd
4 changed files with 91 additions and 8 deletions
|
@ -8,7 +8,7 @@ from cargo2rpm.semver import Version
|
||||||
from rust2rpm import log
|
from rust2rpm import log
|
||||||
from rust2rpm.cli import get_parser
|
from rust2rpm.cli import get_parser
|
||||||
from rust2rpm.conf import load_config
|
from rust2rpm.conf import load_config
|
||||||
from rust2rpm.crate import InvalidProjectError, process_project
|
from rust2rpm.crate import InvalidProjectError, InvalidVersionError, process_project
|
||||||
from rust2rpm.cratesio import NoVersionsError
|
from rust2rpm.cratesio import NoVersionsError
|
||||||
from rust2rpm.distgit import get_package_info
|
from rust2rpm.distgit import get_package_info
|
||||||
from rust2rpm.generator import spec_render_crate, spec_render_project, spec_render_workspace
|
from rust2rpm.generator import spec_render_crate, spec_render_project, spec_render_workspace
|
||||||
|
@ -56,6 +56,12 @@ def main():
|
||||||
except InvalidProjectError:
|
except InvalidProjectError:
|
||||||
log.error(f"Invalid argument: {args.crate!r} is not a local file or directory")
|
log.error(f"Invalid argument: {args.crate!r} is not a local file or directory")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
except InvalidVersionError:
|
||||||
|
log.error(
|
||||||
|
f"Could not determine project name and version automatically. "
|
||||||
|
"Please specify the project version on the command line."
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if metadata.is_workspace():
|
if metadata.is_workspace():
|
||||||
base_name = project
|
base_name = project
|
||||||
|
|
|
@ -58,6 +58,10 @@ class InvalidProjectError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidVersionError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def local_toml_file(toml_path: str) -> tuple[str, list[str], list[str]]:
|
def local_toml_file(toml_path: str) -> tuple[str, list[str], list[str]]:
|
||||||
assert os.path.isfile(toml_path)
|
assert os.path.isfile(toml_path)
|
||||||
assert os.path.basename(toml_path) == "Cargo.toml"
|
assert os.path.basename(toml_path) == "Cargo.toml"
|
||||||
|
@ -149,14 +153,52 @@ def project_is_path(path: str) -> bool:
|
||||||
return "/" in path or path in {".", ".."}
|
return "/" in path or path in {".", ".."}
|
||||||
|
|
||||||
|
|
||||||
def guess_local_project_version_from_path(project: str) -> tuple[str, str]:
|
def guess_local_project_version_from_dir(dir_name: str) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Use a simple heuristic to determine the project name and version from the
|
||||||
|
name of the directory that contains the Cargo.toml file.
|
||||||
|
|
||||||
|
Raises an InvalidVersionError if the automatically determined version is
|
||||||
|
not valid according to SemVer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
project = dir_name.rstrip("0123456789.").removesuffix("-")
|
||||||
|
version = dir_name.removeprefix(f"{project}-")
|
||||||
|
|
||||||
|
try:
|
||||||
|
Version.parse(version)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise InvalidVersionError(exc.args)
|
||||||
|
|
||||||
|
return project, version
|
||||||
|
|
||||||
|
|
||||||
|
def guess_local_project_version_from_path(project: str, version: Optional[str]) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Use a simple heuristic to determine the project name and version from the
|
||||||
|
"project" argument supplied on the command line.
|
||||||
|
|
||||||
|
If the argument points at a file (i.e. a Cargo.toml file), the heuristics
|
||||||
|
use the name of the file's parent directory. If the argument points at a
|
||||||
|
directory, the name of the directory itself is used.
|
||||||
|
|
||||||
|
Raises an InvalidVersionError if the heuristics for automatically
|
||||||
|
determining the project name and version fail, or if the automatically
|
||||||
|
determined version is not valid according to SemVer. In this case,
|
||||||
|
supplying the optional "version" argument on the command line can override
|
||||||
|
the version string.
|
||||||
|
"""
|
||||||
|
|
||||||
if os.path.isdir(project):
|
if os.path.isdir(project):
|
||||||
dir_name = os.path.split(os.path.abspath(project))[1]
|
dir_name = os.path.split(os.path.abspath(project))[1]
|
||||||
else:
|
else:
|
||||||
dir_name = os.path.split(os.path.dirname(os.path.abspath(project)))[1]
|
dir_name = os.path.split(os.path.dirname(os.path.abspath(project)))[1]
|
||||||
project = dir_name.rstrip("0123456789.").removesuffix("-")
|
|
||||||
version = dir_name.removeprefix(f"{project}-")
|
if version:
|
||||||
|
project = dir_name.removesuffix(f"-{version}")
|
||||||
return project, version
|
return project, version
|
||||||
|
else:
|
||||||
|
return guess_local_project_version_from_dir(dir_name)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -170,6 +212,7 @@ def toml_temp_copy(toml_path: str):
|
||||||
|
|
||||||
def process_project_local(
|
def process_project_local(
|
||||||
project: str,
|
project: str,
|
||||||
|
version: Optional[str],
|
||||||
patch: bool,
|
patch: bool,
|
||||||
patch_foreign: bool,
|
patch_foreign: bool,
|
||||||
vendor: bool,
|
vendor: bool,
|
||||||
|
@ -188,7 +231,7 @@ def process_project_local(
|
||||||
|
|
||||||
# fall back to the directory name for determining the name / version
|
# fall back to the directory name for determining the name / version
|
||||||
# of the project heuristically
|
# of the project heuristically
|
||||||
name, version = guess_local_project_version_from_path(project)
|
name, version = guess_local_project_version_from_path(project, version)
|
||||||
|
|
||||||
log.warn(f"Falling back to {name!r} as the name of the project (based on the name of the containing folder).")
|
log.warn(f"Falling back to {name!r} as the name of the project (based on the name of the containing folder).")
|
||||||
diffs: tuple[Optional[list[str]], Optional[list[str]]] = (None, None)
|
diffs: tuple[Optional[list[str]], Optional[list[str]]] = (None, None)
|
||||||
|
@ -276,7 +319,7 @@ def process_project(
|
||||||
log.warn("The '--store-crate' flag has no effect for unpacked sources.")
|
log.warn("The '--store-crate' flag has no effect for unpacked sources.")
|
||||||
|
|
||||||
name, version, diffs, metadata, doc_files, license_files, vendor_tarball = process_project_local(
|
name, version, diffs, metadata, doc_files, license_files, vendor_tarball = process_project_local(
|
||||||
project, patch, patch_foreign, vendor
|
project, version, patch, patch_foreign, vendor
|
||||||
)
|
)
|
||||||
return name, version, diffs, metadata, doc_files, license_files, True, vendor_tarball
|
return name, version, diffs, metadata, doc_files, license_files, True, vendor_tarball
|
||||||
|
|
||||||
|
|
|
@ -533,7 +533,12 @@ def spec_render_workspace(
|
||||||
|
|
||||||
is_cdylib = metadata.is_cdylib()
|
is_cdylib = metadata.is_cdylib()
|
||||||
|
|
||||||
|
try:
|
||||||
rpm_version = Version.parse(upstream_version).to_rpm()
|
rpm_version = Version.parse(upstream_version).to_rpm()
|
||||||
|
except ValueError:
|
||||||
|
log.warn(f"Version {upstream_version!r} is not valid according to SemVer.")
|
||||||
|
rpm_version = upstream_version
|
||||||
|
|
||||||
rpm_description = main_package.get_description()
|
rpm_description = main_package.get_description()
|
||||||
rpm_summary = main_package.get_summary()
|
rpm_summary = main_package.get_summary()
|
||||||
|
|
||||||
|
|
29
rust2rpm/tests/test_crate.py
Normal file
29
rust2rpm/tests/test_crate.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from rust2rpm.crate import guess_local_project_version_from_dir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path,project,version",
|
||||||
|
[
|
||||||
|
("project-1.2.3", "project", "1.2.3"),
|
||||||
|
("test-project-1.2.3", "test-project", "1.2.3"),
|
||||||
|
],
|
||||||
|
ids=repr,
|
||||||
|
)
|
||||||
|
def test_guess_local_project_version_from_dir(path: str, project: str, version: str):
|
||||||
|
p, v = guess_local_project_version_from_dir(path)
|
||||||
|
assert (p, v) == (project, version)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path,error",
|
||||||
|
[
|
||||||
|
("helix-23.10-source", "Invalid version"),
|
||||||
|
],
|
||||||
|
ids=repr,
|
||||||
|
)
|
||||||
|
def test_guess_local_project_version_from_dir_fail(path: str, error: str):
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
guess_local_project_version_from_dir(path)
|
||||||
|
assert error in str(exc.value)
|
Loading…
Reference in a new issue