Modules/businessdev.ALbuild.RuntimePackages/Private/Get-BcRuntimeAppFile.ps1
|
function Get-BcRuntimeAppFile { <# .SYNOPSIS Returns the compiled .app of a product for one platform version, compiling it once if needed. .DESCRIPTION A runtime package is produced from an app that was compiled AGAINST the target platform - the manifest is stamped to that version and the ONPREM / BC<major> preprocessor symbols are applied - so every (product, platform version) pair needs its own build. Results are cached per platform version AND COUNTRY inside the worker's private work root and reused. That matters more than it looks: within one container the dependency apps are compiled first and then installed again for every product that depends on them. Without the cache, 365 business API would be recompiled once per dependent, on every platform version. The country belongs in the key. Run 27378 keyed on the platform version alone, and the two countries of 28.2.50931.51034 - concurrent workers in one slice - compiled into the same output file: 'error AL1028: An IO exception has happened when trying to write to output file ... because it is being used by another process', which then read as a failure of whichever product happened to ask next. The lock was the lucky outcome. Had the timing been kinder, one country would have returned the OTHER country's compiled app from the cache - same version number, different first-party symbols, and nothing in the log to say so. Everything happens inside -WorkRoot, on a copy of the project. The shared checkout is never touched, which is what allows several workers to run at once: the previous design rewrote app.json in place and restored it in a 'finally', so two concurrent versions would have fought over the same file. .PARAMETER Product The planned product entry (Name, Publisher, AppId, AppVersion, Projects). .PARAMETER WorkRoot The worker's private working folder. .PARAMETER PlatformVersion Target platform version. .PARAMETER Country Localisation of the artifact this build targets. Part of the cache key, not decoration: a BC artifact carries ONE localisation, the app is compiled against that country's first-party symbols, and the factory runs the countries of a platform version as concurrent workers. .PARAMETER Signing Splat for Invoke-BcAppSigning applied to the compiled app. Omitted = no signing. .PARAMETER SymbolFolder Package cache folders for the compiler (first-party symbols for this platform version). .OUTPUTS System.String - path to the compiled .app. #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [ValidateNotNull()] [object] $Product, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $WorkRoot, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $PlatformVersion, [string] $Country = '', [hashtable] $Signing, [string[]] $SymbolFolder = @() ) # Short suffix on purpose: these paths already carry a BC version and an app id, and Windows # PowerShell 5.1 enforces MAX_PATH hard. $slot = if ($Country) { "$PlatformVersion-$($Country.ToLowerInvariant())" } else { $PlatformVersion } $cacheFolder = Join-Path (Join-Path $WorkRoot 'apps') $slot if (-not (Test-Path -LiteralPath $cacheFolder)) { New-Item -ItemType Directory -Force -Path $cacheFolder | Out-Null } $cached = Join-Path $cacheFolder "$($Product.AppId).app" if (Test-Path -LiteralPath $cached) { return $cached } # The private, stamped copy - shared with the source engine, so the two routes cannot disagree on # the manifest they compile against. $projectFolder = Get-BcRuntimeProjectFolder -Product $Product -WorkRoot $WorkRoot ` -PlatformVersion $PlatformVersion -Country $Country $packageCache = @(Join-Path $projectFolder '.alpackages') + @($SymbolFolder) $compiled = Invoke-BcCompiler -ProjectFolder $projectFolder -Engine AlTool -OutputFolder $cacheFolder -PackageCachePath $packageCache if (-not $compiled.Success) { throw "AL compilation of '$($Product.Name)' for platform $PlatformVersion failed with exit code $($compiled.ExitCode)." } if ($Signing) { Invoke-BcAppSigning -Path $compiled.OutputFile @Signing } # Store under the app id: the compiler names the file from the manifest, and the dependency lookup # needs a name it can predict without re-reading it. Move-Item -LiteralPath $compiled.OutputFile -Destination $cached -Force return $cached } |