Skip to content

FAQ

Short answers to questions that come up repeatedly.


When should I use --mutable?

Use --mutable when step() must modify state directly — typically for generator or oscillator objects where the state evolves on every call (an NCO that advances its phase, an RNG that updates its seed).

Without --mutable, the generated step() takes const comp_state_t *state, which lets the compiler optimise more aggressively. If your step() only reads state, don't use --mutable.

For sources that produce output with no input, use --arg-type void instead. See Stateful vs Pure for the full shape matrix.


When should I use a module vs standalone objects?

Standalone (just-makeit object, no --module): each type is its own .so and its own top-level import. Good when types are unrelated or will be used independently.

Module (just-makeit module + just-makeit object --module): multiple types share one .so and one subpackage import. Good when types are conceptually related (a filter bank, a set of codec stages) and users will typically import several at once.

Module layout:

from my_filters.filter import Fir, Biquad   # one .so

Standalone layout:

from my_dsp import Gain        # gain.so
from my_dsp import Equalizer   # equalizer.so

The C code is identical either way.


Declare it in the manifest, so every face — this build, the installed CMake package and the .pc — gets it:

[project]
pkg_modules = ["fftw3f"]

[my_filter]
extra_link_libs = ["PkgConfig::FFTW3F"]

then jm apply. See When your library depends on another package. CMake the manifest cannot express goes in native/src/<comp>/<comp>_extra.cmake, never the generated CMakeLists.txt, which the next jm apply rewrites.

For Python runtime dependencies, add to pyproject.toml:

[project]
dependencies = ["numpy", "scipy"]

The generated project's C code and Python binding don't need to change.


Can I add a method that takes extra scalar arguments beyond x?

Yes. Use --extra-arg name:type (or --param name:type) on jm method, or declare extra_args = [{name, type}] in TOML:

just-makeit method integrator step_controlled \
    --arg-type "float _Complex" --return-type "float _Complex" \
    --extra-arg dump_now:bool

Generated C signature:

float _Complex my_project_integrator_step_controlled(
    my_project_integrator_state_t *state, float _Complex x, bool dump_now);

Generated Python stub:

def step_controlled(self, x: complex, dump_now: bool) -> complex: ...

In TOML, extra_args and params are synonyms — use whichever reads more clearly:

[[integrator.methods]]
name        = "step_controlled"
arg_type    = "float _Complex"
return_type = "float _Complex"
extra_args  = [{name = "dump_now", type = "bool"}]

Supported scalar types for extra args are the same as for state fields (see the type mapping table).


Can I add a method that takes no arguments?

Yes. Use --param to declare method parameters; omitting --param gives you a no-argument method:

just-makeit method my_obj flush   # method with no params

The C stub is T my_project_my_obj_flush(my_project_my_obj_state_t *state) and the Python binding calls it as obj.flush().


Which param names can't I use?

A param becomes a C local in the wrapper jm generates, beside jm's own, so a few names are refused before anything is written (gh-1512):

  • self, args, kwds and kwlist: the wrapper's C signature.
  • Any name starting with _: the namespace jm's own locals use.
  • x_obj, x_arr, x_raw, x_len beside an array param x: jm marshals x through the first three and passes x_len to C.
  • On a --variable-output / --out-type method or function only: out, n_out, out_obj, out_arr, arr<N>, v<N>; on a --multi-output one, out<N>.

y is fine: when a param takes it, the wrapper moves its own result to _y. The same rule holds for method params, module-function params and init params, on the command line and in a hand-written manifest.

The kind = "handle", "capsule" and "composer" modules follow the same rule for their args, and jm apply refuses the locals their own wrappers declare too (gh-1525): a capsule init param named mod, w or cap; a handle method arg named after that shape's result or array locals (r, n_in, in_data, out_data, view, ...); a composer source field named fs; a serializer param named segs. The error names the local.


How do I add a struct field without making it a state variable?

