initial commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# Verify the complete inventory before installation and again at the destination.
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/WriteWindowsRuntimeManifest.cmake")
|
||||
|
||||
function(docview_verify_windows_worker executable output_files)
|
||||
docview_runtime_file_info("${executable}" EXE worker_name worker_size)
|
||||
get_filename_component(worker_directory "${executable}" DIRECTORY)
|
||||
set(manifest "${executable}.runtime.json")
|
||||
if(NOT EXISTS "${manifest}" OR IS_SYMLINK "${manifest}" OR IS_DIRECTORY "${manifest}")
|
||||
message(FATAL_ERROR "Build the worker runtime manifest before installing: ${manifest}")
|
||||
endif()
|
||||
file(SIZE "${manifest}" manifest_size)
|
||||
if(manifest_size LESS 1 OR manifest_size GREATER 131072)
|
||||
message(FATAL_ERROR "Worker runtime manifest exceeds its size limit or is empty")
|
||||
endif()
|
||||
file(READ "${manifest}" inventory)
|
||||
string(JSON root_type TYPE "${inventory}")
|
||||
string(JSON fields LENGTH "${inventory}")
|
||||
string(JSON version_type TYPE "${inventory}" schemaVersion)
|
||||
string(JSON executable_type TYPE "${inventory}" executable)
|
||||
string(JSON files_type TYPE "${inventory}" files)
|
||||
string(JSON version GET "${inventory}" schemaVersion)
|
||||
string(JSON declared_executable GET "${inventory}" executable)
|
||||
string(JSON count LENGTH "${inventory}" files)
|
||||
if(NOT root_type STREQUAL "OBJECT" OR NOT fields EQUAL 3 OR
|
||||
NOT version_type STREQUAL "NUMBER" OR NOT version STREQUAL "1" OR
|
||||
NOT executable_type STREQUAL "STRING" OR NOT declared_executable STREQUAL worker_name OR
|
||||
NOT files_type STREQUAL "ARRAY" OR count LESS 1 OR count GREATER 128)
|
||||
message(FATAL_ERROR "Invalid worker runtime inventory schema")
|
||||
endif()
|
||||
math(EXPR last "${count} - 1")
|
||||
set(files)
|
||||
set(names)
|
||||
set(total 0)
|
||||
set(found_executable FALSE)
|
||||
foreach(index RANGE 0 ${last})
|
||||
string(JSON entry_type TYPE "${inventory}" files ${index})
|
||||
string(JSON fields LENGTH "${inventory}" files ${index})
|
||||
string(JSON name_type TYPE "${inventory}" files ${index} path)
|
||||
string(JSON hash_type TYPE "${inventory}" files ${index} sha256)
|
||||
string(JSON size_type TYPE "${inventory}" files ${index} size)
|
||||
string(JSON name GET "${inventory}" files ${index} path)
|
||||
string(JSON expected_hash GET "${inventory}" files ${index} sha256)
|
||||
string(JSON expected_size GET "${inventory}" files ${index} size)
|
||||
string(LENGTH "${expected_hash}" hash_length)
|
||||
if(NOT entry_type STREQUAL "OBJECT" OR NOT fields EQUAL 3 OR
|
||||
NOT name_type STREQUAL "STRING" OR NOT hash_type STREQUAL "STRING" OR
|
||||
NOT size_type STREQUAL "NUMBER" OR NOT expected_size MATCHES "^[1-9][0-9]*$" OR
|
||||
NOT hash_length EQUAL 64 OR NOT expected_hash MATCHES "^[0-9a-f]+$")
|
||||
message(FATAL_ERROR "Invalid worker runtime file record")
|
||||
endif()
|
||||
if(name STREQUAL worker_name)
|
||||
set(kind EXE)
|
||||
set(found_executable TRUE)
|
||||
else()
|
||||
set(kind DLL)
|
||||
endif()
|
||||
get_filename_component(basename "${name}" NAME)
|
||||
if(NOT name STREQUAL basename)
|
||||
message(FATAL_ERROR "Runtime manifest contains a non-basename path")
|
||||
endif()
|
||||
set(path "${worker_directory}/${name}")
|
||||
docview_runtime_file_info("${path}" "${kind}" checked_name size)
|
||||
string(TOLOWER "${name}" folded)
|
||||
if(folded IN_LIST names)
|
||||
message(FATAL_ERROR "Runtime manifest has a duplicate name")
|
||||
endif()
|
||||
list(APPEND names "${folded}")
|
||||
math(EXPR total "${total} + ${size}")
|
||||
if(NOT size EQUAL expected_size OR total GREATER 536870912)
|
||||
message(FATAL_ERROR "Runtime file size no longer matches its build inventory: ${name}")
|
||||
endif()
|
||||
file(SHA256 "${path}" actual_hash)
|
||||
if(NOT actual_hash STREQUAL expected_hash)
|
||||
message(FATAL_ERROR "Runtime file no longer matches its build inventory: ${name}")
|
||||
endif()
|
||||
list(APPEND files "${path}")
|
||||
endforeach()
|
||||
if(NOT found_executable)
|
||||
message(FATAL_ERROR "Runtime manifest is missing its worker executable")
|
||||
endif()
|
||||
set(${output_files} "${files}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
docview_verify_windows_worker("${WORKER_EXECUTABLE}" verified_files)
|
||||
if(NOT WORKER_VERIFY_ONLY)
|
||||
if(NOT IS_ABSOLUTE "${WORKER_INSTALL_DIRECTORY}")
|
||||
message(FATAL_ERROR "An absolute worker installation directory is required")
|
||||
endif()
|
||||
# file(INSTALL) applies DESTDIR itself. Its input must retain the unstaged
|
||||
# install prefix; native tools and the verification below need the real path.
|
||||
file(INSTALL DESTINATION "${WORKER_INSTALL_DIRECTORY}" TYPE FILE
|
||||
FILES ${verified_files} "${WORKER_EXECUTABLE}.runtime.json")
|
||||
get_filename_component(worker_name "${WORKER_EXECUTABLE}" NAME)
|
||||
docview_verify_windows_worker("$ENV{DESTDIR}${WORKER_INSTALL_DIRECTORY}/${worker_name}" installed_files)
|
||||
endif()
|
||||
@@ -0,0 +1,16 @@
|
||||
# Local Arch development packaging is explicit; normal builds do not package.
|
||||
include_guard(GLOBAL)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
return()
|
||||
endif()
|
||||
find_package(Python3 3.11 COMPONENTS Interpreter QUIET)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
set(DOCVIEW_LINUX_PACKAGE_OUTPUT "${CMAKE_BINARY_DIR}/linux-development-package" CACHE PATH
|
||||
"New output directory for the local Arch development package")
|
||||
add_custom_target(linux-development-package
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/package_linux_development.py"
|
||||
--build-dir "${CMAKE_BINARY_DIR}" --output "${DOCVIEW_LINUX_PACKAGE_OUTPUT}"
|
||||
DEPENDS docview docview-pdf-worker docview-archive-worker
|
||||
USES_TERMINAL VERBATIM
|
||||
COMMENT "Packaging the local Arch development build with dependency notices")
|
||||
endif()
|
||||
@@ -0,0 +1,10 @@
|
||||
# The non-V8 build never executes PDF JavaScript or dynamic XFA.
|
||||
set(DOCVIEW_PDFIUM_ROOT "${CMAKE_SOURCE_DIR}/.deps/pdfium" CACHE PATH "Pinned PDFium installation")
|
||||
find_path(DOCVIEW_PDFIUM_INCLUDE_DIR fpdfview.h HINTS "${DOCVIEW_PDFIUM_ROOT}/include" REQUIRED)
|
||||
find_library(DOCVIEW_PDFIUM_LIBRARY NAMES pdfium pdfium.dll HINTS "${DOCVIEW_PDFIUM_ROOT}/lib" REQUIRED)
|
||||
add_library(PDFium::PDFium SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(PDFium::PDFium PROPERTIES IMPORTED_LOCATION "${DOCVIEW_PDFIUM_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${DOCVIEW_PDFIUM_INCLUDE_DIR}")
|
||||
if(WIN32)
|
||||
find_file(DOCVIEW_PDFIUM_RUNTIME pdfium.dll HINTS "${DOCVIEW_PDFIUM_ROOT}/bin" NO_DEFAULT_PATH REQUIRED)
|
||||
set_target_properties(PDFium::PDFium PROPERTIES IMPORTED_IMPLIB "${DOCVIEW_PDFIUM_LIBRARY}" IMPORTED_LOCATION "${DOCVIEW_PDFIUM_RUNTIME}")
|
||||
endif()
|
||||
@@ -0,0 +1,83 @@
|
||||
# Extra notices tied to the exact inspected Linux PDFium library and source pin.
|
||||
# These are install data; no application code or PDF rendering option changes.
|
||||
set(_notice_root "${CMAKE_CURRENT_SOURCE_DIR}/resources/licenses/pdfium-supplemental")
|
||||
file(READ "${_notice_root}/sources.json" _notice_manifest)
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pdfium.lock.json" _pdfium_lock)
|
||||
foreach(_pair "pdfiumVersion;version" "pdfiumUpstreamCommit;upstreamCommit")
|
||||
list(GET _pair 0 _notice_key)
|
||||
list(GET _pair 1 _lock_key)
|
||||
string(JSON _notice_value GET "${_notice_manifest}" "${_notice_key}")
|
||||
string(JSON _lock_value GET "${_pdfium_lock}" "${_lock_key}")
|
||||
if(NOT _notice_value STREQUAL _lock_value)
|
||||
message(FATAL_ERROR "PDFium supplemental notices need review for this source pin")
|
||||
endif()
|
||||
endforeach()
|
||||
string(JSON _notice_binary GET "${_notice_manifest}" pdfiumLibrarySha256)
|
||||
file(SHA256 "${DOCVIEW_PDFIUM_LIBRARY}" _actual_binary)
|
||||
if(NOT _notice_binary STREQUAL _actual_binary OR EXISTS "${DOCVIEW_PDFIUM_ROOT}/BUILDINFO.json")
|
||||
# Only the separately reviewed v2 tuple may differ from the original
|
||||
# provider. There is deliberately no user-supplied trust/hash override.
|
||||
find_package(Python3 3.11 REQUIRED COMPONENTS Interpreter)
|
||||
set(_candidate_verifier "${CMAKE_CURRENT_SOURCE_DIR}/tools/verify_pdfium_candidate.py")
|
||||
set(_candidate_notice "${CMAKE_CURRENT_BINARY_DIR}/pdfium-candidate-notices/sources.json")
|
||||
execute_process(COMMAND "${Python3_EXECUTABLE}" "${_candidate_verifier}"
|
||||
--prefix "${DOCVIEW_PDFIUM_ROOT}" --library "${DOCVIEW_PDFIUM_LIBRARY}"
|
||||
--include-dir "${DOCVIEW_PDFIUM_INCLUDE_DIR}" --notice-output "${_candidate_notice}"
|
||||
RESULT_VARIABLE _candidate_result OUTPUT_VARIABLE _candidate_record ERROR_VARIABLE _candidate_error)
|
||||
if(NOT _candidate_result EQUAL 0)
|
||||
message(FATAL_ERROR "PDFium supplemental notices need review for this binary: ${_candidate_error}")
|
||||
endif()
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${DOCVIEW_PDFIUM_ROOT}/BUILDINFO.json"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pdfium-candidate-v2.lock.json"
|
||||
"${_candidate_verifier}")
|
||||
# Recheck at install as well: configure success is not permission to install
|
||||
# a prefix whose library, headers, or correspondence subsequently changed.
|
||||
install(CODE "
|
||||
execute_process(COMMAND \"${Python3_EXECUTABLE}\" \"${_candidate_verifier}\"
|
||||
--prefix \"${DOCVIEW_PDFIUM_ROOT}\" --library \"${DOCVIEW_PDFIUM_LIBRARY}\"
|
||||
--include-dir \"${DOCVIEW_PDFIUM_INCLUDE_DIR}\" --notice-output \"${_candidate_notice}\"
|
||||
RESULT_VARIABLE candidate_result OUTPUT_QUIET ERROR_VARIABLE candidate_error)
|
||||
if(NOT candidate_result EQUAL 0)
|
||||
message(FATAL_ERROR \"PDFium candidate changed before install: \${candidate_error}\")
|
||||
endif()")
|
||||
string(JSON _candidate_files LENGTH "${_candidate_record}" installFiles)
|
||||
math(EXPR _candidate_last "${_candidate_files} - 1")
|
||||
foreach(_index RANGE 0 ${_candidate_last})
|
||||
string(JSON _source GET "${_candidate_record}" installFiles ${_index} source)
|
||||
string(JSON _destination GET "${_candidate_record}" installFiles ${_index} destination)
|
||||
get_filename_component(_directory "${_destination}" DIRECTORY)
|
||||
install(FILES "${DOCVIEW_PDFIUM_ROOT}/${_source}" DESTINATION "${_directory}")
|
||||
endforeach()
|
||||
install(FILES "${_candidate_notice}" DESTINATION share/doc/docview/pdfium/supplemental)
|
||||
install(FILES "${DOCVIEW_PDFIUM_ROOT}/licenses/libcxx-LICENSE.txt"
|
||||
"${DOCVIEW_PDFIUM_ROOT}/licenses/libcxxabi-LICENSE.txt"
|
||||
DESTINATION share/doc/docview/pdfium/supplemental)
|
||||
return()
|
||||
endif()
|
||||
string(JSON _notice_count LENGTH "${_notice_manifest}" files)
|
||||
string(JSON _notice_schema GET "${_notice_manifest}" schemaVersion)
|
||||
if(NOT _notice_schema EQUAL 1)
|
||||
message(FATAL_ERROR "Unsupported PDFium supplemental notice schema")
|
||||
endif()
|
||||
if(NOT _notice_count EQUAL 2)
|
||||
message(FATAL_ERROR "Expected the two audited PDFium supplemental notices")
|
||||
endif()
|
||||
set(_notice_names)
|
||||
foreach(_index RANGE 0 1)
|
||||
string(JSON _name GET "${_notice_manifest}" files ${_index} name)
|
||||
string(JSON _hash GET "${_notice_manifest}" files ${_index} sha256)
|
||||
if(NOT _name MATCHES "^(libcxx|libcxxabi)-LICENSE[.]txt$")
|
||||
message(FATAL_ERROR "Unexpected PDFium supplemental notice filename")
|
||||
endif()
|
||||
if(_name IN_LIST _notice_names)
|
||||
message(FATAL_ERROR "Duplicate PDFium supplemental notice filename")
|
||||
endif()
|
||||
list(APPEND _notice_names "${_name}")
|
||||
file(SHA256 "${_notice_root}/${_name}" _actual)
|
||||
if(NOT _actual STREQUAL _hash)
|
||||
message(FATAL_ERROR "PDFium supplemental notice digest mismatch")
|
||||
endif()
|
||||
install(FILES "${_notice_root}/${_name}" DESTINATION share/doc/docview/pdfium/supplemental)
|
||||
endforeach()
|
||||
install(FILES "${_notice_root}/sources.json" DESTINATION share/doc/docview/pdfium/supplemental)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,61 @@
|
||||
# Windows SDK tools and matching Qt binaries are required on the build host.
|
||||
include_guard(GLOBAL)
|
||||
if(NOT WIN32)
|
||||
return()
|
||||
endif()
|
||||
set(DOCVIEW_EXTRA_WORKER_RUNTIME_DLLS "" CACHE STRING
|
||||
"Explicit additional redistributable DLLs approved for worker packaging")
|
||||
find_program(DOCVIEW_DUMPBIN_EXECUTABLE NAMES dumpbin REQUIRED)
|
||||
find_program(DOCVIEW_WINDEPLOYQT_EXECUTABLE NAMES windeployqt
|
||||
HINTS "${Qt6_DIR}/../../../bin" REQUIRED)
|
||||
|
||||
function(docview_configure_windows_worker target)
|
||||
set(script "${CMAKE_CURRENT_BINARY_DIR}/stage-${target}-$<CONFIG>.cmake")
|
||||
file(GENERATE OUTPUT "${script}" CONTENT "
|
||||
set(WORKER_EXECUTABLE [==[$<TARGET_FILE:${target}>]==])
|
||||
set(RUNTIME_LIBRARIES [==[$<TARGET_RUNTIME_DLLS:${target}>]==])
|
||||
set(SEARCH_DIRECTORIES [==[$<TARGET_FILE_DIR:Qt6::Core>;$<TARGET_FILE_DIR:${DOCVIEW_ZIP_TARGET}>;$<TARGET_FILE_DIR:qpdf::libqpdf>;${DOCVIEW_PDFIUM_ROOT}/bin]==])
|
||||
set(EXTRA_RUNTIME_LIBRARIES [==[${DOCVIEW_EXTRA_WORKER_RUNTIME_DLLS}]==])
|
||||
set(CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL dumpbin)
|
||||
set(CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND [==[${DOCVIEW_DUMPBIN_EXECUTABLE}]==])
|
||||
include([==[${CMAKE_CURRENT_FUNCTION_LIST_DIR}/StageWindowsWorker.cmake]==])
|
||||
")
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -P "${script}" VERBATIM
|
||||
COMMENT "Collecting and hashing the restricted runtime for ${target}")
|
||||
endfunction()
|
||||
|
||||
function(docview_install_windows_worker target)
|
||||
set(script "${CMAKE_CURRENT_BINARY_DIR}/install-${target}-$<CONFIG>.cmake")
|
||||
file(GENERATE OUTPUT "${script}" CONTENT "
|
||||
set(WORKER_EXECUTABLE [==[$<TARGET_FILE:${target}>]==])
|
||||
set(WORKER_INSTALL_DIRECTORY \"\${CMAKE_INSTALL_PREFIX}/bin\")
|
||||
set(WORKER_VERIFY_ONLY FALSE)
|
||||
include([==[${CMAKE_CURRENT_FUNCTION_LIST_DIR}/InstallWindowsWorker.cmake]==])
|
||||
")
|
||||
install(SCRIPT "${script}")
|
||||
endfunction()
|
||||
|
||||
function(docview_install_windows_gui target)
|
||||
set(script "${CMAKE_CURRENT_BINARY_DIR}/deploy-${target}-$<CONFIG>.cmake")
|
||||
file(GENERATE OUTPUT "${script}" CONTENT "
|
||||
set(destination \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin\")
|
||||
execute_process(COMMAND [==[${DOCVIEW_WINDEPLOYQT_EXECUTABLE}]==]
|
||||
--$<IF:$<CONFIG:Debug>,debug,release> --no-compiler-runtime --nopatchqt
|
||||
--dir \"\${destination}\" --plugindir \"\${destination}/plugins\"
|
||||
--qml-deploy-dir \"\${destination}/qml\" --translationdir \"\${destination}/translations\"
|
||||
--qmldir [==[${CMAKE_SOURCE_DIR}/qml]==] \"\${destination}/$<TARGET_FILE_NAME:${target}>\"
|
||||
RESULT_VARIABLE deployed)
|
||||
if(NOT deployed EQUAL 0)
|
||||
message(FATAL_ERROR \"windeployqt failed: \${deployed}\")
|
||||
endif()
|
||||
file(WRITE \"\${destination}/qt.conf\" \"[Paths]\\nPrefix=.\\nPlugins=plugins\\nQmlImports=qml\\nLibraryExecutables=.\\nData=.\\nTranslations=translations\\n\")
|
||||
# windeployqt must not patch or replace any hashed worker dependency.
|
||||
set(WORKER_VERIFY_ONLY TRUE)
|
||||
foreach(name docview-pdf-worker.exe docview-archive-worker.exe)
|
||||
set(WORKER_EXECUTABLE \"\${destination}/\${name}\")
|
||||
include([==[${CMAKE_CURRENT_FUNCTION_LIST_DIR}/InstallWindowsWorker.cmake]==])
|
||||
endforeach()
|
||||
")
|
||||
install(SCRIPT "${script}")
|
||||
endfunction()
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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()
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fetch the hash-pinned non-V8 PDFium dependency for Linux or Windows x64."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib.request
|
||||
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
lock = json.loads((root / 'cmake/pdfium.lock.json').read_text())
|
||||
host = {'Linux': 'linux-x64', 'Windows': 'windows-x64'}.get(platform.system())
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--platform', choices=['linux-x64', 'windows-x64'], default=host)
|
||||
parser.add_argument('--destination', type=Path)
|
||||
args = parser.parse_args()
|
||||
if args.platform is None or (args.platform == host and platform.machine() not in ('x86_64', 'AMD64')):
|
||||
raise SystemExit('A supported x64 platform is required; specify --platform for a cross-platform download.')
|
||||
prefix = 'windowsX64' if args.platform == 'windows-x64' else 'linuxX64'
|
||||
destination = args.destination or root / '.deps' / ('pdfium' if args.platform == host else 'pdfium-' + args.platform.split('-')[0])
|
||||
with tempfile.TemporaryDirectory(prefix='docview-pdfium-') as temporary:
|
||||
archive = Path(temporary) / 'pdfium.tgz'
|
||||
urllib.request.urlretrieve(lock[prefix + 'Archive'], archive)
|
||||
if hashlib.sha256(archive.read_bytes()).hexdigest() != lock[prefix + 'Sha256']:
|
||||
raise SystemExit('PDFium archive hash mismatch; dependency not installed.')
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
with tarfile.open(archive) as bundle:
|
||||
bundle.extractall(destination, filter='data')
|
||||
print(f'Installed pinned PDFium {lock["version"]} in {destination}')
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"candidateId": "reviewed-v2",
|
||||
"sourceRevision": "a5a7089234f121990b336b3841008009dca143bf",
|
||||
"librarySha256": "cb049fe434f4c911b5eb047c0aca572d4dd9f5e405dde329b76c05a6aa771403",
|
||||
"buildInfoSha256": "6fbb6f24a7ca241150f8abf8d9ad031c76d9cfffc809026a61068c357d47ae18",
|
||||
"anchorRecordPath": "provenance/master6.json",
|
||||
"anchorRecordSha256": "80af72b06d5c9a6a3ab8b9311373e6a23e8df28668ce575e1124350e69aa160b",
|
||||
"patchSha256": "470ca6dd5c83e06ee82c788d1e34b19766f7c592473b80607f0d7bb09d761616",
|
||||
"gnArgsSha256": "5a2e04e1c0bb3eb05dc415dfa005a917804be8f63ade2365016c148e8efd8197",
|
||||
"supplementalSourceSha256": "97d75f0b50e5ad8114229ec4e5cece147272563dfed4fd434087b70d871a114a"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": "155.0.8057.0",
|
||||
"branch": "chromium/8057",
|
||||
"upstreamCommit": "a5a7089234f121990b336b3841008009dca143bf",
|
||||
"upstream": "https://pdfium.googlesource.com/pdfium/+/a5a7089234f121990b336b3841008009dca143bf",
|
||||
"binaryProvider": "https://github.com/bblanchon/pdfium-binaries",
|
||||
"linuxX64Archive": "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F8057/pdfium-linux-x64.tgz",
|
||||
"linuxX64Sha256": "7788fa57a2996ebd21afc4090eb0a42b9f95ef3a72e7ef4c0756f1ea703c0415",
|
||||
"windowsX64Archive": "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F8057/pdfium-win-x64.tgz",
|
||||
"windowsX64Sha256": "e307d519e42f2e69b1b531f0c2a32dffcdf3891ec0eba60328ba51a57cec01ed",
|
||||
"windowsBuildOptions": {
|
||||
"pdf_enable_v8": false,
|
||||
"pdf_enable_xfa": false,
|
||||
"target_cpu": "x64",
|
||||
"target_os": "win"
|
||||
},
|
||||
"buildOptions": {
|
||||
"pdf_enable_v8": false,
|
||||
"pdf_enable_xfa": false,
|
||||
"pdf_is_standalone": true,
|
||||
"is_debug": false,
|
||||
"pdf_use_partition_alloc": false,
|
||||
"target_cpu": "x64",
|
||||
"target_os": "linux"
|
||||
},
|
||||
"licenses": [".deps/pdfium/LICENSE", ".deps/pdfium/licenses/"],
|
||||
"apiNotes": "Structure tree, MCID and page-object access use experimental public APIs, compiled against these exact headers. No private Qt or PDFium API is used."
|
||||
}
|
||||
Reference in New Issue
Block a user