src/OutputCache.ps1

# 产物结果缓存(%TEMP%\ps12exe\cache\output)。
# 非常量 Framework 编译的内容寻址缓存:键由「最终脚本文本 + 序列化后的具名参数 + 编译器源码指纹」决定,
# 命中即直接拷贝产物、跳过 CodeDom/AsmResolver 等全部编译开销。产物内部程序集名固定为 output,运行时
# 行为只依赖 exe 路径/文件名(见 default.cs),因此键里不含输出名,同一份产物可跨输出名复用。
# 命中只读不写,新条目只来自真正编译,所以清理放在写入处即可覆盖增长来源。

# 缓存开关:Core 交给独立编译器;图标会下载/抽取/转换;写 config、KeepSource、代码签名、DllExports
# 都让产物或流程偏离这份缓存所能描述的输入。开关读取调用方(ps12exe 编译流程)的脚本级变量。
function Test-OutputCacheEnabled {
    return -not ($isCoreTarget -or $iconFile -or $configFile -or $prepareDebug -or $CodeSigning -or $DllExportList)
}

# 计算缓存键;缓存不可用(拿不到编译器源文件)返回 $null。
function Get-OutputCacheKey([string]$RepoRoot, [System.Collections.IDictionary]$BoundParams) {
    # 编译器源码指纹:ps12exe.ps1 + src 顶层脚本 + 程序帧 + 运行时芯任一变化即失效
    # (自编译产物拿不到源文件则返回 $null,直接禁用缓存)。
    $compilerFp = Get-CompilerSourceFingerprint $RepoRoot
    if (-not $compilerFp) { return $null }
    # 参数序列化:$BoundParams 是 ps12exe 收到的具名参数,已被预处理/pragma 改写,直接序列化它就覆盖了
    # 全部有效选项,不必逐项列内部规范变量。递归按键排序,保证同一组参数得到稳定字符串。
    function ConvertTo-CanonicalParam([object]$value) {
        if ($null -eq $value) { return '' }
        if ($value -is [System.Collections.IDictionary]) {
            $keys = @($value.Keys | ForEach-Object { "$_" } | Sort-Object)
            return '{' + (($keys | ForEach-Object { $_ + '=' + (ConvertTo-CanonicalParam $value[$_]) }) -join ';') + '}'
        }
        if ($value -is [System.Collections.IEnumerable] -and $value -isnot [string]) {
            return '[' + (($value | ForEach-Object { ConvertTo-CanonicalParam $_ }) -join ',') + ']'
        }
        return "$value"
    }
    $paramParts = @($BoundParams.Keys | Where-Object { $_ -ne 'inputFile' -and $_ -ne 'outputFile' } | Sort-Object | ForEach-Object {
        $_ + '=' + (ConvertTo-CanonicalParam $BoundParams[$_])
    })
    return (Get-TextHash (@(
                "content=$(Get-TextHash $Content)"
                "params=$($paramParts -join ';')"
                "compiler=$compilerFp"
            ) -join "`n"))
}

# 查缓存:命中则把产物写入 $OutputFile 并返回 $true,未命中返回 $false。
function Test-OutputCacheHit([string]$OutputFile, [string]$Key) {
    $cachedExe = Join-Path (Get-CacheRoot 'output') "$Key.exe"
    if (-not (Test-Path -LiteralPath $cachedExe)) { return $false }
    try {
        $cachedBytes = Get-CachedBytes $cachedExe
        [System.IO.File]::WriteAllBytes($OutputFile, $cachedBytes)
        Write-I18n Host CompiledFileSize $cachedBytes.Length
        Write-I18n Verbose OutputPath $OutputFile
        return $true
    }
    catch {
        Write-Debug "ps12exe output cache hit failed, recompiling: $_"
        Remove-Item -LiteralPath $OutputFile -Force -ErrorAction Ignore
        return $false
    }
}

# 编译成功且最终产物已落盘(含 ExeSinker/签名)后回填产物,并顺带清理长期未用的条目。
function Save-OutputCache([string]$Key, [string]$OutputFile) {
    if (-not $Key) { return }
    $dir = Get-CacheRoot 'output'
    try { Set-CachedBytes (Join-Path $dir "$Key.exe") ([System.IO.File]::ReadAllBytes($OutputFile)) }
    catch { Write-Debug "ps12exe output cache store failed: $_" }
    Clear-StaleCache $dir
}