8ad37f7e79c2ae7443b0b3359b04ba84513f1238
[quassel.git] / cmake / QuasselMacros.cmake
1 # This file contains various functions and macros useful for building Quassel.
2 #
3 # (C) 2014-2018 by the Quassel Project <devel@quassel-irc.org>
4 #
5 # Redistribution and use is allowed according to the terms of the BSD license.
6 # For details see the accompanying COPYING-CMAKE-SCRIPTS file.
7 ###################################################################################################
8
9 include(CMakeParseArguments)
10 include(GenerateExportHeader)
11 include(QuasselCompileFeatures)
12
13 ###################################################################################################
14 # Adds a library target for a Quassel module.
15 #
16 # quassel_add_module(Module [STATIC] [EXPORT])
17 #
18 # The function expects the (CamelCased) module name as a parameter, and derives various
19 # strings from it. For example, quassel_add_module(Client) produces
20 #  - a library target named quassel_client with output name (lib)quassel-client(.so)
21 #  - an alias target named Quassel::Client in global scope
22 #
23 # If the optional argument STATIC is given, a static library is built; otherwise, on
24 # platforms other than Windows, a shared library is created. For shared libraries, also
25 # an install rule is added.
26 #
27 # To generate an export header for the library, specify EXPORT. The header will be named
28 # ${module}-export.h (where ${module} is the lower-case name of the module).
29 #
30 # The function exports the TARGET variable which can be used in the current scope
31 # for setting source files, properties, link dependencies and so on.
32 # To refer to the target outside of the current scope, e.g. for linking, use
33 # the alias name.
34 #
35 function(quassel_add_module _module)
36     set(options EXPORT STATIC)
37     set(oneValueArgs )
38     set(multiValueArgs )
39     cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
40
41     # Derive target, alias target, output name from the given module name
42     set(alias "Quassel::${_module}")
43     set(target ${alias})
44     string(TOLOWER ${target} target)
45     string(REPLACE "::" "_" target ${target})
46     string(REPLACE "_" "-" output_name ${target})
47
48     # On Windows, building shared libraries requires export headers.
49     # Let's bother with that later.
50     if (ARG_STATIC OR WIN32)
51         set(buildmode STATIC)
52     else()
53         set(buildmode SHARED)
54     endif()
55
56     add_library(${target} ${buildmode} "")
57     add_library(${alias} ALIAS ${target})
58
59     target_link_libraries(${target} PRIVATE Qt5::Core)
60     target_include_directories(${target}
61         PUBLIC  ${CMAKE_CURRENT_SOURCE_DIR}
62         PRIVATE ${CMAKE_CURRENT_BINARY_DIR} # for generated files
63     )
64     target_compile_features(${target} PUBLIC ${QUASSEL_COMPILE_FEATURES})
65
66     set_target_properties(${target} PROPERTIES
67         OUTPUT_NAME ${output_name}
68         VERSION ${QUASSEL_MAJOR}.${QUASSEL_MINOR}.${QUASSEL_PATCH}
69     )
70
71     if (buildmode STREQUAL "SHARED")
72         install(TARGETS ${target}
73             RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
74             LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
75             ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
76         )
77     endif()
78
79     if (ARG_EXPORT)
80         string(TOLOWER ${_module} lower_module)
81         string(TOUPPER ${_module} upper_module)
82         string(REPLACE "::" "-" header_base ${lower_module})
83         string(REPLACE "::" "_" macro_base ${upper_module})
84         generate_export_header(${target}
85             BASE_NAME ${macro_base}
86             EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/export/${header_base}-export.h
87         )
88         target_include_directories(${target} PUBLIC ${CMAKE_BINARY_DIR}/export)
89     endif()
90
91     # Export the target name for further use
92     set(TARGET ${target} PARENT_SCOPE)
93 endfunction()
94
95 ###################################################################################################
96 # Provides a library that contains data files as a Qt resource (.qrc).
97 #
98 # quassel_add_resource(QrcName
99 #                      [BASEDIR basedir]
100 #                      [PREFIX prefix]
101 #                      PATTERNS pattern1 pattern2...
102 #                      [DEPENDS dep1 dep2...]
103 # )
104 #
105 # The first parameter is the CamelCased name of the resource; the library target will be called
106 # "Quassel::Resource::QrcName". The library provides a Qt resource named "qrcname" (lowercased QrcName)
107 # containing the files matching PATTERNS relative to BASEDIR (by default, the current source dir).
108 # The resource prefix can be set by giving the PREFIX argument.
109 # Additional target dependencies can be specified with DEPENDS.
110 #
111 function(quassel_add_resource _name)
112     set(options )
113     set(oneValueArgs BASEDIR PREFIX)
114     set(multiValueArgs DEPENDS PATTERNS)
115     cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
116
117     if (NOT ARG_BASEDIR)
118         set(ARG_BASEDIR ${CMAKE_CURRENT_SOURCE_DIR})
119     endif()
120     get_filename_component(basedir ${ARG_BASEDIR} REALPATH)
121
122     string(TOLOWER ${_name} lower_name)
123
124     set(qrc_target   quassel-qrc-${lower_name})
125     set(qrc_file     ${lower_name}.qrc)
126     set(qrc_src      qrc_${lower_name}.cpp)
127     set(qrc_filepath ${CMAKE_CURRENT_BINARY_DIR}/${qrc_file})
128     set(qrc_srcpath  ${CMAKE_CURRENT_BINARY_DIR}/${qrc_src})
129
130     # This target will always be built, so the qrc file will always be freshly generated.
131     # That way, changes to the glob result are always taken into account.
132     add_custom_target(${qrc_target} VERBATIM
133         COMMENT "Generating ${qrc_file}"
134         COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake/GenerateQrc.cmake "${qrc_filepath}" "${ARG_PREFIX}" "${ARG_PATTERNS}"
135         DEPENDS ${ARG_DEPENDS}
136         BYPRODUCTS ${qrc_filepath}
137         WORKING_DIRECTORY ${basedir}
138     )
139     set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${qrc_filepath})
140
141     # RCC sucks and expects the data files relative to the qrc file, with no way to configure it differently.
142     # Only when reading from stdin ("-") it takes the working directory as a base, so we have to use this if
143     # we want to use generated qrc files (which obviously cannot be placed in the source directory).
144     # Since neither autorcc nor qt5_add_resources() support this, we have to invoke rcc manually :(
145     #
146     # On Windows, input redirection apparently doesn't work, however piping does. Use this for all platforms for
147     # consistency, accommodating for the fact that the 'cat' equivalent on Windows is 'type'.
148     if (WIN32)
149         set(cat_cmd type)
150     else()
151         set(cat_cmd cat)
152     endif()
153     add_custom_command(VERBATIM
154         COMMENT "Generating ${qrc_src}"
155         COMMAND ${cat_cmd} "$<SHELL_PATH:${qrc_filepath}>"
156                 | "$<SHELL_PATH:$<TARGET_FILE:Qt5::rcc>>" --name "${lower_name}" --output "$<SHELL_PATH:${qrc_srcpath}>" -
157         DEPENDS ${qrc_target}
158         MAIN_DEPENDENCY ${qrc_filepath}
159         OUTPUT ${qrc_srcpath}
160         WORKING_DIRECTORY ${basedir}
161     )
162
163     # Generate library target that can be referenced elsewhere. Force static, because
164     # we can't easily export symbols from the generated sources.
165     quassel_add_module(Resource::${_name} STATIC)
166     target_sources(${TARGET} PRIVATE ${qrc_srcpath})
167     set_target_properties(${TARGET} PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
168
169     # Set variable for referencing the target from outside
170     set(RESOURCE_TARGET ${TARGET} PARENT_SCOPE)
171 endfunction()
172
173 ###################################################################################################
174 # target_link_if_exists(Target
175 #                       [PUBLIC dep1 dep2...]
176 #                       [PRIVATE dep3 dep4...]
177 # )
178 #
179 # Convenience function to add dependencies to a target only if they exist. This is useful when
180 # handling targets that are conditionally created, e.g. resource libraries depending on -DEMBED_DATA.
181 #
182 # NOTE: In order to link a given target, it must already have been created, i.e its subdirectory
183 #       must already have been added. This is also true for globally visible ALIAS targets that
184 #       can otherwise be linked to regardless of creation order; "if (TARGET...)" does not
185 #       support handling this case correctly.
186 #
187 function(target_link_if_exists _target)
188     set(options )
189     set(oneValueArgs )
190     set(multiValueArgs PUBLIC PRIVATE)
191     cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
192
193     if (ARG_PUBLIC)
194         foreach(dep ${ARG_PUBLIC})
195             if (TARGET ${dep})
196                 target_link_libraries(${_target} PUBLIC ${dep})
197             endif()
198         endforeach()
199     endif()
200
201     if (ARG_PRIVATE)
202         foreach(dep ${ARG_PRIVATE})
203             if (TARGET ${dep})
204                 target_link_libraries(${_target} PRIVATE ${dep})
205             endif()
206         endforeach()
207     endif()
208 endfunction()