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.
Files changed (165) hide show
  1. cmakeless-0.5.0/.github/workflows/ci.yml +51 -0
  2. cmakeless-0.5.0/.github/workflows/release.yml +99 -0
  3. cmakeless-0.5.0/.gitignore +24 -0
  4. cmakeless-0.5.0/ARCHITECTURE.md +189 -0
  5. cmakeless-0.5.0/CHANGELOG.md +293 -0
  6. cmakeless-0.5.0/CLAUDE.md +112 -0
  7. cmakeless-0.5.0/CONTRIBUTING.md +82 -0
  8. cmakeless-0.5.0/FEATURES.md +319 -0
  9. cmakeless-0.5.0/INTRODUCTION.md +116 -0
  10. cmakeless-0.5.0/LICENSE +153 -0
  11. cmakeless-0.5.0/PKG-INFO +339 -0
  12. cmakeless-0.5.0/README.md +309 -0
  13. cmakeless-0.5.0/ROADMAP.md +173 -0
  14. cmakeless-0.5.0/benchmarks/README.md +42 -0
  15. cmakeless-0.5.0/benchmarks/bench_presets.py +130 -0
  16. cmakeless-0.5.0/benchmarks/bench_resolution.py +79 -0
  17. cmakeless-0.5.0/docs/benchmarks.md +45 -0
  18. cmakeless-0.5.0/docs/index.md +58 -0
  19. cmakeless-0.5.0/examples/01_hello/cmakelessfile.py +11 -0
  20. cmakeless-0.5.0/examples/01_hello/src/main.cpp +14 -0
  21. cmakeless-0.5.0/examples/02_library/cmakelessfile.py +28 -0
  22. cmakeless-0.5.0/examples/02_library/include/greeter.hpp +11 -0
  23. cmakeless-0.5.0/examples/02_library/src/engine/greeter.cpp +15 -0
  24. cmakeless-0.5.0/examples/02_library/src/engine/internal/greeter_detail.hpp +10 -0
  25. cmakeless-0.5.0/examples/02_library/src/main.cpp +17 -0
  26. cmakeless-0.5.0/examples/03_subprojects/cmakelessfile.py +17 -0
  27. cmakeless-0.5.0/examples/03_subprojects/src/main.cpp +14 -0
  28. cmakeless-0.5.0/examples/03_subprojects/tools/asset_packer/cmakelessfile.py +11 -0
  29. cmakeless-0.5.0/examples/03_subprojects/tools/asset_packer/main.cpp +14 -0
  30. cmakeless-0.5.0/examples/04_dependencies/cmakeless.lock +18 -0
  31. cmakeless-0.5.0/examples/04_dependencies/cmakelessfile.py +19 -0
  32. cmakeless-0.5.0/examples/04_dependencies/src/main.cpp +13 -0
  33. cmakeless-0.5.0/examples/05_testing/cmakeless.lock +18 -0
  34. cmakeless-0.5.0/examples/05_testing/cmakelessfile.py +31 -0
  35. cmakeless-0.5.0/examples/05_testing/include/greeter.hpp +11 -0
  36. cmakeless-0.5.0/examples/05_testing/src/engine/greeter.cpp +12 -0
  37. cmakeless-0.5.0/examples/05_testing/tests/greeter_test.cpp +19 -0
  38. cmakeless-0.5.0/examples/06_ship/cmakelessfile.py +38 -0
  39. cmakeless-0.5.0/examples/06_ship/include/version_banner.hpp +11 -0
  40. cmakeless-0.5.0/examples/06_ship/src/engine/version_banner.cpp +12 -0
  41. cmakeless-0.5.0/examples/06_ship/src/main.cpp +16 -0
  42. cmakeless-0.5.0/examples/07_python_module/cmakeless.lock +18 -0
  43. cmakeless-0.5.0/examples/07_python_module/cmakelessfile.py +24 -0
  44. cmakeless-0.5.0/examples/07_python_module/src/geometry.cpp +111 -0
  45. cmakeless-0.5.0/examples/07_python_module/test_geometry.py +33 -0
  46. cmakeless-0.5.0/examples/08_capstone/README.md +57 -0
  47. cmakeless-0.5.0/examples/08_capstone/cmakeless.lock +39 -0
  48. cmakeless-0.5.0/examples/08_capstone/cmakelessfile.py +77 -0
  49. cmakeless-0.5.0/examples/08_capstone/include/stats/series.hpp +48 -0
  50. cmakeless-0.5.0/examples/08_capstone/src/bindings.cpp +37 -0
  51. cmakeless-0.5.0/examples/08_capstone/src/main.cpp +56 -0
  52. cmakeless-0.5.0/examples/08_capstone/src/series.cpp +86 -0
  53. cmakeless-0.5.0/examples/08_capstone/test_pystats.py +27 -0
  54. cmakeless-0.5.0/examples/08_capstone/tests/series_test.cpp +49 -0
  55. cmakeless-0.5.0/examples/09_build_language/cmakelessfile.py +36 -0
  56. cmakeless-0.5.0/examples/09_build_language/src/main.cpp +16 -0
  57. cmakeless-0.5.0/examples/09_build_language/src/version.hpp +3 -0
  58. cmakeless-0.5.0/examples/09_build_language/tools/gen_version.py +35 -0
  59. cmakeless-0.5.0/examples/README.md +24 -0
  60. cmakeless-0.5.0/pyproject.toml +107 -0
  61. cmakeless-0.5.0/src/cmakeless/__init__.py +70 -0
  62. cmakeless-0.5.0/src/cmakeless/__main__.py +10 -0
  63. cmakeless-0.5.0/src/cmakeless/_constants.py +7 -0
  64. cmakeless-0.5.0/src/cmakeless/_parallel.py +48 -0
  65. cmakeless-0.5.0/src/cmakeless/_version.py +7 -0
  66. cmakeless-0.5.0/src/cmakeless/api/__init__.py +52 -0
  67. cmakeless-0.5.0/src/cmakeless/api/_context.py +246 -0
  68. cmakeless-0.5.0/src/cmakeless/api/commands.py +167 -0
  69. cmakeless-0.5.0/src/cmakeless/api/dependencies.py +268 -0
  70. cmakeless-0.5.0/src/cmakeless/api/options.py +102 -0
  71. cmakeless-0.5.0/src/cmakeless/api/presets.py +101 -0
  72. cmakeless-0.5.0/src/cmakeless/api/project.py +1143 -0
  73. cmakeless-0.5.0/src/cmakeless/api/targets.py +740 -0
  74. cmakeless-0.5.0/src/cmakeless/api/toolchains.py +99 -0
  75. cmakeless-0.5.0/src/cmakeless/api/when.py +234 -0
  76. cmakeless-0.5.0/src/cmakeless/cli.py +266 -0
  77. cmakeless-0.5.0/src/cmakeless/deps/__init__.py +30 -0
  78. cmakeless-0.5.0/src/cmakeless/deps/conan.py +153 -0
  79. cmakeless-0.5.0/src/cmakeless/deps/fetchcontent.py +177 -0
  80. cmakeless-0.5.0/src/cmakeless/deps/find_package.py +144 -0
  81. cmakeless-0.5.0/src/cmakeless/deps/lockfile.py +156 -0
  82. cmakeless-0.5.0/src/cmakeless/deps/provider.py +137 -0
  83. cmakeless-0.5.0/src/cmakeless/deps/registry.py +210 -0
  84. cmakeless-0.5.0/src/cmakeless/deps/resolver.py +160 -0
  85. cmakeless-0.5.0/src/cmakeless/deps/vcpkg.py +210 -0
  86. cmakeless-0.5.0/src/cmakeless/driver/__init__.py +14 -0
  87. cmakeless-0.5.0/src/cmakeless/driver/cmake_driver.py +289 -0
  88. cmakeless-0.5.0/src/cmakeless/driver/error_translation.py +148 -0
  89. cmakeless-0.5.0/src/cmakeless/driver/file_api.py +121 -0
  90. cmakeless-0.5.0/src/cmakeless/driver/generators.py +159 -0
  91. cmakeless-0.5.0/src/cmakeless/emitter/__init__.py +14 -0
  92. cmakeless-0.5.0/src/cmakeless/emitter/cmake_emitter.py +1270 -0
  93. cmakeless-0.5.0/src/cmakeless/emitter/presets_emitter.py +132 -0
  94. cmakeless-0.5.0/src/cmakeless/emitter/sanitizers.py +102 -0
  95. cmakeless-0.5.0/src/cmakeless/emitter/toolchain_emitter.py +50 -0
  96. cmakeless-0.5.0/src/cmakeless/emitter/when_emitter.py +75 -0
  97. cmakeless-0.5.0/src/cmakeless/errors.py +96 -0
  98. cmakeless-0.5.0/src/cmakeless/model/__init__.py +29 -0
  99. cmakeless-0.5.0/src/cmakeless/model/nodes.py +659 -0
  100. cmakeless-0.5.0/src/cmakeless/model/validate.py +1166 -0
  101. cmakeless-0.5.0/src/cmakeless/observer.py +114 -0
  102. cmakeless-0.5.0/src/cmakeless/py.typed +0 -0
  103. cmakeless-0.5.0/tests/unittests/api/test_add_test.py +97 -0
  104. cmakeless-0.5.0/tests/unittests/api/test_commands.py +163 -0
  105. cmakeless-0.5.0/tests/unittests/api/test_depends.py +145 -0
  106. cmakeless-0.5.0/tests/unittests/api/test_install_package.py +81 -0
  107. cmakeless-0.5.0/tests/unittests/api/test_library.py +166 -0
  108. cmakeless-0.5.0/tests/unittests/api/test_observer.py +95 -0
  109. cmakeless-0.5.0/tests/unittests/api/test_options.py +93 -0
  110. cmakeless-0.5.0/tests/unittests/api/test_presets.py +131 -0
  111. cmakeless-0.5.0/tests/unittests/api/test_project.py +160 -0
  112. cmakeless-0.5.0/tests/unittests/api/test_python_module.py +124 -0
  113. cmakeless-0.5.0/tests/unittests/api/test_subprojects.py +129 -0
  114. cmakeless-0.5.0/tests/unittests/api/test_target_vocab.py +167 -0
  115. cmakeless-0.5.0/tests/unittests/api/test_toolchains.py +72 -0
  116. cmakeless-0.5.0/tests/unittests/api/test_when.py +102 -0
  117. cmakeless-0.5.0/tests/unittests/conftest.py +20 -0
  118. cmakeless-0.5.0/tests/unittests/deps/test_conan.py +137 -0
  119. cmakeless-0.5.0/tests/unittests/deps/test_fetchcontent.py +159 -0
  120. cmakeless-0.5.0/tests/unittests/deps/test_find_package.py +82 -0
  121. cmakeless-0.5.0/tests/unittests/deps/test_lockfile.py +96 -0
  122. cmakeless-0.5.0/tests/unittests/deps/test_registry.py +144 -0
  123. cmakeless-0.5.0/tests/unittests/deps/test_resolver.py +166 -0
  124. cmakeless-0.5.0/tests/unittests/deps/test_vcpkg.py +133 -0
  125. cmakeless-0.5.0/tests/unittests/driver/test_cmake_driver.py +268 -0
  126. cmakeless-0.5.0/tests/unittests/driver/test_error_translation.py +87 -0
  127. cmakeless-0.5.0/tests/unittests/driver/test_file_api.py +71 -0
  128. cmakeless-0.5.0/tests/unittests/driver/test_generators.py +105 -0
  129. cmakeless-0.5.0/tests/unittests/emitter/golden/custom_commands.cmake +34 -0
  130. cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_conan.cmake +25 -0
  131. cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_fallback.cmake +34 -0
  132. cmakeless-0.5.0/tests/unittests/emitter/golden/dependencies_vcpkg.cmake +28 -0
  133. cmakeless-0.5.0/tests/unittests/emitter/golden/kitchen_sink.cmake +94 -0
  134. cmakeless-0.5.0/tests/unittests/emitter/golden/optimize_lto.cmake +29 -0
  135. cmakeless-0.5.0/tests/unittests/emitter/golden/options.cmake +14 -0
  136. cmakeless-0.5.0/tests/unittests/emitter/golden/pch_unity.cmake +25 -0
  137. cmakeless-0.5.0/tests/unittests/emitter/golden/python_module.cmake +51 -0
  138. cmakeless-0.5.0/tests/unittests/emitter/golden/raw_cmake.cmake +27 -0
  139. cmakeless-0.5.0/tests/unittests/emitter/golden/ship.cmake +164 -0
  140. cmakeless-0.5.0/tests/unittests/emitter/golden/ship_presets.json +69 -0
  141. cmakeless-0.5.0/tests/unittests/emitter/golden/ship_toolchain.cmake +10 -0
  142. cmakeless-0.5.0/tests/unittests/emitter/golden/target_vocab.cmake +40 -0
  143. cmakeless-0.5.0/tests/unittests/emitter/golden/tree_child.cmake +18 -0
  144. cmakeless-0.5.0/tests/unittests/emitter/golden/tree_parent.cmake +20 -0
  145. cmakeless-0.5.0/tests/unittests/emitter/test_cmake_emitter.py +86 -0
  146. cmakeless-0.5.0/tests/unittests/emitter/test_commands_emitter.py +140 -0
  147. cmakeless-0.5.0/tests/unittests/emitter/test_options_emitter.py +95 -0
  148. cmakeless-0.5.0/tests/unittests/emitter/test_pch_unity_emitter.py +72 -0
  149. cmakeless-0.5.0/tests/unittests/emitter/test_phase1_emitter.py +221 -0
  150. cmakeless-0.5.0/tests/unittests/emitter/test_phase2_emitter.py +155 -0
  151. cmakeless-0.5.0/tests/unittests/emitter/test_phase3_emitter.py +372 -0
  152. cmakeless-0.5.0/tests/unittests/emitter/test_phase4_emitter.py +132 -0
  153. cmakeless-0.5.0/tests/unittests/emitter/test_phase5_emitter.py +132 -0
  154. cmakeless-0.5.0/tests/unittests/emitter/test_target_vocab_emitter.py +88 -0
  155. cmakeless-0.5.0/tests/unittests/emitter/test_when_emitter.py +95 -0
  156. cmakeless-0.5.0/tests/unittests/model/test_nodes.py +61 -0
  157. cmakeless-0.5.0/tests/unittests/model/test_validate.py +280 -0
  158. cmakeless-0.5.0/tests/unittests/model/test_when_model.py +41 -0
  159. cmakeless-0.5.0/tests/unittests/test_cli.py +68 -0
  160. cmakeless-0.5.0/tests/unittests/test_cli_verbs.py +195 -0
  161. cmakeless-0.5.0/tests/unittests/test_code_standards.py +86 -0
  162. cmakeless-0.5.0/tests/unittests/test_end_to_end.py +263 -0
  163. cmakeless-0.5.0/tests/unittests/test_end_to_end_deps.py +92 -0
  164. cmakeless-0.5.0/tests/unittests/test_end_to_end_phase4.py +90 -0
  165. 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`.