Skip to content

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.ymlWasm-сборка~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_64x86_64-unknown-linux-gnuОсновная платформа
Linux ARM64aarch64-unknown-linux-gnuКросс-компиляция на CI
macOS x86_64x86_64-apple-darwinIntel Mac
macOS ARM64aarch64-apple-darwinApple Silicon
Windows x86_64x86_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:

rust
// Унифицированная динамическая линковка
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-distUnix: curl ... | sh
powershell-скрипт✅ cargo-distWindows: irm ... | iex
Homebrew formula✅ cargo-distmacOS: 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:

bash
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/playgroundwasm-библиотека (JS + .wasm)wasm-pack + npm publishОпционально, сейчас публикуется только в docs

Они не конфликтуют, имена тоже не конфликтуют.

Nightly-публикация

cargo-dist не имеет нативной поддержки nightly (#1143, всё ещё open feature request).

Сохраняем существующую схему с cron + tag, часть сборки переключаем на cargo-dist:

yaml
# 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 сгенерируется начальная конфигурация, ожидаемая ключевая часть:

toml
[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 (черновик)

bash
#!/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.yml255Автогенерация cargo-dist
.github/workflows/release.yml176Автогенерация cargo-dist
.github/workflows/nightly.yml145Сборка 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-014bRFC-037
ОбластьСборка и дистрибуция сторонних пакетовУпаковка и дистрибуция самого компилятора
Инструментyaoxiang build / yaoxiang publishcargo-dist
РезультатFFI-библиотеки сторонних пакетовКомпилятор + стандартная библиотека + тулчейн
ВзаимоисключениеНет, дополняют друг другаНет, дополняют друг друга

Альтернативные решения

РешениеПочему не выбрано
Продолжать писать CI вручнуюУже написано ~900 строк, рутинный труд, легко пропустить DLL
Написать собственный инструмент упаковкиНе нужно изобретать велосипед, cargo-dist уже зрел
Использовать только tar.gz без установщиковПользователям нужны более удобные способы установки (Homebrew/MSI)
Дистрибуция через DockerКомпилятору и инструментам языка нужны нативные бинарники, не сценарии контейнеров
Полная статическая линковка Z3Внешние библиотеки нормально распространяются как общие библиотеки, не нужно стремиться к статике
Удалить Inno SetupПривычки китайских пользователей отличаются, стоимость сохранения минимальна

Стратегия реализации

Этап первый: Изменения build.rs + подкоманда gen-std (P0)

  1. Изменить build.rs: унифицировать динамическую линковку на всех платформах, расширить copy_dll() до copy_shared_lib()
  2. Добавить подкоманду yaoxiang package gen-std в main.rs (повторно использовать gen_interfaces.rs)

Этап второй: Интеграция cargo-dist (P0)

  1. Запустить cargo dist init для генерации начальной конфигурации
  2. Написать скрипт упаковки package-dist.sh
  3. Интегрировать в release.yml: cargo-dist сборка → package-dist.sh реструктуризация → загрузка
  4. Проверить правильность структуры и содержимого сгенерированных архивов

Этап третий: Вывод старого CI из эксплуатации (P1)

  1. Параллельно запускать старый и новый CI, сравнивать артефакты
  2. После подтверждения корректности удалить _build-platforms.yml
  3. Сократить nightly.yml (часть сборки заменить на cargo-dist)
  4. Убедиться, что setup.iss всё ещё работает

Этап четвёртый: Включение установщиков (P2)

  1. Настроить автопубликацию Homebrew tap
  2. Настроить генерацию MSI-установщика
  3. Настроить npm-публикацию (@yaoxiang/cli)

Открытые вопросы (закрытые)

Следующие вопросы решены в ходе обсуждения дизайна:

  • Возможность статической линковки Z3 на Windows?Статическую линковку не делаем, на всех платформах динамическая
  • Именование подкоманды gen-std-interfaces?yaoxiang package gen-std
  • Сохранять ли Inno Setup?Сохраняем
  • Условное выполнение extra-artifacts в cargo-dist?Используем скрипт package-dist.sh, ветвление через shell case
  • Совместимость версий файлов интерфейсов стандартной библиотеки?Публикуются вместе с версией компилятора, в одном архиве

Ссылки