ENH: make this work for older versions of OSX
[cmake.git] / Modules / AddExternalProject.cmake
blobbe1ab85a44099cf70fcff3dbce83db4e4c936a87
1 # Requires CVS CMake for 'function' and '-E touch' and '--build'
4 find_package(CVS)
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.
11   #
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.
15   set(key)
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)$")
20       # Keyword
21       set(key "${arg}")
22       if(_aep_keywords_${f} AND NOT key MATCHES "${_aep_keywords_${f}}")
23         message(AUTHOR_WARNING "unknown ${f} keyword: ${key}")
24       endif()
25     elseif(key)
26       # Value
27       if(NOT arg STREQUAL "")
28         set_property(TARGET ${name} APPEND PROPERTY ${ns}${key} "${arg}")
29       else()
30         get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET)
31         if(have_key)
32           get_property(value TARGET ${name} PROPERTY ${ns}${key})
33           set_property(TARGET ${name} PROPERTY ${ns}${key} "${value};${arg}")
34         else()
35           set_property(TARGET ${name} PROPERTY ${ns}${key} "${arg}")
36         endif()
37       endif()
38     else()
39       # Missing Keyword
40       message(AUTHOR_WARNING "value with no keyword in ${f}")
41     endif()
42   endforeach()
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)
63   if(dir)
64     if (IS_ABSOLUTE "${dir}")
65       set(working_dir "${dir}")
66     else()
67       set(working_dir "${source_dir}/${name}/${dir}")
68     endif()
69   else()
70     set(working_dir "${build_dir}/${name}")
71   endif()
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)
80   if(cmd STREQUAL "")
81     # Explicit empty string means no configure step for this project
82     set(${cfg_cmd_id_var} "none" PARENT_SCOPE)
83   else()
84     if(NOT cmd)
85       # Default is "use cmake":
86       set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
87     else()
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)
95       else()
96         set(${cfg_cmd_id_var} "unknown:${cmd}" PARENT_SCOPE)
97       endif()
98     endif()
99   endif()
100 endfunction(get_configure_command_id)
102 function(_aep_get_build_command name step cmd_var)
103   set(cmd "${${cmd_var}}")
104   if(NOT cmd)
105     set(args)
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.
113         set(cmd "$(MAKE)")
114         if(step STREQUAL "INSTALL")
115           set(args install)
116         endif()
117       else()
118         # Drive the project with "cmake --build".
119         get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
120         if(cmake_command)
121           set(cmd "${cmake_command}")
122         else()
123           set(cmd "${CMAKE_COMMAND}")
124         endif()
125         set(args --build ${working_dir} --config ${CMAKE_CFG_INTDIR})
126         if(step STREQUAL "INSTALL")
127           list(APPEND args --target install)
128         endif()
129       endif()
130     else() # if(cfg_cmd_id STREQUAL "configure")
131       # Non-CMake project.  Guess "make" and "make install".
132       set(cmd "make")
133       if(step STREQUAL "INSTALL")
134         set(args install)
135       endif()
136     endif()
138     # Use user-specified arguments instead of default arguments, if any.
139     get_property(have_args TARGET ${name} PROPERTY AEP_${step}_ARGS SET)
140     if(have_args)
141       get_target_property(args ${name} AEP_${step}_ARGS)
142     endif()
144     list(APPEND cmd ${args})
145   endif()
147   set(${cmd_var} "${cmd}" PARENT_SCOPE)
148 endfunction(_aep_get_build_command)
150 function(mkdir d)
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...")
155   endif()
156 endfunction(mkdir)
158 # Pre-compute a regex to match known keywords.
159 set(_aep_keyword_regex "^(")
160 set(_aep_keyword_sep)
161 foreach(key IN ITEMS
162     COMMAND
163     COMMENT
164     DEPENDEES
165     DEPENDERS
166     DEPENDS
167     SYMBOLIC
168     WORKING_DIRECTORY
169     )
170   set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
171   set(_aep_keyword_sep "|")
172 endforeach(key)
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}
183     )
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}
193       )
194   endforeach()
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})
203   endforeach()
205   # The command to run.
206   get_property(command TARGET ${name} PROPERTY AEP_${step}_COMMAND)
207   if(command)
208     set(comment "Performing ${step} step for '${name}'")
209   else()
210     set(comment "No ${step} step for '${name}'")
211   endif()
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)
216   if(sep AND command)
217     string(REPLACE "${sep}" "\\;" command "${command}")
218   endif()
220   # Custom comment?
221   get_property(comment_set TARGET ${name} PROPERTY AEP_${step}_COMMENT SET)
222   if(comment_set)
223     get_property(comment TARGET ${name} PROPERTY AEP_${step}_COMMENT)
224   endif()
226   # Run every time?
227   get_property(symbolic TARGET ${name} PROPERTY AEP_${step}_SYMBOLIC)
228   if(symbolic)
229     set_property(SOURCE ${sentinels_dir}/${name}-${step} PROPERTY SYMBOLIC 1)
230     set(touch)
231   else()
232     set(touch ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-${step})
233   endif()
235   add_custom_command(
236     OUTPUT ${sentinels_dir}/${name}-${step}
237     COMMENT ${comment}
238     COMMAND ${command}
239     COMMAND ${touch}
240     DEPENDS ${depends}
241     WORKING_DIRECTORY ${work_dir}
242     VERBATIM
243     )
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)
261   set(comment)
262   set(work_dir)
264   if(cmd_set)
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}")
269     endif()
271     get_target_property(cvs_module ${name} AEP_CVS_MODULE)
272     if(NOT cvs_module)
273       message(FATAL_ERROR "error: no CVS_MODULE")
274     endif()
276     get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
278     set(repository ${cvs_repository})
279     set(module ${cvs_module})
280     set(tag ${cvs_tag})
281     configure_file(
282       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
283       "${sentinels_dir}/${name}-cvsinfo.txt"
284       @ONLY
285       )
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}")
295     endif()
297     get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
299     set(repository ${svn_repository})
300     set(module)
301     set(tag ${svn_tag})
302     configure_file(
303       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
304       "${sentinels_dir}/${name}-svninfo.txt"
305       @ONLY
306       )
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)
313   elseif(dir)
314     get_filename_component(abs_dir "${dir}" ABSOLUTE)
316     set(repository "add_external_project DIR")
317     set(module "${abs_dir}")
318     set(tag "")
320     configure_file(
321       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
322       "${sentinels_dir}/${name}-dirinfo.txt"
323       @ONLY
324       )
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)
332   elseif(tar)
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})
338   elseif(tgz)
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})
344   elseif(tgz_url)
345     set(repository "add_external_project TGZ_URL")
346     set(module "${tgz_url}")
347     set(tag "")
349     configure_file(
350       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
351       "${sentinels_dir}/${name}-urlinfo.txt"
352       @ONLY
353     )
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)
361   elseif(tar_url)
362     set(repository "add_external_project TAR_URL")
363     set(module "${tar_url}")
364     set(tag "")
366     configure_file(
367       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
368       "${sentinels_dir}/${name}-urlinfo.txt"
369       @ONLY
370     )
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)
378   else()
379     message(SEND_ERROR "error: no download info for '${name}'")
380   endif()
382   add_external_project_step(${name} download
383     COMMENT ${comment}
384     COMMAND ${cmd}
385     WORKING_DIRECTORY ${source_dir}
386     DEPENDS ${depends}
387     )
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)
399   set(work_dir)
400   set(comment)
401   set(symbolic)
402   if(cmd)
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}")
407     endif()
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})
413     set(symbolic 1)
414   elseif(svn_repository)
415     if(NOT Subversion_SVN_EXECUTABLE)
416       message(FATAL_ERROR "error: could not find svn for update of ${name}")
417     endif()
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})
423     set(symbolic 1)
424   endif()
426   add_external_project_step(${name} update
427     COMMENT ${comment}
428     COMMAND ${cmd}
429     SYMBOLIC ${symbolic}
430     WORKING_DIRECTORY ${work_dir}
431     DEPENDEES download
432     )
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)
440   set(work_dir)
441   get_property(cmd TARGET ${name} PROPERTY AEP_PATCH_COMMAND)
442   if(cmd)
443     set(work_dir ${source_dir}/${name})
444   endif()
446   add_external_project_step(${name} patch
447     COMMAND ${cmd}
448     WORKING_DIRECTORY ${work_dir}
449     DEPENDEES download
450     )
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).
461   set(file_deps)
462   get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
463   foreach(arg IN LISTS deps)
464     list(APPEND file_deps ${sentinels_dir}/${arg}-done)
465   endforeach()
466   #message(STATUS "info: name='${name}' file_deps='${file_deps}'")
468   # Create the working_dir for configure, build and install steps:
469   #
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
475     DEPENDS ${file_deps}
476     )
478   get_property(cmd_set TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND SET)
479   if(cmd_set)
480     get_property(cmd TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND)
481   else()
482     get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
483     if(cmake_command)
484       set(cmd "${cmake_command}")
485     else()
486       set(cmd "${CMAKE_COMMAND}")
487     endif()
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)
493     if(cmake_generator)
494       list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}")
495     endif()
496   endif()
498   add_external_project_step(${name} configure
499     COMMAND ${cmd}
500     WORKING_DIRECTORY ${working_dir}
501     DEPENDEES working_dir
502     )
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)
512   if(cmd_set)
513     get_property(cmd TARGET ${name} PROPERTY AEP_BUILD_COMMAND)
514   else()
515     _aep_get_build_command(${name} BUILD cmd)
516   endif()
517   add_external_project_step(${name} build
518     COMMAND ${cmd}
519     WORKING_DIRECTORY ${working_dir}
520     DEPENDEES configure
521     )
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)
531   if(cmd_set)
532     get_property(cmd TARGET ${name} PROPERTY AEP_INSTALL_COMMAND)
533   else()
534     _aep_get_build_command(${name} INSTALL cmd)
535   endif()
536   add_external_project_step(${name} install
537     COMMAND ${cmd}
538     WORKING_DIRECTORY ${working_dir}
539     DEPENDEES build
540     )
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.
553     #
554     # Additionally, the add_custom_command is still used in case somebody
555     # removes one of the necessary directories and tries to rebuild without
556     # re-running cmake.
557     #
558     mkdir("${build_dir}")
559     mkdir("${downloads_dir}")
560     mkdir("${install_dir}")
561     mkdir("${sentinels_dir}")
562     mkdir("${source_dir}")
563     mkdir("${tmp_dir}")
565     add_custom_command(
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"
575       VERBATIM
576     )
578     add_custom_target(CMakeExternals ALL
579       DEPENDS ${sentinels_dir}/CMakeExternals-directories
580     )
581   endif()
582 endfunction(add_CMakeExternals_target)
584 # Pre-compute a regex to match known keywords.
585 set(_aep_keyword_regex "^(")
586 set(_aep_keyword_sep)
587 foreach(key IN ITEMS
588     BUILD_ARGS
589     BUILD_COMMAND
590     CMAKE_ARGS
591     CMAKE_COMMAND
592     CMAKE_GENERATOR
593     CONFIGURE_COMMAND
594     CONFIGURE_DIR
595     CVS_MODULE
596     CVS_REPOSITORY
597     CVS_TAG
598     DEPENDS
599     DIR
600     DOWNLOAD_COMMAND
601     INSTALL_ARGS
602     INSTALL_COMMAND
603     LIST_SEPARATOR
604     PATCH_COMMAND
605     SVN_REPOSITORY
606     SVN_TAG
607     TAR
608     TAR_URL
609     TGZ
610     TGZ_URL
611     UPDATE_COMMAND
612     )
613   set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
614   set(_aep_keyword_sep "|")
615 endforeach(key)
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.
627   #
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)
638   add_custom_command(
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
644     VERBATIM
645     )
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})
655   endforeach()
657   # Set up custom build steps based on the target properties.
658   # Each step depends on the previous one.
659   #
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.)
662   #
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)