Skip to content

Commit

Permalink
Allow useProjectDependencies as a project-level setting (#1393)
Browse files Browse the repository at this point in the history
Allow useProjectDependencies as a project-level setting

---------

Co-authored-by: Maria Zhelezova <[email protected]>
Co-authored-by: Freddy Kristiansen <[email protected]>
  • Loading branch information
3 people authored Jan 20, 2025
1 parent dfa8b44 commit 120540b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 36 deletions.
50 changes: 31 additions & 19 deletions Actions/AL-Go-Helper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2090,11 +2090,11 @@ function CheckAndCreateProjectFolder {
Function AnalyzeProjectDependencies {
Param(
[string] $baseFolder,
[string[]] $projects,
[ref] $buildAlso,
[ref] $projectDependencies
[string[]] $projects
)

$additionalProjectsToBuild = @{}
$projectDependencies = @{}
$appDependencies = @{}
Write-Host "Analyzing projects in $baseFolder"

Expand Down Expand Up @@ -2123,9 +2123,17 @@ Function AnalyzeProjectDependencies {
$unknownDependencies = @()
$apps = @()
Sort-AppFoldersByDependencies -appFolders $folders -baseFolder $baseFolder -WarningAction SilentlyContinue -unknownDependencies ([ref]$unknownDependencies) -knownApps ([ref]$apps) | Out-Null

# If the project is using project dependencies, add the unknown dependencies to the list of dependencies
# If not, the unknown dependencies are ignored
$dependenciesForProject = @()
if ($projectSettings.useProjectDependencies -eq $true) {
$dependenciesForProject = @($unknownDependencies | ForEach-Object { $_.Split(':')[0] })
}

$appDependencies."$project" = @{
"apps" = $apps
"dependencies" = @($unknownDependencies | ForEach-Object { $_.Split(':')[0] })
"dependencies" = $dependenciesForProject
}
}
# AppDependencies is a hashtable with the following structure
Expand Down Expand Up @@ -2160,42 +2168,42 @@ Function AnalyzeProjectDependencies {
# 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.Value.Keys -contains $depProject) {
$foundDependencies += $projectDependencies.value."$depProject"
if ($projectDependencies.Keys -contains $depProject) {
$foundDependencies += $projectDependencies."$depProject"
}
}
}
$foundDependencies = @($foundDependencies | Select-Object -Unique)
# foundDependencies now contains all projects that the current project has a dependency on
# Update ref variable projectDependencies for this project
if ($projectDependencies.Value.Keys -notcontains $project) {
if ($projectDependencies.Keys -notcontains $project) {
# Loop through the list of projects for which we already built a dependency list
# Update the dependency list for that project if it contains the current project, which might lead to a changed dependency list
# This is needed because we are looping through the projects in a any order
$keys = @($projectDependencies.value.Keys)
$keys = @($projectDependencies.Keys)
foreach($key in $keys) {
if ($projectDependencies.value."$key" -contains $project) {
$projectDeps = @( $projectDependencies.value."$key" )
$projectDependencies.value."$key" = @( @($projectDeps + $foundDependencies) | Select-Object -Unique )
if (Compare-Object -ReferenceObject $projectDependencies.value."$key" -differenceObject $projectDeps) {
if ($projectDependencies."$key" -contains $project) {
$projectDeps = @( $projectDependencies."$key" )
$projectDependencies."$key" = @( @($projectDeps + $foundDependencies) | Select-Object -Unique )
if (Compare-Object -ReferenceObject $projectDependencies."$key" -differenceObject $projectDeps) {
Write-Host "Add ProjectDependencies $($foundDependencies -join ',') to $key"
}
}
}
Write-Host "Set ProjectDependencies for $project to $($foundDependencies -join ',')"
$projectDependencies.value."$project" = $foundDependencies
$projectDependencies."$project" = $foundDependencies
}
if ($foundDependencies) {
Write-Host "Found dependencies to projects: $($foundDependencies -join ", ")"
# Add project to buildAlso for this dependency to ensure that this project also gets build when the dependency is built
# Add project to additionalProjectsToBuild for this dependency to ensure that this project also gets build when the dependency is built
foreach($dependency in $foundDependencies) {
if ($buildAlso.value.Keys -contains $dependency) {
if ($buildAlso.value."$dependency" -notcontains $project) {
$buildAlso.value."$dependency" += @( $project )
if ($additionalProjectsToBuild.Keys -contains $dependency) {
if ($additionalProjectsToBuild."$dependency" -notcontains $project) {
$additionalProjectsToBuild."$dependency" += @( $project )
}
}
else {
$buildAlso.value."$dependency" = @( $project )
$additionalProjectsToBuild."$dependency" = @( $project )
}
}
}
Expand All @@ -2215,7 +2223,11 @@ Function AnalyzeProjectDependencies {
$no++
}

return @($projectsOrder)
return [PSCustomObject]@{
FullProjectsOrder = $projectsOrder
AdditionalProjectsToBuild = $additionalProjectsToBuild
ProjectDependencies = $projectDependencies
}
}

function GetBaseFolder {
Expand Down
5 changes: 2 additions & 3 deletions Actions/CheckForUpdates/CheckForUpdates.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ $templateUrl = $templateUrl -replace "^(https:\/\/)(www\.)(.*)$", '$1$3'
# TemplateUrl is now always a full url + @ and a branch name

# CheckForUpdates will read all AL-Go System files from the Template repository and compare them to the ones in the current repository
# CheckForUpdates will apply changes to the AL-Go System files based on AL-Go repo settings, such as "runs-on", "useProjectDependencies", etc.
# CheckForUpdates will apply changes to the AL-Go System files based on AL-Go repo settings, such as "runs-on" etc.
# if $update is set to Y, CheckForUpdates will also update the AL-Go System files in the current repository using a PR or a direct commit (if $directCommit is set to true)
# if $update is set to N, CheckForUpdates will only check for updates and output a warning if there are updates available
# if $downloadLatest is set to true, CheckForUpdates will download the latest version of the template repository, else it will use the templateSha setting in the .github/AL-Go-Settings file
Expand Down Expand Up @@ -115,11 +115,10 @@ $updateFiles = @()
# $removeFiles will hold an array of files, which needs to be removed
$removeFiles = @()

# If useProjectDependencies is true, we need to calculate the dependency depth for all projects
# Dependency depth determines how many build jobs we need to run sequentially
# Every build job might spin up multiple jobs in parallel to build the projects without unresolved deependencies
$depth = 1
if ($repoSettings.useProjectDependencies -and $projects.Count -gt 1) {
if ($projects.Count -gt 1) {
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\DetermineProjectsToBuild\DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking
$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder -buildAllProjects $true -maxBuildDepth 100
$depth = $buildOrder.Count
Expand Down
19 changes: 5 additions & 14 deletions Actions/DetermineProjectsToBuild/DetermineProjectsToBuild.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ function Get-ProjectsToBuild {
Write-Host "Found AL-Go Projects: $($projects -join ', ')"

$projectsToBuild = @()
$projectDependencies = @{}
$projectsOrderToBuild = @()

if ($projects) {
Expand All @@ -238,21 +237,13 @@ function Get-ProjectsToBuild {
$projectsToBuild = @($projects | Where-Object { ShouldBuildProject -baseFolder $baseFolder -project $_ -modifiedFiles $modifiedFilesFullPaths })
}

if($settings.useProjectDependencies) {
$buildAlso = @{}
# Calculate the full projects order
$projectBuildInfo = AnalyzeProjectDependencies -baseFolder $baseFolder -projects $projects

# Calculate the full projects order
$fullProjectsOrder = AnalyzeProjectDependencies -baseFolder $baseFolder -projects $projects -buildAlso ([ref]$buildAlso) -projectDependencies ([ref]$projectDependencies)

$projectsToBuild = @($projectsToBuild | ForEach-Object { $_; if ($buildAlso.Keys -contains $_) { $buildAlso."$_" } } | Select-Object -Unique)
}
else {
# Use a flatten build order (all projects on the same level)
$fullProjectsOrder = @(@{ 'projects' = $projectsToBuild; 'projectsCount' = $projectsToBuild.Count})
}
$projectsToBuild = @($projectsToBuild | ForEach-Object { $_; if ($projectBuildInfo.AdditionalProjectsToBuild.Keys -contains $_) { $projectBuildInfo.AdditionalProjectsToBuild."$_" } } | Select-Object -Unique)

# Create a project order based on the projects to build
foreach($depth in $fullProjectsOrder) {
foreach($depth in $projectBuildInfo.FullProjectsOrder) {
$projectsOnDepth = @($depth.projects | Where-Object { $projectsToBuild -contains $_ })

if ($projectsOnDepth) {
Expand Down Expand Up @@ -281,7 +272,7 @@ function Get-ProjectsToBuild {
throw "The build depth is too deep, the maximum build depth is $maxBuildDepth. You need to run 'Update AL-Go System Files' to update the workflows"
}

return $projects, $projectsToBuild, $projectDependencies, $projectsOrderToBuild
return $projects, $projectsToBuild, $projectBuildInfo.projectDependencies, $projectsOrderToBuild
}
finally {
Pop-Location
Expand Down
104 changes: 104 additions & 0 deletions Tests/DetermineProjectsToBuild.Test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,10 @@ Describe "Get-ProjectsToBuild" {

#Add settings file
$alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true }
New-Item -Path "$baseFolder/.github" -type Directory -Force
$alGoSettings | ConvertTo-Json -Depth 99 -Compress | Out-File (Join-Path $baseFolder ".github/AL-Go-Settings.json") -Encoding UTF8

# Add settings as environment variable to simulate we've run ReadSettings
$env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress

$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder
Expand Down Expand Up @@ -661,6 +665,102 @@ Describe "Get-ProjectsToBuild" {
$buildOrder[1].buildDimensions[0].project | Should -BeExactly "Project2"
}

It 'loads dependent projects correctly, if useProjectDependencies is set to false in a project setting' {
# Add three dependent projects
# Project 1
# Project 2 depends on Project 1 - useProjectDependencies is set to true from the repo settings
# Project 3 depends on Project 1, but has useProjectDependencies set to false in the project settings
$dependecyAppFile = @{ id = '83fb8305-4079-415d-a25d-8132f0436fd1'; name = 'First App'; publisher = 'Contoso'; version = '1.0.0.0'; dependencies = @() }
New-Item -Path "$baseFolder/Project1/.AL-Go/settings.json" -type File -Force
New-Item -Path "$baseFolder/Project1/app/app.json" -Value (ConvertTo-Json $dependecyAppFile -Depth 10) -type File -Force

$dependantAppFile = @{ id = '83fb8305-4079-415d-a25d-8132f0436fd2'; name = 'Second App'; publisher = 'Contoso'; version = '1.0.0.0'; dependencies = @(@{id = '83fb8305-4079-415d-a25d-8132f0436fd1'; name = 'First App'; publisher = 'Contoso'; version = '1.0.0.0'} ) }
New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force
New-Item -Path "$baseFolder/Project2/app/app.json" -Value (ConvertTo-Json $dependantAppFile -Depth 10) -type File -Force

# Third project that also depends on the first project, but has useProjectDependencies set to false
$dependantAppFile3 = @{ id = '83fb8305-4079-415d-a25d-8132f0436fd3'; name = 'Third App'; publisher = 'Contoso'; version = '1.0.0.0'; dependencies = @(@{id = '83fb8305-4079-415d-a25d-8132f0436fd1'; name = 'First App'; publisher = 'Contoso'; version = '1.0.0.0'} ) }
New-Item -Path "$baseFolder/Project3/.AL-Go/settings.json" -type File -Force
@{ useProjectDependencies = $false } | ConvertTo-Json -Depth 99 -Compress | Out-File (Join-Path $baseFolder "Project3/.AL-Go/settings.json") -Encoding UTF8
New-Item -Path "$baseFolder/Project3/app/app.json" -Value (ConvertTo-Json $dependantAppFile3 -Depth 10) -type File -Force

#Add settings file
$alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true }
New-Item -Path "$baseFolder/.github" -type Directory -Force
$alGoSettings | ConvertTo-Json -Depth 99 -Compress | Out-File (Join-Path $baseFolder ".github/AL-Go-Settings.json") -Encoding UTF8

# Add settings as environment variable to simulate we've run ReadSettings
$env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress

$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder

$allProjects | Should -BeExactly @("Project1", "Project2", "Project3")
$projectsToBuild | Should -BeExactly @('Project1', 'Project2', 'Project3')

$projectDependencies | Should -BeOfType System.Collections.Hashtable
$projectDependencies['Project1'] | Should -BeExactly @()
$projectDependencies['Project2'] | Should -BeExactly @("Project1")
$projectDependencies['Project3'] | Should -BeExactly @()

# Build order should have the following structure:
#[
#{
# "buildDimensions": [
# {
# "projectName": "Project1",
# "buildMode": "Default",
# "project": "Project1",
# "githubRunnerShell": "powershell",
# "gitHubRunner": "\"windows-latest\""
# },
# {
# "projectName": "Project3",
# "buildMode": "Default",
# "project": "Project3",
# "githubRunnerShell": "powershell",
# "gitHubRunner": "\"windows-latest\""
# }
# ],
# "projectsCount": 2,
# "projects": [
# "Project1",
# "Project3"
# ]
#},
#{
# "buildDimensions": [
# {
# "projectName": "Project2",
# "buildMode": "Default",
# "project": "Project2",
# "githubRunnerShell": "powershell",
# "gitHubRunner": "\"windows-latest\""
# }
# ],
# "projectsCount": 1,
# "projects": [
# "Project2"
# ]
#}
#]
$buildOrder.Count | Should -BeExactly 2
$buildOrder[0] | Should -BeOfType System.Collections.Hashtable
$buildOrder[0].projects | Should -BeExactly @("Project1", "Project3")
$buildOrder[0].projectsCount | Should -BeExactly 2
$buildOrder[0].buildDimensions.Count | Should -BeExactly 2
$buildOrder[0].buildDimensions[0].buildMode | Should -BeExactly "Default"
$buildOrder[0].buildDimensions[0].project | Should -BeExactly "Project1"
$buildOrder[0].buildDimensions[1].buildMode | Should -BeExactly "Default"
$buildOrder[0].buildDimensions[1].project | Should -BeExactly "Project3"

$buildOrder[1] | Should -BeOfType System.Collections.Hashtable
$buildOrder[1].projects | Should -BeExactly @("Project2")
$buildOrder[1].projectsCount | Should -BeExactly 1
$buildOrder[1].buildDimensions.Count | Should -BeExactly 1
$buildOrder[1].buildDimensions[0].buildMode | Should -BeExactly "Default"
$buildOrder[1].buildDimensions[0].project | Should -BeExactly "Project2"
}

It 'throws if the calculated build depth is more than the maximum supported' {
# Two dependent projects
$dependecyAppFile = @{ id = '83fb8305-4079-415d-a25d-8132f0436fd1'; name = 'First App'; publisher = 'Contoso'; version = '1.0.0.0'; dependencies = @() }
Expand All @@ -673,6 +773,10 @@ Describe "Get-ProjectsToBuild" {

#Add settings file
$alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true }
New-Item -Path "$baseFolder/.github" -type Directory -Force
$alGoSettings | ConvertTo-Json -Depth 99 -Compress | Out-File (Join-Path $baseFolder ".github/AL-Go-Settings.json") -Encoding UTF8

# Add settings as environment variable to simulate we've run ReadSettings
$env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress

{ Get-ProjectsToBuild -baseFolder $baseFolder -maxBuildDepth 1 } | Should -Throw "The build depth is too deep, the maximum build depth is 1. You need to run 'Update AL-Go System Files' to update the workflows"
Expand Down

0 comments on commit 120540b

Please sign in to comment.