Для компоновки нативной программы macOS каждый выбранный срез библиотеки должен соответствовать архитектуре ЦП и платформе Apple, а также иметь совместимый языковой ABI. В этом руководстве показано, как собрать и проверить небольшую библиотеку на C, используемую настольной программой командной строки на C++.
Конкретная задача — создать исполняемый файл macOS из локального исходного кода и диагностировать несовместимую нативную зависимость. В примере используются искусственные целочисленные вычисления, загружать библиотеки не требуется, а результаты для разных архитектур хранятся отдельно.
Что должно совпадать, чтобы скомпоновать нативную библиотеку?
Независимо проверьте три параметра: архитектуру, платформу и двоичный интерфейс между языками. Спецификация целевой платформы Clang включает не только процессор: цель и среда влияют на генерацию кода и выбор библиотек. Зависимости также нужно собирать для нужной целевой платформы. Кросс-компиляция с помощью Clang.
| Проверка | Вопрос для этой задачи на macOS | Что означает несовпадение |
|---|---|---|
| Архитектура ЦП | Содержит ли зависимость срез arm64 или x86_64, который компонуется? |
Получите или соберите отсутствующую архитектуру. |
| Платформа Apple | Собран ли этот срез для нативной macOS? | Получите сборку для macOS; сборки для iOS и Simulator — отдельные варианты. |
| Языковой ABI | Совпадают ли объявления, соглашения о вызовах и необходимые среды выполнения? | Исправьте интерфейс или используйте поддерживаемый механизм взаимодействия языков. |
Список архитектур не позволяет определить платформу. Формат Mach-O от Apple хранит сведения о ЦП отдельно от сведений о платформе сборки и определяет отдельные идентификаторы платформ для macOS, iOS, Mac Catalyst и iOS Simulator. Поэтому зависимость, описанная только как arm64, требует дополнительной проверки. Определения формата Mach-O от Apple.
Для этого примера контракт сборки предполагает нативную macOS, целевую версию macOS 13.0 и отдельную сборку для каждой архитектуры. Библиотека на C предоставляет интерфейс на C; исполняемый файл использует C++17. Выбор версии приведён для примера и не является универсальным минимумом для программ Mac.
Соберите полный пример для macOS с явно заданными целями
Используйте Mac с актуальной установкой Xcode, выбранной для разработки из командной строки. В документации Apple xcrun описан как средство поиска или запуска инструментов в выбранной среде разработчика. Перед проверкой изменений исходного кода убедитесь, что выбрана нужная среда. Часто задаваемые вопросы о сборке из командной строки с помощью Xcode.
Приведённые ниже команды предназначены для Bash в macOS. Здесь они были проверены, но не выполнялись: среда исследования работает под Linux и не содержит инструментов Apple.
xcode-select --print-path
xcrun --sdk macosx --find clang
xcrun --sdk macosx --find clang++
xcrun --sdk macosx --show-sdk-path
Если эти проверки завершаются ошибкой, сначала настройте выбор инструментов разработчика или установите их. Для сборки нужны и компилятор, и SDK macOS. SDK предоставляет заголовки и библиотеки платформы, для которых собирается продукт. Справочник по параметрам сборки Xcode: Base SDK.
Создайте файлы исходного кода
Выполните этот блок настройки в Bash. Он создаёт новый временный каталог и выводит его расположение, поэтому существующие файлы проекта не затрагиваются. Последующие блоки выполняйте в той же оболочке.
set -euo pipefail
example_dir="$(mktemp -d "${TMPDIR:-/tmp}/mac-link-demo.XXXXXX")"
cd "$example_dir"
printf 'Example directory: %s\n' "$example_dir"
mkdir -p include src build/arm64 build/x86_64
cat > include/tile.h <<'EOF'
#ifndef TILE_H
#define TILE_H
#ifdef __cplusplus
extern "C" {
#endif
int tile_total(int rows, int columns);
#ifdef __cplusplus
}
#endif
#endif
EOF
cat > src/tile.c <<'EOF'
#include "tile.h"
int tile_total(int rows, int columns)
{
return rows * columns;
}
EOF
cat > src/main.cpp <<'EOF'
#include <cstdio>
#include "tile.h"
int main()
{
const int total = tile_total(3, 4);
std::printf("Tiles: %d\n", total);
return total == 12 ? 0 : 1;
}
EOF
Заголовочный файл объявляет связь с языком C, когда его подключает единица трансляции C++. Это не позволяет вызывающему коду на C++ запрашивать имя функции C++, искажённое по другим правилам. В FAQ по C++ также рекомендуется поручить C++-компилятору итоговую компоновку, чтобы включить необходимые ему библиотеки. FAQ по стандартному C++: как смешивать C и C++.
Скомпилируйте, заархивируйте и скомпонуйте каждую архитектуру
Следующий блок собирает оба варианта для macOS. Для каждой архитектуры он передаёт одинаковые целевую платформу и SDK при компиляции библиотеки и компоновке исполняемого файла. В документации Clang параметр -c описан как команда создания объектного файла; без параметра выбора этапа драйвер продолжает работу и выполняет компоновку. Руководство по командам компилятора Clang.
mac_sdk="$(xcrun --sdk macosx --show-sdk-path)"
for cpu in arm64 x86_64; do
mac_target="${cpu}-apple-macos13.0"
xcrun --sdk macosx clang \
-target "$mac_target" -isysroot "$mac_sdk" \
-std=c17 -Wall -Wextra -Werror \
-I include -c src/tile.c \
-o "build/$cpu/tile.o"
xcrun ar rcs "build/$cpu/libtile.a" "build/$cpu/tile.o"
xcrun --sdk macosx clang++ \
-target "$mac_target" -isysroot "$mac_sdk" \
-std=c++17 -stdlib=libc++ -Wall -Wextra -Werror \
-I include src/main.cpp "build/$cpu/libtile.a" \
-o "build/$cpu/tile-demo"
done
Здесь -target задаёт архитектуру и целевую версию macOS, а -isysroot указывает корневой каталог SDK. Это отдельные входные параметры. Явный путь к архиву также делает выбранную зависимость видимой в команде. Руководство Clang по кросс-компиляции, Справочник аргументов командной строки Clang.
Команда ar rcs упаковывает скомпилированный объект в статический архив и добавляет индекс символов. Архивирование упаковывает существующие объекты, но не перекомпилирует их для другой цели. В документации LLVM описаны операции с архивами и их совместимость с другими реализациями ar. Документация архиватора LLVM.
Входные значения 3 и 4 должны дать результат Tiles: 12. Эта небольшая функция приведена только для примера компоновки; её интерфейс не определяет поведение для произвольных размеров, произведение которых превышает диапазон int.
Как проверить архитектуру, платформу и символы?
Проверяйте фактический исполняемый файл, библиотеку или объектный файл. В документации Apple параметр lipo -archs описан как средство вывода списка архитектур; там же отмечено, что исполняемый файл приложения находится в каталоге Contents/MacOS/ внутри пакета. Сборка универсального двоичного файла macOS.
xcrun lipo -archs build/arm64/tile-demo
xcrun lipo -archs build/x86_64/tile-demo
xcrun otool -l build/arm64/tile.o
xcrun otool -l build/arm64/tile-demo
xcrun nm -g build/arm64/libtile.a
Первые две команды должны определить соответствующие архитектуры. Чтобы проверить платформу, изучите команды загрузки, выведенные с помощью otool -l; в совместимом инструменте LLVM llvm-otool описан тот же параметр. Инструмент LLVM для просмотра Mach-O.
Найдите LC_BUILD_VERSION. Значение platform должно указывать на macOS, а в minos записана минимальная версия операционной системы. Поле sdk указывает другое значение. В некоторых более старых двоичных файлах вместо него используется команда LC_VERSION_MIN_*. В определениях формата Apple описаны оба представления, включая идентификатор платформы macOS 1. Структуры build-version в Mach-O.
Для статической библиотеки проверяйте входящие в архив объектные файлы, а не считайте сам формат архива доказательством совместимости платформы. В этом примере для этой цели сохраняется tile.o. Если архив вам незнаком, используйте xcrun ar t, чтобы перечислить его содержимое, и изучите его в отдельном рабочем каталоге. В документации LLVM описаны операции просмотра и извлечения файлов из архива. Документация архиватора LLVM.
Наконец, проверьте символы. В Mach-O примере библиотека должна определять _tile_total. В традиционном выводе nm символ T обозначает глобальный символ кода, а U — неопределённую ссылку. Определение и ссылка — разные свидетельства. Документация LLVM по таблице символов.
Используйте следующую последовательность диагностики, чтобы выбрать дальнейшее действие:
- Отсутствует нужный срез ЦП: пересоберите зависимость для этой архитектуры или получите подходящий двоичный файл.
- Архитектура ЦП совпадает, но платформа отличается: получите вариант для нативной macOS. Изменение архитектуры вызывающего кода само по себе не обеспечивает совместимость платформ.
- Архитектура ЦП и платформа совпадают, но функция не найдена: сравните объявленную функцию с экспортируемым символом библиотеки и убедитесь, что нужный архив указан в команде компоновки.
- Имена различаются в C и C++: исправьте языковую связь в общем заголовочном файле, затем пересоберите вызывающий код.
- Целевые версии развёртывания различаются: проверьте минимальную версию зависимости и, если это поддерживается, пересоберите её для нужной целевой версии развёртывания либо намеренно повысьте требования приложения.
Эта последовательность — вспомогательная схема принятия решений, основанная на приведённой выше документации по целевым платформам, Mach-O и компоновке. Она позволяет отличить отсутствие машинного кода от несовместимых метаданных платформы и проблем языкового интерфейса, чтобы каждая пересборка отвечала конкретному результату проверки.
Что меняется, когда Swift вызывает нативную библиотеку?
Совпадение архитектуры ЦП и платформы не означает, что доступен любой языковой интерфейс. Swift по умолчанию поддерживает взаимодействие с C; прямое взаимодействие с C++ нужно включить, и оно поддерживает описанное подмножество возможностей C++. Интерфейс на C из заголовочного файла этого примера — один из поддерживаемых вариантов, если сборка Swift импортирует его через настроенный механизм заголовков или модулей. Совместное использование Swift и C++.
Для прямого интерфейса C++ в документации Swift для сборок из командной строки указан параметр -cxx-interoperability-mode=default, для импорта заголовков — модули Clang, а для передачи настроек импортёра C++ — -Xcc. Настройка импорта и компоновка скомпилированной реализации — отдельные требования сборки. Настройка проектов на Swift и C++ со смешанным языковым кодом.
Соблюдайте документированные ограничения: взаимодействие с C++ требует Swift 5.9 или новее и C++14 или новее. В смешанном коде на Swift и C++ должна использоваться одна и та же стандартная библиотека C++; на платформах Apple это libc++. Для вызова Swift из C++ нужны сгенерированный интерфейс и поддерживаемый компилятор Clang. Для импортированных ссылочных типов C++ также требуется macOS 13.3 или новее. Прежде чем выбирать этот вариант, сверьте нужный API с актуальной документацией о совместимости. Поддерживаемые возможности и ограничения взаимодействия с C++.
Когда использовать универсальный двоичный файл или XCFramework?
Для этого исполняемого файла только под macOS объедините две готовые сборки macOS в универсальный двоичный файл. В документации Apple описано, как скомпилировать и скомпоновать каждую архитектуру, а затем объединить полученные исполняемые файлы с помощью lipo. Сборка универсального двоичного файла macOS.
xcrun lipo -create \
build/arm64/tile-demo \
build/x86_64/tile-demo \
-output build/tile-demo
xcrun lipo -archs build/tile-demo
./build/tile-demo
На поддерживаемом Mac с macOS 13.0 или новее последняя команда должна вывести Tiles: 12 и завершиться успешно. Сборка обоих срезов не означает, что оба можно протестировать: Intel Mac не может запустить срез arm64. Руководство Apple по универсальным двоичным файлам.
Если библиотека поддерживает несколько платформ Apple или их вариантов, используйте XCFramework, чтобы упаковать отдельно собранные варианты. Apple прямо рекомендует хранить статические библиотеки для iOS и iOS Simulator отдельно и поддерживает включение вариантов для нативной macOS и Mac Catalyst в один XCFramework. Для архивации с помощью Xcode в документированном подходе используется назначение, выбирающее платформу и SDK. Создание двоичного пакета платформенного фреймворка.
При следующей ошибке компоновки для macOS зафиксируйте нужную целевую платформу, проверьте архитектуру и платформу выбранной зависимости, а затем проверьте языковой интерфейс. Сохраните результаты рядом с точной командой компоновки. Так у вас будет конкретное основание, чтобы выбрать подходящий двоичный файл или пересобрать его из исходного кода.
Источники
Кросс-компиляция с помощью Clang — проект LLVM.
Часто задаваемые вопросы о сборке из командной строки с помощью Xcode — Apple.
Справочник по параметрам сборки — Apple.
clang — компилятор C, C++ и Objective-C от Clang — проект LLVM.
Справочник аргументов командной строки Clang — проект LLVM.
Стандартный C++: как смешивать C и C++ — фонд Standard C++.
llvm-ar — архиватор LLVM — проект LLVM.
llvm-otool — инструмент для просмотра Mach-O — проект LLVM.
llvm-nm — просмотр таблицы символов биткода LLVM и объектного файла — проект LLVM.
Определения формата Mach-O: loader.h — Apple.
Совместное использование Swift и C++ — Swift.org.
Настройка проектов на Swift и C++ со смешанным языковым кодом — Swift.org.
Поддерживаемые возможности и ограничения взаимодействия с C++ — Swift.org.
Создание двоичного пакета платформенного фреймворка — Apple.