Translate.cmake 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. include_guard(DIRECTORY)
  2. #[[
  3. Add qt translation target.
  4. qm_add_translation(<target>
  5. [LOCALES locales]
  6. [PREFIX prefix]
  7. [SOURCES files... | DIRECTORIES dirs... | TARGETS targets... | TS_FILES files...]
  8. [TS_DIR dir]
  9. [QM_DIR dir]
  10. [TS_OPTIONS options...]
  11. [QM_OPTIONS options...]
  12. [TS_DEPENDS targets...]
  13. [QM_DEPENDS targets...]
  14. [CREATE_ONCE]
  15. )
  16. Arguments:
  17. LOCALES: language names, e.g. zh_CN en_US, must specify if SOURCES or TARGETS is specified
  18. PREFIX: translation file prefix, default to target name
  19. SOURCES: source files
  20. DIRECTORIES: source directories
  21. TARGETS: target names, the source files of which will be collected
  22. TS_FILES: ts file names, add the specified ts file
  23. TS_DIR: ts files destination, default to `CMAKE_CURRENT_SOURCE_DIR`
  24. QM_DIR: qm files destination, default to `CMAKE_CURRENT_BINARY_DIR`
  25. TS_DEPENDS: add lupdate task as a dependency to the given targets
  26. QM_DEPENDS: add lrelease task as a dependency to the given targets
  27. CREATE_ONCE: create translations at configure phase if not exist
  28. ]] #
  29. function(qm_add_translation _target)
  30. set(options CREATE_ONCE)
  31. set(oneValueArgs PREFIX TS_DIR QM_DIR)
  32. set(multiValueArgs LOCALES SOURCES DIRECTORIES TARGETS TS_FILES TS_OPTIONS QM_OPTIONS TS_DEPENDS QM_DEPENDS)
  33. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  34. # Get linguist tools
  35. if(NOT TARGET Qt${QT_VERSION_MAJOR}::lupdate OR NOT TARGET Qt${QT_VERSION_MAJOR}::lrelease)
  36. message(FATAL_ERROR "qm_add_translation: linguist tools not defined. Add find_package(Qt5 COMPONENTS LinguistTools) to CMake to enable.")
  37. endif()
  38. set(_src_files)
  39. set(_include_dirs)
  40. # Collect source files
  41. if(FUNC_SOURCES)
  42. list(APPEND _src_files ${FUNC_SOURCES})
  43. endif()
  44. # Collect source files
  45. if(FUNC_TARGETS)
  46. foreach(_item IN LISTS FUNC_TARGETS)
  47. get_target_property(_type ${_item} TYPE)
  48. if((_type STREQUAL "UTILITY") OR(_type STREQUAL "INTERFACE_LIBRARY"))
  49. continue()
  50. endif()
  51. set(_tmp_files)
  52. get_target_property(_tmp_files ${_item} SOURCES)
  53. list(FILTER _tmp_files INCLUDE REGEX ".+\\.(h|hh|hpp|hxx|c|cc|cpp|cxx|m|mm)$")
  54. list(FILTER _tmp_files EXCLUDE REGEX "^(qasc|moc)_.+")
  55. # Need to convert to absolute path
  56. get_target_property(_target_dir ${_item} SOURCE_DIR)
  57. foreach(_file IN LISTS _tmp_files)
  58. get_filename_component(_abs_file ${_file} ABSOLUTE BASE_DIR ${_target_dir})
  59. list(APPEND _src_files ${_abs_file})
  60. endforeach()
  61. unset(_tmp_files)
  62. get_target_property(_tmp_dirs ${_item} INCLUDE_DIRECTORIES)
  63. list(APPEND _include_dirs ${_tmp_dirs})
  64. endforeach()
  65. endif()
  66. # Collect source directories
  67. if(FUNC_DIRECTORIES)
  68. foreach(_item IN LISTS FUNC_DIRECTORIES)
  69. file(GLOB _tmp
  70. ${_item}/*.h ${_item}/*.hpp
  71. ${_item}/*.hh ${_item}/*.hxx
  72. ${_item}/*.cpp ${_item}/*.cxx
  73. ${_item}/*.c ${_item}/*.cc
  74. ${_item}/*.m ${_item}/*.mm
  75. )
  76. list(APPEND _src_files ${_tmp})
  77. endforeach()
  78. endif()
  79. if(_src_files)
  80. if(NOT FUNC_LOCALES)
  81. message(FATAL_ERROR "qm_add_translation: source files collected but LOCALES not specified!")
  82. endif()
  83. elseif(NOT FUNC_TS_FILES)
  84. message(FATAL_ERROR "qm_add_translation: no source files or ts files collected!")
  85. endif()
  86. add_custom_target(${_target})
  87. set(_qm_depends)
  88. if(FUNC_TS_OPTIONS)
  89. set(_ts_options ${FUNC_TS_OPTIONS})
  90. endif()
  91. if(FUNC_QM_OPTIONS)
  92. set(_qm_options ${FUNC_QM_OPTIONS})
  93. endif()
  94. if(_src_files)
  95. if(FUNC_PREFIX)
  96. set(_prefix ${FUNC_PREFIX})
  97. else()
  98. set(_prefix ${_target})
  99. endif()
  100. if(FUNC_TS_DIR)
  101. set(_ts_dir ${FUNC_TS_DIR})
  102. else()
  103. set(_ts_dir ${CMAKE_CURRENT_SOURCE_DIR})
  104. endif()
  105. set(_ts_files)
  106. foreach(_loc IN LISTS FUNC_LOCALES)
  107. list(APPEND _ts_files ${_ts_dir}/${_prefix}_${_loc}.ts)
  108. endforeach()
  109. # Include options
  110. set(_include_options)
  111. foreach(_inc IN LISTS _include_dirs)
  112. list(APPEND _include_options "-I${_inc}")
  113. endforeach()
  114. # May be an lupdate bug, so we skip passing include directories
  115. # list(APPEND _ts_options ${_include_options})
  116. if(_ts_options)
  117. list(PREPEND _ts_options OPTIONS)
  118. endif()
  119. set(_create_once)
  120. if(FUNC_CREATE_ONCE)
  121. set(_create_once CREATE_ONCE)
  122. endif()
  123. _qm_add_lupdate_target(${_target}_lupdate
  124. INPUT ${_src_files}
  125. OUTPUT ${_ts_files}
  126. ${_ts_options}
  127. ${_create_once}
  128. )
  129. # Add update dependencies
  130. # add_dependencies(${_target} ${_target}_lupdate)
  131. foreach(_item IN LISTS FUNC_TS_DEPENDS)
  132. add_dependencies(${_item} ${_target}_lupdate)
  133. endforeach()
  134. # list(APPEND _qm_depends DEPENDS ${_target}_lupdate)
  135. else()
  136. if(FUNC_PREFIX)
  137. message(WARNING "qm_add_translation: no source files collected, PREFIX ignored")
  138. endif()
  139. if(FUNC_TS_DIR)
  140. message(WARNING "qm_add_translation: no source files collected, TS_DIR ignored")
  141. endif()
  142. if(FUNC_TS_DEPENDS)
  143. message(WARNING "qm_add_translation: no source files collected, TS_DEPENDS ignored")
  144. endif()
  145. endif()
  146. if(FUNC_QM_DIR)
  147. set(_qm_dir ${FUNC_QM_DIR})
  148. else()
  149. set(_qm_dir ${CMAKE_CURRENT_BINARY_DIR})
  150. endif()
  151. set(_qm_target)
  152. if(_qm_options)
  153. list(PREPEND _qm_options OPTIONS)
  154. endif()
  155. _qm_add_lrelease_target(${_target}_lrelease
  156. INPUT ${_ts_files} ${FUNC_TS_FILES}
  157. DESTINATION ${_qm_dir}
  158. ${_qm_options}
  159. ${_qm_depends}
  160. )
  161. add_dependencies(${_target} ${_target}_lrelease)
  162. # Add release dependencies
  163. if(FUNC_TARGETS)
  164. foreach(_item IN LISTS FUNC_TARGETS)
  165. add_dependencies(${_item} ${_target}_lrelease)
  166. endforeach()
  167. endif()
  168. foreach(_item IN LISTS FUNC_QM_DEPENDS)
  169. add_dependencies(${_item} ${_target}_lrelease)
  170. endforeach()
  171. endfunction()
  172. # ----------------------------------
  173. # Private functions
  174. # ----------------------------------
  175. # Input: cxx source files
  176. # Output: target ts files
  177. function(_qm_add_lupdate_target _target)
  178. set(options CREATE_ONCE)
  179. set(oneValueArgs)
  180. set(multiValueArgs INPUT OUTPUT OPTIONS DEPENDS)
  181. cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  182. set(_lupdate_deps ${_LUPDATE_DEPENDS})
  183. set(_my_sources ${_LUPDATE_INPUT})
  184. set(_my_tsfiles ${_LUPDATE_OUTPUT})
  185. add_custom_target(${_target} DEPENDS ${_lupdate_deps})
  186. _get_executable_location(Qt${QT_VERSION_MAJOR}::lupdate _lupdate_exe)
  187. set(_create_once_warning)
  188. set(_create_once_warning_printed off)
  189. # Prepare for create once
  190. if(_LUPDATE_CREATE_ONCE)
  191. # Check if all src files are available
  192. foreach(_file IN LISTS _my_sources)
  193. get_filename_component(_abs_file ${_file} ABSOLUTE)
  194. if(NOT EXISTS ${_abs_file})
  195. get_filename_component(_file ${_file} NAME)
  196. set(_create_once_warning "source file \"${_file}\" is not available, skip generating ts file now")
  197. break()
  198. endif()
  199. endforeach()
  200. # Check if options contain generator expressions
  201. if(NOT _create_once_warning)
  202. foreach(_opt IN LISTS _LUPDATE_OPTIONS)
  203. string(GENEX_STRIP "${_opt}" _no_genex)
  204. if(NOT _no_genex STREQUAL _opt)
  205. set(_create_once_warning "lupdate options contain generator expressions, skip generating ts file now")
  206. break()
  207. endif()
  208. endforeach()
  209. endif()
  210. endif()
  211. foreach(_ts_file IN LISTS _my_tsfiles)
  212. # make a list file to call lupdate on, so we don't make our commands too
  213. # long for some systems
  214. get_filename_component(_ts_name ${_ts_file} NAME)
  215. set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
  216. set(_lst_file_srcs)
  217. foreach(_lst_file_src IN LISTS _my_sources)
  218. set(_lst_file_srcs "${_lst_file_src}\n${_lst_file_srcs}")
  219. endforeach()
  220. get_directory_property(_inc_DIRS INCLUDE_DIRECTORIES)
  221. foreach(_pro_include IN LISTS _inc_DIRS)
  222. get_filename_component(_abs_include "${_pro_include}" ABSOLUTE)
  223. set(_lst_file_srcs "-I${_pro_include}\n${_lst_file_srcs}")
  224. endforeach()
  225. file(WRITE ${_ts_lst_file} "${_lst_file_srcs}")
  226. get_filename_component(_ts_abs ${_ts_file} ABSOLUTE)
  227. if(_LUPDATE_CREATE_ONCE AND NOT EXISTS ${_ts_abs})
  228. if(_create_once_warning)
  229. if(NOT _create_once_warning_printed)
  230. message(WARNING "qm_add_translation: ${_create_once_warning}")
  231. set(_create_once_warning_printed on)
  232. endif()
  233. else()
  234. message(STATUS "Lupdate: Generating ${_ts_name}")
  235. get_filename_component(_abs_file ${_ts_file} ABSOLUTE)
  236. get_filename_component(_dir ${_abs_file} DIRECTORY)
  237. file(MAKE_DIRECTORY ${_dir})
  238. execute_process(
  239. COMMAND ${_lupdate_exe} ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
  240. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  241. OUTPUT_QUIET
  242. COMMAND_ERROR_IS_FATAL ANY
  243. )
  244. endif()
  245. endif()
  246. add_custom_command(
  247. TARGET ${_target} POST_BUILD
  248. COMMAND ${_lupdate_exe}
  249. ARGS ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
  250. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  251. BYPRODUCTS ${_ts_lst_file}
  252. VERBATIM
  253. )
  254. endforeach()
  255. endfunction()
  256. # Input: ts files
  257. # Output: list to append qm files
  258. function(_qm_add_lrelease_target _target)
  259. set(options)
  260. set(oneValueArgs DESTINATION OUTPUT)
  261. set(multiValueArgs INPUT OPTIONS DEPENDS)
  262. cmake_parse_arguments(_LRELEASE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  263. set(_lrelease_files ${_LRELEASE_INPUT})
  264. set(_lrelease_deps ${_LRELEASE_DEPENDS})
  265. _get_executable_location(Qt${QT_VERSION_MAJOR}::lrelease _lrelease_exe)
  266. set(_qm_files)
  267. foreach(_file IN LISTS _lrelease_files)
  268. get_filename_component(_abs_FILE ${_file} ABSOLUTE)
  269. get_filename_component(_qm_file ${_file} NAME)
  270. # everything before the last dot has to be considered the file name (including other dots)
  271. string(REGEX REPLACE "\\.[^.]*$" "" FILE_NAME ${_qm_file})
  272. get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION)
  273. if(output_location)
  274. set(_out_dir ${output_location})
  275. elseif(_LRELEASE_DESTINATION)
  276. set(_out_dir ${_LRELEASE_DESTINATION})
  277. else()
  278. set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
  279. endif()
  280. set(_qm_file "${_out_dir}/${FILE_NAME}.qm")
  281. add_custom_command(
  282. OUTPUT ${_qm_file}
  283. COMMAND ${CMAKE_COMMAND} -E make_directory ${_out_dir}
  284. COMMAND ${_lrelease_exe} ARGS ${_LRELEASE_OPTIONS} ${_abs_FILE} -qm ${_qm_file}
  285. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  286. DEPENDS ${_lrelease_files}
  287. VERBATIM
  288. )
  289. list(APPEND _qm_files ${_qm_file})
  290. endforeach()
  291. add_custom_target(${_target} ALL DEPENDS ${_lrelease_deps} ${_qm_files})
  292. if(_LRELEASE_OUTPUT)
  293. set(${_LRELEASE_OUTPUT} ${_qm_files} PARENT_SCOPE)
  294. endif()
  295. endfunction()
  296. function(_get_executable_location _target _var)
  297. get_target_property(_path ${_target} IMPORTED_LOCATION)
  298. if(NOT _path)
  299. get_target_property(_path ${_target} IMPORTED_LOCATION_RELEASE)
  300. endif()
  301. if(NOT _path)
  302. get_target_property(_path ${_target} IMPORTED_LOCATION_MINSIZEREL)
  303. endif()
  304. if(NOT _path)
  305. get_target_property(_path ${_target} IMPORTED_LOCATION_RELWITHDEBINFO)
  306. endif()
  307. if(NOT _path)
  308. get_target_property(_path ${_target} IMPORTED_LOCATION_DEBUG)
  309. endif()
  310. if(NOT _path)
  311. message(FATAL_ERROR "Could not find imported location of target: ${_target}")
  312. endif()
  313. set(${_var} ${_path} PARENT_SCOPE)
  314. endfunction()