diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 724b3dea..f7e4e884 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -11,7 +11,7 @@ trigger: - support/v2 tags: include: - - 'v*' + - 'v2.*' pr: branches: include: @@ -56,6 +56,20 @@ extends: targetPath: '$(Build.ArtifactStagingDirectory)' steps: + - pwsh: | + $ErrorActionPreference = 'Stop' + $version = ([xml](Get-Content -LiteralPath Directory.Build.props)).SelectSingleNode('/Project/PropertyGroup/Version').InnerText + if ($version -cnotmatch '^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Invalid OData project version: $version." } + $sourceBranch = $env:BUILD_SOURCEBRANCH + if ($sourceBranch.StartsWith('refs/tags/')) { + if ($sourceBranch -cnotmatch '^refs/tags/v2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { + throw "This pipeline only releases OData v2.* tags." + } + if ($sourceBranch -cne "refs/tags/v$version") { throw "OData tag must exactly match Directory.Build.props version." } + } + Write-Host "##vso[task.setvariable variable=ODataVersion]$version" + displayName: 'Verify OData component tag and project version' + - task: UseDotNet@2 displayName: 'Use .NET 6' # needed for ESRP signing inputs: @@ -174,8 +188,13 @@ extends: displayName: 'pack' inputs: command: pack - projects: src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj - arguments: '-o $(Build.ArtifactStagingDirectory) --configuration $(BuildConfiguration) --no-build --include-symbols --include-source /p:SymbolPackageFormat=snupkg' + packagesToPack: src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj + configuration: '$(BuildConfiguration)' + packDirectory: '$(Build.ArtifactStagingDirectory)' + nobuild: true + includesymbols: true + includesource: true + buildProperties: 'SymbolPackageFormat=snupkg' - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 displayName: 'ESRP CodeSigning Nuget Packages' @@ -187,7 +206,7 @@ extends: AuthAKVName: 'akv-prod-eastus' AuthCertName: 'ReferenceLibraryPrivateCert' AuthSignCertName: 'ReferencePackagePublisherCertificate' - Pattern: '*.nupkg' + Pattern: 'Microsoft.OpenApi.OData.$(ODataVersion).nupkg' signConfigType: inlineSignParams inlineOperation: | [ @@ -212,7 +231,7 @@ extends: PendingAnalysisWaitTimeoutMinutes: '5' - stage: deploy - condition: and(contains(variables['build.sourceBranch'], 'refs/tags/v'), succeeded()) + condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v2.')) dependsOn: build jobs: - deployment: deploy @@ -230,10 +249,26 @@ extends: pool: vmImage: ubuntu-latest steps: + - pwsh: | + $ErrorActionPreference = 'Stop' + $sourceBranch = $env:BUILD_SOURCEBRANCH + if ($sourceBranch -cnotmatch '^refs/tags/v2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected an OData v2.* release tag." } + $version = $sourceBranch.Substring('refs/tags/v'.Length) + $packages = @(Get-ChildItem -LiteralPath '$(Pipeline.Workspace)' -Filter '*.nupkg' -File -Recurse) + if ($packages.Count -ne 1 -or $packages[0].Name -cne "Microsoft.OpenApi.OData.$version.nupkg") { + throw "Expected only the exact OData package matching the triggering tag." + } + $symbols = Join-Path $packages[0].DirectoryName "Microsoft.OpenApi.OData.$version.snupkg" + if (-not (Test-Path -LiteralPath $symbols -PathType Leaf)) { + throw "Missing exact OData symbols package for $version." + } + Write-Host "##vso[task.setvariable variable=ODataReleaseVersion]$version" + Write-Host "##vso[task.setvariable variable=ODataPackagePath]$($packages[0].FullName)" + displayName: 'Verify tagged OData package and symbols' - task: 1ES.PublishNuget@1 displayName: 'NuGet push' inputs: - packagesToPush: '$(Pipeline.Workspace)/Microsoft.OpenApi.OData.*.nupkg' + packagesToPush: '$(ODataPackagePath)' nuGetFeedType: external publishFeedCredentials: 'OpenAPI Nuget Connection' packageParentPath: '$(Pipeline.Workspace)' @@ -255,23 +290,30 @@ extends: vmImage: ubuntu-latest steps: - pwsh: | - $artifacts = @(Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.OData.*.nupkg -Recurse) - if ($artifacts.Count -ne 1) { throw "Expected exactly one OData package; found $($artifacts.Count)." } - $artifactName = $artifacts[0] - $artifactVersion= $artifactName.Name -replace "Microsoft.OpenApi.OData.", "" -replace ".nupkg", "" - #Set Variable $artifactName and $artifactVersion - Write-Host "##vso[task.setvariable variable=artifactVersion; isSecret=false;]$artifactVersion" - echo "$artifactVersion" - displayName: 'Fetch Artifact Name' + $ErrorActionPreference = 'Stop' + $sourceBranch = $env:BUILD_SOURCEBRANCH + if ($sourceBranch -cnotmatch '^refs/tags/v2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected an OData v2.* release tag." } + $version = $sourceBranch.Substring('refs/tags/v'.Length) + $packages = @(Get-ChildItem -LiteralPath '$(Pipeline.Workspace)' -Filter '*.nupkg' -File -Recurse) + if ($packages.Count -ne 1 -or $packages[0].Name -cne "Microsoft.OpenApi.OData.$version.nupkg") { + throw "Expected only the exact OData package matching the triggering tag." + } + $symbols = Join-Path $packages[0].DirectoryName "Microsoft.OpenApi.OData.$version.snupkg" + if (-not (Test-Path -LiteralPath $symbols -PathType Leaf)) { + throw "Missing exact OData symbols package for $version." + } + Write-Host "##vso[task.setvariable variable=ODataReleaseVersion]$version" + Write-Host "##vso[task.setvariable variable=ODataPackagePath]$($packages[0].FullName)" + displayName: 'Verify tagged OData release artifact' - task: GitHubRelease@1 displayName: 'GitHub release' - condition: succeededOrFailed() + condition: succeeded() inputs: gitHubConnection: 'Github-MaggieKimani1' action: edit tagSource: userSpecifiedTag - tag: 'v$(artifactVersion)' - title: 'v$(artifactVersion)' + tag: 'v$(ODataReleaseVersion)' + title: 'v$(ODataReleaseVersion)' releaseNotesSource: inline - assets: '$(Pipeline.Workspace)\Microsoft.OpenApi.OData.*.nupkg' + assets: '$(ODataPackagePath)' addChangeLog: false diff --git a/.azure-pipelines/hidi-release.yml b/.azure-pipelines/hidi-release.yml index 6bd8d486..0087ca80 100644 --- a/.azure-pipelines/hidi-release.yml +++ b/.azure-pipelines/hidi-release.yml @@ -49,6 +49,16 @@ extends: artifactName: HidiDockerContext targetPath: '$(Build.ArtifactStagingDirectory)\HidiDockerContext' steps: + - pwsh: | + $ErrorActionPreference = 'Stop' + $version = ([xml](Get-Content -LiteralPath src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj)).SelectSingleNode('/Project/PropertyGroup/Version').InnerText + if ($version -cnotmatch '^2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected a hidi 2.x version, got $version." } + $sourceBranch = $env:BUILD_SOURCEBRANCH + if ($sourceBranch.StartsWith('refs/tags/') -and $sourceBranch -cne "refs/tags/hidi-v$version") { + throw "This pipeline only releases hidi-v2.* tags exactly matching the hidi project version." + } + Write-Host "##vso[task.setvariable variable=HidiVersion]$version" + displayName: Verify Hidi component tag and project version - task: CopyFiles@2 displayName: Stage public-only hidi Docker context inputs: @@ -103,12 +113,6 @@ extends: '@ | Set-Content -Path '$(Build.SourcesDirectory)\nuget.config' displayName: Configure approved NuGet feed - pwsh: | - $version = ([xml](Get-Content src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj)).Project.PropertyGroup.Version - if ($version -notmatch '^2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected a hidi 2.x version, got $version." } - if ('$(Build.SourceBranch)' -like 'refs/tags/*' -and '$(Build.SourceBranch)' -ne "refs/tags/hidi-v$version") { - throw "Hidi tag must exactly match the hidi project version." - } - Write-Host "##vso[task.setvariable variable=HidiVersion]$version" dotnet build src\Microsoft.OpenApi.Hidi\Microsoft.OpenApi.Hidi.csproj -c $(buildConfiguration) -p:GeneratePackageOnBuild=false if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } dotnet run --project test\Microsoft.OpenApi.Hidi.Tests\Microsoft.OpenApi.Hidi.Tests.csproj -c $(buildConfiguration) -- --minimum-expected-tests 1 @@ -226,12 +230,22 @@ extends: deploy: steps: - pwsh: | - $tag = '$(Build.SourceBranch)' -replace '^refs/tags/hidi-v', '' + $ErrorActionPreference = 'Stop' + $sourceBranch = $env:BUILD_SOURCEBRANCH + if ($sourceBranch -cnotmatch '^refs/tags/hidi-v2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected a Hidi hidi-v2.* release tag." } + $tag = $sourceBranch.Substring('refs/tags/hidi-v'.Length) if ([version]($tag -split '-')[0] -le [version]'2.12.2') { throw "Never republish the source migration baseline or an older hidi version." } $package = "$(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.$tag.nupkg" - if (-not (Test-Path $package)) { throw "Missing exact hidi package: $package" } + if (-not (Test-Path -LiteralPath $package -PathType Leaf)) { throw "Missing exact hidi package: $package" } $symbols = "$(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.$tag.snupkg" - if (-not (Test-Path $symbols)) { throw "Missing exact hidi symbols: $symbols" } + if (-not (Test-Path -LiteralPath $symbols -PathType Leaf)) { throw "Missing exact hidi symbols: $symbols" } + $packages = @(Get-ChildItem -LiteralPath '$(Pipeline.Workspace)\hidi' -Filter '*.nupkg' -File) + if ($packages.Count -ne 1 -or $packages[0].Name -cne "Microsoft.OpenApi.Hidi.$tag.nupkg") { + throw "Expected only the exact Hidi package matching the triggering tag." + } + foreach ($artifact in @("$(Pipeline.Workspace)\hidi\Microsoft.OpenApi.Hidi.exe", "$(Pipeline.Workspace)\hidi\hidi-win-x64-$tag.zip")) { + if (-not (Test-Path -LiteralPath $artifact -PathType Leaf)) { throw "Missing exact Hidi executable artifact: $artifact" } + } Write-Host "##vso[task.setvariable variable=HidiReleaseVersion]$tag" displayName: Verify new hidi release version and exact package - task: PowerShell@2 @@ -303,8 +317,10 @@ extends: vmImage: ubuntu-latest steps: - pwsh: | - $version = ([xml](Get-Content -LiteralPath '$(Pipeline.Workspace)/HidiDockerContext/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj')).Project.PropertyGroup.Version - if ('$(Build.SourceBranch)' -ne "refs/tags/hidi-v$version") { throw "Hidi tag does not match project version." } + $ErrorActionPreference = 'Stop' + $version = ([xml](Get-Content -LiteralPath '$(Pipeline.Workspace)/HidiDockerContext/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj')).SelectSingleNode('/Project/PropertyGroup/Version').InnerText + if ($version -cnotmatch '^2\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { throw "Expected a hidi 2.x container version, got $version." } + if ($env:BUILD_SOURCEBRANCH -cne "refs/tags/hidi-v$version") { throw "Hidi tag does not match project version." } if ([version]($version -split '-')[0] -le [version]'2.12.2') { throw "Do not republish the migration baseline or an older hidi version." } Write-Host "##vso[task.setvariable variable=HidiReleaseVersion]$version" displayName: Read independent hidi version diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index 5f46bf25..87d433db 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -41,25 +41,3 @@ jobs: config-file: release-please-config.json manifest-file: .release-please-manifest.json target-branch: ${{ github.ref_name }} - - release-please-hidi: - # Enable only after the destination PR lands and source publishing is cut over. - if: ${{ false }} - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} - private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} - - name: Release Please (independent hidi version) - uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4 - with: - token: ${{ steps.app-token.outputs.token }} - config-file: hidi-release-please-config.json - manifest-file: .hidi-release-please-manifest.json - target-branch: ${{ github.ref_name }} diff --git a/.hidi-release-please-manifest.json b/.hidi-release-please-manifest.json deleted file mode 100644 index ddfb5d57..00000000 --- a/.hidi-release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "2.12.2" -} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 89d8ff81..d12ffb60 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,4 @@ { - ".": "2.2.1" + ".": "2.2.1", + "src/Microsoft.OpenApi.Hidi": "2.12.2" } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d44c94f2..9a777e70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,11 +26,47 @@ public-only signing key preserves assembly identity and friend-assembly access; never add the source repository's private strong-name key. Production signing is handled only by the official Azure pipeline. -Hidi's version is in its own project and `.hidi-release-please-manifest.json`. +Hidi's version is in its own project and the +`src/Microsoft.OpenApi.Hidi` entry in `.release-please-manifest.json`. Its `hidi-v2.*` tags must not trigger OData publishing. Version 2.12.2 is the -already-published migration baseline, not a new release. Hidi release automation -and publishing remain disabled until source cutover. History-import migration -PRs must be merged with a merge commit, never squash or rebase. +already-published migration baseline, not a new release. Production publishing +remains disabled until source cutover. History-import migration PRs must be +merged with a merge commit, never squash or rebase. + +### Independent automated versions + +The standard Release Please action uses one `release-please-config.json` and +one `.release-please-manifest.json` with two components: root OData (`.`) and +Hidi (`src/Microsoft.OpenApi.Hidi`). Their versions advance independently in +the generated release PRs. The stock `separate-pull-requests` option preserves +OData's existing release branch and avoids the stock engine's componentless-root +parsing issue with combined release PRs. + +Hidi source changes update its project `` and local `CHANGELOG.md`; +the root component excludes Hidi source and tests. OData changes update +`Directory.Build.props` and the root `CHANGELOG.md`, without changing Hidi. +Hidi keeps `hidi-v2.*` tags; OData keeps componentless `v2.*` tags. Hidi's +published OpenAPI dependency versions are not changed by its version updater. + +Routing follows component paths, not commit scopes. Hidi-only tests do not +create a release by themselves, and root-level distribution/helper files remain +root-owned under this standard configuration. Production package, executable, +and container publishing remains gated in the official Azure pipeline. + +### Component-tagged Azure releases + +On `support/v2`, `.azure-pipelines/ci-build.yml` releases only the OData package +and attaches only its artifact for `v2.*` tags. +`.azure-pipelines/hidi-release.yml` handles only `hidi-v2.*` tags for Hidi's +NuGet package, Windows executable/ZIP, and container. Hidi publishing stays +disabled until the protected cutover; this routing does not enable it. + +Tag runs must exactly match the component's project version before staging +artifacts. Release jobs require the exact tag-derived package and symbols; +Hidi also requires its executable/ZIP or matching Docker-context version. +Wrong-component, malformed, other-major, or version-mismatched manually selected +tags fail validation instead of publishing. Ordinary branch/PR builds still +validate both projects as before, without running tag-only release stages. OpenAPI.net.OData is open to contributions. There are a couple of different recommended paths to get contributions into the released version of this library. diff --git a/README.md b/README.md index a1e490db..0253c820 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,12 @@ tests requires the .NET 10 SDK. Existing OData tests still use .NET 8 and VSTest Hidi's filtered history was imported with a merge, not squashed. **Merge this migration PR using a merge commit, not squash or rebase**, so the canonical imported commits can also be retained by the later main-branch migration. -Destination hidi release and production publishing are disabled until source -cutover; OpenAPI.NET remains the publisher in the meantime. +The standard Release Please config and manifest track Hidi and OData as separate +components on `support/v2`, updating their project versions and changelogs +independently. Production publishing remains disabled until source cutover; +OpenAPI.NET remains the package publisher in the meantime. +See [independent automated versions](CONTRIBUTING.md#independent-automated-versions) +for component paths and version-file ownership. The gated hidi NuGet release uses `EsrpRelease@14`, staging only the exact `Microsoft.OpenApi.Hidi` package and its `.snupkg` symbols from the Hidi build diff --git a/hidi-release-please-config.json b/hidi-release-please-config.json deleted file mode 100644 index 6e95505c..00000000 --- a/hidi-release-please-config.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "bootstrap-sha": "8ebc6abbb1da683db4b5bc0928183dec947f2f02", - "release-type": "simple", - "include-component-in-tag": true, - "include-v-in-tag": true, - "exclude-paths": [ - ".azure-pipelines", - ".github", - ".idea", - ".vs", - ".vscode", - "src/Microsoft.OpenApi.OData.Reader", - "src/OoasGui", - "src/OoasUtil", - "test/Microsoft.OpenAPI.OData.Reader.Tests", - "docs", - "CHANGELOG.md", - "Directory.Build.props", - "release-please-config.json", - ".release-please-manifest.json" - ], - "packages": { - ".": { - "package-name": "Microsoft.OpenApi.Hidi", - "component": "hidi", - "changelog-path": "src/Microsoft.OpenApi.Hidi/CHANGELOG.md", - "extra-files": [ - { - "type": "xml", - "path": "src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj", - "xpath": "//Project/PropertyGroup/Version" - } - ] - } - } -} diff --git a/release-please-config.json b/release-please-config.json index 75e417d6..1b2a820b 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,23 +5,10 @@ ".github", ".idea", ".vs", - ".vscode", - "src/Microsoft.OpenApi.Hidi", - "src/Microsoft.OpenApi.Tool", - "src/Microsoft.Hidi", - "Microsoft.OpenApi.Hidi.Tests", - "test/Microsoft.OpenApi.Hidi.Tests", - "test/Microsoft.OpenApi.Tests", - "tool/Microsoft.OpenApi.Hidi.public.snk", - "tool/Microsoft.OpenApi.OData.public.snk", - "tool/hidi-local-nuget.config", - "Dockerfile", - ".dockerignore", - "install-tool.ps1", - "hidi-release-please-config.json", - ".hidi-release-please-manifest.json" + ".vscode" ], "release-type": "simple", + "separate-pull-requests": true, "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "include-component-in-tag": false, @@ -31,6 +18,15 @@ ".": { "package-name": "Microsoft.OpenApi.OData", "changelog-path": "CHANGELOG.md", + "exclude-paths": [ + ".azure-pipelines", + ".github", + ".idea", + ".vs", + ".vscode", + "src/Microsoft.OpenApi.Hidi", + "test/Microsoft.OpenApi.Hidi.Tests" + ], "extra-files": [ { "type": "xml", @@ -38,6 +34,20 @@ "xpath": "//Project/PropertyGroup/Version" } ] + }, + "src/Microsoft.OpenApi.Hidi": { + "package-name": "Microsoft.OpenApi.Hidi", + "component": "hidi", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": true, + "include-v-in-tag": true, + "extra-files": [ + { + "type": "xml", + "path": "Microsoft.OpenApi.Hidi.csproj", + "xpath": "//Project/PropertyGroup/Version" + } + ] } }, "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" diff --git a/src/Microsoft.OpenApi.Hidi/CHANGELOG.md b/src/Microsoft.OpenApi.Hidi/CHANGELOG.md index 21c25cb3..7dede925 100644 --- a/src/Microsoft.OpenApi.Hidi/CHANGELOG.md +++ b/src/Microsoft.OpenApi.Hidi/CHANGELOG.md @@ -6,5 +6,6 @@ Migration baseline from OpenAPI.NET `support/v2`. This version is already published and must not be republished from this repository. Earlier release notes remain in [OpenAPI.NET releases](https://github.com/microsoft/OpenAPI.NET/releases). -Hidi has its own release manifest and `hidi-v2.*` tags, independent of OData. -Destination release automation is disabled until the source publishing cutover. +Hidi has its own component entry in `.release-please-manifest.json` and +`hidi-v2.*` tags, independent of OData. +Destination publishing remains disabled until the source publishing cutover. diff --git a/src/Microsoft.OpenApi.Hidi/readme.md b/src/Microsoft.OpenApi.Hidi/readme.md index b8703368..a922a5ee 100644 --- a/src/Microsoft.OpenApi.Hidi/readme.md +++ b/src/Microsoft.OpenApi.Hidi/readme.md @@ -71,9 +71,12 @@ docker run --rm --mount "type=bind,source=$PWD\test\Microsoft.OpenApi.Hidi.Tests Local/CI builds use the public-only strong-name identity. Official release artifacts are signed in Azure Pipelines; private signing keys do not belong in this repository. The migration baseline 2.12.2 is already published. Destination -NuGet, GitHub release, and Docker publishing are disabled until source cutover, -and the first destination release must advance the hidi version. OData releases -use separate artifacts and tags. +NuGet, executable, and Docker publishing are disabled until source cutover. +The standard Release Please config tracks Hidi as its own component, with a +separate manifest version, project version, changelog, and `hidi-v2.*` tags. +The first destination package release must advance beyond the baseline and use +an exact `hidi-v2.` tag matching the project. OData releases use separate +artifacts and tags. Docker builds opt into `HidiPublicSignBuild=true` for the local OData project, using its own public-only key without changing normal OData signing behavior.