未感染者模拟器
62.73M · 2026-04-13
这是一篇“从报错到根因,再到稳定落地方案”的复盘文章,记录我在 UE 5.6 项目里编译 Cesium for Unreal(含 cesium-native 外部依赖)时遇到的一组常见但定位成本较高的问题:vcpkg 拉取失败(网络/代理/超时)、CMake 3.31 解压安全策略、vcpkg/ezvcpkg 的解压链路替换、以及 CMake/VS 工具集版本约束等。
如果你正在 Windows + UE 5.6 + VS2022 下编译 Cesium 插件外部依赖,这篇文章可以当作一份可复用的排查路径。
UE 5.6 基本上要求 VS2022,并且常见项目会锁定到 MSVC 14.38.*(例如 14.38.33130)。如果你的机器装了多个 v143 工具集版本,必须保证:
否则后续极易出现:
当你通过 ezvcpkg/vcpkg 构建外部依赖时,vcpkg 会根据 scripts/vcpkg-tools.json 指定的版本选择并获取工具(包括 CMake)。例如这份 vcpkg 明确写死了 cmake 3.30.1,低于该版本时会尝试下载工具包:
D:ttzx.ezvcpkg2025.09.17scriptsvcpkg-tools.json 里的 cmake 条目(示例路径)这就是为什么把 CMake 降到 3.23.x 往往治不了根因,反而可能触发 vcpkg 下载自己的 CMake,增加变量。
Cesium 的 extern 构建链路里,vcpkg 是最重要的前置条件之一。如果 vcpkg 仓库本体都拉不下来,后面解压、编译都无从谈起。
常见的失败形态包括:
git@github.com:... 的 SSH 拉取)WinHttpSendRequest failed with exit code 12002. 操作超时Downloading ... 长时间卡住后失败这类问题本质是网络可达性/代理配置问题。优先确认:
HTTP_PROXY / HTTPS_PROXY)如果你所在网络环境对 GitHub/SSH 不稳定,最实用的方式是先把 vcpkg 的“仓库本体”以 zip 方式落地,再继续跑后续流程。
提供了一个“手动下载 vcpkg 指南”脚本:
Plugins/cesium-unreal/script-Q/download-vcpkg-manual.ps1建议先把这一关单独跑通(确认 vcpkg 目录已存在且完整)再进入下一关。
外部依赖在解压阶段崩掉,日志里会出现类似:
.../tests/srcimages/テクスチャ.png: Invalid empty pathnamecmake -E tar / tar.exe 解压失败这类问题的特点是:
-- Extracting source ...这不是 “KTX 代码有问题”,而是 “解压链路”出了问题:
cmake -E tar ...核心思路不是“只修 KTX”,而是把 vcpkg 的解压入口统一替换为我们可控的解压器。
我实际做的改动就三件事(都在同一个入口里完成):
EZVCPKG_PATCH_VCPKG_EXTRACTION
文件:Plugins/cesium-unreal/extern/cesium-native/cmake/ezvcpkg/ezvcpkg.cmakeezvcpkg-tar.ps1(写到 vcpkg 目录根下)
目的:避免 vcpkg 走 cmake -E tar,同时保证脚本更新不会被缓存“卡住”。cmake -E tar ... 相关调用替换成调用 ezvcpkg-tar.ps1
覆盖写法:${CMAKE_COMMAND} -E tar / "${CMAKE_COMMAND}" -E tar / cmake -E tar。ezvcpkg-tar.ps1 的解压策略(按压缩包后缀判断):
.zip:先用 .NET (System.IO.Compression.ZipFile) 解压;失败则继续走 tar 兜底.tar.gz/.tgz/.tar.*:用 Python tarfile 解压,并跳过 tests/srcimages、tests/testimages(KTX 的雷点就在这里)tar -xf 兜底,同时带 --exclude 跳过上述 tests 目录,并把错误输出回传到 vcpkg 日志下面这段代码来自:
Plugins/cesium-unreal/extern/cesium-native/cmake/ezvcpkg/ezvcpkg.cmake核心点是:生成 ezvcpkg-tar.ps1,并扫描替换 vcpkg scripts 内的 cmake -E tar 调用。
macro(EZVCPKG_PATCH_VCPKG_EXTRACTION)
if (NOT CMAKE_HOST_WIN32)
return()
endif()
set(EZVCPKG_TAR_WRAPPER "${EZVCPKG_DIR}/ezvcpkg-tar.ps1")
file(WRITE "${EZVCPKG_TAR_WRAPPER}" [=[
param(
[Parameter(Mandatory = $true)][string]$Mode,
[Parameter(Mandatory = $true)][string]$Archive
)
$ErrorActionPreference = "Stop"
$ArchivePath = (Resolve-Path -LiteralPath $Archive).Path
$Destination = (Get-Location).Path
if ($ArchivePath.ToLowerInvariant().EndsWith(".zip"))
{
try
{
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ArchivePath)
try
{
foreach ($Entry in $Zip.Entries)
{
$TargetPath = Join-Path $Destination $Entry.FullName
if ([string]::IsNullOrEmpty($Entry.Name))
{
[System.IO.Directory]::CreateDirectory($TargetPath) | Out-Null
continue
}
$TargetDir = [System.IO.Path]::GetDirectoryName($TargetPath)
if (-not [string]::IsNullOrEmpty($TargetDir))
{
[System.IO.Directory]::CreateDirectory($TargetDir) | Out-Null
}
if ([System.IO.File]::Exists($TargetPath))
{
[System.IO.File]::Delete($TargetPath)
}
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($Entry, $TargetPath)
}
}
finally
{
$Zip.Dispose()
}
exit 0
}
catch
{
}
}
$ArchiveLower = $ArchivePath.ToLowerInvariant()
if ($ArchiveLower.EndsWith(".tar.gz") -or $ArchiveLower.EndsWith(".tgz") -or $ArchiveLower.EndsWith(".tar") -or $ArchiveLower.EndsWith(".tar.bz2") -or $ArchiveLower.EndsWith(".tbz2") -or $ArchiveLower.EndsWith(".tar.xz") -or $ArchiveLower.EndsWith(".txz"))
{
$PyTemplate = @'
import tarfile
archive_path = r'{0}'
dest_dir = r'{1}'
def should_skip(name: str) -> bool:
n = name.replace('\\', '/').lower()
return '/tests/srcimages/' in n or '/tests/testimages/' in n
with tarfile.open(archive_path, 'r:*') as tf:
members = [m for m in tf.getmembers() if not should_skip(m.name)]
tf.extractall(dest_dir, members=members)
'@
$ArchiveEscaped = $ArchivePath.Replace("\", "\\")
$DestEscaped = $Destination.Replace("\", "\\")
$Py = $PyTemplate -f $ArchiveEscaped, $DestEscaped
$PreviousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
& py -3 -c $Py
if ($LASTEXITCODE -eq 0)
{
$ErrorActionPreference = $PreviousErrorActionPreference
exit 0
}
& python -c $Py
$ExitCode = $LASTEXITCODE
$ErrorActionPreference = $PreviousErrorActionPreference
exit $ExitCode
}
$PreviousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$TarOutput = & tar --exclude="*/tests/srcimages/*" --exclude="*/tests/testimages/*" -xf $ArchivePath 2>&1
$TarExitCode = $LASTEXITCODE
if ($TarExitCode -ne 0)
{
$TarOutput | ForEach-Object { Write-Output $_ }
}
$ErrorActionPreference = $PreviousErrorActionPreference
exit $TarExitCode
]=])
set(_ezvcpkg_script_roots
"${EZVCPKG_DIR}/scripts"
"${EZVCPKG_DIR}/scripts/cmake"
)
foreach(_ezvcpkg_script_root IN LISTS _ezvcpkg_script_roots)
if (EXISTS "${_ezvcpkg_script_root}")
file(GLOB_RECURSE _ezvcpkg_cmake_files "${_ezvcpkg_script_root}/*.cmake")
foreach(_ezvcpkg_cmake_file IN LISTS _ezvcpkg_cmake_files)
file(READ "${_ezvcpkg_cmake_file}" _ezvcpkg_cmake_content)
if (_ezvcpkg_cmake_content MATCHES "ezvcpkg-tar\\.ps1")
continue()
endif()
set(_ezvcpkg_original_content "${_ezvcpkg_cmake_content}")
string(REPLACE "COMMAND \${CMAKE_COMMAND} -E tar" "COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File \"${EZVCPKG_TAR_WRAPPER}\"" _ezvcpkg_cmake_content "${_ezvcpkg_cmake_content}")
string(REPLACE "COMMAND \"\${CMAKE_COMMAND}\" -E tar" "COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File \"${EZVCPKG_TAR_WRAPPER}\"" _ezvcpkg_cmake_content "${_ezvcpkg_cmake_content}")
string(REPLACE "COMMAND cmake -E tar" "COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File \"${EZVCPKG_TAR_WRAPPER}\"" _ezvcpkg_cmake_content "${_ezvcpkg_cmake_content}")
if (NOT _ezvcpkg_cmake_content STREQUAL _ezvcpkg_original_content)
file(WRITE "${_ezvcpkg_cmake_file}" "${_ezvcpkg_cmake_content}")
endif()
endforeach()
endif()
endforeach()
endmacro()
以及在 EZVCPKG_BOOTSTRAP 里调用:
EZVCPKG_PATCH_VCPKG_EXTRACTION()
以 KTX 为例,触发问题的文件集中在:
tests/srcimages/*tests/testimages/*这些目录通常不参与库本体构建。过滤它们可以:
看 vcpkg 解压失败日志里的这一行:
Command failed: ...如果你看到:
Command failed: powershell ... -File .../ezvcpkg-tar.ps1 ...说明替换生效,vcpkg 已经不走 cmake -E tar。
如果你还看到:
Command failed: cmake -E tar ...说明替换没生效(常见原因:缓存目录未刷新,或替换扫描范围不足)。
不会。vcpkg_from_github(...) 里的 SHA512 是对“下载到的源码归档(zip/tarball)字节内容”的校验值:
REF/URL、上游重打 tag、或你本地对归档做了二次处理并替换了原文件。当使用较老 CMake(例如 3.23.5)并指定:
-T "version=14.38"可能会出现:
given toolset and version specification v143,version=14.38 does not seem to be installed at ...Microsoft.VCToolsVersion.14.38.props这类问题的本质是:老 CMake 对“toolset version overlay”的识别方式与当前 VS/Toolset 安装结构不一致。
所以更推荐:
建议按这个顺序推进,能最大化减少交叉干扰:
Plugins/cesium-unreal/script-Q/download-vcpkg-manual.ps1 先把仓库本体落地cmake -E tar 替换为 PowerShell 包装器tarfile 并过滤 KTX tests 资源Invalid empty pathname 就一定是 KTX 吗?不一定,但绝大概率是“解压链路 + Windows 路径名规则/编码”问题。先去看失败发生在 Extracting source 还是 Downloading。
很多时候是缓存(下载/解压)导致的“偶然成功”,并不代表根因解决。尤其当 vcpkg 需要下载工具或包时,网络可达性会导致结果不稳定。
这套问题定位成本高的原因在于:它通常不是单点 bug,而是一组工具链边界条件的叠加: