Ориентация главы
Том 12 начинается с инструментальной цепочки. Сборка XRT — не рукописный Makefile, а Python-инструментарий, где config/modules.json — единственный источник фактов: манифест объявляет заголовки/источники/тесты/примеры/документацию каждого модуля (включая замыкание зависимостей), а tools/build.py ведёт из него модульную компиляцию (источники по модулям, объекты кэшируются по отпечатку замыкания) и регрессию однозаголовочного вида (tools/amalgamate.py синтезирует single/xrt.h, затем перекомпилирует все тесты) — двухтрековая проверка. Глава проясняет четыре вещи: структуру манифеста (schema/зависимости/три класса тестовых активов: tests/single_tests/examples); CLI-грань build.py (выбор suite/фильтрация тестов/параллельные jobs/инъекция cflag/наложение манифеста — расширения делят ту же цепочку); смысл двух треков (модульный доказывает «обрезка компилируется», одиночный заголовок — «слияние не конфликтует»; каждый трек — релизный шлюз); кэш по отпечаткам (объекты перекомпилируются только при смене отпечатка замыкания зависимостей — корень корректности инкрементальной сборки).
130.1引入
У сборочных систем C-библиотек три хронические болезни: дрейф манифеста (CMakeLists и реальные исходники говорят по-разному — файл добавили, список забыли, или наоборот); псевдообрезка (заявлена конфигурируемость, но комбинации обрезки никогда не проверялись — взрыв комбинаций макросов свойств не тестирован); рассинхрон одиночного заголовка (заголовок ведётся руками, модуль изменили, слияние забыли). Ответ XRT: один JSON-манифест как источник всего — build (что компилировать), amalgamate (что сливать), package (что упаковывать), measure (что мерить) — всё отправляется от одного modules.json. «Манифест управляет всем» устраняет дрейф по построению: исходника нет в манифесте — он не компилируется; теста нет в манифесте — он не входит в регрессию.
О необходимости двух треков подробнее: модульный трек берёт замыкание зависимостей выбранного suite (скажем core,queue) и компилирует только те источники — проверяя «эта комбинация обрезки собирается и работает»; трек одиночного заголовка сперва сливает все выбранные модули в один single/xrt.h (amalgamate склеивает в топологическом порядке, дедуплицирует include, инлайнит локальные заголовки), затем компилирует те же тесты одной трансляцией с XRT_IMPLEMENTATION — проверяя «слитый одиночный заголовок без конфликтов символов/утечек макросов». Каждый PR проходит оба трека до слияния — это и есть реализационная грань «check + встраиваемые примеры компилируются в CI» из SPEC.
130.2概念
Манифест: декларационная карточка модуля
Манифест одновременно индекс документации (поле docs) и реестр эталонов производительности (поле benchmarks — на карточке модуля queue висят все скрипты и отчёты dev/bench/queue) — полный смысл «источника фактов»: компиляция, тесты, упаковка, измерение, списки документации — пять граней, одна карточка.
CLI-грань build.py
| Параметр | Назначение |
|---|---|
--suite core,queue | имена модулей/через запятую/all — замыкание зависимостей разворачивается автоматически |
--test test_queue_oom | прогнать один модульный тест |
--start-test/--start-single-test | продолжить регрессию с указанного теста (возобновление с контрольной точки — починил одно, гоняй остальное) |
--jobs 8 | число параллельных задач компиляции одиночно-заголовочных тестов (тесты всё равно идут в порядке манифеста — детерминизм) |
--no-single/--no-examples | пропуск треков (только модульный/только тестовый шлюз) |
--cflag/--ldflag | инъекция флагов компиляции/линковки (кросс-сценарии/sanitizer) |
--compiler tcc/--arch x86 | выбор компилятора и архитектуры (TinyCC 32 бита — тоже шлюзная комбинация) |
--manifest path | наложить манифест библиотеки-расширения |
--rebuild | игнорировать отпечатки и перекомпилировать принудительно |
Двухтрековая сборка и кэш по отпечаткам
Модульный трек: suite→замыкание→компиляция объектов по модулям (-DXRT_FEATURE_* генерируется из поля feature манифеста)→линковка→тесты в порядке манифеста; examples компилируются и запускаются по одному (встраиваемые в учебник примеры проверяются здесь — «встраиваемые примеры компилируются» из SPEC).Однозаголовочный трек: amalgamate даёт single/xrt.h (плюс декларационный xrt_decl.h)→single_tests компилируются с #define XRT_IMPLEMENTATION + include одиночного заголовка→запуск.Кэш по отпечаткам: объекты кэшируются по «отпечатку замыкания зависимостей» — любой источник/заголовок внутри замыкания изменился, отпечаток изменился, только тогда перекомпиляция; --rebuild аннулирует явно.Матрица архитектур: native/x86/x64 × gcc/tcc/cl — релизные шлюзы покрывают 32-битный TinyCC (представитель целей с ограниченными ресурсами).
Расширения делят инструментальную цепочку
У пяти библиотек-расширений глав 110–129 у каждой свой config/modules.json — с --manifest extlibs/xhttp/config/modules.json тот же build.py компилирует и тестирует замыкание расширения (включая «требуемое расширению ядро-обрезку»). README xruntime показывает полную шлюзовую последовательность: регрессия build, проверка согласованности amalgamate (--check), проверка зрелости релиза, верификация package — библиотека и расширения — одна цепочка, одна дисциплина.
130.3示例
Первая полная программа: пример, объявленный манифестом
Программа ниже из examples/core/version_limits — элемент массива examples манифеста (оба трека сборки его компилируют):
*/ #include <stdio.h> #include <xrt.h> int main(void) { xrtresourcelimits Limits; printf("version=%s\n", xrtVersion()); xrtResourceLimitsInit(&Limits); printf("limits: depth=%u entries=%llu input=%llu\n", (unsigned)Limits.iMaxDepth, (unsigned long long)Limits.iMaxEntries, (unsigned long long)Limits.iMaxInputBytes); return 0; }
Что только что произошло. (1) Этот файл присутствует в массиве examples карточки модуля core — python tools/build.py --suite core компилирует и запускает его; все встраиваемые в учебник примеры с главы 3 работают так же (код учебника и есть код шлюза — место машинного исполнения красных линий SPEC). (2) xrtVersion() и xrtResourceLimitsInit — API, объявленные в публичных заголовках модуля core; поле public_headers манифеста соответствует карточкам контрактов docs/api один к одному. (3) Умолчания ресурсных границ (глубина 128/100 тысяч записей/ввод 256 МиБ) — общая линия обороны всех парсеров — глава 133 вернётся строить на этих границах DoS-тесты.
Вторая полная программа: пример сборки распределителя
Вторая программа из examples/core/allocator_tour — проверка подключения собственного распределителя:
*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <xrt.h> /* 计数分配器:仍路由系统堆,便于验证替换生效。 */ typedef struct examplecount { size_t iAllocs; size_t iFrees; } examplecount; static ptr exampleAlloc(ptr pContext, size_t iSize) { ++((examplecount*)pContext)->iAllocs; return (ptr)malloc(iSize); } static ptr exampleRealloc(ptr pContext, ptr pMemory, size_t iSize) { (void)pContext; return (ptr)realloc(pMemory, iSize); } static void exampleFree(ptr pContext, ptr pMemory) { ++((examplecount*)pContext)->iFrees; free(pMemory); } int main(void) { xallocator Default; xallocator Custom; examplecount Count; uint8* pBlock; uint8* pDup; int iResult = 1; /* ---- 必须在任何 XRT 分配前完成替换。 ---- */ xrtGetAllocator(&Default); memset(&Count, 0, sizeof(Count)); Custom.Context = (ptr)&Count; Custom.Alloc = exampleAlloc; Custom.Realloc = exampleRealloc; Custom.Free = exampleFree; if ( !xrtSetAllocator(&Custom) ) { goto Cleanup; } /* ---- At 族五接口:位置参数不影响堆行为。 ---- */ pBlock = (uint8*)xrtMallocAt(4u, __FILE__, __LINE__); if ( (pBlock == NULL) || ((pDup = (uint8*)xrtMemDupAt("hi", 2u, __FILE__, __LINE__)) == NULL) || (memcmp(pDup, "hi", 2u) != 0) || ((pBlock = (uint8*)xrtReallocAt(pBlock, 16u, __FILE__, __LINE__)) == NULL) || (xrtCallocAt(2u, 4u, __FILE__, __LINE__) == NULL) ) { goto Cleanup; } /* Calloc 清零验证单独做——保留指针用于释放。 */ { uint8* pZero = (uint8*)xrtCallocAt(2u, 4u, __FILE__, __LINE__); if ( (pZero == NULL) || (pZero[0] != 0u) || (pZero[7] != 0u) ) { goto Cleanup; } xrtFreeAt(pZero, __FILE__, __LINE__); } xrtFreeAt(pDup, __FILE__, __LINE__); xrtFreeAt(pBlock, __FILE__, __LINE__); printf("allocator: swap -> at-family alloc/calloc/dup/realloc ok\n"); * 且 GetAllocator 仍报告已安装的计数分配器。 ---- */ * 分配才经过自定义 Alloc;计数只需 >= 1 即证明替换生效。 */ if ( xrtSetAllocator(&Default) || (Count.iAllocs < 1u) ) { goto Cleanup; } { xallocator Current; xrtGetAllocator(&Current); if ( (Current.Alloc != Custom.Alloc) || (Current.Free != Custom.Free) ) { goto Cleanup; } } printf("allocator: locked after first alloc -> swap rejected ok\n"); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) После xrtSetAllocator с собственным распределителем проходят четыре входа at-семейства (грань памяти главы 5 в регрессии на уровне примера). (2) Проверка семантики блокировки после первого распределения: заменённый распределитель больше не меняется — гарантия стабильности для встраивающих хозяев (глава 135). (3) Две строки вывода — один из продуктов двухтрековой сборки: манифест ведёт, шлюз исполняет — каждый пример учебника, который вы видели, прошёл эту цепочку.
130.4契约
- Единственный источник фактов: modules.json ведёт build/amalgamate/package/measure/doc — прежде добавить исходник, добавь манифест.
- Три класса активов: tests (модульные)/single_tests (одиночный заголовок)/examples (учебник и шлюз) — у каждого свой трек.
- Семантика замыкания: suite разворачивается в замыкание зависимостей; компиляция/обрезка/упаковка — по замыканию.
- Двухтрековый шлюз: модульный доказывает компилируемость обрезки, одиночный заголовок — бесконфликтность слияния — оба зелёные, только тогда слияние.
- Детерминированный порядок: тесты идут в порядке манифеста (jobs параллелит только компиляцию, не тесты).
- Кэш по отпечаткам: объекты кэшируются по отпечатку замыкания; --rebuild аннулирует явно.
- Матрица архитектур: native/x86/x64 × gcc/tcc/cl — TinyCC 32 бита есть шлюз ограниченных ресурсов.
- Возобновление с контрольной точки: start-test/start-single-test продолжают с точки обрыва — починил одно, гоняй остальное.
- Наложение расширений: --manifest накладывает манифесты расширений — библиотека и расширения — одна цепочка.
- Примеры есть шлюз: examples компилируются в обоих треках — код учебника без дополнительной поддержки.
Типичные ошибки
Симптом: «локально собирается» (IDE собирает всё), а модульный трек CI собирает неполное замыкание — ошибка линковки или хуже (символы другого модуля дают утечку).
Причина: IDE не смотрит манифест; build.py компилирует только перечисленное в манифесте. Разница «локально проходит» и «проходит шлюз» — ровно манифест.
/* создали src/foo/bar.c и закоммитили сразу - в манифесте его нет */ /* CI: замыкание модульного трека неполно - ошибка линковки */
/* в той же правке добавить файл в массив sources модуля foo в modules.json */ /* в diff PR запись манифеста и исходник рядом - согласованность видна на ревью */
Симптом: трек одиночного заголовка взрывается — внутренние символы/макросы двух модулей конфликтуют после слияния; single/xrt.h у пользователя не компилируется.
Причина: модульная компиляция изолирует конфликты частных имён между модулями; вскрыть их может только слияние в одиночный заголовок. Ни один трек нельзя пропускать.
python tools/build.py --suite my_module --no-single && merge
/* пропуск трека одиночного заголовка перед слиянием - конфликт взрывается в руках пользователя *//* перед слиянием PR: оба трека зелёные - --no-single только для быстрой итерации в разработке */ python tools/build.py --suite my_module /* включая трек одиночного заголовка */
Симптом: думали, --jobs 8 параллелит тесты — при порядке-зависимых тестах (общие порты/временные файлы) редкие взаимные помехи.
Причина: --jobs параллелит только компиляцию; тесты идут строго в порядке манифеста (детерминизм прежде всего — воспроизводимость отказа и есть ценность шлюза).
/* думали, --jobs 8 параллелит тесты - тесты на общих ресурсах изредка мешают друг другу */ /* факт: --jobs параллелит только компиляцию; тесты строго по манифесту */
/* хотите быстрее: прогоните подмножество, а не параллельно */ python tools/build.py --suite my_module --test test_queue_oom /* возобновление: --start-test продолжает с отказа */
Практические задания
python tools/build.py --suite core (без --no-single) — наблюдать вывод обоих треков. Критерии приёмки: модульные и одиночно-заголовочные тесты зелёные; examples компилируются по разу в каждом треке; вы можете указать, что кэш по отпечаткам работает (повторный запуск пропускает скомпилированные объекты).
--suite core,queue,queue_spsc — собрать и прогнать все тесты; затем сравнить время с --no-single. Критерии приёмки: в замыкании нет источников невыбранных модулей; тесты комбинации обрезки зелёные.
Прогоните по шагам шлюзовую последовательность из README xruntime (регрессия build / amalgamate --check / package --verify). Критерии приёмки: вы понимаете, что проверяет каждая команда; можете своими словами объяснить смысл «согласованности генерации» у amalgamate --check (повторная генерация даёт нулевое отличие от закоммиченного одиночного заголовка).
Итоги
| Тема | Кратко |
|---|---|
| Источник фактов | config/modules.json — компиляция/тесты/упаковка/измерение/документация, пять граней одна карточка |
| Три класса активов | tests (модульные)/single_tests (одиночный заголовок)/examples (учебник + шлюз) |
| Два трека | модульный доказывает обрезку, одиночный заголовок — слияние — оба зелёные, только тогда слияние |
| Кэш по отпечаткам | объекты по отпечатку замыкания; --rebuild аннулирует |
| Детерминизм | тесты в порядке манифеста; jobs параллелит только компиляцию |
| Возобновление | start-test/start-single-test |
| Матрица архитектур | native/x86/x64 × gcc/tcc/cl — TinyCC 32 бита есть шлюз |
| Наложение расширений | --manifest накладывает — пять расширений, одна цепочка |
| Примеры есть шлюз | examples компилируются в обоих треках — код учебника без второй реализации |
| cflag/ldflag | инъекция флагов компиляции/линковки (sanitizer/кросс) |
📖 Справочники API в этой главе:core (25 записей)error (34 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки