Languages: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
The driver turns a command line into a set of executed jobs. PluginDriver.h
exposes that pipeline as six phases and one capability table,
NevercDriverAPI, so a plugin can rewrite arguments, choose a toolchain,
restructure the action graph, add or replace jobs, and even run a job
in-process instead of spawning a process.
#include "neverc/Plugin/PluginDriver.h"
Bootstrap->QueryInterface(
Bootstrap->Context,
(NevercInterfaceID){NEVERC_INTERFACE_DRIVER_HIGH,
NEVERC_INTERFACE_DRIVER_LOW},
NEVERC_DRIVER_API_MAJOR, NEVERC_DRIVER_API_MINOR,
&Table, &Minor, &TableSize);NevercDriverAPI is one flat table of 67 function slots grouped into five
areas: raw arguments, parsed options, toolchain selection, the action graph,
and the job graph. Validate TableSize against the offset of the last slot
you use — GetJobResult is the current tail.
| Phase | Policy | Input → output |
|---|---|---|
neverc.driver.raw_arguments |
OBSERVABLE, INTERCEPTABLE | argv → argv |
neverc.driver.parsed_arguments |
OBSERVABLE, INTERCEPTABLE | parsed option list → parsed option list |
neverc.driver.select_toolchain |
+ REPLACEABLE | toolchain request → toolchain selection |
neverc.driver.build_actions |
+ REPLACEABLE | request → action graph |
neverc.driver.build_jobs |
+ REPLACEABLE | action graph → job graph |
neverc.driver.execute_job |
+ REPLACEABLE | job execution request → job result |
Their macros follow the usual pattern:
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_{NAME,HIGH,LOW,POLICY,…}.
Options are declared once, during Register, and the driver then accepts them
on the command line exactly as if they were built in.
typedef struct NevercOptionDescriptor {
NevercABITableHeader Header;
NevercStringView Spelling;
NevercStringList Aliases;
NevercOptionForm Form; /* FLAG, JOINED, SEPARATE, MULTI_ARG */
NevercOptionValueType ValueType; /* BOOL, INT, UINT, STRING, ENUM, PATH */
NevercOptionMultiplicity Multiplicity; /* SINGLE, LAST_WINS, APPEND */
uint32_t ArgumentCount;
NevercBool Required;
NevercBool Hidden;
NevercStringView Help;
NevercStringView Metavar;
NevercStructArrayView EnumValues; /* NevercOptionEnumValue[] */
NevercStringList Conflicts;
NevercStringList Requires;
NevercStringView TargetPredicate;
NevercOptionValidatorFn Validator;
void *UserData;
NevercDestroyUserDataFn DestroyUserData;
} NevercOptionDescriptor;From pluginsdk/examples/DriverTracePlugin.c:
NevercOptionDescriptor Option = {0};
Option.Header = (NevercABITableHeader){sizeof(Option), NEVERC_DRIVER_API_MAJOR,
NEVERC_DRIVER_API_MINOR, 0};
Option.Spelling = SV("--driver-trace");
Option.Form = NEVERC_OPTION_FLAG;
Option.ValueType = NEVERC_OPTION_BOOL;
Option.Multiplicity = NEVERC_OPTION_SINGLE;
Option.Help = SV("enable the driver trace example plugin");
Status = Registrar->RegisterOption(RegistrarContext, &Option);Validator is called per occurrence with a NevercOptionValidationContext
carrying the plugin ID, spelling, target triple, and occurrence index, so a
value can be rejected with a real diagnostic instead of failing later.
TargetPredicate restricts an option to matching triples. Read values back
with NevercCoreAPI.GetPluginOptionValueCount and GetPluginOptionValue.
At neverc.driver.raw_arguments the artifact is the argv vector. Reading is
index-based, and each entry reports where it came from:
Driver->GetArgumentCount(Driver->Context, Frame, Frame->Input, &Count);
NevercStringView Value, Source;
NevercArgumentOrigin Origin; /* COMMAND_LINE, CONFIGURATION, PLUGIN */
uint64_t Position;
Driver->GetArgument(Driver->Context, Frame, Frame->Input, Index,
&Value, &Origin, &Source, &Position);Editing is transactional and only legal from an interceptor, because the mutation is bound to the continuation:
NevercArgumentMutationHandle Mutation;
Driver->BeginArgumentMutation(Driver->Context, Frame, Continuation,
Frame->Input, &Mutation);
Driver->InsertArgument(Driver->Context, Mutation, Index, SV("-O2"));
Driver->ReplaceArgument(Driver->Context, Mutation, Index, SV("-O3"));
Driver->EraseArgument(Driver->Context, Mutation, Index);
Driver->CommitArgumentMutation(Driver->Context, Mutation); /* or Abort */neverc.driver.parsed_arguments works on option occurrences rather than
strings, which is what you want when adding a flag that must not be
re-lexed:
typedef struct NevercOptionOccurrence {
NevercABITableHeader Header;
uint64_t Occurrence;
NevercStringView Spelling;
NevercStringList Values;
NevercArgumentOrigin Origin;
uint32_t Reserved;
} NevercOptionOccurrence;GetOptionOccurrenceCount and GetOptionOccurrence read; then
BeginParsedArgumentMutation, AddOptionOccurrence,
RemoveOptionOccurrence, ReplaceOptionOccurrence, and
CommitParsedArgumentMutation / AbortParsedArgumentMutation edit.
The request describes what was asked for and what the driver computed:
typedef struct NevercToolChainRequest {
NevercABITableHeader Header;
NevercStringView RequestedTriple;
NevercStringView ComputedTriple;
NevercStringView SysRoot;
NevercStringView ResourceDir;
NevercStringView CPU;
NevercStringList Features;
NevercExecutionLevel ExecutionLevel; /* UNSPECIFIED, USER, KERNEL */
NevercBool DynamicCodeProfile;
uint32_t Reserved;
} NevercToolChainRequest;An interceptor can nudge the request with BeginToolChainMutation,
SetToolChainTriple, SetToolChainCPU, SetToolChainFeatures, and
CommitToolChainMutation. A provider instead answers the phase outright with
CreateToolChainSelection, naming one of the built-in toolchain IDs or its
own:
NEVERC_TOOLCHAIN_ID_DARWIN /* "neverc.builtin.darwin" */
NEVERC_TOOLCHAIN_ID_LINUX /* "neverc.builtin.linux" */
NEVERC_TOOLCHAIN_ID_MSVC /* "neverc.builtin.msvc" */
NEVERC_TOOLCHAIN_ID_GENERIC_ELF /* "neverc.builtin.generic-elf" */
NEVERC_TOOLCHAIN_ID_MACHO /* "neverc.builtin.macho" */
NEVERC_TOOLCHAIN_ID_GENERIC_GCC /* "neverc.builtin.generic-gcc" */GetToolChainSelection reads the result back and reports
BuiltinProviderUsed, so an observer can tell whether a plugin won the phase.
An action node is a typed compilation step. Nodes reference driver inputs and other nodes:
typedef struct NevercActionNode {
NevercABITableHeader Header;
NevercActionNodeID Node;
NevercActionKind Kind;
NevercDriverType OutputType;
uint64_t InputCount;
NevercDriverInputID DriverInput;
NevercStringView BindArch;
uint64_t Reserved;
} NevercActionNode;NevercActionKind |
NevercDriverType |
||
|---|---|---|---|
INPUT |
BIND_ARCH |
PP_C, C, C_HEADER |
PP_ASM, ASM |
PREPROCESS |
COMPILE |
LLVM_IR, LLVM_BC |
LTO_IR, LTO_BC |
BACKEND |
ASSEMBLE |
OBJECT, IMAGE |
DSYM |
LINK, LIPO |
DSYMUTIL |
DEPENDENCIES |
NOTHING |
STATIC_LIB |
DYNCODE |
Read with GetDriverInputCount / GetDriverInput, GetActionNodeCount /
GetActionNode / GetActionNodeInput, and GetActionRootCount /
GetActionRoot.
Building a replacement graph goes through a builder, then a single publish:
NevercActionGraphBuilderHandle Builder;
Driver->CreateActionGraphBuilder(Driver->Context, Frame, Request, &Builder);
NevercActionNodeDescriptor Node = {0};
Node.Header = (NevercABITableHeader){sizeof(Node), NEVERC_DRIVER_API_MAJOR,
NEVERC_DRIVER_API_MINOR, 0};
Node.Kind = NEVERC_ACTION_COMPILE;
Node.OutputType = NEVERC_DRIVER_TYPE_OBJECT;
Node.Inputs = /* NevercActionNodeIDList */;
NevercActionNodeID Created;
Driver->AddActionNode(Driver->Context, Builder, &Node, &Created);
Driver->SetActionRoots(Driver->Context, Builder, Roots);
Driver->PublishActionGraph(Driver->Context, Frame, Builder, &OutGraph);RemoveActionNode, ReplaceActionNodeInputs, SetActionNodeOutputType, and
SetActionNodeBindArch edit an in-progress builder. To adjust the host's
existing graph instead of rebuilding it, use BeginActionGraphMutation and
CommitActionGraphMutation; AbortActionGraphEdit discards either form.
A job is a command to run. NevercJobDescriptor describes one:
typedef struct NevercJobDescriptor {
NevercABITableHeader Header;
NevercJobKind Kind; /* COMMAND, FRONTEND, LINKER,
ARCHIVE, PLUGIN, DYNCODE */
NevercResponseFileKind ResponseFileKind; /* NONE, FULL, LIST */
NevercResponseFileEncoding ResponseFileEncoding;/* UTF8, CURRENT_CODE_PAGE,
UTF16 */
NevercBool InProcess;
NevercActionNodeID SourceAction;
NevercLinkerFlavor LinkerFlavor; /* NONE, GNU, WIN_LINK, DARWIN */
uint32_t Reserved;
NevercStringView Executable;
NevercStringList Arguments;
NevercStringList Environment;
NevercJobFileList Inputs;
NevercJobFileList Outputs;
NevercJobIDList Dependencies;
NevercStringView CallbackID;
NevercPluginJobCallbackFn Callback;
void *UserData;
} NevercJobDescriptor;Set Kind to NEVERC_JOB_PLUGIN with a Callback and the driver runs your
function where it would otherwise spawn a process:
static NevercStatus NEVERC_CALL run_job(const NevercPluginJobContext *Context,
int32_t *OutExitCode, void *UserData) {
/* Context->Arguments, ->Environment, ->Inputs, ->Outputs are borrowed. */
*OutExitCode = 0;
return neverc_status_ok();
}Reading the graph mirrors the action graph: GetJobCount / GetJob,
GetJobDependency, GetJobArgument / GetJobEnvironment, GetJobInput /
GetJobOutput. Note that NevercJob reports only counts — fetch each string
or file by index rather than expecting an inline array.
Editing uses CreateJobGraphBuilder or BeginJobGraphMutation, then
AddJob, RemoveJob, MoveJobBefore, ReplaceJob, SetJobArgument,
SetJobEnvironment, SetJobInput, SetJobOutput, and
ReplaceJobDependencies. Publish with PublishJobGraph or
CommitJobGraphMutation; discard with AbortJobGraphEdit.
At neverc.driver.execute_job the input artifact is a
NevercJobExecutionRequest — the job plus its fully materialized argument,
environment, input, output, and dependency lists. A provider runs the job and
reports the result:
typedef struct NevercJobResultDescriptor {
NevercABITableHeader Header;
int32_t ExitCode;
NevercBool ExecutionFailed;
NevercBool HasProcessStatistics;
uint32_t Reserved;
NevercStringView ErrorMessage;
NevercOutputSealList OutputSeals;
uint64_t TotalTimeMicroseconds;
uint64_t UserTimeMicroseconds;
uint64_t PeakMemoryKiB;
} NevercJobResultDescriptor;OutputSeals carries the NevercOutputSealHandles produced through the I/O
API (see Source and I/O), which is how the host
confirms that the files a job claimed to write actually exist with the digests
reported.
GetJobResult reads a committed result and, like the toolchain selection,
reports BuiltinProviderUsed.
Condensed from pluginsdk/examples/DriverTracePlugin.c. The plugin keeps no
globals: process state holds the negotiated tables, and per-session and
per-task counters are fetched from the host inside each callback.
static NevercStatus NEVERC_CALL
observe_arguments(const NevercPhaseFrame *Frame, NevercObserverPoint Point,
void *UserData) {
DriverTraceProcessState *Process = (DriverTraceProcessState *)UserData;
DriverTraceSessionState *Session = NULL;
uint64_t ArgumentCount = 0;
NevercStatus Status;
if (Frame == NULL || Process == NULL)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
Status = Process->Core->GetSessionState(Process->Core->Context,
Frame->Session, plugin_id(),
(void **)&Session);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
Status = Process->Driver->GetArgumentCount(Process->Driver->Context, Frame,
Frame->Input, &ArgumentCount);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
++Session->ArgumentCallbacks;
if (Point == NEVERC_OBSERVER_BEFORE && !Session->Announced) {
Session->Announced = NEVERC_TRUE;
return emit_trace_remark(Process, Frame, "driver argument phase observed",
30, 1001);
}
return neverc_status_ok();
}
static NevercStatus NEVERC_CALL
intercept_job(const NevercPhaseFrame *Frame,
NevercPhaseContinuation *Continuation,
NevercPhaseResult *OutResult, void *UserData) {
DriverTraceProcessState *Process = (DriverTraceProcessState *)UserData;
NevercJobExecutionRequest Request = {0};
NevercPhaseResult Downstream = {0};
NevercStatus Status;
if (Frame == NULL || Continuation == NULL || OutResult == NULL || !Process)
return status_code(NEVERC_STATUS_INVALID_ARGUMENT);
Request.Header = (NevercABITableHeader){
sizeof(Request), NEVERC_DRIVER_API_MAJOR, NEVERC_DRIVER_API_MINOR, 0};
Status = Process->Driver->GetJobExecutionRequest(
Process->Driver->Context, Frame, Frame->Input, &Request);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
Downstream.Header = (NevercABITableHeader){
sizeof(Downstream), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
Status = Continuation->InvokeNext(Continuation, Frame, &Downstream);
if (Status.Code != NEVERC_STATUS_OK)
return Status;
*OutResult = (NevercPhaseResult){0};
OutResult->Header = (NevercABITableHeader){
sizeof(*OutResult), NEVERC_PLUGIN_ABI_MAJOR, NEVERC_PLUGIN_ABI_MINOR, 0};
OutResult->Action = NEVERC_PHASE_CONTINUE;
return neverc_status_ok();
}Registration wires both to their phases:
Observer.Phase = phase_id(NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_HIGH,
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_LOW);
Observer.Points = NEVERC_OBSERVER_BEFORE | NEVERC_OBSERVER_AFTER;
Observer.Callback = observe_arguments;
Observer.UserData = Process;
Registrar->RegisterObserver(RegistrarContext, &Observer);
Interceptor.Phase = phase_id(NEVERC_PHASE_DRIVER_EXECUTE_JOB_HIGH,
NEVERC_PHASE_DRIVER_EXECUTE_JOB_LOW);
Interceptor.Callback = intercept_job;
Interceptor.UserData = Process;
Registrar->RegisterInterceptor(RegistrarContext, &Interceptor);Build and run it:
cmake --build build-neverc --target neverc-plugin-example-driver-trace
build-neverc/bin/neverc \
-fplugin=build-neverc/neverc/pluginsdk/examples/host/DriverTracePlugin.so \
--driver-trace -c input.c -o input.o- Argument, parsed-argument, toolchain, action-graph, and job-graph mutations
all require the interceptor's
NevercPhaseContinuation; they are rejected withNEVERC_STATUS_WRONG_SCOPEoutside one. - Call
InvokeNextat most once and only on the callback thread. - Every mutation handle must reach exactly one
Commit*orAbort*. - Views returned by a
Get*call are borrowed for the callback. Copy anything you need to keep. - A
NEVERC_JOB_PLUGINcallback must not spawn the process the host would have spawned and then also report success for the built-in path; declareREPLACEand own the outcome. - Report a failed job through
NevercJobResultDescriptor.ExecutionFailedandErrorMessagerather than returning a non-OK status for a job that ran and legitimately failed.
See PluginDriver.h for the normative declarations, PhaseSchema.json for
the driver phase policies, and coverage.json for the test evidence.