macOS и мистический minOS

После трёхлетнего перерыва актуальная версия sView стала снова доступна на macOS. Релиз sView 20.08 обещал поддержку macOS 10.10+, но что-то пошло не так и несколько пользователей обратились со странной проблемой — системы macOS 10.13 и 10.14 отказались запускать приложение с сообщением о необходимости обновиться до macOS 10.15…

1973f8dae024423c9ad48b3afa96d992

Сказать, что ошибка меня озадачила — сильно преуменьшить степень моего негодования, ведь магическая цифра 10.15 нигде не фигурировала ни в скриптах сборки, ни в ресурсах sView! Более того, приложение лично было проверено на более старой версии системы, а именно — на macOS 10.10.

Немного предыстории. В далёком 2011 году вышла первая сборка sView для OS X 10.6 Snow Leopard, и шесть лет именно эта версия системы оставалась минимальным требованием для запуска sView. Поддержка относительно старых версий операционных систем даёт максимальный охват потенциальных пользователей, но требует дополнительных усилий.

Практика разработки Windows, Linux, Android и macOS приложений показывает, что предположения о том, что собранное приложение «вроде должно работать» на всех версиях систем периодически дают сбой, и проблемы совместимости всплывают самым неожиданным образом. В таких случаях возможность проверить работоспособность приложения на разных (в том числе самых старых, формально поддерживаемых) системах становится жизненно необходимой.

Однако старая версия OS X требует такого же старого устройства, так как установить систему на устройство, выпущенное позднее самой системы, зачастую не представляется возможным. Проблему могли бы решить средства виртуализации, однако в случае с macOS дела с ними обстоят не лучшим образом.

Также понадобится и подходящий сборочный инструментарий. В прошлом, сборка приложения для нужной версии OS X требовала наличия нужной версии SDK в XCode. Однако упаковка нескольких SDK в XCode существенно увеличивала размер установки и старые версии SDK быстро исключались из новых версий XCode, осложняя сборку приложений для старых систем.

Для обеспечения совместимости с OS X 10.6 Snow Leopard, приложение sView долгое время собиралось на OS X той же версии, предустановленной на старом MacBook. При этом несколько версий OS X было установлено на внешний жёсткий диск для тестирования.

К счастью, со временем разработчики Apple существенно улучшили инструментарий, внедрив версионизацию на уровне заголовочных файлов, опций компилятора и линковщика. Теперь, XCode поставляется всего с одной версией macOS SDK — с самой последней, -, но приложение можно собрать с совместимостью с более старыми версиями macOS посредством:

  • переменной окружения MACOSX_DEPLOYMENT_TARGET
    (т.е., export MACOSX_DEPLOYMENT_TARGET=10.0);

  • или флага компилятора-mmacosx-version-min
    (т.е., EXTRA_CXXFLAGS += -mmacosx-version-min=10.0).

В случае CMake соответствующий параметр называется CMAKE_OSX_DEPLOYMENT, а у qmake — QMAKE_MACOSX_DEPLOYMENT_TARGET.

Настройки проекта в XCode 11 позволяют выбрать минимальной платформой даже OS X 10.6, но данный выбор приводит только к ошибкам при сборке и Hello World удалось собрать только при выборе 10.7 или версия новее. Впрочем, OS X 10.6 Snow Leopard вышла в далёком 2009 году — то есть одиннадцать лет назад, — и едва ли имеет активных пользователей. Какую же версию выбрать в качестве минимальной?

OS X 10.10 Yosemite была выпущена около 6 лет назад и на 6 релизов «старее» самой актуальной на данный момент macOS 11.0 Big Sur. Трудно представить пользователей более старой OS X с учётом агрессивной политики обновлений Apple. Помимо прочего, OS X 10.10 уже была установлена на моём старом MacBook — слишком старым для разработки, но ещё живом для проверки работоспособности собранного приложения.

598e15b9532ce0636d56433303cb480a.jpg

В попытке обновить «старичка» mid-2010 MacBook выяснилось, что свежие версии macOS более не поддерживают такие устройства , а последней совместимой версией оказалась macOS 10.13 High Sierra выпущенная в 2017 году.
Таким образом, Apple лишила свой продукт программных обновлений спустя 7 лет! При этом магазин приложений Apple более не позволяет загрузить старые версии macOS — то есть и обновить OS X 10.10 до macOS 10.13 не получится обычным способом.

Для сборки sView на свежем инструментарии в Makefile проекта была прописана версия 10.10, а в Info.plist был указан параметр LSMinimumSystemVersion=10.0. Сама сборка была осуществлена на macOS 10.15, установленной на относительно свежем Mac mini »2018, и протестирована на макбуке с OS X 10.10 — приложение заработало и было опубликовано на сайте!

…и тут, как снег на голову, пришли сообщения пользователей об ошибках запуска sView на версиях macOS, новеепротестированной. Вздор! Откуда система вообще могла взять цифру 10.15, если LSMinimumSystemVersion указывает на 10.10 -, а это единственный ранее известный мне источник для подобных сообщений macOS об ошибках?

7c0958392437a4d278d78bb75f76d6f3

В слепую локализовать проблему не удавалось — поиски 10.15 в архиве с приложением и в сборочных скриптах ни к чему не привели. Поэтому было найдено временное подопытное устройство с macOS 10.13, выводящее такое же сообщение об ошибке. Удивительно, но запуск исполнительного файла sView из терминала происходил без всяких проблем и ошибок!

Эксперименты показали, что что-то не так непосредственно с исполнительным файлом sView, и в конце концов, утилита otool -l выявила источник проблемы:

Load command 9
        cmd LC_BUILD_VERSION
    cmdsize 32
   platform macos
        sdk 10.15
      minos 10.15
     ntools 1
       tool ld
    version 450.3

Информации о загадочном minos нашлось не много в интернете, но удалось выяснить, что данное поле появилось в заголовке бинарный файлов macOS относительно недавно. Но этого факта оказалось достаточно, чтобы ответить на первый вопрос — как так получилось, что более старая версия OS X 10.10 запускала sView без проблем, а новые macOS 10.13–10.14 выдавали ошибки? Да просто OS X 10.10 ничего не знает о существовании нового поля minos!

Оставался последний вопрос — где в процессе сборки приложения закралась ошибка? Изучение пакета sView выявило, что поле minos присутствовало только библиотеках и исполняемом файле самого проекта, но не в библиотеках FFmpeg, собранных схожим образом. То есть проблема была явно в Makefile проекта. Как оказалось,  флаг -mmacosx-version-min передавался компилятору через переменную EXTRA_CXXFLAGS, но не передавался линковщику. Добавление флага в переменную EXTRA_LDFLAGS наконец-то решило проблему:

TARGET_OS_VERSION = 10.10
EXTRA_CFLAGS   += -mmacosx-version-min=$(TARGET_OS_VERSION)
EXTRA_CXXFLAGS += -mmacosx-version-min=$(TARGET_OS_VERSION)
EXTRA_LDFLAGS  += -mmacosx-version-min=$(TARGET_OS_VERSION)

Оригинальная публикация на английском может быть найдена здесь.

a9450fb84a5cec631916fa1f9556e550.png

© Habrahabr.ru