Пишем обертку для сборки OpenSSL на CMake

в 10:16, , рубрики: cmake, openssl

Столкнулся я с ситуацией, в которой нужно было собирать 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 скриптам также применимы опции, как и в обычном сценарии использования, исходя из этого определение необходимых действий для каждого варианта сборки можно регулировать опциями.

При написании обертки были поставлены следующие цели:

  1. Превратить скрипты сборки в единый скрипт, для упрощения интеграции с CMake;

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

Скрытый текст

Примерно следующее вы получите в лицо при компиляции

Пишем обертку для сборки OpenSSL на CMake - 1

Для уменьшения объема кода разделим скрипт на 3 части:

  1. build.cmake - основной модуль сборки;

  2. build_win_native.cmake - нативная сборка под windows (необходима конкретно в моем кейсе);

  3. 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(), при ее вызове необходимо:

  1. Получить код ошибки;

  2. Скрыть информацию отправляющуюся в stdout;

  3. Перенаправить 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 # компиляция для конкретной архитектуры

По итогу мы написали универсальную обертку, которая:

  1. Cтандартизирует процесс сборки OpenSSL;

  2. Упрощает интеграцию с проектами написанными на CMake;

  3. Реализует простейшую систему логирования.

Автор: denden1s

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js