Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cmake package config #3722

Merged
merged 2 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/Makefile
CMakeLists.txt*
build.ninja
.ninja_log
libbotan*.so.*
Expand Down
7 changes: 7 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2315,6 +2315,9 @@ def test_exe_extra_ldflags():
if osinfo.soname_pattern_patch is not None:
variables['soname_patch'] = osinfo.soname_pattern_patch.format(**variables)

if options.os == 'windows':
variables['implib_name'] = variables['static_lib_name']

reneme marked this conversation as resolved.
Show resolved Hide resolved
variables['lib_link_cmd'] = variables['lib_link_cmd'].format(**variables)

for var in ['exe_link_cmd']:
Expand Down Expand Up @@ -3342,6 +3345,10 @@ def in_build_module_info(p):
write_template(in_build_dir('build.h'), in_build_data('buildh.in'))
write_template(in_build_dir('botan.doxy'), in_build_data('botan.doxy.in'))

robust_makedirs(in_build_dir("cmake"))
write_template(in_build_dir('cmake/botan-config.cmake'), in_build_data('botan-config.cmake.in'))
write_template(in_build_dir('cmake/botan-config-version.cmake'), in_build_data('botan-config-version.cmake.in'))

if 'botan_pkgconfig' in template_vars:
write_template(template_vars['botan_pkgconfig'], in_build_data('botan.pc.in'))

Expand Down
16 changes: 16 additions & 0 deletions src/build-data/botan-config-version.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
set(PACKAGE_VERSION %{version})

# Botan follows semver:
# * the requested version should be less or equal to the installed version, however
# * the requested major version must fit exactly.

if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
elseif(PACKAGE_FIND_VERSION_MAJOR VERSION_LESS %{version_major})
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
116 changes: 116 additions & 0 deletions src/build-data/botan-config.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#.rst:
# botan-config.cmake
# -----------
#
# Find the botan library.
#
# This CMake configuration file, installed as part of the Botan build,
# provides support for find_package(Botan).
#
# Required version(s) can be passed as usual:
# find_package(Botan 3.3.0 REQUIRED)
#
# COMPONENTS and OPTIONAL_COMPONENTS can be used to specify Botan
# modules that must or should be enabled in the Botan build:
# find_package(Botan 3.3.0 COMPONENTS rsa ecdsa)
#
# IMPORTED Targets
# ^^^^^^^^^^^^^^^^
#
# This module defines :prop_tgt:`IMPORTED` targets:
#
# ``Botan::Botan``
# The botan shared library, if found.
# ``Botan::Botan-static``
# The botan static library, if found.
#
# Result variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables:
#
# ::
#
# Botan_FOUND - true if the headers and library were found
# Botan_VERSION - library version that was found, if any
#

set(_Botan_supported_components
%{for mod_list}
%{i}
%{endfor}
)

unset(${CMAKE_FIND_PACKAGE_NAME}_FOUND)
unset(_Botan_missing_required_modules)

foreach(_comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
if (NOT _comp IN_LIST _Botan_supported_components)
set(${CMAKE_FIND_PACKAGE_NAME}_${_comp}_FOUND False)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${_comp})
list(APPEND _Botan_missing_required_modules ${_comp})
endif()
else()
set(${CMAKE_FIND_PACKAGE_NAME}_${_comp}_FOUND True)
endif()
endforeach()

if(_Botan_missing_required_modules)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False)
list(JOIN _Botan_missing_required_modules ", " _missing_modules)
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "Unsupported module(s): ${_missing_modules}")
endif()

if(DEFINED ${CMAKE_FIND_PACKAGE_NAME}_FOUND AND NOT ${${CMAKE_FIND_PACKAGE_NAME}_FOUND})
return()
endif()

# botan-config.cmake lives in "${_Botan_PREFIX}/lib/cmake/Botan-X": traverse up to $_Botan_PREFIX
set(_Botan_PREFIX "${CMAKE_CURRENT_LIST_DIR}")
get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY)
get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY)
get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY)

%{if build_static_lib}
if(NOT TARGET Botan::Botan-static)
add_library(Botan::Botan-static STATIC IMPORTED)
set_target_properties(Botan::Botan-static
PROPERTIES
IMPORTED_LOCATION "${_Botan_PREFIX}/lib/%{static_lib_name}"
INTERFACE_INCLUDE_DIRECTORIES "${_Botan_PREFIX}/include/botan-%{version_major}"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
INTERFACE_LINK_OPTIONS "SHELL:%{cxx_abi_flags}")
endif()
%{endif}

