Modules/businessdev.ALbuild.RuntimePackages/Private/Import-BcAlCompiler.ps1
|
function Import-BcAlCompiler { <# .SYNOPSIS Loads Microsoft's AL compiler out of a service tier, isolated from this process's own assemblies. .DESCRIPTION The compiler is loaded into its OWN AssemblyLoadContext that serves the service tier first. That is not tidiness, it is the difference between working and failing on someone else's agent: * The tier ships its own closure - System.Text.Json, the Microsoft.Extensions stack, OpenTelemetry - at versions that differ per BC minor. BC 28 carries version 10 of several of them while a net8 host carries 8. * A load context that only handles the Resolving event does NOT isolate: Resolving fires only after the default probing has already succeeded, so the HOST copy wins for anything it also has. Measured: the compiler then dies inside a static initializer while emitting ("The type initializer for 'TranslationItem' threw an exception"), which names nothing. So: BC's own assemblies always come from the tier, and everything else comes from the tier only when the tier's copy is NEWER than the host's. Taking an equal or older copy would fork type identity for no gain - and forking it for System.IO would mean the Stream handed to the compiler is not the Stream it expects. .PARAMETER Tier A Get-BcCompilerServiceTier result. .OUTPUTS PSCustomObject with Context (the load context), Assembly (the compiler) and Tier. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [PSCustomObject] $Tier ) if ($PSVersionTable.PSEdition -ne 'Core') { throw "Building a package from source needs PowerShell 7 or later: Windows PowerShell 5.1 runs on .NET Framework, which has no AssemblyLoadContext and cannot host the AL compiler. Use the container path (New-BcRuntimePackage) or run this step under pwsh." } if (-not $Tier.Hostable) { throw "BC $($Tier.Version) targets $($Tier.TargetFramework), but this PowerShell runs on $($Tier.HostFramework) - its AL compiler cannot be loaded here. Use an artifact on the same .NET major, or run this step under a newer PowerShell." } if (-not ('Albuild.Compiler.TierLoadContext' -as [type])) { # Compiled once per session. The rule lives in C# because AssemblyLoadContext.Load must be # OVERRIDDEN - it cannot be expressed with the Resolving event from script. Add-Type -TypeDefinition @' using System; using System.IO; using System.Reflection; using System.Runtime.Loader; namespace Albuild.Compiler { public sealed class TierLoadContext : AssemblyLoadContext { private readonly string _directory; public TierLoadContext(string directory) : base("bc-tier-" + Path.GetFileName(directory), true) { _directory = directory; } protected override Assembly Load(AssemblyName name) { if (name.Name == null) { return null; } string candidate = Path.Combine(_directory, name.Name + ".dll"); if (!File.Exists(candidate)) { return null; } // not ours: let the host supply it if (name.Name.StartsWith("Microsoft.Dynamics.", StringComparison.Ordinal)) { return LoadFromAssemblyPath(candidate); } Version tier = TryVersion(candidate); Version host = HostVersion(name.Name); return (tier != null && (host == null || tier > host)) ? LoadFromAssemblyPath(candidate) : null; } public Assembly LoadFile(string fileName) { return LoadFromAssemblyPath(Path.Combine(_directory, fileName)); } private static Version HostVersion(string simpleName) { object data = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"); string list = data as string; if (list == null) { return null; } foreach (string path in list.Split(Path.PathSeparator)) { if (path.Length == 0) { continue; } if (string.Equals(Path.GetFileNameWithoutExtension(path), simpleName, StringComparison.OrdinalIgnoreCase)) { return TryVersion(path); } } return null; } private static Version TryVersion(string path) { try { return AssemblyName.GetAssemblyName(path).Version; } catch (Exception) { return null; } // native or unreadable: not a candidate } } } '@ -ErrorAction Stop } $context = [Albuild.Compiler.TierLoadContext]::new($Tier.Directory) try { $assembly = $context.LoadFile('Microsoft.Dynamics.Nav.CodeAnalysis.dll') } catch { throw "The AL compiler in '$($Tier.Directory)' could not be loaded: $($_.Exception.Message)" } [PSCustomObject]@{ Context = $context Assembly = $assembly Tier = $Tier } } |