forked from git-for-windows/git
-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathrelease.yml
More file actions
1185 lines (1120 loc) · 58 KB
/
release.yml
File metadata and controls
1185 lines (1120 loc) · 58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: $(Date:yyyyMMdd)$(Rev:.r)
trigger:
branches:
exclude:
- '*'
tags:
include:
- v[0-9]*vfs*
pr: none
resources:
repositories:
- repository: 1ESPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release
parameters:
- name: 'esrp'
type: boolean
default: true
displayName: 'Enable ESRP code signing'
- name: 'github'
type: boolean
default: true
displayName: 'Enable GitHub release publishing'
- name: 'versionOverride'
type: string
default: '-'
displayName: 'Version override (release publishing is skipped if set)'
#
# 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 (x64)'
pool: GitClientPME-1ESHostedPool-intel-pc
poolArch: amd64
image: win-x86_64-ado1es
os: windows
toolchain: x86_64
cpu_arch: x86_64
mingwprefix: mingw64
msystem: MINGW64
sdk_repo: git-for-windows/git-sdk-64
- id: windows_arm64
jobName: 'Windows (ARM64)'
pool: GitClientPME-1ESHostedPool-arm64-pc
poolArch: arm64
image: win-arm64-ado1es
os: windows
toolchain: clang-aarch64
cpu_arch: aarch64
mingwprefix: clangarm64
msystem: CLANGARM64
sdk_repo: git-for-windows/git-sdk-arm64
- name: macos_matrix
type: object
default:
- id: macos_universal
jobName: 'macOS (x64 + ARM64)'
pool: 'Azure Pipelines'
# macOS-latest is an Intel x86_64 Mac Pro, which can't host the
# arm64 Homebrew the universal-binary build needs. We need to
# explictly target the newer Apple Silicon machines that can run
# both x86_64 and arm64 builds.
image: macOS-15-arm64
os: macos
- name: linux_matrix
type: object
default:
- id: linux_x64
jobName: 'Linux (x64)'
pool: GitClientPME-1ESHostedPool-intel-pc
poolArch: amd64
image: ubuntu-x86_64-ado1es
os: linux
cc_arch: x86_64
deb_arch: amd64
- id: linux_arm64
jobName: 'Linux (ARM64)'
pool: GitClientPME-1ESHostedPool-arm64-pc
poolArch: arm64
image: ubuntu-arm64-ado1es
os: linux
cc_arch: aarch64
deb_arch: arm64
variables:
- name: 'esrpAppConnectionName'
value: '1ESGitClient-ESRP-App'
- name: 'githubConnectionName'
value: 'GitHub-MicrosoftGit'
# ESRP signing variables set in the pipeline settings:
# - esrpEndpointUrl
# - esrpMI
# - esrpClientId
# - esrpTenantId
# - esrpKeyVaultName
# - esrpSignReqCertName
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: prereqs
displayName: 'Prerequisites'
jobs:
- job: prebuild
displayName: 'Pre-build validation'
pool:
name: GitClientPME-1ESHostedPool-intel-pc
image: ubuntu-x86_64-ado1es
os: linux
steps:
- checkout: self
fetchDepth: 0
fetchTags: true
- ${{ if or(eq(parameters.versionOverride, ''), eq(parameters.versionOverride, '-')) }}:
- task: Bash@3
displayName: 'Resolve version and tag information'
name: info
inputs:
targetType: filePath
filePath: .azure-pipelines/scripts/resolve-version.sh
- ${{ if and(ne(parameters.versionOverride, ''), ne(parameters.versionOverride, '-')) }}:
- task: Bash@3
displayName: 'Set version override information'
name: info
inputs:
targetType: inline
script: |
tag_sha=$(git rev-parse HEAD)
echo "##vso[task.logissue type=warning]Using version override: ${{ parameters.versionOverride }}. Release publishing will be skipped."
echo "Git version: ${{ parameters.versionOverride }}"
echo "Tag name: untagged"
echo "Tag SHA: ${tag_sha}"
echo "##vso[task.setvariable variable=git_version;isOutput=true;isReadOnly=true]${{ parameters.versionOverride }}"
echo "##vso[task.setvariable variable=tag_name;isOutput=true;isReadOnly=true]untagged"
echo "##vso[task.setvariable variable=tag_sha;isOutput=true;isReadOnly=true]${tag_sha}"
echo "##vso[build.updatebuildnumber][UNTAGGED] ${tag_sha} (${BUILD_BUILDNUMBER:-unknown})"
- stage: build
displayName: 'Build'
dependsOn: [prereqs]
jobs:
#
# 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 }}
hostArchitecture: ${{ dim.poolArch }}
variables:
tag_name: $[stageDependencies.prereqs.prebuild.outputs['info.tag_name']]
tag_sha: $[stageDependencies.prereqs.prebuild.outputs['info.tag_sha']]
git_version: $[stageDependencies.prereqs.prebuild.outputs['info.git_version']]
toolchain: ${{ dim.toolchain }}
mingwprefix: ${{ dim.mingwprefix }}
sdk_repo: ${{ dim.sdk_repo }}
cpu_arch: ${{ dim.cpu_arch }}
templateContext:
outputs:
- output: pipelineArtifact
targetPath: '$(Build.ArtifactStagingDirectory)/_final'
artifactName: '${{ dim.id }}'
steps:
- checkout: self
# Add Git Bash to the PATH so Bash tasks can find it
- task: BatchScript@1
displayName: 'Add Git Bash to PATH'
inputs:
filename: ./.azure-pipelines/scripts/windows/setup-git-bash.cmd
# Install Azure CLI on arm64 (not pre-installed on these agents)
- ${{ if eq(dim.poolArch, 'arm64') }}:
- powershell: |
$ProgressPreference = 'SilentlyContinue'
$msi = "$env:TEMP\AzureCLI.msi"
Write-Host "Downloading Azure CLI (x64)..."
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile $msi
Write-Host "Installing Azure CLI..."
Start-Process msiexec.exe -ArgumentList "/i", $msi, "/quiet", "/norestart" -Wait
$azPath = "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin"
Write-Host "##vso[task.prependpath]$azPath"
Write-Host "Azure CLI installed."
displayName: 'Install Azure CLI (x64 on ARM64)'
# Install VS 2022 Build Tools on x64 so cv2pdb-strip can locate
# mspdb140.dll. We do not need to do this on ARM64 since we use
# clang's llvm-strip there instead.
- ${{ if eq(dim.poolArch, 'amd64') }}:
- task: PowerShell@2
displayName: 'Setup cv2pdb (x64)'
inputs:
filePath: ./.azure-pipelines/scripts/windows/setup-cv2pdb-x64.ps1
- task: Bash@3
displayName: 'Install Git for Windows SDK'
inputs:
filePath: ./.azure-pipelines/scripts/windows/setup-git-sdk.sh
arguments: '$(sdk_repo) $(mingwprefix) "$(Agent.TempDirectory)\gitsdk"'
env:
BOOTSTRAP_DIR: '$(Build.SourcesDirectory)'
# please.sh's `create-sdk-artifact` step does the
# final sparse-checkout of the build-installers
# SDK subset, which is I/O-bound (lots of small
# writes), not CPU-bound. The default
# checkout.workers=1 leaves the agent's I/O
# subsystem mostly idle; bumping it well beyond
# the core count gives a substantial speedup.
GIT_CONFIG_PARAMETERS: "'checkout.workers=56'"
- task: Bash@3
displayName: 'Clone build-extra into SDK'
inputs:
targetType: inline
script: |
set -euo pipefail
# The please.sh + signtool.sh scripts the build
# relies on live in build-extra; the SDK ships
# without them. Partial clone to keep this fast.
git clone --filter=blob:none --single-branch -b main \
https://github.com/git-for-windows/build-extra \
/usr/src/build-extra
# Setup ESRP code signing for Windows (sets ESRP_TOOL,
# ESRP_AUTH) before the build steps so that the build
# itself (Inno Setup, makepkg-mingw) can invoke ESRP
# via the `git signtool` alias for in-line signing of
# individual binaries.
- ${{ if eq(parameters.esrp, true) }}:
- template: .azure-pipelines/esrp/windows/setup.yml@self
parameters:
serviceConnectionName: $(esrpAppConnectionName)
esrpClientId: $(esrpClientId)
keyVaultName: $(esrpKeyVaultName)
signCertName: $(esrpSignReqCertName)
- task: Bash@3
displayName: 'Configure git signtool alias for ESRP'
inputs:
targetType: inline
script: |
set -euo pipefail
# please.sh, makepkg-mingw, and Inno Setup's
# release.sh all detect this alias and route
# their per-file code-signing through it; see
# build-extra's please.sh + installer/release.sh.
script="$(cygpath -au "$BUILD_SOURCESDIRECTORY/.azure-pipelines/esrp/windows/esrpsign.sh")"
git config --global alias.signtool "!sh \"$script\""
git config --global --get alias.signtool
- task: Bash@3
displayName: 'Apply Windows build patches'
inputs:
targetType: inline
script: |
set -euo pipefail
apply="$(cygpath -au "$BUILD_SOURCESDIRECTORY/.azure-pipelines/scripts/apply-patches.sh")"
patches="$(cygpath -au "$BUILD_SOURCESDIRECTORY/.azure-pipelines/patches/windows")"
bash "$apply" "$patches/build-extra" /usr/src/build-extra
bash "$apply" "$patches/git-sdk" "/$(mingwprefix)"
- task: Bash@3
displayName: 'Build mingw-w64-git package'
env:
# The mingw-w64-git build is heavy on parallel work
# that the underlying compile (and especially the
# contrib + doc + i18n + perl-script generation
# stages) can soak up far more aggressively than the
# core count would suggest, since most of it is I/O
# against the SDK's pacman cache and the make rules
# have very few real serialisation points.
MAKEFLAGS: -j15
${{ if eq(parameters.esrp, true) }}:
ESRP_TOOL: $(ESRP_TOOL)
ESRP_AUTH: $(ESRP_AUTH)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: inline
script: |
set -euo pipefail
set -x
# Detach stdin so descendants like the git-extra
# post_install hook (which runs `for s in $(grep
# -l PAT $(find /mingw*/bin/ ...))` and falls back
# to reading stdin when /mingw*/bin/ is absent
# and find produces empty output) cannot block
# the build waiting for input. Bash@3 leaves the
# task's stdin pipe open with no writer; the
# GitHub Actions runner closes it for the same
# reason (see actions/runner ProcessInvoker.cs).
exec </dev/null
VERSION="$(git_version)"
# Git's GIT-VERSION-GEN expects .rc rather than -rc.
BUILD_VERSION="$(echo "$VERSION" | sed 's/-rc/.rc/g')"
# makepkg-mingw runs git internally and looks for a
# plain /usr/bin/git; trampoline it to the matching
# MinGW-built git.exe.
printf '#!/bin/sh\n\nexec /%s/bin/git.exe "$@"\n' \
"$(mingwprefix)" >/usr/bin/git
chmod +x /usr/bin/git
USER_NAME='microsoft-git-build'
USER_EMAIL='microsoft-git-build@users.noreply.github.com'
git config --global user.name "$USER_NAME"
git config --global user.email "$USER_EMAIL"
export PACKAGER="$USER_NAME <$USER_EMAIL>"
# please.sh build-mingw-w64-git derives the package
# version by running `git for-each-ref --points-at=HEAD
# 'refs/tags/v[0-9]*'` against this source tree (and
# then pushing the chosen tag into the freshly cloned
# /usr/src/MINGW-packages/mingw-w64-git/{git,src/git}
# worktrees that makepkg-mingw actually builds from).
# If no v[0-9]* tag is found at HEAD, please.sh falls
# back to `git describe + timestamp` and creates a
# lightweight tag whose name has nothing to do with
# the pipeline-resolved $(git_version). On a real
# release run HEAD is already tagged via the tag-push
# trigger; on a debug run (e.g. the TO-DROP static-
# version override that only fakes git_version in the
# prereqs stage) it is not, so create an annotated
# tag at HEAD ourselves so please.sh picks it up and
# the resulting mingw-w64-git package, the
# cv2pdb-stripped binaries inside it, and the
# downstream Inno Setup installer are all named after
# the resolved git_version. If the tag already exists
# but does not point at HEAD, fail loudly: silently
# skipping creation would let please.sh's for-each-ref
# not find it (since it is not at HEAD) and fall back
# to the describe+timestamp path this whole block is
# supposed to prevent.
if git rev-parse --verify "refs/tags/v$(git_version)"
then
tag_commit="$(git rev-parse "refs/tags/v$(git_version)^{commit}")"
head_commit="$(git rev-parse HEAD)"
test "$tag_commit" = "$head_commit" || {
echo "##vso[task.logissue type=error]Tag v$(git_version) points at $tag_commit, not HEAD ($head_commit)" >&2
exit 1
}
else
git tag -a -m "v$(git_version)" "v$(git_version)" HEAD
fi
# Pre-create the worktree that makepkg-mingw will
# actually compile, instead of letting please.sh's
# `clone --reference $bare https://github.com/git-for-windows/git`
# do it. The PKGBUILD's `build()` -> `make` invokes
# microsoft/git's GIT-VERSION-GEN, which `git
# describe`s in $srcdir/git and rejects any version
# whose `${VN%%.vfs.*}` does not match the hard-coded
# `${DEF_VER%%.vfs.*}` (`v2.53.0`). For real release
# tags the two match, but for any debug tag (in
# particular the v9.99.99.vfs.0.0 the TO-DROP commit
# produces) the build aborts with "Found version
# v9.99.99.vfs.0.0, which is not based on
# v2.53.0.vfs.0.0". GIT-VERSION-GEN reads a `version`
# file at the source-tree root in preference to
# running git describe, and the validation only
# fires on the describe path; so plant that file
# ahead of the build.
#
# please.sh would clone /usr/src/MINGW-packages
# itself if missing; we do the same clone first so
# the package directory exists and please.sh skips
# its own clone (line 838-840 of please.sh).
test -d /usr/src/MINGW-packages ||
git clone --depth 1 --single-branch -b main \
https://github.com/git-for-windows/MINGW-packages \
/usr/src/MINGW-packages
# mingw-w64-git/src/git is what makepkg's extract_git
# `git fetch`s and `git checkout --force --no-track
# -B makepkg <tag>`s into. Make it a worktree of this
# agent's checkout so the v$VERSION tag we just
# created is already visible there (worktrees share
# refs and objects with the main repo) and we don't
# have to duplicate the source.
#
# Worktrees also share `.git/config` with the main
# repo, so origin would point at the Azure-supplied
# remote URL and extract_git's `git fetch` would go
# online (and abort the build if it failed). Enable
# `extensions.worktreeConfig` and override origin to
# the local checkout via `git config --worktree` so
# the fetch stays on local disk and is effectively a
# no-op. Mirror please.sh's `core.autoCRLF=false`
# under the same per-worktree namespace so the
# checksums calc_checksum_git() computes against
# this tree are reproducible regardless of the main
# repo's autocrlf setting.
mkdir -p /usr/src/MINGW-packages/mingw-w64-git/src
test -d /usr/src/MINGW-packages/mingw-w64-git/src/git || {
git -C "$BUILD_SOURCESDIRECTORY" \
config extensions.worktreeConfig true
git -C "$BUILD_SOURCESDIRECTORY" worktree add \
/usr/src/MINGW-packages/mingw-w64-git/src/git HEAD
git -C /usr/src/MINGW-packages/mingw-w64-git/src/git \
config --worktree remote.origin.url \
"$BUILD_SOURCESDIRECTORY"
git -C /usr/src/MINGW-packages/mingw-w64-git/src/git \
config --worktree core.autoCRLF false
}
# The actual `version` file write that side-steps
# GIT-VERSION-GEN's validation. The file is untracked,
# so makepkg's `git checkout --force --no-track -B
# makepkg <tag>` does not remove it.
echo "$BUILD_VERSION" \
>/usr/src/MINGW-packages/mingw-w64-git/src/git/version
sh -x /usr/src/build-extra/please.sh build-mingw-w64-git \
--only-"$(cpu_arch)" \
-o artifacts \
HEAD
# NOTE: the GitHub workflow additionally GPG-signs
# each tarball and creates a MINGW-packages.bundle
# for downstream Pacman consumers; both are
# intentionally out of scope for the initial port
# and tracked as follow-ups.
- task: Bash@3
displayName: 'Build installer and portable Git'
env:
# `please.sh make_installers_from_mingw_w64_git` invokes
# build-extra's installer/release.sh, which requires
# MSYSTEM to select the architecture branch. Bash@3 does
# not source /etc/profile, so we export it explicitly.
MSYSTEM: ${{ dim.msystem }}
${{ if eq(parameters.esrp, true) }}:
ESRP_TOOL: $(ESRP_TOOL)
ESRP_AUTH: $(ESRP_AUTH)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: inline
script: |
set -euo pipefail
set -x
# Detach stdin so descendants like the git-extra
# post_install hook (which runs `for s in $(grep
# -l PAT $(find /mingw*/bin/ ...))` and falls back
# to reading stdin when /mingw*/bin/ is absent
# and find produces empty output) cannot block
# the build waiting for input. Bash@3 leaves the
# task's stdin pipe open with no writer; the
# GitHub Actions runner closes it for the same
# reason (see actions/runner ProcessInvoker.cs).
exec </dev/null
b=/usr/src/build-extra
# Make every nested bash subshell auto-enable
# xtrace; pacman runs each package's install
# hook in its own non-interactive bash, and
# without this we would not see anything that
# happens between the moment pacman calls into
# the hook and the moment it returns. Tracing
# is cheap; debugging a silent hang is not.
printf 'set -x\n' >/tmp/setx.sh
export BASH_ENV=/tmp/setx.sh
# please.sh make_installers_from_mingw_w64_git
# --include-pdbs reads PDB archives from
# cached-source-packages/.
mkdir -p "$b/cached-source-packages"
cp artifacts/*-pdb* "$b/cached-source-packages/"
# The --pkg=... list excludes the optional pieces
# the workflow drops (signatures, archimport, cvs,
# p4, gitweb, doc-man); keep the same filter so
# the resulting .exe size is comparable.
pkg_args=$(
ls artifacts/mingw-w64-$(toolchain)-*.tar.* \
| sed '/\.sig$/d;/archimport/d;/cvs/d;/p4/d;/gitweb/d;/doc-man/d;s/^/--pkg=/' \
| tr '\n' ' '
)
for type in installer portable; do
eval sh -x "$b"/please.sh make_installers_from_mingw_w64_git --include-pdbs \
--version="$(git_version)" \
-o artifacts --"$type" \
$pkg_args
# The installer .exe is signed inline by Inno
# Setup via the `git signtool` alias; the
# portable .exe is a 7z self-extractor that
# bypasses that path, so sign it explicitly.
if test "$type" = portable && \
test -n "$(git config alias.signtool)"
then
git signtool artifacts/PortableGit-*.exe
fi
done
- task: Bash@3
displayName: 'Stage installer artifacts for upload'
inputs:
targetType: inline
script: |
set -euo pipefail
# Compute SHA-256 over the (possibly signed)
# binaries; if ESRP signing ran, this picks up
# the post-sign bytes, which is what we want to
# publish in the release notes.
openssl dgst -sha256 \
artifacts/Git-*.exe \
artifacts/PortableGit-*.exe \
| sed 's/.* //' >artifacts/sha-256.txt
mkdir -p "$(Build.ArtifactStagingDirectory)/_final"
cp artifacts/Git-*.exe \
artifacts/PortableGit-*.exe \
artifacts/sha-256.txt \
"$(Build.ArtifactStagingDirectory)/_final/"
# Validate the freshly built installer in-place: silently
# install Git-*.exe and assert that `git --version` reports
# the version we resolved at the prereqs stage. Folded into
# the build job so it runs on the same agent without the
# 1ES job-startup overhead a separate validate job carries.
- powershell: |
$exe = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\_final\Git-*.exe" |
Where-Object { $_.Name -notlike 'PortableGit-*' } |
Select-Object -First 1 -ExpandProperty FullName
if (-not $exe) {
Write-Error "No Git-*.exe installer found in _final"
exit 1
}
Write-Host "Installing $exe"
$p = Start-Process -Wait -PassThru -FilePath "$exe" `
-ArgumentList "/SILENT","/VERYSILENT","/NORESTART","/SUPPRESSMSGBOXES","/ALLOWDOWNGRADE=1"
if ($p.ExitCode -ne 0) {
Write-Error "Installer exited with code $($p.ExitCode)"
exit $p.ExitCode
}
displayName: 'Install Git'
- powershell: |
$raw = & "$env:ProgramW6432\Git\cmd\git.exe" --version
$actual = ($raw -replace '^git version ', '').Trim()
$expect = ('$(git_version)' -replace '-rc', '.rc').Trim()
Write-Host "Expected: $expect"
Write-Host "Actual: $actual"
if ($actual -ne $expect) {
Write-Error "Version mismatch: expected '$expect', got '$actual'"
exit 1
}
displayName: 'Validate installed version'
#
# 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 }}
variables:
tag_name: $[stageDependencies.prereqs.prebuild.outputs['info.tag_name']]
tag_sha: $[stageDependencies.prereqs.prebuild.outputs['info.tag_sha']]
git_version: $[stageDependencies.prereqs.prebuild.outputs['info.git_version']]
templateContext:
outputs:
- output: pipelineArtifact
targetPath: '$(Build.ArtifactStagingDirectory)/_final'
artifactName: '${{ dim.id }}'
# macOS build flow:
#
# 1. Configure for a universal build and produce
# Git's own dist tarballs (`make dist dist-doc`).
# 2. Extract the source tarball into payload/, copy
# config.mak in, run `make payload` to compile and
# install into stage/git-universal-<ver>/.
# 3. Mirror stage/ into build-artifacts/ (which is
# what the macos-installer Makefile's `pkg` target
# consumes - see note in the build step).
# 4. ESRP-sign Mach-O files in build-artifacts/.
# 5. `make pkg` -> unsigned .pkg in disk-image/.
# 6. ESRP-sign and ESRP-notarize the .pkg in place.
# 7. `make image` wraps disk-image/ contents in a DMG.
# 8. Stage the .pkg and .dmg under _final/ for upload.
steps:
- checkout: self
- task: Bash@3
displayName: 'Disable Spotlight indexing'
inputs:
targetType: inline
script: |
# Disable Spotlight indexing to prevent file
# locking issues.
set -euo pipefail
sudo mdutil -i off / || true
- task: Bash@3
displayName: 'Install build dependencies'
inputs:
targetType: inline
script: |
set -euo pipefail
# The agent's native arm64 Homebrew lives at
# /opt/homebrew. Install a separate x86_64 Homebrew
# under /usr/local so we can fetch the x86_64 build
# of gettext/libintl alongside the arm64 one.
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
# Native (arm64) build dependencies.
brew install automake asciidoc xmlto docbook
brew link --force gettext
# Make a universal libintl.a out of the two
# arch-specific copies, dropped at the workspace
# root so the build's LDFLAGS = -L"$(pwd)" can find
# it. libintl depends on iconv, but we deliberately
# use the system's universal /usr/lib/libiconv.dylib
# rather than Homebrew's libiconv (which exports
# _libiconv* symbols, while Homebrew's gettext was
# built against system iconv with _iconv* symbols).
lipo -create \
-output libintl.a \
/usr/local/opt/gettext/lib/libintl.a \
/opt/homebrew/opt/gettext/lib/libintl.a
- task: Bash@3
displayName: 'Configure universal build'
inputs:
targetType: inline
script: |
set -euo pipefail
VERSION="$(git_version)"
# Git's GIT-VERSION-GEN expects .rc rather than -rc
BUILD_VERSION="$(echo "$VERSION" | sed 's/-rc/.rc/g')"
echo "$BUILD_VERSION" >version
# HOST_CPU is a bit of a lie and is only used in
# 'git version --build-options'; we'll fix that in
# code. The two -arch flags are what actually drive
# the universal build.
cat >config.mak <<EOF
HOST_CPU = universal
BASIC_CFLAGS += -arch arm64 -arch x86_64
# macOS uses Apple Common Crypto instead of OpenSSL.
# The main Makefile sets NO_OPENSSL and adds
# -DNO_OPENSSL to BASIC_CFLAGS, but contrib
# Makefiles that only include config.mak* miss it.
BASIC_CFLAGS += -DNO_OPENSSL
# On Darwin 24+ (macOS 15) config.mak.uname sets
# USE_HOMEBREW_LIBICONV, pointing ICONVDIR at
# Homebrew's libiconv. That libiconv exports
# _libiconv/_libiconv_open (prefixed) symbols while
# Homebrew's gettext/libintl was built against the
# system iconv that exports _iconv/_iconv_open
# (unprefixed). We pin the build to the system's
# universal /usr/lib/libiconv.dylib instead.
USE_HOMEBREW_LIBICONV =
ICONVDIR =
EOF
# Find the universal libintl.a we lipo'd in the
# previous step ($(pwd) is the workspace root) and
# the gettext headers from either Homebrew prefix.
homebrew_prefix="$(brew --prefix)"
cat >>config.mak <<EOF
CFLAGS = -I$homebrew_prefix/include -I/usr/local/opt/gettext/include
LDFLAGS = -L"$(pwd)"
EOF
# Use the OS-supplied libcurl rather than Homebrew's.
cat >>config.mak <<EOF
CURL_LDFLAGS := -lcurl
CURL_CONFIG := /usr/bin/true
EOF
# The dashed built-ins are supposed to be hard-links
# to the 'git' executable; on the macOS staging tree
# they end up as full copies instead and bloat the
# .dmg indecently. Skip them entirely.
echo 'SKIP_DASHED_BUILT_INS = YabbaDabbaDoo' >>config.mak
- task: Bash@3
displayName: 'Build payload via macos-installer'
env:
# The macos-installer Makefile derives BUILD_DIR from
# $(GITHUB_WORKSPACE), which is unset in ADO. Point it
# at the worktree root.
GITHUB_WORKSPACE: $(Build.SourcesDirectory)
inputs:
targetType: inline
script: |
set -euo pipefail
VERSION="$(git_version)"
BUILD_VERSION="$(echo "$VERSION" | sed 's/-rc/.rc/g')"
# The asciidoc/xmlto build steps need the catalogs
# from Homebrew docbook.
export XML_CATALOG_FILES="$(brew --prefix)/etc/xml/catalog"
# `git commit` (in dist-doc) forks a detached
# `git maintenance run --auto` that keeps writing
# into .git/ after the commit returns, which then
# races with dist-doc's `rm -fr .doc-tmp-dir` and
# produces "Directory not empty". Disable
# auto-maintenance for every git invocation in
# this build.
export GIT_CONFIG_PARAMETERS="'maintenance.auto=false'"
make -j"$(sysctl -n hw.physicalcpu)" GIT-VERSION-FILE dist dist-doc
# Recover the source-tree commit OID from the dist
# tarball; the macos-installer Makefile bakes it
# into 'git version --build-options'.
# `git get-tar-commit-id` reads only the leading
# pax header and then closes its stdin, which
# makes `gunzip -c` exit 141 (SIGPIPE) under the
# outer `set -o pipefail`. Disable pipefail for
# the duration of this one pipeline.
GIT_BUILT_FROM_COMMIT="$(
set +o pipefail
gunzip -c "git-$BUILD_VERSION.tar.gz" |
git get-tar-commit-id
)"
export GIT_BUILT_FROM_COMMIT
export VERSION
mkdir payload manpages
tar -xf "git-$BUILD_VERSION.tar.gz" -C payload
tar -xf "git-manpages-$BUILD_VERSION.tar.gz" -C manpages
# The actual compile happens inside the extracted
# tree, against a copy of the config.mak we wrote
# at the worktree root in the previous step.
cp config.mak "payload/git-$BUILD_VERSION/config.mak"
make -C .github/macos-installer V=1 payload
# NOTE: the macos-installer Makefile produces
# the install tree at stage/git-universal-<ver>/
# but its `pkg` target packages from
# build-artifacts/. Mirror the GitHub workflow
# by copying the tree across so the rest of the
# pipeline (signing, pkg) finds it where the
# Makefile expects.
#
# FUTURE: this duplication exists only because
# .github/macos-installer/Makefile hardcodes
# both DESTDIR (=stage/...) and ARTIFACTDIR
# (=build-artifacts). Overriding ARTIFACTDIR on
# the `make pkg` line below to point at stage/
# would let us drop the cp entirely. Worth
# cleaning up alongside moving the macOS
# installer Makefiles out of .github/ (they are
# build infrastructure, not GitHub-specific).
mkdir -p .github/macos-installer/build-artifacts
cp -R "stage/git-universal-$BUILD_VERSION/." \
.github/macos-installer/build-artifacts/
# ESRP-sign the universal Mach-O binaries inside the
# payload tree. The existing esrp/sign.yml template's
# CopyFiles@2 step uses minimatch globs and the only
# reliable way to detect Mach-O is by file content
# (file --mime), so we pre-filter into a staging dir,
# let the template zip/sign/extract that staging dir,
# then copy the signed binaries back over the payload.
- ${{ if eq(parameters.esrp, true) }}:
# ESRP ADO tasks require .NET, which the macOS pool
# image does not provide by default.
- task: UseDotNet@2
displayName: 'Install .NET for ESRP'
inputs:
packageType: sdk
version: '8.x'
- task: Bash@3
displayName: 'Stage Mach-O binaries for signing'
inputs:
targetType: inline
script: |
set -euo pipefail
# Sign the install tree (build-artifacts/) -
# this is what `make pkg` packages. Signing
# the source tree under payload/ would have
# no effect on the resulting .pkg.
install_tree=".github/macos-installer/build-artifacts/usr/local/git"
stage_dir="$(Build.ArtifactStagingDirectory)/macos-tosign/binaries"
rm -rf "$stage_dir"
mkdir -p "$stage_dir"
pushd "$install_tree"
find . -type f -exec file --mime {} + \
| sed -n '/mach/s/: .*//p' \
| while IFS= read -r f; do
rel="${f#./}"
tgt="$stage_dir/$rel"
mkdir -p "$(dirname "$tgt")"
cp -- "$f" "$tgt"
done
popd
- template: .azure-pipelines/esrp/sign.yml@self
parameters:
displayName: 'ESRP-sign Mach-O binaries'
folderPath: '$(Build.ArtifactStagingDirectory)/macos-tosign/binaries'
pattern: '**/*'
useArchive: true # Required for macOS signing
inlineOperation: |
[
{
"KeyCode": "CP-401337-Apple",
"OperationCode": "MacAppDeveloperSign",
"ToolName": "sign",
"ToolVersion": "1.0",
"Parameters": {
"Hardening": "Enable"
}
}
]
- task: Bash@3
displayName: 'Copy signed binaries back to install tree'
inputs:
targetType: inline
script: |
set -euo pipefail
cp -R "$(Build.ArtifactStagingDirectory)/macos-tosign/binaries"/* \
.github/macos-installer/build-artifacts/usr/local/git/
- task: Bash@3
displayName: 'Build unsigned installer pkg'
env:
GITHUB_WORKSPACE: $(Build.SourcesDirectory)
inputs:
targetType: inline
script: |
set -euo pipefail
VERSION="$(git_version)"
export VERSION
# Leave APPLE_INSTALLER_IDENTITY undefined so the
# Makefile's `pkg` target produces an unsigned .pkg
# (the `ifdef APPLE_INSTALLER_IDENTITY` branch in
# pkg_cmd is skipped). ESRP signs it in the next
# step.
make -C .github/macos-installer V=1 pkg
- ${{ if eq(parameters.esrp, true) }}:
- template: .azure-pipelines/esrp/sign.yml@self
parameters:
displayName: 'ESRP-sign installer pkg'
folderPath: '.github/macos-installer/disk-image'
pattern: '*.pkg'
useArchive: true # Required for macOS signing
inlineOperation: |
[
{
"KeyCode": "CP-401337-Apple",
"OperationCode": "MacAppDeveloperSign",
"ToolName": "sign",
"ToolVersion": "1.0",
"Parameters": {
"Hardening": "Enable"
}
}
]
- template: .azure-pipelines/esrp/sign.yml@self
parameters:
displayName: 'ESRP-notarize installer pkg'
folderPath: '.github/macos-installer/disk-image'
pattern: '*.pkg'
useArchive: true # Required for macOS notarization
inlineOperation: |
[
{
"KeyCode": "CP-401337-Apple",
"OperationCode": "MacAppNotarize",
"ToolName": "sign",
"ToolVersion": "1.0",
"Parameters": {
"BundleId": "com.git.pkg"
}
}
]
- task: Bash@3
displayName: 'Build DMG'
env:
GITHUB_WORKSPACE: $(Build.SourcesDirectory)
inputs:
targetType: inline
script: |
set -euo pipefail
VERSION="$(git_version)"
export VERSION
# Builds .github/macos-installer/git-<version>-universal.dmg
# from the contents of disk-image/, which now contains
# the signed and notarized .pkg.
make -C .github/macos-installer V=1 image
- task: Bash@3
displayName: 'Stage installer artifacts for upload'
inputs:
targetType: inline
script: |
set -euo pipefail
mkdir -p "$(Build.ArtifactStagingDirectory)/_final"
ls -la .github/macos-installer/ \
.github/macos-installer/disk-image/ || true
# The .pkg lands either directly under disk-image/
# or, after ESRP MacAppNotarize re-packs it, inside
# disk-image/<uuid>.zip.unzipped/. Find it.
pkg=$(find .github/macos-installer/disk-image \
-name 'git-*-universal.pkg' -type f \
| head -1)
mv .github/macos-installer/git-*-universal.dmg \
"$pkg" \
"$(Build.ArtifactStagingDirectory)/_final/"
# Validate the freshly built pkg in-place: install it,
# assert `git --version`, and confirm the universal binary
# actually runs natively as the host architecture. Folded
# into the build job so it runs on the same agent without
# the 1ES job-startup overhead a separate validate job
# carries.
- bash: |
set -e
if [ "$(uname -m)" = arm64 ] && command -v brew >/dev/null; then
brew uninstall git || true
fi
pkg=$(find "$(Build.ArtifactStagingDirectory)/_final" \
-name 'git-*-universal.pkg' -type f | head -1)
if [ -z "$pkg" ]; then
echo "No git-*-universal.pkg found in _final" >&2
exit 1
fi
echo "Installing $pkg"
sudo installer -pkg "$pkg" -target /
displayName: 'Install Git'
- bash: |
set -e
actual=$(git --version | sed 's/^git version //')
expect=$(echo "$(git_version)" | sed 's/-rc/.rc/g')
echo "Expected: $expect"
echo "Actual: $actual"
test "$actual" = "$expect"
displayName: 'Validate installed version'
- bash: |
set -ex
git version --build-options >actual
cat actual
grep "cpu: $(uname -m)" actual
displayName: 'Validate universal binary CPU architecture'
#
# 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 }}
hostArchitecture: ${{ dim.poolArch }}
variables:
tag_name: $[stageDependencies.prereqs.prebuild.outputs['info.tag_name']]
tag_sha: $[stageDependencies.prereqs.prebuild.outputs['info.tag_sha']]
git_version: $[stageDependencies.prereqs.prebuild.outputs['info.git_version']]
cc_arch: ${{ dim.cc_arch }}
deb_arch: ${{ dim.deb_arch }}
templateContext:
outputs:
- output: pipelineArtifact
targetPath: '$(Build.ArtifactStagingDirectory)/_final'
artifactName: '${{ dim.id }}'
steps:
- checkout: self
- task: Bash@3
displayName: 'Log build environment'
inputs:
targetType: inline
script: |
lsb_release -a || true
uname -a