diff --git a/Actions/.Modules/CompileFromWorkspace.psm1 b/Actions/.Modules/CompileFromWorkspace.psm1
index 3557892e7..e67827190 100644
--- a/Actions/.Modules/CompileFromWorkspace.psm1
+++ b/Actions/.Modules/CompileFromWorkspace.psm1
@@ -846,6 +846,85 @@ function New-BuildOutputFile {
return $buildOutputPath
}
+<#
+.SYNOPSIS
+ Generates AppSourceCop.json files for app folders with baseline version information.
+.DESCRIPTION
+ For each app folder, creates an AppSourceCop.json file containing the previous version
+ of the app as a baseline for breaking change detection. Also includes mandatory affixes
+ and obsolete tag settings from the project settings.
+.PARAMETER AppFolders
+ Array of app folder paths to generate AppSourceCop.json for.
+.PARAMETER PreviousApps
+ Array of file paths to previous release .app files.
+.PARAMETER CompilerFolder
+ Path to the compiler folder containing the AL tool.
+.PARAMETER Settings
+ Hashtable containing the build settings with AppSourceCop configuration.
+#>
+function New-AppSourceCopJson {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string[]] $AppFolders,
+ [Parameter(Mandatory = $false)]
+ [string[]] $PreviousApps = @(),
+ [Parameter(Mandatory = $true)]
+ [string] $CompilerFolder,
+ [Parameter(Mandatory = $true)]
+ [hashtable] $Settings
+ )
+
+ # Extract version info from previous apps using the AL tool
+ $previousAppVersions = @{}
+ $alToolPath = Get-ALTool -CompilerFolder $CompilerFolder
+ foreach ($appFile in $PreviousApps) {
+ try {
+ $appInfo = RunAndCheck $alToolPath GetPackageManifest $appFile | ConvertFrom-Json
+ $key = "$($appInfo.Publisher)_$($appInfo.Name)"
+ $previousAppVersions[$key] = $appInfo.Version.ToString()
+ }
+ catch {
+ OutputWarning -message "Failed to read manifest from '$appFile': $($_.Exception.Message)"
+ }
+ }
+
+ # Create AppSourceCop.json for each app folder with the previous version as baseline and settings from the project configuration
+ foreach ($folder in $AppFolders) {
+ $appSourceCopJsonFile = Join-Path $folder "AppSourceCop.json"
+ $appSourceCopJson = @{}
+
+ # Set mandatory affixes if specified in settings
+ if ($Settings.appSourceCopMandatoryAffixes -and $Settings.appSourceCopMandatoryAffixes.Count -gt 0) {
+ $appSourceCopJson["mandatoryAffixes"] = @() + $Settings.appSourceCopMandatoryAffixes
+ }
+
+ # Set obsolete tag minimum allowed major.minor version if specified in settings
+ if ($Settings.obsoleteTagMinAllowedMajorMinor) {
+ $appSourceCopJson["obsoleteTagMinAllowedMajorMinor"] = $Settings.obsoleteTagMinAllowedMajorMinor
+ }
+
+ # Match previous app version by Publisher_Name
+ $appJsonPath = Join-Path $folder "app.json"
+ if (Test-Path $appJsonPath) {
+ $appJson = Get-Content -Path $appJsonPath -Raw | ConvertFrom-Json
+ $key = "$($appJson.Publisher)_$($appJson.Name)"
+ if ($previousAppVersions.ContainsKey($key)) {
+ $appSourceCopJson["Publisher"] = $appJson.Publisher
+ $appSourceCopJson["Name"] = $appJson.Name
+ $appSourceCopJson["Version"] = $previousAppVersions[$key]
+ }
+ }
+
+ if ($appSourceCopJson.Count -gt 0) {
+ Write-Host "Creating AppSourceCop.json for $folder"
+ $appSourceCopJson | ConvertTo-Json -Depth 99 | Set-Content -Encoding UTF8 $appSourceCopJsonFile
+ }
+ elseif (Test-Path $appSourceCopJsonFile) {
+ Remove-Item $appSourceCopJsonFile -Force
+ }
+ }
+}
+
Export-ModuleMember -Function Build-AppsInWorkspace
Export-ModuleMember -Function New-BuildOutputFile
Export-ModuleMember -Function Get-BuildMetadata
@@ -853,3 +932,4 @@ Export-ModuleMember -Function Get-CodeAnalyzers
Export-ModuleMember -Function Get-CustomAnalyzers
Export-ModuleMember -Function Get-AssemblyProbingPaths
Export-ModuleMember -Function Update-AppJsonProperties
+Export-ModuleMember -Function New-AppSourceCopJson
diff --git a/Actions/CompileApps/Compile.ps1 b/Actions/CompileApps/Compile.ps1
index 6689a6f78..653c4ef4b 100644
--- a/Actions/CompileApps/Compile.ps1
+++ b/Actions/CompileApps/Compile.ps1
@@ -14,7 +14,9 @@ Param(
[Parameter(HelpMessage = "RunId of the baseline workflow run", Mandatory = $false)]
[string] $baselineWorkflowRunId = '0',
[Parameter(HelpMessage = "SHA of the baseline workflow run", Mandatory = $false)]
- [string] $baselineWorkflowSHA = ''
+ [string] $baselineWorkflowSHA = '',
+ [Parameter(HelpMessage = "Path to folder containing previous release apps for AppSourceCop baseline", Mandatory = $false)]
+ [string] $previousAppsPath = ''
)
. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
@@ -30,7 +32,7 @@ $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $pr
$settings = CheckAppDependencyProbingPaths -settings $settings -token $token -baseFolder $baseFolder -project $project
# Check if there are any app folders or test app folders to compile
-if ($settings.appFolders.Count -eq 0 -and $settings.testFolders.Count -eq 0) {
+if ($settings.appFolders.Count -eq 0 -and $settings.testFolders.Count -eq 0 -and $settings.bcptTestFolders.Count -eq 0) {
Write-Host "No app folders or test app folders specified for compilation. Skipping compilation step."
return
}
@@ -117,13 +119,24 @@ try {
Write-Host "Incremental builds based on modified apps is not yet implemented."
}
- if ((-not $settings.skipUpgrade) -and $settings.enableAppSourceCop) {
- # TODO: Missing implementation of around using latest release as a baseline (skipUpgrade) / Appsourcecop.json baseline implementation (AB#620310)
- Write-Host "Checking for required upgrades using AppSourceCop..."
+ $previousApps = @()
+ if ($previousAppsPath -and (Test-Path $previousAppsPath)) {
+ $previousApps = @(Get-ChildItem -Path $previousAppsPath -Recurse -Filter "*.app" | ForEach-Object { $_.FullName })
+ if ($previousApps.Count -gt 0) {
+ # Copy previous apps to the package cache so AppSourceCop can resolve them
+ $previousApps | ForEach-Object {
+ Copy-Item -Path $_ -Destination $packageCachePath -Force
+ }
+ }
+ }
+
+ if ($settings.enableAppSourceCop) {
+ # Generate AppSourceCop.json files for app folders with baseline version and settings
+ New-AppSourceCopJson -AppFolders $settings.appFolders -PreviousApps $previousApps -CompilerFolder $compilerFolder -Settings $settings
}
# Update the app jsons with version number (and other properties) from the app manifest files
- Update-AppJsonProperties -Folders ($settings.appFolders + $settings.testFolders) `
+ Update-AppJsonProperties -Folders ($settings.appFolders + $settings.testFolders + $settings.bcptTestFolders) `
-MajorMinorVersion $versionNumber.MajorMinorVersion -BuildNumber $versionNumber.BuildNumber -RevisionNumber $versionNumber.RevisionNumber `
-BuildBy $buildMetadata.BuildBy -BuildUrl $buildMetadata.BuildUrl
@@ -174,6 +187,18 @@ try {
-AppType 'testApp'
}
+ if ($settings.bcptTestFolders.Count -gt 0) {
+ if (-not ($settings.enableCodeAnalyzersOnTestApps)) {
+ $buildParams.Analyzers = @()
+ }
+
+ # Compile BCPT Test Apps
+ $testAppFiles += Build-AppsInWorkspace @buildParams `
+ -Folders $settings.bcptTestFolders `
+ -OutFolder $testAppOutputFolder `
+ -AppType 'bcptApp'
+ }
+
} finally {
New-BuildOutputFile -BuildArtifactFolder $buildArtifactFolder -BuildOutputPath (Join-Path $projectFolder "BuildOutput.txt") -DisplayInConsole -FailOn $settings.failOn
}
diff --git a/Actions/CompileApps/action.yaml b/Actions/CompileApps/action.yaml
index e702881a8..6c32b12ab 100644
--- a/Actions/CompileApps/action.yaml
+++ b/Actions/CompileApps/action.yaml
@@ -37,6 +37,10 @@ inputs:
description: SHA of the baseline workflow run
required: false
default: ''
+ previousAppsPath:
+ description: Path to folder containing previous release apps for AppSourceCop baseline
+ required: false
+ default: ''
runs:
using: composite
steps:
@@ -51,9 +55,10 @@ runs:
_dependencyTestAppsJson: ${{ inputs.dependencyTestAppsJson }}
_baselineWorkflowRunId: ${{ inputs.baselineWorkflowRunId }}
_baselineWorkflowSHA: ${{ inputs.baselineWorkflowSHA }}
+ _previousAppsPath: ${{ inputs.previousAppsPath }}
run: |
${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Compile Apps" -Action {
- ${{ github.action_path }}/Compile.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -dependencyAppsJson $ENV:_dependencyAppsJson -dependencyTestAppsJson $ENV:_dependencyTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA
+ ${{ github.action_path }}/Compile.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -dependencyAppsJson $ENV:_dependencyAppsJson -dependencyTestAppsJson $ENV:_dependencyTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA -previousAppsPath $ENV:_previousAppsPath
}
branding:
icon: terminal
diff --git a/Actions/DownloadPreviousRelease/DownloadPreviousRelease.ps1 b/Actions/DownloadPreviousRelease/DownloadPreviousRelease.ps1
new file mode 100644
index 000000000..5067dab6a
--- /dev/null
+++ b/Actions/DownloadPreviousRelease/DownloadPreviousRelease.ps1
@@ -0,0 +1,38 @@
+Param(
+ [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)]
+ [string] $token,
+ [Parameter(HelpMessage = "Project folder", Mandatory = $false)]
+ [string] $project = ""
+)
+
+. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
+
+$baseFolder = (Get-BasePath)
+$projectFolder = Join-Path $baseFolder $project
+$previousAppsPath = Join-Path $projectFolder ".previousRelease"
+New-Item $previousAppsPath -ItemType Directory -Force | Out-Null
+
+OutputGroupStart -Message "Locating previous release"
+try {
+ $branchForRelease = if ($ENV:GITHUB_BASE_REF) { $ENV:GITHUB_BASE_REF } else { $ENV:GITHUB_REF_NAME }
+ $latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $branchForRelease
+ if ($latestRelease) {
+ Write-Host "Using $($latestRelease.name) (tag $($latestRelease.tag_name)) as previous release"
+ DownloadRelease -token $token -projects $project -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $latestRelease -path $previousAppsPath -mask "Apps" -unpack
+ $previousApps = @(Get-ChildItem -Path $previousAppsPath -Recurse -Filter "*.app" | ForEach-Object { $_.FullName })
+ Write-Host "Downloaded $($previousApps.Count) previous release app(s)"
+ }
+ else {
+ OutputWarning -message "No previous release found"
+ }
+}
+catch {
+ OutputError -message "Error trying to locate previous release. Error was $($_.Exception.Message)"
+ exit
+}
+finally {
+ OutputGroupEnd
+}
+
+# Output variable with path to previous apps for use in subsequent steps
+Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "PreviousAppsPath=$previousAppsPath"
diff --git a/Actions/DownloadPreviousRelease/README.md b/Actions/DownloadPreviousRelease/README.md
new file mode 100644
index 000000000..ae7d3e3a1
--- /dev/null
+++ b/Actions/DownloadPreviousRelease/README.md
@@ -0,0 +1,26 @@
+# Download Previous Release
+
+Downloads the latest release apps for use as a baseline in AppSourceCop validation and upgrade testing.
+
+## INPUT
+
+### ENV variables
+
+| Name | Description |
+| :-- | :-- |
+| Settings | env.Settings must be set by a prior call to the ReadSettings Action |
+
+### Parameters
+
+| Name | Required | Description | Default value |
+| :-- | :-: | :-- | :-- |
+| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell |
+| project | | The AL-Go project for which to download previous release apps | '.' |
+
+## OUTPUT
+
+### OUTPUT variables
+
+| Name | Description |
+| :-- | :-- |
+| PreviousAppsPath | Path to the folder containing the downloaded previous release apps. |
diff --git a/Actions/DownloadPreviousRelease/action.yaml b/Actions/DownloadPreviousRelease/action.yaml
new file mode 100644
index 000000000..de2ac603a
--- /dev/null
+++ b/Actions/DownloadPreviousRelease/action.yaml
@@ -0,0 +1,35 @@
+name: Download Previous Release
+author: Microsoft Corporation
+inputs:
+ shell:
+ description: Shell in which you want to run the action (powershell or pwsh)
+ required: false
+ default: powershell
+ token:
+ description: The GitHub token running the action
+ required: false
+ default: ${{ github.token }}
+ project:
+ description: Project folder
+ required: false
+ default: '.'
+outputs:
+ PreviousAppsPath:
+ description: Path to the folder containing the downloaded previous release apps.
+ value: ${{ steps.DownloadPreviousRelease.outputs.PreviousAppsPath }}
+runs:
+ using: composite
+ steps:
+ - name: run
+ shell: ${{ inputs.shell }}
+ id: DownloadPreviousRelease
+ env:
+ _token: ${{ inputs.token }}
+ _project: ${{ inputs.project }}
+ run: |
+ ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DownloadPreviousRelease" -Action {
+ ${{ github.action_path }}/DownloadPreviousRelease.ps1 -token $ENV:_token -project $ENV:_project
+ }
+branding:
+ icon: terminal
+ color: blue
diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1
index f539ae41f..20c9d438b 100644
--- a/Actions/RunPipeline/RunPipeline.ps1
+++ b/Actions/RunPipeline/RunPipeline.ps1
@@ -14,7 +14,9 @@ Param(
[Parameter(HelpMessage = "RunId of the baseline workflow run", Mandatory = $false)]
[string] $baselineWorkflowRunId = '0',
[Parameter(HelpMessage = "SHA of the baseline workflow run", Mandatory = $false)]
- [string] $baselineWorkflowSHA = ''
+ [string] $baselineWorkflowSHA = '',
+ [Parameter(HelpMessage = "Path to folder containing previous release apps for upgrade testing", Mandatory = $false)]
+ [string] $previousAppsPath = ''
)
$containerBaseFolder = $null
@@ -138,17 +140,12 @@ try {
OutputDebug -message "Build artifacts folder $buildArtifactFolder already exists. Previous build artifacts might interfere with the current build."
}
- # When using workspace compilation, apps are already compiled - pass empty folders to Run-AlPipeline
- if ($settings.workspaceCompilation.enabled) {
- $appFolders = @()
- $testFolders = @()
- $bcptTestFolders = $settings.bcptTestFolders
- }
- else {
- $appFolders = $settings.appFolders
- $testFolders = $settings.testFolders
- $bcptTestFolders = $settings.bcptTestFolders
- }
+ # When using workspace compilation, apps are already compiled in .buildartifacts.
+ # Pass appFolders/testFolders to Run-AlPipeline so its prebuilt detection picks them up
+ # and publishes them through the proper upgrade testing path.
+ $appFolders = $settings.appFolders
+ $testFolders = $settings.testFolders
+ $bcptTestFolders = $settings.bcptTestFolders
if ((-not $settings.workspaceCompilation.enabled) -and $baselineWorkflowSHA -and $baselineWorkflowRunId -ne '0' -and $settings.incrementalBuilds.mode -eq 'modifiedApps') {
# Incremental builds are enabled and we are only building modified apps
@@ -275,33 +272,16 @@ try {
$previousApps = @()
if (!$settings.skipUpgrade) {
- if ($settings.workspaceCompilation.enabled) {
- OutputWarning -message "skipUpgrade is ignored when workspaceCompilation is enabled." # TODO: Missing implementation when workspace compilation is enabled (AB#620310)
- } else {
- OutputGroupStart -Message "Locating previous release"
- try {
- $branchForRelease = if ($ENV:GITHUB_BASE_REF) { $ENV:GITHUB_BASE_REF } else { $ENV:GITHUB_REF_NAME }
- $latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $branchForRelease
- if ($latestRelease) {
- Write-Host "Using $($latestRelease.name) (tag $($latestRelease.tag_name)) as previous release"
- $artifactsFolder = Join-Path $baseFolder "artifacts"
- if(-not (Test-Path $artifactsFolder)) {
- New-Item $artifactsFolder -ItemType Directory | Out-Null
- }
- DownloadRelease -token $token -projects $project -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $latestRelease -path $artifactsFolder -mask "Apps"
- $previousApps += @(Get-ChildItem -Path $artifactsFolder | ForEach-Object { $_.FullName })
- }
- else {
- OutputWarning -message "No previous release found"
- }
- }
- catch {
- OutputError -message "Error trying to locate previous release. Error was $($_.Exception.Message)"
- exit
- }
- finally {
- OutputGroupEnd
+ if ($previousAppsPath -and (Test-Path $previousAppsPath)) {
+ # Use previously downloaded release apps
+ $previousApps = @(Get-ChildItem -Path $previousAppsPath -Recurse -Filter "*.app" | ForEach-Object { $_.FullName })
+ if ($previousApps.Count -gt 0) {
+ Write-Host "Using $($previousApps.Count) previous release app(s) from $previousAppsPath"
+ } else {
+ OutputWarning -message "No .app files found in '$previousAppsPath' for upgrade testing."
}
+ } else {
+ OutputWarning -message "Could not locate previous release apps for upgrade testing."
}
}
diff --git a/Actions/RunPipeline/action.yaml b/Actions/RunPipeline/action.yaml
index cd5003f7b..623306f36 100644
--- a/Actions/RunPipeline/action.yaml
+++ b/Actions/RunPipeline/action.yaml
@@ -37,6 +37,10 @@ inputs:
description: SHA of the baseline workflow run
required: false
default: ''
+ previousAppsPath:
+ description: Path to folder containing previous release apps for upgrade testing
+ required: false
+ default: ''
runs:
using: composite
steps:
@@ -51,9 +55,10 @@ runs:
_installTestAppsJson: ${{ inputs.installTestAppsJson }}
_baselineWorkflowRunId: ${{ inputs.baselineWorkflowRunId }}
_baselineWorkflowSHA: ${{ inputs.baselineWorkflowSHA }}
+ _previousAppsPath: ${{ inputs.previousAppsPath }}
run: |
${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "RunPipeline" -Action {
- ${{ github.action_path }}/RunPipeline.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA
+ ${{ github.action_path }}/RunPipeline.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA -previousAppsPath $ENV:_previousAppsPath
}
branding:
icon: terminal
diff --git a/Scenarios/settings.md b/Scenarios/settings.md
index 6a54b92c4..ed1274ee3 100644
--- a/Scenarios/settings.md
+++ b/Scenarios/settings.md
@@ -131,7 +131,7 @@ The repository settings are only read from the repository settings file (.github
| assignPremiumPlan | Setting assignPremiumPlan to true in your project setting file, causes the build container to be created with the AssignPremiumPlan set. This causes the auto-created user to have Premium Plan enabled. This setting is needed if your tests require premium plan enabled. | false |
| enableTaskScheduler | Setting enableTaskScheduler to true in your project setting file, causes the build container to be created with the Task Scheduler running. | false |
| useCompilerFolder | Setting useCompilerFolder to true causes your pipelines to use containerless compiling. Unless you also set **doNotPublishApps** to true, setting useCompilerFolder to true won't give you any performance advantage, since AL-Go for GitHub will still need to create a container in order to publish and test the apps. In the future, publishing and testing will be split from building and there will be other options for getting an instance of Business Central for publishing and testing. **Note** when using UseCompilerFolder you need to sign apps using the new signing mechanism described [here](../Scenarios/Codesigning.md). | false |
-| workspaceCompilation | **PREVIEW:** Configuration for workspace compilation. This uses the AL tool to compile all apps in the workspace in a single operation, which can improve build performance for repositories with multiple apps. Like **useCompilerFolder**, this is containerless compiling.
**enabled** - Set to true to enable workspace compilation. Default: false.
**parallelism** - The number of parallel compilation processes. Set to 0 or -1 to use all available processors. Default: 1.
**Current limitations:**