libdictenstein 4.0.0-rc.3

High-performance dictionary data structures (trie, DAWG, double-array trie, suffix automaton, lock-free durable persistent ART) behind one trait API; pairs with liblevenshtein for fuzzy matching
from __future__ import annotations

import os
import platform
import shutil
import subprocess
from pathlib import Path

from setuptools import Distribution, setup
from setuptools.command.bdist_wheel import bdist_wheel
from setuptools.command.build_py import build_py

BINDING_DIRECTORY = Path(__file__).resolve().parent
REPOSITORY_ROOT = BINDING_DIRECTORY.parents[1]


def native_library_name() -> str:
    system = platform.system()
    if system == "Windows":
        return "libdictenstein.dll"
    if system == "Darwin":
        return "liblibdictenstein.dylib"
    return "liblibdictenstein.so"


def native_library() -> Path:
    explicit = os.environ.get("LIBDICTENSTEIN_PREBUILT_LIBRARY")
    if explicit:
        library = Path(explicit).expanduser().resolve()
        if not library.is_file():
            raise FileNotFoundError(
                f"LIBDICTENSTEIN_PREBUILT_LIBRARY is not a file: {library}"
            )
        return library

    command = [
        "cargo",
        "build",
        "--manifest-path",
        str(REPOSITORY_ROOT / "Cargo.toml"),
        "--release",
        "--features",
        "python-bindings",
    ]
    target = os.environ.get("LIBDICTENSTEIN_RUST_TARGET")
    if target:
        command.extend(["--target", target])
    subprocess.run(command, cwd=REPOSITORY_ROOT, check=True)

    target_directory = Path(
        os.environ.get("CARGO_TARGET_DIR", REPOSITORY_ROOT / "target")
    )
    profile = (
        target_directory / target / "release"
        if target
        else target_directory / "release"
    )
    library = profile / native_library_name()
    if not library.is_file():
        raise FileNotFoundError(f"Cargo did not produce the native library: {library}")
    return library


class BuildWithNativeLibrary(build_py):
    def run(self) -> None:
        super().run()
        destination = Path(self.build_lib) / "libdictenstein" / "native"
        destination.mkdir(parents=True, exist_ok=True)
        shutil.copy2(native_library(), destination / native_library_name())
        shutil.copy2(
            REPOSITORY_ROOT / "LICENSE",
            Path(self.build_lib) / "libdictenstein" / "LICENSE",
        )


class PlatformDistribution(Distribution):
    def has_ext_modules(self) -> bool:
        return True


class PortableWheel(bdist_wheel):
    def finalize_options(self) -> None:
        super().finalize_options()
        self.root_is_pure = False

    def get_tag(self) -> tuple[str, str, str]:
        _, _, platform_tag = super().get_tag()
        return "py3", "none", platform_tag


setup(
    cmdclass={"build_py": BuildWithNativeLibrary, "bdist_wheel": PortableWheel},
    distclass=PlatformDistribution,
)