Столкнулся я с ситуацией, в которой нужно было собирать OpenSSL под разные операционные системы и архитектуры процессоров. В сумме насчитывается 5 сборок.
Главной проблемой сборки OpenSSL выступает система сборки - Autotools, ее сложно интегрировать в CMake. В данной статье рассмотрим как приложив минимальное количество усилий перенести сборку OpenSSL на CMake.
Сборка OpenSSL для Linux систем выглядит так:
chmod +x ./Configure ./Configure [target-arch] [flags] make clean make -j 6 make install
Нативная сборка под Windows примерно так:
call "<Path for Visual studio toolkit>/vcvars32.bat" rem или "<Path for Visual studio toolkit>/vcvars64.bat" perl Configure [target-arch] [flags] nmake clean nmake
Обычным считается кейс, когда CMake используется для конфигурирования и сборки некоторой цели, правила конфигурации и сборки которой определены в файле CMakeLists.txt. Дальше идет стандартный сценарий
cmake -S . -B build && cmake --build build
Есть и другая сторона медали, где CMake можно использовать для выполнения скриптов вызовом волшебной команды
cmake -P <file.cmake>
На данном этапе можно понять, что нет необходимости бросаться переносить OpenSSL на CMake - достаточно просто сделать обертку с использованием скриптов. К CMake скриптам также применимы опции, как и в обычном сценарии использования, исходя из этого определение необходимых действий для каждого варианта сборки можно регулировать опциями.
При написании обертки были поставлены следующие цели:
Превратить скрипты сборки в единый скрипт, для упрощения интеграции с CMake;
Реализовать небольшую систему логов, чтобы избежать получения в лицо сумашедшего вывода о процессе компиляции. OpenSSL при сборке выплевывает очень много ненужной информации, достаточным будет выводить информацию только если сборка завершилась неудачно.
Скрытый текст
Примерно следующее вы получите в лицо при компиляции