%{if implib_name}
set(_Botan_implib "${_Botan_PREFIX}/lib/%{implib_name}")
set(_Botan_shared_lib "${_Botan_PREFIX}/bin/%{shared_lib_name}")
%{endif}
%{unless implib_name}
set(_Botan_implib "")
%{endif}

%{if build_shared_lib}
if(NOT TARGET Botan::Botan)
if(NOT DEFINED _Botan_shared_lib)
set(_Botan_shared_lib "${_Botan_PREFIX}/lib/%{shared_lib_name}")
endif()

add_library(Botan::Botan SHARED IMPORTED)
set_target_properties(Botan::Botan
PROPERTIES
IMPORTED_LOCATION "${_Botan_shared_lib}"
IMPORTED_IMPLIB "${_Botan_implib}"
INTERFACE_INCLUDE_DIRECTORIES "${_Botan_PREFIX}/include/botan-%{version_major}"
INTERFACE_LINK_OPTIONS "SHELL:%{cxx_abi_flags}")
set_property(TARGET Botan::Botan APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
set_target_properties(Botan::Botan
PROPERTIES
IMPORTED_LOCATION_NOCONFIG "${_Botan_PREFIX}/lib/%{shared_lib_name}"
securitykernel marked this conversation as resolved.
Show resolved Hide resolved
IMPORTED_SONAME_NOCONFIG "%{shared_lib_name}"
IMPORTED_IMPLIB_NOCONFIG "${_Botan_implib}")
endif()
%{endif}

set(${CMAKE_FIND_PACKAGE_NAME}_FOUND True)
128 changes: 128 additions & 0 deletions src/scripts/ci/cmake_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
cmake_minimum_required(VERSION 3.24)

project(Botan_CMake_Tests CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED On)
set(CMAKE_CXX_EXTENSIONS Off)

# test #1: find any Botan version and link dynamically
find_package(Botan REQUIRED)
if(NOT Botan_FOUND)
message(FATAL_ERROR "Test #1 failed: Failed to find Botan with any version")
endif()

if(TARGET Botan::Botan)
add_executable(botan_version_test main.cpp)
target_link_libraries(botan_version_test Botan::Botan)

if(WIN32)
add_custom_command(TARGET botan_version_test POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:botan_version_test> $<TARGET_FILE_DIR:botan_version_test>
COMMAND_EXPAND_LISTS)
endif()

add_test(NAME "Using shared library"
COMMAND botan_version_test
PASS_REGULAR_EXPRESSION "${Botan_VERSION}")
endif()

unset(Botan_FOUND)

# test #2: link statically
find_package(Botan REQUIRED)
if(TARGET Botan::Botan-static)
add_executable(botan_version_test_static main.cpp)
target_link_libraries(botan_version_test_static Botan::Botan-static)

add_test(NAME "Using static library"
COMMAND botan_version_test_static
PASS_REGULAR_EXPRESSION "${Botan_VERSION}")
endif()

unset(Botan_FOUND)

# test #3: find Botan version installed by version number
find_package(Botan ${Botan_VERSION} REQUIRED)
if(NOT Botan_FOUND)
message(FATAL_ERROR "Failed to find Botan with version ${Botan_VERSION}")
endif()

unset(Botan_FOUND)

# test #4: find Botan with specific modules
find_package(Botan ${Botan_VERSION} COMPONENTS ecdsa rsa REQUIRED)
if(NOT Botan_ecdsa_FOUND OR NOT Botan_rsa_FOUND)
message(FATAL_ERROR "Package did not specify that the (optional) components were found: ecdsa, rsa")
endif()

if(NOT Botan_FOUND)
message(FATAL_ERROR "Failed to find Botan with version ${Botan_VERSION} containing modules: ecdsa, rsa")
endif()

unset(Botan_FOUND)

# test #5: fail if required module is not included
find_package(Botan ${Botan_VERSION} COMPONENTS tpm QUIET)
if(Botan_FOUND)
message(FATAL_ERROR "Found Botan with version ${Botan_VERSION}: Found module tpm but expected it to be absent")
endif()

unset(Botan_FOUND)

# test #6: do not fail if optional module is not included
find_package(Botan ${Botan_VERSION} OPTIONAL_COMPONENTS tpm REQUIRED)
if(NOT Botan_FOUND)
message(FATAL_ERROR "Failed to find Botan with version ${Botan_VERSION}, despite missing optional modules: tpm")
endif()
if(NOT DEFINED Botan_tpm_FOUND OR ${Botan_tpm_FOUND})
message(FATAL_ERROR "Failed to notify that the optional component was not found")
endif()

unset(Botan_FOUND)

# test #7: fail with some specific (but unavailable) component and fall-back to generic
find_package(Botan ${Botan_VERSION} COMPONENTS nonexistent unobtainium QUIET)
if(NOT Botan_FOUND)
# try again without requiring specific components
find_package(Botan ${Botan_VERSION})
else()
message(FATAL_ERROR "Found Botan regardless of the non-existent component requirement")
endif()
if(NOT Botan_FOUND)
message(FATAL_ERROR "Failed to find generic Botan as a second attempt")
endif()

unset(Botan_FOUND)

# test #8: try to find a future version
math(EXPR Botan_FUTURE_MAJOR_VERSION "${Botan_VERSION_MAJOR} + 1")
find_package(Botan ${Botan_FUTURE_MAJOR_VERSION} QUIET)
if(Botan_FOUND)
message(FATAL_ERROR "Found a future major version that doesn't exist yet: ${Botan_FUTURE_MAJOR_VERSION}")
endif()

unset(Botan_FOUND)

# test #9: try to find an older version (that is not compatible with the installed one)
math(EXPR Botan_PAST_MAJOR_VERSION "${Botan_VERSION_MAJOR} - 1")
find_package(Botan ${Botan_PAST_MAJOR_VERSION} QUIET)
if(Botan_FOUND)
message(FATAL_ERROR "Found a legacy major version that doesn't exist yet: ${Botan_PAST_MAJOR_VERSION}")
endif()

unset(Botan_FOUND)

# test #10: try to find Botan with a different case-sensitive spelling
find_package(botan COMPONENTS rsa REQUIRED)
if(NOT botan_FOUND)
message(FATAL_ERROR "Failed to find Botan with a different case-spelling")
endif()
if(NOT botan_rsa_FOUND)
message(FATAL_ERROR "Failed to find Botan component with a different case-spelling")
endif()

unset(Botan_FOUND)
unset(botan_FOUND)

enable_testing()
21 changes: 21 additions & 0 deletions src/scripts/ci/cmake_tests/CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": 5,
"cmakeMinimumRequired": {
"major": 3,
"minor": 24,
"patch": 0
},
"configurePresets": [
{
"name": "windows_x86_64",
"architecture": "x64"
},
{
"name": "windows_x86",
"architecture": "Win32"
},
{
"name": "unix"
}
]
}
7 changes: 7 additions & 0 deletions src/scripts/ci/cmake_tests/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <botan/version.h>
#include <iostream>

