API reference

JuliaLibWrapping.numpy_dtypesConstant
numpy_dtypes :: Dict{String, String}

Map from Julia primitive type name to the corresponding numpy dtype string. Used by the Python façade emitter to decide which pointer element types can be exposed as numpy arrays in CVector / CMatrix helpers; primitives absent from this table (notably Bool, the platform-aliased C ints) are not auto-wrapped.

source
JuliaLibWrapping.ABIInfoType
ABIInfo

Fields

  • typeinfo::OrderedDict{Int, TypeDesc}: A map from type_id to type_descriptor, sorted by declaration order.
  • forward_declared::BitSet: Indexes into types, indicates which types must be forward-declared for C.
  • entrypoints::Vector{MethodDesc}: A vector of exposed functions from the imported ABI.
source
JuliaLibWrapping.AbstractTargetType
AbstractTarget

Supertype of wrapper-emission targets. Each concrete subtype is a configuration struct describing where and how to emit one output language's bindings; a corresponding write_wrapper method consumes that configuration plus an ABIInfo and writes the files.

Ships today: CTarget for a C header, PythonTarget for a Python ctypes package. New languages are added by defining a subtype and a write_wrapper method for it.

source
JuliaLibWrapping.CTargetType
CTarget(dir, headerbase)

Output configuration for a C header. write_wrapper writes a single file joinpath(dir, headerbase * ".h") containing typedefs for every struct in the ABI and extern declarations for every entrypoint.

Only the primitive Julia types listed in the emitter's ctypes table have a direct C mapping; an ABI referencing any other primitive raises an error. Pointer types are emitted inline as T* rather than as separate typedefs. Non-C-safe identifiers are scrubbed by sanitize_for_c and disambiguated with a numeric suffix on collision.

source
JuliaLibWrapping.PythonTargetType
PythonTarget(dir, package_name, library_basename; bundle_subdir = nothing)

Output configuration for a Python ctypes-based wrapper package. dir is the directory into which the package will be written; a sub-directory named package_name is created and is the importable Python module. library_basename is the shared library's basename without an OS-specific suffix (e.g. "libsimple", which will be loaded from libsimple.so / libsimple.dylib / libsimple.dll depending on the host).

When bundle_subdir is a string (e.g. "bundle"), the emitter assumes the shared library and its juliac runtime closure (libjulia, sysimage, stdlibs, artifacts) will be laid out under that subdirectory of the Python package in the standard --bundle shape (<bundle_subdir>/lib/<lib>, <bundle_subdir>/lib/julia/, <bundle_subdir>/artifacts/). The generated loader looks for the library inside the bundle first, the generated pyproject.toml widens package-data to include the bundle tree, and build_library with bundle = true will copy the bundle there. The default nothing preserves the flat single-.so-next-to-the-package layout and is the right choice for callers placing the library by hand.

source
JuliaLibWrapping._facade_classify_argMethod
_facade_classify_arg(arg, typeinfo, typedict) -> NamedTuple

Classify a method argument for façade auto-wrapping. The return is one of:

  • (kind=:primitive,) — pass-through
  • (kind=:carray, classname=…) — wrap with <class>.from_numpy(name)
  • (kind=:cstring, classname=…) — wrap with <class>.from_str(name)
  • (kind=:opaque, reason=…) — bail out; emit mechanical re-export instead.
source
JuliaLibWrapping._facade_classify_returnMethod
_facade_classify_return(method, typeinfo, typedict) -> NamedTuple

Classify a method's return for façade auto-wrapping. The return is one of:

  • (kind=:passthrough,) — primitive scalar (including Cvoid)
  • (kind=:carray_unwrap, classname=…) — return _result.as_numpy()
  • (kind=:cstring_unwrap, classname=…) — return _result.as_str()
  • (kind=:jlwstatus_discard,) — direct JLWStatus return; discard, return None
  • (kind=:opaque, reason=…) — bail out.
source
JuliaLibWrapping._facade_planMethod
_facade_plan(method, typeinfo, typedict) -> NamedTuple

Decide whether an entrypoint should be auto-wrapped on the façade. Returns (auto::Bool, reason::String, args::Vector, ret::NamedTuple, uses_numpy::Bool).

A function is auto-wrapped only when every argument and return classifies as a recognized form and the wrapping actually adds value (converts a vocabulary type or strips a discardable JLWStatus). Plain primitive-in/primitive-out functions are left as straight re-exports.

source
JuliaLibWrapping.build_libraryMethod
build_library(entry, targets;
              project=dirname(entry), libname, libdir=pwd(),
              abi_path=joinpath(libdir, libname*".abi.json"),
              trim=:safe, compile_ccallable=true,
              backend=:auto, verbose=false,
              bundle=false, bundle_dir=joinpath(libdir, libname*"-bundle"),
              privatize=false)

