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)
214 # Replace list separators.
215 get_property(sep TARGET ${name} PROPERTY AEP_LIST_SEPARATOR)
217 string(REPLACE "${sep}" "\\;" command "${command}")
221 get_property(comment_set TARGET ${name} PROPERTY AEP_${step}_COMMENT SET)
223 get_property(comment TARGET ${name} PROPERTY AEP_${step}_COMMENT)
227 get_property(symbolic TARGET ${name} PROPERTY AEP_${step}_SYMBOLIC)
229 set_property(SOURCE ${sentinels_dir}/${name}-${step} PROPERTY SYMBOLIC 1)
232 set(touch ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-${step})
236 OUTPUT ${sentinels_dir}/${name}-${step}
241 WORKING_DIRECTORY ${work_dir}
244 endfunction(add_external_project_step)
246 function(add_external_project_download_command name)
247 get_external_project_directories(base_dir build_dir downloads_dir install_dir
248 sentinels_dir source_dir tmp_dir)
250 get_property(cmd_set TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND SET)
251 get_property(cmd TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND)
252 get_property(cvs_repository TARGET ${name} PROPERTY AEP_CVS_REPOSITORY)
253 get_property(svn_repository TARGET ${name} PROPERTY AEP_SVN_REPOSITORY)
254 get_property(dir TARGET ${name} PROPERTY AEP_DIR)
255 get_property(tar TARGET ${name} PROPERTY AEP_TAR)
256 get_property(tgz TARGET ${name} PROPERTY AEP_TGZ)
257 get_property(tgz_url TARGET ${name} PROPERTY AEP_TGZ_URL)
258 get_property(tar_url TARGET ${name} PROPERTY AEP_TAR_URL)
260 set(depends ${sentinels_dir}/CMakeExternals-directories)
265 set(work_dir ${downloads_dir})
266 elseif(cvs_repository)
267 if(NOT CVS_EXECUTABLE)
268 message(FATAL_ERROR "error: could not find cvs for checkout of ${name}")
271 get_target_property(cvs_module ${name} AEP_CVS_MODULE)
273 message(FATAL_ERROR "error: no CVS_MODULE")
276 get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
278 set(repository ${cvs_repository})
279 set(module ${cvs_module})
282 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
283 "${sentinels_dir}/${name}-cvsinfo.txt"
287 mkdir("${source_dir}/${name}")
288 set(work_dir ${source_dir})
289 set(comment "Performing download step (CVS checkout) for '${name}'")
290 set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q co ${cvs_tag} -d ${name} ${cvs_module})
291 list(APPEND depends ${sentinels_dir}/${name}-cvsinfo.txt)
292 elseif(svn_repository)
293 if(NOT Subversion_SVN_EXECUTABLE)
294 message(FATAL_ERROR "error: could not find svn for checkout of ${name}")
297 get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
299 set(repository ${svn_repository})
303 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
304 "${sentinels_dir}/${name}-svninfo.txt"
308 mkdir("${source_dir}/${name}")
309 set(work_dir ${source_dir})
310 set(comment "Performing download step (SVN checkout) for '${name}'")
311 set(cmd ${Subversion_SVN_EXECUTABLE} co ${svn_repository} ${svn_tag} ${name})
312 list(APPEND depends ${sentinels_dir}/${name}-svninfo.txt)
314 get_filename_component(abs_dir "${dir}" ABSOLUTE)
316 set(repository "add_external_project DIR")
317 set(module "${abs_dir}")
321 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
322 "${sentinels_dir}/${name}-dirinfo.txt"
326 mkdir("${source_dir}/${name}")
327 set(work_dir ${source_dir})
328 set(comment "Performing download step (DIR copy) for '${name}'")
329 set(cmd ${CMAKE_COMMAND} -E remove_directory ${source_dir}/${name}
330 COMMAND ${CMAKE_COMMAND} -E copy_directory ${abs_dir} ${source_dir}/${name})
331 list(APPEND depends ${sentinels_dir}/${name}-dirinfo.txt)
333 mkdir("${source_dir}/${name}")
334 set(work_dir ${source_dir})
335 set(comment "Performing download step (TAR untar) for '${name}'")
336 set(cmd ${CMAKE_COMMAND} -Dfilename=${tar} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
337 list(APPEND depends ${tar})
339 mkdir("${source_dir}/${name}")
340 set(work_dir ${source_dir})
341 set(comment "Performing download step (TGZ untar) for '${name}'")
342 set(cmd ${CMAKE_COMMAND} -Dfilename=${tgz} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
343 list(APPEND depends ${tgz})
345 set(repository "add_external_project TGZ_URL")
346 set(module "${tgz_url}")
350 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
351 "${sentinels_dir}/${name}-urlinfo.txt"
355 mkdir("${source_dir}/${name}")
356 set(work_dir ${source_dir})
357 set(comment "Performing download step (TGZ_URL download and untar) for '${name}'")
358 set(cmd ${CMAKE_COMMAND} -Dremote=${tgz_url} -Dlocal=${downloads_dir}/${name}.tgz -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
359 COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
360 list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
362 set(repository "add_external_project TAR_URL")
363 set(module "${tar_url}")
367 "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
368 "${sentinels_dir}/${name}-urlinfo.txt"
372 mkdir("${source_dir}/${name}")
373 set(work_dir ${source_dir})
374 set(comment "Performing download step (TAR_URL download and untar) for '${name}'")
375 set(cmd ${CMAKE_COMMAND} -Dremote=${tar_url} -Dlocal=${downloads_dir}/${name}.tar -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
376 COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
377 list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
379 message(SEND_ERROR "error: no download info for '${name}'")
382 add_external_project_step(${name} download
385 WORKING_DIRECTORY ${source_dir}
388 endfunction(add_external_project_download_command)
391 function(add_external_project_update_command name)
392 get_external_project_directories(base_dir build_dir downloads_dir install_dir
393 sentinels_dir source_dir tmp_dir)
395 get_property(cmd TARGET ${name} PROPERTY AEP_UPDATE_COMMAND)
396 get_property(cvs_repository TARGET ${name} PROPERTY AEP_CVS_REPOSITORY)
397 get_property(svn_repository TARGET ${name} PROPERTY AEP_SVN_REPOSITORY)
403 set(work_dir ${source_dir}/${name})
404 elseif(cvs_repository)
405 if(NOT CVS_EXECUTABLE)
406 message(FATAL_ERROR "error: could not find cvs for update of ${name}")
408 mkdir("${source_dir}/${name}")
409 set(work_dir ${source_dir}/${name})
410 set(comment "Performing update step (CVS update) for '${name}'")
411 get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
412 set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q up -dP ${cvs_tag})
414 elseif(svn_repository)
415 if(NOT Subversion_SVN_EXECUTABLE)
416 message(FATAL_ERROR "error: could not find svn for update of ${name}")
418 mkdir("${source_dir}/${name}")
419 set(work_dir ${source_dir}/${name})
420 set(comment "Performing update step (SVN update) for '${name}'")
421 get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
422 set(cmd ${Subversion_SVN_EXECUTABLE} up ${svn_tag})
426 add_external_project_step(${name} update
430 WORKING_DIRECTORY ${work_dir}
433 endfunction(add_external_project_update_command)
436 function(add_external_project_patch_command name)
437 get_external_project_directories(base_dir build_dir downloads_dir install_dir
438 sentinels_dir source_dir tmp_dir)
441 get_property(cmd TARGET ${name} PROPERTY AEP_PATCH_COMMAND)
443 set(work_dir ${source_dir}/${name})
446 add_external_project_step(${name} patch
448 WORKING_DIRECTORY ${work_dir}
451 endfunction(add_external_project_patch_command)
454 # TODO: Make sure external projects use the proper compiler
455 function(add_external_project_configure_command name)
456 get_external_project_directories(base_dir build_dir downloads_dir install_dir
457 sentinels_dir source_dir tmp_dir)
458 get_configure_build_working_dir(${name} working_dir)
460 # Depend on other external projects (file-level).
462 get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
463 foreach(arg IN LISTS deps)
464 list(APPEND file_deps ${sentinels_dir}/${arg}-done)
466 #message(STATUS "info: name='${name}' file_deps='${file_deps}'")
468 # Create the working_dir for configure, build and install steps:
470 mkdir("${working_dir}")
471 add_external_project_step(${name} working_dir
472 COMMENT "Making directory \"${working_dir}\""
473 COMMAND ${CMAKE_COMMAND} -E make_directory ${working_dir}
474 DEPENDEES update patch
478 get_property(cmd_set TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND SET)
480 get_property(cmd TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND)
482 get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
484 set(cmd "${cmake_command}")
486 set(cmd "${CMAKE_COMMAND}")
489 get_property(cmake_args TARGET ${name} PROPERTY AEP_CMAKE_ARGS)
490 list(APPEND cmd ${cmake_args})
492 get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR)
494 list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}")
498 add_external_project_step(${name} configure
500 WORKING_DIRECTORY ${working_dir}
501 DEPENDEES working_dir
503 endfunction(add_external_project_configure_command)
506 function(add_external_project_build_command name)
507 get_external_project_directories(base_dir build_dir downloads_dir install_dir
508 sentinels_dir source_dir tmp_dir)
509 get_configure_build_working_dir(${name} working_dir)
511 get_property(cmd_set TARGET ${name} PROPERTY AEP_BUILD_COMMAND SET)
513 get_property(cmd TARGET ${name} PROPERTY AEP_BUILD_COMMAND)
515 _aep_get_build_command(${name} BUILD cmd)
517 add_external_project_step(${name} build
519 WORKING_DIRECTORY ${working_dir}
522 endfunction(add_external_project_build_command)
525 function(add_external_project_install_command name)
526 get_external_project_directories(base_dir build_dir downloads_dir install_dir
527 sentinels_dir source_dir tmp_dir)
528 get_configure_build_working_dir(${name} working_dir)
530 get_property(cmd_set TARGET ${name} PROPERTY AEP_INSTALL_COMMAND SET)
532 get_property(cmd TARGET ${name} PROPERTY AEP_INSTALL_COMMAND)
534 _aep_get_build_command(${name} INSTALL cmd)
536 add_external_project_step(${name} install
538 WORKING_DIRECTORY ${working_dir}
541 endfunction(add_external_project_install_command)
544 function(add_CMakeExternals_target)
545 if(NOT TARGET CMakeExternals)
546 get_external_project_directories(base_dir build_dir downloads_dir install_dir
547 sentinels_dir source_dir tmp_dir)
549 # Make the directories at CMake configure time *and* add a custom command
550 # to make them at build time. They need to exist at makefile generation
551 # time for Borland make and wmake so that CMake may generate makefiles
552 # with "cd C:\short\paths\with\no\spaces" commands in them.
554 # Additionally, the add_custom_command is still used in case somebody
555 # removes one of the necessary directories and tries to rebuild without
558 mkdir("${build_dir}")
559 mkdir("${downloads_dir}")
560 mkdir("${install_dir}")
561 mkdir("${sentinels_dir}")
562 mkdir("${source_dir}")
566 OUTPUT ${sentinels_dir}/CMakeExternals-directories
567 COMMAND ${CMAKE_COMMAND} -E make_directory ${build_dir}
568 COMMAND ${CMAKE_COMMAND} -E make_directory ${downloads_dir}
569 COMMAND ${CMAKE_COMMAND} -E make_directory ${install_dir}
570 COMMAND ${CMAKE_COMMAND} -E make_directory ${sentinels_dir}
571 COMMAND ${CMAKE_COMMAND} -E make_directory ${source_dir}
572 COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir}
573 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/CMakeExternals-directories
574 COMMENT "Creating CMakeExternals directories"
578 add_custom_target(CMakeExternals ALL
579 DEPENDS ${sentinels_dir}/CMakeExternals-directories
582 endfunction(add_CMakeExternals_target)
584 # Pre-compute a regex to match known keywords.
585 set(_aep_keyword_regex "^(")
586 set(_aep_keyword_sep)
613 set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
614 set(_aep_keyword_sep "|")
616 set(_aep_keyword_regex "${_aep_keyword_regex})$")
617 set(_aep_keyword_sep)
618 set(_aep_keywords_add_external_project "${_aep_keyword_regex}")
620 function(add_external_project name)
621 get_external_project_directories(base_dir build_dir downloads_dir install_dir
622 sentinels_dir source_dir tmp_dir)
625 # Ensure root CMakeExternals target and directories are created.
626 # All external projects will depend on this root CMakeExternals target.
628 add_CMakeExternals_target()
631 # Add a custom target for the external project. The 'complete' step
632 # depends on all other steps and creates a 'done' mark. A dependent
633 # external project's 'configure' step depends on the 'done' mark so
634 # that it rebuilds when this project rebuilds. It is important that
635 # 'done' is not the output of any custom command so that CMake does
636 # not propagate build rules to other external project targets.
637 add_custom_target(${name} ALL DEPENDS ${sentinels_dir}/${name}-complete)
639 OUTPUT ${sentinels_dir}/${name}-complete
640 COMMENT "Completed '${name}'"
641 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-complete
642 COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-done
643 DEPENDS ${sentinels_dir}/${name}-install
646 set_target_properties(${name} PROPERTIES AEP_IS_EXTERNAL_PROJECT 1)
647 add_dependencies(${name} CMakeExternals)
649 _aep_parse_arguments(add_external_project ${name} AEP_ "${ARGN}")
651 # Depend on other external projects (target-level).
652 get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
653 foreach(arg IN LISTS deps)
654 add_dependencies(${name} ${arg})
657 # Set up custom build steps based on the target properties.
658 # Each step depends on the previous one.
660 # The target depends on the output of the final step.
661 # (Already set up above in the DEPENDS of the add_custom_target command.)
663 add_external_project_download_command(${name})
664 add_external_project_update_command(${name})
665 add_external_project_patch_command(${name})
666 add_external_project_configure_command(${name})
667 add_external_project_build_command(${name})
668 add_external_project_install_command(${name})
669 endfunction(add_external_project)