Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions .azure-pipelines/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,24 @@ parameters:
- name: windows_matrix
type: object
default:
- id: windows_x64
- id: windows_x86
jobName: 'Windows (x86)'
runtime: win-x86
pool: GitClientPME-1ESHostedPool-intel-pc
image: win-x86_64-ado1es
os: windows
- id: windows_x64
jobName: 'Windows (x64)'
runtime: win-x64
pool: GitClientPME-1ESHostedPool-intel-pc
image: win-x86_64-ado1es
os: windows
- id: windows_arm64
jobName: 'Windows (ARM64)'
runtime: win-arm64
pool: GitClientPME-1ESHostedPool-arm64-pc
image: win-arm64-ado1es
os: windows

- name: macos_matrix
type: object
Expand Down Expand Up @@ -136,14 +148,15 @@ extends:
arguments: |
-Configuration Release `
-Output $(Build.ArtifactStagingDirectory)\payload `
-SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw
-SymbolOutput $(Build.ArtifactStagingDirectory)\symbols_raw `
-RuntimeIdentifier ${{ dim.runtime }}
- 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'
archiveFile: '$(Build.ArtifactStagingDirectory)\symbols\gcm-${{ dim.runtime }}-$(version)-symbols.zip'
- task: EsrpCodeSigning@5
condition: and(succeeded(), eq('${{ parameters.esrp }}', true))
displayName: 'Sign payload'
Expand Down Expand Up @@ -194,7 +207,8 @@ extends:
--no-dependencies `
-p:NoLayout=true `
-p:PayloadPath="$(Build.ArtifactStagingDirectory)\payload" `
-p:OutputPath="$(Build.ArtifactStagingDirectory)\installers"
-p:OutputPath="$(Build.ArtifactStagingDirectory)\installers" `
-p:RuntimeIdentifier="${{ dim.runtime }}"
- task: EsrpCodeSigning@5
condition: and(succeeded(), eq('${{ parameters.esrp }}', true))
displayName: 'Sign installers'
Expand Down Expand Up @@ -239,7 +253,7 @@ extends:
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)\payload'
includeRootFolder: false
archiveType: zip
archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-win-x86-$(version).zip'
archiveFile: '$(Build.ArtifactStagingDirectory)\installers\gcm-${{ dim.runtime }}-$(version).zip'
- task: PowerShell@2
displayName: 'Collect artifacts for publishing'
inputs:
Expand Down Expand Up @@ -774,6 +788,12 @@ extends:
- input: pipelineArtifact
artifactName: 'win-x86'
targetPath: $(Pipeline.Workspace)/assets/win-x86
- input: pipelineArtifact
artifactName: 'win-x64'
targetPath: $(Pipeline.Workspace)/assets/win-x64
- input: pipelineArtifact
artifactName: 'win-arm64'
targetPath: $(Pipeline.Workspace)/assets/win-arm64
- input: pipelineArtifact
artifactName: 'osx-x64'
targetPath: $(Pipeline.Workspace)/assets/osx-x64
Expand Down Expand Up @@ -805,6 +825,10 @@ extends:
assets: |
$(Pipeline.Workspace)/assets/win-x86/*.exe
$(Pipeline.Workspace)/assets/win-x86/*.zip
$(Pipeline.Workspace)/assets/win-x64/*.exe
$(Pipeline.Workspace)/assets/win-x64/*.zip
$(Pipeline.Workspace)/assets/win-arm64/*.exe
$(Pipeline.Workspace)/assets/win-arm64/*.zip
$(Pipeline.Workspace)/assets/osx-x64/*.pkg
$(Pipeline.Workspace)/assets/osx-x64/*.tar.gz
$(Pipeline.Workspace)/assets/osx-arm64/*.pkg
Expand Down
28 changes: 21 additions & 7 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ jobs:
# ================================
windows:
name: Windows
runs-on: windows-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- runtime: win-x86
os: windows-latest
- runtime: win-x64
os: windows-latest
- runtime: win-arm64
os: windows-11-arm

steps:
- uses: actions/checkout@v6
Expand All @@ -27,24 +36,29 @@ jobs:
run: dotnet restore

- name: Build
run: dotnet build --configuration WindowsRelease
run: |
dotnet build src/windows/Installer.Windows/Installer.Windows.csproj `
--configuration=Release `
--runtime=${{ matrix.runtime }}

- name: Test
run: |
dotnet test --verbosity normal --configuration=WindowsRelease
dotnet test --verbosity normal `
--configuration=WindowsRelease `
--runtime=${{ matrix.runtime }}

- name: Prepare artifacts
shell: bash
run: |
mkdir -p artifacts/bin
mv out/windows/Installer.Windows/bin/Release/net472/win-x86 artifacts/bin/
cp out/windows/Installer.Windows/bin/Release/net472/win-x86.sym/* artifacts/bin/win-x86/
mv out/windows/Installer.Windows/bin/Release/net472/gcm*.exe artifacts/
mv out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }}/gcm*.exe artifacts/
mv out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }} artifacts/bin/
cp out/windows/Installer.Windows/bin/Release/net472/${{ matrix.runtime }}.sym/* artifacts/bin/${{ matrix.runtime }}/

- name: Upload artifacts
uses: actions/upload-artifact@v6
with:
name: win-x86
name: ${{ matrix.runtime }}
path: |
artifacts

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net472;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;osx-x64;linux-x64;osx-arm64;linux-arm64;linux-arm</RuntimeIdentifiers>
<PlatformTarget Condition="'$(OSPlatform)'=='windows'">x86</PlatformTarget>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;osx-x64;linux-x64;osx-arm64;linux-arm64;linux-arm</RuntimeIdentifiers>
<AssemblyName>git-credential-manager</AssemblyName>
<RootNamespace>GitCredentialManager</RootNamespace>
<ApplicationIcon>$(RepoAssetsPath)gcmicon.ico</ApplicationIcon>
Expand Down
25 changes: 20 additions & 5 deletions src/windows/Installer.Windows/Installer.Windows.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
<Project>
<Project>
<!-- Implicit SDK props import -->
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

<!-- Default RuntimeIdentifier to the host architecture if not specified -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">
<RuntimeIdentifier Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X86'">win-x86</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">win-arm64</RuntimeIdentifier>
</PropertyGroup>

<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultItems>false</EnableDefaultItems>
<PayloadPath>$(PlatformOutPath)Installer.Windows\bin\$(Configuration)\net472\win-x86</PayloadPath>
<PayloadPath>$(PlatformOutPath)Installer.Windows\bin\$(Configuration)\net472\$(RuntimeIdentifier)</PayloadPath>
Copy link
Contributor

Choose a reason for hiding this comment

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

The RuntimeIdentifier is not set when building without a specific RID/Architecture parameter, such as when building the solution from inside of an IDE, or with dotnet build -c WindowsDebug.

Installer.Windows net472 failed with 1 error(s) (1.4s)

D:\src\gcm\src\windows\Installer.Windows\Installer.Windows.csproj(43,5): error Layout script failed with exit code 1 and message Output: D:\src\gcm\out\windows\Installer.Windows\bin\Debug\net472;Unsupported RuntimeIdentifier:

The other 'Installer/Packacing' projects for macOS/Linux don't do much except shell out to a shell script that dynamically computes the current host's RID when it's not specified.

We can probably do some MSBuild fun with properties and discovering the current host's arch if RID has not been explicitly specified.

OR.. mimic the macOS/Linux implementations and create some layout.ps1 and pack.ps1 scripts; not relying on project-project dependencies to build the PayloadPath contents.

Copy link
Contributor Author

@dennisameling dennisameling Jan 10, 2026

Choose a reason for hiding this comment

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

Great catch, thank you! I just updated the implementation to default to the host architecture if a RuntimeIdentifier isn't explicitly provided. Tested building all three architectures and validated that the cleanup logic from layout.ps1 works properly. Also tested the arm64 installer again, still looking good, also after installation.

<InnoSetupVersion>6.3.1</InnoSetupVersion>
</PropertyGroup>

Expand All @@ -27,12 +34,20 @@

<Target Name="CoreCompile" Condition="'$(OSPlatform)'=='windows'">
<PropertyGroup>
<InnoSetupCommandSystem>"$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=system "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)"</InnoSetupCommandSystem>
<InnoSetupCommandUser>"$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=user "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)"</InnoSetupCommandUser>
<InnoSetupCommandSystem>"$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=system /DGcmRuntimeIdentifier="$(RuntimeIdentifier)" "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)"</InnoSetupCommandSystem>
<InnoSetupCommandUser>"$(NuGetPackageRoot)Tools.InnoSetup\$(InnoSetupVersion)\tools\ISCC.exe" /DPayloadDir="$(PayloadPath)" /DInstallTarget=user /DGcmRuntimeIdentifier="$(RuntimeIdentifier)" "$(RepoSrcPath)\windows\Installer.Windows\Setup.iss" /O"$(OutputPath)"</InnoSetupCommandUser>
</PropertyGroup>

<Message Text="Lay Out" Importance="High" />
<Exec Condition="'$(NoLayout)'!='true'" Command="powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -Command &quot;&amp; {&amp;'$(MSBuildProjectDirectory)\layout.ps1' -Configuration '$(Configuration)' -Output '$(PayloadPath)'}&quot;" />
<Exec Condition="'$(NoLayout)'!='true'"
ConsoleToMSBuild="true"
Command="powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -Command &quot;&amp; {&amp;'$(MSBuildProjectDirectory)\layout.ps1' -Configuration '$(Configuration)' -Output '$(PayloadPath)' -RuntimeIdentifier '$(RuntimeIdentifier)'; if ($?) { exit 0 } else { exit 1 }}&quot;"
IgnoreExitCode="true">
<!-- If we want to display the console output if the exit code is not 0, we need to capture it and then output it using the <Error /> below -->
<Output TaskParameter="ExitCode" PropertyName="ExitCodeOfExec" />
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
</Exec>
<Error Condition="'$(NoLayout)'!='true' AND '$(ExitCodeOfExec)' != '0'" Text="Layout script failed with exit code $(ExitCodeOfExec) and message $(OutputOfExec)" />
<Message Text="$(InnoSetupCommandSystem)" Importance="High" />
<Exec Command="$(InnoSetupCommandSystem)" />
<Message Text="$(InnoSetupCommandUser)" Importance="High" />
Expand Down
15 changes: 13 additions & 2 deletions src/windows/Installer.Windows/Setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#error Installer target property 'InstallTarget' must be specifed
#endif

#ifndef GcmRuntimeIdentifier
#error GCM Runtime Identifier 'GcmRuntimeIdentifier' must be specifed (e.g. win-x64)
#endif

#if InstallTarget == "user"
#define GcmAppId "{{aa76d31d-432c-42ee-844c-bc0bc801cef3}}"
#define GcmLongName "Git Credential Manager (User)"
Expand All @@ -40,7 +44,6 @@
#define GcmRepoRoot "..\..\.."
#define GcmAssets GcmRepoRoot + "\assets"
#define GcmExe "git-credential-manager.exe"
#define GcmArch "x86"

#ifnexist PayloadDir + "\" + GcmExe
#error Payload files are missing
Expand All @@ -67,9 +70,17 @@ AppUpdatesURL={#GcmUrl}
AppContact={#GcmUrl}
AppCopyright={#GcmCopyright}
AppReadmeFile={#GcmReadme}
; Windows ARM64 supports installing and running x64 binaries, but not vice versa.
#if GcmRuntimeIdentifier=="win-x64"
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
#elif GcmRuntimeIdentifier=="win-arm64"
ArchitecturesAllowed=arm64
ArchitecturesInstallIn64BitMode=arm64
#endif
VersionInfoVersion={#GcmVersion}
LicenseFile={#GcmRepoRoot}\LICENSE
OutputBaseFilename={#GcmSetupExe}-win-{#GcmArch}-{#GcmVersionSimple}
OutputBaseFilename={#GcmSetupExe}-{#GcmRuntimeIdentifier}-{#GcmVersionSimple}
DefaultDirName={autopf}\{#GcmShortName}
Compression=lzma2
SolidCompression=yes
Expand Down
79 changes: 64 additions & 15 deletions src/windows/Installer.Windows/layout.ps1
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
# Inputs
param ([Parameter(Mandatory)] $Configuration, [Parameter(Mandatory)] $Output, $SymbolOutput)
param ([Parameter(Mandatory)] $Configuration, [Parameter(Mandatory)] $Output, $RuntimeIdentifier, $SymbolOutput)

Write-Output "Output: $Output"

# Determine a runtime if one was not provided
if (-not $RuntimeIdentifier) {
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
"AMD64" { $RuntimeIdentifier = "win-x64" }
"x86" { $RuntimeIdentifier = "win-x86" }
"ARM64" { $RuntimeIdentifier = "win-arm64" }
default {
Write-Host "Unknown architecture: $arch"
exit 1
}
}
}

Write-Output "Building for runtime '$RuntimeIdentifier'"

if ($RuntimeIdentifier -ne 'win-x86' -and $RuntimeIdentifier -ne 'win-x64' -and $RuntimeIdentifier -ne 'win-arm64') {
Write-Host "Unsupported RuntimeIdentifier: $RuntimeIdentifier"
exit 1
}

# Directories
$THISDIR = $PSScriptRoot
$ROOT = (Get-Item $THISDIR).Parent.Parent.Parent.FullName
Expand Down Expand Up @@ -41,27 +62,55 @@ Write-Output "Publishing core application..."
dotnet publish "$GCM_SRC" `
--framework net472 `
--configuration "$Configuration" `
--runtime win-x86 `
--runtime $RuntimeIdentifier `
--output "$PAYLOAD"

