1# Copyright 2019 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15# Partially adapted from Abseil's CMake helpers 16# https://github.com/abseil/abseil-cpp/blob/master/CMake/AbseilHelpers.cmake 17 18# Rules for declaring Tink targets in a way similar to Bazel. 19# 20# These functions are intended to reduce the difficulty of supporting completely 21# different build systems, and are designed for Tink internal usage only. 22# They may work outside this project too, but we don't support that. 23# 24# A set of global variables influences the behavior of the rules: 25# 26# TINK_MODULE name used to build more descriptive names and for namespaces. 27# TINK_GENFILE_DIR generated content root, such pb.{cc,h} files. 28# TINK_INCLUDE_DIRS list of global include paths. 29# TINK_CXX_STANDARD C++ standard to enforce, 11 for now. 30# TINK_BUILD_TESTS flag, set to false to disable tests (default false). 31# 32# Sensible defaults are provided for all variables, except TINK_MODULE, which is 33# defined by calls to tink_module(). Please don't alter it directly. 34 35include(CMakeParseArguments) 36 37if (TINK_BUILD_TESTS) 38 enable_testing() 39endif() 40 41if (NOT DEFINED TINK_GENFILE_DIR) 42 set(TINK_GENFILE_DIR "${PROJECT_BINARY_DIR}/__generated") 43endif() 44 45if (NOT DEFINED TINK_CXX_STANDARD) 46 set(TINK_CXX_STANDARD 14) 47 if (DEFINED CMAKE_CXX_STANDARD_REQUIRED AND CMAKE_CXX_STANDARD_REQUIRED AND DEFINED CMAKE_CXX_STANDARD) 48 set(TINK_CXX_STANDARD ${CMAKE_CXX_STANDARD}) 49 endif() 50endif() 51 52set(TINK_DEFAULT_COPTS "") 53if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 54 # This is required to avoid error C1128. 55 # See https://learn.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/fatal-error-c1128?view=msvc-170. 56 set(TINK_DEFAULT_COPTS "/bigobj") 57endif() 58 59list(APPEND TINK_INCLUDE_DIRS "${TINK_GENFILE_DIR}") 60 61set(TINK_IDE_FOLDER "Tink") 62 63set(TINK_TARGET_EXCLUDE_IF_OPENSSL "exclude_if_openssl") 64set(TINK_TARGET_EXCLUDE_IF_WINDOWS "exclude_if_windows") 65 66# Declare the beginning of a new Tink library namespace. 67# 68# As a rule of thumb, every CMakeLists.txt should be a different module, named 69# after the directory that contains it, and this function should appear at the 70# top of each CMakeLists script. 71# 72# This is not a requirement, though. Targets should be grouped logically, and 73# multiple directories can be part of the same module as long as target names 74# do not collide. 75# 76macro(tink_module NAME) 77 set(TINK_MODULE ${NAME}) 78endmacro() 79 80# Declare a Tink library. Produces a static library that can be linked into 81# other test, binary or library targets. Tink libraries are mainly meant as 82# a way to organise code and speed up compilation. 83# 84# Arguments: 85# NAME base name of the target. See below for target naming conventions. 86# SRCS list of source files, including headers. 87# DEPS list of dependency targets. 88# PUBLIC flag, signals that this target is intended for external use. 89# TESTONLY flag, signals that this target should be ignored if 90# TINK_BUILD_TESTS=OFF. 91# 92# If SRCS contains only headers, an INTERFACE rule is created. This rule carries 93# include path and link library information, but is not directly buildable. 94# 95# The corresponding build target is named tink_<MODULE>_<NAME> if PUBLIC is 96# specified, or tink_internal_<MODULE>_<NAME> otherwise. An alias is also 97# defined for use in CMake scripts, in the tink::<MODULE>::<NAME> form. 98# 99# Unlike Bazel, CMake does not enforce the rule that all dependencies must be 100# listed. CMake DEPS just carry include, build and link flags that are passed 101# to the compiler. Because of this, a target might compile even if a dependency 102# is not specified, but that could break at any time. So make sure that all 103# dependencies are explicitly specified. 104# 105function(tink_cc_library) 106 cmake_parse_arguments(PARSE_ARGV 0 tink_cc_library 107 "PUBLIC;TESTONLY" 108 "NAME" 109 "SRCS;DEPS;TAGS" 110 ) 111 112 if (tink_cc_library_TESTONLY AND NOT TINK_BUILD_TESTS) 113 return() 114 endif() 115 116 if (NOT DEFINED TINK_MODULE) 117 message(FATAL_ERROR 118 "TINK_MODULE not defined, perhaps you are missing a tink_module() statement?") 119 endif() 120 121 # Check if this target must be skipped. 122 foreach(_tink_cc_library_tag ${tink_cc_library_TAGS}) 123 # Exclude if we use OpenSSL. 124 if (${_tink_cc_library_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_OPENSSL} AND TINK_USE_SYSTEM_OPENSSL) 125 return() 126 endif() 127 # Exclude if building on Windows. 128 if (${_tink_cc_library_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_WINDOWS} AND WIN32) 129 return() 130 endif() 131 endforeach() 132 133 # We replace :: with __ in targets, because :: may not appear in target names. 134 # However, the module name should still span multiple name spaces. 135 STRING(REPLACE "::" "__" _ESCAPED_TINK_MODULE ${TINK_MODULE}) 136 137 set(_is_headers_only_lib true) 138 foreach(_src_file ${tink_cc_library_SRCS}) 139 if(${_src_file} MATCHES "\\.cc$") 140 set(_is_headers_only_lib false) 141 break() 142 endif() 143 endforeach() 144 145 if (tink_cc_library_PUBLIC) 146 set(_target_name "tink_${_ESCAPED_TINK_MODULE}_${tink_cc_library_NAME}") 147 else() 148 set(_target_name "tink_internal_${_ESCAPED_TINK_MODULE}_${tink_cc_library_NAME}") 149 endif() 150 151 if(NOT _is_headers_only_lib) 152 add_library(${_target_name} STATIC "") 153 target_sources(${_target_name} PRIVATE ${tink_cc_library_SRCS}) 154 target_include_directories(${_target_name} PUBLIC ${TINK_INCLUDE_DIRS}) 155 target_link_libraries(${_target_name} PUBLIC ${tink_cc_library_DEPS}) 156 target_compile_options(${_target_name} PRIVATE ${TINK_DEFAULT_COPTS}) 157 set_property(TARGET ${_target_name} PROPERTY CXX_STANDARD ${TINK_CXX_STANDARD}) 158 set_property(TARGET ${_target_name} PROPERTY CXX_STANDARD_REQUIRED true) 159 if (tink_cc_library_PUBLIC) 160 set_property(TARGET ${_target_name} 161 PROPERTY FOLDER "${TINK_IDE_FOLDER}") 162 else() 163 set_property(TARGET ${_target_name} 164 PROPERTY FOLDER "${TINK_IDE_FOLDER}/Internal") 165 endif() 166 else() 167 add_library(${_target_name} INTERFACE) 168 target_include_directories(${_target_name} INTERFACE ${TINK_INCLUDE_DIRS}) 169 target_link_libraries(${_target_name} INTERFACE ${tink_cc_library_DEPS}) 170 endif() 171 172 add_library( 173 tink::${TINK_MODULE}::${tink_cc_library_NAME} ALIAS ${_target_name}) 174endfunction(tink_cc_library) 175 176# Declare a Tink test using googletest, with a syntax similar to Bazel. 177# 178# Parameters: 179# NAME base name of the test. 180# SRCS list of test source files, headers included. 181# DEPS list of dependencies, see tink_cc_library above. 182# DATA list of non-code dependencies, such as test vectors. 183# 184# Tests added with this macro are automatically registered. 185# Each test produces a build target named tink_test_<MODULE>_<NAME>. 186# 187function(tink_cc_test) 188 cmake_parse_arguments(PARSE_ARGV 0 tink_cc_test 189 "" 190 "NAME" 191 "SRCS;DEPS;DATA;TAGS" 192 ) 193 194 if (NOT TINK_BUILD_TESTS) 195 return() 196 endif() 197 198 if (NOT DEFINED TINK_MODULE) 199 message(FATAL_ERROR "TINK_MODULE not defined") 200 endif() 201 202 # Check if this target must be skipped. 203 foreach(_tink_cc_test_tag ${tink_cc_test_TAGS}) 204 # Exclude if we use OpenSSL. 205 if (${_tink_cc_test_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_OPENSSL} AND TINK_USE_SYSTEM_OPENSSL) 206 return() 207 endif() 208 # Exclude if building on Windows. 209 if (${_tink_cc_test_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_WINDOWS} AND WIN32) 210 return() 211 endif() 212 endforeach() 213 214 # We replace :: with __ in targets, because :: may not appear in target names. 215 # However, the module name should still span multiple name spaces. 216 STRING(REPLACE "::" "__" _ESCAPED_TINK_MODULE ${TINK_MODULE}) 217 218 set(_target_name "tink_test_${_ESCAPED_TINK_MODULE}_${tink_cc_test_NAME}") 219 220 add_executable(${_target_name} 221 ${tink_cc_test_SRCS} 222 ) 223 224 target_link_libraries(${_target_name} 225 gtest_main 226 ${tink_cc_test_DEPS} 227 ) 228 229 set_property(TARGET ${_target_name} 230 PROPERTY FOLDER "${TINK_IDE_FOLDER}/Tests") 231 set_property(TARGET ${_target_name} PROPERTY CXX_STANDARD ${TINK_CXX_STANDARD}) 232 set_property(TARGET ${_target_name} PROPERTY CXX_STANDARD_REQUIRED true) 233 234 # Note: This was preferred over using gtest_discover_tests because of [1]. 235 # [1] https://gitlab.kitware.com/cmake/cmake/-/issues/23039 236 add_test(NAME ${_target_name} COMMAND ${_target_name} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) 237endfunction(tink_cc_test) 238 239# Declare a C++ Proto library. 240# 241# Parameters: 242# NAME base name of the library. 243# SRCS list of .proto source files. 244# DEPS list of proto library dependencies, produced by tink_cc_proto or not. 245# 246# The resulting library follows the same naming convention as tink_cc_library. 247# 248function(tink_cc_proto) 249 cmake_parse_arguments(PARSE_ARGV 0 tink_cc_proto 250 "" 251 "NAME" 252 "SRCS;DEPS" 253 ) 254 255 set(tink_cc_proto_GEN_SRCS) 256 foreach(_src_path ${tink_cc_proto_SRCS}) 257 get_filename_component(_src_absolute_path "${_src_path}" ABSOLUTE) 258 get_filename_component(_src_basename "${_src_path}" NAME_WE) 259 get_filename_component(_src_dir "${_src_absolute_path}" DIRECTORY) 260 file(RELATIVE_PATH _src_rel_path "${PROJECT_SOURCE_DIR}" "${_src_dir}") 261 262 set(_gen_srcs) 263 foreach(_gen_ext .pb.h .pb.cc) 264 list(APPEND _gen_srcs 265 "${TINK_GENFILE_DIR}/${_src_rel_path}/${_src_basename}${_gen_ext}") 266 endforeach() 267 268 list(APPEND tink_cc_proto_GEN_SRCS ${_gen_srcs}) 269 270 add_custom_command( 271 COMMAND protobuf::protoc 272 ARGS 273 --cpp_out "${TINK_GENFILE_DIR}" 274 -I "${PROJECT_SOURCE_DIR}" 275 "${_src_absolute_path}" 276 OUTPUT 277 ${_gen_srcs} 278 DEPENDS 279 protobuf::protoc 280 ${_src_absolute_path} 281 COMMENT "Running CXX protocol buffer compiler on ${_src_path}" 282 VERBATIM 283 ) 284 endforeach() 285 286 set_source_files_properties( 287 ${tink_cc_proto_GEN_SRCS} PROPERTIES GENERATED true) 288 289 tink_cc_library( 290 NAME ${tink_cc_proto_NAME} 291 SRCS ${tink_cc_proto_GEN_SRCS} 292 DEPS 293 protobuf::libprotoc 294 ${tink_cc_proto_DEPS} 295 ) 296endfunction() 297 298# Declare an empty target, that depends on all those specified. Use this rule 299# to group dependencies that are logically related and give them a single name. 300# 301# Parameters: 302# NAME base name of the target. 303# DEPS list of dependencies to group. 304# 305# Each tink_target_group produces a target named tink_<MODULE>_<NAME>. 306function(tink_target_group) 307 cmake_parse_arguments(PARSE_ARGV 0 tink_target_group "" "NAME" "DEPS") 308 set(_target_name "tink_${TINK_MODULE}_${tink_target_group_NAME}") 309 add_custom_target(${_target_name}) 310 add_dependencies(${_target_name} ${tink_target_group_DEPS}) 311endfunction() 312