src/CoreProject.ps1

# Shared(CoreCompiler.ps1)与 Bundled(CoreBundledCompiler.ps1)两个 Core 后端共用的 dotnet 工程辅助:
# 缓存工程目录 + 命名互斥量、统一的 dotnet 调用、产物拷贝,以及按脚本内容粗判 GUI 框架。

# 解析 dotnet 宿主;Windows PowerShell 下调用方应先交接给 pwsh。
function Get-CoreDotnet {
    $dotnet = Get-Command dotnet -ErrorAction Ignore
    if (-not $dotnet) {
        Write-I18n Error CoreCompileNeedDotnet -Category NotInstalled
        throw 'ps12exe:core-need-dotnet'
    }
    return $dotnet
}

# 把选项清单哈希成 24 位十六进制的缓存键。
function Get-CoreBuildKey([string[]]$Invariant) {
    $sha = [System.Security.Cryptography.SHA256]::Create()
    try {
        return [System.BitConverter]::ToString($sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Invariant -join "`n"))).Replace('-', '').Substring(0, 24)
    }
    finally { $sha.Dispose() }
}

# 进入某编译选项组合对应的缓存工程目录:命中即 touch,长期未用则清理;用命名互斥量串行化同一 key 的并发编译。
# 返回 @{ ProjectDir; Mutex; Locked };拿不到锁(超时/平台不支持)时退化为本次独占的临时目录。
function Enter-CoreProject {
    param([string]$CacheTag, [string]$BuildKey, [string]$TempDir, [int]$MutexTimeoutMs = 300000)
    $cacheRoot = Get-CacheRoot 'core'
    $projectDir = Join-Path $cacheRoot $BuildKey
    if (Test-Path -LiteralPath $projectDir) { (Get-Item -LiteralPath $projectDir).LastWriteTimeUtc = [DateTime]::UtcNow }
    Clear-StaleCache $cacheRoot

    $mutex = $null
    $locked = $false
    try {
        $mutex = [System.Threading.Mutex]::new($false, "ps12exe-$CacheTag-$BuildKey")
        $locked = $mutex.WaitOne($MutexTimeoutMs)
    }
    catch { $mutex = $null; $locked = $false }
    if (-not $locked) {
        $projectDir = Join-Path $TempDir "coreproj-$([Guid]::NewGuid().ToString('N'))"
        Remove-Item -LiteralPath $projectDir -Recurse -Force -ErrorAction Ignore
    }
    New-Item -ItemType Directory -Path $projectDir -Force | Out-Null
    return @{ ProjectDir = $projectDir; Mutex = $mutex; Locked = $locked }
}

# 释放 Enter-CoreProject 取得的互斥量。
function Exit-CoreProject($Project) {
    if (-not $Project.Mutex) { return }
    try { if ($Project.Locked) { $Project.Mutex.ReleaseMutex() } } catch {}
    $Project.Mutex.Dispose()
}

# 统一的 dotnet 调用:若 obj/project.assets.json 已存在(缓存命中)则加 --no-restore;
# 若 --no-restore 失败(obj 损坏 / SDK 升级)再退回完整还原重试一次。
function Invoke-CoreDotnet {
    param([string[]]$DotnetArgs, [string]$AssetsPath, [string]$DotnetPath)
    $useNoRestore = Test-Path -LiteralPath $AssetsPath
    $effective = if ($useNoRestore) { $DotnetArgs + '--no-restore' } else { $DotnetArgs }
    $output = & $DotnetPath @effective --nologo -v quiet 2>&1
    if ($LASTEXITCODE -ne 0 -and $useNoRestore) {
        $output = & $DotnetPath @DotnetArgs --nologo -v quiet 2>&1
    }
    if ($LASTEXITCODE -ne 0) { throw ($output -join "`n") }
    return $output
}

# 校验 publish 产物并把 exe(单文件)或整个发布目录(非单文件)拷到 $OutputFile,附带可选 pdb。
function Copy-CorePublishOutput {
    param([string]$PublishDir, [string]$AssemblyName, [string]$OutputFile, [bool]$SingleFile, [bool]$PrepareDebug)
    $publishedExe = Join-Path $PublishDir "$AssemblyName.exe"
    if (-not (Test-Path -LiteralPath $publishedExe)) {
        Write-I18n Error OutputFileNotWritten -Category WriteError
        throw 'ps12exe:core-no-output'
    }
    if ($SingleFile) {
        Copy-Item -LiteralPath $publishedExe -Destination $OutputFile -Force
    }
    else {
        # 非单文件:把整个 publish 目录(exe + deps/runtimeconfig 等)拷到 outputFile 所在目录。
        $outDir = Split-Path -Parent $OutputFile
        New-Item -ItemType Directory -Path $outDir -Force | Out-Null
        Copy-Item -Path (Join-Path $PublishDir '*') -Destination $outDir -Recurse -Force
        $copiedExe = Join-Path $outDir "$AssemblyName.exe"
        if ([System.IO.Path]::GetFullPath($copiedExe) -ne [System.IO.Path]::GetFullPath($OutputFile)) {
            Move-Item -LiteralPath $copiedExe -Destination $OutputFile -Force
        }
    }
    if ($PrepareDebug) {
        $publishedPdb = Join-Path $PublishDir "$AssemblyName.pdb"
        if (Test-Path -LiteralPath $publishedPdb) {
            Copy-Item -LiteralPath $publishedPdb -Destination ($OutputFile -replace '\.exe$', '.pdb') -Force
        }
    }
}

# 从脚本源码粗略识别 WinForms / WPF 的使用:Core 目标下 console 应用默认不引用 WindowsDesktop 框架,
# 命中时由编译器按需打开 UseWindowsForms / UseWPF(PS2EXE.Core #6)。宁可多开也不漏——误判只让产物大一点。
function Get-GuiFrameworkUsage([string]$Text) {
    $winForms = $Text -match '(?i)(?<![\w.])System\.Windows\.Forms(?!\w)|(?<![\w.])System\.Drawing(?!\w)'
    $wpf = $Text -match '(?i)(?<![\w.])System\.Windows\.(?!Forms)|(?<![\w.])PresentationFramework(?!\w)|(?<![\w.])PresentationCore(?!\w)|(?<![\w.])WindowsBase(?!\w)'
    return @{ WinForms = $winForms; Wpf = $wpf }
}