cmakeless 0.5.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.
- cmakeless-0.5.0/.github/workflows/ci.yml +51 -0
- cmakeless-0.5.0/.github/workflows/release.yml +99 -0
- cmakeless-0.5.0/.gitignore +24 -0
- cmakeless-0.5.0/ARCHITECTURE.md +189 -0
- cmakeless-0.5.0/CHANGELOG.md +293 -0
- cmakeless-0.5.0/CLAUDE.md +112 -0
- cmakeless-0.5.0/CONTRIBUTING.md +82 -0
- cmakeless-0.5.0/FEATURES.md +319 -0
- cmakeless-0.5.0/INTRODUCTION.md +116 -0
- cmakeless-0.5.0/LICENSE +153 -0
- cmakeless-0.5.0/PKG-INFO +339 -0
- cmakeless-0.5.0/README.md +309 -0
- cmakeless-0.5.0/ROADMAP.md +173 -0
- cmakeless-0.5.0/benchmarks/README.md +42 -0
- cmakeless-0.5.0/benchmarks/bench_presets.py +130 -0
- cmakeless-0.5.0/benchmarks/bench_resolution.py +79 -0
- cmakeless-0.5.0/docs/benchmarks.md +45 -0
- cmakeless-0.5.0/docs/index.md +58 -0
- cmakeless-0.5.0/examples/01_hello/cmakelessfile.py +11 -0
- cmakeless-0.5.0/examples/01_hello/src/main.cpp +14 -0
- cmakeless-0.5.0/examples/02_library/cmakelessfile.py +28 -0
- cmakeless-0.5.0/examples/02_library/include/greeter.hpp +11 -0
- cmakeless-0.5.0/examples/02_library/src/engine/greeter.cpp +15 -0
- cmakeless-0.5.0/examples/02_library/src/engine/internal/greeter_detail.hpp +10 -0
- cmakeless-0.5.0/examples/02_library/src/main.cpp +17 -0
- cmakeless-0.5.0/examples/03_subprojects/cmakelessfile.py +17 -0
- cmakeless-0.5.0/examples/03_subprojects/src/main.cpp +14 -0
- cmakeless-0.5.0/examples/03_subprojects/tools/asset_packer/cmakelessfile.py +11 -0
- cmakeless-0.5.0/examples/03_subprojects/tools/asset_packer/main.cpp +14 -0
- cmakeless-0.5.0/examples/04_dependencies/cmakeless.lock +18 -0
- cmakeless-0.5.0/examples/04_dependencies/cmakelessfile.py +19 -0
- cmakeless-0.5.0/examples/04_dependencies/src/main.cpp +13 -0
- cmakeless-0.5.0/examples/05_testing/cmakeless.lock +18 -0
- cmakeless-0.5.0/examples/05_testing/cmakelessfile.py +31 -0
- cmakeless-0.5.0/examples/05_testing/include/greeter.hpp +11 -0
- cmakeless-0.5.0/examples/05_testing/src/engine/greeter.cpp +12 -0
- cmakeless-0.5.0/examples/05_testing/tests/greeter_test.cpp +19 -0
- cmakeless-0.5.0/examples/06_ship/cmakelessfile.py +38 -0
- cmakeless-0.5.0/examples/06_ship/include/version_banner.hpp +11 -0
- cmakeless-0.5.0/examples/06_ship/src/engine/version_banner.cpp +12 -0
- cmakeless-0.5.0/examples/06_ship/src/main.cpp +16 -0
- cmakeless-0.5.0/examples/07_python_module/cmakeless.lock +18 -0
- cmakeless-0.5.0/examples/07_python_module/cmakelessfile.py +24 -0
- cmakeless-0.5.0/examples/07_python_module/src/geometry.cpp +111 -0
- cmakeless-0.5.0/examples/07_python_module/test_geometry.py +33 -0
- cmakeless-0.5.0/examples/08_capstone/README.md +57 -0
- cmakeless-0.5.0/examples/08_capstone/cmakeless.lock +39 -0
- cmakeless-0.5.0/examples/08_capstone/cmakelessfile.py +77 -0
- cmakeless-0.5.0/examples/08_capstone/include/stats/series.hpp +48 -0
- cmakeless-0.5.0/examples/08_capstone/src/bindings.cpp +37 -0
- cmakeless-0.5.0/examples/08_capstone/src/main.cpp +56 -0
- cmakeless-0.5.0/examples/08_capstone/src/series.cpp +86 -0
- cmakeless-0.5.0/examples/08_capstone/test_pystats.py +27 -0
- cmakeless-0.5.0/examples/08_capstone/tests/series_test.cpp +49 -0
- cmakeless-0.5.0/examples/09_build_language/cmakelessfile.py +36 -0
- cmakeless-0.5.0/examples/09_build_language/src/main.cpp +16 -0
- cmakeless-0.5.0/examples/09_build_language/src/version.hpp +3 -0
- cmakeless-0.5.0/examples/09_build_language/tools/gen_version.py +35 -0
- cmakeless-0.5.0/examples/README.md +24 -0
- cmakeless-0.5.0/pyproject.toml +107 -0
- cmakeless-0.5.0/src/cmakeless/__init__.py +70 -0
- cmakeless-0.5.0/src/cmakeless/__main__.py +10 -0
- cmakeless-0.5.0/src/cmakeless/_constants.py +7 -0
- cmakeless-0.5.0/src/cmakeless/_parallel.py +48 -0
- cmakeless-0.5.0/src/cmakeless/_version.py +7 -0
- cmakeless-0.5.0/src/cmakeless/api/__init__.py +52 -0
- cmakeless-0.5.0/src/cmakeless/api/_context.py +246 -0
- cmakeless-0.5.0/src/cmakeless/api/commands.py +167 -0
- cmakeless-0.5.0/src/cmakeless/api/dependencies.py +268 -0
- cmakeless-0.5.0/src/cmakeless/api/options.py +102 -0
- cmakeless-0.5.0/src/cmakeless/api/presets.py +101 -0
- cmakeless-0.5.0/src/cmakeless/api/project.py +1143 -0
- cmakeless-0.5.0/src/cmakeless/api/targets.py +740 -0
- cmakeless-0.5.0/src/cmakeless/api/toolchains.py +99 -0
- cmakeless-0.5.0/src/cmakeless/api/when.py +234 -0
- cmakeless-0.5.0/src/cmakeless/cli.py +266 -0
- cmakeless-0.5.0/src/cmakeless/deps/__init__.py +30 -0
- cmakeless-0.5.0/src/cmakeless/deps/conan.py +153 -0
- cmakeless-0.5.0/src/cmakeless/deps/fetchcontent.py +177 -0
- cmakeless-0.5.0/src/cmakeless/deps/find_package.py +144 -0
- cmakeless-0.5.0/src/cmakeless/deps/lockfile.py +156 -0
- cmakeless-0.5.0/src/cmakeless/deps/provider.py +137 -0
- cmakeless-0.5.0/src/cmakeless/deps/registry.py +210 -0
- cmakeless-0.5.0/src/cmakeless/deps/resolver.py +160 -0
- cmakeless-0.5.0/src/cmakeless/deps/vcpkg.py +210 -0
- cmakeless-0.5.0/src/cmakeless/driver/__init__.py +14 -0
- cmakeless-0.5.0/src/cmakeless/driver/cmake_driver.py +289 -0
- cmakeless-0.5.0/src/cmakeless/driver/error_translation.py +148 -0
- cmakeless-0.5.0/src/cmakeless/driver/file_api.py +121 -0
- cmakeless-0.5.0/src/cmakeless/driver/generators.py +159 -0
- cmakeless-0.5.0/src/cmakeless/emitter/__init__.py +14 -0
- cmakeless-0.5.0/src/cmakeless/emitter/cmake_emitter.py +1270 -0
- cmakeless-0.5.0/src/cmakeless/emitter/presets_emitter.py +132 -0
- cmakeless-0.5.0/src/cmakeless/emitter/sanitizers.py +102 -0
- cmakeless-0.5.0/src/cmakeless/emitter/toolchain_emitter.py +50 -0
- cmakeless-0.5.0/src/cmakeless/emitter/when_emitter.py +75 -0
- cmakeless-0.5.0/src/cmakeless/errors.py +96 -0
- cmakeless-0.5.0/src/cmakeless/model/__init__.py +29 -0
- cmakeless-0.5.0/src/cmakeless/model/nodes.py +659 -0
- cmakeless-0.5.0/src/cmakeless/model/validate.py +1166 -0
- cmakeless-0.5.0/src/cmakeless/observer.py +114 -0
- cmakeless-0.5.0/src/cmakeless/py.typed +0 -0
- cmakeless-0.5.0/tests/unittests/api/test_add_test.py +97 -0
- cmakeless-0.5.0/tests/unittests/api/test_commands.py +163 -0
- cmakeless-0.5.0/tests/unittests/api/test_depends.py +145 -0
- cmakeless-0.5.0/tests/unittests/api/test_install_package.py +81 -0
- cmakeless-0.5.0/tests/unittests/api/test_library.py +166 -0
- cmakeless-0.5.0/tests/unittests/api/test_observer.py +95 -0
- cmakeless-0.5.0/tests/unittests/api/test_options.py +93 -0
- cmakeless-0.5.0/tests/unittests/api/test_presets.py +131 -0
- cmakeless-0.5.0/tests/unittests/api/test_project.py +160 -0
- cmakeless-0.5.0/tests/unittests/api/test_python_module.py +124 -0
- cmakeless-0.5.0/tests/unittests/api/test_subprojects.py +129 -0
- cmakeless-0.5.0/tests/unittests/api/test_target_vocab.py +167 -0
- cmakeless-0.5.0/tests/unittests/api/test_toolchains.py +72 -0
- cmakeless-0.5.0/tests/unittests/api/test_when.py +102 -0
- cmakeless-0.5.0/tests/unittests/conftest.py +20 -0
- cmakeless-0.5.0/tests/unittests/deps/test_conan.py +137 -0
- cmakeless-0.5.0/tests/unittests/deps/test_fetchcontent.py +159 -0
- cmakeless-0.5.0/tests/unittests/deps/test_find_package.py +82 -0
- cmakeless-0.5.0/tests/unittests/deps/test_lockfile.py +96 -0
- cmakeless-0.5.0/tests/unittests/deps/test_registry.py +144 -0
- cmakeless-0.5.0/tests/unittests/deps/test_resolver.py +166 -0
- cmakeless-0.5.0/tests/unittests/deps/test_vcpkg.py +133 -0
- cmakeless-0.5.0/tests/unittests/driver/test_cmake_driver.py +268 -0
- cmakeless-0.5.0/tests/unittests/driver/test_error_translation.py +87 -0
- cmakeless-0.5.0/tests/unittests/driver/test_file_api.py +71 -0
- cmakeless-0.5.0/tests/unittests/driver/test_generators.py +105 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/custom_commands.cmake +34 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_conan.cmake +25 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_fallback.cmake +34 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_vcpkg.cmake +28 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/kitchen_sink.cmake +94 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/optimize_lto.cmake +29 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/options.cmake +14 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/pch_unity.cmake +25 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/python_module.cmake +51 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/raw_cmake.cmake +27 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/ship.cmake +164 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/ship_presets.json +69 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/ship_toolchain.cmake +10 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/target_vocab.cmake +40 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/tree_child.cmake +18 -0
- cmakeless-0.5.0/tests/unittests/emitter/golden/tree_parent.cmake +20 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_cmake_emitter.py +86 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_commands_emitter.py +140 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_options_emitter.py +95 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_pch_unity_emitter.py +72 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_phase1_emitter.py +221 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_phase2_emitter.py +155 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_phase3_emitter.py +372 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_phase4_emitter.py +132 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_phase5_emitter.py +132 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_target_vocab_emitter.py +88 -0
- cmakeless-0.5.0/tests/unittests/emitter/test_when_emitter.py +95 -0
- cmakeless-0.5.0/tests/unittests/model/test_nodes.py +61 -0
- cmakeless-0.5.0/tests/unittests/model/test_validate.py +280 -0
- cmakeless-0.5.0/tests/unittests/model/test_when_model.py +41 -0
- cmakeless-0.5.0/tests/unittests/test_cli.py +68 -0
- cmakeless-0.5.0/tests/unittests/test_cli_verbs.py +195 -0
- cmakeless-0.5.0/tests/unittests/test_code_standards.py +86 -0
- cmakeless-0.5.0/tests/unittests/test_end_to_end.py +263 -0
- cmakeless-0.5.0/tests/unittests/test_end_to_end_deps.py +92 -0
- cmakeless-0.5.0/tests/unittests/test_end_to_end_phase4.py +90 -0
- cmakeless-0.5.0/tests/unittests/test_parallel.py +39 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, bbs-dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: ${{ matrix.os }} / py${{ matrix.python-version }}
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
16
|
+
python-version: ["3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install Ninja
|
|
26
|
+
uses: seanmiddleditch/gha-setup-ninja@v5
|
|
27
|
+
|
|
28
|
+
# Ninja needs the MSVC environment on Windows to find cl.exe.
|
|
29
|
+
- name: Set up MSVC environment
|
|
30
|
+
if: runner.os == 'Windows'
|
|
31
|
+
uses: ilammy/msvc-dev-cmd@v1
|
|
32
|
+
|
|
33
|
+
# setup-uv already provisions and activates .venv when python-version
|
|
34
|
+
# is set, so installing into it directly is all that is needed.
|
|
35
|
+
- name: Install package with dev dependencies
|
|
36
|
+
run: uv pip install -e ".[dev]"
|
|
37
|
+
|
|
38
|
+
- name: Lint
|
|
39
|
+
run: |
|
|
40
|
+
uv run ruff check .
|
|
41
|
+
uv run ruff format --check .
|
|
42
|
+
|
|
43
|
+
- name: Type check
|
|
44
|
+
run: uv run mypy src
|
|
45
|
+
|
|
46
|
+
# The network flag unskips the end-to-end dependency tests, which
|
|
47
|
+
# download and compile a real third-party package (fmt).
|
|
48
|
+
- name: Test
|
|
49
|
+
run: uv run pytest
|
|
50
|
+
env:
|
|
51
|
+
CMAKELESS_NETWORK_TESTS: "1"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# PyPI via Trusted Publishing (OIDC)
|
|
4
|
+
# And GitHub Release with changelog section for this version.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distributions
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.13"
|
|
21
|
+
|
|
22
|
+
# The version is hand-maintained in _version.py, so the pushed tag must
|
|
23
|
+
# match it; a mismatch means a release was tagged without a version bump.
|
|
24
|
+
# Read it with sed so the check needs no environment sync.
|
|
25
|
+
- name: Verify tag matches the package version
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
version="$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/cmakeless/_version.py)"
|
|
29
|
+
if [ "$tag" != "$version" ]; then
|
|
30
|
+
echo "::error::tag v$tag does not match cmakeless._version.__version__ ($version)."
|
|
31
|
+
echo "Bump src/cmakeless/_version.py to $tag, or retag to v$version."
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
echo "tag v$tag matches the package version."
|
|
35
|
+
|
|
36
|
+
- name: Build sdist and wheel
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: Check the built distributions
|
|
40
|
+
run: uvx twine check dist/*
|
|
41
|
+
|
|
42
|
+
- name: Upload distributions
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
pypi-publish:
|
|
49
|
+
name: Publish to PyPI
|
|
50
|
+
needs: build
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
environment:
|
|
53
|
+
name: pypi
|
|
54
|
+
url: https://pypi.org/project/cmakeless/
|
|
55
|
+
permissions:
|
|
56
|
+
id-token: write
|
|
57
|
+
steps:
|
|
58
|
+
- name: Download distributions
|
|
59
|
+
uses: actions/download-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
- name: Publish to PyPI
|
|
65
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
66
|
+
|
|
67
|
+
github-release:
|
|
68
|
+
name: Create the GitHub Release
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
permissions:
|
|
72
|
+
contents: write
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
|
|
76
|
+
- name: Download distributions
|
|
77
|
+
uses: actions/download-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: dist
|
|
80
|
+
path: dist/
|
|
81
|
+
|
|
82
|
+
- name: Extract the changelog entry for this tag
|
|
83
|
+
run: |
|
|
84
|
+
version="${GITHUB_REF_NAME#v}"
|
|
85
|
+
awk -v v="$version" '
|
|
86
|
+
$0 == "## [" v "]" {capture=1; next}
|
|
87
|
+
capture && /^## / {exit}
|
|
88
|
+
capture {print}
|
|
89
|
+
' CHANGELOG.md > release-notes.md
|
|
90
|
+
if [ ! -s release-notes.md ]; then
|
|
91
|
+
echo "No changelog section for $version; using a generic note." > release-notes.md
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
- name: Create the release
|
|
95
|
+
uses: softprops/action-gh-release@v2
|
|
96
|
+
with:
|
|
97
|
+
body_path: release-notes.md
|
|
98
|
+
files: dist/*
|
|
99
|
+
fail_on_unmatched_files: true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
PROMPTS.md
|
|
2
|
+
|
|
3
|
+
# Python
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
uv.lock
|
|
13
|
+
|
|
14
|
+
# CMake build trees (out-of-source, always)
|
|
15
|
+
build/
|
|
16
|
+
CMakeUserPresets.json
|
|
17
|
+
|
|
18
|
+
# Generated output inside example projects; regenerated from cmakelessfile.py.
|
|
19
|
+
examples/**/CMakeLists.txt
|
|
20
|
+
examples/**/CMakePresets.json
|
|
21
|
+
examples/**/cmake/
|
|
22
|
+
examples/**/compile_commands.json
|
|
23
|
+
IMPROVEMENTS.md
|
|
24
|
+
SUGGESTIONS.md
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# CMakeless Architecture
|
|
2
|
+
|
|
3
|
+
This document describes how CMakeless is designed, how the codebase is organized, and why each decision was made. If [INTRODUCTION](INTRODUCTION.md) is the "why", this is the "how".
|
|
4
|
+
|
|
5
|
+
## Design Goals
|
|
6
|
+
|
|
7
|
+
Every architectural decision below serves four goals, in priority order:
|
|
8
|
+
|
|
9
|
+
1. **A tiny, obvious public API.** The end user sees a handful of classes. Everything else is private machinery.
|
|
10
|
+
2. **Fail early, fail in Python.** Every error that can be caught before CMake runs must be caught before CMake runs, and reported as a normal Python exception with a helpful message.
|
|
11
|
+
3. **Readable output.** The generated `CMakeLists.txt` must look like an expert wrote it by hand: modern, target-centric, deterministic, diffable. Users must be able to walk away from CMakeless at any time and keep their build.
|
|
12
|
+
4. **Delegate, never reimplement.** CMake does the real work: configuring, generating, building. CMakeless is a frontend, not a build system.
|
|
13
|
+
|
|
14
|
+
## The Big Picture: Four Layers
|
|
15
|
+
|
|
16
|
+
CMakeless is a strict layered architecture. Each layer depends only on the layer directly below it. Data flows down; results and errors flow back up.
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
+--------------------------------------------------------------+
|
|
20
|
+
| 1. API layer cmakeless/api/ |
|
|
21
|
+
| What the user touches: Project, Executable, Library, |
|
|
22
|
+
| Dependency, Toolchain, Preset. Friendly, forgiving, |
|
|
23
|
+
| mutable while the user is describing the build. |
|
|
24
|
+
+--------------------------------------------------------------+
|
|
25
|
+
| freeze() + validate
|
|
26
|
+
v
|
|
27
|
+
+--------------------------------------------------------------+
|
|
28
|
+
| 2. Model layer cmakeless/model/ |
|
|
29
|
+
| The single source of truth: an immutable, validated |
|
|
30
|
+
| build graph made of frozen dataclasses. No CMake |
|
|
31
|
+
| knowledge, no subprocess calls, pure data. |
|
|
32
|
+
+--------------------------------------------------------------+
|
|
33
|
+
| visit
|
|
34
|
+
v
|
|
35
|
+
+--------------------------------------------------------------+
|
|
36
|
+
| 3. Emitter layer cmakeless/emitter/ |
|
|
37
|
+
| Walks the model and generates idiomatic, modern |
|
|
38
|
+
| CMakeLists.txt, preset files, and toolchain files. |
|
|
39
|
+
| Deterministic: same model in, same bytes out. |
|
|
40
|
+
+--------------------------------------------------------------+
|
|
41
|
+
| invoke
|
|
42
|
+
v
|
|
43
|
+
+--------------------------------------------------------------+
|
|
44
|
+
| 4. Driver layer cmakeless/driver/ |
|
|
45
|
+
| Runs cmake / ctest / cpack as subprocesses, consumes |
|
|
46
|
+
| the CMake File API for structured results, and |
|
|
47
|
+
| translates CMake failures into Python exceptions. |
|
|
48
|
+
+--------------------------------------------------------------+
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Why layers, and why these layers
|
|
52
|
+
|
|
53
|
+
The separation between **API** and **model** exists because the two have opposite needs. While the user is describing a build, objects must be mutable and forgiving (`app.link(engine)` after construction). Once description ends, everything downstream wants immutability: the emitter can cache, the validator can reason about the whole graph at once, and parallel threads can share the model without locks. `Project.build()` is the boundary: it freezes the API objects into the model, validates, and only then proceeds.
|
|
54
|
+
|
|
55
|
+
The separation between **model** and **emitter** is what keeps us honest about goal 4. The model knows nothing about CMake syntax. This means the emitter is replaceable and testable in isolation (feed it a model, assert on the generated text), and it leaves the door open for other emitters later (for example, a compile_commands-only emitter for tooling) without touching the user-facing API.
|
|
56
|
+
|
|
57
|
+
The separation between **emitter** and **driver** means generation never requires CMake to be installed. You can generate, inspect, and commit build files on a machine that has never seen a compiler. Only `build()`, `configure()`, and `test()` need the real tool.
|
|
58
|
+
|
|
59
|
+
## The Public API
|
|
60
|
+
|
|
61
|
+
The entire user-facing surface is intentionally small. This is the complete class list, and the intent is that it stays close to this size forever:
|
|
62
|
+
|
|
63
|
+
| Class | Role |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `Project` | The root object and facade. Owns targets, settings, and the `build()` / `configure()` / `test()` / `install()` verbs. |
|
|
66
|
+
| `Executable` | A runnable target. Created via `project.add_executable(...)`. |
|
|
67
|
+
| `Library` | A static, shared, or header-only library. Created via `project.add_library(...)`. |
|
|
68
|
+
| `Dependency` | An external package requirement, usually created implicitly by `target.depends("fmt/10.2.1")`. |
|
|
69
|
+
| `Toolchain` | A compiler/platform description for cross or pinned builds. |
|
|
70
|
+
| `Preset` | A named bundle of configuration (build type, flags, toolchain) mapped onto CMake presets. |
|
|
71
|
+
|
|
72
|
+
Users never import from `cmakeless.model`, `cmakeless.emitter`, or `cmakeless.driver`. Those are implementation details, and the package layout enforces it: only names re-exported in `cmakeless/__init__.py` are public, and the package ships `py.typed` so every signature is checked by the user's IDE and type checker.
|
|
73
|
+
|
|
74
|
+
A deliberate non-feature: there is no CMakeless DSL, no YAML dialect, no magic globals. A `cmakelessfile.py` is a plain Python script that happens to import `cmakeless`. Anything Python can do (conditionals, loops, functions, reading environment variables, importing your own helper modules), your build description can do, with zero new semantics to learn.
|
|
75
|
+
|
|
76
|
+
## Entry Points: `cmakelessfile.py` and the CLI
|
|
77
|
+
|
|
78
|
+
The user's build description lives in **`cmakelessfile.py`** at the project root. This is a convention, not a requirement, chosen deliberately over a single-module design ("just one `cmakeless.py` file") for three reasons:
|
|
79
|
+
|
|
80
|
+
1. `cmakelessfile.py` names the user's *intent* (this file builds the project), the way `conanfile.py` and `noxfile.py` do, while `cmakeless.py` would name our library and shadow the actual `cmakeless` package on the import path, a classic Python footgun.
|
|
81
|
+
2. The library itself must be a package, not a module, because the four layers above need separate, privately importable subpackages to grow without breaking users.
|
|
82
|
+
3. A predictable filename lets the CLI find the build description with zero configuration.
|
|
83
|
+
|
|
84
|
+
Both invocation styles are first-class:
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
$ python cmakelessfile.py # the script is the tool
|
|
88
|
+
$ cmakeless build # the CLI finds cmakelessfile.py and runs it
|
|
89
|
+
$ cmakeless configure --preset debug
|
|
90
|
+
$ cmakeless test
|
|
91
|
+
$ cmakeless clean
|
|
92
|
+
$ cmakeless init # scaffold a new project interactively
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `cmakeless` console script and `python -m cmakeless` share one implementation in `cmakeless/cli.py`.
|
|
96
|
+
|
|
97
|
+
## Repository Layout
|
|
98
|
+
|
|
99
|
+
Standard src-layout, so the installed package is what gets tested, not the working directory:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
cmakeless/
|
|
103
|
+
├── pyproject.toml # PEP 621 metadata; zero runtime dependencies
|
|
104
|
+
├── README.md
|
|
105
|
+
├── INTRODUCTION.md
|
|
106
|
+
├── ARCHITECTURE.md # this file
|
|
107
|
+
├── FEATURES.md
|
|
108
|
+
├── ROADMAP.md
|
|
109
|
+
├── CONTRIBUTING.md
|
|
110
|
+
├── CHANGELOG.md
|
|
111
|
+
├── src/
|
|
112
|
+
│ └── cmakeless/
|
|
113
|
+
│ ├── __init__.py # the ONLY public import surface
|
|
114
|
+
│ ├── py.typed
|
|
115
|
+
│ ├── cli.py
|
|
116
|
+
│ ├── errors.py # exception hierarchy (see below)
|
|
117
|
+
│ ├── api/ # layer 1: Project, targets, deps, toolchains
|
|
118
|
+
│ ├── model/ # layer 2: frozen dataclasses, validation
|
|
119
|
+
│ ├── emitter/ # layer 3: model -> CMakeLists.txt
|
|
120
|
+
│ ├── driver/ # layer 4: subprocess + CMake File API
|
|
121
|
+
│ └── deps/ # dependency-provider strategies (see below)
|
|
122
|
+
├── tests/
|
|
123
|
+
│ └── unittests/ # mirrors src/ structure; pytest
|
|
124
|
+
├── examples/ # runnable example projects, smallest first
|
|
125
|
+
└── docs/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`pyproject.toml` declares **zero runtime dependencies**. A tool whose reason to exist is reducing build friction cannot itself bring a dependency tree. The standard library is enough: `dataclasses` for the model, `subprocess` for the driver, `json` for the File API, `argparse` for the CLI.
|
|
129
|
+
|
|
130
|
+
## Design Patterns, Named
|
|
131
|
+
|
|
132
|
+
CMakeless uses classic, institutional patterns deliberately, so that any contributor can locate responsibility by name:
|
|
133
|
+
|
|
134
|
+
- **Facade.** `Project` is a facade over the entire pipeline. `project.build()` hides freeze, validate, emit, configure, and compile behind one verb. The library as a whole is a facade over CMake.
|
|
135
|
+
- **Builder.** The API layer is a builder for the model layer: users incrementally describe (`add_library`, `link`, `depends`), then `freeze()` produces the immutable product.
|
|
136
|
+
- **Visitor.** The emitter is a visitor over the build graph. Each node type (executable, library, test, install rule) has a visit method that contributes its section of the generated file. New node types plug in without modifying the traversal.
|
|
137
|
+
- **Strategy.** Anything with interchangeable backends is a strategy behind a small interface: CMake generators (Ninja, Visual Studio, Xcode), and above all **dependency providers**.
|
|
138
|
+
- **Adapter.** Each dependency provider in `cmakeless/deps/` (FetchContent, `find_package`, vcpkg, Conan) adapts a foreign tool to the single internal `DependencyProvider` interface, so `target.depends("fmt/10.2.1")` never changes when the backend does.
|
|
139
|
+
- **Composite.** A `Project` may contain subprojects; targets and subprojects form a tree that the emitter and validator traverse uniformly.
|
|
140
|
+
- **Template Method.** Target emission shares a fixed skeleton (declare, sources, properties, links, install) with per-target-type overrides, which is what keeps the generated CMake uniform and boring.
|
|
141
|
+
- **Observer.** The driver publishes progress events (configure started, target compiled, test finished) to subscribers, so the CLI progress display, IDE integrations, and CI log formatting are listeners, not special cases inside the driver.
|
|
142
|
+
|
|
143
|
+
## Error Handling: Errors Are a Feature
|
|
144
|
+
|
|
145
|
+
The single biggest quality-of-life difference over raw CMake is *when* and *how* things fail. The rules:
|
|
146
|
+
|
|
147
|
+
1. **Validate at freeze time.** Unknown source files, dependency cycles, linking a test-only target into a release binary, a typo in a C++ standard: all reported before CMake is ever invoked, with the offending `cmakelessfile.py` line in the traceback.
|
|
148
|
+
2. **Translate at run time.** When CMake or the compiler does fail, the driver parses the output and raises a structured exception instead of dumping a wall of text.
|
|
149
|
+
3. **One hierarchy.** Everything raised on purpose derives from `CmakelessError`:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
CmakelessError
|
|
153
|
+
├── ConfigurationError # invalid build description (caught at freeze)
|
|
154
|
+
├── DependencyError # package cannot be resolved/fetched
|
|
155
|
+
├── ToolchainError # compiler/toolchain missing or misconfigured
|
|
156
|
+
└── CMakeError # CMake itself failed; carries parsed stderr,
|
|
157
|
+
# the exact command line, and the log path
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Every message must say three things: what went wrong, where (file and target), and what to try next. A message that fails the "what to try next" test is a bug.
|
|
161
|
+
|
|
162
|
+
## Free-Threaded Python: Parallelism Where It Pays
|
|
163
|
+
|
|
164
|
+
CMakeless targets Python 3.13+ and is designed for the free-threaded interpreter (PEP 703, supported non-experimentally since 3.14). The immutable model layer is what makes this safe and cheap: frozen dataclasses can be shared across threads with no locks and no copies.
|
|
165
|
+
|
|
166
|
+
Parallelism is applied only where wall-clock time actually lives:
|
|
167
|
+
|
|
168
|
+
- **Dependency resolution.** Fetching and resolving N external packages is embarrassingly parallel network and disk I/O; each provider strategy runs in its own thread.
|
|
169
|
+
- **Multi-configuration emission.** Emitting Debug/Release/RelWithDebInfo trees, or several presets, are independent pure functions over the same frozen model.
|
|
170
|
+
- **Orchestration.** Driving multiple CMake configurations or test partitions concurrently, with the Observer events merged into one coherent progress stream.
|
|
171
|
+
|
|
172
|
+
On a standard GIL build everything still works; the executor degrades to interleaved I/O concurrency, which is where most of the benefit is anyway. Detection is a single startup check (`sys._is_gil_enabled()`), never a fork in the codebase.
|
|
173
|
+
|
|
174
|
+
The compile itself is *not* our parallelism: that belongs to Ninja and the compiler, and per goal 4 we delegate it.
|
|
175
|
+
|
|
176
|
+
## What the Emitter Must Guarantee
|
|
177
|
+
|
|
178
|
+
Because the generated `CMakeLists.txt` is our public face to the rest of the ecosystem, it has its own contract:
|
|
179
|
+
|
|
180
|
+
- **Modern, target-centric CMake only.** `target_include_directories`, `target_compile_features`, `target_link_libraries` with explicit visibility. Never directory-level globals, never `include_directories()`, never `file(GLOB)` at configure time (globs are expanded by CMakeless in Python, where they can be validated).
|
|
181
|
+
- **Deterministic.** Same model in, byte-identical output out. Sorted where order is arbitrary. This makes output committable and diffs reviewable.
|
|
182
|
+
- **Self-describing.** A generated header comment states the CMakeless version and the source `cmakelessfile.py`, so a reader landing in the file knows where the truth lives.
|
|
183
|
+
- **Standalone.** The output must build with plain `cmake` on a machine without Python. This is the no-lock-in promise made mechanical.
|
|
184
|
+
|
|
185
|
+
## Read Next
|
|
186
|
+
|
|
187
|
+
- [FEATURES](FEATURES.md): The complete feature surface built on this architecture.
|
|
188
|
+
- [ROADMAP](ROADMAP.md): The order in which these layers get built.
|
|
189
|
+
- [CONTRIBUTING](CONTRIBUTING.md): How to help build them.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to CMakeless are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.5.0]
|
|
11
|
+
|
|
12
|
+
The language unlock: options, typed conditions, and custom build steps —
|
|
13
|
+
the last things standing between "a nicer way to write the easy 90% of
|
|
14
|
+
CMake" and the full power of CMake, in Python, with types and a debugger.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Breaking: the default build description filename is now `cmakelessfile.py`**
|
|
19
|
+
(was `build.py`). `cmakeless.py` was considered and rejected: on a
|
|
20
|
+
case-insensitive filesystem (Windows, macOS) it would collide with the
|
|
21
|
+
installed `cmakeless` package, making `from cmakeless import Project`
|
|
22
|
+
inside the script resolve to itself. `cmakelessfile.py` names the tool the
|
|
23
|
+
way `Dockerfile`/`Jenkinsfile` do, with no such collision. Since the
|
|
24
|
+
project has not published a stable release yet, there is no migration
|
|
25
|
+
path: rename `build.py` to `cmakelessfile.py` in existing projects.
|
|
26
|
+
- **Breaking: the Python floor is now 3.12** (was 3.13). IMPROVEMENTS.md
|
|
27
|
+
originally suggested 3.10 for the widest possible reach, but the codebase
|
|
28
|
+
already uses PEP 695 syntax (`type X = ...` aliases, `def f[T](...)`
|
|
29
|
+
generics) introduced in 3.12; keeping the floor at 3.12 avoids rewriting
|
|
30
|
+
that syntax while still covering most current LTS/CI base images. CI now
|
|
31
|
+
tests 3.12 and 3.13 across all three OSes.
|
|
32
|
+
- **Breaking: `target.compile_options(..., when=...)` now accepts a `When`
|
|
33
|
+
condition** (see Added, below), or the pre-existing `"gcc|clang"`-style
|
|
34
|
+
compiler string, kept as sugar. The underlying model field is
|
|
35
|
+
`when: WhenModel | None`, replacing `compilers: tuple[str, ...]`.
|
|
36
|
+
- **Breaking: `DependencyProvider.pre_configure()`/`.toolchain_args()` now
|
|
37
|
+
require a `build_type` argument.** Fixes the Conan adapter silently
|
|
38
|
+
installing Release dependencies under a Debug preset (IMPROVEMENTS §2.2):
|
|
39
|
+
the active preset's (or `project.optimize`'s) build type now flows all the
|
|
40
|
+
way through to `conan install -s build_type=...`.
|
|
41
|
+
- ccache/sccache are now wired for Unix Makefiles and Ninja Multi-Config too,
|
|
42
|
+
not just Ninja (IMPROVEMENTS §2.6); new generator shorthands `"ninja-multi"`,
|
|
43
|
+
`"make"`, and `"xcode"` join `"ninja"` and `"vs"`.
|
|
44
|
+
- `find_package(Python ...)` now requests the interpreter actually running
|
|
45
|
+
CMakeless, not a hard-coded `3.13` (IMPROVEMENTS §2.5).
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **`When` conditions** (SUGGESTIONS §2.2): `When.platform(...)`,
|
|
50
|
+
`When.compiler(...)`, `When.config(...)`, and `When.option(...)`,
|
|
51
|
+
composable with `&`/`|`/`~`, rendered as CMake generator expressions.
|
|
52
|
+
Wired into `define()`, `compile_options()`, and the new `link_options()`.
|
|
53
|
+
- **Project options** (IMPROVEMENTS §2.3, SUGGESTIONS §2.1):
|
|
54
|
+
`project.option(name, default=..., help=..., type=...)` declares a typed
|
|
55
|
+
CMake cache variable, discoverable with the new `cmakeless options` verb
|
|
56
|
+
(lists every declared option without building anything) and usable in
|
|
57
|
+
`When.option(...)`.
|
|
58
|
+
- **Expanded presets**: `Preset(options={...}, env={...}, inherits="base")`
|
|
59
|
+
so a preset can set cache-variable overrides, an environment block, and
|
|
60
|
+
inherit from another preset (IMPROVEMENTS §2.3), validated the same way
|
|
61
|
+
toolchain references already are.
|
|
62
|
+
- **Custom build steps** (SUGGESTIONS §1): `project.add_command(output=[...],
|
|
63
|
+
command=[...], depends=[...], comment=...)` for code-generation and
|
|
64
|
+
asset-cooking steps, and `project.add_custom_target(name, command=[...],
|
|
65
|
+
depends=[...])` for always-run targets (lint, docs). A command's output
|
|
66
|
+
feeds a target's `add_sources()` directly; an output nothing consumes is a
|
|
67
|
+
soft freeze-time warning, not a build error.
|
|
68
|
+
- **Target vocabulary completion** (IMPROVEMENTS §2.4): `target.include_dirs(...)`
|
|
69
|
+
(private include directories on any target kind, closing the gap next to
|
|
70
|
+
`Library(public_headers=...)`), `target.link_options(...)` (mirrors
|
|
71
|
+
`compile_options()`), a per-target `cpp_std` override, and
|
|
72
|
+
`target.pch = [...]`/`target.unity = True` for precompiled headers and
|
|
73
|
+
unity builds — retiring the `raw_cmake()` workaround the escape hatch's
|
|
74
|
+
own docstring used to demonstrate.
|
|
75
|
+
- **An extensible dependency registry** (IMPROVEMENTS §2.1):
|
|
76
|
+
`cmakeless.register_dependency(name, RegistryEntry(...))` registers or
|
|
77
|
+
overrides one package, and installed plugin distributions can contribute
|
|
78
|
+
entries via the `"cmakeless.registry"` entry-point group. The curated list
|
|
79
|
+
itself stays the same eleven packages for now; growing it from vcpkg/Conan
|
|
80
|
+
metadata at scale is future work (see ROADMAP.md sub-phase 4.4).
|
|
81
|
+
|
|
82
|
+
### Docs
|
|
83
|
+
|
|
84
|
+
- `ROADMAP.md`'s Phase 4 is now followed by sub-phases 4.1–4.3 (this
|
|
85
|
+
release) and 4.4–4.6 (planned: the interop unlock, the portability
|
|
86
|
+
release, and documentation/quality debt — see ROADMAP.md for what each
|
|
87
|
+
covers).
|
|
88
|
+
- `FEATURES.md` and `README.md` updated throughout for the new surface.
|
|
89
|
+
|
|
90
|
+
## [0.4.1]
|
|
91
|
+
|
|
92
|
+
Sensible defaults, the escape hatch, and a release pipeline: the polish that
|
|
93
|
+
makes the interop and quality-of-life work land in a real project.
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
- **Default binding backend is now pybind11** (was nanobind):
|
|
98
|
+
`add_python_module(name, sources)` builds a pybind11 extension unless you pass
|
|
99
|
+
`binding="nanobind"`. This is a behavior change for callers that relied on the
|
|
100
|
+
implicit default.
|
|
101
|
+
- **Default test framework is now GoogleTest** (was Catch2):
|
|
102
|
+
`add_test(name, sources)` fetches and links GoogleTest unless you pass
|
|
103
|
+
`framework="catch2"` (or `"doctest"`/`"none"`). Also a behavior change for the
|
|
104
|
+
implicit default.
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
|
|
108
|
+
- **The escape hatch (FEATURES section 9), now implemented**:
|
|
109
|
+
`target.raw_cmake("...")` emits a verbatim CMake snippet after the target is
|
|
110
|
+
defined, and `project.raw_cmake_file("cmake/extra.cmake")` includes an existing
|
|
111
|
+
CMake file near the top of the generated `CMakeLists.txt`. Both are fenced with
|
|
112
|
+
a comment naming their `cmakelessfile.py` origin; `raw_cmake_file` paths are validated
|
|
113
|
+
to exist inside the project root at freeze time.
|
|
114
|
+
- **Project-level `optimize` and `lto`** (FEATURES section 3): `project.optimize =
|
|
115
|
+
"release"` and `project.lto = True` set the default (no-preset) build type and
|
|
116
|
+
interprocedural optimization. Both are emitted behind CMake guards, so an active
|
|
117
|
+
preset always wins.
|
|
118
|
+
- **A tag-driven release workflow** (`.github/workflows/release.yml`): pushing a
|
|
119
|
+
`v*` tag builds the sdist and wheel, publishes to PyPI via Trusted Publishing
|
|
120
|
+
(OIDC, no stored token), and cuts a GitHub Release from the matching changelog
|
|
121
|
+
section. The pushed tag is checked against `_version.py`.
|
|
122
|
+
|
|
123
|
+
### Docs and examples
|
|
124
|
+
|
|
125
|
+
- Rewrote `README.md` into a fuller, discoverable front door: the philosophy,
|
|
126
|
+
a concrete end-to-end workflow, a feature tour, and an ecosystem comparison.
|
|
127
|
+
- Reworked `examples/07_python_module` into a real pybind11 module (a `Vec2`
|
|
128
|
+
type with operators, properties, and C++-to-Python exception translation),
|
|
129
|
+
and added `examples/08_capstone`: one `cmakelessfile.py` shipping a library as a CLI,
|
|
130
|
+
a GoogleTest suite, and a pybind11 module, with presets, install/export, CPack,
|
|
131
|
+
and a live Observer.
|
|
132
|
+
- Corrected the project URLs to `github.com/bbalouki/cmakeless`.
|
|
133
|
+
|
|
134
|
+
## [0.4.0]
|
|
135
|
+
|
|
136
|
+
Interop and parallelism: the differentiators. A pybind11 project migrates its
|
|
137
|
+
binding build to one `add_python_module` call, and multi-preset configure runs
|
|
138
|
+
concurrently.
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- **Python and C++ interop**: `project.add_python_module(name, sources,
|
|
143
|
+
binding="nanobind")` builds a C++ extension with nanobind or pybind11. The
|
|
144
|
+
binding backend is acquired like any dependency (pinned in `cmakeless.lock`),
|
|
145
|
+
the module is built against the invoking interpreter's development headers,
|
|
146
|
+
`.pyi` stubs are generated for nanobind, and after `cmakeless build` the
|
|
147
|
+
module is copied into the current environment, so `import <name>` works
|
|
148
|
+
immediately.
|
|
149
|
+
- **Observer event API**: `project.add_observer(observer)` registers a
|
|
150
|
+
consumer that receives a `StepStarted`/`StepFinished`/`StepFailed` event for
|
|
151
|
+
every configure, build, test, install, and package step, so IDE extensions
|
|
152
|
+
and CI log formatters are listeners rather than special cases. The console
|
|
153
|
+
display is now just the default `ConsoleObserver`. `Observer`, `BuildEvent`,
|
|
154
|
+
and the event classes join the public API.
|
|
155
|
+
- **CMake File API**: `project.targets_info()` configures the build and returns
|
|
156
|
+
it as `TargetInfo` objects (name, type, artifacts, sources, dependencies),
|
|
157
|
+
read from CMake's File API rather than scraped from text.
|
|
158
|
+
- **Free-threaded parallelism, measured**: `project.configure_presets()`
|
|
159
|
+
configures every preset concurrently, each into its own build tree, over one
|
|
160
|
+
lock-free frozen model. A reproducible `benchmarks/` harness and published
|
|
161
|
+
numbers for parallel dependency resolution and multi-preset configure live in
|
|
162
|
+
[docs/benchmarks.md](docs/benchmarks.md).
|
|
163
|
+
- `PythonModule`, `Observer`, `BuildEvent`, `StepStarted`, `StepFinished`,
|
|
164
|
+
`StepFailed`, `ConsoleObserver`, and `TargetInfo` join the public API;
|
|
165
|
+
`cmakeless.api` now re-exports the full layer-1 surface. Registry entries for
|
|
166
|
+
pybind11 and nanobind.
|
|
167
|
+
- A new runnable project, `examples/07_python_module`.
|
|
168
|
+
|
|
169
|
+
## [0.3.0]
|
|
170
|
+
|
|
171
|
+
Quality of life: everything that turns "it builds" into "it ships". A
|
|
172
|
+
library author can build, test (sanitized), install, and package a release
|
|
173
|
+
on CI using only `cmakelessfile.py`.
|
|
174
|
+
|
|
175
|
+
### Added
|
|
176
|
+
|
|
177
|
+
- **Testing as a verb**: `project.add_test(name, sources, framework=...)`
|
|
178
|
+
with Catch2, GoogleTest, and doctest auto-integration (the framework is
|
|
179
|
+
acquired like any dependency and pinned in `cmakeless.lock`), CTest
|
|
180
|
+
registration with per-case discovery, and shared-library tests that run
|
|
181
|
+
on Windows without PATH rituals. `cmakeless test` builds and runs the
|
|
182
|
+
suite; `framework="none"` registers a plain pass/fail executable.
|
|
183
|
+
- **Sanitizers**: `target.sanitize = ["address", "undefined"]` applied to
|
|
184
|
+
compile *and* link (the half-applied-sanitizer bug is not reproducible
|
|
185
|
+
through this API), translated per compiler, and rejected loudly where
|
|
186
|
+
unsupported. `cmakeless test --sanitize=address` runs the suite under a
|
|
187
|
+
sanitizer in its own build tree.
|
|
188
|
+
- **`Preset` API**: `project.add_preset(Preset("release",
|
|
189
|
+
optimize="release", lto=True))` generates `CMakePresets.json` (with
|
|
190
|
+
build and test presets), so CLion, Visual Studio, and VS Code pick the
|
|
191
|
+
configurations up natively. Every preset gets its own out-of-source
|
|
192
|
+
build tree under `build/<name>`; `--preset` works on build, configure,
|
|
193
|
+
test, install, and package.
|
|
194
|
+
- **`Toolchain` API**: `Toolchain.from_file(...)` wraps existing toolchain
|
|
195
|
+
files unchanged; `Toolchain(name, compiler=..., system_name=...)`
|
|
196
|
+
generates simple cross-compilation files under `cmake/toolchains/`.
|
|
197
|
+
Presets reference toolchains by name.
|
|
198
|
+
- **Install and packaging**: `project.install(target, headers=True)` emits
|
|
199
|
+
GNUInstallDirs-correct install rules, export sets, and
|
|
200
|
+
`Config.cmake`/`ConfigVersion.cmake` generation so other CMake users can
|
|
201
|
+
`find_package()` the result; `project.package(formats=["zip", "deb"])`
|
|
202
|
+
configures CPack. New `cmakeless install --prefix ...` and `cmakeless
|
|
203
|
+
package` verbs.
|
|
204
|
+
- **Tooling by default**: `compile_commands.json` is always exported and
|
|
205
|
+
copied to the project root; ccache/sccache are auto-detected and wired
|
|
206
|
+
as the compiler launcher on Ninja builds (opt out with `project.cache =
|
|
207
|
+
False`).
|
|
208
|
+
- `Preset`, `Toolchain`, and `Test` join the public API. Curated registry
|
|
209
|
+
pins for the default test framework versions (catch2 3.5.4, googletest
|
|
210
|
+
1.14.0, doctest 2.4.11), so test projects resolve without network.
|
|
211
|
+
- Two new runnable projects: `examples/05_testing` and `examples/06_ship`.
|
|
212
|
+
|
|
213
|
+
## [0.2.0]
|
|
214
|
+
|
|
215
|
+
Dependencies: `app.depends("fmt/10.2.1")` works through four backends, and
|
|
216
|
+
resolution is reproducible from the lockfile alone.
|
|
217
|
+
|
|
218
|
+
### Added
|
|
219
|
+
|
|
220
|
+
- **`target.depends("name/version")`**: one line per external package, with
|
|
221
|
+
`components=` for packages like boost and `public=` visibility. Escape
|
|
222
|
+
hatches (`url=`, `sha256=`, `cmake_name=`, `targets=`) make any package
|
|
223
|
+
usable, not just the built-in registry's.
|
|
224
|
+
- **Built-in package registry**: the folklore raw CMake makes you memorize
|
|
225
|
+
(`fmt::fmt` is not `fmt`, googletest is `GTest`, the vcpkg port of
|
|
226
|
+
nlohmann_json is `nlohmann-json`) curated for fmt, spdlog, catch2,
|
|
227
|
+
doctest, googletest, nlohmann_json, zlib, and boost.
|
|
228
|
+
- **The default acquisition strategy**: emitted CMake tries `find_package`
|
|
229
|
+
first and falls back to `FetchContent` pinned by URL and SHA256, so the
|
|
230
|
+
generated file stays standalone.
|
|
231
|
+
- **`cmakeless.lock`**: byte-deterministic JSON lockfile written on every
|
|
232
|
+
resolution; a complete lockfile resolves with zero network. Refresh it
|
|
233
|
+
with `project.dependencies.lock()` or the new `cmakeless lock` verb.
|
|
234
|
+
- **`find_package` backend** (`project.package_manager = "find_package"`):
|
|
235
|
+
system packages only, version-checked, no source fetches.
|
|
236
|
+
- **vcpkg backend** (`project.package_manager = "vcpkg"`): generates
|
|
237
|
+
`vcpkg.json` (with `builtin-baseline` and version constraints when
|
|
238
|
+
available) and wires the vcpkg toolchain file into the configure step.
|
|
239
|
+
- **Conan 2 backend** (`project.package_manager = "conan"`): generates
|
|
240
|
+
`conanfile.txt`, runs `conan install` before configure, and wires the
|
|
241
|
+
generated toolchain file.
|
|
242
|
+
- **Parallel resolution**: dependencies resolve in one thread each,
|
|
243
|
+
correct on GIL builds and fast on free-threaded ones; completion order
|
|
244
|
+
never leaks into the emitted files or the lockfile.
|
|
245
|
+
- `Dependency` joins the public API; `DependencyError` is now raised.
|
|
246
|
+
- A fourth runnable project, `examples/04_dependencies`.
|
|
247
|
+
|
|
248
|
+
## [0.1.0]
|
|
249
|
+
|
|
250
|
+
The MVP: a small self-contained C++ project can use CMakeless instead of
|
|
251
|
+
hand-written CMake.
|
|
252
|
+
|
|
253
|
+
### Added
|
|
254
|
+
|
|
255
|
+
- **Full target set**: `Library` targets ("static", "shared", "header_only")
|
|
256
|
+
next to `Executable`, with public header directories, correct
|
|
257
|
+
`PUBLIC`/`PRIVATE`/`INTERFACE` visibility, position-independent code, and
|
|
258
|
+
Windows export handling via `generate_export_header` for shared libraries.
|
|
259
|
+
- **The link graph**: `target.link(...)` with `public=` visibility on
|
|
260
|
+
library-to-library links, `INTERFACE` chosen automatically for header-only
|
|
261
|
+
libraries, and link-cycle detection at freeze time that names the cycle.
|
|
262
|
+
- **Compile settings**: `warnings` presets ("strict", "default", "none")
|
|
263
|
+
translated per compiler, `target.define(...)`, and
|
|
264
|
+
`target.compile_options(..., when="gcc|clang")` guards.
|
|
265
|
+
- **Glob sources**: patterns like `src/*.cpp` are expanded and validated in
|
|
266
|
+
Python; a pattern matching zero files is a `ConfigurationError`.
|
|
267
|
+
- **Subprojects**: `project.add_subproject(path)` composes self-contained
|
|
268
|
+
child projects (their own `cmakelessfile.py`); every generated file is standalone.
|
|
269
|
+
- **Driver**: generator selection ("ninja", "vs", or any raw CMake `-G`
|
|
270
|
+
name) and error translation that parses CMake, GCC/Clang, MSVC, and linker
|
|
271
|
+
failures into structured diagnostics on `CMakeError`.
|
|
272
|
+
- **Emitter contract**: byte-deterministic output enforced by golden-file
|
|
273
|
+
tests.
|
|
274
|
+
- **CLI**: `cmakeless configure`, `cmakeless clean`, and `cmakeless init`
|
|
275
|
+
scaffolding next to `cmakeless build`.
|
|
276
|
+
- Three runnable projects under `examples/`, smallest first, and a seeded
|
|
277
|
+
`docs/` landing page.
|
|
278
|
+
|
|
279
|
+
## [0.0.1]
|
|
280
|
+
|
|
281
|
+
Phase 0: the walking skeleton through all four layers.
|
|
282
|
+
|
|
283
|
+
### Added
|
|
284
|
+
|
|
285
|
+
- Walking skeleton through all four layers (API, model, emitter, driver).
|
|
286
|
+
- `Project` and `Executable` public API; `project.build()` freezes, validates,
|
|
287
|
+
emits `CMakeLists.txt`, and drives CMake configure + build.
|
|
288
|
+
- Freeze-time validation: missing source files are reported as
|
|
289
|
+
`ConfigurationError` before CMake ever runs.
|
|
290
|
+
- `cmakeless build` CLI and `python -m cmakeless`; `python cmakelessfile.py` works as
|
|
291
|
+
a first-class entry point.
|
|
292
|
+
- Exception hierarchy: `CmakelessError` with `ConfigurationError`,
|
|
293
|
+
`DependencyError`, `ToolchainError`, and `CMakeError`.
|