diff --git a/docs/source/schema.yaml b/docs/source/schema.yaml index c95c0bf..c825f06 100644 --- a/docs/source/schema.yaml +++ b/docs/source/schema.yaml @@ -193,6 +193,17 @@ test_files: # you need. Globs are expanded using `pathlib.Path.glob`. All paths are # relative to the directory containing the ``polycotylus.yaml``. +dependency_name_map: + GitPython: + debian: python3-git + # An override for `polycotylus`\ 's mapping of pip/PyPI package to Linux + # package equivalents. To be used if one of your dependencies is available in + # the Linux package repositories but `polycotylus` can't find it because it's + # been named something that breaks the distribution's naming convention. Note + # that this option does not exist for ``fedora`` or ``opensuse`` due to their + # handling the mapping using package *capabilities* rather than a naming + # pattern. + # -------------------- # CLI/GUI only options diff --git a/docs/source/schema_to_rst.py b/docs/source/schema_to_rst.py index 88d13dc..064c498 100644 --- a/docs/source/schema_to_rst.py +++ b/docs/source/schema_to_rst.py @@ -66,7 +66,7 @@ yaml[key].append(m[2]) i += 1 value = yaml - if key == "actions": + if key in {"actions", "dependency_name_map"}: chunk = [] while not comment_re.match(lines[i]): chunk.append(lines[i]) diff --git a/examples/ubrotli/ugg/pool/main/u/ubrotli/python3-ubrotli_0.1.0-1_i386.deb b/examples/ubrotli/ugg/pool/main/u/ubrotli/python3-ubrotli_0.1.0-1_i386.deb new file mode 100644 index 0000000..5d9d837 Binary files /dev/null and b/examples/ubrotli/ugg/pool/main/u/ubrotli/python3-ubrotli_0.1.0-1_i386.deb differ diff --git a/polycotylus/_base.py b/polycotylus/_base.py index ba4ed4e..0788e29 100644 --- a/polycotylus/_base.py +++ b/polycotylus/_base.py @@ -98,7 +98,7 @@ def evaluate_requirements_marker(cls, requirement: Requirement): }) @classmethod - def python_package(cls, requirement): + def python_package(cls, requirement, dependency_name_map=None): requirement = Requirement(requirement) name = re.sub("[._-]+", "-", requirement.name.lower()) available = cls.available_packages_normalized() @@ -107,13 +107,15 @@ def python_package(cls, requirement): return else: requirement.marker = None - if cls.python_package_convention(name) in available: + if dependency_name_map and name in dependency_name_map: + name = dependency_name_map[name] + elif cls.python_package_convention(name) in available: name = available[cls.python_package_convention(name)] elif name in available: name = available[name] elif m := re.match("(python|py)3?-?(.*)", name.lower()): try: - name = cls.python_package(m[2]) + name = cls.python_package(m[2], dependency_name_map) except _exceptions.PackageUnavailableError: raise _exceptions.PackageUnavailableError(requirement.name, cls.name) from None else: @@ -123,6 +125,16 @@ def python_package(cls, requirement): requirement.extras = set() return str(requirement) + @property + def dependency_name_map(self): + out = {} + for (package, map) in self.project.dependency_name_map.items(): + for key in map: + if self.name in key.split(): + out[re.sub("[._-]+", "-", package.lower())] = map[key] + break + return out + @abc.abstractmethod def fix_package_name(name): """Apply the distribution's package naming rules for case folding/ @@ -191,7 +203,7 @@ def _dependencies(self, dependencies): for extra in dependencies.get("python", []): out += self.python_extras.get(extra, []) for package in dependencies.get("pip", []): - out.append(self.python_package(package)) + out.append(self.python_package(package, self.dependency_name_map)) out += dependencies.get(self.name, []) return list(filter(None, out)) diff --git a/polycotylus/_exceptions.py b/polycotylus/_exceptions.py index 3ac5204..ec1db61 100644 --- a/polycotylus/_exceptions.py +++ b/polycotylus/_exceptions.py @@ -57,10 +57,18 @@ def __init__(self, package, distribution): def __str__(self): return _unravel(f""" - Dependency "{self.package}" is not available on + Dependency "{self.package}" appears to be unavailable on {self.distribution.title()} Linux. You will need to submit {self.package} to {self.distribution.title()} Linux's package - repositories before you can build your own project. + repositories before you can build your own project. It's also + possible that it is already there but is named something weird, + in which case, supply its name to the dependency_name_map option in + the polycotylus.yaml: + + # polycoylus.yaml + dependency_name_map: + {self.package}: + {self.distribution}: whatever-its-really-called """) diff --git a/polycotylus/_fedora.py b/polycotylus/_fedora.py index 6381dfa..51e4081 100644 --- a/polycotylus/_fedora.py +++ b/polycotylus/_fedora.py @@ -72,7 +72,7 @@ def python_version(cls): return _docker.run(cls.base_image, command, tty=True).output.strip() @classmethod - def python_package(cls, requirement): + def python_package(cls, requirement, _=None): requirement = Requirement(requirement) requirement.name = f"python3dist({cls.fix_package_name(requirement.name)})" if not cls.evaluate_requirements_marker(requirement): diff --git a/polycotylus/_opensuse.py b/polycotylus/_opensuse.py index 6207b03..a556720 100644 --- a/polycotylus/_opensuse.py +++ b/polycotylus/_opensuse.py @@ -98,7 +98,7 @@ def fix_package_name(name): return re.sub(r"[^a-z0-9]+", "-", name.lower()) @classmethod - def python_package(cls, requirement): + def python_package(cls, requirement, _=None): requirement = Requirement(requirement) name = re.sub("[._-]+", "-", requirement.name.lower()) available = cls.available_packages_normalized() diff --git a/polycotylus/_project.py b/polycotylus/_project.py index a93e826..78bcdcb 100644 --- a/polycotylus/_project.py +++ b/polycotylus/_project.py @@ -39,6 +39,7 @@ class Project: dependencies: dict build_dependencies: dict test_dependencies: dict + dependency_name_map: dict test_command: str test_files: list license_names: list @@ -313,6 +314,7 @@ def from_root(cls, root): build_dependencies=dependencies["build"], test_dependencies=dependencies["test"], test_command=test_command, + dependency_name_map=polycotylus_options.get("dependency_name_map", {}), test_files=test_files, url=project["urls"]["homepage"], license_names=license_names, diff --git a/polycotylus/_yaml_schema.py b/polycotylus/_yaml_schema.py index cbc3ed7..edfc1e8 100644 --- a/polycotylus/_yaml_schema.py +++ b/polycotylus/_yaml_schema.py @@ -117,7 +117,6 @@ def validate_scalar(self, chunk): Optional("dependencies"): Map({ Optional(type): dependencies_group for type in ["run", "build", "test"] }), - Optional("dependency_map"): MapPattern(Str(), MapPattern(Str(), Str())), Optional("maintainer"): Maintainer(), Optional("gui"): Bool(), Optional("spdx"): MapPattern(Str(), EmptyDict()), @@ -130,6 +129,7 @@ def validate_scalar(self, chunk): Regex("(any|none)"), WhitespaceDelimited(Regex("!?(" + "|".join(architectures) + ")")), ), + Optional("dependency_name_map"): MapPattern(Str(), MapPattern(Str(), Str())), }) diff --git a/tests/mock-packages/kitchen-sink/polycotylus.yaml b/tests/mock-packages/kitchen-sink/polycotylus.yaml index 654e315..786f824 100644 --- a/tests/mock-packages/kitchen-sink/polycotylus.yaml +++ b/tests/mock-packages/kitchen-sink/polycotylus.yaml @@ -20,6 +20,10 @@ dependencies: python: sqlite3 tkinter pip: pytest[feet] +dependency_name_map: + tzlocal: + arch manjaro: gnu-netcat + test_command: TEST_VARIABLE=hello pytest -k 'not unrunable' && python -c 'print("hello")' test_files: diff --git a/tests/mock-packages/kitchen-sink/pyproject.toml b/tests/mock-packages/kitchen-sink/pyproject.toml index a08aea8..592fbb8 100644 --- a/tests/mock-packages/kitchen-sink/pyproject.toml +++ b/tests/mock-packages/kitchen-sink/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] dynamic = ["version"] license = { file="The license file" } -dependencies = ["certifi>=2020.4", "pywin32-ctypes; platform_system == 'Windows'"] +dependencies = ["certifi>=2020.4", "pywin32-ctypes; platform_system == 'Windows'", "tzlocal"] [build-system] requires = ["colorama; platform_system == 'Linux' and python_version >= '3.8' and implementation_name == 'cpython'", "setuptools", "setuptools-scm"] diff --git a/tests/mock-packages/kitchen-sink/the_test_suite/test_sIlLy_Nam3.py b/tests/mock-packages/kitchen-sink/the_test_suite/test_sIlLy_Nam3.py index a26f7b1..9076bf6 100644 --- a/tests/mock-packages/kitchen-sink/the_test_suite/test_sIlLy_Nam3.py +++ b/tests/mock-packages/kitchen-sink/the_test_suite/test_sIlLy_Nam3.py @@ -30,3 +30,12 @@ def test_unrunable(): def test_environment_variable(): assert os.environ["TEST_VARIABLE"] == "hello" + + +def test_dependency_name_map(): + if shutil.which("pacman"): + assert shutil.which("netcat") + with pytest.raises(ImportError): + import tzlocal + else: + import tzlocal diff --git a/tests/test_alpine.py b/tests/test_alpine.py index 7bb2970..0413016 100644 --- a/tests/test_alpine.py +++ b/tests/test_alpine.py @@ -162,7 +162,7 @@ def test_unknown_package(polycotylus_yaml): """) self = Alpine(Project.from_root(shared.dumb_text_viewer)) with pytest.raises(_exceptions.PolycotylusUsageError, - match="Dependency \"Hippos_can_fly\" is not .* on Alpine Linux. " + match="Dependency \"Hippos_can_fly\" appears .* on Alpine Linux. " ".* submit Hippos_can_fly to Alpine Linux\'s package"): self.apkbuild() polycotylus_yaml("""