diff --git a/eng/scripts/Generate-Patch.ps1 b/eng/scripts/Generate-Patch.ps1
index f14d4cc28dc0..637311734b63 100644
--- a/eng/scripts/Generate-Patch.ps1
+++ b/eng/scripts/Generate-Patch.ps1
@@ -19,6 +19,9 @@
#
# 7. CreateNewBranch - Whether to create a new branch or use an existing branch. You would want to use an existing branch if you are using the same release tag for multiple libraries.
#
+# 8. UseCurrentBranch - When set, creates patch branches from the current branch instead of remote main.
+# Useful for local testing and dry-runs without requiring changes to be merged first. This is not a required parameter.
+#
# Example: .\eng\scripts\Generate-Patch.ps1 -ArtifactName azure-mixedreality-remoterendering -ServiceDirectory remoterendering -ReleaseVersion 1.0.0 -PatchVersion 1.0.1
# This creates a remote branch "release/azure-mixedreality-remoterendering" with all the necessary changes.
@@ -29,7 +32,9 @@ param(
[Parameter(Mandatory=$false)][string]$PatchVersion,
[Parameter(Mandatory=$false)][string]$BranchName,
[Parameter(Mandatory=$false)][boolean]$PushToRemote,
- [Parameter(Mandatory=$false)][boolean]$CreateNewBranch = $true
+ [Parameter(Mandatory=$false)][boolean]$CreateNewBranch = $true,
+ # When set, creates patch branches from the current branch instead of remote main.
+ [Parameter(Mandatory=$false)][switch]$UseCurrentBranch
)
function TestPathThrow($Path, $PathName) {
@@ -171,6 +176,7 @@ function CreatePatchRelease($ArtifactName, $ServiceDirectoryName, $PatchVersion,
$EngVersioningDir = Join-Path $EngDir "versioning"
$SetVersionFilePath = Join-Path $EngVersioningDir "set_versions.py"
$UpdateVersionFilePath = Join-Path $EngVersioningDir "update_versions.py"
+ $VersionClientPath = Join-Path $EngVersioningDir "version_client.txt"
$pkgProperties = Get-PkgProperties -PackageName $ArtifactName -ServiceDirectory $ServiceDirectoryName -GroupId $GroupId
$ChangelogPath = $pkgProperties.ChangeLogPath
$PomFilePath = Join-Path $pkgProperties.DirectoryPath "pom.xml"
@@ -196,8 +202,41 @@ function CreatePatchRelease($ArtifactName, $ServiceDirectoryName, $PatchVersion,
exit 1
}
+ # Resolve new dependency versions for the changelog. Uses pom.xml versions
+ # (post update_versions.py) but substitutes prerelease versions with
+ # version_client.txt column 2 (GA/released version) to avoid showing beta
+ # versions in the changelog.
+ # Key by groupId:artifactId to avoid collisions (e.g., com.azure vs com.azure.v2).
+ $versionClientLookup = @{}
+ foreach ($line in Get-Content -Path $VersionClientPath) {
+ $trimmed = $line.Trim()
+ if ($trimmed.StartsWith('#') -or [string]::IsNullOrWhiteSpace($trimmed)) { continue }
+ $parts = $trimmed.Split(';')
+ if ($parts.Length -ge 2) {
+ $versionClientLookup[$parts[0].Trim()] = $parts[1].Trim()
+ }
+ }
+
$newDependenciesToVersion = New-Object "System.Collections.Generic.Dictionary``2[System.String,System.String]"
- parsePomFileDependencies -PomFilePath $PomFilePath -DependencyToVersion $newDependenciesToVersion
+ $pomFileContent = [xml](Get-Content -Path $PomFilePath)
+ foreach ($dependency in $pomFileContent.project.dependencies.dependency) {
+ $scope = $dependency.scope
+ if ($scope -ne 'test') {
+ $artifactId = $dependency.artifactId
+ $groupId = $dependency.groupId
+ $pomVersion = $dependency.version
+ $key = "${groupId}:${artifactId}"
+ if ($pomVersion -match '-beta\.|_beta\.|BETA|-alpha\.|_alpha\.|ALPHA|-preview\.|_preview\.|PREVIEW|-SNAPSHOT') {
+ if ($versionClientLookup.ContainsKey($key)) {
+ $newDependenciesToVersion[$key] = $versionClientLookup[$key]
+ } else {
+ $newDependenciesToVersion[$key] = $pomVersion
+ }
+ } else {
+ $newDependenciesToVersion[$key] = $pomVersion
+ }
+ }
+ }
$releaseStatus = "$(Get-Date -Format $CHANGELOG_DATE_FORMAT)"
@@ -268,7 +307,8 @@ if(!$BranchName) {
try {
## Creating a new branch
if($CreateNewBranch) {
- $cmdOutput = git checkout -b $BranchName $RemoteName/main
+ $base = if ($UseCurrentBranch) { "HEAD" } else { "$RemoteName/main" }
+ $cmdOutput = git checkout -b $BranchName $base
}
else {
$cmdOutput = git checkout $BranchName
diff --git a/eng/scripts/Generate-Patches-For-Automatic-Releases.ps1 b/eng/scripts/Generate-Patches-For-Automatic-Releases.ps1
index ca455189f22c..c1aee76dcbea 100644
--- a/eng/scripts/Generate-Patches-For-Automatic-Releases.ps1
+++ b/eng/scripts/Generate-Patches-For-Automatic-Releases.ps1
@@ -5,7 +5,9 @@ param(
# $(Build.SourcesDirectory) - root of the repository
[Parameter(Mandatory = $true)][string]$SourcesDirectory,
# The yml file whose artifacts and additionalModules lists will be updated
- [Parameter(Mandatory = $true)][string]$PackagesYmlPath
+ [Parameter(Mandatory = $true)][string]$PackagesYmlPath,
+ # When set, creates patch branches from the current branch instead of remote main.
+ [switch]$UseCurrentBranch
)
$StartTime = $( get-date )
@@ -26,10 +28,11 @@ try {
Write-Host "git fetch --all --prune"
git fetch --all --prune
- # Checkout a branch to work on based off of main in upstream.
+ # Checkout a branch to work on based off of main (or current branch if -UseCurrentBranch).
if ($currentBranchName -ne $branchName) {
- Write-Host "git checkout -b $branchName $remoteName/main"
- git checkout -b $branchName $remoteName/main
+ $base = if ($UseCurrentBranch) { "HEAD" } else { "$remoteName/main" }
+ Write-Host "git checkout -b $branchName $base"
+ git checkout -b $branchName $base
if ($LASTEXITCODE -ne 0) {
LogError "Could not checkout branch $branchName, please check if it already exists and delete as necessary. Exiting..."
@@ -52,9 +55,26 @@ try {
$packagesData = $ymlObject["extends"]["parameters"]["artifacts"]
$libraryList = $null
+ # Build PatchVersionOverrides: map of "${groupId}:${artifactId}" → patch version for all
+ # artifacts being patched. This is passed to generatepatch.ps1 so changelogs show the
+ # correct version when a sibling dependency is also being patched in the same run.
+ $PatchVersionOverrides = @{}
+ foreach ($packageData in $packagesData) {
+ $pkgArtifactId = $packageData["name"]
+ $pkgGroupId = $packageData["groupId"]
+ $pkgKey = "${pkgGroupId}:${pkgArtifactId}"
+ try {
+ $mavenInfo = GetVersionInfoForAnArtifactId -GroupId $pkgGroupId -ArtifactId $pkgArtifactId
+ $patchVersion = GetPatchVersion -ReleaseVersion $mavenInfo.LatestGAOrPatchVersion
+ $PatchVersionOverrides[$pkgKey] = $patchVersion
+ } catch {
+ Write-Warning "Could not determine patch version for ${pkgArtifactId}: $_"
+ }
+ }
+
# Reset each package to the latest stable release and update CHANGELOG, POM and README for patch release.
foreach ($packageData in $packagesData) {
- . "${PSScriptRoot}/generatepatch.ps1" -ArtifactIds $packageData["name"] -ServiceDirectoryName $packageData["ServiceDirectory"] -BranchName $branchName -GroupId $packageData["groupId"]
+ . "${PSScriptRoot}/generatepatch.ps1" -ArtifactIds $packageData["name"] -ServiceDirectoryName $packageData["ServiceDirectory"] -BranchName $branchName -GroupId $packageData["groupId"] -UseCurrentBranch:$UseCurrentBranch -PatchVersionOverrides $PatchVersionOverrides
$libraryList += $packageData["groupId"] + ":" + $packageData["name"] + ","
}
diff --git a/eng/scripts/Update-Artifacts-List-For-Patch-Release.ps1 b/eng/scripts/Update-Artifacts-List-For-Patch-Release.ps1
index 75d402da3976..9a2de59bf5a5 100644
--- a/eng/scripts/Update-Artifacts-List-For-Patch-Release.ps1
+++ b/eng/scripts/Update-Artifacts-List-For-Patch-Release.ps1
@@ -72,7 +72,7 @@ foreach ($ymlFile in $ymlFiles) {
}
}
-# Use OrderedDictionary here for later "FindAllArtifactsThatNeedPatching"
+# Use OrderedDictionary here for later "FindArtifactsThatNeedPatching"
$ArtifactInfos = New-Object System.Collections.Specialized.OrderedDictionary
Write-Host "Loading libraries from text file."
@@ -87,9 +87,7 @@ foreach ($line in Get-Content "${PSScriptRoot}/../pipelines/patch_release_client
UpdateDependencies -ArtifactInfos $ArtifactInfos
-$AllDependenciesWithVersion = CreateForwardLookingVersions -ArtifactInfos $ArtifactInfos
-
-FindAllArtifactsThatNeedPatching -ArtifactInfos $ArtifactInfos -AllDependenciesWithVersion $AllDependenciesWithVersion
+FindArtifactsThatNeedPatching -ArtifactInfos $ArtifactInfos
$ArtifactsToPatch = $ArtifactInfos.Keys | Where-Object { $null -ne $ArtifactInfos[$_].FutureReleasePatchVersion } | ForEach-Object { $ArtifactInfos[$_].ArtifactId }
diff --git a/eng/scripts/bomhelpers.ps1 b/eng/scripts/bomhelpers.ps1
index ea96485513e1..847a5b5711ef 100644
--- a/eng/scripts/bomhelpers.ps1
+++ b/eng/scripts/bomhelpers.ps1
@@ -155,6 +155,79 @@ function GetDependencyToVersion($PomFilePath) {
return $dependencyNameToVersion
}
+# Resolve dependency versions for patch changelog generation.
+# Parses version_client.txt following the same format as utils.load_version_map_from_file
+# (eng/versioning/utils.py), extracting column 2 (CodeModule.dependency) per entry.
+# Uses a layered resolution strategy:
+# 1. PatchVersionOverrides (highest priority) — maps artifactId to the patch version
+# for sibling artifacts being patched in the same run.
+# 2. version_client.txt column 2 — the released/GA version. Used as a fallback for
+# dependencies whose pom.xml version is a prerelease (beta/alpha), which happens
+# when update_versions.py writes column 3 for {x-version-update;...;current} markers.
+# 3. pom.xml version (lowest priority) — used for external dependencies not in
+# version_client.txt and for GA versions that are already correct.
+function GetResolvedDependencyVersions($PomFilePath, $VersionClientPath, $PatchVersionOverrides) {
+ if (-not $VersionClientPath) {
+ $repoRoot = Resolve-Path "${PSScriptRoot}../../.."
+ $VersionClientPath = Join-Path $repoRoot "eng" "versioning" "version_client.txt"
+ }
+ if (-not $PatchVersionOverrides) {
+ $PatchVersionOverrides = @{}
+ }
+
+ # Build lookup: groupId:artifactId → dependency version (column 2) from version_client.txt
+ # Key must include groupId because the same artifactId can appear under different groups
+ # (e.g., com.azure:azure-storage-blob vs com.azure.v2:azure-storage-blob).
+ $versionClientLookup = @{}
+ foreach ($line in Get-Content -Path $VersionClientPath) {
+ $trimmed = $line.Trim()
+ if ($trimmed.StartsWith('#') -or [string]::IsNullOrWhiteSpace($trimmed)) { continue }
+ $parts = $trimmed.Split(';')
+ if ($parts.Length -ge 2) {
+ $key = $parts[0].Trim()
+ $dependencyVersion = $parts[1].Trim()
+ $versionClientLookup[$key] = $dependencyVersion
+ }
+ }
+
+ # Read non-test dependencies from pom.xml, resolve each version using the layered strategy.
+ $resolvedVersions = @{}
+ $pomFileContent = [xml](Get-Content -Path $PomFilePath)
+ foreach ($dependency in $pomFileContent.project.dependencies.dependency) {
+ $scope = $dependency.scope
+ if ($scope -ne 'test') {
+ $artifactId = $dependency.artifactId
+ $groupId = $dependency.groupId
+ $pomVersion = $dependency.version
+ $key = "${groupId}:${artifactId}"
+ $patchOverrideKey = $key
+
+ if ($PatchVersionOverrides.ContainsKey($patchOverrideKey)) {
+ # Sibling artifact being patched in the same run — use its fully qualified
+ # patch version override (groupId:artifactId) to avoid collisions.
+ $resolvedVersions[$artifactId] = $PatchVersionOverrides[$patchOverrideKey]
+ } elseif ($PatchVersionOverrides.ContainsKey($artifactId)) {
+ # Backward compatibility: fall back to artifactId-only override if present.
+ $resolvedVersions[$artifactId] = $PatchVersionOverrides[$artifactId]
+ } elseif ($pomVersion -match '-beta\.|_beta\.|BETA|-alpha\.|_alpha\.|ALPHA|-preview\.|_preview\.|PREVIEW|-SNAPSHOT') {
+ # Pom has a prerelease version (from {;current} marker) — fall back to
+ # version_client.txt column 2 (GA/released version) to avoid showing
+ # beta versions in the changelog.
+ if ($versionClientLookup.ContainsKey($key)) {
+ $resolvedVersions[$artifactId] = $versionClientLookup[$key]
+ } else {
+ $resolvedVersions[$artifactId] = $pomVersion
+ }
+ } else {
+ # GA version from pom (e.g., {;dependency} marker or external dep) — use as-is.
+ $resolvedVersions[$artifactId] = $pomVersion
+ }
+ }
+ }
+
+ return $resolvedVersions
+}
+
# Create the changelog content from a message.
function GetChangeLogContentFromMessage($ContentMessage) {
$content = @()
@@ -231,9 +304,9 @@ function GitCommit($Message) {
}
# Generate patches for given artifact patch infos.
-function GeneratePatches($ArtifactPatchInfos, [string]$BranchName, [string]$RemoteName, [string]$GroupId = "com.azure") {
+function GeneratePatches($ArtifactPatchInfos, [string]$BranchName, [string]$RemoteName, [string]$GroupId = "com.azure", [bool]$UseCurrentBranch = $false, $PatchVersionOverrides = @{}) {
foreach ($patchInfo in $ArtifactPatchInfos) {
- GeneratePatch -PatchInfo $patchInfo -BranchName $BranchName -RemoteName $RemoteName -GroupId $GroupId
+ GeneratePatch -PatchInfo $patchInfo -BranchName $BranchName -RemoteName $RemoteName -GroupId $GroupId -UseCurrentBranch $UseCurrentBranch -PatchVersionOverrides $PatchVersionOverrides
}
#TriggerPipeline -PatchInfos $ArtifactPatchInfos -BranchName $BranchName
@@ -251,7 +324,7 @@ function GetCurrentBranchName() {
3. Updating the changelog and readme's to update the dependency information.
4. Committing these changes.
#>
-function GeneratePatch($PatchInfo, [string]$BranchName, [string]$RemoteName, [string]$GroupId = "com.azure") {
+function GeneratePatch($PatchInfo, [string]$BranchName, [string]$RemoteName, [string]$GroupId = "com.azure", [bool]$UseCurrentBranch = $false, $PatchVersionOverrides = @{}) {
$artifactId = $PatchInfo.ArtifactId
$releaseVersion = $PatchInfo.LatestGAOrPatchVersion
$serviceDirectoryName = $PatchInfo.ServiceDirectoryName
@@ -278,8 +351,9 @@ function GeneratePatch($PatchInfo, [string]$BranchName, [string]$RemoteName, [st
$currentBranchName = GetCurrentBranchName
if ($currentBranchName -ne $BranchName) {
- Write-Host "git checkout -b $BranchName $RemoteName/main"
- $cmdOutput = git checkout -b $BranchName $RemoteName/main
+ $base = if ($UseCurrentBranch) { "HEAD" } else { "$RemoteName/main" }
+ Write-Host "git checkout -b $BranchName $base"
+ $cmdOutput = git checkout -b $BranchName $base
if ($LASTEXITCODE -ne 0) {
LogError "Could not checkout branch $BranchName, please check if it already exists and delete as necessary. Exiting..."
exit $LASTEXITCODE
@@ -369,7 +443,7 @@ function GeneratePatch($PatchInfo, [string]$BranchName, [string]$RemoteName, [st
exit $LASTEXITCODE
}
- $newDependenciesToVersion = GetDependencyToVersion -PomFilePath $pomFilePath
+ $newDependenciesToVersion = GetResolvedDependencyVersions -PomFilePath $pomFilePath -PatchVersionOverrides $PatchVersionOverrides
$releaseStatus = "$(Get-Date -Format $CHANGELOG_DATE_FORMAT)"
$releaseStatus = "($releaseStatus)"
$changeLogEntries = Get-ChangeLogEntries -ChangeLogLocation $changelogPath
diff --git a/eng/scripts/generatepatch.ps1 b/eng/scripts/generatepatch.ps1
index 8c69ee788115..d9b0b120f592 100644
--- a/eng/scripts/generatepatch.ps1
+++ b/eng/scripts/generatepatch.ps1
@@ -44,7 +44,12 @@ param(
[string[]]$ArtifactIds,
[string]$ServiceDirectoryName,
[string]$BranchName,
- [string]$GroupId = 'com.azure'
+ [string]$GroupId = 'com.azure',
+ # When set, creates patch branches from the current branch instead of remote main.
+ [switch]$UseCurrentBranch,
+ # Optional map of artifactId → version for sibling artifacts being patched in the same run.
+ # Passed to GeneratePatch so changelogs show the correct version for sibling dependencies.
+ [hashtable]$PatchVersionOverrides = @{}
)
$RepoRoot = Resolve-Path "${PSScriptRoot}..\..\.."
@@ -87,7 +92,7 @@ foreach ($artifactId in $ArtifactIds) {
$patchInfo = [ArtifactPatchInfo]::new()
$patchInfo.ArtifactId = $artifactId
$patchInfo.ServiceDirectoryName = $ServiceDirectoryName
- GeneratePatch -PatchInfo $patchInfo -BranchName $BranchName -RemoteName $RemoteName -GroupId $GroupId
+ GeneratePatch -PatchInfo $patchInfo -BranchName $BranchName -RemoteName $RemoteName -GroupId $GroupId -UseCurrentBranch $UseCurrentBranch -PatchVersionOverrides $PatchVersionOverrides
#TriggerPipeline -PatchInfos $patchInfo -BranchName $BranchName
}
diff --git a/eng/scripts/patchhelpers.ps1 b/eng/scripts/patchhelpers.ps1
index 4682298462de..e4e7b966f566 100644
--- a/eng/scripts/patchhelpers.ps1
+++ b/eng/scripts/patchhelpers.ps1
@@ -101,70 +101,34 @@ function UpdateCIInformation($ArtifactInfos) {
}
}
-# Create the forward looking graph for once the artifacts have been patched.
-function CreateForwardLookingVersions($ArtifactInfos) {
- $allDependenciesWithVersion = @{}
+# Find all the artifacts that will need to be patched based on dependency analysis.
+# Iterates until no more patches are found (fixed-point), so the result is correct
+# regardless of artifact ordering in patch_release_client.txt.
+# Only dependencies that are themselves in the patch list are checked — external
+# dependencies (reactor-core, jackson, etc.) are ignored.
+function FindArtifactsThatNeedPatching($ArtifactInfos) {
+ $latestVersions = @{}
foreach ($arId in $ArtifactInfos.Keys) {
- foreach ($depId in $ArtifactInfos[$arId].Dependencies.Keys) {
- $depVersion = $ArtifactInfos[$arId].Dependencies[$depId]
- $currentVersion = $allDependenciesWithVersion[$depId]
- if ($null -eq $currentVersion) {
- $latestVersion = $depVersion
- }
- else {
- $orderedVersions = @($depVersion, $currentVersion) | ForEach-Object { [AzureEngSemanticVersion]::ParseVersionString($_) }
- $sortedVersions = [AzureEngSemanticVersion]::SortVersions($orderedVersions)
- if($null -eq $sortedVersions) {
- # We currently have a bug where semantic version may have 4 values just like jackson-databind.
- $latestVersion = $depVersion
- } else {
- $latestVersion = $sortedVersions[0].RawVersion
- }
- }
-
- $allDependenciesWithVersion[$depId] = $latestVersion
- }
+ $latestVersions[$arId] = $ArtifactInfos[$arId].LatestGAOrPatchVersion
}
- return $allDependenciesWithVersion
-}
-
-# Find all the artifacts that will need to be patched based on the dependency analysis.
-# Artifacts will be processed in the same order as defined in patch_release_client.txt (libraries that depend on other
-# libraries will appear later in the file).
-# This guarantees that if dependency libraries are going to be patched, dependent ones will be included as well.
-function FindAllArtifactsThatNeedPatching($ArtifactInfos, $AllDependenciesWithVersion) {
- foreach($arId in $ArtifactInfos.Keys) {
- $arInfo = $ArtifactInfos[$arId]
-
- foreach($depId in $arInfo.Dependencies.Keys) {
- $depVersion = $arInfo.Dependencies[$depId]
-
- if($depVersion -ne $AllDependenciesWithVersion[$depId]) {
- $currentGAOrPatchVersion = $arInfo.LatestGAOrPatchVersion
- $newPatchVersion = GetPatchVersion -ReleaseVersion $currentGAOrPatchVersion
- $arInfo.FutureReleasePatchVersion = $newPatchVersion
- $AllDependenciesWithVersion[$arId] = $newPatchVersion
+ do {
+ $changed = $false
+ foreach ($arId in $ArtifactInfos.Keys) {
+ $arInfo = $ArtifactInfos[$arId]
+ if ($arInfo.FutureReleasePatchVersion) { continue }
+ foreach ($depId in $arInfo.Dependencies.Keys) {
+ if (-not $latestVersions.ContainsKey($depId)) { continue }
+ if ($arInfo.Dependencies[$depId] -ne $latestVersions[$depId]) {
+ $patchVersion = GetPatchVersion -ReleaseVersion $arInfo.LatestGAOrPatchVersion
+ $arInfo.FutureReleasePatchVersion = $patchVersion
+ $latestVersions[$arId] = $patchVersion
+ $changed = $true
+ break
+ }
}
}
- }
-}
-
-# Helper class that analyzes all the artifacts that need to be patched if a given artifact is patched.
-function ArtifactsToPatchUtil([String] $DependencyId, [hashtable]$ArtifactInfos, $AllDependenciesWithVersion) {
- $arInfo = $ArtifactInfos[$DependencyId]
- $currentGAOrPatchVersion = $arInfo.LatestGAOrPatchVersion
- $newPatchVersion = GetPatchVersion -ReleaseVersion $currentGAOrPatchVersion
- $arInfo.FutureReleasePatchVersion = $newPatchVersion
- $AllDependenciesWithVersion[$depId] = $newPatchVersion
-
- foreach($arId in $ArtifactInfos.Keys) {
- $arInfo = $ArtifactInfos[$arId]
- $depVersion = $arInfo.Dependencies[$DependencyId]
- if($depVersion -and $depVersion -ne $newPatchVersion) {
- ArtifactsToPatchUtil -DependencyId $DependencyId -ArtifactInfos $ArtifactInfos -AllDependenciesWithVersion $AllDependenciesWithVersion
- }
- }
+ } while ($changed)
}
# Update dependencies in the version client file.
@@ -273,7 +237,7 @@ function CreateDependencyXmlElement($Artifact, [xml]$Doc) {
}
# Generate BOM file for the given artifacts.
-function GenerateBOMFile($ArtifactInfos, $BomFileBranchName) {
+function GenerateBOMFile($ArtifactInfos, $BomFileBranchName, [bool]$UseCurrentBranch = $false) {
$gaArtifacts = @()
foreach ($artifact in $ArtifactInfos.Values) {
@@ -310,8 +274,9 @@ function GenerateBOMFile($ArtifactInfos, $BomFileBranchName) {
$releaseVersion = $bomFileContent.project.version
$patchVersion = GetPatchVersion -ReleaseVersion $releaseVersion
$remoteName = GetRemoteName
- Write-Host "git checkout -b $BomFileBranchName $remoteName/main"
- $cmdOutput = git checkout -b $BomFileBranchName $remoteName/main
+ $base = if ($UseCurrentBranch) { "HEAD" } else { "$remoteName/main" }
+ Write-Host "git checkout -b $BomFileBranchName $base"
+ $cmdOutput = git checkout -b $BomFileBranchName $base
$bomFileContent.Save($BomFilePath)
Write-Host "git add $BomFilePath"
git add $BomFilePath
diff --git a/eng/scripts/patchreleases.ps1 b/eng/scripts/patchreleases.ps1
index 39397727cbb6..8992eaa662bc 100644
--- a/eng/scripts/patchreleases.ps1
+++ b/eng/scripts/patchreleases.ps1
@@ -9,7 +9,9 @@
4. Generate the forward looking BOM file and create a branch for the BOM release.
#>
param(
- [string]$GroupId = "com.azure"
+ [string]$GroupId = "com.azure",
+ # When set, creates patch branches from the current branch instead of remote main.
+ [switch]$UseCurrentBranch
)
Write-Information "PS Script Root is: $PSScriptRoot"
@@ -57,8 +59,7 @@ UpdateDependencies -ArtifactInfos $ArtifactInfos
# $AzCoreNettyArtifactId = "azure-core-http-netty"
# $ArtifactInfos[$AzCoreNettyArtifactId].Dependencies[$AzCoreArtifactId] = $AzCoreVersion
-$AllDependenciesWithVersion = CreateForwardLookingVersions -ArtifactInfos $ArtifactInfos
-FindAllArtifactsThatNeedPatching -ArtifactInfos $ArtifactInfos -AllDependenciesWithVersion $AllDependenciesWithVersion
+FindArtifactsThatNeedPatching -ArtifactInfos $ArtifactInfos
$ArtifactsToPatch = $ArtifactInfos.Keys | Where-Object { $null -ne $ArtifactInfos[$_].FutureReleasePatchVersion } | ForEach-Object {$ArtifactInfos[$_].ArtifactId}
$RemoteName = GetRemoteName
@@ -73,10 +74,25 @@ $bomPatchVersion = GetNextBomVersion
$bomBranchName = "bom_$bomPatchVersion"
$ArtifactPatchInfos = @()
Write-Output "Preparing patch releases for BOM updates."
+
+# Build a map of artifactId → patch version for all artifacts being patched.
+# This is passed to GeneratePatches so changelogs can show the correct version
+# when a sibling dependency is being patched in the same run.
+$PatchVersionOverrides = @{}
+foreach ($artifactId in $ArtifactInfos.Keys) {
+ $patchVersion = $ArtifactInfos[$artifactId].FutureReleasePatchVersion
+ if ($patchVersion) {
+ $PatchVersionOverrides[$artifactId] = $patchVersion
+ } else {
+ $PatchVersionOverrides[$artifactId] = $ArtifactInfos[$artifactId].LatestGAOrPatchVersion
+ }
+}
+
try {
$patchBranchName = "PatchSet_$bomPatchVersion"
- Write-Host "git checkout -b $patchBranchName $RemoteName/main"
- git checkout -b $patchBranchName $RemoteName/main
+ $base = if ($UseCurrentBranch) { "HEAD" } else { "$RemoteName/main" }
+ Write-Host "git checkout -b $patchBranchName $base"
+ git checkout -b $patchBranchName $base
UpdateDependenciesInVersionClient -ArtifactInfos $ArtifactInfos
foreach ($artifactId in $ArtifactsToPatch) {
@@ -84,7 +100,7 @@ try {
$patchInfo = [ArtifactPatchInfo]::new()
$patchInfo = ConvertToPatchInfo -ArInfo $arInfo
$ArtifactPatchInfos += $patchInfo
- GeneratePatches -ArtifactPatchInfos $patchInfo -BranchName $patchBranchName -RemoteName $RemoteName -GroupId $GroupId
+ GeneratePatches -ArtifactPatchInfos $patchInfo -BranchName $patchBranchName -RemoteName $RemoteName -GroupId $GroupId -UseCurrentBranch $UseCurrentBranch -PatchVersionOverrides $PatchVersionOverrides
}
Write-Host "git -c user.name=`"azure-sdk`" -c user.email=`"azuresdk@microsoft.com`" push $RemoteName $patchBranchName"
@@ -100,7 +116,7 @@ finally {
$cmdOutput = git checkout $CurrentBranchName
}
-GenerateBOMFile -ArtifactInfos $ArtifactInfos -BomFileBranchName $bomBranchName
+GenerateBOMFile -ArtifactInfos $ArtifactInfos -BomFileBranchName $bomBranchName -UseCurrentBranch $UseCurrentBranch
GenerateJsonReport -ArtifactPatchInfos $ArtifactPatchInfos -PatchBranchName $patchBranchName -BomFileBranchName $bomBranchName
#$orderedArtifacts = GetTopologicalSort -ArtifactIds $ArtifactsToPatch.Keys -ArtifactInfos $ArtifactInfos
#GenerateHtmlReport -Artifacts $orderedArtifacts -PatchBranchName $patchBranchName -BomFileBranchName $bomBranchName
diff --git a/eng/scripts/tests/patchhelpers.tests.ps1 b/eng/scripts/tests/patchhelpers.tests.ps1
new file mode 100644
index 000000000000..0a2baf9e0e45
--- /dev/null
+++ b/eng/scripts/tests/patchhelpers.tests.ps1
@@ -0,0 +1,495 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+<#
+.SYNOPSIS
+ Pester tests for patchhelpers.ps1
+
+.DESCRIPTION
+ This file contains unit tests for the patch release helper functions
+ using the Pester testing framework.
+
+ Tests cover the pure-logic functions that operate on in-memory data
+ structures and require no network, git, or file-system access.
+
+.NOTES
+ How to run:
+ 1. Install Pester if not already installed:
+ Install-Module Pester -Force -MinimumVersion 5.3.3
+
+ 2. Run the tests:
+ Invoke-Pester ./patchhelpers.tests.ps1
+#>
+
+BeforeAll {
+ # Import common scripts (loads AzureEngSemanticVersion via SemVer.ps1)
+ $commonScriptPath = Join-Path $PSScriptRoot ".." ".." "common" "scripts" "common.ps1"
+ . $commonScriptPath
+
+ # Import patchhelpers (loads bomhelpers.ps1 transitively)
+ $patchHelpersPath = Join-Path $PSScriptRoot ".." "patchhelpers.ps1"
+ . $patchHelpersPath
+
+ # Helper: build an ArtifactInfo with dependencies pre-populated.
+ function New-TestArtifactInfo {
+ param(
+ [string]$ArtifactId,
+ [string]$LatestGAOrPatchVersion,
+ [hashtable]$Dependencies = @{},
+ [string]$GroupId = "com.azure"
+ )
+ $info = [ArtifactInfo]::new($ArtifactId, $GroupId, $LatestGAOrPatchVersion)
+ $info.Dependencies = $Dependencies
+ return $info
+ }
+}
+
+# ---------------------------------------------------------------------------
+# FindArtifactsThatNeedPatching
+# ---------------------------------------------------------------------------
+Describe "FindArtifactsThatNeedPatching" {
+ It "Single artifact with no dependencies — no patch" {
+ $infos = [ordered]@{
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{})
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-core"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ }
+
+ It "Outdated dependency triggers patch" {
+ $infos = [ordered]@{
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{})
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.40.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-core"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -Be "1.10.1"
+ }
+
+ It "Up-to-date dependency — no patch" {
+ $infos = [ordered]@{
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{})
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.41.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ }
+
+ It "Cascading patches through dependency chain" {
+ $infos = [ordered]@{
+ "azure-json" = (New-TestArtifactInfo -ArtifactId "azure-json" -LatestGAOrPatchVersion "1.4.0" -Dependencies @{})
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{
+ "azure-json" = "1.3.0"
+ })
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.41.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-json"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-core"].FutureReleasePatchVersion | Should -Be "1.41.1"
+ # azure-identity sees azure-core 1.41.0 != 1.41.1 → cascade
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -Be "1.10.1"
+ }
+
+ It "External dependency version difference is ignored (no false positive)" {
+ # azure-core uses reactor-core 3.5.0, azure-identity uses 3.6.0.
+ # reactor-core is NOT in the patch list → should NOT trigger patching.
+ $infos = [ordered]@{
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{
+ "reactor-core" = "3.5.0"
+ })
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.41.0"
+ "reactor-core" = "3.6.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-core"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ }
+
+ It "Independent release of a dependency triggers dependent patch (seeding loop regression)" {
+ # azure-core was independently released: 1.40.0 → 1.41.0 on Maven.
+ # azure-identity's published POM still references azure-core 1.40.0.
+ $infos = [ordered]@{
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{})
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.40.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-core"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -Be "1.10.1"
+ }
+
+ It "Dependency with no sibling dependents still triggers patch (containerinstance scenario)" {
+ # azure-storage-file-share was independently released (12.24.0 → 12.24.1).
+ # azure-resourcemanager-containerinstance depends on it but no OTHER artifact
+ # in the patch list does. Before the algorithm fix, containerinstance was missed.
+ $infos = [ordered]@{
+ "azure-storage-file-share" = (New-TestArtifactInfo -ArtifactId "azure-storage-file-share" -LatestGAOrPatchVersion "12.24.1" -Dependencies @{})
+ "azure-resourcemanager-containerinstance" = (New-TestArtifactInfo -ArtifactId "azure-resourcemanager-containerinstance" -LatestGAOrPatchVersion "2.44.0" -GroupId "com.azure.resourcemanager" -Dependencies @{
+ "azure-storage-file-share" = "12.24.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-storage-file-share"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-resourcemanager-containerinstance"].FutureReleasePatchVersion | Should -Be "2.44.1"
+ }
+
+ It "Diamond dependency — all paths detected" {
+ $infos = [ordered]@{
+ "azure-json" = (New-TestArtifactInfo -ArtifactId "azure-json" -LatestGAOrPatchVersion "1.4.0" -Dependencies @{})
+ "azure-xml" = (New-TestArtifactInfo -ArtifactId "azure-xml" -LatestGAOrPatchVersion "1.2.0" -Dependencies @{
+ "azure-json" = "1.3.0"
+ })
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{
+ "azure-json" = "1.3.0"
+ })
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-xml" = "1.2.0"
+ "azure-core" = "1.41.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-json"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-xml"].FutureReleasePatchVersion | Should -Be "1.2.1"
+ $infos["azure-core"].FutureReleasePatchVersion | Should -Be "1.41.1"
+ # azure-identity sees azure-xml 1.2.0 != 1.2.1 → cascade
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -Be "1.10.1"
+ }
+
+ It "Multiple outdated deps — patch version computed once" {
+ $infos = [ordered]@{
+ "azure-json" = (New-TestArtifactInfo -ArtifactId "azure-json" -LatestGAOrPatchVersion "1.4.0" -Dependencies @{})
+ "azure-xml" = (New-TestArtifactInfo -ArtifactId "azure-xml" -LatestGAOrPatchVersion "1.2.0" -Dependencies @{})
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{
+ "azure-json" = "1.3.0"
+ "azure-xml" = "1.1.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ # azure-core is flagged once (patch version = 1.41.1, not 1.41.2)
+ $infos["azure-core"].FutureReleasePatchVersion | Should -Be "1.41.1"
+ }
+
+ It "Unordered hashtable: cascading still works when iteration order is non-deterministic" {
+ # Production callers (patchreleases.ps1) pass plain @{} not [ordered]@{}.
+ # With an unordered hashtable the iteration order is undefined, so the
+ # fixed-point loop must converge regardless.
+ $infos = @{
+ "azure-identity" = (New-TestArtifactInfo -ArtifactId "azure-identity" -LatestGAOrPatchVersion "1.10.0" -Dependencies @{
+ "azure-core" = "1.41.0"
+ })
+ "azure-json" = (New-TestArtifactInfo -ArtifactId "azure-json" -LatestGAOrPatchVersion "1.4.0" -Dependencies @{})
+ "azure-core" = (New-TestArtifactInfo -ArtifactId "azure-core" -LatestGAOrPatchVersion "1.41.0" -Dependencies @{
+ "azure-json" = "1.3.0"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-json"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-core"].FutureReleasePatchVersion | Should -Be "1.41.1"
+ $infos["azure-identity"].FutureReleasePatchVersion | Should -Be "1.10.1"
+ }
+
+ It "Handles out-of-order: dependent listed before its dependency (checkpointstore scenario)" {
+ # checkpointstore appears BEFORE blob in the iteration order (alphabetical in patch_release_client.txt).
+ # blob gets patched because its own dep (storage-common) was independently released.
+ # checkpointstore must still be patched even though it's iterated first.
+ $infos = [ordered]@{
+ "azure-storage-common" = (New-TestArtifactInfo -ArtifactId "azure-storage-common" -LatestGAOrPatchVersion "12.32.2" -Dependencies @{})
+ "azure-messaging-eventhubs-checkpointstore-blob" = (New-TestArtifactInfo -ArtifactId "azure-messaging-eventhubs-checkpointstore-blob" -LatestGAOrPatchVersion "1.21.4" -Dependencies @{
+ "azure-storage-blob" = "12.33.2"
+ })
+ "azure-storage-blob" = (New-TestArtifactInfo -ArtifactId "azure-storage-blob" -LatestGAOrPatchVersion "12.33.2" -Dependencies @{
+ "azure-storage-common" = "12.32.1"
+ })
+ }
+ FindArtifactsThatNeedPatching -ArtifactInfos $infos
+ $infos["azure-storage-common"].FutureReleasePatchVersion | Should -BeNullOrEmpty
+ $infos["azure-storage-blob"].FutureReleasePatchVersion | Should -Be "12.33.3"
+ $infos["azure-messaging-eventhubs-checkpointstore-blob"].FutureReleasePatchVersion | Should -Be "1.21.5"
+ }
+}
+
+# ---------------------------------------------------------------------------
+# GetPatchVersion
+# ---------------------------------------------------------------------------
+Describe "GetPatchVersion" {
+ It "Bumps the patch version (1.0.0 → 1.0.1)" {
+ GetPatchVersion -ReleaseVersion "1.0.0" | Should -Be "1.0.1"
+ }
+
+ It "Bumps an existing patch version (12.24.1 → 12.24.2)" {
+ GetPatchVersion -ReleaseVersion "12.24.1" | Should -Be "12.24.2"
+ }
+}
+
+# ---------------------------------------------------------------------------
+# GetResolvedDependencyVersions
+# ---------------------------------------------------------------------------
+Describe "GetResolvedDependencyVersions" {
+ BeforeAll {
+ $script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "patchtest_$(Get-Random)"
+ New-Item -ItemType Directory -Path $script:testDir -Force | Out-Null
+ }
+
+ AfterAll {
+ if (Test-Path $script:testDir) {
+ Remove-Item -Recurse -Force $script:testDir
+ }
+ }
+
+ It "Uses pom.xml GA version for dependency marker deps" {
+ $versionClientFile = Join-Path $script:testDir "version_client.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-storage-blob;12.33.2;12.34.0-beta.2"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-storage-blob
+ 12.33.2
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ $result["azure-storage-blob"] | Should -Be "12.33.2"
+ }
+
+ It "Substitutes beta pom version with version_client.txt column 2 (GA)" {
+ $versionClientFile = Join-Path $script:testDir "version_client_beta.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-messaging-eventhubs;5.21.3;5.22.0-beta.1"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom_beta.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-messaging-eventhubs
+ 5.22.0-beta.1
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ $result["azure-messaging-eventhubs"] | Should -Be "5.21.3"
+ }
+
+ It "PatchVersionOverrides take precedence over pom and version_client.txt" {
+ $versionClientFile = Join-Path $script:testDir "version_client_ovr.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-storage-blob;12.33.2;12.34.0-beta.2"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom_ovr.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-storage-blob
+ 12.33.2
+
+
+
+'@
+
+ $overrides = @{ "azure-storage-blob" = "12.33.3" }
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile -PatchVersionOverrides $overrides
+ $result["azure-storage-blob"] | Should -Be "12.33.3"
+ }
+
+ It "PatchVersionOverrides also override beta pom versions" {
+ $versionClientFile = Join-Path $script:testDir "version_client_ovr2.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-messaging-eventhubs;5.21.3;5.22.0-beta.1"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom_ovr2.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-messaging-eventhubs
+ 5.22.0-beta.1
+
+
+
+'@
+
+ $overrides = @{ "azure-messaging-eventhubs" = "5.21.4" }
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile -PatchVersionOverrides $overrides
+ $result["azure-messaging-eventhubs"] | Should -Be "5.21.4"
+ }
+
+ It "Falls back to pom.xml version for external dependencies not in version_client.txt" {
+ $versionClientFile = Join-Path $script:testDir "version_client2.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-core;1.57.1;1.58.0-beta.1"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom2.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-core
+ 1.58.0-beta.1
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ $result["azure-core"] | Should -Be "1.57.1"
+ $result["jsr305"] | Should -Be "3.0.2"
+ }
+
+ It "Excludes test-scoped dependencies" {
+ $versionClientFile = Join-Path $script:testDir "version_client3.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-core;1.57.1;1.58.0-beta.1"
+ "com.azure:azure-core-test;1.27.0-beta.14;1.27.0-beta.15"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom3.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-core
+ 1.58.0-beta.1
+
+
+ com.azure
+ azure-core-test
+ 1.27.0-beta.15
+ test
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ $result.ContainsKey("azure-core") | Should -BeTrue
+ $result.ContainsKey("azure-core-test") | Should -BeFalse
+ }
+
+ It "Skips comment and empty lines in version_client.txt" {
+ $versionClientFile = Join-Path $script:testDir "version_client5.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "# This is a comment"
+ ""
+ "com.azure:azure-core;1.57.1;1.58.0-beta.1"
+ " "
+ "# Another comment"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom5.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-core
+ 1.58.0-beta.1
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ $result["azure-core"] | Should -Be "1.57.1"
+ }
+
+ It "Handles duplicate artifactId with different groupIds (com.azure vs com.azure.v2)" {
+ $versionClientFile = Join-Path $script:testDir "version_client6.txt"
+ Set-Content -Path $versionClientFile -Value @(
+ "com.azure:azure-storage-blob;12.33.2;12.34.0-beta.2"
+ "com.azure.v2:azure-storage-blob;13.0.0-beta.1;13.0.0-beta.1"
+ )
+
+ $pomFile = Join-Path $script:testDir "pom6.xml"
+ Set-Content -Path $pomFile -Value @'
+
+
+
+
+ com.azure
+ azure-storage-blob
+ 12.34.0-beta.2
+
+
+
+'@
+
+ $result = GetResolvedDependencyVersions -PomFilePath $pomFile -VersionClientPath $versionClientFile
+ # Must pick com.azure entry (12.33.2), NOT com.azure.v2 entry (13.0.0-beta.1)
+ $result["azure-storage-blob"] | Should -Be "12.33.2"
+ }
+}
+
+# ---------------------------------------------------------------------------
+# GetTopologicalSort
+# ---------------------------------------------------------------------------
+Describe "GetTopologicalSort" {
+ It "Linear chain returns correct order" {
+ $infos = [ordered]@{
+ "A" = (New-TestArtifactInfo -ArtifactId "A" -LatestGAOrPatchVersion "1.0.0" -Dependencies @{ "B" = "1.0.0" })
+ "B" = (New-TestArtifactInfo -ArtifactId "B" -LatestGAOrPatchVersion "1.0.0" -Dependencies @{ "C" = "1.0.0" })
+ "C" = (New-TestArtifactInfo -ArtifactId "C" -LatestGAOrPatchVersion "1.0.0" -Dependencies @{})
+ }
+ $infos["A"].PipelineName = "pipeline-a"
+ $infos["B"].PipelineName = "pipeline-b"
+ $infos["C"].PipelineName = "pipeline-c"
+
+ $result = GetTopologicalSort -ArtifactIds @("A", "B", "C") -ArtifactInfos $infos
+ $flatOrder = $result | ForEach-Object { $_.ArtifactId }
+ $flatOrder.IndexOf("C") | Should -BeLessThan $flatOrder.IndexOf("B")
+ $flatOrder.IndexOf("B") | Should -BeLessThan $flatOrder.IndexOf("A")
+ }
+
+ It "Independent artifacts are each returned" {
+ $infos = [ordered]@{
+ "X" = (New-TestArtifactInfo -ArtifactId "X" -LatestGAOrPatchVersion "1.0.0" -Dependencies @{})
+ "Y" = (New-TestArtifactInfo -ArtifactId "Y" -LatestGAOrPatchVersion "1.0.0" -Dependencies @{})
+ }
+ $infos["X"].PipelineName = "pipeline-x"
+ $infos["Y"].PipelineName = "pipeline-y"
+
+ $result = GetTopologicalSort -ArtifactIds @("X", "Y") -ArtifactInfos $infos
+ $result.Count | Should -Be 2
+ }
+}