xref: /aosp_15_r20/external/tink/cmake/TinkBuildRules.cmake (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
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