Repository navigation
139 lines (132 loc) · 6.1 KB
/
Copy pathbuild-binary.yml
File metadata and controls
139 lines (132 loc) · 6.1 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
name: Build binary
# Builds the standalone 'git-nested' executable for one target and uploads it
# as an artifact. Called by test.yml (so every push/PR proves the recipe still
# builds) and by release-binary.yml (which attaches it to the release) -- so
# what CI exercises is built by exactly the recipe that ships, rather than a
# lookalike that can drift away from it.
#
# One call builds one target: PyInstaller freezes for the machine it runs on
# and nothing else, so Linux, macOS and Windows binaries come from three calls
# with different 'runs-on'/'container', not from one job.
on:
workflow_call:
inputs:
ref:
description: 'Commit/tag/branch to build. Empty: whatever triggered the caller.'
type: string
required: false
default: ''
artifact:
description: 'Name of the uploaded artifact'
type: string
required: false
default: git-nested-binary
runs-on:
description: 'Runner to build on -- ubuntu-26.04, macos-latest, windows-latest'
type: string
required: false
default: ubuntu-26.04
container:
description: 'Container to build inside (Linux only). Empty: build on the runner itself.'
type: string
required: false
default: almalinux:8
python-version:
description: 'Python to freeze, e.g. 3.12'
type: string
required: false
default: '3.12'
outputs:
archive:
description: 'File name of the built archive, e.g. git-nested-1.2.3-x64-linux.tar.xz. Only meaningful for a single (non-matrix) call.'
value: ${{ jobs.build.outputs.archive }}
version:
description: 'Version of the built git-nested, e.g. 1.2.3'
value: ${{ jobs.build.outputs.version }}
jobs:
build:
runs-on: ${{ inputs.runs-on }}
# An empty string here means "no container", which is what the macOS and
# Windows callers pass -- containers are a Linux-runner feature.
#
# For Linux the default is almalinux:8, and that is a portability
# decision, not a convenience one: a PyInstaller binary links the glibc of
# the machine that built it, and glibc is only backward compatible, so
# built on ubuntu-26.04 it would refuse to start on anything older than
# that runner. AlmaLinux 8 has glibc 2.28, the oldest that still runs the
# node20 the actions below need, which covers every distro from Ubuntu
# 20.04 / Debian 10 / Fedora 29 / RHEL 8 onwards. glibc-based distros only
# -- musl ones (Alpine) can never run this binary.
container: ${{ inputs.container }}
# bash everywhere, including Windows, where the default is pwsh --
# create-python-exe.sh is one script for all three targets.
defaults:
run:
shell: bash
outputs:
archive: ${{ steps.build.outputs.archive }}
version: ${{ steps.build.outputs.version }}
steps:
# Before the checkout -- without git, actions/checkout silently falls
# back to downloading a tarball, and a checkout with no .git has no
# tags for setuptools-scm to derive the version from. Only the container
# needs this; a hosted runner ships all of it.
- name: Install build prerequisites
if: inputs.container != ''
run: dnf -y install "python${{ inputs.python-version }}" "python${{ inputs.python-version }}-pip" git tar xz
# ... and the runners get their python from here instead.
- uses: actions/setup-python@v7
if: inputs.container == ''
with:
python-version: ${{ inputs.python-version }}
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
# the version frozen into the binary comes from the tags, and the
# default shallow checkout fetches none of them.
fetch-depth: 0
# the checkout is owned by a different user than this container's root,
# which git refuses to read from until told it is not a security issue.
- name: Trust the checkout
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Build the executable
id: build
run: |
# In the container python is installed under its versioned name; on
# a runner setup-python puts a plain 'python' on PATH.
if [ -n "${{ inputs.container }}" ]; then
PY="python${{ inputs.python-version }}"
else
PY=python
fi
scripts/create-python-exe.sh --output dist --python "$PY"
# The archive name carries the version and the target
# (git-nested-<version>-<arch>-<os>.tar.xz, .zip on Windows), so it
# cannot be spelled out ahead of time -- it is published as an
# output for callers that need the exact name.
#
# nullglob: only one of the two patterns ever matches (a build
# produces .tar.xz or .zip, never both) -- without it the unmatched
# pattern is passed to ls literally, ls exits non-zero trying to
# read it, and pipefail+errexit abort the step right here with no
# visible error (ls's message went to /dev/null).
shopt -s nullglob
ARCHIVE="$(ls dist/*.tar.xz dist/*.zip 2>/dev/null | head -1)"
test -n "$ARCHIVE" || { echo "no archive produced" >&2; exit 1; }
echo "archive=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
# 'git-nested Version: X.Y.Z' -> X.Y.Z
EXE=dist/git-nested
[ -f "$EXE" ] || EXE=dist/git-nested.exe
echo "version=$("$EXE" --version | head -1 | cut -d' ' -f3)" >> "$GITHUB_OUTPUT"
# the archive rather than the bare executable. It is what a release
# actually ships, and on the two platforms where it is a .tar.xz it
# keeps the exec bit as well, so consumers just untar and run it.
- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact }}
path: |
dist/*.tar.xz
dist/*.zip
if-no-files-found: error
# main keeps its builds for a month, branch and PR builds only for two days.
retention-days: ${{ github.ref == 'refs/heads/main' && 30 || 2 }}