KWSys Nightly Date Stamp
[cmake.git] / Modules / ExternalProject.cmake
blobaacf0b8ccfa786be3ab65b6ead7ebc0eabc07f38
1 # - Create custom targets to build projects in external trees
2 # The 'ExternalProject_Add' function creates a custom target to drive
3 # download, update/patch, configure, build, install and test steps of an
4 # external project:
5 #  ExternalProject_Add(<name>    # Name for custom target
6 #    [DEPENDS projects...]       # Targets on which the project depends
7 #    [PREFIX dir]                # Root dir for entire project
8 #    [LIST_SEPARATOR sep]        # Sep to be replaced by ; in cmd lines
9 #    [TMP_DIR dir]               # Directory to store temporary files
10 #    [STAMP_DIR dir]             # Directory to store step timestamps
11 #   #--Download step--------------
12 #    [DOWNLOAD_DIR dir]          # Directory to store downloaded files
13 #    [DOWNLOAD_COMMAND cmd...]   # Command to download source tree
14 #    [CVS_REPOSITORY cvsroot]    # CVSROOT of CVS repository
15 #    [CVS_MODULE mod]            # Module to checkout from CVS repo
16 #    [CVS_TAG tag]               # Tag to checkout from CVS repo
17 #    [SVN_REPOSITORY url]        # URL of Subversion repo
18 #    [SVN_REVISION rev]          # Revision to checkout from Subversion repo
19 #    [URL /.../src.tgz]          # Full path or URL of source
20 #   #--Update/Patch step----------
21 #    [UPDATE_COMMAND cmd...]     # Source work-tree update command
22 #    [PATCH_COMMAND cmd...]      # Command to patch downloaded source
23 #   #--Configure step-------------
24 #    [SOURCE_DIR dir]            # Source dir to be used for build
25 #    [CONFIGURE_COMMAND cmd...]  # Build tree configuration command
26 #    [CMAKE_COMMAND /.../cmake]  # Specify alternative cmake executable
27 #    [CMAKE_GENERATOR gen]       # Specify generator for native build
28 #    [CMAKE_ARGS args...]        # Arguments to CMake command line
29 #   #--Build step-----------------
30 #    [BINARY_DIR dir]            # Specify build dir location
31 #    [BUILD_COMMAND cmd...]      # Command to drive the native build
32 #    [BUILD_IN_SOURCE 1]         # Use source dir for build dir
33 #   #--Install step---------------
34 #    [INSTALL_DIR dir]           # Installation prefix
35 #    [INSTALL_COMMAND cmd...]    # Command to drive install after build
36 #   #--Test step---------------
37 #    [TEST_BEFORE_INSTALL 1]     # Add test step executed before install step
38 #    [TEST_AFTER_INSTALL 1]      # Add test step executed after install step
39 #    [TEST_COMMAND cmd...]       # Command to drive test
40 #    )
41 # The *_DIR options specify directories for the project, with default
42 # directories computed as follows.
43 # If the PREFIX option is given to ExternalProject_Add() or the EP_PREFIX
44 # directory property is set, then an external project is built and installed
45 # under the specified prefix:
46 #   TMP_DIR      = <prefix>/tmp
47 #   STAMP_DIR    = <prefix>/src/<name>-stamp
48 #   DOWNLOAD_DIR = <prefix>/src
49 #   SOURCE_DIR   = <prefix>/src/<name>
50 #   BINARY_DIR   = <prefix>/src/<name>-build
51 #   INSTALL_DIR  = <prefix>
52 # Otherwise, if the EP_BASE directory property is set then components
53 # of an external project are stored under the specified base:
54 #   TMP_DIR      = <base>/tmp/<name>
55 #   STAMP_DIR    = <base>/Stamp/<name>
56 #   DOWNLOAD_DIR = <base>/Download/<name>
57 #   SOURCE_DIR   = <base>/Source/<name>
58 #   BINARY_DIR   = <base>/Build/<name>
59 #   INSTALL_DIR  = <base>/Install/<name>
60 # If no PREFIX, EP_PREFIX, or EP_BASE is specified then the default
61 # is to set PREFIX to "<name>-prefix".
62 # Relative paths are interpreted with respect to the build directory
63 # corresponding to the source directory in which ExternalProject_Add is
64 # invoked.
66 # If SOURCE_DIR is explicitly set to an existing directory the project
67 # will be built from it.
68 # Otherwise a download step must be specified using one of the
69 # DOWNLOAD_COMMAND, CVS_*, SVN_*, or URL options.
70 # The URL option may refer locally to a directory or source tarball,
71 # or refer to a remote tarball (e.g. http://.../src.tgz).
73 # The 'ExternalProject_Add_Step' function adds a custom step to an external
74 # project:
75 #  ExternalProject_Add_Step(<name> <step> # Names of project and custom step
76 #    [COMMAND cmd...]        # Command line invoked by this step
77 #    [COMMENT "text..."]     # Text printed when step executes
78 #    [DEPENDEES steps...]    # Steps on which this step depends
79 #    [DEPENDERS steps...]    # Steps that depend on this step
80 #    [DEPENDS files...]      # Files on which this step depends
81 #    [ALWAYS 1]              # No stamp file, step always runs
82 #    [WORKING_DIRECTORY dir] # Working directory for command
83 #    )
84 # The command line, comment, and working directory of every standard
85 # and custom step is processed to replace tokens
86 # <SOURCE_DIR>,
87 # <BINARY_DIR>,
88 # <INSTALL_DIR>,
89 # and <TMP_DIR>
90 # with corresponding property values.
92 # The 'ExternalProject_Get_Property' function retrieves external project
93 # target properties:
94 #  ExternalProject_Get_Property(<name> [prop1 [prop2 [...]]])
95 # It stores property values in variables of the same name.
96 # Property names correspond to the keyword argument names of
97 # 'ExternalProject_Add'.
100 # Pre-compute a regex to match documented keywords for each command.
101 file(STRINGS "${CMAKE_CURRENT_LIST_FILE}" lines LIMIT_COUNT 100
102      REGEX "^#  (  \\[[A-Z_]+ [^]]*\\] +#.*$|[A-Za-z_]+\\()")
103 foreach(line IN LISTS lines)
104   if("${line}" MATCHES "^#  [A-Za-z_]+\\(")
105     if(_ep_func)
106       set(_ep_keywords_${_ep_func} "${_ep_keywords_${_ep_func}})$")
107     endif()
108     string(REGEX REPLACE "^#  ([A-Za-z_]+)\\(.*" "\\1" _ep_func "${line}")
109     #message("function [${_ep_func}]")
110     set(_ep_keywords_${_ep_func} "^(")
111     set(_ep_keyword_sep)
112   else()
113     string(REGEX REPLACE "^#    \\[([A-Z_]+) .*" "\\1" _ep_key "${line}")
114     #message("  keyword [${_ep_key}]")
115     set(_ep_keywords_${_ep_func}
116       "${_ep_keywords_${_ep_func}}${_ep_keyword_sep}${_ep_key}")
117     set(_ep_keyword_sep "|")
118   endif()
119 endforeach()
120 if(_ep_func)
121   set(_ep_keywords_${_ep_func} "${_ep_keywords_${_ep_func}})$")
122 endif()
125 function(_ep_parse_arguments f name ns args)
126   # Transfer the arguments to this function into target properties for the
127   # new custom target we just added so that we can set up all the build steps
128   # correctly based on target properties.
129   #
130   # We loop through ARGN and consider the namespace starting with an
131   # upper-case letter followed by at least two more upper-case letters
132   # or underscores to be keywords.
133   set(key)
135   foreach(arg IN LISTS args)
136     set(is_value 1)
138     if(arg MATCHES "^[A-Z][A-Z_][A-Z_]+$" AND
139         NOT ((arg STREQUAL "${key}") AND (key STREQUAL "COMMAND")) AND
140         NOT arg MATCHES "^(TRUE|FALSE)$")
141       if(_ep_keywords_${f} AND arg MATCHES "${_ep_keywords_${f}}")
142         set(is_value 0)
143       else()
144         if(NOT (key STREQUAL "COMMAND")
145           AND NOT (key STREQUAL "CVS_MODULE")
146           AND NOT (key STREQUAL "DEPENDS")
147           )
148           message(AUTHOR_WARNING "unknown ${f} keyword: ${arg}")
149         endif()
150       endif()
151     endif()
153     if(is_value)
154       if(key)
155         # Value
156         if(NOT arg STREQUAL "")
157           set_property(TARGET ${name} APPEND PROPERTY ${ns}${key} "${arg}")
158         else()
159           get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET)
160           if(have_key)
161             get_property(value TARGET ${name} PROPERTY ${ns}${key})
162             set_property(TARGET ${name} PROPERTY ${ns}${key} "${value};${arg}")
163           else()
164             set_property(TARGET ${name} PROPERTY ${ns}${key} "${arg}")
165           endif()
166         endif()
167       else()
168         # Missing Keyword
169         message(AUTHOR_WARNING "value '${arg}' with no previous keyword in ${f}")
170       endif()
171     else()
172       set(key "${arg}")
173     endif()
174   endforeach()
175 endfunction(_ep_parse_arguments)
178 define_property(DIRECTORY PROPERTY "EP_BASE" INHERITED
179   BRIEF_DOCS "Base directory for External Project storage."
180   FULL_DOCS
181   "See documentation of the ExternalProject_Add() function in the "
182   "ExternalProject module."
183   )
185 define_property(DIRECTORY PROPERTY "EP_PREFIX" INHERITED
186   BRIEF_DOCS "Top prefix for External Project storage."
187   FULL_DOCS
188   "See documentation of the ExternalProject_Add() function in the "
189   "ExternalProject module."
190   )
193 function(_ep_write_downloadfile_script script_filename remote local timeout)
194   if(NOT timeout)
195     set(timeout 30)
196   endif()
198   file(WRITE ${script_filename}
199 "message(STATUS \"downloading...
200      src='${remote}'
201      dst='${local}'\")
203 file(DOWNLOAD
204   \"${remote}\"
205   \"${local}\"
206   TIMEOUT ${timeout}
207   STATUS status
208   LOG log)
210 list(GET status 0 status_code)
211 list(GET status 1 status_string)
213 if(NOT status_code EQUAL 0)
214   message(FATAL_ERROR \"error: downloading '${remote}' failed
215   status_code: \${status_code}
216   status_string: \${status_string}
217   log: \${log}
219 endif()
221 message(STATUS \"downloading... done\")
225 endfunction(_ep_write_downloadfile_script)
228 function(_ep_write_extractfile_script script_filename filename tmp directory)
229   set(args "")
231   if(filename MATCHES ".tar$")
232     set(args xf)
233   endif()
235   if(filename MATCHES ".tgz$")
236     set(args xfz)
237   endif()
239   if(filename MATCHES ".tar.gz$")
240     set(args xfz)
241   endif()
243   if(args STREQUAL "")
244     message(SEND_ERROR "error: do not know how to extract '${filename}' -- known types are .tar, .tgz and .tar.gz")
245     return()
246   endif()
248   file(WRITE ${script_filename}
249 "# Make file names absolute:
251 get_filename_component(filename \"${filename}\" ABSOLUTE)
252 get_filename_component(tmp \"${tmp}\" ABSOLUTE)
253 get_filename_component(directory \"${directory}\" ABSOLUTE)
255 message(STATUS \"extracting...
256      src='\${filename}'
257      dst='\${directory}'\")
259 # Prepare a space for extracting:
261 set(i 1)
262 while(EXISTS \"\${tmp}/extract\${i}\")
263   math(EXPR i \"\${i} + 1\")
264 endwhile()
265 set(ut_dir \"\${tmp}/extract\${i}\")
266 file(MAKE_DIRECTORY \"\${ut_dir}\")
268 # Extract it:
270 message(STATUS \"extracting... [tar ${args}]\")
271 execute_process(COMMAND \${CMAKE_COMMAND} -E tar ${args} \${filename}
272   WORKING_DIRECTORY \${ut_dir}
273   RESULT_VARIABLE rv)
275 if(NOT rv EQUAL 0)
276   message(STATUS \"extracting... [error clean up]\")
277   file(REMOVE_RECURSE \"\${ut_dir}\")
278   message(FATAL_ERROR \"error: extract of '\${filename}' failed\")
279 endif()
281 # Analyze what came out of the tar file:
283 message(STATUS \"extracting... [analysis]\")
284 file(GLOB contents \"\${ut_dir}/*\")
285 list(LENGTH contents n)
286 if(NOT n EQUAL 1 OR NOT IS_DIRECTORY \"\${contents}\")
287   set(contents \"\${ut_dir}\")
288 endif()
290 # Copy \"the one\" directory to the final directory:
292 message(STATUS \"extracting... [copy]\")
293 file(COPY \"\${contents}/\" DESTINATION \${directory})
295 # Clean up:
297 message(STATUS \"extracting... [clean up]\")
298 file(REMOVE_RECURSE \"\${ut_dir}\")
300 message(STATUS \"extracting... done\")
304 endfunction(_ep_write_extractfile_script)
307 function(_ep_set_directories name)
308   get_property(prefix TARGET ${name} PROPERTY _EP_PREFIX)
309   if(NOT prefix)
310     get_property(prefix DIRECTORY PROPERTY EP_PREFIX)
311     if(NOT prefix)
312       get_property(base DIRECTORY PROPERTY EP_BASE)
313       if(NOT base)
314         set(prefix "${name}-prefix")
315       endif()
316     endif()
317   endif()
318   if(prefix)
319     set(tmp_default "${prefix}/tmp")
320     set(download_default "${prefix}/src")
321     set(source_default "${prefix}/src/${name}")
322     set(binary_default "${prefix}/src/${name}-build")
323     set(stamp_default "${prefix}/src/${name}-stamp")
324     set(install_default "${prefix}")
325   else() # assert(base)
326     set(tmp_default "${base}/tmp/${name}")
327     set(download_default "${base}/Download/${name}")
328     set(source_default "${base}/Source/${name}")
329     set(binary_default "${base}/Build/${name}")
330     set(stamp_default "${base}/Stamp/${name}")
331     set(install_default "${base}/Install/${name}")
332   endif()
333   get_property(build_in_source TARGET ${name} PROPERTY _EP_BUILD_IN_SOURCE)
334   if(build_in_source)
335     get_property(have_binary_dir TARGET ${name} PROPERTY _EP_BINARY_DIR SET)
336     if(have_binary_dir)
337       message(FATAL_ERROR
338         "External project ${name} has both BINARY_DIR and BUILD_IN_SOURCE!")
339     endif()
340   endif()
341   set(top "${CMAKE_CURRENT_BINARY_DIR}")
342   set(places stamp download source binary install tmp)
343   foreach(var ${places})
344     string(TOUPPER "${var}" VAR)
345     get_property(${var}_dir TARGET ${name} PROPERTY _EP_${VAR}_DIR)
346     if(NOT ${var}_dir)
347       set(${var}_dir "${${var}_default}")
348     endif()
349     if(NOT IS_ABSOLUTE "${${var}_dir}")
350       get_filename_component(${var}_dir "${top}/${${var}_dir}" ABSOLUTE)
351     endif()
352     set_property(TARGET ${name} PROPERTY _EP_${VAR}_DIR "${${var}_dir}")
353   endforeach()
354   if(build_in_source)
355     get_property(source_dir TARGET ${name} PROPERTY _EP_SOURCE_DIR)
356     set_property(TARGET ${name} PROPERTY _EP_BINARY_DIR "${source_dir}")
357   endif()
359   # Make the directories at CMake configure time *and* add a custom command
360   # to make them at build time. They need to exist at makefile generation
361   # time for Borland make and wmake so that CMake may generate makefiles
362   # with "cd C:\short\paths\with\no\spaces" commands in them.
363   #
364   # Additionally, the add_custom_command is still used in case somebody
365   # removes one of the necessary directories and tries to rebuild without
366   # re-running cmake.
367   foreach(var ${places})
368     string(TOUPPER "${var}" VAR)
369     get_property(dir TARGET ${name} PROPERTY _EP_${VAR}_DIR)
370     file(MAKE_DIRECTORY "${dir}")
371     if(NOT EXISTS "${dir}")
372       message(FATAL_ERROR "dir '${dir}' does not exist after file(MAKE_DIRECTORY)")
373     endif()
374   endforeach()
375 endfunction(_ep_set_directories)
378 function(ExternalProject_Get_Property name)
379   foreach(var ${ARGN})
380     string(TOUPPER "${var}" VAR)
381     get_property(${var} TARGET ${name} PROPERTY _EP_${VAR})
382     if(NOT ${var})
383       message(FATAL_ERROR "External project \"${name}\" has no ${var}")
384     endif()
385     set(${var} "${${var}}" PARENT_SCOPE)
386   endforeach()
387 endfunction(ExternalProject_Get_Property)
390 function(_ep_get_configure_command_id name cfg_cmd_id_var)
391   get_target_property(cmd ${name} _EP_CONFIGURE_COMMAND)
393   if(cmd STREQUAL "")
394     # Explicit empty string means no configure step for this project
395     set(${cfg_cmd_id_var} "none" PARENT_SCOPE)
396   else()
397     if(NOT cmd)
398       # Default is "use cmake":
399       set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
400     else()
401       # Otherwise we have to analyze the value:
402       if(cmd MATCHES "^[^;]*/configure")
403         set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
404       elseif(cmd MATCHES "^[^;]*/cmake" AND NOT cmd MATCHES ";-[PE];")
405         set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
406       elseif(cmd MATCHES "config")
407         set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
408       else()
409         set(${cfg_cmd_id_var} "unknown:${cmd}" PARENT_SCOPE)
410       endif()
411     endif()
412   endif()
413 endfunction(_ep_get_configure_command_id)
416 function(_ep_get_build_command name step cmd_var)
417   set(cmd "${${cmd_var}}")
418   if(NOT cmd)
419     set(args)
420     _ep_get_configure_command_id(${name} cfg_cmd_id)
421     if(cfg_cmd_id STREQUAL "cmake")
422       # CMake project.  Select build command based on generator.
423       get_target_property(cmake_generator ${name} _EP_CMAKE_GENERATOR)
424       if("${cmake_generator}" MATCHES "Make" AND
425           "${cmake_generator}" STREQUAL "${CMAKE_GENERATOR}")
426         # The project uses the same Makefile generator.  Use recursive make.
427         set(cmd "$(MAKE)")
428         if(step STREQUAL "INSTALL")
429           set(args install)
430         endif()
431         if(step STREQUAL "TEST")
432           set(args test)
433         endif()
434       else()
435         # Drive the project with "cmake --build".
436         get_target_property(cmake_command ${name} _EP_CMAKE_COMMAND)
437         if(cmake_command)
438           set(cmd "${cmake_command}")
439         else()
440           set(cmd "${CMAKE_COMMAND}")
441         endif()
442         set(args --build ${binary_dir} --config ${CMAKE_CFG_INTDIR})
443         if(step STREQUAL "INSTALL")
444           list(APPEND args --target install)
445         endif()
446         # But for "TEST" drive the project with corresponding "ctest".
447         if(step STREQUAL "TEST")
448           string(REGEX REPLACE "^(.*/)cmake([^/]*)$" "\\1ctest\\2" cmd "${cmd}")
449           set(args "")
450         endif()
451       endif()
452     else() # if(cfg_cmd_id STREQUAL "configure")
453       # Non-CMake project.  Guess "make" and "make install" and "make test".
454       set(cmd "make")
455       if(step STREQUAL "INSTALL")
456         set(args install)
457       endif()
458       if(step STREQUAL "TEST")
459         set(args test)
460       endif()
461     endif()
463     # Use user-specified arguments instead of default arguments, if any.
464     get_property(have_args TARGET ${name} PROPERTY _EP_${step}_ARGS SET)
465     if(have_args)
466       get_target_property(args ${name} _EP_${step}_ARGS)
467     endif()
469     list(APPEND cmd ${args})
470   endif()
472   set(${cmd_var} "${cmd}" PARENT_SCOPE)
473 endfunction(_ep_get_build_command)
476 function(ExternalProject_Add_Step name step)
477   set(cmf_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles)
478   ExternalProject_Get_Property(${name} stamp_dir)
480   add_custom_command(APPEND
481     OUTPUT ${cmf_dir}/${CMAKE_CFG_INTDIR}/${name}-complete
482     DEPENDS ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${step}
483     )
484   _ep_parse_arguments(ExternalProject_Add_Step
485                        ${name} _EP_${step}_ "${ARGN}")
487   # Steps depending on this step.
488   get_property(dependers TARGET ${name} PROPERTY _EP_${step}_DEPENDERS)
489   foreach(depender IN LISTS dependers)
490     add_custom_command(APPEND
491       OUTPUT ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${depender}
492       DEPENDS ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${step}
493       )
494   endforeach()
496   # Dependencies on files.
497   get_property(depends TARGET ${name} PROPERTY _EP_${step}_DEPENDS)
499   # Dependencies on steps.
500   get_property(dependees TARGET ${name} PROPERTY _EP_${step}_DEPENDEES)
501   foreach(dependee IN LISTS dependees)
502     list(APPEND depends ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${dependee})
503   endforeach()
505   # The command to run.
506   get_property(command TARGET ${name} PROPERTY _EP_${step}_COMMAND)
507   if(command)
508     set(comment "Performing ${step} step for '${name}'")
509   else()
510     set(comment "No ${step} step for '${name}'")
511   endif()
512   get_property(work_dir TARGET ${name} PROPERTY _EP_${step}_WORKING_DIRECTORY)
514   # Replace list separators.
515   get_property(sep TARGET ${name} PROPERTY _EP_LIST_SEPARATOR)
516   if(sep AND command)
517     string(REPLACE "${sep}" "\\;" command "${command}")
518   endif()
520   # Replace location tags.
521   foreach(var comment command work_dir)
522     if(${var})
523       foreach(dir SOURCE_DIR BINARY_DIR INSTALL_DIR TMP_DIR)
524         get_property(val TARGET ${name} PROPERTY _EP_${dir})
525         string(REPLACE "<${dir}>" "${val}" ${var} "${${var}}")
526       endforeach()
527     endif()
528   endforeach()
530   # Custom comment?
531   get_property(comment_set TARGET ${name} PROPERTY _EP_${step}_COMMENT SET)
532   if(comment_set)
533     get_property(comment TARGET ${name} PROPERTY _EP_${step}_COMMENT)
534   endif()
536   # Run every time?
537   get_property(always TARGET ${name} PROPERTY _EP_${step}_ALWAYS)
538   if(always)
539     set_property(SOURCE ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${step} PROPERTY SYMBOLIC 1)
540     set(touch)
541   else()
542     set(touch ${CMAKE_COMMAND} -E touch ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${step})
543   endif()
545   add_custom_command(
546     OUTPUT ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-${step}
547     COMMENT ${comment}
548     COMMAND ${command}
549     COMMAND ${touch}
550     DEPENDS ${depends}
551     WORKING_DIRECTORY ${work_dir}
552     VERBATIM
553     )
554 endfunction(ExternalProject_Add_Step)
557 function(_ep_add_mkdir_command name)
558   ExternalProject_Get_Property(${name}
559     source_dir binary_dir install_dir stamp_dir download_dir tmp_dir)
560   ExternalProject_Add_Step(${name} mkdir
561     COMMENT "Creating directories for '${name}'"
562     COMMAND ${CMAKE_COMMAND} -E make_directory ${source_dir}
563     COMMAND ${CMAKE_COMMAND} -E make_directory ${binary_dir}
564     COMMAND ${CMAKE_COMMAND} -E make_directory ${install_dir}
565     COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir}
566     COMMAND ${CMAKE_COMMAND} -E make_directory ${stamp_dir}/${CMAKE_CFG_INTDIR}
567     COMMAND ${CMAKE_COMMAND} -E make_directory ${download_dir}
568     )
569 endfunction(_ep_add_mkdir_command)
572 function(_ep_add_download_command name)
573   ExternalProject_Get_Property(${name} source_dir stamp_dir download_dir tmp_dir)
575   get_property(cmd_set TARGET ${name} PROPERTY _EP_DOWNLOAD_COMMAND SET)
576   get_property(cmd TARGET ${name} PROPERTY _EP_DOWNLOAD_COMMAND)
577   get_property(cvs_repository TARGET ${name} PROPERTY _EP_CVS_REPOSITORY)
578   get_property(svn_repository TARGET ${name} PROPERTY _EP_SVN_REPOSITORY)
579   get_property(url TARGET ${name} PROPERTY _EP_URL)
581   # TODO: Perhaps file:// should be copied to download dir before extraction.
582   string(REGEX REPLACE "^file://" "" url "${url}")
584   set(depends)
585   set(comment)
586   set(work_dir)
588   if(cmd_set)
589     set(work_dir ${download_dir})
590   elseif(cvs_repository)
591     find_package(CVS)
592     if(NOT CVS_EXECUTABLE)
593       message(FATAL_ERROR "error: could not find cvs for checkout of ${name}")
594     endif()
596     get_target_property(cvs_module ${name} _EP_CVS_MODULE)
597     if(NOT cvs_module)
598       message(FATAL_ERROR "error: no CVS_MODULE")
599     endif()
601     get_property(cvs_tag TARGET ${name} PROPERTY _EP_CVS_TAG)
603     set(repository ${cvs_repository})
604     set(module ${cvs_module})
605     set(tag ${cvs_tag})
606     configure_file(
607       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
608       "${stamp_dir}/${name}-cvsinfo.txt"
609       @ONLY
610       )
612     get_filename_component(src_name "${source_dir}" NAME)
613     get_filename_component(work_dir "${source_dir}" PATH)
614     set(comment "Performing download step (CVS checkout) for '${name}'")
615     set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q co ${cvs_tag} -d ${src_name} ${cvs_module})
616     list(APPEND depends ${stamp_dir}/${name}-cvsinfo.txt)
617   elseif(svn_repository)
618     find_package(Subversion)
619     if(NOT Subversion_SVN_EXECUTABLE)
620       message(FATAL_ERROR "error: could not find svn for checkout of ${name}")
621     endif()
623     get_property(svn_revision TARGET ${name} PROPERTY _EP_SVN_REVISION)
625     set(repository ${svn_repository})
626     set(module)
627     set(tag ${svn_revision})
628     configure_file(
629       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
630       "${stamp_dir}/${name}-svninfo.txt"
631       @ONLY
632       )
634     get_filename_component(src_name "${source_dir}" NAME)
635     get_filename_component(work_dir "${source_dir}" PATH)
636     set(comment "Performing download step (SVN checkout) for '${name}'")
637     set(cmd ${Subversion_SVN_EXECUTABLE} co ${svn_repository} ${svn_revision} ${src_name})
638     list(APPEND depends ${stamp_dir}/${name}-svninfo.txt)
639   elseif(url)
640     get_filename_component(work_dir "${source_dir}" PATH)
641     set(repository "external project URL")
642     set(module "${url}")
643     set(tag "")
644     configure_file(
645       "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
646       "${stamp_dir}/${name}-urlinfo.txt"
647       @ONLY
648       )
649     list(APPEND depends ${stamp_dir}/${name}-urlinfo.txt)
650     if(IS_DIRECTORY "${url}")
651       get_filename_component(abs_dir "${url}" ABSOLUTE)
652       set(comment "Performing download step (DIR copy) for '${name}'")
653       set(cmd   ${CMAKE_COMMAND} -E remove_directory ${source_dir}
654         COMMAND ${CMAKE_COMMAND} -E copy_directory ${abs_dir} ${source_dir})
655     else()
656       if("${url}" MATCHES "^[a-z]+://")
657         # TODO: Should download and extraction be different steps?
658         string(REGEX MATCH "[^/]*$" fname "${url}")
659         if(NOT "${fname}" MATCHES "\\.(tar|tgz|tar\\.gz)$")
660           message(FATAL_ERROR "Could not extract tarball filename from url:\n  ${url}")
661         endif()
662         set(file ${download_dir}/${fname})
663         _ep_write_downloadfile_script("${stamp_dir}/download-${name}.cmake" "${url}" "${file}" "")
664         set(cmd ${CMAKE_COMMAND} -P ${stamp_dir}/download-${name}.cmake
665           COMMAND)
666         set(comment "Performing download step (download and extract) for '${name}'")
667       else()
668         set(file "${url}")
669         set(comment "Performing download step (extract) for '${name}'")
670       endif()
671       # TODO: Support other archive formats.
672       _ep_write_extractfile_script("${stamp_dir}/extract-${name}.cmake" "${file}" "${tmp_dir}" "${source_dir}")
673       list(APPEND cmd ${CMAKE_COMMAND} -P ${stamp_dir}/extract-${name}.cmake)
674     endif()
675   else()
676     message(SEND_ERROR "error: no download info for '${name}' -- please specify existing SOURCE_DIR or one of URL, CVS_REPOSITORY and CVS_MODULE, SVN_REPOSITORY or DOWNLOAD_COMMAND")
677   endif()
679   ExternalProject_Add_Step(${name} download
680     COMMENT ${comment}
681     COMMAND ${cmd}
682     WORKING_DIRECTORY ${work_dir}
683     DEPENDS ${depends}
684     DEPENDEES mkdir
685     )
686 endfunction(_ep_add_download_command)
689 function(_ep_add_update_command name)
690   ExternalProject_Get_Property(${name} source_dir)
692   get_property(cmd_set TARGET ${name} PROPERTY _EP_UPDATE_COMMAND SET)
693   get_property(cmd TARGET ${name} PROPERTY _EP_UPDATE_COMMAND)
694   get_property(cvs_repository TARGET ${name} PROPERTY _EP_CVS_REPOSITORY)
695   get_property(svn_repository TARGET ${name} PROPERTY _EP_SVN_REPOSITORY)
697   set(work_dir)
698   set(comment)
699   set(always)
701   if(cmd_set)
702     set(work_dir ${source_dir})
703   elseif(cvs_repository)
704     if(NOT CVS_EXECUTABLE)
705       message(FATAL_ERROR "error: could not find cvs for update of ${name}")
706     endif()
707     set(work_dir ${source_dir})
708     set(comment "Performing update step (CVS update) for '${name}'")
709     get_property(cvs_tag TARGET ${name} PROPERTY _EP_CVS_TAG)
710     set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q up -dP ${cvs_tag})
711     set(always 1)
712   elseif(svn_repository)
713     if(NOT Subversion_SVN_EXECUTABLE)
714       message(FATAL_ERROR "error: could not find svn for update of ${name}")
715     endif()
716     set(work_dir ${source_dir})
717     set(comment "Performing update step (SVN update) for '${name}'")
718     get_property(svn_revision TARGET ${name} PROPERTY _EP_SVN_REVISION)
719     set(cmd ${Subversion_SVN_EXECUTABLE} up ${svn_revision})
720     set(always 1)
721   endif()
723   ExternalProject_Add_Step(${name} update
724     COMMENT ${comment}
725     COMMAND ${cmd}
726     ALWAYS ${always}
727     WORKING_DIRECTORY ${work_dir}
728     DEPENDEES download
729     )
730 endfunction(_ep_add_update_command)
733 function(_ep_add_patch_command name)
734   ExternalProject_Get_Property(${name} source_dir)
736   get_property(cmd_set TARGET ${name} PROPERTY _EP_PATCH_COMMAND SET)
737   get_property(cmd TARGET ${name} PROPERTY _EP_PATCH_COMMAND)
739   set(work_dir)
741   if(cmd_set)
742     set(work_dir ${source_dir})
743   endif()
745   ExternalProject_Add_Step(${name} patch
746     COMMAND ${cmd}
747     WORKING_DIRECTORY ${work_dir}
748     DEPENDEES download
749     )
750 endfunction(_ep_add_patch_command)
753 # TODO: Make sure external projects use the proper compiler
754 function(_ep_add_configure_command name)
755   ExternalProject_Get_Property(${name} source_dir binary_dir)
757   # Depend on other external projects (file-level).
758   set(file_deps)
759   get_property(deps TARGET ${name} PROPERTY _EP_DEPENDS)
760   foreach(dep IN LISTS deps)
761     get_property(dep_stamp_dir TARGET ${dep} PROPERTY _EP_STAMP_DIR)
762     list(APPEND file_deps ${dep_stamp_dir}/${CMAKE_CFG_INTDIR}/${dep}-done)
763   endforeach()
765   get_property(cmd_set TARGET ${name} PROPERTY _EP_CONFIGURE_COMMAND SET)
766   if(cmd_set)
767     get_property(cmd TARGET ${name} PROPERTY _EP_CONFIGURE_COMMAND)
768   else()
769     get_target_property(cmake_command ${name} _EP_CMAKE_COMMAND)
770     if(cmake_command)
771       set(cmd "${cmake_command}")
772     else()
773       set(cmd "${CMAKE_COMMAND}")
774     endif()
776     get_property(cmake_args TARGET ${name} PROPERTY _EP_CMAKE_ARGS)
777     list(APPEND cmd ${cmake_args})
779     get_target_property(cmake_generator ${name} _EP_CMAKE_GENERATOR)
780     if(cmake_generator)
781       list(APPEND cmd "-G${cmake_generator}" "${source_dir}")
782     else()
783       list(APPEND cmd "-G${CMAKE_GENERATOR}" "${source_dir}")
784     endif()
785   endif()
787   ExternalProject_Add_Step(${name} configure
788     COMMAND ${cmd}
789     WORKING_DIRECTORY ${binary_dir}
790     DEPENDEES update patch
791     DEPENDS ${file_deps}
792     )
793 endfunction(_ep_add_configure_command)
796 function(_ep_add_build_command name)
797   ExternalProject_Get_Property(${name} binary_dir)
799   get_property(cmd_set TARGET ${name} PROPERTY _EP_BUILD_COMMAND SET)
800   if(cmd_set)
801     get_property(cmd TARGET ${name} PROPERTY _EP_BUILD_COMMAND)
802   else()
803     _ep_get_build_command(${name} BUILD cmd)
804   endif()
806   ExternalProject_Add_Step(${name} build
807     COMMAND ${cmd}
808     WORKING_DIRECTORY ${binary_dir}
809     DEPENDEES configure
810     )
811 endfunction(_ep_add_build_command)
814 function(_ep_add_install_command name)
815   ExternalProject_Get_Property(${name} binary_dir)
817   get_property(cmd_set TARGET ${name} PROPERTY _EP_INSTALL_COMMAND SET)
818   if(cmd_set)
819     get_property(cmd TARGET ${name} PROPERTY _EP_INSTALL_COMMAND)
820   else()
821     _ep_get_build_command(${name} INSTALL cmd)
822   endif()
824   ExternalProject_Add_Step(${name} install
825     COMMAND ${cmd}
826     WORKING_DIRECTORY ${binary_dir}
827     DEPENDEES build
828     )
829 endfunction(_ep_add_install_command)
832 function(_ep_add_test_command name)
833   ExternalProject_Get_Property(${name} binary_dir)
835   get_property(before TARGET ${name} PROPERTY _EP_TEST_BEFORE_INSTALL)
836   get_property(after TARGET ${name} PROPERTY _EP_TEST_AFTER_INSTALL)
837   get_property(cmd_set TARGET ${name} PROPERTY _EP_TEST_COMMAND SET)
839   # Only actually add the test step if one of the test related properties is
840   # explicitly set. (i.e. the test step is omitted unless requested...)
841   #
842   if(cmd_set OR before OR after)
843     if(cmd_set)
844       get_property(cmd TARGET ${name} PROPERTY _EP_TEST_COMMAND)
845     else()
846       _ep_get_build_command(${name} TEST cmd)
847     endif()
849     if(before)
850       set(dep_args DEPENDEES build DEPENDERS install)
851     else()
852       set(dep_args DEPENDEES install)
853     endif()
855     ExternalProject_Add_Step(${name} test
856       COMMAND ${cmd}
857       WORKING_DIRECTORY ${binary_dir}
858       ${dep_args}
859       )
860   endif()
861 endfunction(_ep_add_test_command)
864 function(ExternalProject_Add name)
865   # Add a custom target for the external project.
866   set(cmf_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles)
867   add_custom_target(${name} ALL DEPENDS ${cmf_dir}/${CMAKE_CFG_INTDIR}/${name}-complete)
868   set_property(TARGET ${name} PROPERTY _EP_IS_EXTERNAL_PROJECT 1)
869   _ep_parse_arguments(ExternalProject_Add ${name} _EP_ "${ARGN}")
870   _ep_set_directories(${name})
871   ExternalProject_Get_Property(${name} stamp_dir)
873   # The 'complete' step depends on all other steps and creates a
874   # 'done' mark.  A dependent external project's 'configure' step
875   # depends on the 'done' mark so that it rebuilds when this project
876   # rebuilds.  It is important that 'done' is not the output of any
877   # custom command so that CMake does not propagate build rules to
878   # other external project targets.
879   add_custom_command(
880     OUTPUT ${cmf_dir}/${CMAKE_CFG_INTDIR}/${name}-complete
881     COMMENT "Completed '${name}'"
882     COMMAND ${CMAKE_COMMAND} -E make_directory ${cmf_dir}/${CMAKE_CFG_INTDIR}
883     COMMAND ${CMAKE_COMMAND} -E touch ${cmf_dir}/${CMAKE_CFG_INTDIR}/${name}-complete
884     COMMAND ${CMAKE_COMMAND} -E touch ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-done
885     DEPENDS ${stamp_dir}/${CMAKE_CFG_INTDIR}/${name}-install
886     VERBATIM
887     )
890   # Depend on other external projects (target-level).
891   get_property(deps TARGET ${name} PROPERTY _EP_DEPENDS)
892   foreach(arg IN LISTS deps)
893     add_dependencies(${name} ${arg})
894   endforeach()
896   # Set up custom build steps based on the target properties.
897   # Each step depends on the previous one.
898   #
899   # The target depends on the output of the final step.
900   # (Already set up above in the DEPENDS of the add_custom_target command.)
901   #
902   _ep_add_mkdir_command(${name})
903   _ep_add_download_command(${name})
904   _ep_add_update_command(${name})
905   _ep_add_patch_command(${name})
906   _ep_add_configure_command(${name})
907   _ep_add_build_command(${name})
908   _ep_add_install_command(${name})
910   # Test is special in that it might depend on build, or it might depend
911   # on install.
912   #
913   _ep_add_test_command(${name})
914 endfunction(ExternalProject_Add)