# Functions shared by the managed/compiler and native/PAL source receipts.
function Get-RuntimeInputHash([string]$Path) {
(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Get-RuntimeRelativeInput([string]$Root,[string]$Path) {
$absolute=[IO.Path]::GetFullPath($Path)
$prefix=[IO.Path]::GetFullPath($Root).TrimEnd([char]'\',[char]'/')+[IO.Path]::DirectorySeparatorChar
if (!$absolute.StartsWith($prefix,[StringComparison]::OrdinalIgnoreCase)) { throw ('Input outside the repository cannot be source-bound: '+$absolute) }
$absolute.Substring($prefix.Length).Replace('\','/')
}
function Get-RuntimeAbsoluteInput([string]$Directory,[string]$Path) {
if([IO.Path]::IsPathRooted($Path)) { return [IO.Path]::GetFullPath($Path) }
return [IO.Path]::GetFullPath((Join-Path $Directory $Path))
}
function Get-RuntimeGlobInputs([string]$Directory,[string]$Pattern) {
$normalized=$Pattern.Replace('\','/')
$first=$normalized.IndexOfAny([char[]]'*?')
if($first -lt 0) { throw ('Not a wildcard input: '+$Pattern) }
$slash=$normalized.LastIndexOf('/', $first)
$parent=if($slash -ge 0){$normalized.Substring(0,$slash)}else{'.'}
$suffix=if($slash -ge 0){$normalized.Substring($slash+1)}else{$normalized}
# Windows GetFullPath rejects wildcard characters. Resolve only the literal
# parent, then match file paths ourselves using MSBuild's ** directory rule.
$absolute=(Get-RuntimeAbsoluteInput $Directory $parent).TrimEnd([char]'\',[char]'/')
if(!(Test-Path -LiteralPath $absolute -PathType Container)) { throw ('Wildcard parent is unavailable: '+$absolute) }
$parts=$suffix.Split('/')
$expression='^'
for($index=0;$index -lt $parts.Length;$index++) {
$part=$parts[$index]
if($part -eq '**') {
$expression+=if($index -lt $parts.Length-1){'(?:[^/]+/)*'}else{'.*'}
continue
}
if($part -eq '..') { throw ('Parent traversal after a wildcard cannot be source-bound: '+$Pattern) }
foreach($character in $part.ToCharArray()) {
if($character -eq '*'){$expression+='[^/]*'}
elseif($character -eq '?'){$expression+='[^/]'}
else{$expression+=[regex]::Escape([string]$character)}
}
if($index -lt $parts.Length-1){$expression+='/'}
}
$expression+='$'
$matcher=[regex]::new($expression,[Text.RegularExpressions.RegexOptions]::IgnoreCase)
$found=@(Get-ChildItem -LiteralPath $absolute -Recurse -File | Where-Object {
$relative=$_.FullName.Substring($absolute.Length+1).Replace('\','/')
$matcher.IsMatch($relative)
})
if(!$found.Count) { throw ('Embedded/input pattern has no files: '+$Pattern) }
return $found
}
function Add-RuntimeBuildImports([string]$Path,$Files,$Visited) {
$absolute=[IO.Path]::GetFullPath($Path)
if(!$Visited.Add($absolute)) { return }
[void]$Files.Add($absolute)
[xml]$xml=Get-Content -LiteralPath $absolute -Raw -Encoding UTF8
$directory=Split-Path -Parent $absolute
foreach($import in $xml.SelectNodes('//Import[@Project]')) {
$expression=$import.Project.Replace('$(MSBuildThisFileDirectory)',$directory+[IO.Path]::DirectorySeparatorChar)
if($expression -match '[$][(]') { throw ('Unevaluated explicit MSBuild import: '+$expression) }
foreach($include in $expression.Split(';')) {
if($include -match '[*?]') {
foreach($file in Get-RuntimeGlobInputs $directory $include) { Add-RuntimeBuildImports $file.FullName $Files $Visited }
} else {
$next=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $next -PathType Leaf)) { throw ('Explicit MSBuild import is unavailable: '+$next) }
Add-RuntimeBuildImports $next $Files $Visited
}
}
}
}
function Get-RuntimeProjectInputs([string]$Root,[string]$Project) {
$files=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$projects=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$queue=[Collections.Generic.Queue[string]]::new()
$queue.Enqueue([IO.Path]::GetFullPath($Project))
while($queue.Count) {
$current=$queue.Dequeue()
if(!$projects.Add($current)) { continue }
$directory=Split-Path -Parent $current
[void]$files.Add($current)
foreach($file in Get-ChildItem -LiteralPath $directory -Recurse -File) {
if($file.FullName -notmatch '[/\\](bin|obj)[/\\]') { [void]$files.Add($file.FullName) }
}
[xml]$xml=Get-Content -LiteralPath $current -Raw -Encoding UTF8
if($xml.SelectNodes('//PackageReference').Include -match '^Cosmos[.]') { throw ('Cosmos package in native application graph: '+$current) }
foreach($reference in $xml.SelectNodes('//ProjectReference[@Include]')) {
foreach($include in $reference.Include.Split(';')) {
if($include -match '[$][(]|[*?]') { throw ('Unresolved project reference in source receipt: '+$include) }
$next=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $next -PathType Leaf)) { throw ('Referenced project is unavailable: '+$next) }
$queue.Enqueue($next)
}
}
# Capture linked C# files and exact external embedded assets, including
# resources outside the project's directory and semicolon/wildcard items.
foreach($item in $xml.SelectNodes('//Compile[@Include]|//EmbeddedResource[@Include]|//AdditionalFiles[@Include]|//Reference/HintPath|//Import[@Project]')) {
$expression=if($item.Name -eq 'HintPath'){$item.InnerText}elseif($item.Name -eq 'Import'){$item.Project}else{$item.Include}
foreach($include in $expression.Split(';')) {
if(!$include) { continue }
if($include -match '[$][(]|[%][(]') { throw ('Unevaluated build input in source receipt: '+$include) }
if($include -match '[*?]') {
$found=@(Get-RuntimeGlobInputs $directory $include)
foreach($file in $found) { [void]$files.Add($file.FullName) }
} else {
$absolute=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $absolute -PathType Leaf)) { throw ('Explicit build input is unavailable: '+$absolute) }
[void]$files.Add($absolute)
}
}
}
}
foreach($name in @('Directory.Build.props','Directory.Build.targets','Directory.Packages.props','NuGet.config','nuget.config','global.json')) {
$path=Join-Path $Root $name
if(Test-Path -LiteralPath $path -PathType Leaf) { [void]$files.Add([IO.Path]::GetFullPath($path)) }
}
$imports=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($name in @('Directory.Build.props','Directory.Build.targets','Directory.Packages.props')) {
$path=Join-Path $Root $name
if(Test-Path -LiteralPath $path -PathType Leaf) { Add-RuntimeBuildImports $path $files $imports }
}
# HostJint.targets executes this source tool as part of the real application
# build. It is an implicit build dependency, not a ProjectReference.
$jintTool=Join-Path $Root 'Tools/JintCleanupPatch'
if(Test-Path -LiteralPath $jintTool -PathType Container) {
foreach($file in Get-ChildItem -LiteralPath $jintTool -Recurse -File) {
if($file.FullName -notmatch '[/\\](bin|obj)[/\\]') { [void]$files.Add($file.FullName) }
}
}
$hashes=[ordered]@{}
foreach($path in @($files | Sort-Object)) { $hashes[(Get-RuntimeRelativeInput $Root $path)]=Get-RuntimeInputHash $path }
return $hashes
}
function Assert-RuntimeSourceInputs([string]$Root,$Hashes) {
foreach($entry in $Hashes.GetEnumerator()) {
$path=Join-Path $Root $entry.Key
if(!(Test-Path -LiteralPath $path -PathType Leaf) -or (Get-RuntimeInputHash $path) -cne $entry.Value) {
throw ('A source/asset input changed during compilation: '+$entry.Key)
}
}
}
function Assert-RuntimeManagedDependencies([string]$ManagedInputPath,[string[]]$References) {
$manifest=[IO.Path]::ChangeExtension($ManagedInputPath,'.deps.json')
if(!(Test-Path -LiteralPath $manifest -PathType Leaf)) { throw ('Managed dependency manifest is unavailable: '+$manifest) }
$dependencies=Get-Content -LiteralPath $manifest -Raw -Encoding UTF8 | ConvertFrom-Json
$target=$dependencies.targets.PSObject.Properties[$dependencies.runtimeTarget.name]
if(!$target) { throw ('Managed dependency target is unavailable: '+$dependencies.runtimeTarget.name) }
$available=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($path in (@($ManagedInputPath)+@($References))) {
if(!(Test-Path -LiteralPath $path -PathType Leaf)) { throw ('Managed compiler input is unavailable: '+$path) }
[void]$available.Add([IO.Path]::GetFileName($path))
}
$required=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($library in $target.Value.PSObject.Properties) {
foreach($asset in $library.Value.runtime.PSObject.Properties) {
if(![string]::Equals([IO.Path]::GetExtension($asset.Name),'.dll',[StringComparison]::OrdinalIgnoreCase)) { continue }
$name=[IO.Path]::GetFileName($asset.Name)
[void]$required.Add($name)
if(!$available.Contains($name)) { throw ('Declared managed runtime dependency is absent from ILC inputs: '+$name+' ('+$library.Name+')') }
}
}
if(!$required.Count -or !$required.Contains([IO.Path]::GetFileName($ManagedInputPath))) { throw 'Managed dependency manifest does not identify the entry assembly.' }
return [pscustomobject]@{Path=$manifest;Sha256=(Get-RuntimeInputHash $manifest);RuntimeTarget=$dependencies.runtimeTarget.name;RuntimeAssemblies=@($required | Sort-Object)}
}
function Assert-RuntimeIlcDiagnostics([string]$LogPath) {
$diagnostics=Get-Content -LiteralPath $LogPath -Raw -Encoding UTF8
if($diagnostics -match '(?im)\b(?:AOT analysis|code generation)\s+error\b|\berror\s+IL[0-9]{4}\b') {
throw ('NativeAOT reported a compilation/analysis error despite its process exit code; see '+$LogPath)
}
}
# Functions shared by the managed/compiler and native/PAL source receipts.
function Get-RuntimeInputHash([string]$Path) {
(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Get-RuntimeRelativeInput([string]$Root,[string]$Path) {
$absolute=[IO.Path]::GetFullPath($Path)
$prefix=[IO.Path]::GetFullPath($Root).TrimEnd([char]'\',[char]'/')+[IO.Path]::DirectorySeparatorChar
if (!$absolute.StartsWith($prefix,[StringComparison]::OrdinalIgnoreCase)) { throw ('Input outside the repository cannot be source-bound: '+$absolute) }
$absolute.Substring($prefix.Length).Replace('\','/')
}
function Get-RuntimeAbsoluteInput([string]$Directory,[string]$Path) {
if([IO.Path]::IsPathRooted($Path)) { return [IO.Path]::GetFullPath($Path) }
return [IO.Path]::GetFullPath((Join-Path $Directory $Path))
}
function Get-RuntimeGlobInputs([string]$Directory,[string]$Pattern) {
$normalized=$Pattern.Replace('\','/')
$first=$normalized.IndexOfAny([char[]]'*?')
if($first -lt 0) { throw ('Not a wildcard input: '+$Pattern) }
$slash=$normalized.LastIndexOf('/', $first)
$parent=if($slash -ge 0){$normalized.Substring(0,$slash)}else{'.'}
$suffix=if($slash -ge 0){$normalized.Substring($slash+1)}else{$normalized}
# Windows GetFullPath rejects wildcard characters. Resolve only the literal
# parent, then match file paths ourselves using MSBuild's ** directory rule.
$absolute=(Get-RuntimeAbsoluteInput $Directory $parent).TrimEnd([char]'\',[char]'/')
if(!(Test-Path -LiteralPath $absolute -PathType Container)) { throw ('Wildcard parent is unavailable: '+$absolute) }
$parts=$suffix.Split('/')
$expression='^'
for($index=0;$index -lt $parts.Length;$index++) {
$part=$parts[$index]
if($part -eq '**') {
$expression+=if($index -lt $parts.Length-1){'(?:[^/]+/)*'}else{'.*'}
continue
}
if($part -eq '..') { throw ('Parent traversal after a wildcard cannot be source-bound: '+$Pattern) }
foreach($character in $part.ToCharArray()) {
if($character -eq '*'){$expression+='[^/]*'}
elseif($character -eq '?'){$expression+='[^/]'}
else{$expression+=[regex]::Escape([string]$character)}
}
if($index -lt $parts.Length-1){$expression+='/'}
}
$expression+='$'
$matcher=[regex]::new($expression,[Text.RegularExpressions.RegexOptions]::IgnoreCase)
$found=@(Get-ChildItem -LiteralPath $absolute -Recurse -File | Where-Object {
$relative=$_.FullName.Substring($absolute.Length+1).Replace('\','/')
$matcher.IsMatch($relative)
})
if(!$found.Count) { throw ('Embedded/input pattern has no files: '+$Pattern) }
return $found
}
function Add-RuntimeBuildImports([string]$Path,$Files,$Visited) {
$absolute=[IO.Path]::GetFullPath($Path)
if(!$Visited.Add($absolute)) { return }
[void]$Files.Add($absolute)
[xml]$xml=Get-Content -LiteralPath $absolute -Raw -Encoding UTF8
$directory=Split-Path -Parent $absolute
foreach($import in $xml.SelectNodes('//Import[@Project]')) {
$expression=$import.Project.Replace('$(MSBuildThisFileDirectory)',$directory+[IO.Path]::DirectorySeparatorChar)
if($expression -match '[$][(]') { throw ('Unevaluated explicit MSBuild import: '+$expression) }
foreach($include in $expression.Split(';')) {
if($include -match '[*?]') {
foreach($file in Get-RuntimeGlobInputs $directory $include) { Add-RuntimeBuildImports $file.FullName $Files $Visited }
} else {
$next=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $next -PathType Leaf)) { throw ('Explicit MSBuild import is unavailable: '+$next) }
Add-RuntimeBuildImports $next $Files $Visited
}
}
}
}
function Get-RuntimeProjectInputs([string]$Root,[string]$Project) {
$files=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$projects=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$queue=[Collections.Generic.Queue[string]]::new()
$queue.Enqueue([IO.Path]::GetFullPath($Project))
while($queue.Count) {
$current=$queue.Dequeue()
if(!$projects.Add($current)) { continue }
$directory=Split-Path -Parent $current
[void]$files.Add($current)
foreach($file in Get-ChildItem -LiteralPath $directory -Recurse -File) {
if($file.FullName -notmatch '[/\\](bin|obj)[/\\]') { [void]$files.Add($file.FullName) }
}
[xml]$xml=Get-Content -LiteralPath $current -Raw -Encoding UTF8
if($xml.SelectNodes('//PackageReference').Include -match '^Cosmos[.]') { throw ('Cosmos package in native application graph: '+$current) }
foreach($reference in $xml.SelectNodes('//ProjectReference[@Include]')) {
foreach($include in $reference.Include.Split(';')) {
if($include -match '[$][(]|[*?]') { throw ('Unresolved project reference in source receipt: '+$include) }
$next=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $next -PathType Leaf)) { throw ('Referenced project is unavailable: '+$next) }
$queue.Enqueue($next)
}
}
# Capture linked C# files and exact external embedded assets, including
# resources outside the project's directory and semicolon/wildcard items.
foreach($item in $xml.SelectNodes('//Compile[@Include]|//EmbeddedResource[@Include]|//AdditionalFiles[@Include]|//Reference/HintPath|//Import[@Project]')) {
$expression=if($item.Name -eq 'HintPath'){$item.InnerText}elseif($item.Name -eq 'Import'){$item.Project}else{$item.Include}
foreach($include in $expression.Split(';')) {
if(!$include) { continue }
if($include -match '[$][(]|[%][(]') { throw ('Unevaluated build input in source receipt: '+$include) }
if($include -match '[*?]') {
$found=@(Get-RuntimeGlobInputs $directory $include)
foreach($file in $found) { [void]$files.Add($file.FullName) }
} else {
$absolute=Get-RuntimeAbsoluteInput $directory $include
if(!(Test-Path -LiteralPath $absolute -PathType Leaf)) { throw ('Explicit build input is unavailable: '+$absolute) }
[void]$files.Add($absolute)
}
}
}
}
foreach($name in @('Directory.Build.props','Directory.Build.targets','Directory.Packages.props','NuGet.config','nuget.config','global.json')) {
$path=Join-Path $Root $name
if(Test-Path -LiteralPath $path -PathType Leaf) { [void]$files.Add([IO.Path]::GetFullPath($path)) }
}
$imports=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($name in @('Directory.Build.props','Directory.Build.targets','Directory.Packages.props')) {
$path=Join-Path $Root $name
if(Test-Path -LiteralPath $path -PathType Leaf) { Add-RuntimeBuildImports $path $files $imports }
}
# HostJint.targets executes this source tool as part of the real application
# build. It is an implicit build dependency, not a ProjectReference.
$jintTool=Join-Path $Root 'Tools/JintCleanupPatch'
if(Test-Path -LiteralPath $jintTool -PathType Container) {
foreach($file in Get-ChildItem -LiteralPath $jintTool -Recurse -File) {
if($file.FullName -notmatch '[/\\](bin|obj)[/\\]') { [void]$files.Add($file.FullName) }
}
}
$hashes=[ordered]@{}
foreach($path in @($files | Sort-Object)) { $hashes[(Get-RuntimeRelativeInput $Root $path)]=Get-RuntimeInputHash $path }
return $hashes
}
function Assert-RuntimeSourceInputs([string]$Root,$Hashes) {
foreach($entry in $Hashes.GetEnumerator()) {
$path=Join-Path $Root $entry.Key
if(!(Test-Path -LiteralPath $path -PathType Leaf) -or (Get-RuntimeInputHash $path) -cne $entry.Value) {
throw ('A source/asset input changed during compilation: '+$entry.Key)
}
}
}
function Assert-RuntimeManagedDependencies([string]$ManagedInputPath,[string[]]$References) {
$manifest=[IO.Path]::ChangeExtension($ManagedInputPath,'.deps.json')
if(!(Test-Path -LiteralPath $manifest -PathType Leaf)) { throw ('Managed dependency manifest is unavailable: '+$manifest) }
$dependencies=Get-Content -LiteralPath $manifest -Raw -Encoding UTF8 | ConvertFrom-Json
$target=$dependencies.targets.PSObject.Properties[$dependencies.runtimeTarget.name]
if(!$target) { throw ('Managed dependency target is unavailable: '+$dependencies.runtimeTarget.name) }
$available=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($path in (@($ManagedInputPath)+@($References))) {
if(!(Test-Path -LiteralPath $path -PathType Leaf)) { throw ('Managed compiler input is unavailable: '+$path) }
[void]$available.Add([IO.Path]::GetFileName($path))
}
$required=[Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach($library in $target.Value.PSObject.Properties) {
foreach($asset in $library.Value.runtime.PSObject.Properties) {
if(![string]::Equals([IO.Path]::GetExtension($asset.Name),'.dll',[StringComparison]::OrdinalIgnoreCase)) { continue }
$name=[IO.Path]::GetFileName($asset.Name)
[void]$required.Add($name)
if(!$available.Contains($name)) { throw ('Declared managed runtime dependency is absent from ILC inputs: '+$name+' ('+$library.Name+')') }
}
}
if(!$required.Count -or !$required.Contains([IO.Path]::GetFileName($ManagedInputPath))) { throw 'Managed dependency manifest does not identify the entry assembly.' }
return [pscustomobject]@{Path=$manifest;Sha256=(Get-RuntimeInputHash $manifest);RuntimeTarget=$dependencies.runtimeTarget.name;RuntimeAssemblies=@($required | Sort-Object)}
}
function Assert-RuntimeIlcDiagnostics([string]$LogPath) {
$diagnostics=Get-Content -LiteralPath $LogPath -Raw -Encoding UTF8
if($diagnostics -match '(?im)\b(?:AOT analysis|code generation)\s+error\b|\berror\s+IL[0-9]{4}\b') {
throw ('NativeAOT reported a compilation/analysis error despite its process exit code; see '+$LogPath)
}
}