From defa6909c80a7568d73244d6ab1c871d411916df Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 23 Sep 2025 17:05:15 +0100 Subject: [PATCH 01/13] linux/{pack,layout}.sh: allow specification of output dir Allow a caller of pack.sh and layout.sh to specify the location of the payload and symbols. Signed-off-by: Matthew John Cheetham --- src/linux/Packaging.Linux/build.sh | 2 +- src/linux/Packaging.Linux/layout.sh | 17 +++++++++++++---- src/linux/Packaging.Linux/pack.sh | 12 ++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/linux/Packaging.Linux/build.sh b/src/linux/Packaging.Linux/build.sh index 62352a7e8..4a77eff69 100755 --- a/src/linux/Packaging.Linux/build.sh +++ b/src/linux/Packaging.Linux/build.sh @@ -64,7 +64,7 @@ PAYLOAD="$OUTDIR/payload" SYMBOLS="$OUTDIR/payload.sym" # Lay out payload -"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" || exit 1 +"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --runtime="$RUNTIME" --output="$PAYLOAD" --symbol-output="$SYMBOLS" || exit 1 if [ $INSTALL_FROM_SOURCE = true ]; then echo "Installing to $INSTALL_PREFIX" diff --git a/src/linux/Packaging.Linux/layout.sh b/src/linux/Packaging.Linux/layout.sh index ccf031156..fe3a0f2b8 100755 --- a/src/linux/Packaging.Linux/layout.sh +++ b/src/linux/Packaging.Linux/layout.sh @@ -23,10 +23,17 @@ case "$i" in CONFIGURATION="${i#*=}" shift # past argument=value ;; + --output=*) + PAYLOAD="${i#*=}" + shift # past argument=value + ;; --runtime=*) RUNTIME="${i#*=}" shift # past argument=value ;; + --symbol-output=*) + SYMBOLOUT="${i#*=}" + ;; *) # unknown option ;; @@ -46,10 +53,12 @@ FRAMEWORK=net8.0 # Perform pre-execution checks CONFIGURATION="${CONFIGURATION:=Debug}" - -# Outputs -PAYLOAD="$PROJ_OUT/$CONFIGURATION/payload" -SYMBOLOUT="$PROJ_OUT/$CONFIGURATION/payload.sym" +if [ -z "$PAYLOAD" ]; then + die "--output was not set" +fi +if [ -z "$SYMBOLOUT" ]; then + SYMBOLOUT="$PAYLOAD.sym" +fi # Cleanup payload directory if [ -d "$PAYLOAD" ]; then diff --git a/src/linux/Packaging.Linux/pack.sh b/src/linux/Packaging.Linux/pack.sh index e69783bb6..52d0137e4 100755 --- a/src/linux/Packaging.Linux/pack.sh +++ b/src/linux/Packaging.Linux/pack.sh @@ -36,6 +36,10 @@ case "$i" in CONFIGURATION="${i#*=}" shift # past argument=value ;; + --output=*) + OUTPUT_ROOT="${i#*=}" + shift # past argument=value + ;; *) # unknown option ;; @@ -59,11 +63,15 @@ if [ -z "$RUNTIME" ]; then die "--runtime was not set" fi -TAROUT="$PROJ_OUT/$CONFIGURATION/tar/" +if [ -z "$OUTPUT_ROOT" ]; then + OUTPUT_ROOT="$PROJ_OUT/$CONFIGURATION" +fi + +TAROUT="$OUTPUT_ROOT/tar" TARBALL="$TAROUT/gcm-$RUNTIME.$VERSION.tar.gz" SYMTARBALL="$TAROUT/gcm-$RUNTIME.$VERSION-symbols.tar.gz" -DEBOUT="$PROJ_OUT/$CONFIGURATION/deb" +DEBOUT="$OUTPUT_ROOT/deb" DEBROOT="$DEBOUT/root" DEBPKG="$DEBOUT/gcm-$RUNTIME.$VERSION.deb" mkdir -p "$DEBROOT" From f893082095185e1ca735e2664ad8a05f64688556 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 11:41:24 +0100 Subject: [PATCH 02/13] dotnettool: translate layout+pack scripts to pwsh Translate the layout.sh and pack.sh Bash scripts to PowerShell scripts. We are now building the .NET tool NuGet packages on Windows. Signed-off-by: Matthew John Cheetham --- .github/workflows/release.yml | 2 +- src/shared/DotnetTool/dotnet-tool.nuspec | 5 +- src/shared/DotnetTool/layout.ps1 | 90 ++++++++++++++++++++++ src/shared/DotnetTool/layout.sh | 83 --------------------- src/shared/DotnetTool/pack.ps1 | 95 ++++++++++++++++++++++++ src/shared/DotnetTool/pack.sh | 52 ------------- 6 files changed, 188 insertions(+), 139 deletions(-) create mode 100644 src/shared/DotnetTool/layout.ps1 delete mode 100755 src/shared/DotnetTool/layout.sh create mode 100644 src/shared/DotnetTool/pack.ps1 delete mode 100755 src/shared/DotnetTool/pack.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2640fe21f..20d3309f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -401,7 +401,7 @@ jobs: run: | src/shared/DotnetTool/pack.sh --configuration=Release \ --version="${{ needs.prereqs.outputs.version }}" \ - --publish-dir=$(pwd)/signed + --package-root=$(pwd)/signed - name: Upload unsigned package uses: actions/upload-artifact@v5 diff --git a/src/shared/DotnetTool/dotnet-tool.nuspec b/src/shared/DotnetTool/dotnet-tool.nuspec index cf9ba7444..35f81ebc9 100644 --- a/src/shared/DotnetTool/dotnet-tool.nuspec +++ b/src/shared/DotnetTool/dotnet-tool.nuspec @@ -6,13 +6,12 @@ Secure, cross-platform Git credential storage with authentication to Azure Repos, GitHub, and other popular Git hosting services. git-credential-manager images\icon.png - https://raw.githubusercontent.com/git-ecosystem/git-credential-manager/main/assets/gcm-transparent.png - - + + diff --git a/src/shared/DotnetTool/layout.ps1 b/src/shared/DotnetTool/layout.ps1 new file mode 100644 index 000000000..ca9b13011 --- /dev/null +++ b/src/shared/DotnetTool/layout.ps1 @@ -0,0 +1,90 @@ +<# +.SYNOPSIS + Lays out the .NET tool package directory. + +.PARAMETER Configuration + Build configuration (Debug/Release). Defaults to Debug. + +.PARAMETER Output + Root output directory for the nupkg layout. If omitted: + out/shared/DotnetTool/nupkg/ + +.EXAMPLE + pwsh ./layout.ps1 -Configuration Release + +.EXAMPLE + pwsh ./layout.ps1 -Output C:\temp\tool-layout + +#> + +[CmdletBinding()] +param( + [string]$Configuration = "Debug", + [string]$Output +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Make-Absolute { + param([string]$Path) + if ([string]::IsNullOrWhiteSpace($Path)) { return $null } + if ([System.IO.Path]::IsPathRooted($Path)) { return $Path } + return (Join-Path -Path (Get-Location) -ChildPath $Path) +} + +Write-Host "Starting layout..." -ForegroundColor Cyan + +# Directories +$ScriptDir = $PSScriptRoot +$Root = (Resolve-Path (Join-Path $ScriptDir "..\..\..")).Path +$Src = Join-Path $Root "src" +$Out = Join-Path $Root "out" +$DotnetToolRel = "shared/DotnetTool" +$GcmSrc = Join-Path $Src "shared\Git-Credential-Manager" +$ProjOut = Join-Path $Out $DotnetToolRel + +$Framework = "net8.0" + +if (-not $Output -or $Output.Trim() -eq "") { + $Output = Join-Path $ProjOut "nupkg\$Configuration" +} + +$ImgOut = Join-Path $Output "images" +$BinOut = Join-Path $Output "tools\$Framework\any" + +# Cleanup previous layout +if (Test-Path $Output) { + Write-Host "Cleaning existing output directory '$Output'..." + Remove-Item -Force -Recurse $Output +} + +# Recreate directories +$null = New-Item -ItemType Directory -Path $BinOut -Force +$null = New-Item -ItemType Directory -Path $ImgOut -Force + +# Determine DOTNET_ROOT if not set +if (-not $env:DOTNET_ROOT -or $env:DOTNET_ROOT.Trim() -eq "") { + $dotnetCmd = Get-Command dotnet -ErrorAction Stop + $env:DOTNET_ROOT = Split-Path -Parent $dotnetCmd.Source +} + +Write-Host "Publishing core application..." +& "$env:DOTNET_ROOT/dotnet" publish $GcmSrc ` + --configuration $Configuration ` + --framework $Framework ` + --output (Make-Absolute $BinOut) ` + -p:UseAppHost=false + +if ($LASTEXITCODE -ne 0) { + Write-Error "dotnet publish failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +Write-Host "Copying package configuration file..." +Copy-Item -Path (Join-Path $Src "$DotnetToolRel\DotnetToolSettings.xml") -Destination $BinOut -Force + +Write-Host "Copying images..." +Copy-Item -Path (Join-Path $Src "$DotnetToolRel\icon.png") -Destination $ImgOut -Force + +Write-Host "Layout complete." -ForegroundColor Green diff --git a/src/shared/DotnetTool/layout.sh b/src/shared/DotnetTool/layout.sh deleted file mode 100755 index f5244dbbd..000000000 --- a/src/shared/DotnetTool/layout.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -make_absolute () { - case "$1" in - /*) - echo "$1" - ;; - *) - echo "$PWD/$1" - ;; - esac -} - -##################################################################### -# Lay out -##################################################################### -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -GCM_SRC="$SRC/shared/Git-Credential-Manager" -DOTNET_TOOL="shared/DotnetTool" -PROJ_OUT="$OUT/$DOTNET_TOOL" - -CONFIGURATION="${CONFIGURATION:=Debug}" - -# Build parameters -FRAMEWORK=net8.0 - -# Outputs -OUTDIR="$PROJ_OUT/nupkg/$CONFIGURATION" -IMGOUT="$OUTDIR/images" -PAYLOAD="$OUTDIR/payload" -SYMBOLOUT="$OUTDIR/payload.sym" - -# Cleanup output directory -if [ -d "$OUTDIR" ]; then - echo "Cleaning existing output directory '$OUTDIR'..." - rm -rf "$OUTDIR" -fi - -# Ensure output directories exist -mkdir -p "$PAYLOAD" "$SYMBOLOUT" "$IMGOUT" - -if [ -z "$DOTNET_ROOT" ]; then - DOTNET_ROOT="$(dirname $(which dotnet))" -fi - -# Publish core application executables -echo "Publishing core application..." -$DOTNET_ROOT/dotnet publish "$GCM_SRC" \ - --configuration="$CONFIGURATION" \ - --framework="$FRAMEWORK" \ - --output="$(make_absolute "$PAYLOAD")" \ - -p:UseAppHost=false || exit 1 - -# Collect symbols -echo "Collecting managed symbols..." -mv "$PAYLOAD"/*.pdb "$SYMBOLOUT" || exit 1 - -# Copy DotnetToolSettings.xml file -echo "Copying out package configuration files..." -cp "$SRC/$DOTNET_TOOL/DotnetToolSettings.xml" "$PAYLOAD/" - -# Copy package icon image -echo "Copying images..." -cp "$SRC/$DOTNET_TOOL/icon.png" "$IMGOUT" || exit 1 - -echo "Build complete." diff --git a/src/shared/DotnetTool/pack.ps1 b/src/shared/DotnetTool/pack.ps1 new file mode 100644 index 000000000..6842d030a --- /dev/null +++ b/src/shared/DotnetTool/pack.ps1 @@ -0,0 +1,95 @@ +<# +.SYNOPSIS + Creates the NuGet package for the .NET tool. + +.PARAMETER Configuration + Build configuration (Debug/Release). Defaults to Debug. + +.PARAMETER Version + Package version (required). + +.PARAMETER PackageRoot + Root of the pre-laid-out package structure (from layout). Defaults to: + out/shared/DotnetTool/nupkg/ + +.PARAMETER Output + Optional directory for the produced .nupkg/.snupkg. If omitted NuGet chooses. + +.EXAMPLE + pwsh ./pack.ps1 -Version 2.0.123-beta + +.EXAMPLE + pwsh ./pack.ps1 -Configuration Release -Version 2.1.0 -Output C:\pkgs + +#> + +[CmdletBinding()] +param( + [string]$Configuration = "Debug", + [Parameter(Mandatory = $true)] + [string]$Version, + [string]$PackageRoot, + [string]$Output +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +Write-Host "Starting pack..." -ForegroundColor Cyan + +# Directories +$ScriptDir = $PSScriptRoot +$Root = (Resolve-Path (Join-Path $ScriptDir "..\..\..")).Path +$Src = Join-Path $Root "src" +$Out = Join-Path $Root "out" +$DotnetToolRel = "shared\DotnetTool" +$NuspecFile = Join-Path $Src "$DotnetToolRel\dotnet-tool.nuspec" + +if (-not (Test-Path $NuspecFile)) { + Write-Error "Could not locate nuspec file at '$NuspecFile'" + exit 1 +} + +if (-not $PackageRoot -or $PackageRoot.Trim() -eq "") { + $PackageRoot = Join-Path $Out "$DotnetToolRel\nupkg\$Configuration" +} + +if (-not (Test-Path $PackageRoot)) { + Write-Error "Package root '$PackageRoot' does not exist. Run layout.ps1 first." + exit 1 +} + +# Locate nuget +$nugetCmd = Get-Command nuget -ErrorAction SilentlyContinue +if (-not $nugetCmd) { + Write-Error "nuget CLI not found in PATH (install: https://www.nuget.org/downloads)" + exit 1 +} +$nugetExe = $nugetCmd.Source + +Write-Host "Creating .NET tool package..." + +$packArgs = @( + "pack", "$NuspecFile", + "-Properties", "Configuration=$Configuration", + "-Version", $Version, + "-Symbols", "-SymbolPackageFormat", "snupkg", + "-BasePath", "$PackageRoot" +) + +if ($Output -and $Output.Trim() -ne "") { + if (-not (Test-Path $Output)) { + Write-Host "Creating output directory '$Output'..." + New-Item -ItemType Directory -Force -Path $Output | Out-Null + } + $packArgs += @("-OutputDirectory", "$Output") +} + +& $nugetExe @packArgs + +if ($LASTEXITCODE -ne 0) { + Write-Error "nuget pack failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +Write-Host ".NET tool pack complete." -ForegroundColor Green diff --git a/src/shared/DotnetTool/pack.sh b/src/shared/DotnetTool/pack.sh deleted file mode 100755 index 5b2eaf8dc..000000000 --- a/src/shared/DotnetTool/pack.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} - -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - --version=*) - VERSION="${i#*=}" - shift # past argument=value - ;; - --publish-dir=*) - PUBLISH_DIR="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -CONFIGURATION="${CONFIGURATION:=Debug}" -if [ -z "$VERSION" ]; then - die "--version was not set" -fi - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -DOTNET_TOOL="shared/DotnetTool" - -if [ -z "$PUBLISH_DIR" ]; then - PUBLISH_DIR="$OUT/$DOTNET_TOOL/nupkg/$CONFIGURATION" -fi - -echo "Creating dotnet tool package..." - -dotnet pack "$SRC/$DOTNET_TOOL/DotnetTool.csproj" \ - /p:Configuration="$CONFIGURATION" \ - /p:PackageVersion="$VERSION" \ - /p:PublishDir="$PUBLISH_DIR/" - -echo "Dotnet tool pack complete." From 6e374baf267e24f011f45017b7c87946620503cf Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Fri, 31 Oct 2025 11:26:41 +0000 Subject: [PATCH 03/13] osx/codesign.sh: print entitlements file before signing Signed-off-by: Matthew John Cheetham --- src/osx/Installer.Mac/codesign.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/osx/Installer.Mac/codesign.sh b/src/osx/Installer.Mac/codesign.sh index d66c8acd9..edd3d09fb 100755 --- a/src/osx/Installer.Mac/codesign.sh +++ b/src/osx/Installer.Mac/codesign.sh @@ -20,6 +20,11 @@ echo "Directory: $SIGN_DIR" echo "Developer ID: $DEVELOPER_ID" echo "Entitlements: $ENTITLEMENTS_FILE" echo "======== END INPUTS ========" +echo +echo "======== ENTITLEMENTS ========" +cat $ENTITLEMENTS_FILE +echo "======== END ENTITLEMENTS ========" +echo cd $SIGN_DIR for f in * From c09872027012218730c379a036435c16a7250bda Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 4 Nov 2025 13:44:28 +0000 Subject: [PATCH 04/13] osx/codesign.sh: apply linter recommendations Apply linter recommendations to the codesign.sh script used on macOS. Also always pass the absolute path of the entitlements file to the codesign command as using relative paths can sometimes fail. Signed-off-by: Matthew John Cheetham --- src/osx/Installer.Mac/codesign.sh | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/osx/Installer.Mac/codesign.sh b/src/osx/Installer.Mac/codesign.sh index edd3d09fb..44feedb6f 100755 --- a/src/osx/Installer.Mac/codesign.sh +++ b/src/osx/Installer.Mac/codesign.sh @@ -15,6 +15,13 @@ elif [ -z "$ENTITLEMENTS_FILE" ]; then exit 1 fi +# The codesign command needs the entitlements file to be given as an absolute +# file path; relative paths can cause issues. +if [[ "${ENTITLEMENTS_FILE}" != /* ]]; then + echo "error: entitlements file argument must be an absolute path" + exit 1 +fi + echo "======== INPUTS ========" echo "Directory: $SIGN_DIR" echo "Developer ID: $DEVELOPER_ID" @@ -22,30 +29,31 @@ echo "Entitlements: $ENTITLEMENTS_FILE" echo "======== END INPUTS ========" echo echo "======== ENTITLEMENTS ========" -cat $ENTITLEMENTS_FILE +cat "$ENTITLEMENTS_FILE" echo "======== END ENTITLEMENTS ========" echo -cd $SIGN_DIR +cd "$SIGN_DIR" || exit 1 for f in * do - macho=$(file --mime $f | grep mach) + macho=$(file --mime "$f" | grep mach) # Runtime sign dylibs and Mach-O binaries - if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; + if [[ $f == *.dylib ]] || [ -n "$macho" ]; then - echo "Runtime Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE + echo "Signing with entitlements and hardening: $f" + codesign -s "$DEVELOPER_ID" "$f" --timestamp --force --options=runtime --entitlements "$ENTITLEMENTS_FILE" elif [ -d "$f" ]; then - echo "Signing files in subdirectory $f" - cd $f - for i in * - do - codesign -s "$DEVELOPER_ID" $i --timestamp --force - done - cd .. + echo "Signing files in subdirectory: $f" + ( + cd "$f" || exit 1 + for i in * + do + codesign -s "$DEVELOPER_ID" "$i" --timestamp --force + done + ) else - echo "Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force + echo "Signing: $f" + codesign -s "$DEVELOPER_ID" "$f" --timestamp --force fi done From 2d42fe4423667c5993c80bc12c1aa8a3f1084fc1 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:40:25 +0100 Subject: [PATCH 05/13] .azure-pipelines/release.yml: add SDL pool info Add specific Windows pool information for SDL source tasks. These tasks only run on Windows. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index c2c644db8..3cd41d4b6 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -18,6 +18,12 @@ parameters: extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines parameters: + sdl: + # SDL source analysis tasks only run on Windows images + sourceAnalysisPool: + name: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows stages: - stage: windows displayName: 'Windows' From 7d409d74bddf9035890c81eae5d210ff074bc2c2 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:41:04 +0100 Subject: [PATCH 06/13] .azure-pipelines/release.yml: use simple build number Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 3cd41d4b6..72d1972b2 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -1,4 +1,4 @@ -name: Release-$(Date:yyyyMMdd)$(Rev:.r) +name: $(Date:yyyyMMdd)$(Rev:.r) trigger: none pr: none From 19a60783f5601dec4378ca75875e204ddd31ed87 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 15:45:05 +0100 Subject: [PATCH 07/13] .azure-pipelines/release.yml: add Windows builds Add Windows release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 188 +++++++++++++++++++++++++++++++++-- 1 file changed, 178 insertions(+), 10 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 72d1972b2..b62f76537 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -15,6 +15,31 @@ parameters: default: false displayName: 'Enable ESRP code signing' +# +# 1ES Pipeline Templates do not allow using a matrix strategy so we create +# a YAML object parameter with and foreach to create jobs for each entry. +# Each OS has its own matrix object since their build steps differ. +# + - name: windows_matrix + type: object + default: + - id: windows_x64 + jobName: 'Windows (x86)' + runtime: win-x86 + pool: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + +variables: + - name: 'esrpAppConnectionName' + value: '1ESGitClient-ESRP-App' + # ESRP signing variables set in the pipeline settings: + # - esrpEndpointUrl + # - esrpClientId + # - esrpTenantId + # - esrpKeyVaultName + # - esrpSignReqCertName + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelines parameters: @@ -25,14 +50,157 @@ extends: image: win-x86_64-ado1es os: windows stages: - - stage: windows - displayName: 'Windows' + - stage: build + displayName: 'Build and Sign' jobs: - - job: win_x86_build - displayName: 'Windows Build and Sign (x86)' - pool: - name: GitClient-1ESHostedPool-intel-pc - image: win-x86_64-ado1es - os: windows - steps: - - checkout: self + # + # Windows build jobs + # + - ${{ each dim in parameters.windows_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)\_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: PowerShell@2 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + $version = (Get-Content .\VERSION) -replace '\.\d+$', '' + Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: PowerShell@2 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: '.\src\windows\Installer.Windows\layout.ps1' + arguments: | + -Configuration Release ` + -Output $(Build.ArtifactStagingDirectory)\payload ` + -SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw + - task: ArchiveFiles@2 + displayName: 'Archive symbols' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\symbols_raw' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-win-x86-$(version)-symbols.zip' + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)\payload' + pattern: | + **/*.exe + **/*.dll + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: PowerShell@2 + displayName: 'Build installers' + inputs: + targetType: inline + script: | + dotnet build '.\src\windows\Installer.Windows\Installer.Windows.csproj' ` + --configuration Release ` + --no-dependencies ` + -p:NoLayout=true ` + -p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" ` + -p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign installers' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)\installers' + pattern: '**/*.exe' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: ArchiveFiles@2 + displayName: 'Archive signed payload' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\payload' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-win-x86-$(version).zip' + - task: PowerShell@2 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + New-Item -Path "$(Build.ArtifactStagingDirectory)\_final" -ItemType Directory -Force + Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.exe" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\symbols\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" + Copy-Item "$(Build.ArtifactStagingDirectory)\payload" -Destination "$(Build.ArtifactStagingDirectory)\_final" -Recurse From 620c3bf4ba547d86b33d036cf1249557586eace5 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 10:47:35 +0100 Subject: [PATCH 08/13] .azure-pipelines/release.yml: add macOS builds Add macOS release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 286 +++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index b62f76537..0666d77a3 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -30,9 +30,27 @@ parameters: image: win-x86_64-ado1es os: windows + - name: macos_matrix + type: object + default: + - id: macos_x64 + jobName: 'macOS (x64)' + runtime: osx-x64 + pool: 'Azure Pipelines' + image: macOS-latest + os: macos + - id: macos_arm64 + jobName: 'macOS (ARM64)' + runtime: osx-arm64 + pool: 'Azure Pipelines' + image: macOS-latest + os: macos + variables: - name: 'esrpAppConnectionName' value: '1ESGitClient-ESRP-App' + - name: 'esrpMIConnectionName' + value: '1ESGitClient-ESRP-MI' # ESRP signing variables set in the pipeline settings: # - esrpEndpointUrl # - esrpClientId @@ -204,3 +222,271 @@ extends: Copy-Item "$(Build.ArtifactStagingDirectory)\installers\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\symbols\*.zip" -Destination "$(Build.ArtifactStagingDirectory)\_final" Copy-Item "$(Build.ArtifactStagingDirectory)\payload" -Destination "$(Build.ArtifactStagingDirectory)\_final" -Recurse + + # + # macOS build jobs + # + - ${{ each dim in parameters.macos_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: Bash@3 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: Bash@3 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/layout.sh' + arguments: | + --runtime="${{ dim.runtime }}" \ + --configuration="Release" \ + --output="$(Build.ArtifactStagingDirectory)/payload" \ + --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" + - task: ArchiveFiles@2 + displayName: 'Archive symbols' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/symbols_raw' + includeRootFolder: false + archiveType: tar + tarCompression: gz + archiveFile: '$(Build.ArtifactStagingDirectory)/symbols/gcm-${{ dim.runtime }}-$(version)-symbols.tar.gz' + - task: AzureKeyVault@2 + displayName: 'Download developer certificate' + inputs: + azureSubscription: '$(esrpMIConnectionName)' + keyVaultName: '$(esrpKeyVaultName)' + secretsFilter: 'mac-developer-certificate,mac-developer-certificate-password,mac-developer-certificate-identity' + - task: Bash@3 + displayName: 'Import developer certificate' + inputs: + targetType: inline + script: | + # Create and unlock a keychain for the developer certificate + security create-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain + security default-keychain -s $(Agent.TempDirectory)/buildagent.keychain + security unlock-keychain -p pwd $(Agent.TempDirectory)/buildagent.keychain + + echo $(mac-developer-certificate) | base64 -D > $(Agent.TempDirectory)/cert.p12 + echo $(mac-developer-certificate-password) > $(Agent.TempDirectory)/cert.password + + # Import the developer certificate + security import $(Agent.TempDirectory)/cert.p12 \ + -k $(Agent.TempDirectory)/buildagent.keychain \ + -P "$(mac-developer-certificate-password)" \ + -T /usr/bin/codesign + + # Clean up the cert file immediately after import + rm $(Agent.TempDirectory)/cert.p12 + + # Set ACLs to allow codesign to access the private key + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k pwd \ + $(Agent.TempDirectory)/buildagent.keychain + - task: Bash@3 + displayName: 'Developer sign payload files' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign/payload + + # Copy the files that need signing (Mach-o executables and dylibs) + pushd $(Build.ArtifactStagingDirectory)/payload + find . -type f -exec file --mime {} + \ + | sed -n '/mach/s/: .*//p' \ + | while IFS= read -r f; do + rel="${f#./}" + tgt="$(Build.ArtifactStagingDirectory)/tosign/payload/$rel" + mkdir -p "$(dirname "$tgt")" + cp -- "$f" "$tgt" + done + popd + + # Developer sign the files + ./src/osx/Installer.Mac/codesign.sh \ + "$(Build.ArtifactStagingDirectory)/tosign/payload" \ + "$(mac-developer-certificate-identity)" \ + "$PWD/src/osx/Installer.Mac/entitlements.xml" + # ESRP code signing for macOS requires the files be packaged in a zip file for submission + - task: ArchiveFiles@2 + displayName: 'Archive files for signing' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/tosign/payload' + includeRootFolder: false + archiveType: zip + archiveFile: '$(Build.ArtifactStagingDirectory)/tosign/payload.zip' + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'payload.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "Enable" + } + } + ] + # Extract signed files, overwriting the unsigned files, ready for packaging + - task: Bash@3 + displayName: 'Extract signed payload files' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/payload.zip -d $(Build.ArtifactStagingDirectory)/payload + - task: Bash@3 + displayName: 'Build component package' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/pack.sh' + arguments: | + --version="$(version)" \ + --payload="$(Build.ArtifactStagingDirectory)/payload" \ + --output="$(Build.ArtifactStagingDirectory)/pkg/com.microsoft.gitcredentialmanager.component.pkg" + - task: Bash@3 + displayName: 'Build installer package' + inputs: + targetType: filePath + filePath: './src/osx/Installer.Mac/dist.sh' + arguments: | + --version="$(version)" \ + --runtime="${{ dim.runtime }}" \ + --package-path="$(Build.ArtifactStagingDirectory)/pkg" \ + --output="$(Build.ArtifactStagingDirectory)/installers-presign/gcm-${{ dim.runtime }}-$(version).pkg" + # ESRP code signing for macOS requires the files be packaged in a zip file first + - task: Bash@3 + displayName: 'Prepare installer package for signing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign + cd $(Build.ArtifactStagingDirectory)/installers-presign + zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers-presign.zip *.pkg + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign installer package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'installers-presign.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "Enable" + } + } + ] + # Extract signed installer, overwriting the unsigned installer + - task: Bash@3 + displayName: 'Extract signed installer package' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers-presign.zip -d $(Build.ArtifactStagingDirectory)/installers + - task: Bash@3 + displayName: 'Prepare installer package for notarization' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/tosign + cd $(Build.ArtifactStagingDirectory)/installers + zip -rX $(Build.ArtifactStagingDirectory)/tosign/installers.zip *.pkg + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Notarize installer package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/tosign' + pattern: 'installers.zip' + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401337-Apple", + "OperationCode": "MacAppNotarize", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "BundleId": "com.microsoft.gitcredentialmanager" + } + } + ] + # Extract signed and notarized installer pkg files, overwriting the unsigned files, ready for upload + - task: Bash@3 + displayName: 'Extract signed and notarized installer package' + inputs: + targetType: inline + script: | + unzip -uo $(Build.ArtifactStagingDirectory)/tosign/installers.zip -d $(Build.ArtifactStagingDirectory)/installers + - task: ArchiveFiles@2 + displayName: 'Archive signed payload' + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/payload' + includeRootFolder: false + archiveType: tar + tarCompression: gz + archiveFile: '$(Build.ArtifactStagingDirectory)/installers/gcm-${{ dim.runtime }}-$(version).tar.gz' + - task: Bash@3 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.pkg $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final From 296838cfc55491f8fc338255fb4f79ab7d3dbd83 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 10:48:28 +0100 Subject: [PATCH 09/13] .azure-pipelines/release.yml: add Linux builds Add Linux release build definitions on Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 0666d77a3..f0496aa57 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -46,6 +46,16 @@ parameters: image: macOS-latest os: macos + - name: linux_matrix + type: object + default: + - id: linux_x64 + jobName: 'Linux (x64)' + runtime: linux-x64 + pool: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: - name: 'esrpAppConnectionName' value: '1ESGitClient-ESRP-App' @@ -490,3 +500,102 @@ extends: cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final + + # + # Linux build jobs + # + - ${{ each dim in parameters.linux_matrix }}: + - job: ${{ dim.id }} + displayName: ${{ dim.jobName }} + pool: + name: ${{ dim.pool }} + image: ${{ dim.image }} + os: ${{ dim.os }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/_final' + artifactName: '${{ dim.runtime }}' + steps: + - checkout: self + - task: Bash@3 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=version;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: Bash@3 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/linux/Packaging.Linux/layout.sh' + arguments: | + --runtime="${{ dim.runtime }}" \ + --configuration="Release" \ + --output="$(Build.ArtifactStagingDirectory)/payload" \ + --symbol-output="$(Build.ArtifactStagingDirectory)/symbols_raw" + - task: Bash@3 + displayName: 'Build packages' + inputs: + targetType: filePath + filePath: './src/linux/Packaging.Linux/pack.sh' + arguments: | + --version="$(version)" \ + --runtime="${{ dim.runtime }}" \ + --payload="$(Build.ArtifactStagingDirectory)/payload" \ + --symbols="$(Build.ArtifactStagingDirectory)/symbols_raw" \ + --output="$(Build.ArtifactStagingDirectory)/pkg" + - task: Bash@3 + displayName: 'Move packages' + inputs: + targetType: inline + script: | + # Move symbols + mkdir -p $(Build.ArtifactStagingDirectory)/symbols + mv $(Build.ArtifactStagingDirectory)/pkg/tar/gcm-*-symbols.tar.gz $(Build.ArtifactStagingDirectory)/symbols + + # Move binary packages + mkdir -p $(Build.ArtifactStagingDirectory)/installers + mv $(Build.ArtifactStagingDirectory)/pkg/tar/*.tar.gz $(Build.ArtifactStagingDirectory)/installers + mv $(Build.ArtifactStagingDirectory)/pkg/deb/*.deb $(Build.ArtifactStagingDirectory)/installers + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign Debian package' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/installers' + pattern: | + **/*.deb + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-453387-Pgp", + "OperationCode": "LinuxSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: Bash@3 + displayName: 'Collect artifacts for publishing' + inputs: + targetType: inline + script: | + mkdir -p $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.deb $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final + cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final From 68d6cd6c0ec9b3842236537b807d1635bcced9c3 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 11:42:09 +0100 Subject: [PATCH 10/13] .azure-pipelines/release.yml: add .NET Tool release pipeline Add a release pipeline for the .NET Tool using Azure Pipelines. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 118 +++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index f0496aa57..bcf20f2a2 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -599,3 +599,121 @@ extends: cp $(Build.ArtifactStagingDirectory)/installers/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp $(Build.ArtifactStagingDirectory)/symbols/*.tar.gz $(Build.ArtifactStagingDirectory)/_final cp -r $(Build.ArtifactStagingDirectory)/payload $(Build.ArtifactStagingDirectory)/_final + + # + # .NET Tool build job + # + - job: dotnet_tool + displayName: '.NET Tool NuGet Package' + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: win-x86_64-ado1es + os: windows + templateContext: + outputs: + - output: pipelineArtifact + targetPath: '$(Build.ArtifactStagingDirectory)/packages' + artifactName: 'dotnet-tool' + steps: + - checkout: self + - task: PowerShell@2 + displayName: 'Read version file' + inputs: + targetType: inline + script: | + $version = (Get-Content .\VERSION) -replace '\.\d+$', '' + Write-Host "##vso[task.setvariable variable=version;isReadOnly=true]$version" + - task: UseDotNet@2 + displayName: 'Use .NET 8 SDK' + inputs: + packageType: sdk + version: '8.x' + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet CLI' + inputs: + versionSpec: '>= 6.0' + - task: PowerShell@2 + displayName: 'Build payload' + inputs: + targetType: filePath + filePath: './src/shared/DotnetTool/layout.ps1' + arguments: | + -Configuration Release ` + -Output "$(Build.ArtifactStagingDirectory)/nupkg" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign payload' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/nupkg' + pattern: | + **/*.exe + **/*.dll + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "OpusName": "Microsoft", + "OpusInfo": "https://www.microsoft.com", + "FileDigest": "/fd SHA256", + "PageHash": "/NPH", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + }, + { + "KeyCode": "CP-230012", + "OperationCode": "SigntoolVerify", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] + - task: PowerShell@2 + displayName: 'Create NuGet packages' + inputs: + targetType: filePath + filePath: './src/shared/DotnetTool/pack.ps1' + arguments: | + -Configuration Release ` + -Version "$(version)" ` + -PackageRoot "$(Build.ArtifactStagingDirectory)/nupkg" ` + -Output "$(Build.ArtifactStagingDirectory)/packages" + - task: EsrpCodeSigning@5 + condition: and(succeeded(), eq('${{ parameters.esrp }}', true)) + displayName: 'Sign NuGet packages' + inputs: + connectedServiceName: '$(esrpAppConnectionName)' + useMSIAuthentication: true + appRegistrationClientId: '$(esrpClientId)' + appRegistrationTenantId: '$(esrpTenantId)' + authAkvName: '$(esrpKeyVaultName)' + authSignCertName: '$(esrpSignReqCertName)' + serviceEndpointUrl: '$(esrpEndpointUrl)' + folderPath: '$(Build.ArtifactStagingDirectory)/packages' + pattern: | + **/*.nupkg + **/*.snupkg + useMinimatch: true + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": {} + } + ] From 66af9504aa24a3b149544a096b34044b82ab5ceb Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 24 Sep 2025 14:31:24 +0100 Subject: [PATCH 11/13] .azure-pipelines/release.yml: add GitHub and NuGet.org publishing Add a new stage (after build) to publish the assets to GitHub and NuGet.org. Each target (GitHub and NuGet.org) need to run in separate jobs due to restrictions of the 1ES pipeline templates: - Publishing a NuGet package requires us to use template `outputs` - `type: releaseJob` cannot specify outputs - `type: releaseJob` is required to use the `GitHubRelease` task Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 112 +++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index bcf20f2a2..0e76acf89 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -14,6 +14,14 @@ parameters: type: boolean default: false displayName: 'Enable ESRP code signing' + - name: 'github' + type: boolean + default: false + displayName: 'Enable GitHub release publishing' + - name: 'nuget' + type: boolean + default: false + displayName: 'Enable NuGet package publishing' # # 1ES Pipeline Templates do not allow using a matrix strategy so we create @@ -61,6 +69,10 @@ variables: value: '1ESGitClient-ESRP-App' - name: 'esrpMIConnectionName' value: '1ESGitClient-ESRP-MI' + - name: 'githubConnectionName' + value: 'GitHub-GitCredentialManager' + - name: 'nugetConnectionName' + value: '1ESGitClient-NuGet' # ESRP signing variables set in the pipeline settings: # - esrpEndpointUrl # - esrpClientId @@ -717,3 +729,103 @@ extends: "Parameters": {} } ] + + - stage: release + displayName: 'Release' + dependsOn: [build] + condition: and(succeeded(), or(eq('${{ parameters.github }}', true), eq('${{ parameters.nuget }}', true))) + jobs: + - job: release_validation + displayName: 'Release validation' + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + steps: + - task: Bash@3 + displayName: 'Read version file' + name: version + inputs: + targetType: inline + script: | + echo "##vso[task.setvariable variable=value;isOutput=true;isReadOnly=true]$(cat ./VERSION | sed -E 's/.[0-9]+$//')" + + - job: github + displayName: 'Publish GitHub release' + dependsOn: release_validation + condition: and(succeeded(), eq('${{ parameters.github }}', true)) + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: + version: $[dependencies.release_validation.outputs['version.value']] + templateContext: + type: releaseJob + isProduction: true + inputs: + # Installers and packages + - input: pipelineArtifact + artifactName: 'win-x86' + targetPath: $(Pipeline.Workspace)/assets/win-x86 + - input: pipelineArtifact + artifactName: 'osx-x64' + targetPath: $(Pipeline.Workspace)/assets/osx-x64 + - input: pipelineArtifact + artifactName: 'osx-arm64' + targetPath: $(Pipeline.Workspace)/assets/osx-arm64 + - input: pipelineArtifact + artifactName: 'linux-x64' + targetPath: $(Pipeline.Workspace)/assets/linux-x64 + - input: pipelineArtifact + artifactName: 'dotnet-tool' + targetPath: $(Pipeline.Workspace)/assets/dotnet-tool + steps: + - task: GitHubRelease@1 + displayName: 'Create Draft GitHub Release' + condition: and(succeeded(), eq('${{ parameters.github }}', true)) + inputs: + gitHubConnection: $(githubConnectionName) + repositoryName: git-ecosystem/git-credential-manager + tag: 'v$(version)' + tagSource: userSpecifiedTag + target: release + title: 'GCM $(version)' + isDraft: true + addChangeLog: false + assets: | + $(Pipeline.Workspace)/assets/win-x86/*.exe + $(Pipeline.Workspace)/assets/win-x86/*.zip + $(Pipeline.Workspace)/assets/osx-x64/*.pkg + $(Pipeline.Workspace)/assets/osx-x64/*.tar.gz + $(Pipeline.Workspace)/assets/osx-arm64/*.pkg + $(Pipeline.Workspace)/assets/osx-arm64/*.tar.gz + $(Pipeline.Workspace)/assets/linux-x64/*.deb + $(Pipeline.Workspace)/assets/linux-x64/*.tar.gz + $(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg + $(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg + + - job: nuget + displayName: 'Publish NuGet package' + dependsOn: release_validation + condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) + pool: + name: GitClientPME-1ESHostedPool-intel-pc + image: ubuntu-x86_64-ado1es + os: linux + variables: + version: $[dependencies.release_validation.outputs['version.value']] + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'dotnet-tool' + targetPath: $(Pipeline.Workspace)/assets/dotnet-tool + outputs: + - output: nuget + condition: and(succeeded(), eq('${{ parameters.nuget }}', true)) + displayName: 'Publish .NET Tool NuGet package' + packagesToPush: '$(Pipeline.Workspace)/assets/dotnet-tool/*.nupkg;$(Pipeline.Workspace)/assets/dotnet-tool/*.snupkg' + packageParentPath: $(Pipeline.Workspace)/assets/dotnet-tool + nuGetFeedType: external + publishPackageMetadata: true + publishFeedCredentials: $(nugetConnectionName) From 80ef7490b5da095007a395f8d286a5767a682335 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 10 Nov 2025 13:53:27 +0000 Subject: [PATCH 12/13] .github/workflows: remove old release workflows Remove the GitHub Actions-based release workflow files. We are using Azure Pipelines instead now. Signed-off-by: Matthew John Cheetham --- .github/workflows/release-dotnet-tool.yaml | 22 - .github/workflows/release.yml | 672 --------------------- 2 files changed, 694 deletions(-) delete mode 100644 .github/workflows/release-dotnet-tool.yaml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release-dotnet-tool.yaml b/.github/workflows/release-dotnet-tool.yaml deleted file mode 100644 index 594a2f4a3..000000000 --- a/.github/workflows/release-dotnet-tool.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: release-dotnet-tool -on: - release: - types: [released] - -jobs: - release: - runs-on: windows-latest - environment: release - steps: - - name: Download NuGet package from release and publish - run: | - # Get asset information - $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json - $asset = $github.release.assets | Where-Object -Property name -match '.nupkg$' - - # Download asset - Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $asset.name - - # Publish asset - dotnet nuget push $asset.name --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json - shell: powershell diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 20d3309f1..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,672 +0,0 @@ -name: release - -on: - workflow_dispatch: - -permissions: - id-token: write - contents: write - -jobs: - prereqs: - name: Prerequisites - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v5 - - - name: Set version - run: echo "version=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_OUTPUT - id: version - -# ================================ -# macOS -# ================================ - create-macos-artifacts: - name: Create macOS artifacts - runs-on: macos-latest - environment: release - needs: prereqs - strategy: - matrix: - runtime: [ osx-x64, osx-arm64 ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build src/osx/Installer.Mac/*.csproj \ - --configuration=MacRelease --no-self-contained \ - --runtime=${{ matrix.runtime }} - - - name: Run macOS unit tests - run: | - dotnet test --configuration=MacRelease - - - name: Lay out payload and symbols - run: | - src/osx/Installer.Mac/layout.sh \ - --configuration=MacRelease --output=payload \ - --symbol-output=symbols --runtime=${{ matrix.runtime }} - - - name: Set up signing/notarization infrastructure - env: - A1: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }} - A2: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }} - I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }} - I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} - N1: ${{ secrets.APPLE_TEAM_ID }} - N2: ${{ secrets.APPLE_DEVELOPER_ID }} - N3: ${{ secrets.APPLE_DEVELOPER_PASSWORD }} - N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} - run: | - echo "Setting up signing certificates" - security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - security default-keychain -s $RUNNER_TEMP/buildagent.keychain - security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain - - echo $A1 | base64 -D > $RUNNER_TEMP/cert.p12 - security import $RUNNER_TEMP/cert.p12 \ - -k $RUNNER_TEMP/buildagent.keychain \ - -P $A2 \ - -T /usr/bin/codesign - security set-key-partition-list \ - -S apple-tool:,apple:,codesign: \ - -s -k pwd \ - $RUNNER_TEMP/buildagent.keychain - - echo $I1 | base64 -D > $RUNNER_TEMP/cert.p12 - security import $RUNNER_TEMP/cert.p12 \ - -k $RUNNER_TEMP/buildagent.keychain \ - -P $I2 \ - -T /usr/bin/productbuild - security set-key-partition-list \ - -S apple-tool:,apple:,productbuild: \ - -s -k pwd \ - $RUNNER_TEMP/buildagent.keychain - - echo "Setting up notarytool" - xcrun notarytool store-credentials \ - --team-id $N1 \ - --apple-id $N2 \ - --password $N3 \ - "$N4" - - - name: Run codesign against payload - env: - A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }} - run: | - ./src/osx/Installer.Mac/codesign.sh "payload" "$A3" \ - "$GITHUB_WORKSPACE/src/osx/Installer.Mac/entitlements.xml" - - - name: Create component package - run: | - src/osx/Installer.Mac/pack.sh --payload="payload" \ - --version="${{ needs.prereqs.outputs.version }}" \ - --output="components/com.microsoft.gitcredentialmanager.component.pkg" - - - name: Create and sign product archive - env: - I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }} - run: | - src/osx/Installer.Mac/dist.sh --package-path=components \ - --version="${{ needs.prereqs.outputs.version }}" \ - --runtime="${{ matrix.runtime }}" \ - --output="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ - --identity="$I3" || exit 1 - - - name: Notarize product archive - env: - N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} - run: | - src/osx/Installer.Mac/notarize.sh \ - --package="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \ - --keychain-profile="$N4" - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: macos-${{ matrix.runtime }}-artifacts - path: | - ./pkg/* - ./symbols/* - ./payload/* - -# ================================ -# Windows -# ================================ - create-windows-artifacts: - name: Create Windows Artifacts - runs-on: windows-latest - environment: release - needs: prereqs - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build --configuration=WindowsRelease - - - name: Run Windows unit tests - run: | - dotnet test --configuration=WindowsRelease - - - name: Lay out Windows payload and symbols - run: | - cd $env:GITHUB_WORKSPACE\src\windows\Installer.Windows\ - ./layout.ps1 -Configuration WindowsRelease ` - -Output $env:GITHUB_WORKSPACE\payload ` - -SymbolOutput $env:GITHUB_WORKSPACE\symbols - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Sign payload files with Azure Code Signing - uses: azure/trusted-signing-action@v0.5.10 - with: - endpoint: https://wus2.codesigning.azure.net/ - trusted-signing-account-name: git-fundamentals-signing - certificate-profile-name: git-fundamentals-windows-signing - files-folder: ${{ github.workspace }}\payload - files-folder-filter: exe,dll - file-digest: SHA256 - timestamp-rfc3161: http://timestamp.acs.microsoft.com - timestamp-digest: SHA256 - - # The Azure Code Signing action overrides the .NET version, so we reset it. - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build with signed payload - run: | - dotnet build $env:GITHUB_WORKSPACE\src\windows\Installer.Windows ` - /p:PayloadPath=$env:GITHUB_WORKSPACE\payload /p:NoLayout=true ` - --configuration=WindowsRelease - mkdir installers - Move-Item -Path .\out\windows\Installer.Windows\bin\Release\net472\*.exe ` - -Destination $env:GITHUB_WORKSPACE\installers - - - name: Sign installers with Azure Code Signing - uses: azure/trusted-signing-action@v0.5.10 - with: - endpoint: https://wus2.codesigning.azure.net/ - trusted-signing-account-name: git-fundamentals-signing - certificate-profile-name: git-fundamentals-windows-signing - files-folder: ${{ github.workspace }}\installers - files-folder-filter: exe - file-digest: SHA256 - timestamp-rfc3161: http://timestamp.acs.microsoft.com - timestamp-digest: SHA256 - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: windows-artifacts - path: | - payload - installers - symbols - -# ================================ -# Linux -# ================================ - create-linux-artifacts: - name: Create Linux Artifacts - runs-on: ubuntu-latest - environment: release - needs: prereqs - strategy: - matrix: - runtime: [ linux-x64, linux-arm64, linux-arm ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build - run: | - dotnet build src/linux/Packaging.Linux/*.csproj \ - --configuration=LinuxRelease --no-self-contained \ - --runtime=${{ matrix.runtime }} - - - name: Run Linux unit tests - run: | - dotnet test --configuration=LinuxRelease - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Prepare for GPG signing - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - GPG_KEY_SECRET_NAME: ${{ secrets.GPG_KEY_SECRET_NAME }} - GPG_PASSPHRASE_SECRET_NAME: ${{ secrets.GPG_PASSPHRASE_SECRET_NAME }} - GPG_KEYGRIP_SECRET_NAME: ${{ secrets.GPG_KEYGRIP_SECRET_NAME }} - run: | - # Install debsigs - sudo apt install debsigs - - # Download GPG key, passphrase, and keygrip from Azure Key Vault - key=$(az keyvault secret show --name $GPG_KEY_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - passphrase=$(az keyvault secret show --name $GPG_PASSPHRASE_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - keygrip=$(az keyvault secret show --name $GPG_KEYGRIP_SECRET_NAME --vault-name $AZURE_VAULT --query "value") - - # Remove quotes from downloaded values - key=$(sed -e 's/^"//' -e 's/"$//' <<<"$key") - passphrase=$(sed -e 's/^"//' -e 's/"$//' <<<"$passphrase") - keygrip=$(sed -e 's/^"//' -e 's/"$//' <<<"$keygrip") - - # Import GPG key - echo "$key" | base64 -d | gpg --import --no-tty --batch --yes - - # Configure GPG - echo "allow-preset-passphrase" > ~/.gnupg/gpg-agent.conf - gpg-connect-agent RELOADAGENT /bye - /usr/lib/gnupg2/gpg-preset-passphrase --preset "$keygrip" <<<"$passphrase" - - - name: Sign Debian package and tarball - run: | - # Sign Debian package - version=${{ needs.prereqs.outputs.version }} - mv out/linux/Packaging.Linux/Release/deb/gcm-${{ matrix.runtime }}.$version.deb . - debsigs --sign=origin --verify --check gcm-${{ matrix.runtime }}.$version.deb - - # Generate tarball signature file - mv -v out/linux/Packaging.Linux/Release/tar/* . - gpg --batch --yes --armor --output gcm-${{ matrix.runtime }}.$version.tar.gz.asc \ - --detach-sig gcm-${{ matrix.runtime }}.$version.tar.gz - - - name: Upload artifacts - uses: actions/upload-artifact@v5 - with: - name: ${{ matrix.runtime }}-artifacts - path: | - ./*.deb - ./*.asc - ./*.tar.gz - -# ================================ -# .NET Tool -# ================================ - dotnet-tool-build: - name: Build .NET tool - runs-on: ubuntu-latest - needs: prereqs - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Build .NET tool - run: | - src/shared/DotnetTool/layout.sh --configuration=Release - - - name: Upload .NET tool artifacts - uses: actions/upload-artifact@v5 - with: - name: tmp.dotnet-tool-build - path: | - out/shared/DotnetTool/nupkg/Release - - dotnet-tool-payload-sign: - name: Sign .NET tool payload - runs-on: windows-latest - environment: release - needs: dotnet-tool-build - steps: - - uses: actions/checkout@v5 - - - name: Download payload - uses: actions/download-artifact@v6 - with: - name: tmp.dotnet-tool-build - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Install sign CLI tool - run: | - dotnet tool install -g sign --version 0.9.1-beta.24325.5 - - - name: Sign payload - run: | - sign.exe code trusted-signing payload/* ` - -tse https://wus2.codesigning.azure.net/ ` - -tsa git-fundamentals-signing ` - -tscp git-fundamentals-windows-signing - - - name: Lay out signed payload, images, and symbols - shell: bash - run: | - mkdir dotnet-tool-payload-sign - mv images payload.sym payload -t dotnet-tool-payload-sign - - - name: Upload signed payload - uses: actions/upload-artifact@v5 - with: - name: dotnet-tool-payload-sign - path: | - dotnet-tool-payload-sign - - dotnet-tool-pack: - name: Package .NET tool - runs-on: ubuntu-latest - needs: [ prereqs, dotnet-tool-payload-sign ] - steps: - - uses: actions/checkout@v5 - - - name: Download signed payload - uses: actions/download-artifact@v6 - with: - name: dotnet-tool-payload-sign - path: signed - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Package tool - run: | - src/shared/DotnetTool/pack.sh --configuration=Release \ - --version="${{ needs.prereqs.outputs.version }}" \ - --package-root=$(pwd)/signed - - - name: Upload unsigned package - uses: actions/upload-artifact@v5 - with: - name: tmp.dotnet-tool-package-unsigned - path: | - out/shared/DotnetTool/nupkg/Release/*.nupkg - - dotnet-tool-sign: - name: Sign .NET tool package - runs-on: windows-latest - environment: release - needs: dotnet-tool-pack - steps: - - uses: actions/checkout@v5 - - - name: Download unsigned package - uses: actions/download-artifact@v6 - with: - name: tmp.dotnet-tool-package-unsigned - path: nupkg - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Install sign CLI tool - run: | - dotnet tool install -g sign --version 0.9.1-beta.24325.5 - - - name: Sign package - run: | - sign.exe code trusted-signing nupkg/* ` - -tse https://wus2.codesigning.azure.net/ ` - -tsa git-fundamentals-signing ` - -tscp git-fundamentals-windows-signing - - mv nupkg/* . - - # Remove this once NuGet supports the subscriber identity validation EKU: - # https://github.com/NuGet/NuGetGallery/issues/10027 - - name: Extract signing certificate from package - shell: pwsh - run: | - dotnet tool install --global Knapcode.CertificateExtractor - $nupkg = gci *.nupkg - nuget-cert-extractor --file $nupkg --output certs --code-signing --author --leaf - $cert = gci certs\*.cer - mv $cert .\nuget-signing.cer - - - name: Publish signed package and certificate - uses: actions/upload-artifact@v5 - with: - name: dotnet-tool-sign - path: | - *.nupkg - *.cer - -# ================================ -# Validate -# ================================ - validate: - name: Validate installers - strategy: - matrix: - component: - - os: ubuntu-latest - artifact: linux-x64-artifacts - command: git-credential-manager - description: linux-x64 - - os: macos-latest - artifact: macos-osx-x64-artifacts - command: git-credential-manager - description: osx-x64 - - os: windows-latest - artifact: windows-artifacts - # Even when a standalone GCM version is installed, GitHub actions - # runners still only recognize the version bundled with Git for - # Windows due to its placement on the PATH. For this reason, we use - # the full path to our installation to validate the Windows version. - command: "$PROGRAMFILES (x86)/Git Credential Manager/git-credential-manager.exe" - description: windows - - os: ubuntu-latest - artifact: dotnet-tool-sign - command: git-credential-manager - description: dotnet-tool - runs-on: ${{ matrix.component.os }} - needs: [ create-macos-artifacts, create-windows-artifacts, create-linux-artifacts, dotnet-tool-sign ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Download artifacts - uses: actions/download-artifact@v6 - with: - name: ${{ matrix.component.artifact }} - - - name: Install Windows - if: contains(matrix.component.description, 'windows') - shell: pwsh - run: | - $exePaths = Get-ChildItem -Path ./installers/*.exe | %{$_.FullName} - foreach ($exePath in $exePaths) - { - Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART" - } - - - name: Install Linux x64 (Debian package) - if: contains(matrix.component.description, 'linux-x64') - run: | - debpath=$(find ./*.deb) - sudo apt install $debpath - "${{ matrix.component.command }}" configure - - - name: Install Linux x64 (tarball) - if: contains(matrix.component.description, 'linux-x64') - run: | - # Ensure we find only the source tarball, not the symbols - tarpath=$(find . -name '*[[:digit:]].tar.gz') - tar -xvf $tarpath -C /usr/local/bin - "${{ matrix.component.command }}" configure - - - name: Install macOS - if: contains(matrix.component.description, 'osx-x64') - run: | - # Only validate x64, given arm64 agents are not available - pkgpath=$(find ./pkg/*.pkg) - sudo installer -pkg $pkgpath -target / - - - name: Install .NET tool - if: contains(matrix.component.description, 'dotnet-tool') - run: | - nupkgpath=$(find ./*.nupkg) - dotnet tool install -g --add-source $(dirname "$nupkgpath") git-credential-manager - "${{ matrix.component.command }}" configure - - - name: Validate - shell: bash - run: | - "${{ matrix.component.command }}" --version | sed 's/+.*//' >actual - cat VERSION | sed -E 's/.[0-9]+$//' >expect - cmp expect actual || exit 1 - -# ================================ -# Publish -# ================================ - create-github-release: - name: Publish GitHub draft release - runs-on: ubuntu-latest - env: - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - GPG_PUBLIC_KEY_SECRET_NAME: ${{ secrets.GPG_PUBLIC_KEY_SECRET_NAME }} - environment: release - needs: [ prereqs, validate ] - steps: - - uses: actions/checkout@v5 - - - name: Set up .NET - uses: actions/setup-dotnet@v5.0.0 - with: - dotnet-version: 8.0.x - - - name: Download artifacts - uses: actions/download-artifact@v6 - - - name: Archive macOS payload and symbols - run: | - version="${{ needs.prereqs.outputs.version }}" - mkdir osx-payload-and-symbols - - tar -C macos-osx-x64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-x64-$version.tar.gz . - tar -C macos-osx-x64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-x64-$version-symbols.tar.gz . - - tar -C macos-osx-arm64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-arm64-$version.tar.gz . - tar -C macos-osx-arm64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-arm64-$version-symbols.tar.gz . - - - name: Archive Windows payload and symbols - run: | - version="${{ needs.prereqs.outputs.version }}" - mkdir win-x86-payload-and-symbols - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version.zip windows-artifacts/payload - zip -jr win-x86-payload-and-symbols/gcm-win-x86-$version-symbols.zip windows-artifacts/symbols - - - name: Log into Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Download GPG public key signature file - run: | - az keyvault secret show --name "$GPG_PUBLIC_KEY_SECRET_NAME" \ - --vault-name "$AZURE_VAULT" --query "value" \ - | sed -e 's/^"//' -e 's/"$//' | base64 -d >gcm-public.asc - cp gcm-public.asc linux-x64-artifacts/ - cp gcm-public.asc linux-arm64-artifacts/ - mv gcm-public.asc linux-arm-artifacts - - - uses: actions/github-script@v8 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const version = "${{ needs.prereqs.outputs.version }}" - - var releaseMetadata = { - owner: context.repo.owner, - repo: context.repo.repo - }; - - // Create the release - var tagName = `v${version}`; - var createdRelease = await github.rest.repos.createRelease({ - ...releaseMetadata, - draft: true, - tag_name: tagName, - target_commitish: context.sha, - name: `GCM ${version}` - }); - releaseMetadata.release_id = createdRelease.data.id; - - // Uploads contents of directory to the release created above - async function uploadDirectoryToRelease(directory, includeExtensions=[]) { - return fs.promises.readdir(directory) - .then(async(files) => Promise.all( - files.filter(file => { - return includeExtensions.length==0 || includeExtensions.includes(path.extname(file).toLowerCase()); - }) - .map(async (file) => { - var filePath = path.join(directory, file); - github.rest.repos.uploadReleaseAsset({ - ...releaseMetadata, - name: file, - headers: { - "content-length": (await fs.promises.stat(filePath)).size - }, - data: fs.createReadStream(filePath) - }); - })) - ); - } - - await Promise.all([ - // Upload Windows artifacts - uploadDirectoryToRelease('windows-artifacts/installers'), - uploadDirectoryToRelease('win-x86-payload-and-symbols'), - - // Upload macOS artifacts - uploadDirectoryToRelease('macos-osx-x64-artifacts/pkg'), - uploadDirectoryToRelease('macos-osx-arm64-artifacts/pkg'), - uploadDirectoryToRelease('osx-payload-and-symbols'), - - // Upload Linux artifacts - uploadDirectoryToRelease('linux-x64-artifacts'), - uploadDirectoryToRelease('linux-arm64-artifacts'), - uploadDirectoryToRelease('linux-arm-artifacts'), - - // Upload .NET tool package - uploadDirectoryToRelease('dotnet-tool-sign'), - ]); From a254ae1d5ab36b481eb11f293b23b6ba2d835e7e Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 13 Nov 2025 14:02:54 +0000 Subject: [PATCH 13/13] release.yml: enable signing, GitHub, NuGet publishing Default enable ESRP code signing, as well as publishing to GitHub and NuGet.org. Signed-off-by: Matthew John Cheetham --- .azure-pipelines/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 0e76acf89..da78d9334 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -12,15 +12,15 @@ resources: parameters: - name: 'esrp' type: boolean - default: false + default: true displayName: 'Enable ESRP code signing' - name: 'github' type: boolean - default: false + default: true displayName: 'Enable GitHub release publishing' - name: 'nuget' type: boolean - default: false + default: true displayName: 'Enable NuGet package publishing' #