1 # Requires CVS CMake for 'function' and '-E touch' and '--build'
5 find_package(Subversion)
7 function(_aep_parse_arguments f name ns args)
8 # Transfer the arguments to this function into target properties for the
9 # new custom target we just added so that we can set up all the build steps
10 # correctly based on target properties.
12 # We loop through ARGN and consider the namespace starting with an
13 # upper-case letter followed by at least two more upper-case letters
14 # or underscores to be keywords.
16 foreach(arg IN LISTS args)
17 if(arg MATCHES "^[A-Z][A-Z_][A-Z_]+$" AND
18 NOT ((arg STREQUAL "${key}") AND (key STREQUAL "COMMAND")) AND
19 NOT arg MATCHES "^(TRUE|FALSE)$")
22 if(_aep_keywords_${f} AND NOT key MATCHES "${_aep_keywords_${f}}")
23 message(AUTHOR_WARNING "unknown ${f} keyword: ${key}")
27 if(NOT arg STREQUAL "")
28 set_property(TARGET ${name} APPEND PROPERTY ${ns}${key} "${arg}")
30 get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET)
32 get_property(value TARGET ${name} PROPERTY ${ns}${key})
33 set_property(TARGET ${name} PROPERTY ${ns}${key} "${value};${arg}")
35 set_property(TARGET ${name} PROPERTY ${ns}${key} "${arg}")
40 message(AUTHOR_WARNING "value with no keyword in ${f}")
43 endfunction(_aep_parse_arguments)
46 function(get_external_project_directories base_dir_var build_dir_var downloads_dir_var install_dir_var sentinels_dir_var source_dir_var tmp_dir_var)
47 set(base "${CMAKE_BINARY_DIR}/CMakeExternals")
48 set(${base_dir_var} "${base}" PARENT_SCOPE)
49 set(${build_dir_var} "${base}/Build" PARENT_SCOPE)
50 set(${downloads_dir_var} "${base}/Downloads" PARENT_SCOPE)
51 set(${install_dir_var} "${base}/Install" PARENT_SCOPE)
52 set(${sentinels_dir_var} "${base}/Sentinels" PARENT_SCOPE)
53 set(${source_dir_var} "${base}/Source" PARENT_SCOPE)
54 set(${tmp_dir_var} "${base}/tmp" PARENT_SCOPE)
55 endfunction(get_external_project_directories)
58 function(get_configure_build_working_dir name working_dir_var)
59 get_external_project_directories(base_dir build_dir downloads_dir install_dir
60 sentinels_dir source_dir tmp_dir)
62 get_target_property(dir ${name} AEP_CONFIGURE_DIR)
64 if (IS_ABSOLUTE "${dir}")
65 set(working_dir "${dir}")
67 set(working_dir "${source_dir}/${name}/${dir}")
70 set(working_dir "${build_dir}/${name}")
73 set(${working_dir_var} "${working_dir}" PARENT_SCOPE)
74 endfunction(get_configure_build_working_dir)
77 function(get_configure_command_id name cfg_cmd_id_var)
78 get_target_property(cmd ${name} AEP_CONFIGURE_COMMAND)
81 # Explicit empty string means no configure step for this project
82 set(${cfg_cmd_id_var} "none" PARENT_SCOPE)
85 # Default is "use cmake":
86 set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
88 # Otherwise we have to analyze the value:
89 if(cmd MATCHES "^[^;]*/configure")
90 set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
91 elseif(cmd MATCHES "^[^;]*/cmake" AND NOT cmd MATCHES ";-[PE];")
92 set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
93 elseif(cmd MATCHES "config")
94 set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
96 set(${cfg_cmd_id_var} "unknown:${cmd}" PARENT_SCOPE)
100 endfunction(get_configure_command_id)
102 function(_aep_get_build_command name step cmd_var)
103 set(cmd "${${cmd_var}}")
106 get_configure_command_id(${name} cfg_cmd_id)
107 if(cfg_cmd_id STREQUAL "cmake")
108 # CMake project. Select build command based on generator.
109 get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR)
110 if("${cmake_generator}" MATCHES "Make" AND
111 "${cmake_generator}" STREQUAL "${CMAKE_GENERATOR}")
112 # The project uses the same Makefile generator. Use recursive make.
114 if(step STREQUAL "INSTALL")
118 # Drive the project with "cmake --build".
119 get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
121 set(cmd "${cmake_command}")
123 set(cmd "${CMAKE_COMMAND}")
125 set(args --build ${working_dir} --config ${CMAKE_CFG_INTDIR})
126 if(step STREQUAL "INSTALL")
127 list(APPEND args --target install)
130 else() # if(cfg_cmd_id STREQUAL "configure")
131 # Non-CMake project. Guess "make" and "make install".
133 if(step STREQUAL "INSTALL")
138 # Use user-specified arguments instead of default arguments, if any.
139 get_property(have_args TARGET ${name} PROPERTY AEP_${step}_ARGS SET)
141 get_target_property(args ${name} AEP_${step}_ARGS)
144 list(APPEND cmd ${args})
147 set(${cmd_var} "${cmd}" PARENT_SCOPE)
148 endfunction(_aep_get_build_command)
151 file(MAKE_DIRECTORY "${d}")
152 #message(STATUS "mkdir d='${d}'")
153 if(NOT EXISTS "${d}")
154 message(FATAL_ERROR "error: dir '${d}' does not exist after file(MAKE_DIRECTORY call...")
158 # Pre-compute a regex to match known keywords.
159 set(_aep_keyword_regex "^(")
160 set(_aep_keyword_sep)
170 set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
171 set(_aep_keyword_sep "|")
173 set(_aep_keyword_regex "${_aep_keyword_regex})$")
174 set(_aep_keyword_sep)
175 set(_aep_keywords_add_external_project_step "${_aep_keyword_regex}")
177 function(add_external_project_step name step)
178 get_external_project_directories(base_dir build_dir downloads_dir install_dir
179 sentinels_dir source_dir tmp_dir)
180 add_custom_command(APPEND
181 OUTPUT ${sentinels_dir}/${name}-complete
182 DEPENDS ${sentinels_dir}/${name}-${step}
184 _aep_parse_arguments(add_external_project_step
185 ${name} AEP_${step}_ "${ARGN}")
187 # Steps depending on this step.
188 get_property(dependers TARGET ${name} PROPERTY AEP_${step}_DEPENDERS)
189 foreach(depender IN LISTS dependers)
190 add_custom_command(APPEND
191 OUTPUT ${sentinels_dir}/${name}-${depender}
192 DEPENDS ${sentinels_dir}/${name}-${step}
196 # Dependencies on files.
197 get_property(depends TARGET ${name} PROPERTY AEP_${step}_DEPENDS)
199 # Dependencies on steps.
200 get_property(dependees TARGET ${name} PROPERTY AEP_${step}_DEPENDEES)
201 foreach(dependee IN LISTS dependees)
202 list(APPEND depends ${sentinels_dir}/${name}-${dependee})
205 # The command to run.
206 get_property(command TARGET ${name} PROPERTY AEP_${step}_COMMAND)
208 set(comment "Performing ${step} step for '${name}'")
210 set(comment "No ${step} step for '${name}'")
212 get_property(work_dir TARGET ${name} PROPERTY AEP_${step}_WORKING_DIRECTORY)
215 get_property(comment_set TARGET ${name} PROPERTY AEP_${step}_COMMENT SET)
217 get_property(comment TARGET ${name} PROPERTY AEP_${step}_COMMENT)
221 get_property(symbolic TARGET ${name} PROPERTY AEP_${step}_SYMBOLIC)
223 set_property(SOURCE ${sentinels_dir}/${name}-${step} PROPERTY SYMBOLIC 1)
226 set(touch ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-${step})
230 OUTPUT ${sentinels_dir}/${name}-${step}
235 WORKING_DIRECTORY ${work_dir}
238 endfunction(add_external_project_step)
240 function(add_external_project_download_command name)
241 get_external_project_directories(base_dir build_dir downloads_dir install_dir
242 sentinels_dir source_dir tmp_dir)
244 get_property(cmd_set TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND SET)
245 get_property(cmd TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND)
246 get_property(cvs_repository TARGET ${name} PROPERTY AEP_CVS_REPOSITORY)
247 get_property(svn_repository TARGET ${name} PROPERTY AEP_SVN_REPOSITORY)
248 get_property(dir TARGET ${name} PROPERTY AEP_DIR)
249 get_property(tar TARGET ${name} PROPERTY AEP_TAR)
250 get_property(tgz TARGET ${name} PROPERTY AEP_TGZ)
251 get_property(tgz_url TARGET ${name} PROPERTY AEP_TGZ_URL)
252 get_property(tar_url TARGET ${name} PROPERTY AEP_TAR_URL)
254 set(depends ${sentinels_dir}/CMakeExternals-directories)
259 set(work_dir ${downloads_dir})
260 elseif(cvs_repository)
261 if(NOT CVS_EXECUTABLE)
262 message(FATAL_ERROR "error: could not find cvs for checkout of ${name}")
265 get_target_property(cvs_module ${name} AEP_CVS_MODULE)
267 message(FATAL_ERROR "error: no CVS_MODULE")
270 get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
272 set(repository ${cvs_repository})
273 set(module ${cvs_module})
276 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
277 "${sentinels_dir}/${name}-cvsinfo.txt"
281 mkdir("${source_dir}/${name}")
282 set(work_dir ${source_dir})
283 set(comment "Performing download step (CVS checkout) for '${name}'")
284 set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q co ${cvs_tag} -d ${name} ${cvs_module})
285 list(APPEND depends ${sentinels_dir}/${name}-cvsinfo.txt)
286 elseif(svn_repository)
287 if(NOT Subversion_SVN_EXECUTABLE)
288 message(FATAL_ERROR "error: could not find svn for checkout of ${name}")
291 get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
293 set(repository ${svn_repository})
297 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
298 "${sentinels_dir}/${name}-svninfo.txt"
302 mkdir("${source_dir}/${name}")
303 set(work_dir ${source_dir})
304 set(comment "Performing download step (SVN checkout) for '${name}'")
305 set(cmd ${Subversion_SVN_EXECUTABLE} co ${svn_repository} ${svn_tag} ${name})
306 list(APPEND depends ${sentinels_dir}/${name}-svninfo.txt)
308 get_filename_component(abs_dir "${dir}" ABSOLUTE)
310 set(repository "add_external_project DIR")
311 set(module "${abs_dir}")
315 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
316 "${sentinels_dir}/${name}-dirinfo.txt"
320 mkdir("${source_dir}/${name}")
321 set(work_dir ${source_dir})
322 set(comment "Performing download step (DIR copy) for '${name}'")
323 set(cmd ${CMAKE_COMMAND} -E remove_directory ${source_dir}/${name}
324 COMMAND ${CMAKE_COMMAND} -E copy_directory ${abs_dir} ${source_dir}/${name})
325 list(APPEND depends ${sentinels_dir}/${name}-dirinfo.txt)
327 mkdir("${source_dir}/${name}")
328 set(work_dir ${source_dir})
329 set(comment "Performing download step (TAR untar) for '${name}'")
330 set(cmd ${CMAKE_COMMAND} -Dfilename=${tar} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
331 list(APPEND depends ${tar})
333 mkdir("${source_dir}/${name}")
334 set(work_dir ${source_dir})
335 set(comment "Performing download step (TGZ untar) for '${name}'")
336 set(cmd ${CMAKE_COMMAND} -Dfilename=${tgz} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
337 list(APPEND depends ${tgz})
339 set(repository "add_external_project TGZ_URL")
340 set(module "${tgz_url}")
344 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
345 "${sentinels_dir}/${name}-urlinfo.txt"
349 mkdir("${source_dir}/${name}")
350 set(work_dir ${source_dir})
351 set(comment "Performing download step (TGZ_URL download and untar) for '${name}'")
352 set(cmd ${CMAKE_COMMAND} -Dremote=${tgz_url} -Dlocal=${downloads_dir}/${name}.tgz -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
353 COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
354 list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
356 set(repository "add_external_project TAR_URL")
357 set(module "${tar_url}")
361 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
362 "${sentinels_dir}/${name}-urlinfo.txt"
366 mkdir("${source_dir}/${name}")
367 set(work_dir ${source_dir})
368 set(comment "Performing download step (TAR_URL download and untar) for '${name}'")
369 set(cmd ${CMAKE_COMMAND} -Dremote=${tar_url} -Dlocal=${downloads_dir}/${name}.tar -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
370 COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
371 list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
373 message(SEND_ERROR "error: no download info for '${name}'")
376 add_external_project_step(${name} download
379 WORKING_DIRECTORY ${source_dir}
382 endfunction(add_external_project_download_command)
385 function(add_external_project_update_command name)
386 get_external_project_directories(base_dir build_dir downloads_dir install_dir
387 sentinels_dir source_dir tmp_dir)
389 get_property(cmd TARGET ${name} PROPERTY AEP_UPDATE_COMMAND)
390 get_property(cvs_repository TARGET ${name} PROPERTY AEP_CVS_REPOSITORY)
391 get_property(svn_repository TARGET ${name} PROPERTY AEP_SVN_REPOSITORY)
397 set(work_dir ${source_dir}/${name})
398 elseif(cvs_repository)
399 if(NOT CVS_EXECUTABLE)
400 message(FATAL_ERROR "error: could not find cvs for update of ${name}")
402 mkdir("${source_dir}/${name}")
403 set(work_dir ${source_dir}/${name})
404 set(comment "Performing update step (CVS update) for '${name}'")
405 get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
406 set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q up -dP ${cvs_tag})
408 elseif(svn_repository)
409 if(NOT Subversion_SVN_EXECUTABLE)
410 message(FATAL_ERROR "error: could not find svn for update of ${name}")
412 mkdir("${source_dir}/${name}")
413 set(work_dir ${source_dir}/${name})
414 set(comment "Performing update step (SVN update) for '${name}'")
415 get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
416 set(cmd ${Subversion_SVN_EXECUTABLE} up ${svn_tag})
420 add_external_project_step(${name} update
424 WORKING_DIRECTORY ${work_dir}
427 endfunction(add_external_project_update_command)
430 function(add_external_project_patch_command name)
431 get_external_project_directories(base_dir build_dir downloads_dir install_dir
432 sentinels_dir source_dir tmp_dir)
435 get_property(cmd TARGET ${name} PROPERTY AEP_PATCH_COMMAND)
437 set(work_dir ${source_dir}/${name})
440 add_external_project_step(${name} patch
442 WORKING_DIRECTORY ${work_dir}
445 endfunction(add_external_project_patch_command)
448 # TODO: Make sure external projects use the proper compiler
449 function(add_external_project_configure_command name)
450 get_external_project_directories(base_dir build_dir downloads_dir install_dir
451 sentinels_dir source_dir tmp_dir)
452 get_configure_build_working_dir(${name} working_dir)
454 # Depend on other external projects (file-level).
456 get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
457 foreach(arg IN LISTS deps)
458 list(APPEND file_deps ${sentinels_dir}/${arg}-done)
460 #message(STATUS "info: name='${name}' file_deps='${file_deps}'")
462 # Create the working_dir for configure, build and install steps:
464 mkdir("${working_dir}")
465 add_external_project_step(${name} working_dir
466 COMMENT "Making directory \"${working_dir}\""
467 COMMAND ${CMAKE_COMMAND} -E make_directory ${working_dir}
468 DEPENDEES update patch
472 get_property(cmd_set TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND SET)
474 get_property(cmd TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND)
476 get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
478 set(cmd "${cmake_command}")
480 set(cmd "${CMAKE_COMMAND}")
483 get_property(cmake_args TARGET ${name} PROPERTY AEP_CMAKE_ARGS)
484 list(APPEND cmd ${cmake_args})
486 get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR)
488 list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}")
492 add_external_project_step(${name} configure
494 WORKING_DIRECTORY ${working_dir}
495 DEPENDEES working_dir
497 endfunction(add_external_project_configure_command)
500 function(add_external_project_build_command name)
501 get_external_project_directories(base_dir build_dir downloads_dir install_dir
502 sentinels_dir source_dir tmp_dir)
503 get_configure_build_working_dir(${name} working_dir)
505 get_property(cmd_set TARGET ${name} PROPERTY AEP_BUILD_COMMAND SET)
507 get_property(cmd TARGET ${name} PROPERTY AEP_BUILD_COMMAND)
509 _aep_get_build_command(${name} BUILD cmd)
511 add_external_project_step(${name} build
513 WORKING_DIRECTORY ${working_dir}
516 endfunction(add_external_project_build_command)
519 function(add_external_project_install_command name)
520 get_external_project_directories(base_dir build_dir downloads_dir install_dir
521 sentinels_dir source_dir tmp_dir)
522 get_configure_build_working_dir(${name} working_dir)
524 get_property(cmd_set TARGET ${name} PROPERTY AEP_INSTALL_COMMAND SET)
526 get_property(cmd TARGET ${name} PROPERTY AEP_INSTALL_COMMAND)
528 _aep_get_build_command(${name} INSTALL cmd)
530 add_external_project_step(${name} install
532 WORKING_DIRECTORY ${working_dir}
535 endfunction(add_external_project_install_command)
538 function(add_CMakeExternals_target)
539 if(NOT TARGET CMakeExternals)
540 get_external_project_directories(base_dir build_dir downloads_dir install_dir
541 sentinels_dir source_dir tmp_dir)
543 # Make the directories at CMake configure time *and* add a custom command
544 # to make them at build time. They need to exist at makefile generation
545 # time for Borland make and wmake so that CMake may generate makefiles
546 # with "cd C:\short\paths\with\no\spaces" commands in them.
548 # Additionally, the add_custom_command is still used in case somebody
549 # removes one of the necessary directories and tries to rebuild without
552 mkdir("${build_dir}")
553 mkdir("${downloads_dir}")
554 mkdir("${install_dir}")
555 mkdir("${sentinels_dir}")
556 mkdir("${source_dir}")
560 OUTPUT ${sentinels_dir}/CMakeExternals-directories
561 COMMAND ${CMAKE_COMMAND} -E make_directory ${build_dir}
562 COMMAND ${CMAKE_COMMAND} -E make_directory ${downloads_dir}
563 COMMAND ${CMAKE_COMMAND} -E make_directory ${install_dir}
564 COMMAND ${CMAKE_COMMAND} -E make_directory ${sentinels_dir}
565 COMMAND ${CMAKE_COMMAND} -E make_directory ${source_dir}
566 COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir}
567 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/CMakeExternals-directories
568 COMMENT "Creating CMakeExternals directories"
572 add_custom_target(CMakeExternals ALL
573 DEPENDS ${sentinels_dir}/CMakeExternals-directories
576 endfunction(add_CMakeExternals_target)
578 # Pre-compute a regex to match known keywords.
579 set(_aep_keyword_regex "^(")
580 set(_aep_keyword_sep)
606 set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
607 set(_aep_keyword_sep "|")
609 set(_aep_keyword_regex "${_aep_keyword_regex})$")
610 set(_aep_keyword_sep)
611 set(_aep_keywords_add_external_project "${_aep_keyword_regex}")
613 function(add_external_project name)
614 get_external_project_directories(base_dir build_dir downloads_dir install_dir
615 sentinels_dir source_dir tmp_dir)
618 # Ensure root CMakeExternals target and directories are created.
619 # All external projects will depend on this root CMakeExternals target.
621 add_CMakeExternals_target()
624 # Add a custom target for the external project. The 'complete' step
625 # depends on all other steps and creates a 'done' mark. A dependent
626 # external project's 'configure' step depends on the 'done' mark so
627 # that it rebuilds when this project rebuilds. It is important that
628 # 'done' is not the output of any custom command so that CMake does
629 # not propagate build rules to other external project targets.
630 add_custom_target(${name} ALL DEPENDS ${sentinels_dir}/${name}-complete)
632 OUTPUT ${sentinels_dir}/${name}-complete
633 COMMENT "Completed '${name}'"
634 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-complete
635 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-done
636 DEPENDS ${sentinels_dir}/${name}-install
639 set_target_properties(${name} PROPERTIES AEP_IS_EXTERNAL_PROJECT 1)
640 add_dependencies(${name} CMakeExternals)
642 _aep_parse_arguments(add_external_project ${name} AEP_ "${ARGN}")
644 # Depend on other external projects (target-level).
645 get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
646 foreach(arg IN LISTS deps)
647 add_dependencies(${name} ${arg})
650 # Set up custom build steps based on the target properties.
651 # Each step depends on the previous one.
653 # The target depends on the output of the final step.
654 # (Already set up above in the DEPENDS of the add_custom_target command.)
656 add_external_project_download_command(${name})
657 add_external_project_update_command(${name})
658 add_external_project_patch_command(${name})
659 add_external_project_configure_command(${name})
660 add_external_project_build_command(${name})
661 add_external_project_install_command(${name})
662 endfunction(add_external_project)