Перейти к основному содержимому

Сборка, компиляция и публикация приложений

Разработчику Аналитику Тестировщику
Архитектору Инженеру


Сборка, компиляция и публикация приложений

Основные понятия

Компилятор – программа, преобразующая исходный код в машинный код, понятный компьютеру. В IDE компилятор работает тогда, когда мы нажимаем на команду Сборка (Build) – в этот момент создаётся исполняемый файл, а IDE показывает ошибки, если они есть. Горячая клавиша, к примеру, Ctrl+Shift+B. Такой подход можно увидеть в языках C, C#, C++, Java, Go.


Интерпретатор – программа, выполняющая исходный код построчно и без предварительной компиляции. В IDE для этого выполняется команда Запуск (Run), где интерпретатор читает файл и сразу выполняет, а ошибки выводятся в консоли по мере возникновения. Горячая клавиша, к примеру, F5. Такой подход в языках Python, JavaScript, Ruby.


Сборка – процесс преобразования исходного кода в исполняемый файл или пакет. Сборки бывают двух типов:

  • Debug – с отладочной информацией – медленная, и нужна для разбора ошибок;
  • Release – оптимизированная версия, без отладочных данных.

Сборка проходит несколько этапов:

  • препроцессинг;
  • компиляция в файлы;
  • линковка (объединение в один исполняемый файл).

Именно так и рождается программа – тот самый "exe" в Windows, к примеру.

Play ITЗагрузка интерактивного демо…


Публикация – процесс размещения программы на сервере или в магазине приложений. Публикация может быть нескольких вариантов:

  • Веб-публикация – Docker-контейнеры, статические файлы (актуально для HTML/JS);
  • Мобильное приложение – APK для Android, IPA для iOS;
  • Десктопное приложение – EXE для Windows, DMG для macOS, DEB/RPM для Linux.

Под магазинами подразумеваем Google Play, App Store, Steam.


Что важно знать перед компиляцией и публикацией

  • зависимости – нужно убедиться, что все библиотеки установлены и проверить версии;
  • конфигурация – убедиться в корректности настроек баз данных, API, файлов с переменными окружения и прочих параметров;
  • безопасность – убрать все секретные и конфиденциальные данные – логины/пароли, ключи – из кода, при надобности можно использовать указание служебных файлов в .gitignore, чтобы не публиковать их в Git;
  • тестирование – нужно проверить и отладить работу программы, провести юнит-тесты и интеграционные тесты, в зависимости от проекта.

Препроцессинг

Препроцессинг — это первый этап обработки исходного кода перед его компиляцией. Он выполняется препроцессором, который работает на уровне текста и не анализирует синтаксис языка программирования. Его задача — подготовить окончательный текст программы для передачи компилятору.

Основные действия препроцессора:

  • Подстановка содержимого заголовочных файлов
    Директива #include указывает препроцессору вставить содержимое другого файла в текущее место исходного кода. Это позволяет использовать общие определения, такие как функции, типы данных или макросы, из внешних источников.

  • Макроподстановки
    С помощью #define можно задать имя, которое будет заменено на указанное значение или выражение во всём файле. Например, #define PI 3.14159 приведёт к тому, что каждое упоминание PI в коде будет заменено на число до начала компиляции.

  • Условная компиляция
    Конструкции вроде #ifdef, #ifndef, #if, #else, #endif позволяют включать или исключать фрагменты кода в зависимости от того, определены ли определённые символы. Это полезно для написания платформенно-зависимого кода или включения отладочной информации только в режиме разработки.

  • Удаление комментариев
    Препроцессор убирает все комментарии из исходного кода, так как они не нужны на следующих этапах.

Результат работы препроцессора — единый текстовый файл без директив препроцессора, готовый к передаче компилятору.


Компиляция в файл

Компиляция — это процесс перевода исходного кода на высокоуровневом языке в низкоуровневое представление, понятное целевой системе. На этом этапе компилятор анализирует синтаксис, семантику и структуру программы и создаёт объектный файл.

Объектный файл содержит:

  • Машинный код (или байт-код, в зависимости от языка и платформы) для каждой функции и глобальной переменной.
  • Таблицы символов — имена функций и переменных с указанием их адресов внутри файла.
  • Информацию о внешних зависимостях — ссылки на функции или переменные, которые объявлены, но не определены в данном файле (например, из других модулей или библиотек).

Каждый исходный файл обычно компилируется отдельно в свой объектный файл. Это позволяет компилировать большие проекты по частям и повторно использовать уже скомпилированные модули при изменении только части кода.

Формат объектного файла зависит от операционной системы и архитектуры. Распространённые форматы — ELF (Linux), Mach-O (macOS), COFF/PE (Windows).


Линковка

Линковка (компоновка) — это завершающий этап сборки программы. Линковщик берёт один или несколько объектных файлов, а также библиотеки, и объединяет их в единый исполняемый файл или динамическую библиотеку.

