rust2rpm/rust2rpm/metadata.py

109 lines
3.7 KiB
Python

from typing import Optional
from cargo2rpm.metadata import Metadata, Package
from cargo2rpm.semver import VersionReq, Comparator, Op as Operator
from rust2rpm import log
def get_required_features_for_binaries(package: Package) -> set[str]:
required_features = set()
for target in package.targets:
if "bin" in target.kind and "bin" in target.crate_types:
if reqs := target.required_features:
required_features.update(reqs)
if "cdylib" in target.kind and "cdylib" in target.crate_types:
if reqs := target.required_features:
required_features.update(reqs)
return required_features
def guess_main_package(metadata: Metadata, hint: Optional[str] = None, interactive: bool = False) -> Package:
if not metadata.is_workspace():
return metadata.packages[0]
with_bin = []
with_cdylib = []
for package in metadata.packages:
for target in package.targets:
if "bin" in target.kind and "bin" in target.crate_types:
with_bin.append(package)
if "cdylib" in target.kind and "cdylib" in target.crate_types:
with_cdylib.append(package)
if len(with_cdylib) == 1:
return with_cdylib[0]
if len(with_bin) == 1:
return with_bin[0]
if interactive: # pragma nocover
choices = [package.name for package in with_bin] + [package.name for package in with_cdylib]
log.warn(
'Heuristic for determining the "main" crate of the workspace failed. '
+ 'Please specify the name of the "main" crate (usually the crate which '
+ "provides the user-facing CLI) manually."
)
log.info("Choices: " + ", ".join(choices))
hint = input("Crate name: ")
if hint not in choices:
log.error("Invalid crate name.")
raise ValueError
for package in metadata.packages:
if package.name == hint:
return package
if hint:
for package in metadata.packages:
if package.name == hint:
log.warn(
f'Using {hint!r} as the name of the "main" crate. If this is not correct, use interactive mode.'
)
return package
log.error("Heuristic for determining the main crate of the workspace failed.")
log.error("Please use '--interactive' mode.")
raise ValueError
def package_uses_rust_1_60_feature_syntax(features: dict[str, list[str]]) -> bool:
for name, deps in features.items():
for dep in deps:
if "?/" in dep:
return True
if dep.startswith("dep:"):
if len(deps) != 1:
return True
if dep.removeprefix("dep:") != name:
return True
return False
def warn_if_package_uses_restrictive_dependencies(package: Package):
for dependency in package.dependencies:
name = dependency.rename or dependency.name
req = VersionReq.parse(dependency.req)
for comp in req.comparators:
if _is_strict_dep(comp):
log.warn(f"Dependency on {name!r} stricter than necessary: {comp}")
def _is_strict_dep(comp: Comparator) -> bool:
# dependencies like "~1.0" are stricter than SemVer
if comp.op == Operator.TILDE and comp.major > 0 and comp.minor is not None:
return True
# dependencies like "1.0.*" are stricter than SemVer
if comp.op == Operator.WILDCARD and comp.major > 0 and comp.minor is not None:
return True
# dependencies like "=1.2" are stricter than SemVer
if comp.op == Operator.EXACT and comp.major > 0 and comp.minor is not None and comp.patch is None:
return True
return False