State variables (declared with --state) get a constructor parameter, getter/setter pair, and reset target. If you just need a field in the struct — a scratch buffer, a lookup table, something initialised in _create — add it manually to native/inc/<pkg>/<obj>/<obj>_core.h inside the struct body.

The state struct (and the inline step() body) in <obj>_core.h is sacred — just-makeit apply never re-renders it; it only injects missing method/property declarations from the manifest. Your hand-added fields stay put across apply. The exception is a rebuild (just-makeit add or just-makeit regenerate): it re-stubs the struct from the manifest, so re-add any manual fields after one — or declare them in TOML so they come back automatically.


Does jm add / jm apply overwrite my hand-edited files?

It depends on the verb — they follow a sacred/glue contract. See the Customization page for the complete table. In short:

  • jm apply never rewrites or removes what you wrote in _core.c. It does add to it: a method the manifest declares and the file has no definition for gets a stub appended, which is how a declaration you add to the TOML reaches the C. It regenerates the glue (*_ext.c, .pyi, CMakeLists.txt) and injects any missing method/property declaration into *_core.h; the state struct and inline step() body stay sacred.
  • jm method / computed jm property / jm function are additive: they inject one declaration and append a fresh stub (for jm function, a new native/src/<mod>/<fn>.c unless --functions-in-core), leaving your existing bodies intact.
  • jm add is structural — it rebuilds the object from the manifest, which re-stubs the sacred _core.c. Keep your algorithm in the TOML impl/create_impl (the rebuild re-asserts it) or git stash first.

So editing the TOML propagates to the glue. A signature change or a new state field is structural — use jm regenerate (or jm add for state) to rebuild the sacred _core.c body from the manifest.


How do I force everything to rebuild from the manifest?

just-makeit regenerate <component> deletes every file the component owns and re-runs apply, rebuilding from just-makeit.toml. It is the deliberate-refresh counterpart to the sacred-file contract: by default it lifts hand-written _core.c/_core.h bodies before deleting the files and splices them back into the fresh scaffold (best-effort text matching, not a guarantee — git stash first regardless); pass --discard for a truly clean reset back to the template. It leaves the manifest untouched (unlike jm remove). One confirmation prompt; --force skips it. Works for both standalone and module objects.


Can I use just-makeit without Python (C-only)?

The generator requires Python to run. The generated project does not — the C library (libmy_project.so) and its headers are standalone. You can build and distribute the C library without Python installed on end-user machines.

See C library for the full install and consumption story.


How do I run the bundled examples?

just-makeit example fir_filter

This runs the fir_filter example end-to-end in a temporary directory — no git clone required. The bundled examples ship inside the wheel. For a list, run just-makeit example with no name.


Why does make test use unittest instead of pytest?

By default the generated tests use python -m unittest discover so that no extra dependencies are required. If you want pytest:

just-makeit new my_project --object obj --pytest

Or add --pytest-benchmark to also generate pytest-benchmark bench files.


Does it work on Windows?

Yes, with clang-cl, and with no flag or manifest key (gh-1368). A generated project builds, tests and imports on Windows exactly as generated, from a Developer PowerShell or by opening its folder in Visual Studio: the generated CMakePresets.json selects clang-cl, Ninja and the project's .venv (gh-1376). CI builds every bundled example with clang-cl on windows-latest, and that job gates the merge.

MSVC's own cl.exe cannot build it (it has no C99 _Complex), MinGW is retired, and native ARM64 is not covered. The prerequisites, both ways in, and what behaves differently are in Building on Windows.

How do I ship a Python wheel with the C extension?

just-makeit build

This configures and builds the C extension via CMake, then packages the wheel by calling just-buildit's build_wheel() directly (not a pip wheel subprocess) — the same backend named in the generated pyproject.toml's build-backend. For cross-platform wheel distribution, the generated project is cibuildwheel-compatible — add a cibuildwheel section to pyproject.toml and configure for your target platforms.