int main() {
std::cout << Botan::short_version_string() << std::endl;
return 0;
}
33 changes: 33 additions & 0 deletions src/scripts/ci_check_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
This script is used to validate the results of `make install`

(C) 2020 Jack Lloyd, René Meusel, Hannes Rantzsch
(C) 2023 René Meusel

Botan is released under the Simplified BSD License (see license.txt)
"""
Expand All @@ -14,6 +15,7 @@
import sys
import json
import re
import subprocess

def verify_library(build_config):
lib_dir = build_config['libdir']
Expand Down Expand Up @@ -75,6 +77,34 @@ def verify_includes(build_config):

return True

def verify_cmake_package(build_config):
if build_config['os'] not in ['windows', 'linux', 'macos']:
return True # skip (e.g. for mingw)

cmake_build_dir = os.path.join(build_config['abs_root_dir'], build_config['build_dir'], 'cmake_test')
cmake_test_dir = os.path.join(build_config['abs_root_dir'], "src", "scripts", "ci", "cmake_tests")

def cmake_preset():
if build_config['os'] == 'windows':
return 'windows_x86_64' if build_config['arch'] == 'x86_64' else 'windows_x86'
return 'unix'

def test_target():
return 'test' if build_config['os'] != 'windows' else 'RUN_TESTS'

try:
subprocess.run(["cmake", "--preset", cmake_preset(),
"-B", cmake_build_dir,
"-S", cmake_test_dir,
"-DCMAKE_PREFIX_PATH=%s" % build_config['prefix']], check=True)
subprocess.run(["cmake", "--build", cmake_build_dir, "--config", "Release"], check=True)
subprocess.run(["cmake", "--build", cmake_build_dir, "--config", "Release", "--target", test_target()], check=True)
reneme marked this conversation as resolved.
Show resolved Hide resolved
except RuntimeError as e:
print("Using the CMake package failed: %s" % str(e))
return False

return True

def main(args=None):
if args is None:
args = sys.argv
Expand All @@ -98,6 +128,9 @@ def main(args=None):
if not verify_library(build_config):
return 1

if not verify_cmake_package(build_config):
return 1

return 0

if __name__ == '__main__':
Expand Down
Loading