Modules/businessdev.ALbuild.Apps/Private/Resolve-BcAlToolVersion.ps1
|
function Resolve-BcAlToolVersion { <# .SYNOPSIS Selects the AL Tool (dotnet tool) version that can compile a given AL runtime. .DESCRIPTION The AL Tool package is versioned so that its MAJOR is the HIGHEST AL runtime it supports (17.x compiles runtime 17 = BC28 and everything older; 18.x compiles runtime 18 = BC29 and older). A compiler older than the app's declared runtime fails the compile outright with AL1043 ("The runtime version 'x' is not supported by the AL compiler"), so the compiler has to satisfy 'major >= required runtime' rather than be left at "whatever happens to be installed". Selection takes the CLOSEST compiler that satisfies that: the lowest major still >= the required runtime, newest build inside it. Requiring an exact major match would be wrong, because entire majors ship prerelease-only for good - nuget.org carries no stable 15.x at all, so every BC26 build would land on a '-beta' compiler even though stable 16.x/17.x compile the very same app. A prerelease is therefore only chosen when NO stable compiler on the feed supports the required runtime - i.e. for a BC major that has not shipped yet (NextMajor). That makes NextMajor builds work today and returns to a stable compiler the moment such a version exists, without a pipeline change. Versions are read from the NuGet flat-container index (a plain JSON GET, no NuGet client). .PARAMETER PackageId The dotnet tool package id. Default 'Microsoft.Dynamics.BusinessCentral.Development.Tools'. .PARAMETER RequiredMajor The AL runtime major that must be supported (AL Tool major >= this). 0 = no constraint, which selects the newest version allowed by -Prerelease. .PARAMETER Prerelease Auto (default) - the closest stable compiler supporting the required runtime; a prerelease only when no stable one on the feed supports it. Always - the newest build of the required runtime's own major, prerelease included. Never - stable only; returns nothing when no stable compiler supports the required runtime. .PARAMETER IndexUrl Override the version index URL (defaults to the nuget.org flat-container index for -PackageId). .PARAMETER TimeoutSec HTTP timeout for the index query. .EXAMPLE Resolve-BcAlToolVersion -RequiredMajor 18 # -> 18.0.40.43394-beta while BC29 is prerelease; a stable compiler the moment one ships. .EXAMPLE Resolve-BcAlToolVersion -RequiredMajor 15 # -> the newest stable 16.x: runtime 15 (BC26) has no stable compiler of its own on the feed. .OUTPUTS PSCustomObject: Version, IsPrerelease, RequiredMajor, Available - or $null when nothing matches. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [string] $PackageId = 'Microsoft.Dynamics.BusinessCentral.Development.Tools', [int] $RequiredMajor = 0, [ValidateSet('Auto', 'Always', 'Never')] [string] $Prerelease = 'Auto', [string] $IndexUrl, [int] $TimeoutSec = 60 ) if (-not $IndexUrl) { # The flat-container ("package base address") index lists every published version, prerelease # included; the id must be lower-cased for that endpoint. $IndexUrl = "https://api.nuget.org/v3-flatcontainer/$($PackageId.ToLowerInvariant())/index.json" } # Windows PowerShell 5.1 still negotiates TLS 1.0 by default on some build agents, which nuget.org # refuses; opt into TLS 1.2 for this call (additive, so nothing already enabled is turned off). try { if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } } catch { Write-ALbuildLog -Level Warning "Could not enable TLS 1.2 for the AL Tool version query: $($_.Exception.Message)" } $raw = @() try { $response = Invoke-RestMethod -Uri $IndexUrl -Method Get -TimeoutSec $TimeoutSec if ($response -and ($response.PSObject.Properties.Name -contains 'versions')) { $raw = @($response.versions) } } catch { throw "Could not query the available AL Tool versions from '$IndexUrl': $($_.Exception.Message)" } if ($raw.Count -eq 0) { throw "The AL Tool version index '$IndexUrl' returned no versions." } # Split '<numeric>-<label>' (e.g. '18.0.39.10160-beta'); a version whose numeric part does not parse # is skipped rather than failing the whole selection. $parsed = foreach ($entry in $raw) { if (-not $entry) { continue } $text = [string] $entry $parts = $text -split '-', 2 $numericText = $parts[0] $label = if ($parts.Count -gt 1) { $parts[1] } else { '' } $numeric = $null if (-not [version]::TryParse($numericText, [ref] $numeric)) { continue } [PSCustomObject]@{ Version = $text Numeric = $numeric IsPrerelease = [bool] $label } } $parsed = @($parsed) if ($parsed.Count -eq 0) { throw "None of the $($raw.Count) AL Tool version(s) from '$IndexUrl' could be parsed." } # A compiler can build its OWN runtime and every older one - AL1043 only fires when the declared # runtime is HIGHER than the compiler (verified against AL Tool 18: runtime 15.0 compiles, 19.0 # raises AL1043). So the constraint is '>= RequiredMajor', not '== RequiredMajor'. That matters # because whole majors ship prerelease-only forever: nuget.org has no stable 15.x at all, so an # exact match would push every BC26 build onto a '-beta' compiler although stable 16.x/17.x # compile the same app. $candidates = @($parsed) if ($RequiredMajor -gt 0) { $candidates = @($parsed | Where-Object { $_.Numeric.Major -ge $RequiredMajor }) } # Pick the CLOSEST compiler that can do the job: the lowest major still satisfying the requirement, # and the newest build inside it. Jumping straight to the newest major would swap the analyzer and # diagnostic set underneath an existing build for no benefit. $closest = { param($pool) $pool = @($pool) if ($pool.Count -eq 0) { return $null } if ($RequiredMajor -le 0) { # No requirement: keep the historical "newest wins" behaviour. return @($pool | Sort-Object -Property @{ Expression = 'Numeric'; Descending = $true })[0] } @($pool | Sort-Object -Property ` @{ Expression = { $_.Numeric.Major }; Descending = $false }, ` @{ Expression = 'Numeric'; Descending = $true })[0] } $stable = @($candidates | Where-Object { -not $_.IsPrerelease }) $selected = switch ($Prerelease) { 'Never' { & $closest $stable } # 'Always' stays "bleeding edge for the required runtime": the requirement's own major, newest # build, prerelease included - not the newest major on the whole feed. 'Always' { $ownMajor = if ($RequiredMajor -gt 0) { @($candidates | Where-Object { $_.Numeric.Major -eq $RequiredMajor }) } else { @() } if (@($ownMajor).Count -gt 0) { @($ownMajor | Sort-Object -Property @{ Expression = 'Numeric'; Descending = $true })[0] } else { & $closest $candidates } } default { $pick = & $closest $stable if (-not $pick) { $pick = & $closest $candidates } $pick } } if (-not $selected) { return $null } if ($selected.IsPrerelease -and $Prerelease -eq 'Always') { Write-ALbuildLog -Level Warning ("AL Tool $($selected.Version) is a PRERELEASE - selected because " + "-Prerelease Always was requested$(if (@($stable).Count -gt 0) { " (a stable compiler for runtime $RequiredMajor is available)" }).") } elseif ($selected.IsPrerelease) { Write-ALbuildLog -Level Warning ("AL Tool $($selected.Version) is a PRERELEASE" + $(if ($RequiredMajor -gt 0) { " - no stable AL compiler supporting AL runtime $RequiredMajor or newer exists on the feed yet (expected while that BC major is in preview)" } else { '' }) + '.') } elseif ($RequiredMajor -gt 0 -and $selected.Numeric.Major -ne $RequiredMajor) { Write-ALbuildLog ("AL runtime $RequiredMajor has no stable compiler of its own; using the stable AL Tool " + "$($selected.Version) instead, which supports runtime $RequiredMajor as well.") } return [PSCustomObject]@{ Version = $selected.Version IsPrerelease = $selected.IsPrerelease RequiredMajor = $RequiredMajor Available = $candidates.Count } } |