1 ###########################################################################
2 # Copyright 2015, 2016, 2017 IoT.bzh
4 # author: Fulup Ar Foll <fulup@iot.bzh>
5 # contrib: Romain Forlot <romain.forlot@iot.bzh>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 ###########################################################################
20 #--------------------------------------------------------------------------
22 # Do not change this cmake template
23 # Customise your preferences in "./conf.d/cmake/config.cmake"
24 #--------------------------------------------------------------------------
25 # CMake 3.6 imported macros to simulate list(FILTER ...) subcommand
26 # -----------------------
27 MACRO(PARSE_ARGUMENTS prefix arg_names option_names)
29 FOREACH(arg_name ${arg_names})
30 SET(${prefix}_${arg_name})
32 FOREACH(option ${option_names})
33 SET(${prefix}_${option} FALSE)
36 SET(current_arg_name DEFAULT_ARGS)
39 LIST_CONTAINS(is_arg_name ${arg} ${arg_names})
41 SET(${prefix}_${current_arg_name} ${current_arg_list})
42 SET(current_arg_name ${arg})
45 LIST_CONTAINS(is_option ${arg} ${option_names})
47 SET(${prefix}_${arg} TRUE)
49 SET(current_arg_list ${current_arg_list} ${arg})
53 SET(${prefix}_${current_arg_name} ${current_arg_list})
54 ENDMACRO(PARSE_ARGUMENTS)
56 MACRO(LIST_CONTAINS var value)
58 FOREACH (value2 ${ARGN})
59 IF (${value} STREQUAL ${value2})
61 ENDIF (${value} STREQUAL ${value2})
63 ENDMACRO(LIST_CONTAINS)
66 PARSE_ARGUMENTS(LIST_FILTER "OUTPUT_VARIABLE" "" ${ARGV})
68 LIST(LENGTH LIST_FILTER_DEFAULT_ARGS LIST_FILTER_default_length)
69 IF(${LIST_FILTER_default_length} EQUAL 0)
70 MESSAGE(FATAL_ERROR "LIST_FILTER: missing list variable.")
71 ENDIF(${LIST_FILTER_default_length} EQUAL 0)
72 IF(${LIST_FILTER_default_length} EQUAL 1)
73 MESSAGE(FATAL_ERROR "LIST_FILTER: missing regular expression variable.")
74 ENDIF(${LIST_FILTER_default_length} EQUAL 1)
75 # Reset output variable
76 IF(NOT LIST_FILTER_OUTPUT_VARIABLE)
77 SET(LIST_FILTER_OUTPUT_VARIABLE "LIST_FILTER_internal_output")
78 ENDIF(NOT LIST_FILTER_OUTPUT_VARIABLE)
79 SET(${LIST_FILTER_OUTPUT_VARIABLE})
80 # Extract input list from arguments
81 LIST(GET LIST_FILTER_DEFAULT_ARGS 0 LIST_FILTER_input_list)
82 LIST(REMOVE_AT LIST_FILTER_DEFAULT_ARGS 0)
83 FOREACH(LIST_FILTER_item ${${LIST_FILTER_input_list}})
84 FOREACH(LIST_FILTER_regexp_var ${LIST_FILTER_DEFAULT_ARGS})
85 FOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}})
86 IF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp})
87 LIST(APPEND ${LIST_FILTER_OUTPUT_VARIABLE} ${LIST_FILTER_item})
88 ENDIF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp})
89 ENDFOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}})
90 ENDFOREACH(LIST_FILTER_regexp_var)
91 ENDFOREACH(LIST_FILTER_item)
92 # If OUTPUT_VARIABLE is not specified, overwrite the input list.
93 IF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output")
94 SET(${LIST_FILTER_input_list} ${${LIST_FILTER_OUTPUT_VARIABLE}})
95 ENDIF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output")
98 # Generic useful macro
99 # -----------------------
100 macro(PROJECT_TARGET_ADD TARGET_NAME)
101 set_property(GLOBAL APPEND PROPERTY PROJECT_TARGETS ${TARGET_NAME})
102 set(TARGET_NAME ${TARGET_NAME})
103 endmacro(PROJECT_TARGET_ADD)
105 macro(PROJECT_PKGDEP_ADD PKG_NAME)
106 set_property(GLOBAL APPEND PROPERTY PROJECT_PKG_DEPS ${PKG_NAME})
107 endmacro(PROJECT_PKGDEP_ADD)
109 macro(defstr name value)
110 add_definitions(-D${name}=${value})
113 macro(configure_files_in_dir dir)
114 file(GLOB filelist "${dir}/*in")
115 foreach(file ${filelist})
116 get_filename_component(filename ${file} NAME)
117 string(REGEX REPLACE "target" "${RSYNC_TARGET}" destinationfile ${filename})
118 string(REGEX REPLACE ".in$" "" destinationfile ${destinationfile})
119 configure_file(${file} ${CMAKE_CURRENT_BINARY_DIR}/target/${destinationfile})
120 set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/target/${destinationfile}")
122 endmacro(configure_files_in_dir)
124 # Create custom target dedicated for HTML5 and DATA AGL target type
125 macro(add_input_files INPUT_FILES)
126 set(XML_LIST ${INPUT_FILES})
127 set(LUA_LIST ${INPUT_FILES})
128 set(JSON_LIST ${INPUT_FILES})
129 # These are v3.6 subcommand. Not used as default for now as
130 # many dev use Ubuntu 16.04 which have only 3.5 version
131 #list(FILTER XML_LIST INCLUDE REGEX "xml$")
132 #list(FILTER LUA_LIST INCLUDE REGEX "lua$")
133 #list(FILTER JSON_LIST INCLUDE REGEX "json$")
134 list_filter(XML_LIST "xml$")
135 list_filter(LUA_LIST "lua$")
136 list_filter(JSON_LIST "json$")
138 add_custom_target(${TARGET_NAME}
139 DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}
142 foreach(file ${XML_LIST})
143 add_custom_command(TARGET ${TARGET_NAME}
145 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
146 COMMAND ${XML_CHECKER} ${file}
149 foreach(file ${LUA_LIST})
150 add_custom_command(TARGET ${TARGET_NAME}
152 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
153 COMMAND ${LUA_CHECKER} ${file}
156 foreach(file ${JSON_LIST})
157 add_custom_command(TARGET ${TARGET_NAME}
159 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
160 COMMAND cat ${file} | ${JSON_CHECKER}
164 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}
165 DEPENDS ${INPUT_FILES}
166 COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}
167 COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}
168 COMMAND cp -r ${INPUT_FILES} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}
172 # Set the name of the OPENAPI definition JSON file for binding v2
173 macro(set_openapi_filename openapi_filename)
174 set(OPENAPI_DEF ${openapi_filename} CACHE STRING "OpenAPI JSON file name used to generate binding header file before building a binding v2 target.")
178 macro(project_targets_populate)
179 # Default Widget default directory
180 set(PACKAGE_BINDIR ${PROJECT_PKG_BUILD_DIR}/bin)
181 set(PACKAGE_ETCDIR ${PROJECT_PKG_BUILD_DIR}/etc)
182 set(PACKAGE_LIBDIR ${PROJECT_PKG_BUILD_DIR}/lib)
183 set(PACKAGE_HTTPDIR ${PROJECT_PKG_BUILD_DIR}/htdocs)
184 set(PACKAGE_DATADIR ${PROJECT_PKG_BUILD_DIR}/data)
186 add_custom_command(OUTPUT ${PACKAGE_BINDIR} ${PACKAGE_ETCDIR} ${PACKAGE_LIBDIR} ${PACKAGE_HTTPDIR} ${PACKAGE_DATADIR}
187 COMMAND mkdir -p ${PACKAGE_BINDIR} ${PACKAGE_ETCDIR} ${PACKAGE_LIBDIR} ${PACKAGE_HTTPDIR} ${PACKAGE_DATADIR})
188 add_custom_target(populate DEPENDS ${PACKAGE_BINDIR} ${PACKAGE_ETCDIR} ${PACKAGE_LIBDIR} ${PACKAGE_HTTPDIR} ${PACKAGE_DATADIR})
189 get_property(PROJECT_TARGETS GLOBAL PROPERTY PROJECT_TARGETS)
190 foreach(TARGET ${PROJECT_TARGETS})
191 get_target_property(T ${TARGET} LABELS)
193 # Declaration of a custom command that will populate widget tree with the target
194 set(POPULE_PACKAGE_TARGET "project_populate_${TARGET}")
196 get_target_property(P ${TARGET} PREFIX)
197 get_target_property(BD ${TARGET} BINARY_DIR)
198 get_target_property(SD ${TARGET} SOURCE_DIR)
199 get_target_property(OUT ${TARGET} OUTPUT_NAME)
201 if(P MATCHES "NOTFOUND$")
202 if (${T} STREQUAL "BINDING")
209 if(${T} STREQUAL "BINDING")
210 list(APPEND BINDINGS_LIST "${P}${OUT}")
211 add_custom_command(OUTPUT ${PACKAGE_LIBDIR}/${P}${OUT}.so
212 DEPENDS ${BD}/${P}${OUT}.so
213 COMMAND mkdir -p ${PACKAGE_LIBDIR}
214 COMMAND cp ${BD}/${P}${OUT}.so ${PACKAGE_LIBDIR}
216 add_custom_target(${POPULE_PACKAGE_TARGET} DEPENDS ${PACKAGE_LIBDIR}/${P}${OUT}.so)
217 add_dependencies(populate ${POPULE_PACKAGE_TARGET})
218 add_dependencies(${POPULE_PACKAGE_TARGET} ${TARGET})
219 elseif(${T} STREQUAL "BINDINGV2")
220 add_custom_command(OUTPUT ${PACKAGE_LIBDIR}/${P}${OUT}.so
221 DEPENDS ${BD}/${P}${OUT}.so
222 COMMAND mkdir -p ${PACKAGE_LIBDIR}
223 COMMAND cp ${BD}/${P}${OUT}.so ${PACKAGE_LIBDIR}
225 add_custom_target(${POPULE_PACKAGE_TARGET} DEPENDS ${PACKAGE_LIBDIR}/${P}${OUT}.so)
228 add_custom_command(OUTPUT ${SD}/${OPENAPI_DEF}.h
229 DEPENDS ${SD}/${OPENAPI_DEF}.json
230 COMMAND afb-genskel ${SD}/${OPENAPI_DEF}.json > ${SD}/${OPENAPI_DEF}.h
232 add_custom_target("${TARGET}_GENSKEL" DEPENDS ${SD}/${OPENAPI_DEF}.h
233 COMMENT "Generating OpenAPI header file ${OPENAPI_DEF}.h")
234 add_dependencies(${TARGET} "${TARGET}_GENSKEL")
236 add_custom_command(OUTPUT ${SD}/${OUT}-apidef.h
237 DEPENDS ${SD}/${OUT}-apidef.json
238 COMMAND afb-genskel ${SD}/${OUT}-apidef.json > ${SD}/${OUT}-apidef.h
240 add_custom_target("${TARGET}_GENSKEL" DEPENDS ${SD}/${OUT}-apidef.h
241 COMMENT "Generating OpenAPI header file ${OUT}-apidef.h")
242 add_dependencies(${TARGET} "${TARGET}_GENSKEL")
244 elseif(${T} STREQUAL "EXECUTABLE")
245 add_custom_command(OUTPUT ${PACKAGE_BINDIR}/${P}${OUT}
246 DEPENDS ${BD}/${P}${OUT}
247 COMMAND mkdir -p ${PACKAGE_BINDIR}
248 COMMAND cp ${BD}/${P}${OUT} ${PACKAGE_BINDIR}
250 add_custom_target(${POPULE_PACKAGE_TARGET} DEPENDS ${PACKAGE_BINDIR}/${P}${OUT})
251 add_dependencies(populate ${POPULE_PACKAGE_TARGET})
252 add_dependencies(${POPULE_PACKAGE_TARGET} ${TARGET})
253 elseif(${T} STREQUAL "HTDOCS")
254 add_custom_command(OUTPUT ${PACKAGE_HTTPDIR}-xx
255 DEPENDS ${BD}/${P}${OUT}
256 COMMAND mkdir -p ${PACKAGE_HTTPDIR}
257 COMMAND touch ${PACKAGE_HTTPDIR}
258 COMMAND cp -r ${BD}/${P}${OUT}/* ${PACKAGE_HTTPDIR}
260 add_custom_target(${POPULE_PACKAGE_TARGET} DEPENDS ${PACKAGE_HTTPDIR}-xx)
261 add_dependencies(populate ${POPULE_PACKAGE_TARGET})
262 add_dependencies(${POPULE_PACKAGE_TARGET} ${TARGET})
263 elseif(${T} STREQUAL "DATA")
264 # Generate list of output files instead of just one output directory
265 get_target_property(SF ${TARGET} SOURCES)
267 get_filename_component(JUST_FILENAME ${file} NAME)
268 list(APPEND OUTPUT_FILES ${PACKAGE_DATADIR}/${JUST_FILENAME})
270 add_custom_target(${POPULE_PACKAGE_TARGET})
271 add_custom_command(TARGET ${POPULE_PACKAGE_TARGET}
273 COMMAND mkdir -p ${PACKAGE_DATADIR}
274 COMMAND touch ${PACKAGE_DATADIR}
275 COMMAND cp -r ${BD}/${TARGET} ${PACKAGE_DATADIR}
277 add_dependencies(populate ${POPULE_PACKAGE_TARGET})
278 add_dependencies(${POPULE_PACKAGE_TARGET} ${TARGET})
279 endif(${T} STREQUAL "BINDING")
280 elseif(${CMAKE_BUILD_TYPE} MATCHES "[Dd][Ee][Bb][Uu][Gg]")
281 MESSAGE("${Yellow}.. Warning: ${TARGET} ignored when packaging.${ColourReset}")
284 endmacro(project_targets_populate)
286 macro(remote_targets_populate)
287 if (DEFINED ENV{RSYNC_TARGET})
288 set (RSYNC_TARGET $ENV{RSYNC_TARGET})
290 if (DEFINED ENV{RSYNC_PREFIX})
291 set (RSYNC_PREFIX $ENV{RSYNC_PREFIX})
295 REMOTE_LAUNCH "Test on target with: ${CMAKE_CURRENT_BINARY_DIR}/target/start-on-${RSYNC_TARGET}.sh"
296 CACHE STRING "Command to start ${PROJECT_NAME} on remote target ${RSYNC_TARGET}"
299 if(NOT RSYNC_TARGET OR NOT RSYNC_PREFIX)
300 message ("${Yellow}.. Warning: RSYNC_TARGET RSYNC_PREFIX not defined 'make remote-target-populate' not instanciated${ColourReset}")
301 add_custom_target(remote-target-populate
302 COMMENT "${Red}*** Fatal: RSYNC_TARGET RSYNC_PREFIX environment variables required with 'make remote-target-populate'${ColourReset}"
306 set(BINDINGS_REGEX "not_set")
307 if(DEFINED BINDINGS_LIST)
308 list(LENGTH BINDINGS_LIST BINDINGS_LIST_LENGTH)
309 if(BINDINGS_LIST_LENGTH EQUAL 1)
310 list(GET BINDINGS_LIST 0 BINDINGS_REGEX)
311 string(APPEND BINDINGS_REGEX ".so")
312 elseif(BINDINGS_LIST_LENGTH GREATER 1)
313 foreach(B IN LISTS BINDINGS_LIST)
314 STRING(APPEND BINDINGS_STR "${B}|")
316 STRING(REGEX REPLACE "^(.*)\\|$" "(\\1).so" BINDINGS_REGEX ${BINDINGS_STR})
320 configure_files_in_dir(${TEMPLATE_DIR})
321 configure_files_in_dir(${TEMPLATE_DIR})
323 add_custom_target(remote-target-populate
324 COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/target/*.sh
325 COMMAND rsync -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --archive --delete ${PROJECT_PKG_BUILD_DIR}/ ${RSYNC_TARGET}:${RSYNC_PREFIX}/${PROJECT_NAME}
326 COMMENT "${REMOTE_LAUNCH}"
328 add_dependencies(remote-target-populate populate)
330 endmacro(remote_targets_populate)
332 macro(wgt_package_build)
333 if(NOT EXISTS ${WIDGET_CONFIG_TEMPLATE})
334 MESSAGE(FATAL_ERROR "${Red}WARNING ! Missing mandatory files to build widget file.\nYou need a config.xml template: please specify WIDGET_CONFIG_TEMPLATE correctly.${ColourReset}")
337 MESSAGE(FATAL_ERROR "WIDGET_TYPE must be set in your config.cmake.\neg.: set(WIDGET_TYPE application/vnd.agl.service)")
339 if(NOT DEFINED PROJECT_ICON)
340 if( ${WIDGET_TYPE} MATCHES "agl.native")
341 set(ICON_PATH ${PKG_APP_TEMPLATE_DIR}/wgt/icon-native.png)
342 elseif( ${WIDGET_TYPE} MATCHES "agl.service")
343 set(ICON_PATH ${PKG_APP_TEMPLATE_DIR}/wgt/icon-service.png)
344 elseif( ${WIDGET_TYPE} MATCHES "x-executable")
345 set(ICON_PATH ${PKG_APP_TEMPLATE_DIR}/wgt/icon-qml.png)
346 elseif( ${WIDGET_TYPE} MATCHES "text/html")
347 set(ICON_PATH ${PKG_APP_TEMPLATE_DIR}/wgt/icon-html5.png)
349 elseif(EXISTS "${CMAKE_SOURCE_DIR}/${WIDGET_ICON}")
350 set(ICON_PATH "${CMAKE_SOURCE_DIR}/${WIDGET_ICON}")
351 elseif(EXISTS "${WIDGET_ICON}")
352 set(ICON_PATH "${WIDGET_ICON}")
354 set(ICON_PATH ${CMAKE_SOURCE_DIR}/${PROJECT_APP_TEMPLATES_DIR}/wgt/icon-default.png)
357 if(NOT WIDGET_ENTRY_POINT)
358 set(WIDGET_ENTRY_POINT lib)
361 add_custom_command(OUTPUT ${PROJECT_PKG_BUILD_DIR}/config.xml
362 COMMAND ${CMAKE_COMMAND} -DINFILE=${WIDGET_CONFIG_TEMPLATE} -DOUTFILE=${PROJECT_PKG_BUILD_DIR}/config.xml -DPROJECT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_APP_TEMPLATES_DIR}/cmake/configure_file.cmake
363 COMMAND cp ${ICON_PATH} ${PROJECT_PKG_BUILD_DIR}/${PROJECT_ICON}
366 add_custom_target(packaging_wgt DEPENDS ${PROJECT_PKG_BUILD_DIR}/config.xml)
368 # Fulup ??? copy any extra file in wgt/etc into populate package before building the widget
369 file(GLOB PROJECT_CONF_FILES "${TEMPLATE_DIR}/etc/*")
370 if(${PROJECT_CONF_FILES})
371 file(COPY "${TEMPLATE_DIR}/etc/*" DESTINATION ${PROJECT_PKG_BUILD_DIR}/etc/)
372 endif(${PROJECT_CONF_FILES})
374 add_custom_command(OUTPUT ${PROJECT_NAME}.wgt
375 DEPENDS ${PROJECT_TARGETS}
376 COMMAND wgtpkg-pack -f -o ${PROJECT_NAME}.wgt ${PROJECT_PKG_BUILD_DIR}
379 add_custom_target(widget DEPENDS ${PROJECT_NAME}.wgt)
380 add_dependencies(widget populate packaging_wgt)
381 set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.wgt")
384 message ("${Yellow}.. Warning: RSYNC_TARGET not defined 'make widget-target-install' not instanciated${ColourReset}")
385 add_custom_target(widget-target-install
386 COMMENT "${Red}*** Fatal: RSYNC_TARGET RSYNC_PREFIX environment variables required with 'make widget-target-install'${ColourReset}"
390 configure_files_in_dir(${TEMPLATE_DIR})
391 add_custom_target(widget-target-install
393 COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/target/install-wgt-on-${RSYNC_TARGET}.sh
394 COMMAND ${CMAKE_CURRENT_BINARY_DIR}/target/install-wgt-on-${RSYNC_TARGET}.sh
399 add_custom_command(TARGET widget
401 COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "++ ${PACKAGE_MESSAGE}")
403 endmacro(wgt_package_build)
405 macro(rpm_package_build)
406 add_custom_command(OUTPUT ${NPKG_PROJECT_NAME}.spec
407 DEPENDS ${PROJECT_TARGETS}
410 COMMAND rpmbuild --define=\"%_sourcedir ${PROJECT_PKG_ENTRY_POINT}\" -ba ${PROJECT_PKG_ENTRY_POINT}/${NPKG_PROJECT_NAME}.spec
413 add_custom_target(rpm DEPENDS ${NPKG_PROJECT_NAME}.spec)
414 add_dependencies(rpm populate packaging)
417 add_custom_command(TARGET rpm
419 COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "++ ${PACKAGE_MESSAGE}")
421 endmacro(rpm_package_build)
423 macro(deb_package_build)
425 endmacro(deb_package_build)
427 macro(project_package_build)
428 if(EXISTS ${TEMPLATE_DIR})
432 if(EXISTS ${TEMPLATE_DIR})
436 if(EXISTS ${TEMPLATE_DIR})
439 endmacro(project_package_build)
441 macro(project_subdirs_add)
442 set (ARGSLIST ${ARGN})
443 list(LENGTH ARGSLIST ARGSNUM)
444 if(${ARGSNUM} GREATER 0)
445 file(GLOB filelist "${ARGV0}")
447 file(GLOB filelist "*")
450 foreach(filename ${filelist})
451 if(EXISTS "${filename}/CMakeLists.txt")
452 add_subdirectory(${filename})
453 endif(EXISTS "${filename}/CMakeLists.txt")
455 endmacro(project_subdirs_add)
457 # Print developer helper message when build is done
458 # -------------------------------------------------------
459 macro(project_closing_msg)
460 get_property(PROJECT_TARGETS_SET GLOBAL PROPERTY PROJECT_TARGETS SET)
461 get_property(PROJECT_TARGETS GLOBAL PROPERTY PROJECT_TARGETS)
462 if(CLOSING_MESSAGE AND ${PROJECT_TARGETS_SET})
463 add_custom_target(${PROJECT_NAME}_build_done ALL
464 COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "++ ${CLOSING_MESSAGE}"
466 add_dependencies(${PROJECT_NAME}_build_done
467 ${DEPENDENCIES_TARGET} ${PROJECT_TARGETS})