- 新增 Obfuscar 混淆发布脚本、发布测试与产物隐私检查 - 调整 VS Code 发布任务和宿主发布配置,统一走混淆流程 - 为 debug 页面内嵌静态接口说明,并补充对应集成测试 - 更新 README 与设计计划文档,记录跨平台发布约束
253 lines
9.0 KiB
PowerShell
253 lines
9.0 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
验证混淆发布脚本生成的 Obfuscar 配置满足仓库发布契约。
|
||
|
||
.DESCRIPTION
|
||
该测试使用临时空文件模拟发布目录,仅验证模块筛选、绝对路径和失败语义,
|
||
不会执行 dotnet publish,也不会要求测试环境安装 Obfuscar。
|
||
#>
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||
$publishScriptPath = Join-Path $repositoryRoot 'scripts\Publish-Obfuscated.ps1'
|
||
$publishProfilePath = Join-Path $repositoryRoot 'src\Flyshot.Server.Host\Properties\PublishProfiles\FolderProfile.pubxml'
|
||
$temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) "flyshot-obfuscation-test-$([guid]::NewGuid().ToString('N'))"
|
||
$inputDirectory = Join-Path $temporaryRoot 'input'
|
||
$outputDirectory = Join-Path $temporaryRoot 'obfuscated'
|
||
$configPath = Join-Path $temporaryRoot 'obfuscar.xml'
|
||
|
||
$expectedAssemblies = @(
|
||
'Flyshot.Server.Host.dll'
|
||
'Flyshot.ControllerClientCompat.dll'
|
||
'Flyshot.Core.Config.dll'
|
||
'Flyshot.Core.Domain.dll'
|
||
'Flyshot.Core.Planning.dll'
|
||
'Flyshot.Core.Triggering.dll'
|
||
'Flyshot.Runtime.Fanuc.dll'
|
||
'Flyshot.Runtime.Common.dll'
|
||
)
|
||
$futureAssemblyName = 'Flyshot.Future.Module.dll'
|
||
|
||
try
|
||
{
|
||
$publishScriptContent = Get-Content -LiteralPath $publishScriptPath -Raw
|
||
$publishScriptTokens = $null
|
||
$publishScriptErrors = $null
|
||
$publishScriptAst = [System.Management.Automation.Language.Parser]::ParseInput(
|
||
$publishScriptContent,
|
||
[ref]$publishScriptTokens,
|
||
[ref]$publishScriptErrors)
|
||
|
||
if ($publishScriptErrors.Count -ne 0)
|
||
{
|
||
throw "Publish-Obfuscated.ps1 存在 PowerShell 语法错误:$($publishScriptErrors[0].Message)"
|
||
}
|
||
|
||
$scriptParameters = @($publishScriptAst.ParamBlock.Parameters | ForEach-Object {
|
||
$_.Name.VariablePath.UserPath
|
||
})
|
||
if ('RuntimeIdentifiers' -in $scriptParameters -or 'ExistingRuntimeIdentifier' -in $scriptParameters)
|
||
{
|
||
throw '发布脚本不应再暴露运行平台或 RID 参数。'
|
||
}
|
||
|
||
if ($publishScriptContent -match "'-r'|--self-contained'\s*,?\s*'true'|win-x64|linux-x64")
|
||
{
|
||
throw '发布脚本默认发布流程不得再指定 RID 或生成自包含平台包。'
|
||
}
|
||
|
||
if ($publishScriptContent -notmatch '-p:UseAppHost=false')
|
||
{
|
||
throw '框架依赖发布必须关闭平台相关 apphost,只保留跨平台 DLL 入口。'
|
||
}
|
||
|
||
[xml]$publishProfile = Get-Content -LiteralPath $publishProfilePath -Raw
|
||
$publishNamespace = [System.Xml.XmlNamespaceManager]::new($publishProfile.NameTable)
|
||
$publishNamespace.AddNamespace('msb', $publishProfile.Project.NamespaceURI)
|
||
|
||
$useAppHost = $publishProfile.SelectSingleNode(
|
||
'/msb:Project/msb:PropertyGroup/msb:UseAppHost',
|
||
$publishNamespace)
|
||
if ($null -eq $useAppHost -or $useAppHost.InnerText -ne 'false')
|
||
{
|
||
throw 'FolderProfile.pubxml 必须关闭平台相关 apphost。'
|
||
}
|
||
|
||
$afterPublishTarget = $publishProfile.SelectSingleNode(
|
||
'/msb:Project/msb:Target[@Name="ObfuscatePublishedFlyshotAssemblies"]',
|
||
$publishNamespace)
|
||
if ($null -eq $afterPublishTarget -or $afterPublishTarget.AfterTargets -ne 'Publish')
|
||
{
|
||
throw 'FolderProfile.pubxml 必须注册 Publish 完成后的混淆 Target。'
|
||
}
|
||
|
||
$publishExec = $afterPublishTarget.SelectSingleNode('msb:Exec', $publishNamespace)
|
||
if (
|
||
$null -eq $publishExec -or
|
||
$publishExec.Command -notmatch '-ObfuscateExistingPublish' -or
|
||
$publishExec.Command -notmatch '\$\(PublishDir\)\.' -or
|
||
$publishExec.Command -match 'RuntimeIdentifier'
|
||
)
|
||
{
|
||
throw 'Visual Studio 发布 Target 必须把 PublishDir 以安全的非反斜杠结尾形式交给混淆模式。'
|
||
}
|
||
|
||
New-Item -ItemType Directory -Path $inputDirectory -Force | Out-Null
|
||
|
||
foreach ($assemblyName in $expectedAssemblies)
|
||
{
|
||
New-Item -ItemType File -Path (Join-Path $inputDirectory $assemblyName) -Force | Out-Null
|
||
}
|
||
|
||
# 模拟未来新增模块,证明脚本会自动保护所有 Flyshot.* 程序集,而不是只处理当前固定清单。
|
||
New-Item -ItemType File -Path (Join-Path $inputDirectory $futureAssemblyName) -Force | Out-Null
|
||
|
||
# 第三方程序集用于证明配置生成器只选择 Flyshot 自研模块。
|
||
New-Item -ItemType File -Path (Join-Path $inputDirectory 'NLog.dll') -Force | Out-Null
|
||
|
||
& pwsh -NoProfile -File $publishScriptPath `
|
||
-GenerateConfigOnly `
|
||
-InputDirectory $inputDirectory `
|
||
-ObfuscatedDirectory $outputDirectory `
|
||
-ConfigPath $configPath
|
||
|
||
if ($LASTEXITCODE -ne 0)
|
||
{
|
||
throw "配置生成命令退出码不是 0:$LASTEXITCODE"
|
||
}
|
||
|
||
[xml]$configuration = Get-Content -LiteralPath $configPath -Raw
|
||
$modulePaths = @($configuration.Obfuscator.Module | ForEach-Object { [string]$_.file })
|
||
|
||
$allExpectedAssemblies = @($expectedAssemblies) + $futureAssemblyName
|
||
if ($modulePaths.Count -ne $allExpectedAssemblies.Count)
|
||
{
|
||
throw "预期生成 $($allExpectedAssemblies.Count) 个模块,实际为 $($modulePaths.Count) 个。"
|
||
}
|
||
|
||
foreach ($assemblyName in $allExpectedAssemblies)
|
||
{
|
||
$expectedPath = [System.IO.Path]::GetFullPath((Join-Path $inputDirectory $assemblyName))
|
||
if ($expectedPath -notin $modulePaths)
|
||
{
|
||
throw "Obfuscar 配置缺少自研程序集:$assemblyName"
|
||
}
|
||
}
|
||
|
||
if ($modulePaths | Where-Object { [System.IO.Path]::GetFileName($_) -eq 'NLog.dll' })
|
||
{
|
||
throw '第三方程序集 NLog.dll 不应进入 Obfuscar 模块列表。'
|
||
}
|
||
|
||
foreach ($path in $modulePaths)
|
||
{
|
||
if (-not [System.IO.Path]::IsPathFullyQualified($path))
|
||
{
|
||
throw "Obfuscar 模块路径不是绝对路径:$path"
|
||
}
|
||
}
|
||
|
||
foreach ($module in @($configuration.Obfuscator.Module))
|
||
{
|
||
$anonymousTypeRule = @($module.SkipType | Where-Object {
|
||
[string]$_.name -eq '^<>f__AnonymousType.*'
|
||
})
|
||
|
||
if ($anonymousTypeRule.Count -ne 1)
|
||
{
|
||
throw "模块 $($module.file) 必须显式保留匿名类型元数据。"
|
||
}
|
||
|
||
if (
|
||
[string]$anonymousTypeRule[0].skipMethods -ne 'true' -or
|
||
[string]$anonymousTypeRule[0].skipFields -ne 'true' -or
|
||
[string]$anonymousTypeRule[0].skipProperties -ne 'true' -or
|
||
[string]$anonymousTypeRule[0].skipEvents -ne 'true'
|
||
)
|
||
{
|
||
throw "模块 $($module.file) 的匿名类型排除规则不完整。"
|
||
}
|
||
}
|
||
|
||
$assemblySearchPaths = @($configuration.Obfuscator.AssemblySearchPath | ForEach-Object {
|
||
[string]$_.path
|
||
})
|
||
if ($assemblySearchPaths.Count -lt 2)
|
||
{
|
||
throw 'Obfuscar 配置必须包含 .NETCore.App 与 ASP.NET Core 共享框架搜索目录。'
|
||
}
|
||
|
||
foreach ($assemblySearchPath in $assemblySearchPaths)
|
||
{
|
||
if (-not [System.IO.Path]::IsPathFullyQualified($assemblySearchPath))
|
||
{
|
||
throw "共享框架搜索目录不是绝对路径:$assemblySearchPath"
|
||
}
|
||
}
|
||
|
||
if (-not ($assemblySearchPaths | Where-Object { $_ -match 'Microsoft\.NETCore\.App' }))
|
||
{
|
||
throw 'Obfuscar 配置缺少 Microsoft.NETCore.App 搜索目录。'
|
||
}
|
||
|
||
if (-not ($assemblySearchPaths | Where-Object { $_ -match 'Microsoft\.AspNetCore\.App' }))
|
||
{
|
||
throw 'Obfuscar 配置缺少 Microsoft.AspNetCore.App 搜索目录。'
|
||
}
|
||
|
||
$variables = @{}
|
||
foreach ($variable in $configuration.Obfuscator.Var)
|
||
{
|
||
$variables[[string]$variable.name] = [string]$variable.value
|
||
}
|
||
|
||
if ($variables['KeepPublicApi'] -ne 'true')
|
||
{
|
||
throw 'KeepPublicApi 必须为 true,以保护 ASP.NET Core 和公共契约。'
|
||
}
|
||
|
||
if ($variables['HidePrivateApi'] -ne 'true')
|
||
{
|
||
throw 'HidePrivateApi 必须为 true,以混淆内部实现。'
|
||
}
|
||
|
||
if ($variables['RenameProperties'] -ne 'false' -or $variables['RenameEvents'] -ne 'false')
|
||
{
|
||
throw '属性和事件重命名必须关闭,以保护配置绑定及序列化契约。'
|
||
}
|
||
|
||
if ($variables['SkipGenerated'] -ne 'true')
|
||
{
|
||
throw '编译器生成类型必须跳过混淆,以保护匿名 JSON 响应和异步状态机。'
|
||
}
|
||
|
||
if ($variables['SkipSpecialName'] -ne 'true')
|
||
{
|
||
throw 'SpecialName 成员必须跳过混淆,以保护属性、事件和框架反射约定。'
|
||
}
|
||
|
||
Remove-Item -LiteralPath (Join-Path $inputDirectory 'Flyshot.Runtime.Common.dll') -Force
|
||
|
||
$missingAssemblyConfigPath = Join-Path $temporaryRoot 'missing-assembly.xml'
|
||
& pwsh -NoProfile -File $publishScriptPath `
|
||
-GenerateConfigOnly `
|
||
-InputDirectory $inputDirectory `
|
||
-ObfuscatedDirectory $outputDirectory `
|
||
-ConfigPath $missingAssemblyConfigPath *> $null
|
||
|
||
if ($LASTEXITCODE -eq 0)
|
||
{
|
||
throw '缺少预期的 Flyshot 程序集时,配置生成必须失败。'
|
||
}
|
||
|
||
Write-Host 'Publish-Obfuscated.Tests.ps1: PASS'
|
||
}
|
||
finally
|
||
{
|
||
if (Test-Path -LiteralPath $temporaryRoot)
|
||
{
|
||
Remove-Item -LiteralPath $temporaryRoot -Recurse -Force
|
||
}
|
||
}
|