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