API Reference
The public API is small: a build lifecycle (discover → build → wrap), a local registry with a community Hub (register / use / search), an experimental ingest path for pre-built binaries, and an environment check. Everything else — the compiler, wrapper generators, DWARF parser, MLIR/JLCS dialect bindings — is internal and documented under Internals.
Every entry point takes a path to a replibuild.toml (or a project directory that contains one) and is idempotent: unchanged inputs hit the content-hash cache and return immediately.
Build lifecycle
The four-stage pipeline. discover writes a replibuild.toml, build compiles source to a .so + DWARF metadata, wrap emits the Julia module, clean removes generated artifacts. info reports status without touching anything.
toml = RepliBuild.discover("path/to/project") # scan → replibuild.toml
RepliBuild.build(toml) # clang → LLVM IR → .so + compilation_metadata.json
RepliBuild.wrap(toml) # DWARF + symbols → julia/<Module>.jldiscover accepts build=true, wrap=true to run the whole pipeline in one call. Re-running discover(force=true) regenerates the config but preserves the hand-curated keys that cannot be derived from source — [types].templates / template_headers, [wrap].varargs / macros / shim_headers / cstring_owned / tier1, and [link].promote_statics — so a forced re-scan never eats your intent. Everything else is regenerated from the scan. See what discovery cannot know for the information you are expected to supply by hand. build(clean=true) forces a full rebuild past the cache.
RepliBuild.discover — Function
discover(target_dir="."; force=false, build=false, wrap=false) -> StringScan C++ project and generate replibuild.toml configuration file.
This is the entry point for new projects. Run this first to set up RepliBuild.
Arguments
target_dir: Project directory to scan (default: current directory)force: Force rediscovery even if replibuild.toml exists (default: false)build: Automatically run build() after discovery (default: false)wrap: Automatically run wrap() after build (requires build=true, default: false)
Returns
Path to generated replibuild.toml file
Workflow
Basic workflow (step-by-step):
# 1. Discover and create config
toml_path = RepliBuild.discover()
# 2. Build the library
RepliBuild.build(toml_path)
# 3. Generate Julia wrappers
RepliBuild.wrap(toml_path)Chained workflow (automated):
# Discover → Build → Wrap (all at once)
toml_path = RepliBuild.discover(build=true, wrap=true)
# Or just discover and build
toml_path = RepliBuild.discover(build=true)Examples
# Discover current directory
RepliBuild.discover()
# Discover another directory
RepliBuild.discover("path/to/cpp/project")
# Force regenerate config
RepliBuild.discover(force=true)
# Full automated pipeline
RepliBuild.discover(build=true, wrap=true)RepliBuild.build — Function
build(toml_path="replibuild.toml"; clean=false)Compile C++ project → library (.so/.dylib/.dll)
What it does:
- Compiles your C++ code to LLVM IR
- Links and optimizes IR
- Generates library file
- Extracts metadata (DWARF + symbols) for wrapping
What it does NOT do:
- Does NOT generate Julia wrappers (use
wrap()for that)
Arguments
toml_path: Path to replibuild.toml configuration file (default: "replibuild.toml")clean: Clean before building (default: false)
Returns
Library path (String)
Examples
# Build using replibuild.toml in current directory
RepliBuild.build()
# Build with specific config file
RepliBuild.build("path/to/replibuild.toml")
# Clean build
RepliBuild.build(clean=true)
# Then generate Julia wrappers:
RepliBuild.wrap("replibuild.toml")RepliBuild.wrap — Function
wrap(toml_path="replibuild.toml"; headers=String[])Generate Julia wrapper from compiled library
What it does:
- Loads metadata from build (DWARF + symbols)
- Generates Julia module with ccall wrappers
- Creates type definitions from C++ structs
- Saves to julia/ directory
Requirements:
- Must run
build()first - Metadata must exist in julia/compilation_metadata.json
Arguments
toml_path: Path to replibuild.toml configuration file (default: "replibuild.toml")headers: C++ headers for advanced wrapping (optional)
Returns
Path to generated Julia wrapper file
Examples
# Generate wrapper using replibuild.toml in current directory
RepliBuild.wrap()
# Generate wrapper with specific config file
RepliBuild.wrap("path/to/replibuild.toml")
# With headers for better type info
RepliBuild.wrap("replibuild.toml", headers=["mylib.h"])RepliBuild.clean — Function
clean(toml_path="replibuild.toml")Remove build artifacts (build/, julia/, caches)
Arguments
toml_path: Path to replibuild.toml configuration file (default: "replibuild.toml")
Examples
# Clean using replibuild.toml in current directory
RepliBuild.clean()
# Clean specific project
RepliBuild.clean("path/to/replibuild.toml")RepliBuild.info — Function
info(toml_path="replibuild.toml")Show project status (config, library, wrapper)
Arguments
toml_path: Path to replibuild.toml configuration file (default: "replibuild.toml")
Examples
# Show info for current directory
RepliBuild.info()
# Show info for specific project
RepliBuild.info("path/to/replibuild.toml")Package registry and the Hub
register records a project in the local registry (~/.replibuild/registry/). use is the one-call "give me a loaded module": it resolves dependencies, builds (or serves the cached build from ~/.replibuild/builds/<hash>/), wraps, and returns the loaded Julia module. On a local-registry miss, use/search fall through to the RepliBuild-Hub community registry.
Lua = RepliBuild.use("lua") # build + wrap + load (cached), Hub on miss
Lua.luaL_newstate()
RepliBuild.search("xml") # query the Hub by name/description/tags/language
RepliBuild.list_registry() # what's registered locallyThe build-cache key includes RepliBuild's own version and git revision, so upgrading the generator rebuilds each package once with current codegen instead of serving a stale wrapper. scaffold_package turns a registered project into a distributable, Pkg.add-able Julia package. Two environment overrides: REPLIBUILD_HOME relocates the registry, REPLIBUILD_HUB_URL points Hub operations at a private mirror.
RepliBuild.register — Function
register(toml_path::String; name="", verified=false) -> RegistryEntryHash and store a replibuild.toml in the global registry (~/.replibuild/registry/). Name is inferred from [project].name if not provided. Called automatically by discover().
RepliBuild.use — Function
use(name::String; force_rebuild=false, verbose=true) -> ModuleLoad a wrapper by registry name. Resolves dependencies, checks environment, builds if needed, and returns the loaded Julia module.
Example
Lua = RepliBuild.use("lua")
Lua.luaL_newstate()RepliBuild.search — Function
search(query::String="")Search the RepliBuild Hub for available packages. Matches against names, descriptions, tags, and language. Call with no arguments to list everything.
RepliBuild.search() # list all hub packages
RepliBuild.search("json") # filter by keywordRepliBuild.list_registry — Function
list_registry()Print all registered packages in the global RepliBuild registry.
RepliBuild.unregister — Function
unregister(name::String)Remove a package from the global registry.
RepliBuild.scaffold_package — Function
scaffold_package(name::String; path::String=".") -> StringGenerate a standardized Julia package for distributing RepliBuild wrappers.
Creates a complete package with Project.toml, replibuild.toml, source stub, deps/build.jl hook, and test skeleton. Edit the replibuild.toml to point at your C/C++ source, then Pkg.build() compiles and wraps automatically.
Example
RepliBuild.scaffold_package("MyEigenWrapper")Ingest (experimental, C only)
For C libraries whose build systems RepliBuild's source pipeline can't reproduce (autotools, CMake code generators, configure scripts), build the .so yourself with -g and ingest it — RepliBuild skips compilation and runs only DWARF extraction + wrapper generation. Ingested libraries dispatch through Tier 3 (ccall) exclusively; the C++ API surface of an ingested binary is not supported (classes/methods/templates/virtual dispatch need the thunks only the source build produces — at best the extern "C" surface works). Prefer the source build; reach for ingest only when you must.
toml = RepliBuild.ingest("/path/to/libfoo.so",
headers=["/path/to/include"],
name="foo", language=:c,
build=true, wrap=true)RepliBuild.ingest — Function
ingest(library_path; headers=String[], extra_link_libs=String[],
name="", project_dir=".", language=:c, build=false, wrap=false) -> StringEXPERIMENTAL. Scaffold a replibuild.toml for ingest mode: wrap a pre-built .so (built by upstream's own build system) without recompiling. RepliBuild only runs DWARF metadata extraction + wrapper generation; the library must be built with -g.
Support matrix — the maintained, flagship path is the source-build pipeline (discover/build/wrap), where RepliBuild's own version-matched compilation guarantees the DWARF it consumes:
language = :c— works for plain-C ABIs (Tier-3ccallonly, no bitcode/thunks), but is best-effort: upstream's compiler and debug-info settings are outside RepliBuild's control, so extraction quality varies. Prefer the source build when the sources compile under one flag set.language = :cpp— NOT supported. The C++ ABI surface (classes, methods, templates, virtual dispatch) requires the MLIR dialect to marshal calls and generate thunks, which only the source-build pipeline produces. Ingesting a C++ library can at best expose itsextern "C"surface; the generated wrapper for the C++ API proper is unusable. If the library ships a C API variant, ingest that withlanguage = :cinstead.
This is the fallback for C libraries with elaborate build systems (autotools, CMake with code generators, configure scripts) that RepliBuild's source-build pipeline can't reproduce.
Arguments
library_path: path to the pre-built.so/.dylib/.dllheaders: header search dirs for type extraction (recommended)extra_link_libs: additional-llibraries the wrapper needs at load timename: project name (default: derived from library basename)project_dir: where to write replibuild.toml (default: cwd)language::cor:cpp— drives wrapper generator selectionbuild: also runbuild()after scaffoldingwrap: also runwrap()(requiresbuild=true)
Returns
Path to the generated replibuild.toml.
Example
toml = RepliBuild.ingest("/usr/lib/libsqlite3.so",
headers=["/usr/include"],
name="sqlite_ingest",
build=true, wrap=true)Environment
check_environment validates both toolchain buckets — the C bucket (JLL clang + Julia's resident libLLVM, no external install) and the C++/Tier-2 bucket (system LLVM/MLIR 21+, Clang, mlir-tblgen, CMake, and libJLCS.so) — and reports which dispatch tiers are available, with OS-specific install hints for anything missing.
RepliBuild.check_environment — Function
check_environment(; verbose=true, throw_on_error=false) -> ToolchainStatusRun environment diagnostics to verify LLVM 21+, MLIR, CMake, and other toolchain requirements.
Prints a colorful report showing which tools are found, their versions, and installation instructions for anything missing. Use throw_on_error=true to abort on missing requirements.
Example
status = RepliBuild.check_environment()
status.ready # true if Tier 3 (ccall) builds will work
status.tier2_ready # true if MLIR JIT tier is also availableWhat a generated wrapper exposes about itself
Beyond the wrapped API, every generated module carries facts about its own generation. None of these are exported — reach for them qualified:
| Name | What it answers |
|---|---|
dispatch_tier(f) | Which tier f actually dispatches through now: :tier1 / :tier2 / :tier3, :unknown (not wrapped), :mixed (methods on several tiers), :deferred (asked during precompilation — ask at runtime) |
DISPATCH_TIER | Symbol => Symbol — the tier the generator emitted for each function |
struct_size(name) | Byte size of a wrapped struct, from DWARF |
member_offset(name, member) | Byte offset of a member within it |
STRUCT_SIZES / STRUCT_OFFSETS | The tables behind those two |
TIER1_FUNCTIONS / TIER1_DECLARES | C only: functions on a bitcode slice, and the symbols each slice declares |
BUILD_ID / BUILD_TARGET / BUILD_GENERATOR | Identity of the library and generator the wrapper came from |
dispatch_tier and DISPATCH_TIER disagree exactly when a Tier-1 kernel demotes (missing slices/, unresolvable declare); the function reports what will actually run, the table what was intended. dispatch_tier forces the kernel to generate, so it is not read-only and refuses to answer during precompilation.
Debugging generated thunks
RepliBuild.Debug inspects what the Tier-2 pipeline emitted for a package — including one this process never built — without running anything:
D = RepliBuild.Debug
D.thunks(pkg) # thunk symbols available to ask about
D.mlir_body(pkg, symbol) # the generated dialect for one thunk
D.disassemble(pkg; symbol = s) # objdump -dS: ops interleaved with machine code
D.dwarf(pkg; section = "line") # the address → MLIR-line table
D.walk(pkg, symbol) # the common combination, one callThe disassembly path needs ENV["REPLIBUILD_JIT_OBJDUMP"] = "1" set before the wrapper loads. The live-process counterpart — breaking inside the emitted MLIR under gdb — is documented in Debugging a thunk.