Ориентация главы

Том 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概念

Манифест: декларационная карточка модуля

1modules.json (schema 1)одна карточка на модуль
2Наложение расширенийextlibs/*/config/modules.json накладывается параметром --manifest

Манифест одновременно индекс документации (поле 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 манифеста (оба трека сборки его компилируют):

examples/core/version_limits/main.c源码 ↗
















 */

#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/core/version_limits/main.c -lws2_32 -liphlpapiversion=2.0.0-devlimits: depth=128 entries=100000 input=268435456

Что только что произошло. (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 — проверка подключения собственного распределителя:

examples/core/allocator_tour/main.c源码 ↗



















 */

#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/core/allocator_tour/main.c -lws2_32 -liphlpapiallocator: swap -> at-family alloc/calloc/dup/realloc okallocator: locked after first alloc -> swap rejected ok

Что только что произошло. (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 компилируются в обоих треках — код учебника без дополнительной поддержки.

Типичные ошибки

Ловушка 1: добавили исходник и забыли манифест:Ловушка 1: добавили исходник и забыли манифест

Симптом: «локально собирается» (IDE собирает всё), а модульный трек CI собирает неполное замыкание — ошибка линковки или хуже (символы другого модуля дают утечку).

Причина: IDE не смотрит манифест; build.py компилирует только перечисленное в манифесте. Разница «локально проходит» и «проходит шлюз» — ровно манифест.

错误写法
/* создали src/foo/bar.c и закоммитили сразу - в манифесте его нет */
/* CI: замыкание модульного трека неполно - ошибка линковки */
正确写法
/* в той же правке добавить файл в массив sources модуля foo в modules.json */
/* в diff PR запись манифеста и исходник рядом - согласованность видна на ревью */
Ловушка 2: слияние после одного модульного трека:Ловушка 2: слияние после одного модульного трека

Симптом: трек одиночного заголовка взрывается — внутренние символы/макросы двух модулей конфликтуют после слияния; single/xrt.h у пользователя не компилируется.

Причина: модульная компиляция изолирует конфликты частных имён между модулями; вскрыть их может только слияние в одиночный заголовок. Ни один трек нельзя пропускать.

错误写法
python tools/build.py --suite my_module --no-single && merge
/* пропуск трека одиночного заголовка перед слиянием - конфликт взрывается в руках пользователя */
正确写法
/* перед слиянием PR: оба трека зелёные - --no-single только для быстрой итерации в разработке */
python tools/build.py --suite my_module   /* включая трек одиночного заголовка */
Ловушка 3: --jobs принят за параллелизм тестов:Ловушка 3: --jobs принят за параллелизм тестов

Симптом: думали, --jobs 8 параллелит тесты — при порядке-зависимых тестах (общие порты/временные файлы) редкие взаимные помехи.

Причина: --jobs параллелит только компиляцию; тесты идут строго в порядке манифеста (детерминизм прежде всего — воспроизводимость отказа и есть ценность шлюза).

错误写法
/* думали, --jobs 8 параллелит тесты - тесты на общих ресурсах изредка мешают друг другу */
/* факт: --jobs параллелит только компиляцию; тесты строго по манифесту */
正确写法
/* хотите быстрее: прогоните подмножество, а не параллельно */
python tools/build.py --suite my_module --test test_queue_oom
/* возобновление: --start-test продолжает с отказа */

Практические задания

Базовое: прогнать suite core на обоих треках

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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки