Plugin Development Guide
XIDL supports external generators through a plugin model. A plugin is a process
that xidlc launches, then communicates with over JSON-RPC 2.0.
How plugins fit into the compiler
The compiler pipeline is:
- parse IDL
- lower it to HIR
- pick a built-in generator or external plugin
- send generation requests
- write returned artifacts
Plugins only replace the final generation stage. They do not replace parsing, diagnostics, or HIR creation.
xidlc launches the plugin as a child process and passes an RPC endpoint with
--endpoint <uri>.
Conventions
- Plugin executable name:
xidl-<lang>(for example,xidl-foo). - Invocation example:
xidlc --lang foo --out-dir out path/to/file.idl - Transport:
- Unix:
ipc://...over Unix domain sockets - Windows:
tcp://127.0.0.1:PORT - Protocol: JSON-RPC 2.0 over the endpoint passed in
--endpoint.
The <lang> portion is the target string the user passes to --lang.
Required Methods
Plugins must implement two required methods that the driver calls during setup and generation.
parser_properties
Returns parser options. Currently:
Use this to tell the compiler which parser-side behavior your plugin expects.
generate
Parameters:
Returns:
This is the core generation call. The plugin receives HIR and returns one or more generated files.
Request/Response Examples
parser_properties request:
Response:
generate request:
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": { "files": [{ "filename": "out.txt", "filecontent": "..." }] }
}
Error response:
Rust Plugin Example
The repository already uses JSON-RPC runtime support, so Rust is the easiest way to build a plugin.
Step 1. Generate the IPC bindings
Generate the RPC bindings from ipc.idl, then use the generated Codegen
interface in your plugin:
Step 2. Implement the generated trait
use clap::Parser;
use xidl_jsonrpc::Error;
use xidl_parser::hir::{ParserProperties, Specification};
mod ipc;
use ipc::{Codegen, CodegenServer, GeneratedFile};
struct MyCodegen;
#[async_trait::async_trait]
impl Codegen for MyCodegen {
async fn get_properties(&self) -> Result<ParserProperties, Error> {
Ok(ParserProperties::default())
}
async fn get_engine_version(&self) -> Result<String, Error> {
Ok(env!("CARGO_PKG_VERSION").to_string())
}
async fn generate(
&self,
hir: Specification,
path: String,
props: ParserProperties,
) -> Result<Vec<GeneratedFile>, Error> {
let _ = (hir, path, props);
Ok(Vec::new())
}
}
#[derive(Parser)]
struct Args {
#[arg(long)]
endpoint: String,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
let handler = CodegenServer::new(MyCodegen);
let _ = xidl_jsonrpc::Server::builder()
.with_service(handler)
.serve_on(&args.endpoint)
.await;
}
Step 3. Invoke it through xidlc
If your plugin binary is named xidl-foo, users can run:
The compiler will resolve foo to the external executable, start it, and
deliver the generation request over the endpoint passed in --endpoint.
End-to-end plugin workflow
- Choose a target name such as
foo. - Build an executable named
xidl-foo. - Implement
parser_properties. - Implement
generate. - Return a file list from
generate. - Run
xidlc --lang foo ...against a sample IDL file. - Verify the produced files in the output directory.
Data contract details
hiris the JSON serialization ofxidl_parser::hir::Specificationinputis the original input path- returned files contain
filenameandfilecontent - file writing is handled by the compiler driver after the RPC response
Design guidance for plugin authors
- treat HIR as the source of truth instead of reparsing source text
- keep output filenames deterministic
- fail with clear JSON-RPC errors when input is unsupported
- document any plugin-specific annotations or conventions you add
- keep parser property requirements minimal unless your generator truly needs them
Related files in this repository
xidlc/src/driver/generate_session.rsxidlc/src/driver/lang.rsxidl-jsonrpc/
Notes
- Plugins usually derive output filenames from the
inputpath. - If you change parsing or HIR output, update snapshots with
make test-update.