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.
- scaldys_project-0.10.0/LICENSE +21 -0
- scaldys_project-0.10.0/PKG-INFO +76 -0
- scaldys_project-0.10.0/README.md +51 -0
- scaldys_project-0.10.0/pyproject.toml +71 -0
- scaldys_project-0.10.0/setup.cfg +4 -0
- scaldys_project-0.10.0/src/scaldys_project/__about__.py +19 -0
- scaldys_project-0.10.0/src/scaldys_project/__init__.py +0 -0
- scaldys_project-0.10.0/src/scaldys_project/__main__.py +120 -0
- scaldys_project-0.10.0/src/scaldys_project/common/__init__.py +0 -0
- scaldys_project-0.10.0/src/scaldys_project/common/base.py +181 -0
- scaldys_project-0.10.0/src/scaldys_project/common/compile_runner.py +88 -0
- scaldys_project-0.10.0/src/scaldys_project/common/config.py +182 -0
- scaldys_project-0.10.0/src/scaldys_project/common/docs.py +169 -0
- scaldys_project-0.10.0/src/scaldys_project/common/utils.py +294 -0
- scaldys_project-0.10.0/src/scaldys_project/windows/__init__.py +0 -0
- scaldys_project-0.10.0/src/scaldys_project/windows/builder.py +983 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/PKG-INFO +76 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/SOURCES.txt +23 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/dependency_links.txt +1 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/entry_points.txt +2 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/requires.txt +13 -0
- scaldys_project-0.10.0/src/scaldys_project.egg-info/top_level.txt +1 -0
- scaldys_project-0.10.0/tests/test_config.py +106 -0
- scaldys_project-0.10.0/tests/test_docs.py +154 -0
- 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,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()
|
|
File without changes
|
|
@@ -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
|
+
)
|