Visitar URL original
Coverage review before 1.0: Cython workflows we do/don't support Β· Issue #84 Β· scikit-build/cython-cmake Β· GitHub
Skip to content

Coverage review before 1.0: Cython workflows we do/don't supportΒ #84

Open

Description

@henryiii

πŸ€– AI text below πŸ€–

Tracking issue: review of Cython workflows we do and don't cover, as a checklist before tagging 1.0. Nothing below is a hard blocker β€” CYTHON_ARGS is a sufficient escape hatch for almost everything β€” but a 1.0 implies an API contract, so the main risk is documenting boundaries rather than missing capability.

Likely-intentional boundaries that should be documented

These aren't bugs, but users migrating from setuptools/cythonize will hit them:

  1. # distutils: directives other than language are silently ignored. cythonize honors # distutils: sources = ..., libraries = ..., include_dirs = ..., extra_compile_args = ..., define_macros = ... and applies them to the extension; we only parse language (_cython_compute_language). This is the right call β€” CMake owns build config β€” but it's the No 1 surprise coming from setuptools. One README sentence ("only the language directive is read; express libraries/includes/flags as CMake target properties") would help.
  2. No cython_add_module / one-call helper. We transpile, then the user calls python_add_library. Classic scikit-build UseCython had add_cython_target. Decide consciously whether 1.0 ships a convenience wrapper or commits to the two-step idiom.
  3. No cythonize-style behavior generally (parallelism, dependency-graph resolution, directory globbing). We wrap the raw cython CLI one file at a time. Worth a line.

Real gaps worth considering

  1. Pure-Python mode (.py input). Cython compiles .py files. Our code is filename-agnostic (NAME_WE -> foo.c), so it probably works, but there's no test fixture and the README only mentions .pyx. Add a fixture, or document that it's .pyx only.
  2. language_level. Not set by default, so Cython emits its "directive 'language_level' not set" warning unless the user adds -X language_level=3 to CYTHON_ARGS. Worth showing in the README example so the common case is warning-free.
  3. --annotate (-a) HTML output isn't declared to CMake. Generated next to the .c but not tracked (no clean, not in OUTPUT_VARIABLE). Minor.
  4. -I / cimport include dirs only reachable via CYTHON_ARGS. A dedicated keyword could be nice for multi-package layouts, but depfile + relative cimports cover most cases β€” probably leave it.

Polish (doc-only)

  1. Coverage (-X linetrace=True + CYTHON_TRACE define) and profiling β€” work today via CYTHON_ARGS + target compile definitions; pure documentation.
  2. --embed standalone executables β€” work via CYTHON_ARGS; a short example would help.

Suggested 1.0 priority

  • Document the "only language directive is read" boundary (No 1) and add language_level to the example (No 5).
  • Decide on .py support + add a fixture or a "pyx only" note (No 4).
  • Decide on a one-call helper vs. the two-step idiom (No 2).

Activity

  1. henryiii commented on Jul 9, 2026

    @henryiii
    ContributorAuthor

    Ha, tried to get Fable to make an independent assessment, but it found this issue.

    πŸ€– AI text below πŸ€–

    Good timing β€” issue #84 already tracks a pre-1.0 coverage checklist (distutils directives, one-call helper, pure-Python .py mode, language_level, annotate output, -I dirs, coverage/embed docs). Setting those aside, here's what I'd add after reading FindCython.cmake and UseCython.cmake fresh:

    Real functional gaps not in #84:

    1. cdef public / cdef api headers are undeclared outputs. When a .pyx contains public or api declarations, Cython writes foo.h / foo_api.h next to the .c file. Cython_transpile doesn't declare them as BYPRODUCTS/OUTPUT, doesn't return their paths, and other targets can't depend on them cleanly. This is the whole "embed Cython in a larger C/C++ app" workflow β€” arguably the workflow where CMake users most outnumber setuptools users. A GENERATE_HEADERS-style option (or auto-detection plus an OUTPUT_HEADER_VARIABLE) would cover it.

    2. No MODULE_NAME β†’ --module-name mapping. For a .pyx nested in a package, the raw CLI derives the module name from the filename, so __module__, pickling, and error messages get the unqualified name unless the source tree happens to have __init__ files that Cython can walk. cythonize computes the dotted name for you; here the user has to know to pass --module-name pkg.sub.mod via CYTHON_ARGS. A first-class keyword is cheap and self-documenting. Your multiple_packages fixture would be a natural place to assert on this.

    3. Cython::Cython isn't actually used by UseCython. The transpile command reads CYTHON_EXECUTABLE (or falls back to python -m cython) but never references the imported target, and the custom command has no dependency on the cython binary β€” upgrading Cython doesn't trigger re-transpilation. Using $<TARGET_FILE:Cython::Cython> in the command (when the target exists) would fix staleness and let users point the target elsewhere. Related: find_package(Cython) isn't required before include(UseCython), which is flexible, but the python -m cython fallback silently bypasses any version check the user did in find_package.

    4. Inter-package .pxd story. Packages that want others to cimport them (the NumPy pattern) need to install their .pxd files and consumers need the right -I. There's no helper for either direction β€” no "install these pxd next to the module" convenience, and Coverage review before 1.0: Cython workflows we do/don't supportΒ #84 punts on the -I keyword. Even doc-only guidance ("install .pxd via install(FILES ...), consume via CYTHON_ARGS -I") would help; a INCLUDE_DIRECTORIES keyword would help more, since CYTHON_ARGS is a single escape hatch that collides once users need it for two things.

    Documentation gaps (beyond #84's list):

    1. NumPy. The single most common Cython pattern is cimport numpy β€” needing find_package(Python COMPONENTS NumPy), linking Python::NumPy, and NPY_NO_DEPRECATED_API. A README/example fixture showing this would preempt a lot of first-contact friction, and the C++ fixture shows you're already using fixtures as documentation.

    2. Free-threaded Python. Cython 3.1+ builds for 3.13t/3.14t; nothing in this package blocks it, but a sentence confirming it works (and a CI job on 3.13t) would be a cheap trust signal, since scikit-build-core users are exactly the crowd trying it.

    3. What "no cythonize" means for compiler directives. cythonize reads [tool.cython]-style defaults and directive stacking; here everything is per-call CYTHON_ARGS. A short "recommended pattern: set the CYTHON_ARGS cache variable once at top level" note would make the existing fallback (UseCython.cmake:200) discoverable β€” it's currently only visible by reading the source.

    If I were prioritizing for 1.0: items 2 and 3 are small, contained, and API-shaping (better before the contract freezes than after); item 1 is the biggest genuine capability gap; 4–7 can land any time since they're additive. Want me to add any of these to issue #84, or file them separately?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions