Packaging and Language Wrappers

Copperlace is implemented in Rust and exposed through native and language wrapper packages.

For the release operator checklist, see Copperlace release process.

Build Workflows

Use the root Makefile for normal development workflows:

make check
make package
make clean

make check runs Rust formatting checks plus Rust, Python, and Java tests. make package builds distributable CLI/native, Python, JS/TS, and Java artifacts for the current platform. make clean removes generated Rust, Python, JS/TS, and Java build output.

Release Automation

GitHub Actions builds and smoke-tests packages for pull requests and pushes to main. Version tags matching v* build the same package set, generate SHA256SUMS, and publish assets to the GitHub Release for that tag.

The first-class native release targets are:

  • linux-x86_64

  • linux-aarch64

  • macos-x86_64

  • macos-aarch64

  • windows-x86_64

Windows aarch64 is not part of the first release matrix. Other platforms may still work from source when Rust, Python, Java, or Node tooling is available, but packaged native artifacts are not promised for them.

Release assets include:

  • platform CLI archives containing the copperlace binary and native library;

  • Python wheels with the platform native library bundled under copperlace/native/, plus a source distribution that can build the bundled Rust core from source;

  • Java API, all-platform, and platform native JARs;

  • a JS/WASM package tarball generated from wasm-pack --target bundler;

  • an Elixir Hex package containing the NIF shim and Elixir modules;

  • SHA256SUMS for uploaded release assets.

Use make release-check locally to verify Cargo, Python, Java, and Elixir versions match before creating a release tag. Release tags should use the package version prefixed with v, such as v0.1.1.

Rust Artifacts

The Rust package in rust-core/ builds:

  • an rlib Rust library;

  • a cdylib native dynamic library;

  • the copperlace CLI binary.

  • a WebAssembly package when built with wasm-pack.

make cli-archive builds the current-platform CLI release archive under target/release-artifacts/.

Prebuilt CLI Archives

GitHub Releases publish prebuilt CLI archives for each first-class native platform. These archives are for users who want the copperlace executable without installing Rust or building from source.

Archive names use this shape:

copperlace-<version>-linux-x86_64-cli.tar.gz
copperlace-<version>-linux-aarch64-cli.tar.gz
copperlace-<version>-macos-x86_64-cli.tar.gz
copperlace-<version>-macos-aarch64-cli.tar.gz
copperlace-<version>-windows-x86_64-cli.zip

Each archive contains:

  • the copperlace executable, or copperlace.exe on Windows;

  • the platform native library;

  • README.adoc;

  • LICENSE.

On Linux and macOS, extract the archive and run the executable:

tar -xzf copperlace-<version>-<platform>-cli.tar.gz
cd copperlace-<version>-<platform>-cli
./copperlace --help

On Windows, extract copperlace-<version>-windows-x86_64-cli.zip and run copperlace.exe.

The CLI supports:

copperlace render --config <path>
copperlace render -c <path> -r <name> --count <n> --set name=Mia
copperlace render -c <path> -r <name> --max-recursion-depth <n>
copperlace render -c <path> -r <object-rule> --compact-json

CLI rendering uses the builtin processor registry. If --rule is omitted, the CLI renders origin. --count renders multiple outputs from one loaded config. --set key=value provides initial context values for each render and may be repeated; duplicate keys use the last value. --max-recursion-depth <n> opts in to limited recursive rule expansion. The default 0 keeps recursive references as errors; values greater than zero cut off recursive calls with an empty string after the configured number of recursive re-entries.

The CLI infers output mode from the selected rule shape. String-valued and list-valued rules render as text. Object-valued rules render as structured JSON, formatted with tabs by default. Use --compact-json for compact JSON; with --count --compact-json, the CLI writes one compact JSON value per line.

C ABI

The native dynamic library exposes an opaque ruleset handle API:

  • copperlace_ruleset_from_file

  • copperlace_ruleset_from_file_with_processors

  • copperlace_ruleset_from_string

  • copperlace_ruleset_from_string_with_processors

  • copperlace_ruleset_render

  • copperlace_ruleset_render_with_context

  • copperlace_ruleset_render_with_context_and_options

  • copperlace_ruleset_render_inferred

  • copperlace_ruleset_render_inferred_with_context

  • copperlace_ruleset_render_inferred_with_context_and_options

  • copperlace_ruleset_render_structured_json

  • copperlace_ruleset_render_structured_json_with_context

  • copperlace_ruleset_render_structured_json_with_context_and_options

  • copperlace_processor_result_set_output

  • copperlace_processor_result_set_error

  • copperlace_ruleset_free

  • copperlace_string_free

Returned strings are allocated by Copperlace and must be released with copperlace_string_free. Ruleset handles must be released with copperlace_ruleset_free. Custom processor callbacks must remain valid until the ruleset handle is freed.

Python Wrapper

The Python package in python/ uses ctypes over the C ABI. Public entry points are:

  • Copperlace.from_string(config, processors=None)

  • Copperlace.from_file(path, processors=None)

  • Copperlace.render(rule, context=None, max_recursion_depth=0)

  • Copperlace.render_inferred(rule, context=None, max_recursion_depth=0)

  • Copperlace.render_structured(rule, context=None, max_recursion_depth=0)

  • RuleSet.from_string(config, processors=None)

  • RuleSet.from_file(path, processors=None)

  • RuleSet.render(rule, context=None, max_recursion_depth=0)

  • RuleSet.render_inferred(rule, context=None, max_recursion_depth=0)

  • RuleSet.render_structured(rule, context=None, max_recursion_depth=0)

  • render_str(config, rule, context=None, max_recursion_depth=0, processors=None)

  • render_file(path, rule, context=None, max_recursion_depth=0, processors=None)

  • render_str_inferred(config, rule, context=None, max_recursion_depth=0, processors=None)

  • render_file_inferred(path, rule, context=None, max_recursion_depth=0, processors=None)

  • render_str_structured(config, rule, context=None, max_recursion_depth=0, processors=None)

  • render_file_structured(path, rule, context=None, max_recursion_depth=0, processors=None)

Copperlace is the recommended load-once API for repeated renders. Copperlace and RuleSet are context managers and release the native handle when closed. Wrapper failures are raised as CopperlaceError.

The wheel build compiles rust-core and bundles the platform native library under copperlace/native/. Linux wheels are repaired with auditwheel before publication so they use PyPI-compatible platform tags. The source distribution includes the Rust sources needed to build the native library from source.

PyPI releases publish:

  • platform wheels for Linux x86_64/aarch64, macOS x86_64/aarch64, and Windows x86_64;

  • one source distribution;

  • the same package version as Cargo, Java, and GitHub Release artifacts.

Python rendering uses the builtin processor registry plus any custom processor callbacks provided at construction time.

Python structured rendering is intentionally string-based. The wrapper keeps the Rust-to-Python boundary as rendering to text or structured JSON text instead of exposing a second structured value model. Inferred rendering also returns strings: normal text for string-valued and list-valued rules, or formatted JSON text for object-valued rules.

JS/TS WebAssembly Package

The JS/TS package in js/ is generated by wasm-pack from rust-core/. Public entry points are:

  • new Copperlace(config)

  • Copperlace.withProcessors(config, processors)

  • Copperlace.render(rule)

  • Copperlace.renderWithOptions(rule, maxRecursionDepth)

  • Copperlace.renderWithContext(rule, context)

  • Copperlace.renderWithContextAndOptions(rule, context, maxRecursionDepth)

  • renderString(config, rule)

  • renderStringWithOptions(config, rule, maxRecursionDepth)

  • renderStringWithContext(config, rule, context)

  • renderStringWithContextAndOptions(config, rule, context, maxRecursionDepth)

  • renderStringWithProcessors(config, rule, processors)

  • renderStringWithProcessorsAndContext(config, rule, processors, context)

  • renderStringWithProcessorsContextAndOptions(config, rule, processors, context, maxRecursionDepth)

Copperlace is the recommended load-once API for repeated renders in browser apps. Browser callers should load rule text themselves, usually with fetch, and pass the config string into Copperlace; file-path APIs are not exported to WebAssembly.

Build the bundler-oriented package with:

make js-package

Build a direct browser ES module package with:

make js-web

Both commands write generated output to js/pkg/, which is a build artifact. JS/TS rendering uses the builtin processor registry plus any custom processor functions provided at construction time.

Java Wrapper

The Java module in java/ uses Java FFM over the C ABI. The simplest Maven Central dependency is dev.mahe.copperlace:copperlace, which depends on the Java API plus all first-class supported native platform artifacts.

<dependency>
  <groupId>dev.mahe.copperlace</groupId>
  <artifactId>copperlace</artifactId>
  <version>0.1.1</version>
</dependency>

Gradle Kotlin DSL:

implementation("dev.mahe.copperlace:copperlace:0.1.1")

Applications that only want one runtime platform can depend on one platform artifact instead. Choose the runtime platform, not necessarily the build platform:

  • dev.mahe.copperlace:copperlace-linux-x86_64

  • dev.mahe.copperlace:copperlace-linux-aarch64

  • dev.mahe.copperlace:copperlace-macos-x86_64

  • dev.mahe.copperlace:copperlace-macos-aarch64

  • dev.mahe.copperlace:copperlace-windows-x86_64

Each platform artifact depends on dev.mahe.copperlace:copperlace-api transitively. Advanced users can depend on copperlace-api directly when they provide the native library through COPPERLACE_LIBRARY_PATH or source-tree build output.

Public entry points are:

  • Copperlace.fromString(config)

  • Copperlace.fromStringWithProcessors(config, processors)

  • Copperlace.fromFile(path)

  • Copperlace.fromFileWithProcessors(path, processors)

  • Copperlace.render(rule)

  • Copperlace.render(rule, maxRecursionDepth)

  • Copperlace.render(rule, context)

  • Copperlace.render(rule, context, maxRecursionDepth)

  • Copperlace.renderInferred(rule)

  • Copperlace.renderInferred(rule, maxRecursionDepth)

  • Copperlace.renderInferred(rule, context)

  • Copperlace.renderInferred(rule, context, maxRecursionDepth)

  • Copperlace.renderStructuredJson(rule)

  • Copperlace.renderStructuredJson(rule, formatJson)

  • Copperlace.renderStructuredJson(rule, formatJson, maxRecursionDepth)

  • Copperlace.renderStructuredJson(rule, context)

  • Copperlace.renderStructuredJson(rule, context, formatJson)

  • Copperlace.renderStructuredJson(rule, context, formatJson, maxRecursionDepth)

  • Copperlace.renderString(config, rule)

  • Copperlace.renderString(config, rule, maxRecursionDepth)

  • Copperlace.renderString(config, rule, context)

  • Copperlace.renderStringInferred(config, rule)

  • Copperlace.renderStringInferred(config, rule, maxRecursionDepth)

  • Copperlace.renderStringInferred(config, rule, context)

  • Copperlace.renderStringStructuredJson(config, rule)

  • Copperlace.renderStringStructuredJson(config, rule, formatJson)

  • Copperlace.renderStringStructuredJson(config, rule, formatJson, maxRecursionDepth)

  • Copperlace.renderStringStructuredJson(config, rule, context)

  • Copperlace.renderStringStructuredJson(config, rule, context, formatJson)

  • Copperlace.renderStringWithProcessors(config, rule, processors)

  • Copperlace.renderStringWithProcessors(config, rule, context, processors)

  • Copperlace.renderFile(path, rule)

  • Copperlace.renderFile(path, rule, context)

  • Copperlace.renderFileInferred(path, rule)

  • Copperlace.renderFileInferred(path, rule, context)

  • Copperlace.renderFileStructuredJson(path, rule)

  • Copperlace.renderFileStructuredJson(path, rule, formatJson)

  • Copperlace.renderFileStructuredJson(path, rule, context)

  • Copperlace.renderFileStructuredJson(path, rule, context, formatJson)

  • Copperlace.renderFileWithProcessors(path, rule, processors)

  • Copperlace.renderFileWithProcessors(path, rule, context, processors)

  • RuleSet.fromString(config)

  • RuleSet.fromStringWithProcessors(config, processors)

  • RuleSet.fromFile(path)

  • RuleSet.fromFileWithProcessors(path, processors)

  • RuleSet.render(rule)

  • RuleSet.render(rule, maxRecursionDepth)

  • RuleSet.render(rule, context)

  • RuleSet.render(rule, context, maxRecursionDepth)

  • RuleSet.renderInferred(rule)

  • RuleSet.renderInferred(rule, maxRecursionDepth)

  • RuleSet.renderInferred(rule, context)

  • RuleSet.renderInferred(rule, context, maxRecursionDepth)

  • RuleSet.renderStructuredJson(rule)

  • RuleSet.renderStructuredJson(rule, formatJson)

  • RuleSet.renderStructuredJson(rule, formatJson, maxRecursionDepth)

  • RuleSet.renderStructuredJson(rule, context)

  • RuleSet.renderStructuredJson(rule, context, formatJson)

  • RuleSet.renderStructuredJson(rule, context, formatJson, maxRecursionDepth)

Copperlace is the recommended load-once API for repeated renders. Copperlace and RuleSet implement AutoCloseable; callers should use try-with-resources or call close.

The Java API JAR contains only Java classes. Native binaries are packaged in platform artifacts for linux-x86_64, linux-aarch64, macos-x86_64, macos-aarch64, and windows-x86_64.

Plain Maven builds include the current-system native Java package by default when the staged native library exists under java/native-artifacts/<classifier>/. Maven does not compile the native library; use make java-package to build Rust, stage the current native library, and package the Java API plus current-platform native JAR. To build only the Java API, run Maven with -pl api.

At runtime, the Java wrapper loads the native library in this order:

  1. COPPERLACE_LIBRARY_PATH

  2. packaged classifier resource

  3. local Rust build output for source-tree development

Java rendering uses the builtin processor registry plus any custom processor callbacks provided at construction time.

Java structured rendering is intentionally string-based. The Java wrapper keeps the Rust-to-Java boundary as rendering to text or structured JSON text instead of exposing a second structured value model. No-argument structured JSON overloads return formatted JSON using tabs for indentation. The formatJson parameter controls serialization: false returns compact JSON and true returns formatted JSON. renderInferred also returns strings: normal text for string-valued and list-valued rules, or formatted JSON text for object-valued rules.

Elixir Wrapper

The Elixir package in elixir/ binds the C ABI to the BEAM through Native Implemented Functions (NIFs). The NIF shim in c_src/copperlace_nif.c `dlopen`s the native library at load time and never links it at build time, mirroring the Python and Java wrappers. Build it with:

make elixir-build
make elixir-test
make elixir-package

Add the dependency in mix.exs:

defp deps do
  [{:copperlace, "~> 0.3"}]
end

Public entry points are:

  • Copperlace.from_string(config, opts \\ [])

  • Copperlace.from_file(path, opts \\ [])

  • Copperlace.render(copperlace, rule, context \\ %{}, opts \\ [])

  • Copperlace.render_inferred(copperlace, rule, context \\ %{}, opts \\ [])

  • Copperlace.render_structured(copperlace, rule, context \\ %{}, opts \\ [])

  • Copperlace.render!/4, Copperlace.render_inferred!/4, Copperlace.render_structured!/4

The native handle is a NIF resource released by the BEAM garbage collector when the %Copperlace{} struct becomes unreachable. There is no explicit close/1; this avoids the use-after-free risk of freeing a handle while a concurrent render is still in flight.

render/4 options: :max_recursion_depth (default 0). render_structured/4 options: :max_recursion_depth (default 0) and :format_json (default true; false returns compact JSON).

context is a map of string to string. Each render uses a fresh context.

{:ok, copperlace} = Copperlace.from_string(~s(name = ["Mia"]\norigin = "Hello {name}"))
{:ok, "Hello Mia"} = Copperlace.render(copperlace, "origin")
{:ok, _} = Copperlace.render(copperlace, "origin", %{"name" => "Darcy"})
{:ok, _} = Copperlace.render_structured(copperlace, "object-rule")

At runtime, the NIF resolves the native library in this order:

  1. COPPERLACE_LIBRARY_PATH

  2. packaged native library under priv/native/<libname>

  3. local Rust build output for source-tree development

When the Hex package includes a checksum.exs file (generated during the release process), elixir_make downloads a precompiled archive at install time that contains both the NIF shim and the Copperlace native library for the current platform. No C toolchain or COPPERLACE_LIBRARY_PATH is needed. If the precompiled archive is unavailable for the current target or NIF version, elixir_make falls back to compiling the NIF shim from source and the native library is resolved at runtime via the order above.

Precompiled archives are built against OTP 28 (NIF API 2.17) with Elixir 1.19. Use OTP 28.1 or later with Elixir 1.19 to receive a precompiled archive.

Ruleset handles are NIF resource objects; the BEAM releases them through the resource destructor, which calls copperlace_ruleset_free. Strings returned by the C ABI are copied into Elixir binaries and freed immediately. Wrapper failures are returned as {:error, %Copperlace.Error{}} tuples; the bang variants raise Copperlace.Error with :status (:invalid_argument, :parse_error, :render_error, or :native_not_loaded) and :message.

This release uses the builtin processor registry only. Custom Elixir processor callbacks are not yet supported; passing :processors to from_string/2 or from_file/2 raises ArgumentError. A follow-up will wire C-to-Elixir processor callbacks.

Elixir structured and inferred rendering are intentionally string-based, like the other wrappers: render_structured/4 returns JSON text, and render_inferred/4 returns text for string-valued and list-valued rules or formatted JSON text for object-valued rules.