Задачи линковщика:

  • Разрешение символов
    Если в одном объектном файле используется функция, определённая в другом, линковщик связывает вызов с реальным адресом этой функции. То же самое происходит с глобальными переменными.

  • Объединение секций
    Код, данные, константы и другие части программы из разных объектных файлов группируются в соответствующие секции итогового файла.

  • Перерасчёт адресов
    Поскольку каждый объектный файл предполагает, что его код будет загружен по определённому базовому адресу, линковщик корректирует все внутренние адреса с учётом финального расположения частей программы в памяти.

  • Встраивание или подключение библиотек
    Статические библиотеки полностью встраиваются в исполняемый файл. Динамические библиотеки подключаются по ссылке, и их код загружается в память только во время выполнения программы.

Результат линковки — готовый к запуску исполняемый файл, который операционная система может загрузить и передать управление его точке входа (обычно функции main).


Статическая линковка

При статической линковке код сторонних библиотек (в том числе стандартной библиотеки языка) полностью копируется в итоговый исполняемый файл на этапе сборки.


Характеристики

  • Самодостаточность
    Исполняемый файл содержит всё необходимое для запуска: ни одна внешняя зависимость не требуется. Это упрощает развёртывание — достаточно передать один файл.

  • Большой размер
    Каждая программа, использующая одну и ту же библиотеку, включает её копию. При множестве приложений это приводит к избыточному потреблению дискового пространства и памяти.

  • Изоляция версий
    Программа работает с той версией библиотеки, которая была использована при сборке. Это предотвращает проблемы, связанные с несовместимостью при обновлении системных библиотек.

  • Сложность обновления
    Чтобы применить исправление в библиотеке (например, устранение уязвимости), необходимо пересобрать и перевыпустить само приложение.

  • Простота отладки и тестирования
    Поведение программы не зависит от состояния системы — оно воспроизводимо в любой среде.


Типичное применение

  • Встраиваемые системы (где нет файловой системы или менеджера пакетов)
  • Утилиты командной строки, распространяемые как единый бинарник
  • Критически важные приложения, где стабильность важнее гибкости
  • Кроссплатформенные инструменты (например, Go-приложения, собранные с CGO_ENABLED=0)

Динамическая линковка

При динамической линковке библиотеки не встраиваются в исполняемый файл. Вместо этого программа содержит ссылки на общие библиотечные файлы, которые загружаются операционной системой во время запуска или выполнения.


Характеристики

  • Разделение ресурсов
    Одна копия библиотеки может использоваться множеством программ одновременно. Это экономит память и место на диске.

  • Меньший размер исполняемого файла
    Бинарник содержит только собственный код и метаданные о зависимостях.

  • Гибкость обновления
    Исправления в библиотеке (включая патчи безопасности) применяются сразу ко всем программам, которые её используют, без пересборки.

  • Зависимость от окружения
    Программа может не запуститься, если требуемая версия библиотеки отсутствует, повреждена или несовместима. Эта проблема известна как "dependency hell" (ад зависимостей).

  • Версионная нестабильность
    Обновление системной библиотеки может нарушить работу старых приложений, если новая версия не обеспечивает обратную совместимость.

  • Поддержка плагинов и расширений
    Динамические библиотеки позволяют загружать функциональность по требованию (например, модули веб-сервера, драйверы, игровые скрипты).


Форматы динамических библиотек

  • Windows: .dll (Dynamic Link Library)
  • Linux и другие Unix-подобные: .so (Shared Object)
  • macOS: .dylib (Dynamic Library)

Типичное применение

  • Операционные системы и системные утилиты
  • Серверные приложения, зависящие от системных библиотек (OpenSSL, libc)
  • Приложения, распространяемые через пакетные менеджеры (APT, Homebrew, RPM)
  • Плагины, модули и расширения

Сравнение по ключевым параметрам

КритерийСтатическая линковкаДинамическая линковка
Размер исполняемого файлаБольшойМаленький
Потребление памятиВыше (копии в каждом процессе)Ниже (общая память для всех процессов)
ПортируемостьВысокая (один файл — работает везде)Зависит от наличия библиотек в системе
БезопасностьТребует пересборки при патчахПатчи применяются централизованно
СовместимостьГарантирована на момент сборкиМожет нарушиться при обновлении системы
ГибкостьНизкаяВысокая (поддержка плагинов, hot-swap)

Строка "портируемость" в таблице относится к зависимостям времени выполнения, а не к переносу между Windows и Linux. Перенос между платформами разбирается ниже.


Портирование и кроссплатформенная сборка

Портирование программного обеспечения — адаптация программы (или её части) к другой среде выполнения — другая ОС, архитектура процессора (x86-64, ARM), набор системных API, разрядность, иногда — другое "железо" или встраиваемая платформа. Исходный замысел сохраняют; меняют код, сборку, зависимости или способ упаковки.

