scaldys-project 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. scaldys_project-0.10.0/LICENSE +21 -0
  2. scaldys_project-0.10.0/PKG-INFO +76 -0
  3. scaldys_project-0.10.0/README.md +51 -0
  4. scaldys_project-0.10.0/pyproject.toml +71 -0
  5. scaldys_project-0.10.0/setup.cfg +4 -0
  6. scaldys_project-0.10.0/src/scaldys_project/__about__.py +19 -0
  7. scaldys_project-0.10.0/src/scaldys_project/__init__.py +0 -0
  8. scaldys_project-0.10.0/src/scaldys_project/__main__.py +120 -0
  9. scaldys_project-0.10.0/src/scaldys_project/common/__init__.py +0 -0
  10. scaldys_project-0.10.0/src/scaldys_project/common/base.py +181 -0
  11. scaldys_project-0.10.0/src/scaldys_project/common/compile_runner.py +88 -0
  12. scaldys_project-0.10.0/src/scaldys_project/common/config.py +182 -0
  13. scaldys_project-0.10.0/src/scaldys_project/common/docs.py +169 -0
  14. scaldys_project-0.10.0/src/scaldys_project/common/utils.py +294 -0
  15. scaldys_project-0.10.0/src/scaldys_project/windows/__init__.py +0 -0
  16. scaldys_project-0.10.0/src/scaldys_project/windows/builder.py +983 -0
  17. scaldys_project-0.10.0/src/scaldys_project.egg-info/PKG-INFO +76 -0
  18. scaldys_project-0.10.0/src/scaldys_project.egg-info/SOURCES.txt +23 -0
  19. scaldys_project-0.10.0/src/scaldys_project.egg-info/dependency_links.txt +1 -0
  20. scaldys_project-0.10.0/src/scaldys_project.egg-info/entry_points.txt +2 -0
  21. scaldys_project-0.10.0/src/scaldys_project.egg-info/requires.txt +13 -0
  22. scaldys_project-0.10.0/src/scaldys_project.egg-info/top_level.txt +1 -0
  23. scaldys_project-0.10.0/tests/test_config.py +106 -0
  24. scaldys_project-0.10.0/tests/test_docs.py +154 -0
  25. scaldys_project-0.10.0/tests/test_utils.py +88 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Scaldys
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: scaldys-project
3
+ Version: 0.10.0
4
+ Summary: A modular build system for Python projects targeting Windows.
5
+ Author-email: Scaldys <scaldys@scaldys.net>
6
+ Classifier: Programming Language :: Python
7
+ Classifier: Operating System :: Microsoft :: Windows
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Build Tools
11
+ Requires-Python: >=3.13
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: rich>=15.0.0
15
+ Requires-Dist: typer>=0.25.0
16
+ Provides-Extra: cython
17
+ Requires-Dist: cython>=3.2.4; extra == "cython"
18
+ Requires-Dist: setuptools>=82.0.1; extra == "cython"
19
+ Provides-Extra: windows
20
+ Requires-Dist: pyinstaller>=6.20.0; extra == "windows"
21
+ Provides-Extra: docs
22
+ Requires-Dist: sphinx>=9.1.0; extra == "docs"
23
+ Requires-Dist: sphinx-rtd-theme>=3.1.0; extra == "docs"
24
+ Dynamic: license-file
25
+
26
+ # scaldys-project
27
+
28
+ `scaldys-project` automates the complete Windows distribution pipeline for
29
+ Python projects. A single command takes your source code through Sphinx
30
+ documentation, optional Cython compilation, and — depending on the chosen
31
+ deployment mode — one of three distribution strategies:
32
+
33
+ - **pyinstaller** (default) — PyInstaller exe + Inno Setup installer
34
+ - **pyruntime** — binary wheel + Inno Setup installer with a managed Python
35
+ runtime (for apps that coexist with Quarto/Jupyter)
36
+ - **wheel_only** — binary wheel only, no installer (for pip-installable apps)
37
+
38
+ If you are starting a new project,
39
+ [scaldys-template](https://github.com/scaldys/scaldys-template) provides a
40
+ ready-to-use project scaffold with `scaldys-project` already integrated:
41
+ packaging scripts, Sphinx documentation, CI/CD workflows, and a working
42
+ `scaldys.toml`.
43
+
44
+ For a full guide on using and integrating `scaldys-project` in your project, see
45
+ the [user documentation](https://github.com/scaldys/scaldys-project).
46
+
47
+ ---
48
+
49
+ ## Development
50
+
51
+ This section is for contributors working on `scaldys-project` itself.
52
+
53
+ Project repository and issue tracker: https://github.com/scaldys/scaldys-project
54
+
55
+ ### Quick start
56
+
57
+ ```bash
58
+ git clone https://github.com/scaldys/scaldys-project.git
59
+ cd scaldys-project
60
+ uv sync --group dev
61
+ uv run pytest
62
+ uv run ruff check src tests && uv run pyright
63
+ ```
64
+
65
+ ### Further reading
66
+
67
+ The manual's Developers section covers everything else a contributor needs:
68
+
69
+ - [Architecture](docs/manual/source/developers/architecture.rst) — module
70
+ layout, three-class composition pattern, execution flow, configuration
71
+ loading, and why `compile_runner.py` runs as a subprocess
72
+ - [Extension Points](docs/manual/source/developers/extension_points.rst) —
73
+ design conventions and how to add Cython modules, build steps, or a new
74
+ platform builder
75
+ - [Contributing](docs/manual/source/developers/contributing.rst) — linting, type
76
+ checking, building docs, versioning, and publishing
@@ -0,0 +1,51 @@
1
+ # scaldys-project
2
+
3
+ `scaldys-project` automates the complete Windows distribution pipeline for
4
+ Python projects. A single command takes your source code through Sphinx
5
+ documentation, optional Cython compilation, and — depending on the chosen
6
+ deployment mode — one of three distribution strategies:
7
+
8
+ - **pyinstaller** (default) — PyInstaller exe + Inno Setup installer
9
+ - **pyruntime** — binary wheel + Inno Setup installer with a managed Python
10
+ runtime (for apps that coexist with Quarto/Jupyter)
11
+ - **wheel_only** — binary wheel only, no installer (for pip-installable apps)
12
+
13
+ If you are starting a new project,
14
+ [scaldys-template](https://github.com/scaldys/scaldys-template) provides a
15
+ ready-to-use project scaffold with `scaldys-project` already integrated:
16
+ packaging scripts, Sphinx documentation, CI/CD workflows, and a working
17
+ `scaldys.toml`.
18
+
19
+ For a full guide on using and integrating `scaldys-project` in your project, see
20
+ the [user documentation](https://github.com/scaldys/scaldys-project).
21
+
22
+ ---
23
+
24
+ ## Development
25
+
26
+ This section is for contributors working on `scaldys-project` itself.
27
+
28
+ Project repository and issue tracker: https://github.com/scaldys/scaldys-project
29
+
30
+ ### Quick start
31
+
32
+ ```bash
33
+ git clone https://github.com/scaldys/scaldys-project.git
34
+ cd scaldys-project
35
+ uv sync --group dev
36
+ uv run pytest
37
+ uv run ruff check src tests && uv run pyright
38
+ ```
39
+
40
+ ### Further reading
41
+
42
+ The manual's Developers section covers everything else a contributor needs:
43
+
44
+ - [Architecture](docs/manual/source/developers/architecture.rst) — module
45
+ layout, three-class composition pattern, execution flow, configuration
46
+ loading, and why `compile_runner.py` runs as a subprocess
47
+ - [Extension Points](docs/manual/source/developers/extension_points.rst) —
48
+ design conventions and how to add Cython modules, build steps, or a new
49
+ platform builder
50
+ - [Contributing](docs/manual/source/developers/contributing.rst) — linting, type
51
+ checking, building docs, versioning, and publishing
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "scaldys-project"
3
+ version = "0.10.0"
4
+ authors = [
5
+ { name="Scaldys", email="scaldys@scaldys.net" },
6
+ ]
7
+ description = "A modular build system for Python projects targeting Windows."
8
+ readme = "README.md"
9
+ requires-python = ">=3.13"
10
+ classifiers = [
11
+ "Programming Language :: Python",
12
+ "Operating System :: Microsoft :: Windows",
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Topic :: Software Development :: Build Tools",
16
+ ]
17
+ license-files = ["LICEN[CS]E*"]
18
+ dependencies = [
19
+ "rich>=15.0.0",
20
+ "typer>=0.25.0",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ cython = [
25
+ "cython>=3.2.4",
26
+ "setuptools>=82.0.1",
27
+ ]
28
+ windows = [
29
+ "pyinstaller>=6.20.0",
30
+ ]
31
+ docs = [
32
+ "sphinx>=9.1.0",
33
+ "sphinx-rtd-theme>=3.1.0",
34
+ ]
35
+
36
+ [project.scripts]
37
+ scaldys-project = "scaldys_project.__main__:app"
38
+
39
+ [build-system]
40
+ requires = ["setuptools"]
41
+ build-backend = "setuptools.build_meta"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "cython>=3.2.4",
49
+ "pre-commit>=4.5.0",
50
+ "pyright>=1.1.409",
51
+ "pyinstaller>=6.20.0",
52
+ "pytest>=9.0.3",
53
+ "pytest-cov>=7.1.0",
54
+ "ruff>=0.15.12",
55
+ "setuptools>=82.0.1",
56
+ "sphinx>=9.1.0",
57
+ "sphinx-rtd-theme>=3.1.0",
58
+ ]
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ target-version = "py313"
63
+
64
+ [tool.ruff.lint.per-file-ignores]
65
+ "__init__.py" = ["F403"]
66
+
67
+ [tool.pyright]
68
+ exclude = [".venv"]
69
+ pythonVersion = "3.13"
70
+ venvPath = "."
71
+ venv = ".venv"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ __all__ = [
4
+ "APP_NAME",
5
+ "PACKAGE_NAME",
6
+ "ORGANIZATION_NAME",
7
+ "VERSION",
8
+ ]
9
+
10
+ from importlib.metadata import version, PackageNotFoundError
11
+
12
+ APP_NAME = "Scaldys Builder"
13
+ PACKAGE_NAME = "scaldys-project"
14
+ ORGANIZATION_NAME = "Scaldys"
15
+
16
+ try:
17
+ VERSION = version(PACKAGE_NAME)
18
+ except PackageNotFoundError:
19
+ VERSION = "0.0.0"
File without changes
@@ -0,0 +1,120 @@
1
+ """
2
+ Main entry point for the scaldys-project build system.
3
+
4
+ Provides a unified CLI for building projects. Run from the root of the
5
+ consuming project (the directory that contains ``pyproject.toml``)::
6
+
7
+ scaldys-project build all # full build: docs + Windows distribution
8
+ scaldys-project build docs # documentation only
9
+ scaldys-project build windows # Windows distribution only (mode-dependent)
10
+ scaldys-project build clean # remove build/, dist/ and artifacts/
11
+ scaldys-project check # verify project compliance
12
+
13
+ The Windows distribution step is controlled by ``deployment_mode`` in
14
+ ``scaldys.toml``:
15
+
16
+ pyinstaller (default) — PyInstaller exe + Inno Setup installer
17
+ pyruntime — binary wheel + Inno Setup installer with PythonRuntime
18
+ wheel_only — binary wheel only, no installer
19
+ """
20
+
21
+ import logging
22
+ from pathlib import Path
23
+ import typer
24
+ from rich.console import Console
25
+ from rich.logging import RichHandler
26
+
27
+ from scaldys_project.windows.builder import WindowsBuilder
28
+
29
+
30
+ def _find_project_root() -> Path:
31
+ """
32
+ Walk up from the current directory to find the project root.
33
+
34
+ The project root is identified as the first directory containing a
35
+ ``pyproject.toml`` file. Falls back to ``cwd`` if none is found.
36
+ """
37
+ cwd = Path.cwd()
38
+ for candidate in [cwd, *cwd.parents]:
39
+ if (candidate / "pyproject.toml").exists():
40
+ return candidate
41
+ return cwd
42
+
43
+
44
+ PROJECT_ROOT = _find_project_root()
45
+
46
+ console = Console()
47
+ logging.basicConfig(
48
+ level=logging.INFO,
49
+ format="%(message)s",
50
+ datefmt="[%X]",
51
+ handlers=[RichHandler(rich_tracebacks=True, console=console, show_path=False, markup=True)],
52
+ )
53
+ logger = logging.getLogger(__name__)
54
+
55
+ app = typer.Typer(help="Scaldys Builder", no_args_is_help=True)
56
+ build_app = typer.Typer(help="Build subcommands", no_args_is_help=True)
57
+
58
+ app.add_typer(build_app, name="build")
59
+
60
+
61
+ @build_app.command("all")
62
+ def build_all(
63
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
64
+ ) -> None:
65
+ """Full build: documentation + Windows distribution."""
66
+ builder = WindowsBuilder(PROJECT_ROOT, verbose=verbose)
67
+ builder.main(console=console)
68
+
69
+
70
+ @build_app.command("docs")
71
+ def build_docs(
72
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
73
+ ) -> None:
74
+ """Build documentation only."""
75
+ builder = WindowsBuilder(PROJECT_ROOT, verbose=verbose)
76
+ builder.env.pre_flight_checks(require_sphinx=True)
77
+ builder.build_docs()
78
+
79
+
80
+ @build_app.command("windows")
81
+ def build_windows(
82
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
83
+ ) -> None:
84
+ """Build Windows distribution artifact (behaviour depends on deployment_mode in scaldys.toml)."""
85
+ builder = WindowsBuilder(PROJECT_ROOT, verbose=verbose)
86
+ builder.build_distribution(console=console)
87
+
88
+
89
+ @build_app.command("clean")
90
+ def build_clean(
91
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
92
+ ) -> None:
93
+ """Remove build/, dist/ and artifacts/ directories."""
94
+ builder = WindowsBuilder(PROJECT_ROOT, verbose=verbose)
95
+ builder.clean()
96
+
97
+
98
+ @app.command("check")
99
+ def check(
100
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output."),
101
+ ) -> None:
102
+ """Check that the current project is compliant with scaldys-project requirements."""
103
+ try:
104
+ builder = WindowsBuilder(PROJECT_ROOT, verbose=verbose)
105
+ except (FileNotFoundError, KeyError) as exc:
106
+ logger.error(f"[bold red]Cannot read project metadata:[/bold red] {exc}")
107
+ logger.error(
108
+ " [red]\u2717[/red] Ensure a valid 'pyproject.toml' with "
109
+ "[project] name and version is present at the project root."
110
+ )
111
+ raise SystemExit(1) from exc
112
+
113
+ mode = builder.env.config.windows.deployment_mode
114
+ is_wheel_only = mode == "wheel_only"
115
+ builder.env.check_compliance(require_exe=True, require_installer=not is_wheel_only)
116
+ logger.info("[bold green]\u2713 Project is compliant.[/bold green]")
117
+
118
+
119
+ if __name__ == "__main__":
120
+ app()
@@ -0,0 +1,181 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import shutil
5
+ import tomllib
6
+ from pathlib import Path
7
+ from subprocess import PIPE, CalledProcessError, run
8
+ from typing import Any
9
+
10
+ from scaldys_project.common.config import load_config, BuildConfig
11
+ from scaldys_project.common.utils import safe_empty_dir, safe_unlink
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class BaseBuildEnvironment:
17
+ """
18
+ Handle path discovery, tool location, and command execution.
19
+
20
+ This base class manages common paths and tools that are likely to be
21
+ used across different platforms.
22
+ """
23
+
24
+ def __init__(self, project_path: Path, verbose: bool = False) -> None:
25
+ """
26
+ Initialize the build environment.
27
+
28
+ Parameters
29
+ ----------
30
+ project_path : Path
31
+ The root directory of the project.
32
+ verbose : bool, default False
33
+ If True, set logging level to DEBUG.
34
+ """
35
+ if verbose:
36
+ logger.setLevel(logging.DEBUG)
37
+
38
+ self.verbose = verbose
39
+ self.project_path = project_path.resolve()
40
+
41
+ # Read project name and version from pyproject.toml — single source of truth.
42
+ with open(self.project_path / "pyproject.toml", "rb") as _f:
43
+ _meta = tomllib.load(_f)["project"]
44
+ # Raw name (hyphens preserved) — used for exe naming, packaging file names,
45
+ # and display. Do not use for Python import paths.
46
+ self.project_name: str = _meta["name"]
47
+ # PEP 503/508: package directories must use underscores; hyphens are
48
+ # valid in pyproject.toml [project] name but illegal in Python imports.
49
+ self.project_package_name: str = _meta["name"].replace("-", "_")
50
+ self.version: str = _meta["version"]
51
+
52
+ # Load project-specific build configuration from scaldys.toml (defaults if absent).
53
+ self.config: BuildConfig = load_config(self.project_path)
54
+
55
+ self.python_exe_path = Path(sys.executable)
56
+ self.python_dir_path = self.python_exe_path.parent
57
+ self.exe_name = self.project_name
58
+
59
+ # Core project paths
60
+ self.build_dir_path = self.project_path.joinpath("build")
61
+ self.python_version_file_path = self.project_path / ".python-version"
62
+ self.docs_dir_path = self.project_path.joinpath("docs")
63
+ self.src_dir_path = self.project_path / self.config.cython.source_root
64
+ self.dist_dir_path = self.project_path.joinpath("dist")
65
+ self.artifacts_dir_path = self.project_path.joinpath("artifacts")
66
+
67
+ # Executables (find in PATH)
68
+ self.sphinx_exe_path = self._find_tool(
69
+ "sphinx-build" + (".exe" if os.name == "nt" else ""), self.python_dir_path
70
+ )
71
+ self.sphinx_apidoc_exe_path = self._find_tool(
72
+ "sphinx-apidoc" + (".exe" if os.name == "nt" else ""), self.python_dir_path
73
+ )
74
+
75
+ def _find_tool(self, name: str, fallback_path: Path) -> Path:
76
+ """
77
+ Find a tool in PATH or in a fallback location.
78
+
79
+ Parameters
80
+ ----------
81
+ name : str
82
+ The name of the tool executable.
83
+ fallback_path : Path
84
+ The directory to look in if the tool is not in PATH.
85
+
86
+ Returns
87
+ -------
88
+ Path
89
+ The path to the tool.
90
+ """
91
+ tool_path = shutil.which(name)
92
+ if tool_path:
93
+ return Path(tool_path)
94
+ return fallback_path.joinpath(name)
95
+
96
+ def run_command(self, cmd: list[str], err_msg: str, cwd: Path | None = None) -> tuple[str, str]:
97
+ """
98
+ Run command in subprocess and raise on unexpected exit status.
99
+
100
+ Parameters
101
+ ----------
102
+ cmd : list of str
103
+ The command to run.
104
+ err_msg : str
105
+ The error message to display if the command fails.
106
+ cwd : Path, optional
107
+ The working directory to run the command in.
108
+
109
+ Returns
110
+ -------
111
+ stdout : str
112
+ The standard output of the command.
113
+ stderr : str
114
+ The standard error of the command.
115
+
116
+ Raises
117
+ ------
118
+ RuntimeError
119
+ If the command returns a non-zero exit status.
120
+ """
121
+ try:
122
+ proc = run(
123
+ cmd,
124
+ stdout=PIPE,
125
+ stderr=PIPE,
126
+ text=True,
127
+ check=True,
128
+ cwd=cwd,
129
+ )
130
+ return proc.stdout, proc.stderr
131
+ except CalledProcessError as e:
132
+ logger.error(f"{err_msg}: {e}")
133
+ if e.stderr:
134
+ logger.error(f"Error output: {e.stderr}")
135
+ raise RuntimeError(err_msg) from e
136
+ except Exception as e: # Catch-all for unexpected OS-level errors (FileNotFoundError, etc.)
137
+ logger.error(f"{err_msg}: {e}")
138
+ raise RuntimeError(err_msg) from e
139
+
140
+
141
+ class BaseBuilder:
142
+ """
143
+ Base class for platform-specific builders.
144
+
145
+ Orchestrates the build pipeline for a specific platform.
146
+ """
147
+
148
+ def __init__(self, env: BaseBuildEnvironment):
149
+ """
150
+ Initialize the builder.
151
+
152
+ Parameters
153
+ ----------
154
+ env : BaseBuildEnvironment
155
+ The build environment configuration.
156
+ """
157
+ self.env = env
158
+
159
+ def pre_flight_checks(self, **kwargs: Any) -> None:
160
+ """
161
+ Perform checks before starting the build.
162
+
163
+ Parameters
164
+ ----------
165
+ **kwargs : Any
166
+ Arguments passed to the environment's pre-flight checks.
167
+ """
168
+ pass
169
+
170
+ def clean(self) -> None:
171
+ """
172
+ Clean out files generated by previous builds.
173
+ """
174
+ target_dirs = [self.env.build_dir_path, self.env.dist_dir_path, self.env.artifacts_dir_path]
175
+ logger.info("[bold]Cleaning build directories...[/bold]")
176
+ for path in target_dirs:
177
+ logger.info(f" Cleaning directory '{path}'")
178
+ safe_empty_dir(path)
179
+
180
+ for c_file in self.env.src_dir_path.rglob("*.c"):
181
+ safe_unlink(c_file)
@@ -0,0 +1,88 @@
1
+ """
2
+ Cython compilation runner for scaldys-project.
3
+
4
+ Invoked as a module by the build system::
5
+
6
+ python -P -m scaldys_project.common.compile_runner build_ext --build-lib <path>
7
+
8
+ Reads the list of modules to compile from ``scaldys.toml`` in the current
9
+ working directory (the consuming project's root). If no modules are configured
10
+ or ``scaldys.toml`` is absent, exits immediately without error — fully
11
+ supporting pure-Python projects that have no Cython compilation step.
12
+
13
+ The ``--compiler=msvc`` flag is passed by the build system on Windows because
14
+ the MSVC compiler is required for CPython extension modules. MinGW is not
15
+ supported for this purpose on Windows builds.
16
+ """
17
+
18
+ import os
19
+ import sys
20
+ import tomllib
21
+ from pathlib import Path
22
+ from setuptools import setup, Extension, find_packages
23
+ from setuptools.dist import Distribution
24
+
25
+
26
+ class BinaryDistribution(Distribution):
27
+ """Distribution that always forces a binary package with a platform name."""
28
+
29
+ def has_ext_modules(self) -> bool:
30
+ return True
31
+
32
+
33
+ def _load_cython_config() -> tuple[list[str], str]:
34
+ """
35
+ Read ``compiled_modules`` and ``source_root`` from ``scaldys.toml`` in cwd.
36
+
37
+ Returns
38
+ -------
39
+ compiled_modules : list of str
40
+ Dotted module paths to compile. Empty list if unconfigured.
41
+ source_root : str
42
+ Source directory relative to project root. Defaults to ``"src"``.
43
+ """
44
+ config_file = Path.cwd() / "scaldys.toml"
45
+ if not config_file.exists():
46
+ return [], "src"
47
+ with open(config_file, "rb") as f:
48
+ data = tomllib.load(f)
49
+ cython_cfg = data.get("cython", {})
50
+ return (
51
+ cython_cfg.get("compiled_modules", []),
52
+ cython_cfg.get("source_root", "src"),
53
+ )
54
+
55
+
56
+ def _get_extensions(compiled_modules: list[str], source_root: str) -> list[Extension]:
57
+ extensions = []
58
+ for module in compiled_modules:
59
+ source_file = os.path.join(source_root, *module.split(".")) + ".py"
60
+ extensions.append(Extension(module, sources=[source_file]))
61
+ return extensions
62
+
63
+
64
+ if __name__ == "__main__":
65
+ # Deferred import: Cython may not be installed in doc-build or pure-Python
66
+ # environments. Keeping these imports here ensures the module can be safely
67
+ # imported without executing it (e.g., by test runners or import scanners).
68
+ from Cython.Build import cythonize
69
+ from Cython.Distutils.build_ext import build_ext
70
+
71
+ compiled_modules, source_root = _load_cython_config()
72
+
73
+ if not compiled_modules:
74
+ # Nothing to compile; pure-Python project or no modules declared.
75
+ sys.exit(0)
76
+
77
+ setup(
78
+ name="__compile_runner__",
79
+ cmdclass={"build_ext": build_ext},
80
+ ext_modules=cythonize(
81
+ _get_extensions(compiled_modules, source_root),
82
+ annotate=False,
83
+ language_level="3",
84
+ ),
85
+ package_dir={"": source_root},
86
+ packages=find_packages(source_root),
87
+ distclass=BinaryDistribution,
88
+ )