Resync
[CMakeLuaTailorHgBridge.git] / CMakeLua / Modules / AddExternalProject.cmake
blob2474de816e2bbc16ea3f89669bdd5737b5a311bc
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   # Custom comment?
215   get_property(comment_set TARGET ${name} PROPERTY AEP_${step}_COMMENT SET)
216   if(comment_set)
217     get_property(comment TARGET ${name} PROPERTY AEP_${step}_COMMENT)
218   endif()
220   # Run every time?
221   get_property(symbolic TARGET ${name} PROPERTY AEP_${step}_SYMBOLIC)
222   if(symbolic)
223     set_property(SOURCE ${sentinels_dir}/${name}-${step} PROPERTY SYMBOLIC 1)
224     set(touch)
225   else()
226     set(touch ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-${step})
227   endif()
229   add_custom_command(
230     OUTPUT ${sentinels_dir}/${name}-${step}
231     COMMENT ${comment}
232     COMMAND ${command}
233     COMMAND ${touch}
234     DEPENDS ${depends}
235     WORKING_DIRECTORY ${work_dir}
236     VERBATIM
237     )
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)
255   set(comment)
256   set(work_dir)
258   if(cmd_set)
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}")
263     endif()
265     get_target_property(cvs_module ${name} AEP_CVS_MODULE)
266     if(NOT cvs_module)
267       message(FATAL_ERROR "error: no CVS_MODULE")
268     endif()
270     get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
272     set(repository ${cvs_repository})
273     set(module ${cvs_module})
274     set(tag ${cvs_tag})
275     configure_file(
276       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
277       "${sentinels_dir}/${name}-cvsinfo.txt"
278       @ONLY
279       )
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}")
289     endif()
291     get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
293     set(repository ${svn_repository})
294     set(module)
295     set(tag ${svn_tag})
296     configure_file(
297       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
298       "${sentinels_dir}/${name}-svninfo.txt"
299       @ONLY
300       )
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)
307   elseif(dir)
308     get_filename_component(abs_dir "${dir}" ABSOLUTE)
310     set(repository "add_external_project DIR")
311     set(module "${abs_dir}")
312     set(tag "")
314     configure_file(
315       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
316       "${sentinels_dir}/${name}-dirinfo.txt"
317       @ONLY
318       )
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)
326   elseif(tar)
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})
332   elseif(tgz)
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})
338   elseif(tgz_url)
339     set(repository "add_external_project TGZ_URL")
340     set(module "${tgz_url}")
341     set(tag "")
343     configure_file(
344       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
345       "${sentinels_dir}/${name}-urlinfo.txt"
346       @ONLY
347     )
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)
355   elseif(tar_url)
356     set(repository "add_external_project TAR_URL")
357     set(module "${tar_url}")
358     set(tag "")
360     configure_file(
361       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
362       "${sentinels_dir}/${name}-urlinfo.txt"
363       @ONLY
364     )
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)
372   else()
373     message(SEND_ERROR "error: no download info for '${name}'")
374   endif()
376   add_external_project_step(${name} download
377     COMMENT ${comment}
378     COMMAND ${cmd}
379     WORKING_DIRECTORY ${source_dir}
380     DEPENDS ${depends}
381     )
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)
393   set(work_dir)
394   set(comment)
395   set(symbolic)
396   if(cmd)
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}")
401     endif()
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})
407     set(symbolic 1)
408   elseif(svn_repository)
409     if(NOT Subversion_SVN_EXECUTABLE)
410       message(FATAL_ERROR "error: could not find svn for update of ${name}")
411     endif()
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})
417     set(symbolic 1)
418   endif()
420   add_external_project_step(${name} update
421     COMMENT ${comment}
422     COMMAND ${cmd}
423     SYMBOLIC ${symbolic}
424     WORKING_DIRECTORY ${work_dir}
425     DEPENDEES download
426     )
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)
434   set(work_dir)
435   get_property(cmd TARGET ${name} PROPERTY AEP_PATCH_COMMAND)
436   if(cmd)
437     set(work_dir ${source_dir}/${name})
438   endif()
440   add_external_project_step(${name} patch
441     COMMAND ${cmd}
442     WORKING_DIRECTORY ${work_dir}
443     DEPENDEES download
444     )
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).
455   set(file_deps)
456   get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
457   foreach(arg IN LISTS deps)
458     list(APPEND file_deps ${sentinels_dir}/${arg}-done)
459   endforeach()
460   #message(STATUS "info: name='${name}' file_deps='${file_deps}'")
462   # Create the working_dir for configure, build and install steps:
463   #
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
469     DEPENDS ${file_deps}
470     )
472   get_property(cmd_set TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND SET)
473   if(cmd_set)
474     get_property(cmd TARGET ${name} PROPERTY AEP_CONFIGURE_COMMAND)
475   else()
476     get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
477     if(cmake_command)
478       set(cmd "${cmake_command}")
479     else()
480       set(cmd "${CMAKE_COMMAND}")
481     endif()
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)
487     if(cmake_generator)
488       list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}")
489     endif()
490   endif()
492   add_external_project_step(${name} configure
493     COMMAND ${cmd}
494     WORKING_DIRECTORY ${working_dir}
495     DEPENDEES working_dir
496     )
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)
506   if(cmd_set)
507     get_property(cmd TARGET ${name} PROPERTY AEP_BUILD_COMMAND)
508   else()
509     _aep_get_build_command(${name} BUILD cmd)
510   endif()
511   add_external_project_step(${name} build
512     COMMAND ${cmd}
513     WORKING_DIRECTORY ${working_dir}
514     DEPENDEES configure
515     )
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)
525   if(cmd_set)
526     get_property(cmd TARGET ${name} PROPERTY AEP_INSTALL_COMMAND)
527   else()
528     _aep_get_build_command(${name} INSTALL cmd)
529   endif()
530   add_external_project_step(${name} install
531     COMMAND ${cmd}
532     WORKING_DIRECTORY ${working_dir}
533     DEPENDEES build
534     )
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.
547     #
548     # Additionally, the add_custom_command is still used in case somebody
549     # removes one of the necessary directories and tries to rebuild without
550     # re-running cmake.
551     #
552     mkdir("${build_dir}")
553     mkdir("${downloads_dir}")
554     mkdir("${install_dir}")
555     mkdir("${sentinels_dir}")
556     mkdir("${source_dir}")
557     mkdir("${tmp_dir}")
559     add_custom_command(
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"
569       VERBATIM
570     )
572     add_custom_target(CMakeExternals ALL
573       DEPENDS ${sentinels_dir}/CMakeExternals-directories
574     )
575   endif()
576 endfunction(add_CMakeExternals_target)
578 # Pre-compute a regex to match known keywords.
579 set(_aep_keyword_regex "^(")
580 set(_aep_keyword_sep)
581 foreach(key IN ITEMS
582     BUILD_ARGS
583     BUILD_COMMAND
584     CMAKE_ARGS
585     CMAKE_COMMAND
586     CMAKE_GENERATOR
587     CONFIGURE_COMMAND
588     CONFIGURE_DIR
589     CVS_MODULE
590     CVS_REPOSITORY
591     CVS_TAG
592     DEPENDS
593     DIR
594     DOWNLOAD_COMMAND
595     INSTALL_ARGS
596     INSTALL_COMMAND
597     PATCH_COMMAND
598     SVN_REPOSITORY
599     SVN_TAG
600     TAR
601     TAR_URL
602     TGZ
603     TGZ_URL
604     UPDATE_COMMAND
605     )
606   set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
607   set(_aep_keyword_sep "|")
608 endforeach(key)
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.
620   #
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)
631   add_custom_command(
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
637     VERBATIM
638     )
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})
648   endforeach()
650   # Set up custom build steps based on the target properties.
651   # Each step depends on the previous one.
652   #
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.)
655   #
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)