initial commit
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
# Native script/include inputs: WORKER_EXECUTABLE, RUNTIME_LIBRARIES,
|
||||
# SEARCH_DIRECTORIES and EXTRA_RUNTIME_LIBRARIES (explicit MSVC redistributables).
|
||||
# No input is resolved through PATH. CMake's PE scanner searches the importing
|
||||
# binary's directory, OS directories and the explicit DIRECTORIES list.
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/WriteWindowsRuntimeManifest.cmake")
|
||||
|
||||
function(docview_runtime_actual_path input output)
|
||||
if(NOT IS_ABSOLUTE "${input}" OR NOT EXISTS "${input}" OR IS_SYMLINK "${input}")
|
||||
message(FATAL_ERROR "Invalid worker runtime source: ${input}")
|
||||
endif()
|
||||
set(actual "${input}")
|
||||
if(WIN32)
|
||||
# CMake before 3.27 lowercases DLL result names. Recover on-disk casing
|
||||
# before comparing an imported target against the scanner's result.
|
||||
get_filename_component(parent "${input}" DIRECTORY)
|
||||
get_filename_component(name "${input}" NAME)
|
||||
string(TOLOWER "${name}" wanted)
|
||||
file(GLOB siblings LIST_DIRECTORIES FALSE "${parent}/*")
|
||||
set(matches)
|
||||
foreach(sibling IN LISTS siblings)
|
||||
get_filename_component(candidate "${sibling}" NAME)
|
||||
string(TOLOWER "${candidate}" folded)
|
||||
if(folded STREQUAL wanted)
|
||||
list(APPEND matches "${sibling}")
|
||||
endif()
|
||||
endforeach()
|
||||
list(LENGTH matches count)
|
||||
if(NOT count EQUAL 1)
|
||||
message(FATAL_ERROR "Ambiguous case-insensitive runtime source: ${input}")
|
||||
endif()
|
||||
list(GET matches 0 actual)
|
||||
endif()
|
||||
set(${output} "${actual}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Pure package-copy step is independently testable with synthetic MZ fixtures.
|
||||
# It does not claim that these files passed the native PE dependency scanner.
|
||||
function(docview_stage_runtime_files executable sources)
|
||||
docview_runtime_file_info("${executable}" EXE executable_name executable_size)
|
||||
get_filename_component(directory "${executable}" DIRECTORY)
|
||||
file(LOCK "${directory}/.docview-worker-runtime.lock" GUARD FUNCTION TIMEOUT 60 RESULT_VARIABLE locked)
|
||||
if(NOT locked STREQUAL "0")
|
||||
message(FATAL_ERROR "Cannot lock the worker runtime package directory: ${locked}")
|
||||
endif()
|
||||
set(names)
|
||||
set(paths)
|
||||
set(digests)
|
||||
set(sizes)
|
||||
set(total "${executable_size}")
|
||||
foreach(input IN LISTS sources)
|
||||
docview_runtime_actual_path("${input}" source)
|
||||
docview_runtime_file_info("${source}" DLL name size)
|
||||
string(TOLOWER "${name}" key)
|
||||
list(FIND names "${key}" existing)
|
||||
file(SHA256 "${source}" digest)
|
||||
if(NOT existing EQUAL -1)
|
||||
list(GET paths ${existing} previous)
|
||||
get_filename_component(previous_name "${previous}" NAME)
|
||||
list(GET digests ${existing} previous_digest)
|
||||
if(NOT name STREQUAL previous_name OR NOT digest STREQUAL previous_digest)
|
||||
message(FATAL_ERROR "Conflicting worker runtime DLL sources: ${previous};${source}")
|
||||
endif()
|
||||
continue()
|
||||
endif()
|
||||
list(APPEND names "${key}")
|
||||
list(APPEND paths "${source}")
|
||||
list(APPEND digests "${digest}")
|
||||
list(APPEND sizes "${size}")
|
||||
list(LENGTH names count)
|
||||
math(EXPR total "${total} + ${size}")
|
||||
if(count GREATER 127 OR total GREATER 536870912)
|
||||
message(FATAL_ERROR "Worker runtime exceeds 128 files or 512 MiB")
|
||||
endif()
|
||||
endforeach()
|
||||
# Inspect existing names case-insensitively, including on case-sensitive
|
||||
# build hosts. Never overwrite a different DLL shared by another worker.
|
||||
file(GLOB existing_entries LIST_DIRECTORIES TRUE "${directory}/*")
|
||||
set(runtime_files "${executable}")
|
||||
list(LENGTH paths count)
|
||||
if(count GREATER 0)
|
||||
math(EXPR last "${count} - 1")
|
||||
foreach(index RANGE 0 ${last})
|
||||
list(GET paths ${index} source)
|
||||
list(GET names ${index} key)
|
||||
list(GET digests ${index} digest)
|
||||
list(GET sizes ${index} size)
|
||||
get_filename_component(name "${source}" NAME)
|
||||
set(destination "${directory}/${name}")
|
||||
foreach(entry IN LISTS existing_entries)
|
||||
get_filename_component(existing_name "${entry}" NAME)
|
||||
string(TOLOWER "${existing_name}" existing_key)
|
||||
if(existing_key STREQUAL key AND NOT existing_name STREQUAL name)
|
||||
message(FATAL_ERROR "Case-insensitive runtime destination collision: ${name}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(EXISTS "${destination}" OR IS_SYMLINK "${destination}")
|
||||
docview_runtime_file_info("${destination}" DLL old_name old_size)
|
||||
file(SHA256 "${destination}" old_digest)
|
||||
if(NOT old_size EQUAL size OR NOT old_digest STREQUAL digest)
|
||||
message(FATAL_ERROR "Refusing to replace a different runtime DLL: ${destination}")
|
||||
endif()
|
||||
else()
|
||||
string(RANDOM LENGTH 24 ALPHABET 0123456789abcdef nonce)
|
||||
set(temporary "${destination}.${nonce}.tmp")
|
||||
if(EXISTS "${temporary}" OR IS_SYMLINK "${temporary}")
|
||||
message(FATAL_ERROR "Runtime copy temporary path already exists")
|
||||
endif()
|
||||
file(COPY_FILE "${source}" "${temporary}" RESULT copied)
|
||||
if(NOT copied STREQUAL "0")
|
||||
file(REMOVE "${temporary}")
|
||||
message(FATAL_ERROR "Cannot copy runtime DLL: ${copied}")
|
||||
endif()
|
||||
file(SHA256 "${temporary}" copied_digest)
|
||||
file(SIZE "${temporary}" copied_size)
|
||||
if(NOT copied_digest STREQUAL digest OR NOT copied_size EQUAL size)
|
||||
file(REMOVE "${temporary}")
|
||||
message(FATAL_ERROR "Runtime DLL changed while copying: ${source}")
|
||||
endif()
|
||||
file(RENAME "${temporary}" "${destination}" NO_REPLACE RESULT renamed)
|
||||
if(NOT renamed STREQUAL "0")
|
||||
file(REMOVE "${temporary}")
|
||||
message(FATAL_ERROR "Cannot publish runtime DLL: ${renamed}")
|
||||
endif()
|
||||
# Older CMake on POSIX can publish NO_REPLACE with a hard link
|
||||
# while leaving the source name behind. Remove only our own
|
||||
# temporary name after successful publication, preserving the
|
||||
# destination and NO_REPLACE's collision protection.
|
||||
file(REMOVE "${temporary}")
|
||||
if(EXISTS "${temporary}" OR IS_SYMLINK "${temporary}")
|
||||
message(FATAL_ERROR "Cannot remove the runtime copy temporary path")
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND runtime_files "${destination}")
|
||||
endforeach()
|
||||
endif()
|
||||
docview_write_windows_runtime_manifest("${executable}" "${runtime_files}")
|
||||
endfunction()
|
||||
|
||||
function(docview_regex_case_path path output)
|
||||
string(REPLACE "\\" "/" normalized "${path}")
|
||||
string(LENGTH "${normalized}" length)
|
||||
math(EXPR last "${length} - 1")
|
||||
set(regex)
|
||||
set(metacharacters "." "[" "]" "(" ")" "+" "*" "?" "^" "$" "|" "{" "}")
|
||||
foreach(index RANGE 0 ${last})
|
||||
string(SUBSTRING "${normalized}" ${index} 1 character)
|
||||
if(character MATCHES "[A-Za-z]")
|
||||
string(TOUPPER "${character}" upper)
|
||||
string(TOLOWER "${character}" lower)
|
||||
string(APPEND regex "[${upper}${lower}]")
|
||||
elseif(character IN_LIST metacharacters)
|
||||
string(APPEND regex "\\${character}")
|
||||
else()
|
||||
string(APPEND regex "${character}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(${output} "${regex}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(docview_is_windows_os_runtime path system_root output)
|
||||
string(REPLACE "\\" "/" normalized "${path}")
|
||||
string(REPLACE "\\" "/" root "${system_root}")
|
||||
string(REGEX REPLACE "/+$" "" root "${root}")
|
||||
string(TOLOWER "${normalized}" normalized)
|
||||
string(TOLOWER "${root}/" root)
|
||||
string(FIND "${normalized}" "${root}" prefix)
|
||||
set(result FALSE)
|
||||
if(prefix EQUAL 0)
|
||||
string(LENGTH "${root}" length)
|
||||
string(SUBSTRING "${normalized}" ${length} -1 relative)
|
||||
if(relative MATCHES "^(system32|syswow64)/" OR relative MATCHES "^[^/]+[.]dll$")
|
||||
set(result TRUE)
|
||||
endif()
|
||||
endif()
|
||||
set(${output} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(docview_stage_windows_worker)
|
||||
if(NOT CMAKE_HOST_WIN32)
|
||||
message(FATAL_ERROR "Native Windows PE runtime dependency scanning requires a Windows host")
|
||||
endif()
|
||||
docview_runtime_file_info("${WORKER_EXECUTABLE}" EXE executable_name executable_size)
|
||||
file(TO_CMAKE_PATH "$ENV{SystemRoot}" system_root)
|
||||
if(NOT IS_ABSOLUTE "${system_root}" OR NOT IS_DIRECTORY "${system_root}/System32")
|
||||
message(FATAL_ERROR "Cannot locate the Windows OS runtime directories")
|
||||
endif()
|
||||
docview_regex_case_path("${system_root}" os_regex)
|
||||
set(os_exclusions "^${os_regex}/[Ss][Yy][Ss][Tt][Ee][Mm]32/"
|
||||
"^${os_regex}/[Ss][Yy][Ss][Ww][Oo][Ww]64/"
|
||||
"^${os_regex}/[^/]+[.][Dd][Ll][Ll]$")
|
||||
set(explicit_libraries)
|
||||
set(directories ${SEARCH_DIRECTORIES})
|
||||
foreach(input IN LISTS RUNTIME_LIBRARIES EXTRA_RUNTIME_LIBRARIES)
|
||||
docview_runtime_actual_path("${input}" source)
|
||||
docview_runtime_file_info("${source}" DLL name size)
|
||||
string(TOLOWER "${name}" folded)
|
||||
if(folded MATCHES "^(api|ext)-ms-win-.*[.]dll$")
|
||||
message(FATAL_ERROR "API-set DLLs belong to Windows and must not be copied: ${name}")
|
||||
endif()
|
||||
docview_is_windows_os_runtime("${source}" "${system_root}" os_file)
|
||||
if(os_file)
|
||||
message(FATAL_ERROR "Do not package DLLs from Windows OS directories: ${source}")
|
||||
endif()
|
||||
list(APPEND explicit_libraries "${source}")
|
||||
get_filename_component(parent "${source}" DIRECTORY)
|
||||
list(APPEND directories "${parent}")
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES explicit_libraries)
|
||||
list(REMOVE_DUPLICATES directories)
|
||||
foreach(directory IN LISTS directories)
|
||||
if(NOT IS_ABSOLUTE "${directory}" OR NOT IS_DIRECTORY "${directory}")
|
||||
message(FATAL_ERROR "DLL search directories must be explicit existing absolute paths: ${directory}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM "windows+pe")
|
||||
if(DEFINED CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND AND
|
||||
(NOT IS_ABSOLUTE "${CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND}" OR
|
||||
NOT EXISTS "${CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND}"))
|
||||
message(FATAL_ERROR "The PE inspection tool must have an existing absolute path")
|
||||
endif()
|
||||
file(GET_RUNTIME_DEPENDENCIES
|
||||
EXECUTABLES "${WORKER_EXECUTABLE}"
|
||||
LIBRARIES ${explicit_libraries}
|
||||
DIRECTORIES ${directories}
|
||||
RESOLVED_DEPENDENCIES_VAR resolved
|
||||
UNRESOLVED_DEPENDENCIES_VAR unresolved
|
||||
CONFLICTING_DEPENDENCIES_PREFIX conflicting
|
||||
PRE_EXCLUDE_REGEXES "^api-ms-win-.*[.]dll$" "^ext-ms-win-.*[.]dll$"
|
||||
POST_EXCLUDE_REGEXES ${os_exclusions})
|
||||
if(unresolved)
|
||||
message(FATAL_ERROR "Unresolved worker runtime DLL dependencies: ${unresolved}")
|
||||
endif()
|
||||
if(conflicting_FILENAMES)
|
||||
message(FATAL_ERROR "Conflicting worker runtime DLL dependencies: ${conflicting_FILENAMES}")
|
||||
endif()
|
||||
set(package_dependencies)
|
||||
foreach(dependency IN LISTS resolved)
|
||||
docview_runtime_actual_path("${dependency}" dependency)
|
||||
docview_is_windows_os_runtime("${dependency}" "${system_root}" os_file)
|
||||
get_filename_component(name "${dependency}" NAME)
|
||||
string(TOLOWER "${name}" folded)
|
||||
if(NOT os_file AND NOT folded MATCHES "^(api|ext)-ms-win-.*[.]dll$")
|
||||
list(APPEND package_dependencies "${dependency}")
|
||||
endif()
|
||||
endforeach()
|
||||
# Explicit redistributables are included even if a matching import resolved
|
||||
# to a Windows OS DLL that the scanner correctly excluded.
|
||||
set(package_sources ${explicit_libraries} ${package_dependencies})
|
||||
docview_stage_runtime_files("${WORKER_EXECUTABLE}" "${package_sources}")
|
||||
endfunction()
|
||||
|
||||
if(DEFINED WORKER_EXECUTABLE)
|
||||
docview_stage_windows_worker()
|
||||
endif()
|
||||
Reference in New Issue
Block a user