Столкнулся я с ситуацией, в которой нужно было собирать 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;
-
Реализует простейшую систему логирования.
Автор: denden1s