Modules/businessdev.ALbuild.Apps/Private/Resolve-BcAlAppContext.ps1
|
function Resolve-BcAlAppContext { <# .SYNOPSIS Resolves the AL app an .al file belongs to: its manifest, whether it is a test app, and the preprocessor symbols it was compiled with. .DESCRIPTION Coverage needs two facts about every source file, and both come from the same place - the app.json of the app the file belongs to: * Is this a TEST app? Its objects must not be measured. Test code is covered by construction (it runs when the test runs), so counting it rewards test volume instead of test depth. In one real repository 42% of the denominator was test code. * Which preprocessor symbols were used? Lines inside an inactive #if branch are not compiled and can never be covered, so counting them only inflates the denominator. The symbols are read from the manifest rather than passed down from the pipeline: the compile step writes them there (Update-BcAppManifest sets 'preprocessorSymbols' alongside application, runtime and platform) before the tests run. That makes the coverage view agree with what alc.exe actually compiled - including repository-specific symbols such as ONPREM or CLEAN - without any pipeline wiring, and it is per-app-correct in a repository holding several apps. Results are cached per app folder: Test-BcIsTestApp may scan AL source when the manifest is inconclusive, which is far too expensive to repeat for every file of an app. .PARAMETER Path The .al file (or a folder) to resolve the owning app for. .OUTPUTS PSCustomObject: AppFolder, Manifest, IsTestApp, PreprocessorSymbols. A file outside any app (no app.json above it) yields IsTestApp = $false and no symbols, so callers behave exactly as they did before this existed. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Path ) # Test-Path over the variable provider, not `-not $script:...`: the module runs under # Set-StrictMode -Version Latest, where reading a never-assigned variable throws. if (-not (Test-Path -LiteralPath 'variable:script:BcAlAppContextCache')) { $script:BcAlAppContextCache = @{} } $none = [PSCustomObject]@{ AppFolder = ''; Manifest = $null; IsTestApp = $false; PreprocessorSymbols = @() } # Start at the file's folder (or the folder itself) and walk up to the first app.json. $dir = if (Test-Path -LiteralPath $Path -PathType Container) { $Path } else { Split-Path -Parent $Path } if (-not $dir) { return $none } $appFolder = $null $current = $dir while ($current) { if (Test-Path -LiteralPath (Join-Path $current 'app.json')) { $appFolder = $current; break } $parent = Split-Path -Parent $current if ($parent -eq $current) { break } # reached the drive/volume root $current = $parent } if (-not $appFolder) { return $none } $key = $appFolder.ToLowerInvariant() if ($script:BcAlAppContextCache.ContainsKey($key)) { return $script:BcAlAppContextCache[$key] } $manifest = $null try { $manifest = Get-Content -LiteralPath (Join-Path $appFolder 'app.json') -Raw -Encoding UTF8 -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-ALbuildLog -Level Verbose "Could not read '$appFolder\app.json' ($($_.Exception.Message)); treating it as a non-test app without symbols." $script:BcAlAppContextCache[$key] = $none return $none } $isTest = $false try { $isTest = [bool] (Test-BcIsTestApp -Manifest $manifest -AppFolder $appFolder) } catch { Write-ALbuildLog -Level Verbose "Could not classify '$appFolder' as app/test app ($($_.Exception.Message)); treating it as an app." } $symbols = @() if ($manifest.PSObject.Properties.Name -contains 'preprocessorSymbols') { $symbols = @($manifest.preprocessorSymbols | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") } | ForEach-Object { "$_".Trim() }) } $context = [PSCustomObject]@{ AppFolder = $appFolder Manifest = $manifest IsTestApp = $isTest PreprocessorSymbols = $symbols } $script:BcAlAppContextCache[$key] = $context return $context } |