Платформа в инженерном смысле — комбинация ОС + ISA (набор инструкций) + ABI (соглашения о вызовах) + типичные библиотеки. Сборка под Linux x86-64 и под Windows x86-64 — разные платформы, даже при одном процессоре.

ПодходЧто делаютКогда достаточноПример
Кроссплатформенная сборкаОдин исходник, отдельная сборка на каждую цель (GOOS/GOARCH, dotnet publish -r, CI-матрица)API и зависимости абстрагированы рантаймом или стандартной библиотекойGo, Rust, .NET, Java (JAR + JVM на целевой ОС)
ПортированиеПравки кода, условная компиляция, замена библиотек, тесты на целевой ОСРазные системные вызовы, UI, драйверы, нативные SDKПеренос C++ с WinAPI на Linux, игры между консолями
Слой совместимостиБинарник старой платформы + переводчик APIБыстрый запуск без полного переписыванияWine, Rosetta 2, WSL для Linux-утилит
Контейнер / образФиксируют окружение Linux (libc, пакеты) на хосте с Docker/K8sСерверный деплой; хост может быть любым, где крутится runtimeОбраз mcr.microsoft.com/dotnet/aspnet

Чем портирование отличается от обычной сборки

Сборка (Build в IDE) обычно целится в одну выбранную конфигурацию: Debug|x64, Release|Any CPU. Портирование добавляет вторую (и третью) цель и проверяет, что поведение совпадает.

ЭтапСборка на "своей" машинеПортирование
ИсходникБез измененийЧасто #ifdef, разные пути к файлам, замена WinAPI → POSIX
ТрансляцияКомпилятор под текущую ОСКросс-компиляция — toolchain на машине A строит бинарник для B
ЛинковкаЛокальные .lib / .soБиблиотеки целевой платформы (sysroot, SDK)
ПроверкаЗапуск на dev-ПКТесты на CI-агенте целевой ОС или устройстве
ПоставкаОдин артефактНабор артефактов (installer per OS, multi-arch fat binary)

Кросс-компиляция — сборка исполняемого файла для платформы B на машине с платформой A. В embedded и мобильной разработке это норма; в веб-бэкенде чаще собирают в Docker-образе той же ОС, что и прод.

Условная компиляция из раздела Препроцессинг (#ifdef _WIN32, #[cfg(target_os = "linux")] в Rust) — типичный инструмент порта: один репозиторий, разные ветки кода для платформ.

Типичные препятствия при порте

  • ABI и вызовы — порядок аргументов, кто чистит стек, размер int/long, выравнивание структур. См. ассемблер и ABI.
  • Системные API — файлы, сокеты, потоки, пути (\ и /), кодировки по умолчанию.
  • Зависимости — нет той же версии библиотеки на целевой ОС; нужна статическая линковка или vendoring.
  • UI и ввод — десктоп (WinForms ↔ GTK), мобильные SDK (отдельные проекты).
  • Производительность и разрядность — перенос с 32 на 64 бит, endianness при сетевых протоколах и бинарных форматах.
Порт, миграция и "облако"

Миграция данных (СУБД, бакеты) — отдельная задача от порта кода.

Контейнер переносит окружение Linux, но не превращает Windows-приложение в Linux-приложение без пересборки или слоя совместимости.

Подробнее про образы — контейнеризация; про повторное использование чужих модулей — модульность и reuse.

Практический чек-лист порта

  1. Зафиксировать целевые платформы (ОС, CPU, минимальные версии).
  2. Собрать матрицу в CI (GitHub Actions runs-on, strategy.matrix).
  3. Прогнать тесты и smoke-сценарии на целевой ОС, не только кросс-сборку.
  4. Проверить лицензии нативных библиотек — 7.07/114.
  5. Обновить артефакты публикации (см. Публикация выше) — отдельные пакеты для магазинов и дистрибутивов.

Связанные темы: архитектура процессора, базовая информатика — уровни языков, трансляторы.


Практические рекомендации для инженеров и архитекторов

  • Выбирайте статическую линковку, если:

    • Цель — максимальная автономность и предсказуемость
    • Приложение развёртывается в контролируемой или изолированной среде
    • Важна защита от внешних изменений в системе
  • Выбирайте динамическую линковку, если:

    • Приложение интегрируется в экосистему ОС
    • Требуется частое обновление компонентов без пересборки
    • Нужно минимизировать потребление ресурсов на устройстве пользователя
  • Гибридный подход также возможен: часть критических библиотек встраивается статически, а остальные подключаются динамически. Например, веб-сервер может статически линковать свой ядро-движок, но динамически загружать модули аутентификации или логирования.

  • Современные практики (особенно в облачных и контейнерных средах) часто склоняются к статической линковке или полной упаковке зависимостей (например, в Docker-образ), чтобы избежать неопределённости окружения.