@@ -174,6 +174,23 @@ calling any of the functions provided by this module.
174174 some platforms. The ``ExternalData_NO_SYMLINKS`` variable may be set
175175 to disable use of symbolic links and enable use of copies instead.
176176
177+ .. variable:: ExternalData_LINK_MODE
178+
179+ The ``ExternalData_LINK_MODE`` variable selects how real data files are
180+ exposed under ``ExternalData_BINARY_ROOT``. Supported values are
181+ ``auto``, ``hardlink``, ``symlink``, and ``copy``.
182+
183+ When set to ``auto``, the module chooses the lightest supported mode for the
184+ host. The default order is ``hardlink -> symlink -> copy`` on Windows and
185+ ``symlink -> hardlink -> copy`` elsewhere.
186+
187+ .. variable:: ExternalData_STATE_ROOT
188+
189+ The ``ExternalData_STATE_ROOT`` variable may be set to place module-managed
190+ metadata outside ``ExternalData_BINARY_ROOT``. When set, ``ExternalData``
191+ stores its hash records and build driver stamps under this directory while
192+ still materializing real data files under ``ExternalData_BINARY_ROOT``.
193+
177194.. variable:: ExternalData_OBJECT_STORES
178195
179196 The ``ExternalData_OBJECT_STORES`` variable may be set to a list of local
@@ -516,24 +533,26 @@ function(ExternalData_add_target target)
516533 string (REPLACE "|" ";" tuple "${entry} " )
517534 list (GET tuple 0 file)
518535 list (GET tuple 1 name)
536+ _ExternalData_compute_build_stamp_file ("${file} " build_stamp_file )
519537 if (NOT DEFINED "_ExternalData_FILE_${file} " )
520538 set ("_ExternalData_FILE_${file} " 1)
521539 get_property (added DIRECTORY PROPERTY "_ExternalData_FILE_${file} " )
522540 if (NOT added)
523541 set_property (DIRECTORY PROPERTY "_ExternalData_FILE_${file} " 1 )
524542 add_custom_command (
525543 COMMENT "Generating ${file} "
526- OUTPUT "${file} "
544+ OUTPUT "${build_stamp_file} " " ${ file} "
527545 COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
528546 -Dfile=${file} -Dname=${name}
529547 -DExternalData_ACTION=local
530548 -DExternalData_SHOW_PROGRESS=${_ExternalData_add_target_SHOW_PROGRESS}
531549 -DExternalData_CONFIG=${config}
532550 -P ${CMAKE_CURRENT_FUNCTION_LIST_FILE}
551+ COMMAND ${CMAKE_COMMAND} -E touch "${build_stamp_file} "
533552 MAIN_DEPENDENCY "${name} "
534553 )
535554 endif ()
536- list (APPEND files "${file } " )
555+ list (APPEND files "${build_stamp_file } " )
537556 endif ()
538557 endforeach ()
539558
@@ -546,7 +565,8 @@ function(ExternalData_add_target target)
546565 list (GET tuple 2 exts)
547566 string (REPLACE "+" ";" exts_list "${exts} " )
548567 list (GET exts_list 0 first_ext)
549- set (stamp "-hash-stamp" )
568+ _ExternalData_compute_stamp_file ("${file} " stamp_file )
569+ _ExternalData_compute_build_stamp_file ("${file} " build_stamp_file )
550570 if (NOT DEFINED "_ExternalData_FILE_${file} " )
551571 set ("_ExternalData_FILE_${file} " 1)
552572 get_property (added DIRECTORY PROPERTY "_ExternalData_FILE_${file} " )
@@ -555,23 +575,25 @@ function(ExternalData_add_target target)
555575 add_custom_command (
556576 # Users care about the data file, so hide the hash/timestamp file.
557577 COMMENT "Generating ${file} "
558- # The hash/timestamp file is the output from the build perspective.
559- # List the real file as a second output in case it is a broken link.
578+ # Use a dedicated build stamp as the primary output so IDE
579+ # generators keep an explicit build step for each materialized file.
580+ # List the hash record and real file as secondary outputs.
560581 # The files must be listed in this order so CMake can hide from the
561582 # make tool that a symlink target may not be newer than the input.
562- OUTPUT "${file}${stamp } " "${file} "
583+ OUTPUT "${build_stamp_file} " " ${stamp_file } " "${file} "
563584 # Run the data fetch/update script.
564585 COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
565586 -Dfile=${file} -Dname=${name} -Dexts=${exts}
566587 -DExternalData_ACTION=fetch
567588 -DExternalData_SHOW_PROGRESS=${_ExternalData_add_target_SHOW_PROGRESS}
568589 -DExternalData_CONFIG=${config}
569590 -P ${CMAKE_CURRENT_FUNCTION_LIST_FILE}
591+ COMMAND ${CMAKE_COMMAND} -E touch "${build_stamp_file} "
570592 # Update whenever the object hash changes.
571593 MAIN_DEPENDENCY "${name}${first_ext} "
572594 )
573595 endif ()
574- list (APPEND files "${file}${stamp } " )
596+ list (APPEND files "${build_stamp_file } " )
575597 endif ()
576598 endforeach ()
577599
@@ -641,12 +663,56 @@ function(_ExternalData_exact_regex regex_var string)
641663endfunction ()
642664
643665function (_ExternalData_atomic_write file content )
666+ get_filename_component (dir "${file} " DIRECTORY )
667+ file (MAKE_DIRECTORY "${dir} " )
644668 _ExternalData_random (random )
645669 set (tmp "${file} .tmp${random} " )
646670 file (WRITE "${tmp} " "${content} " )
647671 file (RENAME "${tmp} " "${file} " )
648672endfunction ()
649673
674+ function (_ExternalData_compute_stamp_file file var_stamp )
675+ if (DEFINED ExternalData_STATE_ROOT AND NOT "${ExternalData_STATE_ROOT} " STREQUAL "" )
676+ if (NOT ExternalData_BINARY_ROOT)
677+ set (top_bin "${CMAKE_BINARY_DIR} " )
678+ else ()
679+ set (top_bin "${ExternalData_BINARY_ROOT} " )
680+ endif ()
681+ file (RELATIVE_PATH relfile "${top_bin} " "${file} " )
682+ if (IS_ABSOLUTE "${relfile} " OR "${relfile} " MATCHES "^\\ .\\ ./" )
683+ message (FATAL_ERROR
684+ "File path is not under ExternalData_BINARY_ROOT:\n "
685+ " file = ${file} \n "
686+ " ExternalData_BINARY_ROOT = ${top_bin} " )
687+ endif ()
688+ set (stamp_file "${ExternalData_STATE_ROOT} /${relfile} -hash-stamp" )
689+ else ()
690+ set (stamp_file "${file} -hash-stamp" )
691+ endif ()
692+ set (${var_stamp} "${stamp_file} " PARENT_SCOPE )
693+ endfunction ()
694+
695+ function (_ExternalData_compute_build_stamp_file file var_stamp )
696+ if (DEFINED ExternalData_STATE_ROOT AND NOT "${ExternalData_STATE_ROOT} " STREQUAL "" )
697+ if (NOT ExternalData_BINARY_ROOT)
698+ set (top_bin "${CMAKE_BINARY_DIR} " )
699+ else ()
700+ set (top_bin "${ExternalData_BINARY_ROOT} " )
701+ endif ()
702+ file (RELATIVE_PATH relfile "${top_bin} " "${file} " )
703+ if (IS_ABSOLUTE "${relfile} " OR "${relfile} " MATCHES "^\\ .\\ ./" )
704+ message (FATAL_ERROR
705+ "File path is not under ExternalData_BINARY_ROOT:\n "
706+ " file = ${file} \n "
707+ " ExternalData_BINARY_ROOT = ${top_bin} " )
708+ endif ()
709+ set (build_stamp_file "${ExternalData_STATE_ROOT} /${relfile} -build-stamp" )
710+ else ()
711+ set (build_stamp_file "${file} -build-stamp" )
712+ endif ()
713+ set (${var_stamp} "${build_stamp_file} " PARENT_SCOPE )
714+ endfunction ()
715+
650716function (_ExternalData_link_content name var_ext )
651717 if ("${ExternalData_LINK_CONTENT} " MATCHES "^(${_ExternalData_REGEX_ALGO} )$" )
652718 set (algo "${ExternalData_LINK_CONTENT} " )
@@ -1294,11 +1360,7 @@ if("${ExternalData_ACTION}" STREQUAL "fetch")
12941360 message (FATAL_ERROR "${errorMsg} " )
12951361 endif ()
12961362 # Check if file already corresponds to the object.
1297- if (DEFINED ExternalData_STAMP_FILE AND NOT "${ExternalData_STAMP_FILE} " STREQUAL "" )
1298- set (stamp_file "${ExternalData_STAMP_FILE} " )
1299- else ()
1300- set (stamp_file "${file} -hash-stamp" )
1301- endif ()
1363+ _ExternalData_compute_stamp_file ("${file} " stamp_file )
13021364 set (file_up_to_date 0)
13031365 if (EXISTS "${file} " AND EXISTS "${stamp_file} " )
13041366 file (READ "${stamp_file} " f_hash )
0 commit comments