Sprachen: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
Der Driver verwandelt eine Kommandozeile in eine Menge ausgeführter Jobs.
PluginDriver.h legt diese Pipeline als sechs Phasen und eine
Capability-Tabelle NevercDriverAPI offen, sodass ein Plugin Argumente
umschreiben, eine Toolchain wählen, den Aktionsgraphen umbauen, Jobs hinzufügen
oder ersetzen und einen Job sogar prozessintern ausführen kann, statt einen
Prozess zu starten.
#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 ist eine flache Tabelle mit 67 Funktions-Slots, gegliedert in
fünf Bereiche: rohe Argumente, geparste Optionen, Toolchain-Auswahl,
Aktionsgraph und Job-Graph. Prüfen Sie TableSize gegen den Offset des letzten
Slots, den Sie verwenden — das aktuelle Ende ist GetJobResult.
| Phase | Policy | Eingabe → Ausgabe |
|---|---|---|
neverc.driver.raw_arguments |
OBSERVABLE, INTERCEPTABLE | argv → argv |
neverc.driver.parsed_arguments |
OBSERVABLE, INTERCEPTABLE | geparste Optionsliste → geparste Optionsliste |
neverc.driver.select_toolchain |
zusätzlich REPLACEABLE | Toolchain-Anfrage → Toolchain-Auswahl |
neverc.driver.build_actions |
zusätzlich REPLACEABLE | Anfrage → Aktionsgraph |
neverc.driver.build_jobs |
zusätzlich REPLACEABLE | Aktionsgraph → Job-Graph |
neverc.driver.execute_job |
zusätzlich REPLACEABLE | Job-Ausführungsanfrage → Job-Ergebnis |
Die zugehörigen Makros folgen dem üblichen Muster:
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_{NAME,HIGH,LOW,POLICY,…}.
Optionen werden genau einmal während Register deklariert; danach akzeptiert
der Driver sie auf der Kommandozeile so, als wären sie eingebaut.
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;Aus 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 wird pro Vorkommen mit einem NevercOptionValidationContext
aufgerufen, der Plugin-ID, Schreibweise, Ziel-Triple und Vorkommensindex trägt.
Ein Wert lässt sich damit mit einer echten Diagnose ablehnen, statt später zu
scheitern. TargetPredicate beschränkt eine Option auf passende Triples. Werte
liest man mit NevercCoreAPI.GetPluginOptionValueCount und
GetPluginOptionValue zurück.
Bei neverc.driver.raw_arguments ist das Artefakt der argv-Vektor. Gelesen wird
indexbasiert, und jeder Eintrag meldet seine Herkunft:
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);Das Bearbeiten ist transaktional und nur aus einem Interceptor heraus zulässig, weil die Mutation an die Continuation gebunden ist:
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); /* oder Abort */neverc.driver.parsed_arguments arbeitet auf Optionsvorkommen statt auf
Zeichenketten — genau das, was man braucht, wenn man ein Flag hinzufügt, das
nicht erneut lexikalisch zerlegt werden darf:
typedef struct NevercOptionOccurrence {
NevercABITableHeader Header;
uint64_t Occurrence;
NevercStringView Spelling;
NevercStringList Values;
NevercArgumentOrigin Origin;
uint32_t Reserved;
} NevercOptionOccurrence;GetOptionOccurrenceCount und GetOptionOccurrence lesen; danach bearbeiten
BeginParsedArgumentMutation, AddOptionOccurrence,
RemoveOptionOccurrence, ReplaceOptionOccurrence sowie
CommitParsedArgumentMutation / AbortParsedArgumentMutation.
Die Anfrage beschreibt, was verlangt wurde und was der Driver berechnet hat:
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;Ein Interceptor kann die Anfrage mit BeginToolChainMutation,
SetToolChainTriple, SetToolChainCPU, SetToolChainFeatures und
CommitToolChainMutation anpassen. Ein Provider beantwortet die Phase
stattdessen direkt mit CreateToolChainSelection und benennt eine der
eingebauten Toolchain-IDs oder seine eigene:
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 liest das Ergebnis zurück und meldet
BuiltinProviderUsed, sodass ein Observer erkennt, ob ein Plugin die Phase für
sich entschieden hat.
Ein Aktionsknoten ist ein typisierter Kompilierschritt. Knoten referenzieren Driver-Eingaben und andere Knoten:
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 |
Gelesen wird mit GetDriverInputCount / GetDriverInput,
GetActionNodeCount / GetActionNode / GetActionNodeInput sowie
GetActionRootCount / GetActionRoot.
Ein Ersatzgraph entsteht über einen Builder und wird einmal veröffentlicht:
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 und
SetActionNodeBindArch bearbeiten einen laufenden Builder. Um den bestehenden
Graphen des Hosts anzupassen, statt ihn neu zu bauen, verwenden Sie
BeginActionGraphMutation und CommitActionGraphMutation;
AbortActionGraphEdit verwirft beide Formen.
Ein Job ist ein auszuführendes Kommando. NevercJobDescriptor beschreibt eines:
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;Setzen Sie Kind auf NEVERC_JOB_PLUGIN und geben Sie einen Callback an —
dann führt der Driver Ihre Funktion dort aus, wo er sonst einen Prozess starten
würde:
static NevercStatus NEVERC_CALL run_job(const NevercPluginJobContext *Context,
int32_t *OutExitCode, void *UserData) {
/* Context->Arguments, ->Environment, ->Inputs, ->Outputs sind geliehen. */
*OutExitCode = 0;
return neverc_status_ok();
}Das Lesen des Graphen entspricht dem Aktionsgraphen: GetJobCount / GetJob,
GetJobDependency, GetJobArgument / GetJobEnvironment, GetJobInput /
GetJobOutput. Beachten Sie, dass NevercJob nur Anzahlen meldet — holen Sie
jede Zeichenkette und jede Datei über den Index, statt ein eingebettetes Array
zu erwarten.
Bearbeitet wird über CreateJobGraphBuilder oder BeginJobGraphMutation und
dann AddJob, RemoveJob, MoveJobBefore, ReplaceJob, SetJobArgument,
SetJobEnvironment, SetJobInput, SetJobOutput und
ReplaceJobDependencies. Veröffentlichen mit PublishJobGraph oder
CommitJobGraphMutation; verwerfen mit AbortJobGraphEdit.
Bei neverc.driver.execute_job ist das Eingabeartefakt ein
NevercJobExecutionRequest — der Job samt seiner vollständig materialisierten
Argument-, Umgebungs-, Eingabe-, Ausgabe- und Abhängigkeitslisten. Ein Provider
führt den Job aus und meldet das Ergebnis:
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 trägt die über die I/O-API erzeugten NevercOutputSealHandles
(siehe Source und I/O). So bestätigt der
Host, dass die Dateien, die ein Job zu schreiben behauptete, tatsächlich mit
den gemeldeten Digests existieren. GetJobResult liest ein committetes
Ergebnis und meldet wie die Toolchain-Auswahl BuiltinProviderUsed.
Verdichtet aus pluginsdk/examples/DriverTracePlugin.c. Das Plugin hält keine
globalen Variablen: Der Prozesszustand hält die ausgehandelten Tabellen, und die
Zähler pro Session und pro Task werden in jedem Callback vom Host geholt.
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();
}Die Registrierung verdrahtet beide mit ihren Phasen:
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);Bauen und ausführen:
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- Mutationen an Argumenten, geparsten Argumenten, Toolchain, Aktionsgraph und
Job-Graph benötigen alle die
NevercPhaseContinuationdes Interceptors; außerhalb davon werden sie mitNEVERC_STATUS_WRONG_SCOPEabgewiesen. - Rufen Sie
InvokeNexthöchstens einmal und nur auf dem Callback-Thread auf. - Jedes Mutations-Handle muss genau ein
Commit*oderAbort*erreichen. - Von einem
Get*-Aufruf zurückgegebene Sichten sind für die Dauer des Callbacks geliehen. Kopieren Sie, was Sie behalten wollen. - Ein
NEVERC_JOB_PLUGIN-Callback darf nicht den Prozess starten, den der Host gestartet hätte, und zusätzlich Erfolg für den eingebauten Pfad melden; deklarieren SieREPLACEund verantworten Sie das Ergebnis selbst. - Melden Sie einen fehlgeschlagenen Job über
NevercJobResultDescriptor.ExecutionFailedundErrorMessage, statt für einen Job, der lief und berechtigt fehlschlug, einen Nicht-OK-Status zurückzugeben.
Die normativen Deklarationen stehen in PluginDriver.h, die Policies der
Driver-Phasen in PhaseSchema.json und die Testnachweise in coverage.json.