Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions engraphis/cloud_license.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import logging
import math
import os
import sys
import tempfile
import threading
import urllib.error
Expand Down Expand Up @@ -653,3 +654,34 @@ def revalidate(lic, key_material: str, *, base_url: Optional[str] = None) -> str
return "offline"
_write_lease(token)
return "ok"


# ── compilation integrity guard — runs at import time ────────────────────────────


def _verify_module_integrity():
"""Detect if this module was replaced with editable source after a compiled
extension was already installed (same check as licensing.py)."""
mod = sys.modules.get(__name__)
if mod is None:
return
f = getattr(mod, "__file__", "")
if not f:
return
if os.environ.get("ENGRAPHIS_DEV", "").strip() in ("1", "true", "yes"):
return
if not f.endswith(".py"):
return
from importlib.machinery import EXTENSION_SUFFIXES
dirname = os.path.dirname(f)
basename = os.path.splitext(os.path.basename(f))[0]
for suffix in EXTENSION_SUFFIXES:
if os.path.exists(os.path.join(dirname, basename + suffix)):
raise RuntimeError(
"Engraphis cloud_license integrity check failed: a compiled native "
"extension exists but is not being loaded. Reinstall from the "
"official distribution. For development, set ENGRAPHIS_DEV=1."
)


_verify_module_integrity()
67 changes: 67 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Build script — compiles licensing + cloud_license into native C extensions for
distribution. Dev installs (``pip install -e .``) skip Cython and use the plain .py
sources; ``python -m build`` / ``pip install .`` (release) compile them via Cython."""

import os

from setuptools import Extension, setup
from setuptools.command.build_py import build_py as _build_py


SKIP_CYTHON = os.environ.get("ENGRAPHIS_SKIP_CYTHON", "").strip() == "1"
EXT_MODULES = []

if not SKIP_CYTHON:
try:
from Cython.Build import cythonize
except ImportError:
cythonize = None

if cythonize is not None and not (
# pip install -e . does NOT run build_ext, so skip. Detect via
# SETUPTOOLS_ENABLE_FEATURES (setuptools >= 69) or legacy editable flag.
os.environ.get("SETUPTOOLS_ENABLE_FEATURES", "") == "legacy-editable"
):
EXT_MODULES = cythonize(
[
Extension(
"engraphis.licensing",
["engraphis/licensing.py"],
),
Extension(
"engraphis.cloud_license",
["engraphis/cloud_license.py"],
),
],
compiler_directives={
"language_level": "3",
"boundscheck": False,
"wraparound": False,
},
# Defer the dep files into the build dir so the engraphis/ source tree
# stays clean on every platform (Cython generates licensing.c etc.)
build_dir="build",
)


class build_py(_build_py):
"""Exclude licensing.py and cloud_license.py from the package when compiled
extensions exist — ship the .pyd/.so instead so the compiled version wins."""

def find_package_modules(self, package, package_dir):
modules = super().find_package_modules(package, package_dir)
if not EXT_MODULES:
return modules
return [
m for m in modules
if (package, m[1]) not in {
("engraphis", "licensing"),
("engraphis", "cloud_license"),
}
]


setup(
ext_modules=EXT_MODULES,
cmdclass={"build_py": build_py},
)