Modules/businessdev.ALbuild.Apps/Private/Resolve-BcCallGraphDependency.ps1
|
function Resolve-BcCallGraphDependency { <# .SYNOPSIS Finds the dependency closure of the given apps and unpacks the source of every dependency that ships it. .DESCRIPTION al graph compiles source only: a call into an app whose source is not part of the corpus is not resolved, and a call that merely passes a type of such an app is not bound at all. So the source of the dependencies - Microsoft's from the BC artifact, others from the package caches - is unpacked into a cache folder and added to the corpus. Packages are matched by their file name ('<Publisher>_<Name>[_<Version>].app') and confirmed by the app id in their manifest; the one with source and the highest version wins. ReadyToRun packages, which wrap the real app as an inner .app, are read through. Unpacked sources are cached per app id and version under '<BaseFolder>\callgraph\src' and reused by later builds on the same agent. .PARAMETER Apps The own apps: objects with Id plus the app.json 'dependencies' and 'application' of each project. .PARAMETER SearchFolder Folders searched (recursively) for .app packages. .PARAMETER CacheFolder Where unpacked sources are kept. Default '<BaseFolder>\callgraph\src'. .OUTPUTS PSCustomObject per dependency: Id, Name, Publisher, Version, HasSource, SourceFolder, PackagePath. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [object[]] $Apps, [string[]] $SearchFolder = @(), [string] $CacheFolder ) Import-BcCallGraphEngine if (-not $CacheFolder) { $CacheFolder = Join-Path -Path (Join-Path -Path (Get-ALbuildConfig -Name 'BaseFolder') -ChildPath 'callgraph') -ChildPath 'src' } # The 'Application' umbrella app stands for Base Application, System Application, Business Foundation... $applicationApp = 'c1335042-3002-4257-bf8a-75c898ccb1b8' $ownIds = @{} foreach ($a in $Apps) { $ownIds["$($a.Id)".ToLowerInvariant()] = $true } # File index: every .app below the search folders, by lower-case file name. $packages = New-Object System.Collections.Generic.List[System.IO.FileInfo] foreach ($folder in ($SearchFolder | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -Unique)) { foreach ($f in (Get-ChildItem -LiteralPath $folder -Filter '*.app' -File -Recurse -ErrorAction SilentlyContinue)) { $packages.Add($f) } } Write-ALbuildLog "Call graph: $($packages.Count) package(s) found in $(@($SearchFolder).Count) search folder(s)." $queue = New-Object System.Collections.Generic.Queue[object] foreach ($a in $Apps) { foreach ($d in @($a.Dependencies)) { if ($d -and $d.id) { $queue.Enqueue([PSCustomObject]@{ Id = "$($d.id)".ToLowerInvariant(); Name = "$($d.name)"; Publisher = "$($d.publisher)" }) } } if ($a.Application) { $queue.Enqueue([PSCustomObject]@{ Id = $applicationApp; Name = 'Application'; Publisher = 'Microsoft' }) } } $seen = @{} $results = New-Object System.Collections.Generic.List[object] while ($queue.Count -gt 0) { $dep = $queue.Dequeue() if (-not $dep.Id -or $seen.ContainsKey($dep.Id) -or $ownIds.ContainsKey($dep.Id)) { continue } $seen[$dep.Id] = $true $prefix = "$($dep.Publisher)_$($dep.Name)".ToLowerInvariant() $candidates = @($packages | Where-Object { $n = $_.Name.ToLowerInvariant() $n -eq "$prefix.app" -or $n.StartsWith("$($prefix)_") }) $manifests = @(foreach ($c in $candidates) { try { $m = [ALbuild.CallGraph.AppPackage]::ReadManifest($c.FullName); if ($m -and $m.Id -eq $dep.Id) { $m } } catch { Write-ALbuildLog -Level Verbose "Call graph: could not read '$($c.FullName)': $($_.Exception.Message)" } }) $best = $manifests | Sort-Object -Property @{ Expression = { $_.HasSource }; Descending = $true }, @{ Expression = { [version]$_.Version }; Descending = $true } | Select-Object -First 1 if (-not $best) { Write-ALbuildLog -Level Warning "Call graph: no package of '$($dep.Name)' by '$($dep.Publisher)' found; calls into it stay unresolved." $results.Add([PSCustomObject]@{ Id = $dep.Id; Name = $dep.Name; Publisher = $dep.Publisher; Version = $null; HasSource = $false; SourceFolder = $null; PackagePath = $null }) continue } foreach ($d in $best.Dependencies) { $queue.Enqueue([PSCustomObject]@{ Id = $d.Id; Name = $d.Name; Publisher = $d.Publisher }) } # The umbrella app has no code of its own; only its dependencies matter. if ($best.Id -eq $applicationApp -and -not $best.HasSource) { continue } $sourceFolder = $null if ($best.HasSource) { $sourceFolder = Join-Path -Path $CacheFolder -ChildPath "$($best.Id)_$($best.Version)" $marker = Join-Path -Path $sourceFolder -ChildPath '.albuild-complete' if (Test-Path -LiteralPath $marker) { Write-ALbuildLog "Call graph: source of '$($best.Name)' $($best.Version) from the cache." } else { # Unpack into a staging folder and rename it into place, so a cancelled build never leaves a # half-unpacked source that a later build would trust. $staging = Join-Path -Path $CacheFolder -ChildPath ('.staging-' + [guid]::NewGuid().ToString('N').Substring(0, 8)) $sw = [System.Diagnostics.Stopwatch]::StartNew() $count = [ALbuild.CallGraph.AppPackage]::ExtractSource($best.Path, $staging) Set-Content -LiteralPath (Join-Path $staging '.albuild-complete') -Value $best.Path -Encoding UTF8 if (Test-Path -LiteralPath $sourceFolder) { Remove-Item -LiteralPath $sourceFolder -Recurse -Force -ErrorAction SilentlyContinue } try { [System.IO.Directory]::Move($staging, $sourceFolder) } catch { # Another build on this agent got there first. Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue if (-not (Test-Path -LiteralPath $marker)) { throw } } Write-ALbuildLog "Call graph: unpacked $count source file(s) of '$($best.Name)' $($best.Version) in $([int]$sw.Elapsed.TotalSeconds) s." } } else { Write-ALbuildLog -Level Warning "Call graph: '$($best.Name)' $($best.Version) ships no source; calls into it - and calls passing its types - stay unresolved." } $results.Add([PSCustomObject]@{ Id = $best.Id; Name = $best.Name; Publisher = $best.Publisher; Version = $best.Version; HasSource = [bool]$best.HasSource; SourceFolder = $sourceFolder; PackagePath = $best.Path }) } return $results.ToArray() } |