Языки: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
Драйвер превращает командную строку в набор выполняемых заданий.
PluginDriver.h раскрывает этот конвейер в виде шести фаз и одной таблицы
возможностей NevercDriverAPI, так что плагин может переписывать аргументы,
выбирать инструментальную цепочку, перестраивать граф действий, добавлять или
заменять задания и даже выполнять задание внутри текущего процесса вместо
порождения нового.
#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 — это одна плоская таблица из 67 функциональных слотов,
сгруппированных в пять областей: сырые аргументы, разобранные опции, выбор
инструментальной цепочки, граф действий и граф заданий. Проверяйте TableSize
по смещению последнего слота, который вы используете, — сейчас хвостом является
GetJobResult.
| Фаза | Политика | Вход → выход |
|---|---|---|
neverc.driver.raw_arguments |
OBSERVABLE, INTERCEPTABLE | argv → argv |
neverc.driver.parsed_arguments |
OBSERVABLE, INTERCEPTABLE | список разобранных опций → список разобранных опций |
neverc.driver.select_toolchain |
плюс REPLACEABLE | запрос цепочки → выбор цепочки |
neverc.driver.build_actions |
плюс REPLACEABLE | запрос → граф действий |
neverc.driver.build_jobs |
плюс REPLACEABLE | граф действий → граф заданий |
neverc.driver.execute_job |
плюс REPLACEABLE | запрос на выполнение → результат задания |
Их макросы следуют обычному образцу:
NEVERC_PHASE_DRIVER_RAW_ARGUMENTS_{NAME,HIGH,LOW,POLICY,…}.
Опции объявляются ровно один раз, во время Register, после чего драйвер
принимает их в командной строке точно так же, как если бы они были встроенными.
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;Из 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 вызывается для каждого вхождения и получает
NevercOptionValidationContext с идентификатором плагина, написанием, целевым
триплетом и индексом вхождения, так что значение можно отвергнуть настоящей
диагностикой, а не провалиться позже. TargetPredicate ограничивает опцию
подходящими триплетами. Значения читаются обратно через
NevercCoreAPI.GetPluginOptionValueCount и GetPluginOptionValue.
На фазе neverc.driver.raw_arguments артефактом является вектор argv. Чтение
идёт по индексу, и каждая запись сообщает своё происхождение:
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);Редактирование транзакционно и допустимо только из перехватчика, потому что мутация привязана к продолжению:
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); /* или Abort */neverc.driver.parsed_arguments работает с вхождениями опций, а не со
строками, — именно это нужно, когда добавляется флаг, который не должен
подвергаться повторному лексическому разбору:
typedef struct NevercOptionOccurrence {
NevercABITableHeader Header;
uint64_t Occurrence;
NevercStringView Spelling;
NevercStringList Values;
NevercArgumentOrigin Origin;
uint32_t Reserved;
} NevercOptionOccurrence;Читают GetOptionOccurrenceCount и GetOptionOccurrence; затем редактируют
BeginParsedArgumentMutation, AddOptionOccurrence,
RemoveOptionOccurrence, ReplaceOptionOccurrence и
CommitParsedArgumentMutation / AbortParsedArgumentMutation.
Запрос описывает и то, что было запрошено, и то, что вычислил драйвер:
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;Перехватчик может подправить запрос через BeginToolChainMutation,
SetToolChainTriple, SetToolChainCPU, SetToolChainFeatures и
CommitToolChainMutation. Провайдер же отвечает на фазу целиком через
CreateToolChainSelection, называя один из встроенных идентификаторов цепочки
или свой собственный:
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 читает результат обратно и сообщает
BuiltinProviderUsed, так что наблюдатель может понять, выиграл ли фазу плагин.
Узел действия — это типизированный шаг компиляции. Узлы ссылаются на входы драйвера и на другие узлы:
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 |
Чтение — через GetDriverInputCount / GetDriverInput,
GetActionNodeCount / GetActionNode / GetActionNodeInput и
GetActionRootCount / GetActionRoot.
Построение замещающего графа идёт через строитель и одну публикацию:
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 и
SetActionNodeBindArch редактируют строитель в работе. Чтобы подправить
существующий граф хоста, а не строить заново, используйте
BeginActionGraphMutation и CommitActionGraphMutation;
AbortActionGraphEdit отбрасывает любую из форм.
Задание — это команда для выполнения. NevercJobDescriptor описывает одно:
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;Задайте Kind равным NEVERC_JOB_PLUGIN вместе с Callback, и драйвер
выполнит вашу функцию там, где иначе породил бы процесс:
static NevercStatus NEVERC_CALL run_job(const NevercPluginJobContext *Context,
int32_t *OutExitCode, void *UserData) {
/* Context->Arguments, ->Environment, ->Inputs, ->Outputs — заимствованы. */
*OutExitCode = 0;
return neverc_status_ok();
}Чтение графа зеркалит граф действий: GetJobCount / GetJob,
GetJobDependency, GetJobArgument / GetJobEnvironment, GetJobInput /
GetJobOutput. Учтите, что NevercJob сообщает только количества — забирайте
каждую строку или файл по индексу, а не рассчитывайте на встроенный массив.
Редактирование начинается с CreateJobGraphBuilder или
BeginJobGraphMutation, далее — AddJob, RemoveJob, MoveJobBefore,
ReplaceJob, SetJobArgument, SetJobEnvironment, SetJobInput,
SetJobOutput и ReplaceJobDependencies. Публикация — PublishJobGraph или
CommitJobGraphMutation; отказ — AbortJobGraphEdit.
На фазе neverc.driver.execute_job входным артефактом служит
NevercJobExecutionRequest — само задание плюс полностью материализованные
списки аргументов, окружения, входов, выходов и зависимостей. Провайдер
выполняет задание и сообщает результат:
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 несёт дескрипторы NevercOutputSealHandle, созданные через
API ввода-вывода (см. Source и ввод-вывод);
именно так хост подтверждает, что файлы, которые задание заявило записанными,
действительно существуют с указанными дайджестами. GetJobResult читает
зафиксированный результат и, как и выбор цепочки, сообщает
BuiltinProviderUsed.
Сжато из pluginsdk/examples/DriverTracePlugin.c. Плагин не держит глобальных
переменных: состояние процесса хранит согласованные таблицы, а счётчики уровня
сессии и задачи запрашиваются у хоста внутри каждого обратного вызова.
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();
}Регистрация связывает обе функции с их фазами:
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);Сборка и запуск:
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- Мутации аргументов, разобранных аргументов, инструментальной цепочки, графа
действий и графа заданий требуют
NevercPhaseContinuationперехватчика; вне него они отвергаются сNEVERC_STATUS_WRONG_SCOPE. - Вызывайте
InvokeNextне более одного раза и только в потоке обратного вызова. - Каждый дескриптор мутации должен дойти ровно до одного
Commit*илиAbort*. - Представления, возвращённые вызовом
Get*, заимствованы на время обратного вызова. Копируйте всё, что нужно сохранить. - Обратный вызов
NEVERC_JOB_PLUGINне должен порождать процесс, который породил бы хост, и при этом сообщать об успехе встроенного пути; объявитеREPLACEи отвечайте за исход сами. - О неудачном задании сообщайте через
NevercJobResultDescriptor.ExecutionFailedиErrorMessage, а не возвратом статуса, отличного от OK, для задания, которое выполнилось и закономерно завершилось неудачей.
Нормативные объявления смотрите в PluginDriver.h, политики фаз драйвера — в
PhaseSchema.json, свидетельства тестов — в coverage.json.