nuwa-build 0.2.5__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 (31) hide show
  1. nuwa_build-0.2.5/LICENSE +21 -0
  2. nuwa_build-0.2.5/MANIFEST.in +5 -0
  3. nuwa_build-0.2.5/PKG-INFO +505 -0
  4. nuwa_build-0.2.5/README.md +476 -0
  5. nuwa_build-0.2.5/pyproject.toml +47 -0
  6. nuwa_build-0.2.5/setup.cfg +4 -0
  7. nuwa_build-0.2.5/src/nuwa_build/__init__.py +22 -0
  8. nuwa_build-0.2.5/src/nuwa_build/backend.py +440 -0
  9. nuwa_build-0.2.5/src/nuwa_build/cli.py +425 -0
  10. nuwa_build-0.2.5/src/nuwa_build/config.py +160 -0
  11. nuwa_build-0.2.5/src/nuwa_build/discovery.py +115 -0
  12. nuwa_build-0.2.5/src/nuwa_build/errors.py +217 -0
  13. nuwa_build-0.2.5/src/nuwa_build/stubs.py +129 -0
  14. nuwa_build-0.2.5/src/nuwa_build/templates.py +422 -0
  15. nuwa_build-0.2.5/src/nuwa_build/utils.py +236 -0
  16. nuwa_build-0.2.5/src/nuwa_build/wheel_utils.py +84 -0
  17. nuwa_build-0.2.5/src/nuwa_build.egg-info/PKG-INFO +505 -0
  18. nuwa_build-0.2.5/src/nuwa_build.egg-info/SOURCES.txt +29 -0
  19. nuwa_build-0.2.5/src/nuwa_build.egg-info/dependency_links.txt +1 -0
  20. nuwa_build-0.2.5/src/nuwa_build.egg-info/entry_points.txt +5 -0
  21. nuwa_build-0.2.5/src/nuwa_build.egg-info/requires.txt +13 -0
  22. nuwa_build-0.2.5/src/nuwa_build.egg-info/top_level.txt +1 -0
  23. nuwa_build-0.2.5/tests/conftest.py +151 -0
  24. nuwa_build-0.2.5/tests/fixtures/projects/multi_file/multi_file_test/__init__.py +5 -0
  25. nuwa_build-0.2.5/tests/fixtures/projects/simple/simple_test/__init__.py +5 -0
  26. nuwa_build-0.2.5/tests/fixtures/projects/with_errors/with_errors/__init__.py +5 -0
  27. nuwa_build-0.2.5/tests/integration/test_compilation.py +259 -0
  28. nuwa_build-0.2.5/tests/unit/test_config.py +238 -0
  29. nuwa_build-0.2.5/tests/unit/test_discovery.py +142 -0
  30. nuwa_build-0.2.5/tests/unit/test_errors.py +218 -0
  31. nuwa_build-0.2.5/tests/unit/test_utils.py +166 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Martin Eastwood
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ recursive-include src/nuwa_build *.py
5
+ recursive-include tests *.py
@@ -0,0 +1,505 @@
1
+ Metadata-Version: 2.4
2
+ Name: nuwa-build
3
+ Version: 0.2.5
4
+ Summary: Build and publish Nim extensions for Python with zero configuration.
5
+ Author-email: Martin Eastwood <martin.eastwood@gmx.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: tomli>=1.2.0; python_version < "3.11"
20
+ Requires-Dist: watchdog>=5.0.0
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=7.0.0; extra == "test"
23
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
26
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
27
+ Requires-Dist: pre-commit>=4.0.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Nuwa Build
31
+
32
+ Build Python extensions with Nim using zero-configuration tooling.
33
+
34
+ ## Status
35
+
36
+ 🚧 **Work In Progress:** This library is currently under active development. APIs may change, and things might break. Use at your own risk!
37
+
38
+ ## Features
39
+
40
+ - **Zero Configuration**: Works out of the box with sensible defaults
41
+ - **Multi-file Projects**: Compile multiple Nim files into a single Python extension
42
+ - **Flexible Configuration**: Configure via `pyproject.toml` or CLI arguments
43
+ - **PEP 517/660 Compatible**: Build wheels and source distributions
44
+ - **Editable Installs**: `pip install -e .` support for development
45
+ - **Watch Mode**: Auto-recompile on file changes with `nuwa watch`
46
+ - **Auto Dependencies**: Automatically install Nimble packages before build
47
+ - **Testing Support**: Includes pytest tests in project template
48
+ - **Validation**: Validates configuration and provides helpful error messages
49
+ - **Proper Platform Tags**: Generates correct wheel tags for your platform
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ # Install Nuwa
55
+ pip install nuwa-build
56
+
57
+ # Install nimpy (Nim-Python bridge)
58
+ nimble install nimpy
59
+ ```
60
+
61
+ **Requirements**:
62
+
63
+ - Python 3.9+
64
+ - Nim compiler (must be installed and available in your PATH)
65
+ - nimpy library (install via `nimble install nimpy`)
66
+
67
+ ## Quick Start
68
+
69
+ ### Create a New Project
70
+
71
+ ```bash
72
+ nuwa new my_project
73
+ cd my_project
74
+ ```
75
+
76
+ This creates:
77
+
78
+ ```
79
+ my_project/
80
+ ├── pyproject.toml # Python project config
81
+ ├── nim/ # Nim source files
82
+ │ ├── my_project_lib.nim # Main entry point (filename = module name)
83
+ │ └── helpers.nim # Additional modules
84
+ ├── my_project/ # Python package
85
+ │ ├── __init__.py # Package wrapper
86
+ │ └── my_project_lib.so # Compiled extension (generated)
87
+ ├── tests/ # Test files
88
+ │ └── test_my_project.py # Pytest tests
89
+ ├── example.py # Example/test file
90
+ └── README.md
91
+ ```
92
+
93
+ ### Build and Test
94
+
95
+ ```bash
96
+ # Compile debug build
97
+ nuwa develop
98
+
99
+ # Compile release build
100
+ nuwa develop --release
101
+
102
+ # Run example
103
+ python example.py
104
+
105
+ # Run tests (requires pytest)
106
+ pip install pytest
107
+ pytest
108
+ ```
109
+
110
+ **Note**: No `pip install -e .` needed due to flat project layout. You can run `python example.py` and `pytest` directly after compiling the default template project.
111
+
112
+ ### 🤖 AI-Assisted Development
113
+
114
+ This project includes a `skill.md` file designed to teach AI coding agents (Cursor, Antigravity, Copilot, etc.) how to work with Nuwa Build.
115
+
116
+ **Why use it?**
117
+
118
+ Standard LLMs often assume Python extensions require `setup.py` or `pip install -e .`. The `skill.md` file provides your agent with the correct context to:
119
+
120
+ - **Understand the flat layout:** Knows that `.so` files are generated directly in the package directory.
121
+ - **Use correct commands:** Enforces `nuwa develop` and `nuwa watch` instead of standard pip commands.
122
+ - **Write correct Nim:** Reminds the agent to use `include` for shared libraries and `{.nuwa_export.}` for bindings (which automatically generates type stubs).
123
+
124
+ **How to use:**
125
+
126
+ 1. **Claude Code:** Copy the `skill.md` file into `~/.claude/skills/nuwa-build/` for global use or `<workspace-root>/.claude/skills/nuwa-build/` for project-specific use.
127
+ 2. **Antigravity:** Copy the `skill.md` file into `~/.gemini/antigravity/skills/nuwa-build/` for global use or `<workspace-root>/.agent/skills/nuwa-build/` for project-specific use.
128
+ 3. **Cursor:** Copy the `skill.md` file into `~/.cursor/skills/nuwa-build/` for global use or `<workspace-root>/.cursor/skills/nuwa-build/` for project-specific use.
129
+ 4. **General Chat:** Upload or paste `skill.md` into your context window when starting a new session.
130
+
131
+ ### Watch Mode
132
+
133
+ For development, use watch mode to automatically recompile when you change Nim files:
134
+
135
+ ```bash
136
+ # Watch for changes and auto-recompile
137
+ nuwa watch
138
+
139
+ # Watch with tests after each compile
140
+ nuwa watch --run-tests
141
+
142
+ # Watch in release mode
143
+ nuwa watch --release
144
+ ```
145
+
146
+ ### Install and Distribute
147
+
148
+ ```bash
149
+ # Build a wheel
150
+ pip install . --no-build-isolation
151
+
152
+ # Build source distribution
153
+ python -m build
154
+ ```
155
+
156
+ ## Project Structure
157
+
158
+ Nuwa uses a simple flat layout for easy development:
159
+
160
+ ```
161
+ project/
162
+ ├── pyproject.toml # Configuration
163
+ ├── nim/ # Nim source files
164
+ │ ├── my_package_lib.nim # Main entry point (determines module name)
165
+ │ └── helpers.nim # Additional modules
166
+ ├── my_package/ # Python package
167
+ │ ├── __init__.py # Package wrapper (can add Python code)
168
+ │ └── my_package_lib.so # Compiled Nim extension (generated)
169
+ └── tests/
170
+ └── test_my_package.py # Pytest tests
171
+ ```
172
+
173
+ The compiled extension is named `{module_name}_lib.so` to avoid conflicts with the Python package. Your `__init__.py` imports from it and can add Python wrappers.
174
+
175
+ ## Configuration
176
+
177
+ ### pyproject.toml
178
+
179
+ Configure your project in the `[tool.nuwa]` section:
180
+
181
+ ```toml
182
+ [build-system]
183
+ requires = ["nuwa-build"]
184
+ build-backend = "nuwa_build"
185
+
186
+ [project]
187
+ name = "my-package"
188
+ version = "0.1.0"
189
+
190
+ [tool.nuwa]
191
+ # Nim source directory (default: "nim")
192
+ nim-source = "nim"
193
+
194
+ # Python module name (default: derived from project name)
195
+ module-name = "my_package"
196
+
197
+ # Internal library name (default: "{module_name}_lib")
198
+ lib-name = "my_package_lib"
199
+
200
+ # Entry point file (default: "{lib_name}.nim")
201
+ entry-point = "my_package_lib.nim"
202
+
203
+ # Output location: "auto", "src", or explicit path
204
+ output-location = "auto"
205
+
206
+ # Additional Nim compiler flags (optional)
207
+ nim-flags = []
208
+
209
+ # Nimble dependencies (auto-installed before build)
210
+ nimble-deps = ["nimpy", "cligen >= 1.0.0"]
211
+ ```
212
+
213
+ ### Configuration Options
214
+
215
+ | Option | Type | Default | Description |
216
+ | ----------------- | ------ | ------------------------- | -------------------------------------------------------------- |
217
+ | `nim-source` | string | `"nim"` | Directory containing Nim source files |
218
+ | `module-name` | string | Derived from project name | Python package name |
219
+ | `lib-name` | string | `{module_name}_lib` | Internal compiled extension name |
220
+ | `entry-point` | string | `{lib_name}.nim` | Main entry point file (relative to `nim-source`) |
221
+ | `output-location` | string | `"auto"` | Where to place compiled extension (`"auto"`, `"src"`, or path) |
222
+ | `nim-flags` | list | `[]` | Additional compiler flags |
223
+ | `nimble-deps` | list | `[]` | Nimble packages to auto-install before build |
224
+
225
+ **Note**: The entry point filename determines the Python module name of the compiled extension. If your entry point is `my_package_lib.nim`, the module will be importable as `my_package_lib`.
226
+
227
+ ## CLI Commands
228
+
229
+ ### `nuwa new <path>`
230
+
231
+ Create a new project scaffold:
232
+
233
+ ```bash
234
+ nuwa new my_project
235
+ nuwa new my_project --name custom-name
236
+ ```
237
+
238
+ ### `nuwa develop`
239
+
240
+ Compile the project in-place:
241
+
242
+ ```bash
243
+ # Debug build
244
+ nuwa develop
245
+
246
+ # Release build
247
+ nuwa develop --release
248
+
249
+ # Override configuration
250
+ nuwa develop --module-name my_module
251
+ nuwa develop --nim-source my_nim_dir
252
+ nuwa develop --entry-point main.nim
253
+ nuwa develop --output-dir build/
254
+ nuwa develop --nim-flag="-d:danger" --nim-flag="--opt:size"
255
+ ```
256
+
257
+ **Note**: After running `nuwa develop`, the compiled extension will be in `{module_name}/`. You can then run `python example.py` or `pytest` directly without any installation step.
258
+
259
+ ### `nuwa watch`
260
+
261
+ Watch for file changes and automatically recompile:
262
+
263
+ ```bash
264
+ # Watch for changes and auto-recompile
265
+ nuwa watch
266
+
267
+ # Watch with tests after each compile
268
+ nuwa watch --run-tests
269
+
270
+ # Watch in release mode
271
+ nuwa watch --release
272
+
273
+ # Override configuration
274
+ nuwa watch --module-name my_module
275
+ nuwa watch --nim-source my_nim_dir
276
+ ```
277
+
278
+ ### `nuwa clean`
279
+
280
+ Clean build artifacts and dependencies:
281
+
282
+ ```bash
283
+ # Clean everything (dependencies + artifacts)
284
+ nuwa clean
285
+
286
+ # Clean only dependencies (.nimble/ directory)
287
+ nuwa clean --deps
288
+
289
+ # Clean only build artifacts and cache
290
+ nuwa clean --artifacts
291
+ ```
292
+
293
+ **What gets cleaned:**
294
+
295
+ - `--deps`: Removes the `.nimble/` directory (local Nimble packages)
296
+ - `--artifacts`: Removes `nimcache/`, `build/`, `dist/` directories and compiled `.so`/`.pyd` files
297
+ - (no flags): Cleans both dependencies and artifacts
298
+
299
+ ## Entry Point Discovery
300
+
301
+ If `entry-point` is not specified, Nuwa will automatically discover the main entry point using this priority:
302
+
303
+ 1. Explicit `[tool.nuwa] entry-point` configuration
304
+ 2. `{module_name}_lib.nim` (matches the lib-name)
305
+ 3. `lib.nim` (fallback convention)
306
+ 4. First (and only) `.nim` file if only one exists
307
+ 5. Error if multiple files found and no clear entry point
308
+
309
+ ## Mixing Python and Nim
310
+
311
+ Your `__init__.py` can import from the compiled Nim extension and add Python wrappers:
312
+
313
+ ```python
314
+ # In my_package/__init__.py
315
+ from .my_package_lib import *
316
+
317
+ __version__ = "0.1.0"
318
+
319
+ # Example: Wrap Nim functions with Python code
320
+ def validate_dataframe(df, column_name):
321
+ """Extract pandas data and pass to Nim for zero-copy validation"""
322
+ import numpy as np
323
+ from ctypes import c_void_p
324
+
325
+ # Extract data as numpy array (zero-copy view)
326
+ data = df[column_name].to_numpy()
327
+
328
+ # Get pointer and pass to Nim for validation
329
+ result = validate_array_raw(
330
+ data.ctypes.data_as(c_void_p),
331
+ len(data)
332
+ )
333
+ return result
334
+ ```
335
+
336
+ This allows you to:
337
+
338
+ - Use Python to extract/prepare data (e.g., from pandas DataFrames)
339
+ - Pass pointers/arrays to Nim for zero-copy processing
340
+ - Return results back to Python for formatting
341
+
342
+ ## Multi-File Projects
343
+
344
+ Nim's module system handles dependencies automatically. Use `include` to add code from other Nim files:
345
+
346
+ **nim/my_package_lib.nim:**
347
+
348
+ ```nim
349
+ import nuwa_sdk # Provides nuwa_export for automatic type stub generation
350
+ include helpers # Include helpers.nim from same directory
351
+
352
+ proc greet(name: string): string {.nuwa_export.} =
353
+ return make_greeting(name)
354
+
355
+ proc add(a: int, b: int): int {.nuwa_export.} =
356
+ return a + b
357
+ ```
358
+
359
+ **nim/helpers.nim:**
360
+
361
+ ```nim
362
+ proc make_greeting(name: string): string =
363
+ return "Hello, " & name & "!"
364
+ ```
365
+
366
+ Compile `my_package_lib.nim` and both modules are included in the final `.so`/`.pyd` file.
367
+
368
+ **Important**: Use `include` (not `import`) when building shared libraries. The `include` directive literally includes the code at compile time, while `import` creates a separate module namespace.
369
+
370
+ ### Exporting Functions to Python
371
+
372
+ **You must add the `{.nuwa_export.}` pragma to any Nim procedure you want to access from Python.**
373
+
374
+ - ✅ **Exported**: `proc add(a: int, b: int): int {.nuwa_export.}` - Accessible from Python
375
+ - ❌ **Not exported**: `proc add(a: int, b: int): int` - Not accessible from Python
376
+
377
+ The `{.nuwa_export.}` pragma does two things:
378
+ 1. Makes the function callable from Python (via the underlying nimpy library)
379
+ 2. Generates type stub information for IDE autocomplete and type checking
380
+
381
+ **Common mistake**: Forgetting the pragma means your function won't be available in Python, even though it compiles successfully.
382
+
383
+ ## Output Location
384
+
385
+ The `output-location` setting controls where the compiled extension is placed:
386
+
387
+ - **`"auto"`** (default): Uses flat layout - places extension in `{module_name}/`
388
+
389
+ - **`"src"`**: Explicitly uses `src/{module_name}/` (for compatibility with old projects)
390
+
391
+ - **Explicit path**: Use a custom output directory
392
+
393
+ ## Python Usage
394
+
395
+ Once compiled and installed, use your Nim extension like any Python module:
396
+
397
+ ```python
398
+ import my_package
399
+
400
+ # Call Nim-compiled functions (imported via __init__.py)
401
+ result = my_package.greet("World")
402
+ print(result) # "Hello, World!"
403
+
404
+ sum_result = my_package.add(5, 10)
405
+ print(sum_result) # 15
406
+ ```
407
+
408
+ ## How It Works
409
+
410
+ 1. **Validation**: Checks Nim compiler is installed and config is valid
411
+ 2. **Source Discovery**: Finds Nim files in the configured directory
412
+ 3. **Entry Point Detection**: Identifies the main entry point file
413
+ 4. **Compilation**: Invokes `nim c --app:lib` with appropriate flags
414
+ 5. **Module Path**: Adds `--path:{nim_dir}` so includes work between files
415
+ 6. **Output**: Generates proper `{lib_name}.so` (Linux/Mac) or `{lib_name}.pyd` (Windows) in the module directory
416
+ 7. **Ready to use**: Module is immediately importable from the project root
417
+
418
+ ### Contributing
419
+
420
+ Contributions are welcome! The codebase is well-organized with:
421
+
422
+ - Full type hints
423
+ - Comprehensive error handling
424
+ - Proper logging support
425
+ - Context managers for resource management
426
+
427
+ ## Troubleshooting
428
+
429
+ ### "Nim compiler not found"
430
+
431
+ Make sure Nim is installed and in your PATH:
432
+
433
+ ```bash
434
+ nim --version
435
+ ```
436
+
437
+ Install from https://nim-lang.org/install.html if needed.
438
+
439
+ ### "cannot open file: nimpy"
440
+
441
+ You need to install the nimpy library. You can either:
442
+
443
+ **Option 1: Auto-install via configuration** (Recommended)
444
+
445
+ ```toml
446
+ [tool.nuwa]
447
+ nimble-deps = ["nimpy"]
448
+ ```
449
+
450
+ **Option 2: Manual installation**
451
+
452
+ ```bash
453
+ nimble install nimpy
454
+ ```
455
+
456
+ ### "nimble package manager not found"
457
+
458
+ Nimble is installed with Nim. Make sure Nim is properly installed and in your PATH:
459
+
460
+ ```bash
461
+ nim --version
462
+ nimble --version
463
+ ```
464
+
465
+ If nimble is not found, reinstall Nim from https://nim-lang.org/install.html.
466
+
467
+ ### "ModuleNotFoundError: No module named 'my_package'"
468
+
469
+ The module needs to be compiled first. Run:
470
+
471
+ ```bash
472
+ nuwa develop
473
+ ```
474
+
475
+ Then you can import it directly from the project root. No `pip install` needed!
476
+
477
+ **For pytest**: Make sure you've compiled the extension with `nuwa develop` first. The flat layout allows pytest to discover the module automatically.
478
+
479
+ ### "ValueError: Module name '...' is not a valid Python identifier"
480
+
481
+ Your project name contains invalid characters for Python modules. Module names can only contain letters, numbers, and underscores, and cannot start with a number. Use the `--name` option:
482
+
483
+ ```bash
484
+ nuwa new my-project --name my_valid_name
485
+ ```
486
+
487
+ ### "Multiple .nim files found in nim/"
488
+
489
+ Nuwa found multiple `.nim` files but can't determine which is the entry point. Specify it in `pyproject.toml`:
490
+
491
+ ```toml
492
+ [tool.nuwa]
493
+ entry-point = "my_entry_file.nim"
494
+ ```
495
+
496
+ Or ensure there's only one `.nim` file, or name your entry point `{module_name}_lib.nim`.
497
+
498
+ ## License
499
+
500
+ MIT
501
+
502
+ ## Acknowledgments
503
+
504
+ - Uses [nimpy](https://github.com/yglukhov/nimpy) for Python bindings
505
+ - Named after [Nüwa](https://en.wikipedia.org/wiki/N%C3%BCwa), the Chinese goddess of creation