chore(validation): enforce package graph boundaries
Reject internal package version drift, dependency cycles, and runtime-to-integration reverse dependencies before launching the external UPM consumer validation.
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
# UPM Consumer Validation
|
||||
|
||||
`Validate-UpmConsumer.ps1` verifies the package graph from outside the main Unity
|
||||
project. It performs three checks:
|
||||
project. It performs five checks:
|
||||
|
||||
1. Every dependency on another local `com.cneicy.*` package must use that
|
||||
package's current `package.json` version.
|
||||
2. A temporary Unity project is created under `Temp/UpmConsumerValidation`, all
|
||||
2. The local package graph must be acyclic.
|
||||
3. A normal package must not depend on a `*-integration-*` package. Integration
|
||||
packages may depend on each other, and starter packages may aggregate them.
|
||||
4. A temporary Unity project is created under `Temp/UpmConsumerValidation`, all
|
||||
local packages are installed through `file:` UPM dependencies, package tests
|
||||
are enabled, and Unity must load every package and asmdef.
|
||||
3. The same temporary UPM consumer project runs all package EditMode tests.
|
||||
5. The same temporary UPM consumer project runs all package EditMode tests.
|
||||
|
||||
Run with the Unity version declared by this repository:
|
||||
|
||||
@@ -16,6 +19,12 @@ Run with the Unity version declared by this repository:
|
||||
./Tools/UpmConsumerValidation/Validate-UpmConsumer.ps1
|
||||
```
|
||||
|
||||
Run only the package graph checks without starting Unity:
|
||||
|
||||
```powershell
|
||||
./Tools/UpmConsumerValidation/Validate-UpmConsumer.ps1 -GraphOnly
|
||||
```
|
||||
|
||||
Use `-UnityEditorPath` when the matching editor is not installed in a standard
|
||||
location. Pass `-KeepProject` to retain the temporary project, reports, and Unity logs for
|
||||
investigation. `-SkipTests` is only for a quick package-resolution diagnostic and is not the
|
||||
|
||||
@@ -3,7 +3,8 @@ param(
|
||||
[string]$UnityEditorPath,
|
||||
[string]$ProjectRoot,
|
||||
[switch]$KeepProject,
|
||||
[switch]$SkipTests
|
||||
[switch]$SkipTests,
|
||||
[switch]$GraphOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -101,8 +102,12 @@ foreach ($record in $packageRecords) {
|
||||
$packageByName[$record.Name] = $record
|
||||
}
|
||||
|
||||
$versionErrors = @()
|
||||
$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
|
||||
}
|
||||
@@ -112,14 +117,60 @@ foreach ($record in $packageRecords) {
|
||||
continue
|
||||
}
|
||||
|
||||
[void]$dependencies.Add($dependency.Name)
|
||||
$actualVersion = $packageByName[$dependency.Name].Version
|
||||
if ([string]$dependency.Value -ne $actualVersion) {
|
||||
$versionErrors += "$($record.Name) requires $($dependency.Name) $($dependency.Value), local version is $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 ($versionErrors.Count -gt 0) {
|
||||
throw "Internal package versions are inconsistent:`n$($versionErrors -join "`n")"
|
||||
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) {
|
||||
@@ -133,6 +184,7 @@ $dependencies = [ordered]@{
|
||||
'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
|
||||
|
||||
Reference in New Issue
Block a user