Run the full juliac → ABI JSON → wrapper pipeline in one call.

entry is the path to the Julia source file (or package directory) that juliac will compile; targets is a vector of AbstractTargets that will each receive a write_wrapper call once the ABI JSON is available.

Returns a NamedTuple (library, abi_path, abi_info, target_outputs, backend, bundle_dir). bundle_dir is the path to the produced bundle tree when bundle = true, and nothing otherwise.

Backend

build_library drives JuliaC.jl (a weak dependency); load it with using JuliaC before calling this function. backend = :auto (the default) and backend = :juliac are synonyms; the kwarg is retained so additional backends can be added without breaking the call surface.

Example

using JuliaLibWrapping
out = mktempdir()
result = build_library(
    joinpath(@__DIR__, "src/mylib.jl"),
    [CTarget(out, "mylib"), PythonTarget(out, "mylib_py", "mylib")];
    project = @__DIR__,
    libname = "mylib",
    libdir  = out,
)

[sources] paths must be absolute

juliac relocates the project into a temporary directory before compiling. Relative [sources] paths in the entry project's Project.toml cannot be resolved from there, so this function rejects them up front. Either use absolute paths or Pkg.develop the dependency.

Bundling for distribution (issue #17)

A juliac-produced .so depends on libjulia, a sysimage, stdlibs, and artifacts — none of which a pip install-ing Python user has on their machine. Pass bundle = true to also produce a self-contained directory tree (the juliac --bundle layout) and copy it into every PythonTarget's package. The Python loader generated for those targets searches the bundle first, so the baked-in RUNPATH resolves libjulia from inside the wheel at import time.

bundle = true requires the :juliac backend and that each PythonTarget declare a bundle_subdir (e.g. PythonTarget(out, "mylib_py", "mylib"; bundle_subdir = "bundle")). Targets that are not Python (e.g. CTarget) are unaffected — C consumers manage their own linkage.

privatize = true salts the bundled libjulia files with a random prefix so they cannot collide with a system libjulia. Off by default; opt in if the wrapper might be loaded into a process that also has a different libjulia.

source
JuliaLibWrapping.carray_struct_infoMethod
carray_struct_info(desc::StructDesc, typeinfo) -> Union{Nothing, NamedTuple}

Recognize the JLWInterop CArray{T,N} shape (which subsumes CVector{T} = CArray{T,1} and CMatrix{T} = CArray{T,2}): a struct whose name starts with "CArray", "CVector", or "CMatrix", with exactly two fields named dims (a fixed-size array of N integers, i.e. an ArrayDesc of a signed/unsigned integer primitive in numpy_dtypes) and data (a pointer to a primitive numeric type also in numpy_dtypes). Field order may be either dims, data or data, dims. Returns (; pointee_name, pointee_ctype, dtype, ndim) on a match (with ndim set to the dims array's count), otherwise nothing.

Like is_jlwstatus_struct, recognition is by name + shape so authors who copy-paste a compatible definition still get the behavior.

source
JuliaLibWrapping.cstring_struct_infoMethod
cstring_struct_info(desc::StructDesc, typeinfo) -> Bool

Recognize the JLWInterop CString shape: a struct whose name starts with "CString", with exactly two fields named length (a primitive integer) and data (a pointer to UInt8). The pointee type is restricted to UInt8 specifically (other widths would not round-trip as a UTF-8 string). Returns true on a match, false otherwise. Field order may be either length, data or data, length. Recognition is by name + shape (see is_jlwstatus_struct for the rationale).

source
JuliaLibWrapping.is_jlwstatus_structMethod
is_jlwstatus_struct(desc::StructDesc, typeinfo) -> Bool

Recognize the JLWInterop error-status convention by structural shape: a struct named JLWStatus with two fields — an integer code field and a message field that is a fixed-size byte array. Matching by name + shape (rather than by package identity) means authors who copy-paste a compatible definition still get the behavior.

source
JuliaLibWrapping.jlwstatus_access_pathMethod
jlwstatus_access_path(method, typeinfo) -> Union{Nothing, String}

If method's return type carries a JLWStatus (either the return type is a JLWStatus or it is a struct with a JLWStatus field), return the Python attribute path from _result to that status (e.g. "" for direct return, or ".status" for an embedded field). Otherwise return nothing. Recognition is shallow on purpose — only the immediate return struct's top-level fields are inspected.

source
JuliaLibWrapping.mangle_python!Method
mangle_python!(typedict, type_id, typeinfo) -> String

Return a Python expression naming the ctypes type for type_id. Struct names go through sanitize_for_c (whose output is also a valid Python identifier) with a _<id> collision suffix matching mangle_c!. Pointer types render inline as ctypes.POINTER(...); Ptr{Cvoid} collapses to ctypes.c_void_p. Array types render inline as (<eltype> * N). Results are memoized in typedict.

source
JuliaLibWrapping.parse_abi_infoMethod
abi_info = parse_abi_info(parsed::AbstractDict)

Build an ABIInfo from a parsed juliac ABI-info JSON document. parsed is the dictionary returned by JSON.parsefile (or JSON.parse) on such a file.

See read_abi_info for the file-based convenience.

source
JuliaLibWrapping.raw_primitive_pointer_argsMethod
raw_primitive_pointer_args(method::MethodDesc, typeinfo) -> Vector{Int}

Return positional indices into method.args for arguments whose static type is a bare Ptr{T} where T is a primitive numeric type recognized by numpy_dtypes. Ptr{Cvoid} is excluded (it lowers to ctypes.c_void_p). Pointers wrapped inside CArray / CString structs are not reported — only top-level argument types are examined.

A non-empty result signals an argument that hands the C function a raw memory address with no length, ownership, or layout metadata. The Python emitter uses this to attach a docstring on the wrapper noting the column-major contract and recommending the JLWInterop.CArray vocabulary instead.

source
JuliaLibWrapping.read_abi_infoMethod
abi_info = read_abi_info(filename::AbstractString)
abi_info = read_abi_info(io::IO)

Read and parse a juliac ABI-info JSON file, returning an ABIInfo. The first form is equivalent to parse_abi_info(JSON.parsefile(filename)); the second reads the document from a stream.

source
JuliaLibWrapping.sanitize_for_cMethod
sanitize_for_c(str) -> String

Return str with all non-alphanumeric (non-underscore) characters replaced by _, leading/trailing underscores stripped, and runs of underscores collapsed. Used to coerce Julia identifiers and type names into valid C tokens. Two distinct inputs may collide; callers that need uniqueness (e.g. mangle_c!) suffix the result with a numeric disambiguator.

source
JuliaLibWrapping.sanitize_python_argnameFunction
sanitize_python_argname(name) -> String
sanitize_python_argname(name, seen::Set{String}) -> String

Return a Python-identifier form of name: characters illegal in identifiers are stripped via sanitize_for_c, an empty result becomes "_", a leading digit (legal in juliac-emitted tuple field names like "1", "2", …, but illegal in a Python identifier) is prefixed with _, and any reserved Python keyword is suffixed with _.

When a seen set is supplied, the returned name is also made unique within that scope (callers should pass one Set{String} per scope — e.g. one per function signature, one per struct's field list). If the candidate already appears in seen, an integer suffix (2, 3, …) is appended until the name is fresh, skipping any value that itself already collides — so the result is safe even when sanitized input happens to look like another argument plus a numeric tail. The chosen name is inserted into seen before returning.

source
JuliaLibWrapping.sort_declarations!Method
sort_declarations!(typedescs) -> forward_declarations

Sort typedescs w.r.t. type-dependencies (e.g. Type A using Type B in a field), so that a type-descriptor always appears after any dependencies. Sort is performed in-place.

Returns indices of all types that could not be sorted (due to recursive types, e.g. a linked list in C). In C, these are the definitions that must be forward-declared.

source
JuliaLibWrapping.standard_buildFunction
standard_build(dir = pwd(); libname, kwargs...)

Convenience wrapper around build_library for the conventional single-library layout:

dir/
├── Project.toml          # entry project (runtime deps only)
├── src/
│   └── <libname>.jl      # @ccallable entrypoints
└── out/                  # generated artifacts

Emits both a C header and a Python ctypes package (<libname>_py), bundled for distribution. Equivalent to:

build_library(joinpath(dir, "src", libname*".jl"),
    [CTarget(joinpath(dir, "out"), libname),
     PythonTarget(joinpath(dir, "out"), libname*"_py", libname;
                  bundle_subdir = "bundle")];
    project = dir, libname, libdir = joinpath(dir, "out"),
    bundle = true, kwargs...)

The kwargs out, entry, python_package, project, and bundle override the defaults above; anything else is forwarded to build_library (e.g. verbose, trim, privatize). project defaults to dir, but can be pointed at a separate location when the on-disk source layout and the entry Project.toml live in different directories (e.g. a transient project materialized with absolute [sources] paths for juliac). For layouts outside this convention, call build_library directly.

source
JuliaLibWrapping.write_wrapperFunction
write_wrapper(target::AbstractTarget, abi_info::ABIInfo)

Emit wrapper source files for abi_info into the location described by target. The methods that ship are dispatched on CTarget (one .h file) and PythonTarget (a Python ctypes package directory). Add a method for a new AbstractTarget subtype to support another output language.

source