Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Actions/.Modules/ReadSettings.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ function GetDefaultSettings
"templateBranch" = ""
"appDependencyProbingPaths" = @()
"useProjectDependencies" = $false
"staticProjectDependencies" = @()
"skipDependenciesBuiltByCurrentProject" = $false
"runs-on" = "windows-latest"
"shell" = ""
"githubRunner" = ""
Expand Down
11 changes: 11 additions & 0 deletions Actions/.Modules/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,17 @@
"type": "boolean",
"description": "See https://aka.ms/ALGoSettings#useprojectdependencies"
},
"staticProjectDependencies": {
"type": "array",
"items": {
"type": "string"
},
"description": "See https://aka.ms/ALGoSettings#staticprojectdependencies"
},
"skipDependenciesBuiltByCurrentProject": {
"type": "boolean",
"description": "See https://aka.ms/ALGoSettings#skipdependenciesbuiltbycurrentproject"
},
"runs-on": {
"type": "string",
"minLength": 1,
Expand Down
59 changes: 45 additions & 14 deletions Actions/AL-Go-Helper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1763,13 +1763,24 @@ Function AnalyzeProjectDependencies {
# If the project is using project dependencies, add the unknown dependencies to the list of dependencies
# If not, the unknown dependencies are ignored
$dependenciesForProject = @()
$staticProjectDeps = @()
if ($projectSettings.useProjectDependencies -eq $true) {
$dependenciesForProject = @($unknownDependencies | ForEach-Object { $_.Split(':')[0] })
# Check if staticProjectDependencies are defined (explicit list of project names)
if ($projectSettings.staticProjectDependencies -and $projectSettings.staticProjectDependencies.Count -gt 0) {
# Use static project dependencies - bypass automatic App ID-based discovery
$staticProjectDeps = @($projectSettings.staticProjectDependencies)
Write-Host "Using static project dependencies for '$project': $($staticProjectDeps -join ', ')"
}
else {
# Use automatic App ID-based discovery
$dependenciesForProject = @($unknownDependencies | ForEach-Object { $_.Split(':')[0] })
}
}

$appDependencies."$project" = @{
"apps" = $apps
"dependencies" = $dependenciesForProject
"apps" = $apps
"dependencies" = $dependenciesForProject
"staticProjectDependencies" = $staticProjectDeps
}
}
# AppDependencies is a hashtable with the following structure
Expand All @@ -1794,18 +1805,38 @@ Function AnalyzeProjectDependencies {
# The loop continues until all projects have been added to the build order
foreach($project in $projects) {
Write-Host "- $project"
# Find all project dependencies for the current project
$dependencies = $appDependencies."$project".dependencies
# Loop through all dependencies and locate the projects, containing the apps for which the current project has a dependency
# Check if static project dependencies are defined (explicit list bypasses App ID-based discovery)
$staticDeps = $appDependencies."$project".staticProjectDependencies
$foundDependencies = @()
foreach($dependency in $dependencies) {
# Find the project that contains the app for which the current project has a dependency
$depProjects = @($projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency })
# Add this project and all projects on which that project has a dependency to the list of dependencies for the current project
foreach($depProject in $depProjects) {
$foundDependencies += $depProject
if ($projectDependencies.Keys -contains $depProject) {
$foundDependencies += $projectDependencies."$depProject"

if ($staticDeps -and $staticDeps.Count -gt 0) {
# Use static project dependencies directly (only include projects that are in the build)
foreach($staticDep in $staticDeps) {
if ($projects -contains $staticDep) {
$foundDependencies += $staticDep
# Also add transitive dependencies from the static dependency
if ($projectDependencies.Keys -contains $staticDep) {
$foundDependencies += $projectDependencies."$staticDep"
}
}
else {
Write-Host "Static dependency '$staticDep' for project '$project' is not in the build (may have been built already)"
}
}
}
else {
# Use automatic App ID-based discovery
$dependencies = $appDependencies."$project".dependencies
# Loop through all dependencies and locate the projects, containing the apps for which the current project has a dependency
foreach($dependency in $dependencies) {
# Find the project that contains the app for which the current project has a dependency
$depProjects = @($projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency })
# Add this project and all projects on which that project has a dependency to the list of dependencies for the current project
foreach($depProject in $depProjects) {
$foundDependencies += $depProject
if ($projectDependencies.Keys -contains $depProject) {
$foundDependencies += $projectDependencies."$depProject"
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,101 @@ function DownloadDependenciesFromCurrentBuild {
return $downloadedDependencies
}

<#
.Synopsis
Gets the App IDs for all apps that a project will build.
.Description
Reads the app.json files from all app folders in the project and returns an array of App IDs.
This is used to identify apps that the current project builds, so they can be excluded from
downloaded dependencies (to avoid overwriting locally-built apps with ones from dependency projects).
#>
function GetProjectAppIds {
param(
$baseFolder,
$project
)

$projectSettings = ReadSettings -project $project -baseFolder $baseFolder
ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings)

Push-Location $baseFolder
try {
$folders = @($projectSettings.appFolders) + @($projectSettings.testFolders) + @($projectSettings.bcptTestFolders) |
Where-Object { $_ } |
ForEach-Object {
$resolvedPath = Join-Path $baseFolder "$project/$_"
if (Test-Path $resolvedPath) {
return (Resolve-Path $resolvedPath -Relative)
}
} | Where-Object { $_ }
}
finally {
Pop-Location
}

if (-not $folders -or $folders.Count -eq 0) {
return @()
}

$unknownDependencies = @()
$appIds = @()
Sort-AppFoldersByDependencies -appFolders $folders -baseFolder $baseFolder -WarningAction SilentlyContinue -unknownDependencies ([ref]$unknownDependencies) -knownApps ([ref]$appIds) | Out-Null

return $appIds
}

<#
.Synopsis
Filters downloaded dependencies by excluding apps that the current project builds.
.Description
When project B depends on project A, and both projects build an app with the same App ID,
we should use the one built by project B (the current project) instead of downloading it from project A.
This function filters out downloaded apps whose App ID matches one that the current project will build.
#>
function FilterDependenciesByAppId {
param(
[string[]] $downloadedDependencies,
[string[]] $excludeAppIds
)

if (-not $excludeAppIds -or $excludeAppIds.Count -eq 0) {
return $downloadedDependencies
}

$filteredDependencies = @()
foreach ($dependency in $downloadedDependencies) {
$appPath = $dependency.Trim('()')

if (-not (Test-Path $appPath)) {
# If the file doesn't exist, keep it in the list (might be a URL or other reference)
$filteredDependencies += $dependency
continue
}

try {
$appJson = Get-AppJsonFromAppFile -appFile $appPath
if ($excludeAppIds -contains $appJson.Id) {
Write-Host "Excluding downloaded app '$($appJson.Name)' (ID: $($appJson.Id)) - this app is built by the current project"
# Delete the downloaded file to avoid confusion
Remove-Item -Path $appPath -Force -ErrorAction SilentlyContinue
continue
}
}
catch {
Write-Host "Warning: Could not read app info from $appPath - keeping the dependency. Error: $($_.Exception.Message)"
}

$filteredDependencies += $dependency
}

return $filteredDependencies
}

. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)

# Import BcContainerHelper module for Sort-AppFoldersByDependencies function
DownloadAndImportBcContainerHelper

Write-Host "Downloading dependencies for project '$project'. BuildMode: $buildMode, Base Folder: $baseFolder, Destination Path: $destinationPath"

$downloadedDependencies = @()
Expand All @@ -123,6 +216,25 @@ Write-Host "::group::Downloading project dependencies from probing paths"
$downloadedDependencies += DownloadDependenciesFromProbingPaths -baseFolder $baseFolder -project $project -destinationPath $destinationPath -token $token
Write-Host "::endgroup::"

# Filter out apps that the current project builds (to avoid overwriting locally-built apps with dependency versions)
# This is controlled by the 'skipDependenciesBuiltByCurrentProject' setting (defaults to false for backwards compatibility)
$settings = $env:Settings | ConvertFrom-Json
if ($settings.skipDependenciesBuiltByCurrentProject) {
Write-Host "::group::Filtering dependencies by App ID (skipDependenciesBuiltByCurrentProject is enabled)"
$currentProjectAppIds = GetProjectAppIds -baseFolder $baseFolder -project $project
if ($currentProjectAppIds -and $currentProjectAppIds.Count -gt 0) {
OutputMessageAndArray -message "App IDs built by current project '$project'" -arrayOfStrings $currentProjectAppIds
$downloadedDependencies = FilterDependenciesByAppId -downloadedDependencies $downloadedDependencies -excludeAppIds $currentProjectAppIds
}
else {
Write-Host "No apps found in current project '$project' - no filtering needed"
}
Write-Host "::endgroup::"
}
else {
Write-Host "Dependency filtering by App ID is disabled (skipDependenciesBuiltByCurrentProject is false or not set)"
}

$downloadedApps = @()
$downloadedTestApps = @()

Expand Down
39 changes: 39 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,45 @@ Previously, when running the "Publish To Environment" workflow with an environme

Now, the workflow will fail with a clear error message if the specified environment doesn't exist. If you intentionally want to deploy to a new environment that hasn't been configured yet, you can check the **Create environment if it does not exist** checkbox when running the workflow.

### New settings for project dependency handling

Two new settings have been added to provide more control over how project dependencies are resolved when using `useProjectDependencies`:

#### skipDependenciesBuiltByCurrentProject

When set to `true`, downloaded dependency apps that have the same App ID as apps built by the current project will be excluded. This prevents dependency artifacts from overwriting locally-built apps.

This is useful in scenarios where multiple projects build the same app (e.g., country-specific versions of a base app with the same App ID). Without this setting, the dependency resolution might download an app from a dependency project that overwrites the locally-built version.

Example configuration in project settings:

```json
{
"skipDependenciesBuiltByCurrentProject": true
}
```

Defaults to `false` for backwards compatibility.

#### staticProjectDependencies

When using `useProjectDependencies`, you can now explicitly specify which projects to depend on instead of relying on automatic App ID-based discovery. This gives you full control over the build order and dependency resolution.

This is useful when multiple projects build apps with the same App ID, but you want to control exactly which project's artifacts are used as dependencies.

Example configuration in project settings:

```json
{
"useProjectDependencies": true,
"staticProjectDependencies": ["W1"]
}
```

In this example, the project will only depend on the `W1` project, even if other projects (like `GB`) also build apps that match the project's dependencies. The project will build in parallel with other projects that also only depend on `W1`.

Defaults to an empty array `[]`, which means automatic App ID-based discovery is used (existing behavior).

### Set default values for workflow inputs

The `workflowDefaultInputs` setting now also applies to `workflow_call` inputs when an input with the same name exists for `workflow_dispatch`.
Expand Down
Loading
Loading