Skip to content
Open
46 changes: 43 additions & 3 deletions eng/scripts/Generate-Patch.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
)
Comment on lines +35 to 38
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script has a numbered list of supported arguments in the header comments, but the newly added -UseCurrentBranch parameter isn’t documented there. Please update the header argument list so users discover the new switch and understand when to use it.

Copilot uses AI. Check for mistakes.

function TestPathThrow($Path, $PathName) {
Expand Down Expand Up @@ -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"
Expand All @@ -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)"
Expand Down Expand Up @@ -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
Expand Down
30 changes: 25 additions & 5 deletions eng/scripts/Generate-Patches-For-Automatic-Releases.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand All @@ -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..."
Expand All @@ -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"] + ","
}

Expand Down
6 changes: 2 additions & 4 deletions eng/scripts/Update-Artifacts-List-For-Patch-Release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand All @@ -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 }

Expand Down
86 changes: 80 additions & 6 deletions eng/scripts/bomhelpers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @()
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions eng/scripts/generatepatch.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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}..\..\.."
Expand Down Expand Up @@ -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
}

Expand Down
Loading
Loading