Skip to content

build-git-installers #214

build-git-installers

build-git-installers #214

name: build-git-installers
on:
push:
tags:
- 'v[0-9]*vfs*' # matches "v<number><any characters>vfs<any characters>"
permissions:
id-token: write # required for Azure login via OIDC
jobs:
# Check prerequisites for the workflow
prereqs:
runs-on: ubuntu-latest
environment: release
outputs:
tag_name: ${{ steps.tag.outputs.name }} # The full name of the tag, e.g. v2.32.0.vfs.0.0
tag_version: ${{ steps.tag.outputs.version }} # The version number (without preceding "v"), e.g. 2.32.0.vfs.0.0
steps:
- name: Validate tag
run: |
echo "$GITHUB_REF" |
grep -E '^refs/tags/v2\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.vfs\.0\.(0|[1-9][0-9]*)(\.rc[0-9])?$' || {
echo "::error::${GITHUB_REF#refs/tags/} is not of the form v2.<X>.<Y>.vfs.0.<W>[.rc<N>]" >&2
exit 1
}
- name: Determine tag to build
run: |
echo "name=${GITHUB_REF#refs/tags/}" >>$GITHUB_OUTPUT
echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
id: tag
- name: Clone git
uses: actions/checkout@v4
- name: Validate the tag identified with trigger
run: |
die () {
echo "::error::$*" >&2
exit 1
}
# `actions/checkout` only downloads the peeled tag (i.e. the commit)
git fetch origin +$GITHUB_REF:$GITHUB_REF
# Verify that the tag is annotated
test $(git cat-file -t "$GITHUB_REF") == "tag" || die "Tag ${{ steps.tag.outputs.name }} is not annotated"
# Verify tag follows rules in GIT-VERSION-GEN (i.e., matches the specified "DEF_VER" in
# GIT-VERSION-FILE) and matches tag determined from trigger
make GIT-VERSION-FILE
test "${{ steps.tag.outputs.version }}" == "$(sed -n 's/^GIT_VERSION = //p'< GIT-VERSION-FILE)" || die "GIT-VERSION-FILE tag does not match ${{ steps.tag.outputs.name }}"
# End check prerequisites for the workflow
# Build Windows installers (x86_64 & aarch64; installer & portable)
windows_pkg:
environment: release
needs: prereqs
strategy:
fail-fast: false
matrix:
arch:
- name: x86_64
artifact: pkg-x86_64
toolchain: x86_64
mingwprefix: /mingw64
runner: windows-2019
- name: aarch64
artifact: pkg-aarch64
toolchain: clang-aarch64
mingwprefix: /clangarm64
runner: ['self-hosted', '1ES.Pool=github-arm64-pool']
runs-on: ${{ matrix.arch.runner }}
env:
GPG_OPTIONS: "--batch --yes --no-tty --list-options no-show-photos --verify-options no-show-photos --pinentry-mode loopback"
HOME: "${{github.workspace}}\\home"
USERPROFILE: "${{github.workspace}}\\home"
steps:
- name: Configure user
shell: bash
run:
USER_NAME="${{github.actor}}" &&
USER_EMAIL="${{github.actor}}@users.noreply.github.com" &&
mkdir -p "$HOME" &&
git config --global user.name "$USER_NAME" &&
git config --global user.email "$USER_EMAIL" &&
echo "PACKAGER=$USER_NAME <$USER_EMAIL>" >>$GITHUB_ENV
- uses: git-for-windows/setup-git-for-windows-sdk@v1
with:
flavor: build-installers
architecture: ${{ matrix.arch.name }}
- name: Clone build-extra
shell: bash
run: |
git clone --filter=blob:none --single-branch -b main https://github.com/git-for-windows/build-extra /usr/src/build-extra
- name: Clone git
shell: bash
run: |
# Since we cannot directly clone a specified tag (as we would a branch with `git clone -b <branch name>`),
# this clone has to be done manually (via init->fetch->reset).
tag_name="${{ needs.prereqs.outputs.tag_name }}" &&
git -c init.defaultBranch=main init &&
git remote add -f origin https://github.com/git-for-windows/git &&
git fetch "https://github.com/${{github.repository}}" refs/tags/${tag_name}:refs/tags/${tag_name} &&
git reset --hard ${tag_name}
- name: Prepare home directory for code-signing
env:
CODESIGN_P12: ${{secrets.CODESIGN_P12}}
CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
if: env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
shell: bash
run: |
cd home &&
mkdir -p .sig &&
echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >.sig/codesign.p12 &&
echo -n "$CODESIGN_PASS" >.sig/codesign.pass
git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
- name: Prepare home directory for GPG signing
if: env.GPGKEY != ''
shell: bash
run: |
# This section ensures that the identity for the GPG key matches the git user identity, otherwise
# signing will fail
echo '${{secrets.PRIVGPGKEY}}' | tr % '\n' | gpg $GPG_OPTIONS --import &&
info="$(gpg --list-keys --with-colons "${GPGKEY%% *}" | cut -d : -f 1,10 | sed -n '/^uid/{s|uid:||p;q}')" &&
git config --global user.name "${info% <*}" &&
git config --global user.email "<${info#*<}"
env:
GPGKEY: ${{secrets.GPGKEY}}
- name: Build mingw-w64-${{matrix.arch.toolchain}}-git
env:
GPGKEY: "${{secrets.GPGKEY}}"
shell: bash
run: |
set -x
# Make sure that there is a `/usr/bin/git` that can be used by `makepkg-mingw`
printf '#!/bin/sh\n\nexec ${{matrix.arch.mingwprefix}}/bin/git.exe "$@"\n' >/usr/bin/git &&
sh -x /usr/src/build-extra/please.sh build-mingw-w64-git --only-${{matrix.arch.name}} --build-src-pkg -o artifacts HEAD &&
if test -n "$GPGKEY"
then
for tar in artifacts/*.tar*
do
/usr/src/build-extra/gnupg-with-gpgkey.sh --detach-sign --no-armor $tar
done
fi &&
b=$PWD/artifacts &&
version=${{ needs.prereqs.outputs.tag_name }} &&
(cd /usr/src/MINGW-packages/mingw-w64-git &&
cp PKGBUILD.$version PKGBUILD &&
git commit -s -m "mingw-w64-git: new version ($version)" PKGBUILD &&
git bundle create "$b"/MINGW-packages.bundle origin/main..main)
- name: Publish mingw-w64-${{matrix.arch.toolchain}}-git
uses: actions/upload-artifact@v4
with:
name: "${{ matrix.arch.artifact }}"
path: artifacts
windows_artifacts:
environment: release
needs: [prereqs, windows_pkg]
env:
HOME: "${{github.workspace}}\\home"
strategy:
fail-fast: false
matrix:
arch:
- name: x86_64
artifact: pkg-x86_64
toolchain: x86_64
runner: windows-2019
- name: aarch64
artifact: pkg-aarch64
toolchain: clang-aarch64
runner: ['self-hosted', '1ES.Pool=github-arm64-pool']
type:
- name: installer
fileprefix: Git
- name: portable
fileprefix: PortableGit
runs-on: ${{ matrix.arch.runner }}
steps:
- name: Download ${{ matrix.arch.artifact }}
uses: actions/download-artifact@v4
with:
name: ${{ matrix.arch.artifact }}
path: ${{ matrix.arch.artifact }}
- uses: git-for-windows/setup-git-for-windows-sdk@v1
with:
flavor: build-installers
architecture: ${{ matrix.arch.name }}
- name: Clone build-extra
shell: bash
run: |
git clone --filter=blob:none --single-branch -b main https://github.com/git-for-windows/build-extra /usr/src/build-extra
- name: Prepare home directory for code-signing
env:
CODESIGN_P12: ${{secrets.CODESIGN_P12}}
CODESIGN_PASS: ${{secrets.CODESIGN_PASS}}
if: env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
shell: bash
run: |
mkdir -p home/.sig &&
echo -n "$CODESIGN_P12" | tr % '\n' | base64 -d >home/.sig/codesign.p12 &&
echo -n "$CODESIGN_PASS" >home/.sig/codesign.pass &&
git config --global alias.signtool '!sh "/usr/src/build-extra/signtool.sh"'
- name: Retarget auto-update to microsoft/git
shell: bash
run: |
set -x
b=/usr/src/build-extra &&
filename=$b/git-update-git-for-windows.config
tr % '\t' >$filename <<-\EOF &&
[update]
%fromFork = microsoft/git
EOF
sed -i -e '/^#include "file-list.iss"/a\
Source: {#SourcePath}\\..\\git-update-git-for-windows.config; DestDir: {app}\\mingw64\\bin; Flags: replacesameversion; AfterInstall: DeleteFromVirtualStore' \
-e '/^Type: dirifempty; Name: {app}\\{#MINGW_BITNESS}$/i\
Type: files; Name: {app}\\{#MINGW_BITNESS}\\bin\\git-update-git-for-windows.config\
Type: dirifempty; Name: {app}\\{#MINGW_BITNESS}\\bin' \
$b/installer/install.iss
- name: Set alerts to continue until upgrade is taken
shell: bash
run: |
set -x
b=/mingw64/bin &&
sed -i -e '6 a use_recently_seen=no' \
$b/git-update-git-for-windows
- name: Set the installer Publisher to the Git Client team
shell: bash
run: |
b=/usr/src/build-extra &&
sed -i -e 's/^\(AppPublisher=\).*/\1The Git Client Team at Microsoft/' $b/installer/install.iss
- name: Let the installer configure Visual Studio to use the installed Git
shell: bash
run: |
set -x
b=/usr/src/build-extra &&
sed -i -e '/^ *InstallAutoUpdater();$/a\
CustomPostInstall();' \
-e '/^ *UninstallAutoUpdater();$/a\
CustomPostUninstall();' \
$b/installer/install.iss &&
cat >>$b/installer/helpers.inc.iss <<\EOF
procedure CustomPostInstall();
begin
if not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\15.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) or
not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\16.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) or
not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\17.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) or
not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\18.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) or
not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\19.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) or
not RegWriteStringValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\20.0\TeamFoundation\GitSourceControl','GitPath',ExpandConstant('{app}')) then
LogError('Could not register TeamFoundation\GitSourceControl');
end;
procedure CustomPostUninstall();
begin
if not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\15.0\TeamFoundation\GitSourceControl','GitPath') or
not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\16.0\TeamFoundation\GitSourceControl','GitPath') or
not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\17.0\TeamFoundation\GitSourceControl','GitPath') or
not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\18.0\TeamFoundation\GitSourceControl','GitPath') or
not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\19.0\TeamFoundation\GitSourceControl','GitPath') or
not RegDeleteValue(HKEY_CURRENT_USER,'Software\Microsoft\VSCommon\20.0\TeamFoundation\GitSourceControl','GitPath') then
LogError('Could not register TeamFoundation\GitSourceControl');
end;
EOF
- name: Enable Scalar/C and the auto-updater in the installer by default
shell: bash
run: |
set -x
b=/usr/src/build-extra &&
sed -i -e "/ChosenOptions:=''/a\\
if (ExpandConstant('{param:components|/}')='/') then begin\n\
WizardSelectComponents('autoupdate');\n\
#ifdef WITH_SCALAR\n\
WizardSelectComponents('scalar');\n\
#endif\n\
end;" $b/installer/install.iss
- name: Build ${{matrix.type.name}} (${{matrix.arch.name}})
shell: bash
run: |
set -x
# Copy the PDB archive to the directory where `--include-pdbs` expects it
b=/usr/src/build-extra &&
mkdir -p $b/cached-source-packages &&
cp ${{matrix.arch.artifact}}/*-pdb* $b/cached-source-packages/ &&
# Build the installer, embedding PDBs
eval $b/please.sh make_installers_from_mingw_w64_git --include-pdbs \
--version=${{ needs.prereqs.outputs.tag_version }} \
-o artifacts --${{matrix.type.name}} \
--pkg=${{matrix.arch.artifact}}/mingw-w64-${{matrix.arch.toolchain}}-git-[0-9]*.tar.xz \
--pkg=${{matrix.arch.artifact}}/mingw-w64-${{matrix.arch.toolchain}}-git-doc-html-[0-9]*.tar.xz &&
if test portable = '${{matrix.type.name}}' && test -n "$(git config alias.signtool)"
then
git signtool artifacts/PortableGit-*.exe
fi &&
openssl dgst -sha256 artifacts/${{matrix.type.fileprefix}}-*.exe | sed "s/.* //" >artifacts/sha-256.txt
- name: Verify that .exe files are code-signed
if: env.CODESIGN_P12 != '' && env.CODESIGN_PASS != ''
shell: bash
run: |
PATH=$PATH:"/c/Program Files (x86)/Windows Kits/10/App Certification Kit/" \
signtool verify //pa artifacts/${{matrix.type.fileprefix}}-*.exe
- name: Publish ${{matrix.type.name}}-${{matrix.arch.name}}
uses: actions/upload-artifact@v4
with:
name: win-${{matrix.type.name}}-${{matrix.arch.name}}
path: artifacts
# End build Windows installers
# Build and sign Mac OSX installers & upload artifacts
create-macos-artifacts:
runs-on: macos-latest-xl-arm64
needs: prereqs
env:
VERSION: "${{ needs.prereqs.outputs.tag_version }}"
environment: release
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
path: 'git'
- name: Install Git dependencies
run: |
set -ex
# Install x86_64 packages
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install gettext
# Install arm64 packages
brew install automake asciidoc xmlto docbook
brew link --force gettext
# Make universal gettext library
lipo -create -output libintl.a /usr/local/opt/gettext/lib/libintl.a /opt/homebrew/opt/gettext/lib/libintl.a
- name: Set up signing/notarization infrastructure
env:
A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }}
A2: ${{ secrets.APPLICATION_CERTIFICATE_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
# Prevent re-locking
security set-keychain-settings $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/pkgbuild
security set-key-partition-list \
-S apple-tool:,apple:,pkgbuild: \
-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: Build, sign, and notarize artifacts
env:
A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }}
I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}
N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }}
run: |
die () {
echo "$*" >&2
exit 1
}
# Trace execution, stop on error
set -ex
# Write to "version" file to force match with trigger payload version
echo "${{ needs.prereqs.outputs.tag_version }}" >>git/version
# Configure universal build
cat >git/config.mak <<EOF
# Create universal binaries. HOST_CPU is a bit of a lie and only
# used in 'git version --build-options'. We'll fix that in code.
HOST_CPU = universal
BASIC_CFLAGS += -arch arm64 -arch x86_64
EOF
# Configure the Git build to pick up gettext
homebrew_prefix="$(brew --prefix)"
cat >>git/config.mak <<EOF
CFLAGS = -I$homebrew_prefix/include -I/usr/local/opt/gettext/include
LDFLAGS = -L"$(pwd)"
EOF
# Configure the Git to use the OS supplied libcurl.
cat >>git/config.mak <<EOF
CURL_LDFLAGS := -lcurl
CURL_CONFIG := /usr/bin/true
EOF
# Avoid even building the dashed built-ins; Those should be hard-linked
# copies of the `git` executable but would end up as actual copies instead,
# bloating the size of the `.dmg` indecently.
echo 'SKIP_DASHED_BUILT_INS = YabbaDabbaDoo' >>git/config.mak
# To make use of the catalogs...
export XML_CATALOG_FILES=$homebrew_prefix/etc/xml/catalog
make -C git -j$(sysctl -n hw.physicalcpu) GIT-VERSION-FILE dist dist-doc
export GIT_BUILT_FROM_COMMIT=$(gunzip -c git/git-$VERSION.tar.gz | git get-tar-commit-id) ||
die "Could not determine commit for build"
# Extract tarballs
mkdir payload manpages
tar -xvf git/git-$VERSION.tar.gz -C payload
tar -xvf git/git-manpages-$VERSION.tar.gz -C manpages
# Lay out payload
cp git/config.mak payload/git-$VERSION/config.mak
make -C git/.github/macos-installer V=1 payload
# Codesign payload
cp -R stage/git-universal-$VERSION/ \
git/.github/macos-installer/build-artifacts
make -C git/.github/macos-installer V=1 codesign \
APPLE_APP_IDENTITY="$A3" || die "Creating signed payload failed"
# Build and sign pkg
make -C git/.github/macos-installer V=1 pkg \
APPLE_INSTALLER_IDENTITY="$I3" \
|| die "Creating signed pkg failed"
# Notarize pkg
make -C git/.github/macos-installer V=1 notarize \
APPLE_INSTALLER_IDENTITY="$I3" APPLE_KEYCHAIN_PROFILE="$N4" \
|| die "Creating signed and notarized pkg failed"
# Create DMG
make -C git/.github/macos-installer V=1 image || die "Creating DMG failed"
# Move all artifacts into top-level directory
mv git/.github/macos-installer/disk-image/*.pkg git/.github/macos-installer/
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: macos-artifacts
path: |
git/.github/macos-installer/*.dmg
git/.github/macos-installer/*.pkg
# End build and sign Mac OSX installers
# Build and sign Debian package
create-linux-artifacts:
runs-on: ubuntu-latest
needs: prereqs
environment: release
steps:
- name: Install git dependencies
run: |
set -ex
sudo apt-get update -q
sudo apt-get install -y -q --no-install-recommends gettext libcurl4-gnutls-dev libpcre3-dev asciidoc xmlto
- name: Clone git
uses: actions/checkout@v4
with:
path: git
- name: Build and create Debian package
run: |
set -ex
die () {
echo "$*" >&2
exit 1
}
echo "${{ needs.prereqs.outputs.tag_version }}" >>git/version
make -C git GIT-VERSION-FILE
VERSION="${{ needs.prereqs.outputs.tag_version }}"
ARCH="$(dpkg-architecture -q DEB_HOST_ARCH)"
if test -z "$ARCH"; then
die "Could not determine host architecture!"
fi
PKGNAME="microsoft-git_$VERSION"
PKGDIR="$(dirname $(pwd))/$PKGNAME"
rm -rf "$PKGDIR"
mkdir -p "$PKGDIR"
DESTDIR="$PKGDIR" make -C git -j5 V=1 DEVELOPER=1 \
USE_LIBPCRE=1 \
NO_CROSS_DIRECTORY_HARDLINKS=1 \
ASCIIDOC8=1 ASCIIDOC_NO_ROFF=1 \
ASCIIDOC='TZ=UTC asciidoc' \
prefix=/usr/local \
gitexecdir=/usr/local/lib/git-core \
libexecdir=/usr/local/lib/git-core \
htmldir=/usr/local/share/doc/git/html \
install install-doc install-html
cd ..
mkdir "$PKGNAME/DEBIAN"
# Based on https://packages.ubuntu.com/xenial/vcs/git
cat >"$PKGNAME/DEBIAN/control" <<EOF
Package: microsoft-git
Version: $VERSION
Section: vcs
Priority: optional
Architecture: $ARCH
Depends: libcurl3-gnutls, liberror-perl, libexpat1, libpcre2-8-0, perl, perl-modules, zlib1g
Maintainer: GitClient <[email protected]>
Description: Git client built from the https://github.com/microsoft/git repository,
specialized in supporting monorepo scenarios. Includes the Scalar CLI.
EOF
dpkg-deb -Zxz --build "$PKGNAME"
# Move Debian package for later artifact upload
mv "$PKGNAME.deb" "$GITHUB_WORKSPACE"
- 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
run: |
# Sign Debian package
version="${{ needs.prereqs.outputs.tag_version }}"
debsigs --sign=origin --verify --check microsoft-git_"$version".deb
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: linux-artifacts
path: |
*.deb
# End build and sign Debian package
# Validate installers
validate-installers:
name: Validate installers
strategy:
matrix:
component:
- os: ubuntu-latest
artifact: linux-artifacts
command: git
- os: macos-latest-xl-arm64
artifact: macos-artifacts
command: git
- os: macos-latest
artifact: macos-artifacts
command: git
- os: windows-latest
artifact: win-installer-x86_64
command: $PROGRAMFILES\Git\cmd\git.exe
- os: ['self-hosted', '1ES.Pool=github-arm64-pool']
artifact: win-installer-aarch64
command: $PROGRAMFILES\Git\cmd\git.exe
runs-on: ${{ matrix.component.os }}
needs: [prereqs, windows_artifacts, create-macos-artifacts, create-linux-artifacts]
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: ${{ matrix.component.artifact }}
- name: Install Windows
if: contains(matrix.component.artifact, 'win-installer')
shell: pwsh
run: |
$exePath = Get-ChildItem -Path ./*.exe | %{$_.FullName}
Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART /SUPPRESSMSGBOXES /ALLOWDOWNGRADE=1"
- name: Install Linux
if: contains(matrix.component.artifact, 'linux')
run: |
debpath=$(find ./*.deb)
sudo apt install $debpath
- name: Install macOS
if: contains(matrix.component.artifact, 'macos')
run: |
# avoid letting Homebrew's `git` in `/opt/homebrew/bin` override `/usr/local/bin/git`
arch="$(uname -m)"
test arm64 != "$arch" ||
brew uninstall git
pkgpath=$(find ./*universal*.pkg)
sudo installer -pkg $pkgpath -target /
- name: Validate
shell: bash
run: |
"${{ matrix.component.command }}" --version | sed 's/git version //' >actual
echo ${{ needs.prereqs.outputs.tag_version }} >expect
cmp expect actual || exit 1
- name: Validate universal binary CPU architecture
if: contains(matrix.component.os, 'macos')
shell: bash
run: |
set -ex
git version --build-options >actual
cat actual
grep "cpu: $(uname -m)" actual
# End validate installers
create-github-release:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # required for Azure login via OIDC
needs:
- validate-installers
- create-linux-artifacts
- create-macos-artifacts
- windows_artifacts
- prereqs
env:
AZURE_VAULT: ${{ secrets.AZURE_VAULT }}
GPG_PUBLIC_KEY_SECRET_NAME: ${{ secrets.GPG_PUBLIC_KEY_SECRET_NAME }}
environment: release
if: |
success() ||
(needs.create-linux-artifacts.result == 'skipped' &&
needs.create-macos-artifacts.result == 'success' &&
needs.windows_artifacts.result == 'success')
steps:
- name: Download Windows portable (x86_64)
uses: actions/download-artifact@v4
with:
name: win-portable-x86_64
path: win-portable-x86_64
- name: Download Windows portable (aarch64)
uses: actions/download-artifact@v4
with:
name: win-portable-aarch64
path: win-portable-aarch64
- name: Download Windows installer (x86_64)
uses: actions/download-artifact@v4
with:
name: win-installer-x86_64
path: win-installer-x86_64
- name: Download Windows installer (aarch64)
uses: actions/download-artifact@v4
with:
name: win-installer-aarch64
path: win-installer-aarch64
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-artifacts
path: macos-artifacts
- name: Download Debian package
uses: actions/download-artifact@v4
with:
name: linux-artifacts
path: deb-package
- 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 >msft-git-public.asc
mv msft-git-public.asc deb-package
- uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const path = require('path');
var releaseMetadata = {
owner: context.repo.owner,
repo: context.repo.repo
};
// Create the release
var tagName = "${{ needs.prereqs.outputs.tag_name }}";
var createdRelease = await github.rest.repos.createRelease({
...releaseMetadata,
draft: true,
tag_name: tagName,
name: tagName
});
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 x86_64 artifacts
uploadDirectoryToRelease('win-installer-x86_64', ['.exe']),
uploadDirectoryToRelease('win-portable-x86_64', ['.exe']),
// Upload Windows aarch64 artifacts
uploadDirectoryToRelease('win-installer-aarch64', ['.exe']),
uploadDirectoryToRelease('win-portable-aarch64', ['.exe']),
// Upload Mac artifacts
uploadDirectoryToRelease('macos-artifacts'),
// Upload Ubuntu artifacts
uploadDirectoryToRelease('deb-package')
]);