diff --git a/CMakeModules/CopyMinGWDeps.cmake b/CMakeModules/CopyMinGWDeps.cmake index c708e0fb33..f468d80b1c 100755 --- a/CMakeModules/CopyMinGWDeps.cmake +++ b/CMakeModules/CopyMinGWDeps.cmake @@ -1,40 +1,40 @@ -# CopyMinGWDeps.cmake -# Recursively resolves and copies all MinGW DLL dependencies for a target executable. -# Also deploys Qt6 plugins including TLS backends required for SSL/HTTPS. -# Usage: copy_mingw_deps(target_name) - -function(copy_mingw_deps target) - set(options "") - set(oneValueArgs "") - set(multiValueArgs SEARCH_PATHS) - cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - +# CopyMinGWDeps.cmake +# Recursively resolves and copies all MinGW DLL dependencies for a target executable. +# Also deploys Qt6 plugins including TLS backends required for SSL/HTTPS. +# Usage: copy_mingw_deps(target_name) + +function(copy_mingw_deps target) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SEARCH_PATHS) + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + # Prefer llvm-readobj from the compiler toolchain. - get_filename_component(COMPILER_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) - find_program(READOBJ_EXECUTABLE NAMES llvm-readobj llvm-readobj-19 llvm-readobj-18 llvm-readobj-17 - HINTS "${COMPILER_BIN_DIR}") - - if (READOBJ_EXECUTABLE) - set(DUMP_TOOL "${READOBJ_EXECUTABLE}") - set(DUMP_MODE "READOBJ") - else() - find_program(OBJDUMP_EXECUTABLE NAMES objdump x86_64-w64-mingw32-objdump - HINTS "${COMPILER_BIN_DIR}") - if (OBJDUMP_EXECUTABLE) - set(DUMP_TOOL "${OBJDUMP_EXECUTABLE}") - set(DUMP_MODE "OBJDUMP") - else() - message(WARNING \"Neither llvm-readobj nor objdump found. MinGW DLL deployment may fail.\") - return() - endif() - endif() - + get_filename_component(COMPILER_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + find_program(READOBJ_EXECUTABLE NAMES llvm-readobj llvm-readobj-19 llvm-readobj-18 llvm-readobj-17 + HINTS "${COMPILER_BIN_DIR}") + + if (READOBJ_EXECUTABLE) + set(DUMP_TOOL "${READOBJ_EXECUTABLE}") + set(DUMP_MODE "READOBJ") + else() + find_program(OBJDUMP_EXECUTABLE NAMES objdump x86_64-w64-mingw32-objdump + HINTS "${COMPILER_BIN_DIR}") + if (OBJDUMP_EXECUTABLE) + set(DUMP_TOOL "${OBJDUMP_EXECUTABLE}") + set(DUMP_MODE "OBJDUMP") + else() + message(WARNING \"Neither llvm-readobj nor objdump found. MinGW DLL deployment may fail.\") + return() + endif() + endif() + # Prefer bundled DLL locations before the toolchain bin dir. - set(MINGW_SEARCH_PATHS "") - if (ARG_SEARCH_PATHS) - list(APPEND MINGW_SEARCH_PATHS ${ARG_SEARCH_PATHS}) - endif() - + set(MINGW_SEARCH_PATHS "") + if (ARG_SEARCH_PATHS) + list(APPEND MINGW_SEARCH_PATHS ${ARG_SEARCH_PATHS}) + endif() + # Add Qt runtime bin if available. if (QT_TARGET_PATH AND EXISTS "${QT_TARGET_PATH}/bin") list(APPEND MINGW_SEARCH_PATHS "${QT_TARGET_PATH}/bin") @@ -63,14 +63,14 @@ function(copy_mingw_deps target) if (CMAKE_CROSSCOMPILING AND CMAKE_FIND_ROOT_PATH) list(APPEND MINGW_SEARCH_PATHS "${CMAKE_FIND_ROOT_PATH}/bin") endif() - - list(REMOVE_DUPLICATES MINGW_SEARCH_PATHS) - - set(DEPLOY_SCRIPT "${CMAKE_BINARY_DIR}/deploy_mingw_deps_${target}.cmake") - file(WRITE "${DEPLOY_SCRIPT}" " -# Auto-generated MinGW DLL deployment script -set(DUMP_TOOL \"${DUMP_TOOL}\") -set(DUMP_MODE \"${DUMP_MODE}\") + + list(REMOVE_DUPLICATES MINGW_SEARCH_PATHS) + + set(DEPLOY_SCRIPT "${CMAKE_BINARY_DIR}/deploy_mingw_deps_${target}.cmake") + file(WRITE "${DEPLOY_SCRIPT}" " +# Auto-generated MinGW DLL deployment script +set(DUMP_TOOL \"${DUMP_TOOL}\") +set(DUMP_MODE \"${DUMP_MODE}\") # Search local bin first. set(SEARCH_PATHS \"\${EXE_DIR};${MINGW_SEARCH_PATHS}\") set(EXE_DIR \"\${TARGET_DIR}\") @@ -78,10 +78,10 @@ set(TARGET_FILE \"\${TARGET_FILE}\") set(COMPILER_BIN_DIR \"${COMPILER_BIN_DIR}\") set(TOOLCHAIN_RUNTIME_PATHS \"${TOOLCHAIN_RUNTIME_PATHS}\") set(QT_TARGET_PLUGIN_DIR \"${QT_TARGET_PATH}/plugins\") - + # 1. Deploy whitelisted Qt6 plugins. -set(QT_PLATFORMS_PLUGINS qdirect2d qminimal qoffscreen qwindows) -set(QT_STYLES_PLUGINS qmodernwindowsstyle qwindowsvistastyle) +set(QT_PLATFORMS_PLUGINS qdirect2d qminimal qoffscreen qwindows) +set(QT_STYLES_PLUGINS qmodernwindowsstyle qwindowsvistastyle) set(QT_IMAGEFORMATS_PLUGINS qgif qicns qico qjpeg qsvg qtga qtiff qwbmp qwebp) set(QT_ICONENGINES_PLUGINS qsvgicon) set(QT_TLS_PLUGINS qcertonlybackend qopensslbackend qschannelbackend) @@ -98,7 +98,7 @@ set(ALLOWED_COMPILER_BIN_DLLS libva.dll libva_win32.dll zlib1.dll) - + set(QT_PLUGIN_BASE \"\${QT_TARGET_PLUGIN_DIR}\") if (NOT EXISTS \"\${QT_PLUGIN_BASE}\") set(QT_PLUGIN_BASE \"${Qt6_DIR}/../../../plugins\") @@ -106,72 +106,75 @@ endif() if (NOT EXISTS \"\${QT_PLUGIN_BASE}\") set(QT_PLUGIN_BASE \"\${COMPILER_BIN_DIR}/../share/qt6/plugins\") endif() - -if (EXISTS \"\${QT_PLUGIN_BASE}\") - set(PLUGIN_SUBDIRS platforms styles imageformats iconengines tls) - foreach(subdir \${PLUGIN_SUBDIRS}) - if (EXISTS \"\${QT_PLUGIN_BASE}/\${subdir}\") - file(MAKE_DIRECTORY \"\${EXE_DIR}/\${subdir}\") - + +if (EXISTS \"\${QT_PLUGIN_BASE}\") + set(PLUGIN_SUBDIRS platforms styles imageformats iconengines tls) + foreach(subdir \${PLUGIN_SUBDIRS}) + if (EXISTS \"\${QT_PLUGIN_BASE}/\${subdir}\") + file(MAKE_DIRECTORY \"\${EXE_DIR}/\${subdir}\") + # Use the matching whitelist for this plugin dir. - string(TOUPPER \"\${subdir}\" subdir_upper) - set(whitelist \${QT_\${subdir_upper}_PLUGINS}) - - set(pcount 0) - foreach(plugin_name \${whitelist}) - set(plugin_path \"\${QT_PLUGIN_BASE}/\${subdir}/\${plugin_name}.dll\") - if (EXISTS \"\${plugin_path}\") - file(COPY \"\${plugin_path}\" DESTINATION \"\${EXE_DIR}/\${subdir}\") - math(EXPR pcount \"\${pcount} + 1\") - endif() - endforeach() - - if (pcount GREATER 0) - message(STATUS \" Deployed \${pcount} whitelist plugin(s) to \${subdir}/\") - endif() - endif() - endforeach() + string(TOUPPER \"\${subdir}\" subdir_upper) + set(whitelist \${QT_\${subdir_upper}_PLUGINS}) + + set(pcount 0) + foreach(plugin_name \${whitelist}) + set(plugin_path \"\${QT_PLUGIN_BASE}/\${subdir}/\${plugin_name}.dll\") + if (EXISTS \"\${plugin_path}\") + file(COPY \"\${plugin_path}\" DESTINATION \"\${EXE_DIR}/\${subdir}\") + math(EXPR pcount \"\${pcount} + 1\") + endif() + endforeach() + + if (pcount GREATER 0) + message(STATUS \" Deployed \${pcount} whitelist plugin(s) to \${subdir}/\") + endif() + endif() + endforeach() endif() file(WRITE \"\${EXE_DIR}/qt.conf\" \"[Paths]\nPlugins = .\n\") # 2. Recursively collect all DLL dependencies function(resolve_deps file visited_var deps_var) - if (DUMP_MODE STREQUAL \"READOBJ\") - execute_process( - COMMAND \${DUMP_TOOL} --coff-imports \"\${file}\" - OUTPUT_VARIABLE dump_out - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - string(REGEX MATCHALL \"Name: [^\\r\\n]+\" dll_entries \"\${dump_out}\") - set(REGEX_REPLACE \"Name: +\") - else() - execute_process( - COMMAND \${DUMP_TOOL} -p \"\${file}\" - OUTPUT_VARIABLE dump_out - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - string(REGEX MATCHALL \"DLL Name: [^\\r\\n]+\" dll_entries \"\${dump_out}\") - set(REGEX_REPLACE \"DLL Name: +\") - endif() - - foreach(entry \${dll_entries}) - string(REGEX REPLACE \"\${REGEX_REPLACE}\" \"\" dll_name \"\${entry}\") - string(STRIP \"\${dll_name}\" dll_name) - string(REGEX REPLACE \"[^ -~]\" \"\" dll_name \"\${dll_name}\") - string(TOLOWER \"\${dll_name}\" dll_name_lower) - list(FIND \${visited_var} \"\${dll_name_lower}\" idx) - if (NOT idx EQUAL -1) - continue() - endif() - + if (DUMP_MODE STREQUAL \"READOBJ\") + execute_process( + COMMAND \${DUMP_TOOL} --coff-imports \"\${file}\" + OUTPUT_VARIABLE dump_out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + string(REGEX MATCHALL \"Name: [^\\r\\n]+\" dll_entries \"\${dump_out}\") + set(REGEX_REPLACE \"Name: +\") + else() + execute_process( + COMMAND \${DUMP_TOOL} -p \"\${file}\" + OUTPUT_VARIABLE dump_out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + string(REGEX MATCHALL \"DLL Name: [^\\r\\n]+\" dll_entries \"\${dump_out}\") + set(REGEX_REPLACE \"DLL Name: +\") + endif() + + foreach(entry \${dll_entries}) + string(REGEX REPLACE \"\${REGEX_REPLACE}\" \"\" dll_name \"\${entry}\") + string(STRIP \"\${dll_name}\" dll_name) + string(REGEX REPLACE \"[^ -~]\" \"\" dll_name \"\${dll_name}\") + string(TOLOWER \"\${dll_name}\" dll_name_lower) + list(FIND \${visited_var} \"\${dll_name_lower}\" idx) + if (NOT idx EQUAL -1) + continue() + endif() + # Search all candidate paths in order. - set(dll_path \"\${dll_name}-NOTFOUND\") + set(dll_path \"\${dll_name}-NOTFOUND\") foreach(search_path \${SEARCH_PATHS}) # Skip host MSYS2 DLLs except the allowed leaves and toolchain runtimes. - if (\"\${search_path}\" MATCHES \"([/\\\\])msys64([/\\\\])\") + # Match only known MSYS2 system subdirectories, not user home paths such as + # msys64/home//.cache/cpm which would incorrectly filter Qt DLLs + # when CPM_SOURCE_CACHE lives under the default MSYS2 home directory. + if (\"\${search_path}\" MATCHES \"([/\\\\])msys64([/\\\\])(usr|clang64|ucrt64|mingw64|mingw32)([/\\\\])\") list(FIND TOOLCHAIN_RUNTIME_DLLS \"\${dll_name_lower}\" toolchain_runtime_idx) if (NOT toolchain_runtime_idx EQUAL -1) # Keep Clang runtimes from the active toolchain. @@ -182,32 +185,32 @@ function(resolve_deps file visited_var deps_var) endif() endif() endif() - - if (EXISTS \"\${search_path}/\${dll_name}\") - set(dll_path \"\${search_path}/\${dll_name}\") - break() - endif() - endforeach() - - if (EXISTS \"\${dll_path}\" AND NOT IS_DIRECTORY \"\${dll_path}\") - list(APPEND \${visited_var} \"\${dll_name_lower}\") - list(APPEND \${deps_var} \"\${dll_path}\") - + + if (EXISTS \"\${search_path}/\${dll_name}\") + set(dll_path \"\${search_path}/\${dll_name}\") + break() + endif() + endforeach() + + if (EXISTS \"\${dll_path}\" AND NOT IS_DIRECTORY \"\${dll_path}\") + list(APPEND \${visited_var} \"\${dll_name_lower}\") + list(APPEND \${deps_var} \"\${dll_path}\") + # Log the chosen path for the DLL. - message(STATUS \" Found \${dll_name} in: \${dll_path}\") - - resolve_deps(\"\${dll_path}\" \${visited_var} \${deps_var}) + message(STATUS \" Found \${dll_name} in: \${dll_path}\") + + resolve_deps(\"\${dll_path}\" \${visited_var} \${deps_var}) elseif (NOT \"\${dll_name_lower}\" MATCHES \"^(advapi32|authz|avrt|bcrypt|cfgmgr32|comctl32|comdlg32|crypt32|cryptui|d2d1|d3d9|d3d11|d3d12|dbghelp|dnsapi|dsound|dwmapi|dxgi|dwrite|ext-ms-win-|gdi32|hid|imm32|iphlpapi|kernel32|mpr|msvcp_win|mswsock|ncrypt|netapi32|ntdll|ole32|oleaut32|onecore|powrprof|secur32|setupapi|shcore|shell32|shlwapi|user32|userenv|uxtheme|version|winhttp|wininet|winmm|winspool|wintrust|wtsapi32|ws2_32|api-ms-win-).*[.]dll$\") message(STATUS \" Warning: Could not find DLL: \${dll_name}\") endif() - endforeach() - set(\${visited_var} \${\${visited_var}} PARENT_SCOPE) - set(\${deps_var} \${\${deps_var}} PARENT_SCOPE) -endfunction() - -set(visited \"\") -set(all_deps \"\") - + endforeach() + set(\${visited_var} \${\${visited_var}} PARENT_SCOPE) + set(\${deps_var} \${\${deps_var}} PARENT_SCOPE) +endfunction() + +set(visited \"\") +set(all_deps \"\") + # 2.3 Pin core Clang runtimes first. foreach(runtime libc++.dll libunwind.dll libgcc_s_seh-1.dll libstdc++-6.dll) set(runtime_search_paths \"\${TOOLCHAIN_RUNTIME_PATHS};\${SEARCH_PATHS}\") @@ -228,37 +231,37 @@ endforeach() # Resolve for the targeted executable resolve_deps(\"\${EXE_DIR}/\${TARGET_FILE}\" visited all_deps) - + # Resolve only the plugins we deployed above. -foreach(subdir platforms styles imageformats iconengines tls) - if (EXISTS \"\${EXE_DIR}/\${subdir}\") - file(GLOB plugin_dlls \"\${EXE_DIR}/\${subdir}/*.dll\") - foreach(dll \${plugin_dlls}) - resolve_deps(\"\${dll}\" visited all_deps) - endforeach() - endif() -endforeach() - -# 3. Deploy everything +foreach(subdir platforms styles imageformats iconengines tls) + if (EXISTS \"\${EXE_DIR}/\${subdir}\") + file(GLOB plugin_dlls \"\${EXE_DIR}/\${subdir}/*.dll\") + foreach(dll \${plugin_dlls}) + resolve_deps(\"\${dll}\" visited all_deps) + endforeach() + endif() +endforeach() + +# 3. Deploy everything if (all_deps) list(REMOVE_DUPLICATES all_deps) list(LENGTH all_deps dep_count) message(STATUS \"Deploying \${dep_count} MinGW DLL(s) to \${EXE_DIR}\") - - set(files_to_copy \"\") - foreach(dll \${all_deps}) - list(APPEND files_to_copy \"\${dll}\") - endforeach() - - if (files_to_copy) - list(LENGTH files_to_copy copy_count) - message(STATUS \" Copying \${copy_count} missing DLL(s)...\") - foreach(f \${files_to_copy}) - get_filename_component(fn \"\${f}\" NAME) - message(STATUS \" -> \${fn}\") - file(COPY \"\${f}\" DESTINATION \"\${EXE_DIR}\") - endforeach() - message(STATUS \" Deployment complete.\") + + set(files_to_copy \"\") + foreach(dll \${all_deps}) + list(APPEND files_to_copy \"\${dll}\") + endforeach() + + if (files_to_copy) + list(LENGTH files_to_copy copy_count) + message(STATUS \" Copying \${copy_count} missing DLL(s)...\") + foreach(f \${files_to_copy}) + get_filename_component(fn \"\${f}\" NAME) + message(STATUS \" -> \${fn}\") + file(COPY \"\${f}\" DESTINATION \"\${EXE_DIR}\") + endforeach() + message(STATUS \" Deployment complete.\") else() message(STATUS \" All DLLs are already up to date.\") endif() @@ -281,7 +284,7 @@ if (all_deps) endforeach() endif() ") - + add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} "-DTARGET_DIR=$" diff --git a/build-clangtron-windows.sh b/build-clangtron-windows.sh index b39aea088a..cb2d15d238 100755 --- a/build-clangtron-windows.sh +++ b/build-clangtron-windows.sh @@ -379,6 +379,33 @@ if [[ "${CPM_SOURCE_CACHE}" == *" "* ]]; then " export CPM_SOURCE_CACHE=\"/tmp/cpm-cache\"" fi +# download_with_retry URL OUTPUT_FILE [MAX_RETRIES] +# Downloads URL to OUTPUT_FILE using wget, retrying on failure with +# exponential back-off (5 s → 10 s → 20 s …). +# Returns 0 on success, 1 after all attempts are exhausted. +download_with_retry() { + local url="$1" + local output="$2" + local max_retries="${3:-3}" + local attempt=1 + local delay=5 + + while [[ "${attempt}" -le "${max_retries}" ]]; do + if [[ "${attempt}" -gt 1 ]]; then + warn "Download retry ${attempt}/${max_retries} (waiting ${delay}s): $(basename "${output}")" + sleep "${delay}" + delay=$(( delay * 2 )) + fi + if wget -q --show-progress --timeout=60 --tries=1 \ + -O "${output}" "${url}" 2>&1; then + return 0 + fi + rm -f "${output}" + attempt=$(( attempt + 1 )) + done + return 1 +} + # _sudo — portable sudo wrapper. # On Windows/MSYS2, sudo is unavailable; run privileged commands directly. # On Linux, delegate to the real sudo as usual. @@ -555,30 +582,16 @@ build_bolt_from_source() { local bolt_tag="" local install_dir="/usr/local/bin" - # Probe candidate tags in order: X.0.0 → X.1.0 → X.1.1 → … → X.1.9 - # LLVM releases often skip X.0.0 and go straight to X.1.0 for final releases. - # Point releases are frequent — LLVM 21 reached 21.1.7. We probe the full - # range and take the latest confirmed tag so this stays correct automatically. + # Probe for the latest point-release tag with a single ls-remote call. + # Fetches all refs/tags/llvmorg-.* in one round-trip, then picks + # the highest version with sort -V. local found_tag="" - for _candidate in \ - "llvmorg-${CLANG_VERSION}.0.0" \ - "llvmorg-${CLANG_VERSION}.1.0" \ - "llvmorg-${CLANG_VERSION}.1.1" \ - "llvmorg-${CLANG_VERSION}.1.2" \ - "llvmorg-${CLANG_VERSION}.1.3" \ - "llvmorg-${CLANG_VERSION}.1.4" \ - "llvmorg-${CLANG_VERSION}.1.5" \ - "llvmorg-${CLANG_VERSION}.1.6" \ - "llvmorg-${CLANG_VERSION}.1.7" \ - "llvmorg-${CLANG_VERSION}.1.8" \ - "llvmorg-${CLANG_VERSION}.1.9"; do - info "Checking for LLVM tag ${_candidate}..." - if git ls-remote --tags https://github.com/llvm/llvm-project.git "${_candidate}" \ - 2>/dev/null | grep -q "${_candidate}"; then - found_tag="${_candidate}" - # Keep probing — we want the latest point-release tag - fi - done + local _all_tags + _all_tags="$(git ls-remote --tags https://github.com/llvm/llvm-project.git \ + "refs/tags/llvmorg-${CLANG_VERSION}.*" 2>/dev/null || true)" + found_tag="$(printf '%s\n' "${_all_tags}" \ + | grep -o "llvmorg-${CLANG_VERSION}\.[0-9][0-9.]*" \ + | sort -V | tail -1 || true)" if [[ -z "${found_tag}" ]]; then error "Could not find any LLVM ${CLANG_VERSION} release tag on GitHub.\n" \ @@ -1671,14 +1684,20 @@ rebuild_ffmpeg_pthread_free() { local ffmpeg_src="" - # Priority 1: previously downloaded/extracted source tree - if [[ -f "${ffmpeg_src_dir}/configure" ]]; then + # Priority 1: previously downloaded/extracted source tree. + # Requires the .ffmpeg_src_ready sentinel written after a clean extraction — + # a directory with configure but no sentinel means a partial/interrupted + # extraction from a prior run and must not be reused. + if [[ -f "${ffmpeg_src_dir}/.ffmpeg_src_ready" && -f "${ffmpeg_src_dir}/configure" ]]; then if _ffmpeg_abi_matches "${ffmpeg_src_dir}"; then ffmpeg_src="${ffmpeg_src_dir}" info "[ffmpeg-rebuild] Using cached FFmpeg ${FFMPEG_VERSION} source" else warn "[ffmpeg-rebuild] Cached source ABI does not match FFmpeg ${FFMPEG_VERSION} — ignoring" fi + elif [[ -d "${ffmpeg_src_dir}" ]]; then + warn "[ffmpeg-rebuild] Cached source dir exists but lacks .ffmpeg_src_ready sentinel — likely a partial extraction. Wiping and re-downloading." + rm -rf "${ffmpeg_src_dir}" fi # Priority 2: vendored submodule (only if sonames match) @@ -1709,16 +1728,19 @@ rebuild_ffmpeg_pthread_free() { local ffmpeg_url="https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2" info "[ffmpeg-rebuild] Downloading FFmpeg ${FFMPEG_VERSION} source from ffmpeg.org..." mkdir -p "${BUILD_ROOT}" - if ! wget -q --show-progress -O "${tarball}" "${ffmpeg_url}"; then - warn "[ffmpeg-rebuild] Download failed — FFmpeg DLLs will NOT be built (cmake will fail)" - return 0 - fi + download_with_retry "${ffmpeg_url}" "${tarball}" 3 \ + || error "[ffmpeg-rebuild] Failed to download FFmpeg ${FFMPEG_VERSION} after 3 attempts. + URL: ${ffmpeg_url} + Check network connectivity or set CPM_SOURCE_CACHE to a pre-populated directory." info "[ffmpeg-rebuild] Extracting FFmpeg ${FFMPEG_VERSION}..." mkdir -p "${ffmpeg_src_dir}" - tar -xjf "${tarball}" -C "${ffmpeg_src_dir}" --strip-components=1 || { - warn "[ffmpeg-rebuild] Extraction failed" - return 0 - } + info "[ffmpeg-rebuild] Verifying tarball integrity..." + tar -tjf "${tarball}" > /dev/null 2>&1 \ + || error "[ffmpeg-rebuild] Tarball integrity check failed — download is corrupt. + Delete ${tarball} and retry." + tar -xjf "${tarball}" -C "${ffmpeg_src_dir}" --strip-components=1 \ + || error "[ffmpeg-rebuild] Extraction failed — tarball may be corrupt. Delete ${tarball} and retry." + touch "${ffmpeg_src_dir}/.ffmpeg_src_ready" ffmpeg_src="${ffmpeg_src_dir}" success "[ffmpeg-rebuild] FFmpeg ${FFMPEG_VERSION} source ready" fi @@ -1843,8 +1865,9 @@ rebuild_ffmpeg_pthread_free() { done if [[ "${installed}" -eq 0 ]]; then - warn "[ffmpeg-rebuild] No static libs installed — FFmpeg configure/make may have failed" - return 0 + error "[ffmpeg-rebuild] No static libs were installed after make — FFmpeg build silently produced nothing. + Check make output above for configuration errors. + Try removing ${ffmpeg_bld} and re-running." fi # ── Verify: static libs must NOT depend on libwinpthread ───────────────── @@ -2105,13 +2128,23 @@ build_common_cmake_args() { "-DVulkan_GLSLC_EXECUTABLE=${GLSLC_PATH}" "-DVulkan_GLSLANG_VALIDATOR_EXECUTABLE=${GLSLC_PATH}" ) - # Static FFmpeg dir (set by rebuild_ffmpeg_pthread_free; may not exist yet during first cmake args call) + # Static FFmpeg dir — only passed when the sentinel confirms rebuild_ffmpeg_pthread_free + # completed successfully. Passing a nonexistent dir causes CMake to fall through to + # the legacy RELEASE-file path and then immediately fail with a fatal error. if [[ -n "${FFMPEG_VERSION:-}" ]]; then - local _ffmpeg_static="${CMAKE_BUILD_ROOT}/externals/ffmpeg-${FFMPEG_VERSION}-static" - if [[ "${_HOST_OS}" == "windows" ]]; then - _ffmpeg_static="$(cygpath -m "${BUILD_ROOT}/externals/ffmpeg-${FFMPEG_VERSION}-static")" + local _ffmpeg_ext_dir="${BUILD_ROOT}/externals/ffmpeg-${FFMPEG_VERSION}-static" + local _ffmpeg_sentinel="${_ffmpeg_ext_dir}/lib/.llvm_static_built" + if [[ -f "${_ffmpeg_sentinel}" ]]; then + local _ffmpeg_static="${_ffmpeg_ext_dir}" + if [[ "${_HOST_OS}" == "windows" ]]; then + _ffmpeg_static="$(cygpath -m "${_ffmpeg_ext_dir}")" + fi + _CMAKE_ARGS+=("-DCITRON_FFMPEG_STATIC_DIR=${_ffmpeg_static}") + else + error "FFmpeg static libs not ready — sentinel missing: ${_ffmpeg_sentinel} + rebuild_ffmpeg_pthread_free() must complete successfully before cmake is invoked. + If this is unexpected, delete ${_ffmpeg_ext_dir} and re-run." fi - _CMAKE_ARGS+=("-DCITRON_FFMPEG_STATIC_DIR=${_ffmpeg_static}") fi [[ -n "${CITRON_BUILD_TYPE:-}" ]] && _CMAKE_ARGS+=("-DCITRON_BUILD_TYPE=${CITRON_BUILD_TYPE}") [[ "${UNITY_BUILD}" == "ON" ]] && _CMAKE_ARGS+=("-DENABLE_UNITY_BUILD=ON") @@ -2395,8 +2428,8 @@ stage_csgenerate() { local _gen_cfg="${BUILD_ROOT}/.citron-gen-config" if [[ -f "${_gen_cfg}" ]]; then local _gen_lto _gen_pgo - _gen_lto=$(grep -oP "(?<=LTO=)\S+" "${_gen_cfg}" || true) - _gen_pgo=$(grep -oP "(?<=PGO=)\S+" "${_gen_cfg}" || true) + _gen_lto=$(awk -F= '/^LTO=/{print $2; exit}' "${_gen_cfg}" 2>/dev/null || true) + _gen_pgo=$(awk -F= '/^PGO=/{print $2; exit}' "${_gen_cfg}" 2>/dev/null || true) if [[ -n "${_gen_lto}" && "${_gen_lto}" != "${LTO_MODE}" ]]; then error "LTO mismatch: generate used LTO=${_gen_lto}, csgenerate has LTO=${LTO_MODE}.\n"\ " IR PGO profiles are tied to the IR produced at generate time.\n"\ @@ -2841,8 +2874,8 @@ stage_use() { local _gen_cfg="${BUILD_ROOT}/.citron-gen-config" if [[ -f "${_gen_cfg}" ]]; then local _gen_lto _gen_pgo - _gen_lto=$(grep -oP "(?<=LTO=)\S+" "${_gen_cfg}" || true) - _gen_pgo=$(grep -oP "(?<=PGO=)\S+" "${_gen_cfg}" || true) + _gen_lto=$(awk -F= '/^LTO=/{print $2; exit}' "${_gen_cfg}" 2>/dev/null || true) + _gen_pgo=$(awk -F= '/^PGO=/{print $2; exit}' "${_gen_cfg}" 2>/dev/null || true) if [[ -n "${_gen_lto}" && "${_gen_lto}" != "${LTO_MODE}" ]]; then error "LTO mismatch: generate used LTO=${_gen_lto}, use has LTO=${LTO_MODE}.\n"\ " IR PGO profiles are tied to the IR produced at generate time.\n"\