Для уменьшения объема кода разделим скрипт на 3 части:
build.cmake - основной модуль сборки;
build_win_native.cmake - нативная сборка под windows (необходима конкретно в моем кейсе);
prepare_build.cmake - конфигурирование сборки.
Идея обертки заключается в превращении процесса сборки в конструктор команд, например команда конфигурирования OpenSSL для Linux систем может выглядеть так
./Configure linux-x86_64 no-asm no-tests
или так
./Configure linux-mips32 -znow -zrelro -Wl,--gc-sections enable-shared \ -DOPENSSL_PREFER_CHACHA_OVER_GCM -DOPENSSL_SMALL_FOOTPRINT no-afalgeng \ no-aria no-asan no-async no-blake2 no-buildtest-c++ no-camellia no-comp \ no-crypto-mdebug no-crypto-mdebug-backtrace no-devcryptoeng no-dtls \ no-dtls1 no-dtls1_2 no-ec2m no-ec_nistp_64_gcc_128 no-egd \ no-external-tests no-fuzz-afl no-fuzz-libfuzzer no-gost no-heartbeats \ no-hw-padlock no-idea no-md2 no-mdc2 no-msan no-nextprotoneg no-rc5 \ no-rfc3779 no-sctp no-seed no-sm2 no-sm3 no-sm4 no-ssl-trace no-ssl3 \ no-ssl3-method no-ubsan no-unit-test no-tests no-weak-ssl-ciphers \ no-whirlpool no-zlib no-zlib-dynamic
Независимо от флагов компиляции нужно передавать целевую архитектуру сборки, для этого добавим переменную описывающую используемые архитектуры.
set(ARCH "LINUX_X86" CACHE STRING "Arch option can be: LINUX_X86 (default), MIPS, ARM, WIN32 and WIN64" )
Перейдем к основной функции скрипта сборки. В ней нужно предусмотреть нативную сборку для Windows в отдельной ветке, чтобы было проще в дальнейшем от нее отказаться.
По классике необходимо выделить команды: prepare, clean, configure и compile.
function(build) if(WIN32) build_win_native() return() elseif(UNIX) prepare_build_lin() clean() configure() compile() endif() endfunction(build) # При вызове команды cmake -P build.cmake вызываем главную функцию build()
Вызов терминальных команд будет осуществляться при помощи команды execute_process(), при ее вызове необходимо:
Получить код ошибки;
Скрыть информацию отправляющуюся в stdout;
Перенаправить stderr в файл.
set(LOG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/build_errors.log") #... function(clean) execute_process( COMMAND $ENV{CLEAN_COMMAND} ERROR_FILE ${LOG_FILE} OUTPUT_QUIET ) endfunction(clean)
Исходя из вышеописанной функции clean() следует, что общение между файлами будет осуществляться через переменные окружения CMake. Можно было использовать кэширование переменных, но тогда в момент их объявления пришлось бы тянуть хвост в виде CACHE STRING "something help info" или объявлять их отдельно.
Для гибкости стоит предусмотреть, что флаги конфигурации OpenSSL могут меняться, для этого была добавлена проверка передаваемых флагов.
set(CONFIGURE_FLAGS "" CACHE STRING "Configure options") #... function(configure) if(NOT CONFIGURE_FLAGS STREQUAL "") string(REPLACE " " ";" _CONFIGURE_FLAGS "${CONFIGURE_FLAGS}") set(ENV{CONFIGURE_FLAGS} "${_CONFIGURE_FLAGS}") endif() #... endfunction(configure)
Стадия prepare подразумевает установку не только команд сборки, но и в проверке возможности сборки под конкретную архитектуру.
# prepare_build.cmake function(prepare_build_lin) # Установка компиляторов для CMake скриптов # отличается от сборки обычного проекта set(CMAKE_C_COMPILER $ENV{CC}) set(CMAKE_CXX_COMPILER $ENV{CXX}) # Валидация хост системы лишней не будет execute_process( COMMAND uname -m OUTPUT_VARIABLE HOST_ARCH OUTPUT_STRIP_TRAILING_WHITESPACE ) if(ARCH STREQUAL "ARM" AND (CMAKE_C_COMPILER MATCHES "arm" OR HOST_ARCH MATCHES "arm")) prepare_linux_arm() elseif(ARCH STREQUAL "MIPS" AND (CMAKE_C_COMPILER MATCHES "mips" OR HOST_ARCH MATCHES "mips")) prepare_linux_mips() elseif(ARCH STREQUAL "WIN64") prepare_win64() elseif(ARCH STREQUAL "WIN32") prepare_win32() elseif(ARCH STREQUAL "LINUX_X86") prepare_linux_x86() else() message(FATAL_ERROR "Bad ARCH option") endif() prepare_linux_general() endfunction(prepare_build_lin)
Проверка переменной CMAKE_C_COMPILER не актуальна для windows, т.к. при использовании единого Docker образа для WIN32 и WIN64 раннее связывание неуместно, поэтому установленный компилятор нужно проверять в функции prepare_win().
function(prepare_win64) #... if (NOT CMAKE_C_COMPILER MATCHES "mingw") set(ENV{CONFIGURE_FLAGS} "$ENV{CONFIGURE_FLAGS};--cross-compile-prefix=x86_64-w64-mingw32-") endif() endfunction(prepare_win64)
Каждая функция prepare*() устанавливает переменные целевой архитектуры и флаги компиляции.
function(prepare_linux_arm) set(ENV{_ARCH} "linux-aarch64") set(ENV{CONFIGURE_FLAGS} "no-asm;no-tests") endfunction(prepare_linux_arm)
Команда configure() должна вызывать команду конфигурирования с указанием архитектуры и флагов компиляции.
function(configure) #... execute_process( COMMAND $ENV{PREPARE_COMMAND} $ENV{CONFIGURE_COMMAND} $ENV{_ARCH} $ENV{CONFIGURE_FLAGS} WORKING_DIRECTORY . RESULT_VARIABLE ret ERROR_FILE ${LOG_FILE} OUTPUT_QUIET ) endfunction(configure)
Команда compile() должна вызвать команду сборки.
function(compile) execute_process( COMMAND $ENV{COMPILE_COMMAND} $ENV{COMPILE_FLAG} WORKING_DIRECTORY . RESULT_VARIABLE ret ERROR_FILE ${LOG_FILE} OUTPUT_QUIET ) endfunction(compile)
Вызов скрипта сборки происходит следующей командой:
cmake -P build.cmake # компиляция для архитектуры поумолчанию cmake -DARCH=<ARCH> build.cmake # компиляция для конкретной архитектуры
По итогу мы написали универсальную обертку, которая:
Cтандартизирует процесс сборки OpenSSL;
Упрощает интеграцию с проектами написанными на CMake;
Реализует простейшую систему логирования.
