124 lines
5.5 KiB
CMake
124 lines
5.5 KiB
CMake
# Script API: -DWORKER_EXECUTABLE=<absolute exe> -DRUNTIME_FILES=<absolute list>
|
|
# 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()
|