RFC-037: Промышленная схема дистрибуции — упаковка компилятора/тулчейна на основе cargo-dist
Данный RFC дополняет RFC-014b: Система сборки и бинарная дистрибуция. RFC-014b определяет, как менеджер пакетов YaoXiang строит и распространяет сторонние пакеты; данный RFC определяет, как сам компилятор/тулчейн YaoXiang упаковывается и распространяется.
Аннотация
Замена существующей ручной CI логики сборки/упаковки на cargo-dist (стандартный инструмент бинарной дистрибуции в экосистеме Rust), реализующая кроссплатформенную автоматизированную публикацию. Решает проблемы отсутствия libz3.dll, неупакованных файлов интерфейсов стандартной библиотеки, беспорядка в структуре каталогов, повторяющейся поддержки CI-скриптов и других.
Мотивация
Зачем нужен этот функционал?
Пользователи, скачивающие YaoXiang, должны получить работающий продукт из коробки, без каких-либо дополнительных действий.
Текущие проблемы
Проблема 1: Windows-пользователи не могут запустить после скачивания
Текущий Release загружает только yaoxiang.exe, но libz3.dll не упакован. При двойном клике пользователя на Windows появится ошибка:
The code execution cannot proceed because libz3.dll was not found.Это критический баг — пользователь не может пройти даже первый шаг.
Проблема 2: В Release-артефактах только один файл exe
yaoxiang-v0.7.10-x86_64-pc-windows-msvc.zip
└── yaoxiang.exeФайлы интерфейсов стандартной библиотеки (.yx файлы, необходимые LSP) не включены в дистрибутив. Пользователям нужно запускать yaoxiang package init для генерации. В промышленной практике дистрибутив должен содержать стандартную библиотеку.
Проблема 3: Ручные CI-скрипты требуют повторяющейся поддержки
В настоящее время поддерживается 4 набора сборочных конвейеров:
| Файл | Назначение | Строк |
|---|---|---|
_build-platforms.yml | Кроссплатформенная сборка | ~255 строк |
release.yml | Релизная публикация | ~176 строк |
nightly.yml | Ежедневная сборка | ~145 строк |
_build-wasm.yml | Wasm-сборка | ~75 строк |
scripts/build/setup.iss | Установщик Inno Setup | ~250 строк |
| Итого | ~900 строк |
Большая часть повторяется (установка Rust → кэширование → сборка → переименование → загрузка), каждый раз для каждой платформы. cargo-dist одной командой генерирует аналогичный конвейер.
Проблема 4: Номер версии Inno Setup захардкожен
В setup.iss MyAppVersion записан как 0.7.0, при сборке заменяется через sed. Рано или поздно это приведёт к сбою.
Проблема 5: Размытые границы с RFC-014b
RFC-014b определяет "механизм сборки и дистрибуции пакетов YaoXiang" (то есть конфигурация [build] и [binaries] в yaoxiang.toml), но не охватывает "как публикуется сам компилятор YaoXiang". Данный RFC заполняет этот пробел.
Предложение
Основной дизайн
Использование cargo-dist в качестве скелета конвейера публикации совместно с пользовательскими post-build скриптами для обработки структуры пакетов и дополнительных файлов.
Обязанности cargo-dist:
├── Кроссплатформенная компиляция (6 target)
├── Генерация CI-конвейера (замена ~900 строк ручного YAML)
├── Генерация установщиков (MSI / shell / powershell / homebrew)
├── Публикация в npm (@yaoxiang/cli — wrapper для загрузки бинарников)
├── checksum + подпись
└── Загрузка в GitHub Release
build.rs продолжает отвечать за:
└── Загрузку/линковку Z3 (существующая логика, переход на динамическую линковку на всех платформах)
Пользовательские скрипты YaoXiang (package-dist.sh) отвечают за:
├── Пост-сборочное изменение структуры zip (bin/ + lib/)
├── Прикрепление общих библиотек (libz3.so / dylib / dll)
└── Предварительную генерацию .yx файлов интерфейсов стандартной библиотекиСтруктура каталогов публикации
Каждый платформенный релиз-архив, сформированный package-dist.sh после cargo-dist сборки:
yaoxiang-{version}-{target}.tar.gz / .zip
├── bin/
│ ├── yaoxiang # или yaoxiang.exe
│ └── libz3.so / libz3.dylib / libz3.dll
├── lib/
│ └── yaoxiang/
│ └── std/ # Предварительно сгенерированные файлы интерфейсов стандартной библиотеки
│ ├── io.yx
│ ├── math.yx
│ ├── string.yx
│ ├── ...
│ └── mod.yx
├── README.md
└── LICENSEСтруктура zip по умолчанию в cargo-dist плоская (бинарник + автоматически включённые README/LICENSE в корне). Это не проблема — чёткое разделение обязанностей: cargo-dist управляет компиляцией+CI+установщиками, YaoXiang 50 строками package-dist.sh управляет структурой zip.
Поддержка платформ
| Платформа | target triple | Описание |
|---|---|---|
| Linux x86_64 | x86_64-unknown-linux-gnu | Основная платформа |
| Linux ARM64 | aarch64-unknown-linux-gnu | Кросс-компиляция на CI |
| macOS x86_64 | x86_64-apple-darwin | Intel Mac |
| macOS ARM64 | aarch64-apple-darwin | Apple Silicon |
| Windows x86_64 | x86_64-pc-windows-msvc | Основная платформа |
Windows ARM64 временно не поддерживается (Z3 официально не предоставляет прекомпилированных ARM64 пакетов).
Стратегия дистрибуции Z3
Унификация перехода на динамическую линковку на всех платформах.
| Платформа | Изменение | Артефакт |
|---|---|---|
| Linux | Статика→Динамика | libz3.so |
| macOS | Статика→Динамика | libz3.dylib |
| Windows | Без изменений | libz3.dll |
| wasm32 | Без изменений (статическая линковка) | встроенный .a |
Обоснование:
- Согласованность — поведение трёх платформ унифицировано, без индивидуальных особенностей
- Это внешняя библиотека, она должна распространяться как общая библиотека. Python (
python3.dll+DLLs/lib*.dll), Node (node+lib/) поступают так же - Пользователям не нужно ждать новую версию компилятора для обновления Z3 — достаточно заменить
.so/.dylib/.dll - Меньший размер бинарников — Z3 немаленькая, статическая линковка раздует exe на несколько MB
Соответствующие изменения в build.rs:
// Унифицированная динамическая линковка
fn link_z3(z3_dir: &Path) {
println!("cargo:rustc-link-lib=z3"); // Больше не различаем Windows/не-Windows
// Сохраняем линковку стандартной библиотеки C++ без изменений
let cxx = if target_os == "macos" { "c++" } else { "stdc++" };
println!("cargo:rustc-link-lib={}", cxx);
}"Полная статическая линковка на всех платформах" больше не является целью. Это не устранение особых случаев, а устранение разумного случая неправильным способом. Общие библиотеки — нормальный способ дистрибуции внешних библиотек.
Поддержка установщиков
| Установщик | Статус | Описание |
|---|---|---|
| zip / tar.gz | ✅ По умолчанию | Все платформы, ручное скачивание |
| shell-скрипт | ✅ cargo-dist | Unix: curl ... | sh |
| powershell-скрипт | ✅ cargo-dist | Windows: irm ... | iex |
| Homebrew formula | ✅ cargo-dist | macOS: brew install yaoxiang |
| Windows MSI | ✅ cargo-dist | На основе WiX, основной Windows-установщик |
| Inno Setup | ✅ Сохранён как вспомогательный | Резерв для китайских пользователей, не удаляется |
Причины сохранения Inno Setup:
- Китайские Windows-пользователи привыкли к мастерам установки exe (Далее → Далее → Готово)
- MSI в некоторых корпоративных/школьных сетях заблокирован
- Поддержка дополнительного
setup.issобходится значительно дешевле, чем потеря части пользователей
Генерация файлов интерфейсов стандартной библиотеки
Имя подкоманды: yaoxiang package gen-std (в той же системе, что и существующие package init/add/install)
Текущий src/std/gen_interfaces.rs уже имеет полную реализацию (generate_all_interfaces(), write_interfaces_to_dir()), нужно только добавить точку входа подкоманды в main.rs, затем вызвать в package-dist.sh:
yaoxiang package gen-std --out-dir "$PKG_ROOT/lib/yaoxiang/std/"Wasm-сборка
Остаётся独立ной, не переносится в cargo-dist.
cargo-dist управляет "отправкой компилятора пользователям", wasm — это "встраивание в онлайн-playground и документацию сайта" — два совершенно разных дистрибутива.
| Аспект | Подход |
|---|---|
| Инструмент сборки | Сохраняем wasm-pack build |
| CI workflow | Сохраняем _build-wasm.yml как独立ную job |
| Триггер | В той же push-событии что и release, параллельная独立ная job |
| Цель публикации | docs/public/wasm/ → GitHub Pages |
Публикация в npm
Два разных npm-пакета, независимых друг от друга:
| Пакет | Содержимое | Инструмент | Статус |
|---|---|---|---|
@yaoxiang/cli | Загрузка CLI-бинарника (wrapper) | cargo-dist нативная генерация | Готово к настройке через cargo-dist |
@yaoxiang/playground | wasm-библиотека (JS + .wasm) | wasm-pack + npm publish | Опционально, сейчас публикуется только в docs |
Они не конфликтуют, имена тоже не конфликтуют.
Nightly-публикация
cargo-dist не имеет нативной поддержки nightly (#1143, всё ещё open feature request).
Сохраняем существующую схему с cron + tag, часть сборки переключаем на cargo-dist:
# nightly.yml(после миграции, ~50 строк)
on: schedule: "17 22 * * *"
jobs:
build:
# Используем возможности сборки cargo-dist, но не его релизный конвейер
uses: ./.github/workflows/release.yml # Сгенерированная cargo-dist job сборки
publish:
# Сохраняем существующее: nightly tag → перезапись GitHub Pre-releaseКонфигурация cargo-dist (черновик)
После выполнения cargo dist init сгенерируется начальная конфигурация, ожидаемая ключевая часть:
[workspace]
members = ["cargo:."]
[dist]
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]
installers = [
"shell",
"powershell",
"homebrew",
"msi",
]Конкретные пункты конфигурации должны соответствовать фактическому результату cargo dist init.
package-dist.sh (черновик)
#!/bin/bash
# Выполняется после cargo-dist сборки, изменяет структуру дистрибутивного пакета
# Вызывается из extra-artifacts cargo-dist или独立ного CI step
set -euo pipefail
VERSION="$1"
TARGET="$2"
DIST_DIR="target/distrib"
PKG_ROOT="$DIST_DIR/yaoxiang-$VERSION-$TARGET"
mkdir -p "$PKG_ROOT/bin" "$PKG_ROOT/lib/yaoxiang/std"
# binary
mv "$DIST_DIR/yaoxiang" "$PKG_ROOT/bin/"
# Общие библиотеки
Z3_DIR=".z3/z3-4.16.0-..."
case "$TARGET" in
*windows*) cp "$Z3_DIR/bin/libz3.dll" "$PKG_ROOT/bin/" ;;
*linux*) cp "$Z3_DIR/lib/libz3.so" "$PKG_ROOT/bin/" ;;
*apple*) cp "$Z3_DIR/lib/libz3.dylib" "$PKG_ROOT/bin/" ;;
esac
# Файлы интерфейсов стандартной библиотеки
yaoxiang package gen-std --out-dir "$PKG_ROOT/lib/yaoxiang/std/"
# README + LICENSE
cp README.md LICENSE "$PKG_ROOT/"
# Переупаковка
cd "$DIST_DIR"
tar czf "yaoxiang-$VERSION-$TARGET.tar.gz" "yaoxiang-$VERSION-$TARGET"Генерация файлов интерфейсов стандартной библиотеки
Текущий src/std/gen_interfaces.rs уже реализует функцию генерации .yx файлов интерфейсов (write_interfaces_to_dir), команда package init также её вызывает.
Нужно только добавить точку входа подкоманды в main.rs, затем вызвать в скрипте упаковки.
Устаревшие ручные CI
После завершения миграции удалить следующие файлы:
| Файл | Строк | Замена |
|---|---|---|
.github/workflows/_build-platforms.yml | 255 | Автогенерация cargo-dist |
.github/workflows/release.yml | 176 | Автогенерация cargo-dist |
.github/workflows/nightly.yml | 145 | Сборка cargo-dist + сохранение логики публикации |
scripts/build/setup.iss | ~250 | Сохраняем(для Китая) |
| Итого сокращение | ~600 строк |
Сохраняемые:
ci.yml(повседневные fmt + clippy + test + MSRV, не входят в конвейер публикации)nightly.yml(часть логики публикации сохраняется)_build-wasm.yml(независимый сборочный поток)_build-z3-wasm.yml(wasm-специфичный Z3)setup.iss(вспомогательный установщик для Китая)docs-deploy.yml(развёртывание документации)
Компромиссы
Преимущества
- Работает из коробки — Пользователь скачивает, распаковывает и сразу запускает, без проблем с отсутствующими DLL
- Снижение затрат на поддержку — Удаление ~600 строк ручного CI YAML, cargo-dist автоматически поддерживает
- Стандартизация — Инструмент индустриального стандарта, проверенный сотнями проектов
- Кроссплатформенная согласованность — Динамическая линковка на всех платформах, унифицированное поведение
- Покрытие установщиками — shell/powershell/homebrew/msi/inno setup全部支持
Недостатки
- Изучение конфигурации cargo-dist — Команде нужно изучить новый инструмент
- Пользовательские скрипты упаковки всё ещё требуют поддержки — Скрипты структуры пакетов и файлов интерфейсов стандартной библиотеки нуждаются в поддержке
- Итерации версий cargo-dist — Нужно следить за изменениями upstream
- cargo-dist не имеет нативной поддержки nightly — Часть nightly-публикации всё ещё ручная
Отношение к RFC-014b
| RFC-014b | RFC-037 | |
|---|---|---|
| Область | Сборка и дистрибуция сторонних пакетов | Упаковка и дистрибуция самого компилятора |
| Инструмент | yaoxiang build / yaoxiang publish | cargo-dist |
| Результат | FFI-библиотеки сторонних пакетов | Компилятор + стандартная библиотека + тулчейн |
| Взаимоисключение | Нет, дополняют друг друга | Нет, дополняют друг друга |
Альтернативные решения
| Решение | Почему не выбрано |
|---|---|
| Продолжать писать CI вручную | Уже написано ~900 строк, рутинный труд, легко пропустить DLL |
| Написать собственный инструмент упаковки | Не нужно изобретать велосипед, cargo-dist уже зрел |
| Использовать только tar.gz без установщиков | Пользователям нужны более удобные способы установки (Homebrew/MSI) |
| Дистрибуция через Docker | Компилятору и инструментам языка нужны нативные бинарники, не сценарии контейнеров |
| Полная статическая линковка Z3 | Внешние библиотеки нормально распространяются как общие библиотеки, не нужно стремиться к статике |
| Удалить Inno Setup | Привычки китайских пользователей отличаются, стоимость сохранения минимальна |
Стратегия реализации
Этап первый: Изменения build.rs + подкоманда gen-std (P0)
- Изменить
build.rs: унифицировать динамическую линковку на всех платформах, расширитьcopy_dll()доcopy_shared_lib() - Добавить подкоманду
yaoxiang package gen-stdвmain.rs(повторно использоватьgen_interfaces.rs)
Этап второй: Интеграция cargo-dist (P0)
- Запустить
cargo dist initдля генерации начальной конфигурации - Написать скрипт упаковки
package-dist.sh - Интегрировать в
release.yml: cargo-dist сборка →package-dist.shреструктуризация → загрузка - Проверить правильность структуры и содержимого сгенерированных архивов
Этап третий: Вывод старого CI из эксплуатации (P1)
- Параллельно запускать старый и новый CI, сравнивать артефакты
- После подтверждения корректности удалить
_build-platforms.yml - Сократить
nightly.yml(часть сборки заменить на cargo-dist) - Убедиться, что
setup.issвсё ещё работает
Этап четвёртый: Включение установщиков (P2)
- Настроить автопубликацию Homebrew tap
- Настроить генерацию MSI-установщика
- Настроить npm-публикацию (
@yaoxiang/cli)
Открытые вопросы (закрытые)
Следующие вопросы решены в ходе обсуждения дизайна:
Возможность статической линковки Z3 на Windows?→ Статическую линковку не делаем, на всех платформах динамическаяИменование подкоманды gen-std-interfaces?→yaoxiang package gen-stdСохранять ли Inno Setup?→ СохраняемУсловное выполнение extra-artifacts в cargo-dist?→ Используем скриптpackage-dist.sh, ветвление через shell caseСовместимость версий файлов интерфейсов стандартной библиотеки?→ Публикуются вместе с версией компилятора, в одном архиве
