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
copperlacebinary 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;
-
SHA256SUMSfor 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
rlibRust library; -
a
cdylibnative dynamic library; -
the
copperlaceCLI 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
copperlaceexecutable, orcopperlace.exeon 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:
-
COPPERLACE_LIBRARY_PATH -
packaged classifier resource
-
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:
-
COPPERLACE_LIBRARY_PATH -
packaged native library under
priv/native/<libname> -
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.