reproc.cmake 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. include(CheckCCompilerFlag)
  2. include(CMakePackageConfigHelpers)
  3. include(GenerateExportHeader)
  4. include(GNUInstallDirs)
  5. # Developer options
  6. option(REPROC_DEVELOP "Enable all developer options" $ENV{REPROC_DEVELOP})
  7. option(REPROC_TEST "Build tests" ${REPROC_DEVELOP})
  8. option(REPROC_EXAMPLES "Build examples" ${REPROC_DEVELOP})
  9. option(REPROC_WARNINGS "Enable compiler warnings" ${REPROC_DEVELOP})
  10. option(REPROC_TIDY "Run clang-tidy when building" ${REPROC_DEVELOP})
  11. option(
  12. REPROC_SANITIZERS
  13. "Build with sanitizers on configurations that support it"
  14. ${REPROC_DEVELOP}
  15. )
  16. option(
  17. REPROC_WARNINGS_AS_ERRORS
  18. "Add -Werror or equivalent to the compile flags and clang-tidy"
  19. )
  20. mark_as_advanced(
  21. REPROC_TIDY
  22. REPROC_SANITIZERS
  23. REPROC_WARNINGS_AS_ERRORS
  24. )
  25. # Installation options
  26. option(REPROC_OBJECT_LIBRARIES "Build CMake object libraries" ${REPROC_DEVELOP})
  27. if(NOT REPROC_OBJECT_LIBRARIES)
  28. set(REPROC_INSTALL_DEFAULT ON)
  29. endif()
  30. option(REPROC_INSTALL "Generate installation rules" ${REPROC_INSTALL_DEFAULT})
  31. option(REPROC_INSTALL_PKGCONFIG "Install pkg-config files" ON)
  32. set(
  33. REPROC_INSTALL_CMAKECONFIGDIR
  34. ${CMAKE_INSTALL_LIBDIR}/cmake
  35. CACHE STRING "CMake config files installation directory"
  36. )
  37. set(
  38. REPROC_INSTALL_PKGCONFIGDIR
  39. ${CMAKE_INSTALL_LIBDIR}/pkgconfig
  40. CACHE STRING "pkg-config files installation directory"
  41. )
  42. mark_as_advanced(
  43. REPROC_OBJECT_LIBRARIES
  44. REPROC_INSTALL
  45. REPROC_INSTALL_PKGCONFIG
  46. REPROC_INSTALL_CMAKECONFIGDIR
  47. REPROC_INSTALL_PKGCONFIGDIR
  48. )
  49. # Testing
  50. if(REPROC_TEST)
  51. enable_testing()
  52. endif()
  53. # Build type
  54. if(REPROC_DEVELOP AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  55. set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
  56. set_property(
  57. CACHE CMAKE_BUILD_TYPE
  58. PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo
  59. )
  60. endif()
  61. # clang-tidy
  62. if(REPROC_TIDY)
  63. find_program(REPROC_TIDY_PROGRAM clang-tidy)
  64. mark_as_advanced(REPROC_TIDY_PROGRAM)
  65. if(NOT REPROC_TIDY_PROGRAM)
  66. message(FATAL_ERROR "clang-tidy not found")
  67. endif()
  68. if(REPROC_WARNINGS_AS_ERRORS)
  69. set(REPROC_TIDY_PROGRAM ${REPROC_TIDY_PROGRAM} -warnings-as-errors=*)
  70. endif()
  71. endif()
  72. # Functions
  73. function(reproc_common TARGET LANGUAGE NAME DIRECTORY)
  74. if(LANGUAGE STREQUAL C)
  75. set(STANDARD 99)
  76. target_compile_features(${TARGET} PUBLIC c_std_99)
  77. else()
  78. # clang-tidy uses the MSVC standard library instead of MinGW's standard
  79. # library so we have to use C++14 (because MSVC headers use C++14).
  80. if(MINGW AND REPROC_TIDY)
  81. set(STANDARD 14)
  82. else()
  83. set(STANDARD 11)
  84. endif()
  85. target_compile_features(${TARGET} PUBLIC cxx_std_11)
  86. endif()
  87. set_target_properties(${TARGET} PROPERTIES
  88. ${LANGUAGE}_STANDARD ${STANDARD}
  89. ${LANGUAGE}_STANDARD_REQUIRED ON
  90. ${LANGUAGE}_EXTENSIONS OFF
  91. OUTPUT_NAME "${NAME}"
  92. RUNTIME_OUTPUT_DIRECTORY "${DIRECTORY}"
  93. ARCHIVE_OUTPUT_DIRECTORY "${DIRECTORY}"
  94. LIBRARY_OUTPUT_DIRECTORY "${DIRECTORY}"
  95. )
  96. if(REPROC_TIDY AND REPROC_TIDY_PROGRAM)
  97. set_property(
  98. TARGET ${TARGET}
  99. # `REPROC_TIDY_PROGRAM` is a list so we surround it with quotes to pass it
  100. # as a single argument.
  101. PROPERTY ${LANGUAGE}_CLANG_TIDY "${REPROC_TIDY_PROGRAM}"
  102. )
  103. endif()
  104. # Common development flags (warnings + sanitizers + colors)
  105. if(REPROC_WARNINGS)
  106. if(MSVC)
  107. check_c_compiler_flag(/permissive- REPROC_HAVE_PERMISSIVE)
  108. target_compile_options(${TARGET} PRIVATE
  109. /nologo # Silence MSVC compiler version output.
  110. $<$<BOOL:${REPROC_WARNINGS_AS_ERRORS}>:/WX> # -Werror
  111. $<$<BOOL:${REPROC_HAVE_PERMISSIVE}>:/permissive->
  112. )
  113. if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15.0)
  114. # CMake 3.15 does not add /W3 to the compiler flags by default anymore
  115. # so we add /W4 instead.
  116. target_compile_options(${TARGET} PRIVATE /W4)
  117. endif()
  118. if(LANGUAGE STREQUAL C)
  119. # Disable MSVC warnings that flag C99 features as non-standard.
  120. target_compile_options(${TARGET} PRIVATE /wd4204 /wd4221)
  121. endif()
  122. else()
  123. target_compile_options(${TARGET} PRIVATE
  124. -Wall
  125. -Wextra
  126. -pedantic
  127. -Wconversion
  128. -Wsign-conversion
  129. $<$<BOOL:${REPROC_WARNINGS_AS_ERRORS}>:-Werror>
  130. $<$<BOOL:${REPROC_WARNINGS_AS_ERRORS}>:-pedantic-errors>
  131. )
  132. if(LANGUAGE STREQUAL C OR CMAKE_CXX_COMPILER_ID MATCHES Clang)
  133. target_compile_options(${TARGET} PRIVATE -Wmissing-prototypes)
  134. endif()
  135. endif()
  136. if(WIN32)
  137. target_compile_definitions(${TARGET} PRIVATE _CRT_SECURE_NO_WARNINGS)
  138. endif()
  139. target_compile_options(${TARGET} PRIVATE
  140. $<$<${LANGUAGE}_COMPILER_ID:GNU>:-fdiagnostics-color>
  141. $<$<${LANGUAGE}_COMPILER_ID:Clang>:-fcolor-diagnostics>
  142. )
  143. endif()
  144. if(REPROC_SANITIZERS AND NOT MSVC AND NOT MINGW)
  145. if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15.0)
  146. set_property(
  147. TARGET ${TARGET}
  148. PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded
  149. )
  150. endif()
  151. target_compile_options(${TARGET} PRIVATE
  152. -fsanitize=address,undefined
  153. -fno-omit-frame-pointer
  154. )
  155. target_link_libraries(${TARGET} PRIVATE -fsanitize=address,undefined)
  156. endif()
  157. endfunction()
  158. function(reproc_library TARGET LANGUAGE)
  159. if(REPROC_OBJECT_LIBRARIES)
  160. add_library(${TARGET} OBJECT)
  161. else()
  162. add_library(${TARGET})
  163. endif()
  164. reproc_common(${TARGET} ${LANGUAGE} "" lib)
  165. if(BUILD_SHARED_LIBS AND NOT REPROC_OBJECT_LIBRARIES)
  166. # Enable -fvisibility=hidden and -fvisibility-inlines-hidden.
  167. set_target_properties(${TARGET} PROPERTIES
  168. ${LANGUAGE}_VISIBILITY_PRESET hidden
  169. VISIBILITY_INLINES_HIDDEN true
  170. )
  171. # clang-tidy errors with: unknown argument: '-fno-keep-inline-dllexport'
  172. # when enabling `VISIBILITY_INLINES_HIDDEN` on MinGW so we disable it when
  173. # running clang-tidy on MinGW.
  174. if(MINGW AND REPROC_TIDY)
  175. set_property(TARGET ${TARGET} PROPERTY VISIBILITY_INLINES_HIDDEN false)
  176. endif()
  177. # Disable CMake's default export definition.
  178. set_property(TARGET ${TARGET} PROPERTY DEFINE_SYMBOL "")
  179. string(TOUPPER ${TARGET} TARGET_UPPER)
  180. string(REPLACE + X TARGET_SANITIZED ${TARGET_UPPER})
  181. target_compile_definitions(${TARGET} PRIVATE ${TARGET_SANITIZED}_BUILDING)
  182. if(WIN32)
  183. target_compile_definitions(${TARGET} PUBLIC ${TARGET_SANITIZED}_SHARED)
  184. endif()
  185. endif()
  186. # Make sure we follow the popular naming convention for shared libraries on
  187. # UNIX systems.
  188. set_target_properties(${TARGET} PROPERTIES
  189. VERSION ${PROJECT_VERSION}
  190. SOVERSION ${PROJECT_VERSION_MAJOR}
  191. )
  192. # Only use the headers from the repository when building. When installing we
  193. # want to use the install location of the headers (e.g. /usr/include) as the
  194. # include directory instead.
  195. target_include_directories(${TARGET} PUBLIC
  196. $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  197. )
  198. # Adapted from https://codingnest.com/basic-cmake-part-2/.
  199. # Each library is installed separately (with separate config files).
  200. if(REPROC_INSTALL)
  201. # Headers
  202. install(
  203. DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
  204. DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  205. COMPONENT ${TARGET}-development
  206. )
  207. # Library
  208. install(
  209. TARGETS ${TARGET}
  210. EXPORT ${TARGET}-targets
  211. RUNTIME
  212. DESTINATION ${CMAKE_INSTALL_BINDIR}
  213. COMPONENT ${TARGET}-runtime
  214. LIBRARY
  215. DESTINATION ${CMAKE_INSTALL_LIBDIR}
  216. COMPONENT ${TARGET}-runtime
  217. NAMELINK_COMPONENT ${TARGET}-development
  218. ARCHIVE
  219. DESTINATION ${CMAKE_INSTALL_LIBDIR}
  220. COMPONENT ${TARGET}-development
  221. INCLUDES
  222. DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  223. )
  224. if(NOT APPLE)
  225. set_property(
  226. TARGET ${TARGET}
  227. PROPERTY INSTALL_RPATH $ORIGIN
  228. )
  229. endif()
  230. # CMake config
  231. configure_package_config_file(
  232. ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}-config.cmake.in
  233. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config.cmake
  234. INSTALL_DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET}
  235. )
  236. write_basic_package_version_file(
  237. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config-version.cmake
  238. COMPATIBILITY SameMajorVersion
  239. )
  240. install(
  241. FILES
  242. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config.cmake
  243. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-config-version.cmake
  244. DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET}
  245. COMPONENT ${TARGET}-development
  246. )
  247. install(
  248. EXPORT ${TARGET}-targets
  249. DESTINATION ${REPROC_INSTALL_CMAKECONFIGDIR}/${TARGET}
  250. COMPONENT ${TARGET}-development
  251. )
  252. # pkg-config
  253. if(REPROC_INSTALL_PKGCONFIG)
  254. configure_file(
  255. ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET}.pc.in
  256. ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.pc
  257. @ONLY
  258. )
  259. install(
  260. FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.pc
  261. DESTINATION ${REPROC_INSTALL_PKGCONFIGDIR}
  262. COMPONENT ${TARGET}-development
  263. )
  264. endif()
  265. endif()
  266. endfunction()
  267. function(reproc_test TARGET NAME LANGUAGE)
  268. if(NOT REPROC_TEST)
  269. return()
  270. endif()
  271. if(LANGUAGE STREQUAL C)
  272. set(EXTENSION c)
  273. else()
  274. set(EXTENSION cpp)
  275. endif()
  276. add_executable(${TARGET}-test-${NAME} test/${NAME}.${EXTENSION})
  277. reproc_common(${TARGET}-test-${NAME} ${LANGUAGE} ${NAME} test)
  278. target_link_libraries(${TARGET}-test-${NAME} PRIVATE ${TARGET})
  279. if(MINGW)
  280. target_compile_definitions(${TARGET}-test-${NAME} PRIVATE
  281. __USE_MINGW_ANSI_STDIO=1 # Add %zu on Mingw
  282. )
  283. endif()
  284. add_test(NAME ${TARGET}-test-${NAME} COMMAND ${TARGET}-test-${NAME})
  285. if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources/${NAME}.c)
  286. target_compile_definitions(${TARGET}-test-${NAME} PRIVATE
  287. RESOURCE_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/resources"
  288. )
  289. if (NOT TARGET ${TARGET}-resource-${NAME})
  290. add_executable(${TARGET}-resource-${NAME} resources/${NAME}.c)
  291. reproc_common(${TARGET}-resource-${NAME} C ${NAME} resources)
  292. endif()
  293. # Make sure the test resource is available when running the test.
  294. add_dependencies(${TARGET}-test-${NAME} ${TARGET}-resource-${NAME})
  295. endif()
  296. endfunction()
  297. function(reproc_example TARGET NAME LANGUAGE)
  298. cmake_parse_arguments(OPT "" "" "ARGS;DEPENDS" ${ARGN})
  299. if(NOT REPROC_EXAMPLES)
  300. return()
  301. endif()
  302. if(LANGUAGE STREQUAL C)
  303. set(EXTENSION c)
  304. else()
  305. set(EXTENSION cpp)
  306. endif()
  307. add_executable(${TARGET}-example-${NAME} examples/${NAME}.${EXTENSION})
  308. reproc_common(${TARGET}-example-${NAME} ${LANGUAGE} ${NAME} examples)
  309. target_link_libraries(${TARGET}-example-${NAME} PRIVATE ${TARGET} ${OPT_DEPENDS})
  310. if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources/${NAME}.c)
  311. target_compile_definitions(${TARGET}-example-${NAME} PRIVATE
  312. RESOURCE_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/resources"
  313. )
  314. if (NOT TARGET ${TARGET}-resource-${NAME})
  315. add_executable(${TARGET}-resource-${NAME} resources/${NAME}.c)
  316. reproc_common(${TARGET}-resource-${NAME} C ${NAME} resources)
  317. endif()
  318. # Make sure the example resource is available when running the example.
  319. add_dependencies(${TARGET}-example-${NAME} ${TARGET}-resource-${NAME})
  320. endif()
  321. if(REPROC_TEST)
  322. if(NOT DEFINED OPT_ARGS)
  323. set(OPT_ARGS cmake --help)
  324. endif()
  325. add_test(
  326. NAME ${TARGET}-example-${NAME}
  327. COMMAND ${TARGET}-example-${NAME} ${OPT_ARGS}
  328. )
  329. endif()
  330. endfunction()