# Delete libraries that are not needed for Windows but find their way
# into the publish output.
Remove-Item -Path "$PAYLOAD/*.dylib" -Force -ErrorAction Ignore

# Delete extraneous files that get included for other architectures
# We only care about x86 as the core GCM executable is only targeting x86
Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore
# Delete extraneous files that get included for other runtimes
Remove-Item -Path "$PAYLOAD/musl-x64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore

# The Avalonia and MSAL binaries in these directories are already included in
# the $PAYLOAD directory directly, so we can delete these extra copies.
Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Recurse -Force -ErrorAction Ignore

switch ($RuntimeIdentifier) {
"win-x86" {
Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore
# The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly
Remove-Item -Path "$PAYLOAD/x86/libSkiaSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x86/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x86/native/msalruntime_x86.dll" -Force -ErrorAction Ignore
}
"win-x64" {
Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x86/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x86/" -Recurse -Force -ErrorAction Ignore
# The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly
Remove-Item -Path "$PAYLOAD/x64/libSkiaSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/libSkiaSharp.so" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/libHarfBuzzSharp.so" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x64/native/msalruntime.dll" -Force -ErrorAction Ignore
}
"win-arm64" {
Remove-Item -Path "$PAYLOAD/arm/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x86/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/x64/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x86/" -Recurse -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-x64/" -Recurse -Force -ErrorAction Ignore
# The Avalonia and MSAL binaries are already included in the $PAYLOAD directory directly
Remove-Item -Path "$PAYLOAD/arm64/libSkiaSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/libHarfBuzzSharp.dll" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/libSkiaSharp.so" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/arm64/libHarfBuzzSharp.so" -Force -ErrorAction Ignore
Remove-Item -Path "$PAYLOAD/runtimes/win-arm64/native/msalruntime_arm64.dll" -Force -ErrorAction Ignore
}
}

# Delete localized resource assemblies - we don't localize the core GCM assembly anyway
Get-ChildItem "$PAYLOAD" -Recurse -Include "*.resources.dll" | Remove-Item -Force -ErrorAction Ignore
Expand Down