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