Modules/businessdev.ALbuild.RuntimePackages/Private/Invoke-BcRuntimeVersionBuildFromSource.ps1
|
function Invoke-BcRuntimeVersionBuildFromSource { <# .SYNOPSIS Builds every product of one platform version WITHOUT a container, straight from AL source. .DESCRIPTION The container route spends 78-94 % of a platform version on provisioning, and it spends it for two things: publishing the dependency chain so the service tier can resolve it, and asking that tier for the runtime package. New-BcRuntimePackageFromSource removes both - the AL compiler that ships inside the artifact emits the package itself. Measured on this catalogue: 11 s per package. The dependencies do not disappear, they change shape. A dependent still needs its chain, but only as SYMBOLS - which is exactly what the chain folder already holds, because Get-BcRuntimeAppFile compiles each chain app for this platform version and drops the .app there. So the loop below is the container loop with the publish, the package request and the teardown taken out; everything the caller sees - the result object, the per-product rows, the checkpoint - is unchanged, and that is deliberate: the report stages, the JUnit and the by-cause report all read those rows. What CANNOT be reproduced without a server: a legacy 'extensionsPermissionSet.xml' is migrated into PermissionSet objects by the service tier at publish time, and the compiler does not do that. Measured on Sanction Screen (modern permission sets, no legacy file) the two routes differ by one part in 154 - the compiler-generated .g.xlf. On an app that still carries the legacy file the difference is larger, and three products still do: License, API and ERiC. .PARAMETER Item One work item: PlatformVersion, ArtifactUrl, Country, Products[]. .PARAMETER WorkRoot The worker's private working folder. .PARAMETER OutputFolder Root of the '<appId>/<platformVersion>/' output layout. .PARAMETER Signing Splat for Invoke-BcAppSigning, applied to the produced package. .PARAMETER Started When the caller started this platform version, so the reported duration covers the whole thing. .OUTPUTS PSCustomObject: PlatformVersion, Country, Status, Seconds, Products[] - the same shape the container route returns. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNull()] [object] $Item, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $WorkRoot, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $OutputFolder, [hashtable] $Signing, [datetime] $Started = (Get-Date) ) $pv = "$($Item.PlatformVersion)" $country = "$(Get-BcRuntimeProperty -InputObject $Item -Name 'Country' -Default '')" $results = [System.Collections.Generic.List[object]]::new() $failedProducts = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $status = 'Succeeded' try { # Not caught: without the first-party symbols nothing here can compile, so it is a failure of the # platform version - which the outer catch reports on every product, with the real message. $artifact = Get-BcArtifact -ArtifactUrl $Item.ArtifactUrl $symbolFolders = @(Get-BcArtifactSymbolFolder -Artifact $artifact) Write-ALbuildLog "[$pv] Symbol folders: $($symbolFolders.Count) (no container: the compiler in the artifact emits the package)." # The chain, as symbols. A folder of its own, not the shared app cache, so a product never finds # ITSELF in its own package cache. $chainSymbolFolder = Join-Path (Join-Path $WorkRoot 'chainsym') "$pv$(if ($country) { "-$($country.ToLowerInvariant())" })" if (Test-Path -LiteralPath $chainSymbolFolder) { Remove-Item -LiteralPath $chainSymbolFolder -Recurse -Force } New-Item -ItemType Directory -Force -Path $chainSymbolFolder | Out-Null foreach ($product in @($Item.Products)) { $productStarted = Get-Date $chain = @(Get-BcRuntimeProperty -InputObject $product -Name 'DependencyChain' -Default @()) $blockedBy = @($chain | Where-Object { $failedProducts.Contains("$($_.Name)") } | ForEach-Object { "$($_.Name)" }) if ($blockedBy.Count -gt 0) { [void]$failedProducts.Add("$($product.Name)") $status = 'SucceededWithIssues' $results.Add([PSCustomObject]@{ Name = "$($product.Name)" Status = 'Failed' Seconds = 0 Error = "Skipped because its dependency failed on this platform version: $($blockedBy -join ', ')." File = $null }) Write-ALbuildLog -Level Warning "[$pv] $($product.Name): dependency failed ($($blockedBy -join ', ')); not attempted." continue } try { # The chain is compiled, not installed. Deepest first, so each entry compiles against the # ones before it. foreach ($dependency in $chain) { try { $dependencyApp = Get-BcRuntimeAppFile -Product $dependency -WorkRoot $WorkRoot -PlatformVersion $pv ` -Country $country -Signing $Signing -SymbolFolder ($symbolFolders + @($chainSymbolFolder)) } catch { # A chain app that fails here fails for everyone; mark it by name so the check # above skips the products that need it, including its own turn. [void]$failedProducts.Add("$($dependency.Name)") throw } Copy-Item -LiteralPath $dependencyApp -Destination (Join-Path $chainSymbolFolder (Split-Path -Leaf $dependencyApp)) -Force } # The product's own project, prepared exactly as the container route prepares it: private # copy, manifest stamped to this platform version, released version written in. $projectFolder = Get-BcRuntimeProjectFolder -Product $product -WorkRoot $WorkRoot -PlatformVersion $pv -Country $country $targetFolder = Join-Path $OutputFolder ($product.TargetPath -replace '/', [System.IO.Path]::DirectorySeparatorChar) if (-not (Test-Path -LiteralPath $targetFolder)) { New-Item -ItemType Directory -Force -Path $targetFolder | Out-Null } $runtimeApp = New-BcRuntimePackageFromSource -ProjectFolder $projectFolder -OutputFolder $targetFolder ` -SymbolFolder ($symbolFolders + @($chainSymbolFolder)) -PlatformPath $artifact.PlatformPath -Kind Runtime if ($Signing) { Invoke-BcAppSigning -Path $runtimeApp @Signing } $targetFile = Join-Path $targetFolder "$($product.FileName)" if ("$runtimeApp" -ne "$targetFile") { Move-Item -LiteralPath $runtimeApp -Destination $targetFile -Force } $results.Add([PSCustomObject]@{ Name = "$($product.Name)" Status = 'Succeeded' Seconds = [Math]::Round(((Get-Date) - $productStarted).TotalSeconds, 1) Error = $null File = $targetFile }) Write-ALbuildLog -Level Success "[$pv] $($product.Name): runtime package created." } catch { [void]$failedProducts.Add("$($product.Name)") $status = 'SucceededWithIssues' $results.Add([PSCustomObject]@{ Name = "$($product.Name)" Status = 'Failed' Seconds = [Math]::Round(((Get-Date) - $productStarted).TotalSeconds, 1) Error = $_.Exception.Message File = $null }) Write-ALbuildLog -Level Warning "[$pv] $($product.Name) failed: $($_.Exception.Message)" } } } catch { $status = 'Failed' Write-ALbuildLog -Level Warning "[$pv] Platform version failed: $($_.Exception.Message)" foreach ($product in @($Item.Products)) { if (@($results | Where-Object { $_.Name -eq "$($product.Name)" }).Count -gt 0) { continue } $results.Add([PSCustomObject]@{ Name = "$($product.Name)" Status = 'Failed' Seconds = 0 Error = "Platform version failed before this app was reached: $($_.Exception.Message)" File = $null }) } } return [PSCustomObject]@{ PlatformVersion = $pv Country = $country Status = $status Seconds = [Math]::Round(((Get-Date) - $Started).TotalSeconds, 1) Products = $results.ToArray() } } |