# Script API: -DWORKER_EXECUTABLE= -DRUNTIME_FILES= # Include API: docview_write_windows_runtime_manifest(executable "${files}"). # This writer validates the package inventory, not the PE import table. The # native dependency scanner is StageWindowsWorker.cmake. cmake_minimum_required(VERSION 3.24) include_guard(GLOBAL) function(docview_runtime_file_info path kind out_name out_size) if(NOT IS_ABSOLUTE "${path}" OR NOT EXISTS "${path}" OR IS_DIRECTORY "${path}" OR IS_SYMLINK "${path}") message(FATAL_ERROR "Worker runtime requires an absolute regular file: ${path}") endif() get_filename_component(name "${path}" NAME) string(LENGTH "${name}" length) string(TOLOWER "${name}" folded) if(length GREATER 128 OR NOT name MATCHES "^[A-Za-z0-9][A-Za-z0-9._-]*$" OR name MATCHES "[.]$" OR folded MATCHES "^(con|prn|aux|nul|com[1-9]|lpt[1-9])([.]|$)") message(FATAL_ERROR "Invalid Windows runtime filename: ${name}") endif() if((kind STREQUAL "EXE" AND NOT folded MATCHES "[.]exe$") OR (kind STREQUAL "DLL" AND NOT folded MATCHES "[.]dll$")) message(FATAL_ERROR "Only the worker executable and DLLs may enter its runtime: ${name}") endif() file(SIZE "${path}" size) if(size LESS 2 OR size GREATER 268435456) message(FATAL_ERROR "Worker runtime file exceeds the 256 MiB limit or is empty: ${name}") endif() file(READ "${path}" signature LIMIT 2 HEX) if(NOT signature STREQUAL "4d5a") message(FATAL_ERROR "Worker runtime file has no MZ signature: ${name}") endif() set(${out_name} "${name}" PARENT_SCOPE) set(${out_size} "${size}" PARENT_SCOPE) endfunction() function(docview_write_windows_runtime_manifest executable files) docview_runtime_file_info("${executable}" EXE executable_name executable_size) file(REAL_PATH "${executable}" executable_real) get_filename_component(directory "${executable_real}" DIRECTORY) if(WIN32) string(TOLOWER "${directory}" directory) endif() list(LENGTH files count) if(count LESS 1 OR count GREATER 128) message(FATAL_ERROR "Worker runtime inventory must contain 1..128 files") endif() set(seen) set(total 0) set(has_executable FALSE) # Check every limit before hashing potentially large files or changing an # existing manifest. File order does not affect the serialized inventory. foreach(path IN LISTS files) get_filename_component(name "${path}" NAME) if(name STREQUAL executable_name) set(kind EXE) else() set(kind DLL) endif() docview_runtime_file_info("${path}" "${kind}" name size) file(REAL_PATH "${path}" real) get_filename_component(parent "${real}" DIRECTORY) if(WIN32) string(TOLOWER "${parent}" parent) endif() if(NOT parent STREQUAL directory) message(FATAL_ERROR "Manifest entries must already be beside the worker: ${name}") endif() string(TOLOWER "${name}" key) if(key IN_LIST seen) message(FATAL_ERROR "Case-insensitive duplicate worker runtime filename: ${name}") endif() list(APPEND seen "${key}") if(name STREQUAL executable_name) if(NOT real STREQUAL executable_real) message(FATAL_ERROR "Manifest executable does not match its worker") endif() set(has_executable TRUE) endif() math(EXPR total "${total} + ${size}") if(total GREATER 536870912) message(FATAL_ERROR "Worker runtime exceeds the 512 MiB aggregate limit") endif() endforeach() if(NOT has_executable) message(FATAL_ERROR "Worker runtime manifest must include its executable") endif() list(SORT files CASE INSENSITIVE) set(entries) foreach(path IN LISTS files) get_filename_component(name "${path}" NAME) file(SIZE "${path}" size) file(SHA256 "${path}" digest) string(TOLOWER "${digest}" digest) list(APPEND entries " {\"path\":\"${name}\",\"sha256\":\"${digest}\",\"size\":${size}}") endforeach() list(JOIN entries ",\n" entries_json) set(json "{\n \"schemaVersion\":1,\n \"executable\":\"${executable_name}\",\n \"files\":[\n${entries_json}\n ]\n}\n") string(LENGTH "${json}" json_size) if(json_size GREATER 131072) message(FATAL_ERROR "Worker runtime manifest exceeds its 128 KiB limit") endif() set(output "${executable}.runtime.json") if(IS_SYMLINK "${output}" OR IS_DIRECTORY "${output}") message(FATAL_ERROR "Refusing a non-regular worker runtime manifest destination") endif() string(RANDOM LENGTH 24 ALPHABET 0123456789abcdef nonce) set(temporary "${output}.${nonce}.tmp") if(EXISTS "${temporary}" OR IS_SYMLINK "${temporary}") message(FATAL_ERROR "Worker runtime manifest temporary path already exists") endif() file(WRITE "${temporary}" "${json}") file(RENAME "${temporary}" "${output}" RESULT renamed) if(NOT renamed STREQUAL "0") file(REMOVE "${temporary}") message(FATAL_ERROR "Cannot atomically publish worker runtime manifest: ${renamed}") endif() endfunction() if(CMAKE_SCRIPT_MODE_FILE STREQUAL CMAKE_CURRENT_LIST_FILE) if(NOT DEFINED WORKER_EXECUTABLE OR NOT DEFINED RUNTIME_FILES) message(FATAL_ERROR "WORKER_EXECUTABLE and RUNTIME_FILES are required") endif() docview_write_windows_runtime_manifest("${WORKER_EXECUTABLE}" "${RUNTIME_FILES}") endif()