Subsystem · plugins

Plugins

Extend the build with external source generators. Any binary named curie-<name> on PATH becomes a plugin — Curie invokes it before compilation, feeds it the resolved tool paths, and adds its output directories to the compiler's source roots.

Activating a plugin

Add a [plugin.<name>] table to Curie.toml. The key after plugin. is the plugin name; Curie will look for a binary called curie-<name> on PATH.

Curie.toml
[application]
name    = "proto-greeter"
version = "0.1.0"

[java]
version = "21"

[dependencies]
"com.google.protobuf:protobuf-java" = "3.25.0"

[plugin.protobuf]
version   = "3.25.0"
sourceDir = "proto"

The entire [plugin.protobuf] table — including any nested subtables like [plugin.protobuf.importPaths] — is forwarded verbatim as JSON to the plugin. Plugin keys are camelCase by convention.

Multiple plugins can be active at once: each [plugin.<name>] key activates one binary. Order of execution follows alphabetical key order.

What Curie handles

Plugins are pure logic — they never download anything. Curie owns the parts that touch the network and the filesystem:

Protobuf plugin

curie-protobuf is the reference plugin. It generates Java stubs from .proto files and optionally gRPC service interfaces.

Installation

terminal
cargo install curie-protobuf

Plain protobuf

Curie.toml
[plugin.protobuf]
version   = "3.25.0"   # protobuf + protoc version
sourceDir = "proto"    # directory containing .proto files (default: "proto")

Generated sources land in target/generated-sources/protobuf/. After a successful run the stamp records the mtime of every file under proto/, so subsequent builds skip protoc when nothing changed.

gRPC

Curie.toml
[plugin.protobuf]
version     = "3.25.0"
grpc        = true
grpcVersion = "1.60.0"
sourceDir   = "proto"

With grpc = true, the plugin also downloads protoc-gen-grpc-java and passes it as a protoc plugin, producing both message classes and service stubs.

Build output

$ curie build
Building proto-greeter v0.1.0
  Plugin protobuf  generate-sources  proto/greeter.proto
  Compile          3 source file(s)
  Tests            no test sources found
  Package          proto-greeter-0.1.0.jar
  Done             target/proto-greeter-0.1.0.jar

On the second run with no changes the Plugin protobuf line is omitted — the stamp matched, so protoc is not invoked and the previously generated sources are used directly.

Plugin protocol (for plugin authors)

A plugin is any binary that implements two subcommands. The full stdin/stdout protocol is described here so you can write your own.

Subcommand 1: manifest

Called on every build, even when up-to-date. Must be fast — no I/O other than reading stdin.

invocation
curie-<name> manifest --project <dir>

Stdin — JSON envelope:

stdin (manifest)
{
  "curie_version": "0.6.0",
  "config": {
    "version": "3.25.0",
    "sourceDir": "proto"
  }
}

config is the full [plugin.<name>] TOML tree converted to JSON, including nested tables. Ignore unknown top-level fields for forward-compatibility.

Stdout — manifest JSON:

stdout (manifest response)
{
  "name":        "protobuf",
  "description": "Generate Java stubs from .proto files",
  "version":     "0.1.0",
  "types":       ["source-generator"],
  "inputs": {
    "dirs":       ["proto"],
    "file_regex": "\\.proto$"
  },
  "outputs": {
    "source_dirs": ["target/generated-sources/protobuf"]
  },
  "artifacts": [
    {
      "id":         "protoc",
      "group":     "com.google.protobuf",
      "artifact":  "protoc",
      "version":   "3.25.0",
      "classifier":"linux-x86_64",
      "extension": "exe",
      "executable":true
    }
  ]
}
FieldMeaning
typesCapability array. Only "source-generator" is acted on today; list multiple to serve future roles.
inputs.dirsDirectories Curie watches for new/removed/modified files.
inputs.file_regexOptional regex filter applied to files within dirs.
inputs.filesOptional explicit file list, used instead of (or in addition to) dirs.
outputs.source_dirsDirectories added to javac's source root list after generation.
artifacts[].idLogical key used in the generate-sources call's artifact map.
artifacts[].classifierPlatform-specific (the plugin detects OS/arch here).
artifacts[].executableCurie sets the executable bit on the cached file when true.

Subcommand 2: generate-sources

Called only when the stamp is stale. Curie has already downloaded all declared artifacts before this call.

invocation
curie-<name> generate-sources --project <dir> [--offline]

Stdin — the same envelope as manifest, extended with artifacts:

stdin (generate-sources)
{
  "curie_version": "0.6.0",
  "config": { "version": "3.25.0", "sourceDir": "proto" },
  "artifacts": {
    "protoc": "/home/user/.m2/repository/com/google/protobuf/protoc/3.25.0/protoc-3.25.0-linux-x86_64.exe"
  }
}

Write progress to stderr. Exit 0 on success. Curie writes the stamp after a successful exit.

Artifact caching

Curie caches downloaded artifacts in the standard Maven local repository layout:

cache path
~/.m2/repository/<group/path>/<artifact>/<version>/<artifact>-<version>[-<classifier>].<ext>

Downloads are verified against a SHA-1 sidecar fetched from the same repository. A cached file is never re-downloaded. The --offline flag passed to generate-sources reflects curie build --offline; the plugin can use it to skip any network calls of its own.

OpenAPI plugin

curie-openapi generates Java model and API sources from an OpenAPI 3.x spec using openapi-generator-cli. The CLI JAR is cross-platform; no native binary or platform classifier is needed.

Installation

terminal
cargo install curie-openapi

Configuration

Curie.toml
[plugin.openapi]
version       = "7.2.0"        # openapi-generator-cli version
specFile      = "api/greeter.yaml"
generatorName = "java"

# optional
modelPackage   = "com.example.model"
apiPackage     = "com.example.api"
invokerPackage = "com.example"
outputDir      = "target/generated-sources/openapi"  # default
sourceFolder   = "src/main/java"                     # default

[plugin.openapi.additionalProperties]
library                 = "native"  # use java.net.http + Jackson; no OkHttp needed
openApiNullable         = "false"
hideGenerationTimestamp = "true"

[plugin.openapi.globalProperties]
models          = ""
supportingFiles = ""

All keys under [plugin.openapi.additionalProperties] are forwarded to openapi-generator as --additional-properties key=val,.... Keys under [plugin.openapi.globalProperties] are passed as repeated --global-property key=val flags.

Generated sources land in target/generated-sources/openapi/src/main/java/ and are added to javac's source root automatically. The specFile mtime is stamped so protoc-style incremental builds work: regeneration only happens when the spec file changes.

Build output

$ curie build
Building openapi-greeter v0.1.0
  Plugin openapi  greeter.yaml
  Resolve deps    6 JAR(s)
  Compile         14 source file(s)  [no class files]
  Package         openapi-greeter-0.1.0.jar
  Done            target/openapi-greeter-0.1.0.jar

Example: proto-greeter

The examples/proto-greeter project shows the full setup end-to-end.

proto/greeter.proto
syntax = "proto3";

option java_package = "com.example.greeter";
option java_multiple_files = true;

message GreetRequest  { string name    = 1; }
message GreetResponse { string message = 1; }
src/com.example/Greeter.java
package com.example;

import com.example.greeter.GreetRequest;
import com.example.greeter.GreetResponse;

public class Greeter {
    public static GreetResponse greet(String name) {
        return GreetResponse.newBuilder()
            .setMessage("Hello, " + name + "!")
            .build();
    }
}