Files
cneicy 64248f5d4d
Validate ShrinkSDK Workspace / unity (push) Failing after 2m59s
chore(release): 对齐 ShrinkSDK 2026-08 稳定版本
2026-08-28 03:49:52 +08:00

327 lines
12 KiB
PowerShell

[CmdletBinding()]
param(
[string]$UnityEditorPath,
[string]$ProjectRoot,
[switch]$KeepProject,
[switch]$SkipTests,
[switch]$GraphOnly
)
$ErrorActionPreference = 'Stop'
function Resolve-UnityEditor {
param([string]$RequestedPath)
if ($RequestedPath) {
$resolved = Resolve-Path -LiteralPath $RequestedPath -ErrorAction Stop
return $resolved.Path
}
$projectVersionPath = Join-Path $script:ResolvedProjectRoot 'ProjectSettings/ProjectVersion.txt'
$versionLine = Get-Content -LiteralPath $projectVersionPath |
Where-Object { $_ -like 'm_EditorVersion:*' } |
Select-Object -First 1
if (-not $versionLine) {
throw "Unity version was not found in $projectVersionPath"
}
$version = ($versionLine -split ':', 2)[1].Trim()
$running = Get-Process Unity -ErrorAction SilentlyContinue |
Where-Object { $_.Path -and $_.Path -like "*$version*" } |
Select-Object -First 1
if ($running) {
return $running.Path
}
$candidates = @(
"D:/UnityEditor/$version/Editor/Unity.exe",
"C:/Program Files/Unity/Hub/Editor/$version/Editor/Unity.exe",
"C:/Program Files/Unity Hub/Editor/$version/Editor/Unity.exe"
)
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) {
return (Resolve-Path -LiteralPath $candidate).Path
}
}
throw "Unity $version was not found. Pass -UnityEditorPath explicitly."
}
function Convert-ToFileDependency {
param([string]$Path)
return 'file:' + ([System.IO.Path]::GetFullPath($Path).Replace('\', '/'))
}
function Convert-ToCSharpLiteral {
param([string]$Value)
return '"' + $Value.Replace('\', '\\').Replace('"', '\"') + '"'
}
if (-not $ProjectRoot) {
$ProjectRoot = Join-Path $PSScriptRoot '../..'
}
$script:ResolvedProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path
$modulesRoot = Join-Path $script:ResolvedProjectRoot 'Assets/Modules'
$tempRoot = Join-Path $script:ResolvedProjectRoot 'Temp'
$consumerRoot = Join-Path $tempRoot 'UpmConsumerValidation'
$packagesPath = Join-Path $consumerRoot 'Packages'
$assetsEditorPath = Join-Path $consumerRoot 'Assets/Editor'
$projectSettingsPath = Join-Path $consumerRoot 'ProjectSettings'
$reportPath = Join-Path $consumerRoot 'upm-consumer-report.txt'
$logPath = Join-Path $consumerRoot 'unity-upm-consumer.log'
$testResultsPath = Join-Path $consumerRoot 'editmode-results.xml'
$testLogPath = Join-Path $consumerRoot 'unity-editmode-tests.log'
$resolvedTempRoot = [System.IO.Path]::GetFullPath($tempRoot).TrimEnd('\') + '\'
$resolvedConsumerRoot = [System.IO.Path]::GetFullPath($consumerRoot)
if (-not $resolvedConsumerRoot.StartsWith($resolvedTempRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Consumer project must stay under the repository Temp directory: $resolvedConsumerRoot"
}
$packageRecords = @()
foreach ($directory in Get-ChildItem -LiteralPath $modulesRoot -Directory) {
$manifestPath = Join-Path $directory.FullName 'package.json'
if (-not (Test-Path -LiteralPath $manifestPath)) {
continue
}
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
$packageRecords += [PSCustomObject]@{
Directory = $directory.FullName
Name = [string]$manifest.name
Version = [string]$manifest.version
Manifest = $manifest
}
}
$packageByName = @{}
foreach ($record in $packageRecords) {
if ($packageByName.ContainsKey($record.Name)) {
throw "Duplicate package name: $($record.Name)"
}
$packageByName[$record.Name] = $record
}
$graphErrors = @()
$localDependencies = @{}
foreach ($record in $packageRecords) {
$dependencies = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::Ordinal)
$localDependencies[$record.Name] = $dependencies
if (-not $record.Manifest.dependencies) {
continue
}
foreach ($dependency in $record.Manifest.dependencies.PSObject.Properties) {
if (-not $packageByName.ContainsKey($dependency.Name)) {
continue
}
[void]$dependencies.Add($dependency.Name)
$actualVersion = $packageByName[$dependency.Name].Version
if ([string]$dependency.Value -ne $actualVersion) {
$graphErrors += "$($record.Name) requires $($dependency.Name) $($dependency.Value), local version is $actualVersion"
}
$dependsOnIntegration = $dependency.Name -like '*-integration-*'
$isIntegrationOrStarter =
$record.Name -like '*-integration-*' -or
$record.Name -like '*-starter-*'
if ($dependsOnIntegration -and -not $isIntegrationOrStarter) {
$graphErrors += "$($record.Name) must not depend on integration package $($dependency.Name)"
}
}
}
if ($graphErrors.Count -gt 0) {
throw "Internal package graph is invalid:`n$($graphErrors -join "`n")"
}
$resolvedPackages = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::Ordinal)
do {
$madeProgress = $false
foreach ($packageName in $localDependencies.Keys | Sort-Object) {
if ($resolvedPackages.Contains($packageName)) {
continue
}
$unresolvedDependencies = @($localDependencies[$packageName] |
Where-Object { -not $resolvedPackages.Contains($_) })
if ($unresolvedDependencies.Count -eq 0) {
[void]$resolvedPackages.Add($packageName)
$madeProgress = $true
}
}
} while ($madeProgress)
if ($resolvedPackages.Count -ne $packageRecords.Count) {
$cycleDetails = $localDependencies.Keys |
Where-Object { -not $resolvedPackages.Contains($_) } |
Sort-Object |
ForEach-Object {
$packageName = $_
$blockedBy = @($localDependencies[$packageName] |
Where-Object { -not $resolvedPackages.Contains($_) } |
Sort-Object)
"$packageName -> $($blockedBy -join ', ')"
}
throw "Circular internal package dependencies detected:`n$($cycleDetails -join "`n")"
}
Write-Host "Package graph PASS packages=$($packageRecords.Count)"
if ($GraphOnly) {
return
}
if (Test-Path -LiteralPath $consumerRoot) {
Remove-Item -LiteralPath $consumerRoot -Recurse -Force
}
New-Item -ItemType Directory -Path $packagesPath, $assetsEditorPath, $projectSettingsPath -Force | Out-Null
$dependencies = [ordered]@{
'com.cysharp.unitask' = 'https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#7c0f199fe0d3fc528024488ccd671e6c7b27745b'
'com.unity.nuget.newtonsoft-json' = '3.2.2'
'com.unity.test-framework' = '1.1.33'
'com.unity.textmeshpro' = '3.0.7'
'com.unity.ugui' = '1.0.0'
'com.unity.modules.uielements' = '1.0.0'
}
foreach ($record in $packageRecords | Sort-Object Name) {
$dependencies[$record.Name] = Convert-ToFileDependency $record.Directory
}
$consumerManifest = [ordered]@{
dependencies = $dependencies
testables = @($packageRecords.Name | Sort-Object)
}
$consumerManifest | ConvertTo-Json -Depth 8 |
Set-Content -LiteralPath (Join-Path $packagesPath 'manifest.json') -Encoding utf8
Copy-Item -LiteralPath (Join-Path $script:ResolvedProjectRoot 'ProjectSettings/ProjectVersion.txt') `
-Destination (Join-Path $projectSettingsPath 'ProjectVersion.txt')
$expectedPackages = ($packageRecords.Name | Sort-Object | ForEach-Object { Convert-ToCSharpLiteral $_ }) -join ",`n "
$expectedAssemblies = $packageRecords |
ForEach-Object { Get-ChildItem -LiteralPath $_.Directory -Recurse -Filter '*.asmdef' -File } |
Where-Object { $_.FullName -notmatch '[\\/][^\\/]+~[\\/]' } |
ForEach-Object { (Get-Content -Raw -LiteralPath $_.FullName | ConvertFrom-Json).name } |
Sort-Object -Unique |
ForEach-Object { Convert-ToCSharpLiteral $_ }
$expectedAssemblies = $expectedAssemblies -join ",`n "
$escapedReportPath = Convert-ToCSharpLiteral $reportPath
$validatorSource = @"
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
public static class UpmConsumerValidator
{
private static readonly string[] ExpectedPackages =
{
$expectedPackages
};
private static readonly string[] ExpectedAssemblies =
{
$expectedAssemblies
};
public static void Run()
{
try
{
var installed = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()
.Select(package => package.name)
.ToHashSet(StringComparer.Ordinal);
var assemblies = CompilationPipeline.GetAssemblies()
.Select(assembly => assembly.name)
.ToHashSet(StringComparer.Ordinal);
var missingPackages = ExpectedPackages.Where(name => !installed.Contains(name)).ToArray();
var missingAssemblies = ExpectedAssemblies.Where(name => !assemblies.Contains(name)).ToArray();
if (missingPackages.Length > 0 || missingAssemblies.Length > 0)
{
throw new InvalidOperationException(
"Missing packages: " + string.Join(", ", missingPackages) + Environment.NewLine +
"Missing assemblies: " + string.Join(", ", missingAssemblies));
}
File.WriteAllText($escapedReportPath,
"PASS" + Environment.NewLine +
"packages=" + ExpectedPackages.Length + Environment.NewLine +
"assemblies=" + ExpectedAssemblies.Length + Environment.NewLine);
EditorApplication.Exit(0);
}
catch (Exception exception)
{
File.WriteAllText($escapedReportPath, "FAIL" + Environment.NewLine + exception);
UnityEngine.Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
}
"@
$validatorSource | Set-Content -LiteralPath (Join-Path $assetsEditorPath 'UpmConsumerValidator.cs') -Encoding utf8
$resolvedUnity = Resolve-UnityEditor $UnityEditorPath
Write-Host "Validating $($packageRecords.Count) local UPM packages with $resolvedUnity"
$validationProcess = Start-Process -FilePath $resolvedUnity -ArgumentList @(
'-batchmode',
'-nographics',
'-projectPath', $consumerRoot,
'-executeMethod', 'UpmConsumerValidator.Run',
'-quit',
'-logFile', $logPath
) -WindowStyle Hidden -Wait -PassThru
$unityExitCode = $validationProcess.ExitCode
if (-not (Test-Path -LiteralPath $reportPath)) {
$logTail = if (Test-Path -LiteralPath $logPath) { (Get-Content -LiteralPath $logPath -Tail 120) -join "`n" } else { '<no Unity log>' }
throw "UPM consumer validator did not produce a report (Unity exit $unityExitCode).`n$logTail"
}
$report = Get-Content -Raw -LiteralPath $reportPath
if ($unityExitCode -ne 0 -or -not $report.StartsWith('PASS', [System.StringComparison]::Ordinal)) {
$logTail = (Get-Content -LiteralPath $logPath -Tail 120) -join "`n"
throw "UPM consumer validation failed (Unity exit $unityExitCode).`n$report`n$logTail"
}
Write-Host $report.Trim()
Write-Host "Unity log: $logPath"
if (-not $SkipTests) {
Write-Host 'Running EditMode tests from the real UPM consumer project'
$testProcess = Start-Process -FilePath $resolvedUnity -ArgumentList @(
'-batchmode',
'-nographics',
'-projectPath', $consumerRoot,
'-runTests',
'-testPlatform', 'EditMode',
'-testResults', $testResultsPath,
'-logFile', $testLogPath
) -WindowStyle Hidden -Wait -PassThru
$testExitCode = $testProcess.ExitCode
if (-not (Test-Path -LiteralPath $testResultsPath)) {
$testLogTail = if (Test-Path -LiteralPath $testLogPath) { (Get-Content -LiteralPath $testLogPath -Tail 160) -join "`n" } else { '<no Unity test log>' }
throw "Unity Test Runner did not produce results (Unity exit $testExitCode).`n$testLogTail"
}
[xml]$testResults = Get-Content -Raw -LiteralPath $testResultsPath
$testRun = $testResults.'test-run'
if ($testExitCode -ne 0 -or [string]$testRun.result -ne 'Passed' -or [int]$testRun.failed -ne 0) {
$testLogTail = (Get-Content -LiteralPath $testLogPath -Tail 160) -join "`n"
throw "UPM consumer EditMode tests failed (Unity exit $testExitCode, result=$($testRun.result), total=$($testRun.total), failed=$($testRun.failed)).`n$testLogTail"
}
Write-Host "EditMode PASS total=$($testRun.total) passed=$($testRun.passed)"
Write-Host "Test results: $testResultsPath"
Write-Host "Test log: $testLogPath"
}
if (-not $KeepProject) {
Remove-Item -LiteralPath $consumerRoot -Recurse -Force
}