Skip to content

Commit b71fd85

Browse files
committed
rearchitect ci workflow to use fpc mirror when possible
1 parent ee646db commit b71fd85

2 files changed

Lines changed: 476 additions & 193 deletions

File tree

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
#!/usr/bin/env bash
2+
# ─────────────────────────────────────────────────────────────────────
3+
# Unified FPC + Lazarus installer for CI.
4+
#
5+
# Fetches the FPC tarball from the official freepascal.org mirror,
6+
# runs install.sh non-interactively, then clones Lazarus and builds
7+
# lazbuild. Works on Linux, macOS, *BSD, Solaris, and Windows
8+
# (the latter via Git Bash, which ships pre-installed on
9+
# windows-latest GitHub runners).
10+
#
11+
# All work happens under $HOME — no sudo, no system-level changes.
12+
#
13+
# Inputs (env vars):
14+
# FPC_TARGET e.g. x86_64-linux, aarch64-darwin, x86_64-win64
15+
# FPC_VERSION FPC release version, e.g. 3.2.2
16+
# INSTALL_PREFIX where FPC is installed (default: $HOME/fpc-install)
17+
# LAZARUS_DIR where Lazarus source is cloned and built
18+
# (default: $HOME/lazarus-src)
19+
# LAZARUS_BRANCH branch/tag to clone, e.g. lazarus_4_4
20+
# LAZARUS_REPO git URL
21+
# MAKE_CMD 'make' on Linux/Windows/macOS, 'gmake' on BSD/Solaris
22+
# (auto-detected if unset)
23+
#
24+
# Outputs (appended to $GITHUB_PATH if set):
25+
# $INSTALL_PREFIX/bin and $LAZARUS_DIR are added to PATH
26+
# ─────────────────────────────────────────────────────────────────────
27+
28+
set -xeuo pipefail
29+
30+
: "${FPC_VERSION:?FPC_VERSION is required (e.g. 3.2.2)}"
31+
: "${FPC_TARGET:?FPC_TARGET is required (e.g. x86_64-linux)}"
32+
: "${LAZARUS_BRANCH:?LAZARUS_BRANCH is required}"
33+
: "${LAZARUS_REPO:?LAZARUS_REPO is required}"
34+
35+
case "$(uname -s)" in
36+
MINGW*|MSYS*|CYGWIN*) IS_WINDOWS=1 ;;
37+
*) IS_WINDOWS=0 ;;
38+
esac
39+
40+
: "${INSTALL_PREFIX:=$HOME/fpc-install}"
41+
: "${LAZARUS_DIR:=$HOME/lazarus-src}"
42+
43+
# Pick GNU make. On most platforms 'make' is GNU make. On BSDs and
44+
# Solaris, 'make' is BSD make and we need 'gmake'. On Windows
45+
# (windows-latest runner) the only GNU make pre-installed is
46+
# Strawberry Perl's 'gmake.exe' — there is no 'make' on PATH.
47+
# Caller can override MAKE_CMD if they have a different setup.
48+
if [ -z "${MAKE_CMD:-}" ]; then
49+
case "$(uname -s)" in
50+
*BSD|DragonFly|SunOS) MAKE_CMD="gmake" ;;
51+
MINGW*|MSYS*|CYGWIN*) MAKE_CMD="gmake" ;;
52+
*) MAKE_CMD="make" ;;
53+
esac
54+
fi
55+
56+
# ── Fetch + extract ──────────────────────────────────────────────────
57+
58+
# Some BSD tarballs have an OS-version suffix in the filename, e.g.
59+
# fpc-3.2.2.x86_64-freebsd11.tar. The tarball extracts to a directory
60+
# WITHOUT the version suffix. Map our canonical FPC_TARGET to the
61+
# actual filename here; let the post-extract glob handle the directory
62+
# name.
63+
case "$FPC_TARGET" in
64+
x86_64-freebsd) TAR_TARGET="x86_64-freebsd11" ;;
65+
*) TAR_TARGET="$FPC_TARGET" ;;
66+
esac
67+
68+
TARBALL="fpc-${FPC_VERSION}.${TAR_TARGET}.tar"
69+
URL="http://downloads.freepascal.org/fpc/dist/${FPC_VERSION}/${FPC_TARGET}/${TARBALL}"
70+
71+
WORK_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t fpc-install)"
72+
cd "$WORK_DIR"
73+
74+
echo "Downloading $URL"
75+
# curl is on every platform we target; wget isn't (macOS notably).
76+
# Retry to ride out transient mirror hiccups.
77+
curl -fL --retry 5 --retry-delay 5 --retry-all-errors \
78+
-o "$TARBALL" "$URL"
79+
80+
tar xf "$TARBALL"
81+
82+
# Glob the extracted directory: name varies (freebsd11 → freebsd).
83+
# shellcheck disable=SC2086
84+
set -- "fpc-${FPC_VERSION}".*
85+
EXTRACT_DIR="$1"
86+
cd "$EXTRACT_DIR"
87+
88+
# ── Run install.sh non-interactively ─────────────────────────────────
89+
#
90+
# The script asks (in order, each conditional on a file being present):
91+
# 1. Install prefix [always]
92+
# 2. Install Cross binutils? (Y/n) [if binutils-*.tar.gz]
93+
# 3. Install Textmode IDE? (Y/n) [if ide.<target>.tar.gz]
94+
# 4. Install documentation? (Y/n) [if doc-pdf.tar.gz]
95+
# 5. Install demos? (Y/n) [if demo.tar.gz]
96+
# 6. Install demos in <dir> [path] [only if 5 was Y]
97+
# 7. Substitute version by $fpcversion? (Y/n) [if fpc.cfg has version]
98+
#
99+
# We answer the prefix explicitly, then 'n' to everything optional.
100+
# install.sh's yesno() treats empty input as YES, so if our answer
101+
# stream runs short we'd silently install bundles we don't want.
102+
# We send 10 'n's (more than the max number of yesno prompts) to
103+
# guarantee every prompt gets an explicit 'n'. A bounded list is
104+
# preferable to `yes n` — the latter receives SIGPIPE on consumer
105+
# exit, which `set -o pipefail` would surface as a pipeline failure.
106+
mkdir -p "$INSTALL_PREFIX"
107+
108+
# install.sh doesn't `set -e`, so it returns 0 even when its final
109+
# samplecfg call fails (which it does on Windows — see below). We
110+
# verify the install succeeded by checking for the compiler binary.
111+
printf '%s\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\n' "$INSTALL_PREFIX" \
112+
| bash ./install.sh
113+
114+
# ── Locate the installed compiler ────────────────────────────────────
115+
#
116+
# Layout differs between Unix and Windows:
117+
# Unix: $PREFIX/bin/fpc (single binary)
118+
# Windows: $PREFIX/bin/<target>/fpc.exe (target-specific)
119+
# $PREFIX/bin/instantfpc.exe (target-independent
120+
# utilities)
121+
# Both directories need to be on PATH on Windows.
122+
if [ "$IS_WINDOWS" = "1" ]; then
123+
FPC_EXE="$INSTALL_PREFIX/bin/$FPC_TARGET/fpc.exe"
124+
FPC_UTIL_DIR="$INSTALL_PREFIX/bin"
125+
else
126+
FPC_EXE="$INSTALL_PREFIX/bin/fpc"
127+
FPC_UTIL_DIR=""
128+
fi
129+
130+
if [ ! -f "$FPC_EXE" ]; then
131+
echo "ERROR: FPC compiler not found at $FPC_EXE" >&2
132+
ls -la "$INSTALL_PREFIX/bin/" || true
133+
if [ "$IS_WINDOWS" = "1" ]; then
134+
ls -la "$INSTALL_PREFIX/bin/$FPC_TARGET/" || true
135+
fi
136+
exit 1
137+
fi
138+
139+
FPC_BIN_DIR="$(dirname "$FPC_EXE")"
140+
if [ -n "$FPC_UTIL_DIR" ]; then
141+
export PATH="$FPC_BIN_DIR:$FPC_UTIL_DIR:$PATH"
142+
else
143+
export PATH="$FPC_BIN_DIR:$PATH"
144+
fi
145+
146+
# ── Linux glibc 2.34+ workaround ─────────────────────────────────────
147+
#
148+
# FPC 3.2.2 was built against glibc < 2.34 and its cprt0.o references
149+
# __libc_csu_init / __libc_csu_fini, which glibc 2.34 (Aug 2021) made
150+
# private. Linking anything FPC produces against current glibc fails:
151+
# undefined reference to `__libc_csu_init'
152+
# undefined reference to `__libc_csu_fini'
153+
#
154+
# Ubuntu 22.04+ ships glibc 2.34+ and is affected. The fix shipped in
155+
# FPC 3.2.4+ (and Debian/Fedora patched their packages); for our
156+
# pre-built tarball we patch in place by merging stub object code
157+
# into cprt0.o, satisfying the symbols at link time.
158+
#
159+
# Affected: every Linux target (x86_64, aarch64, arm/armhf). The
160+
# 'cprt0' name is consistent across architectures.
161+
#
162+
# See https://gitlab.com/freepascal.org/fpc/source/-/issues/39295
163+
if [ "$(uname -s)" = "Linux" ]; then
164+
RTL_DIR="$INSTALL_PREFIX/lib/fpc/$FPC_VERSION/units/$FPC_TARGET/rtl"
165+
CPRT0="$RTL_DIR/cprt0.o"
166+
if [ -f "$CPRT0" ]; then
167+
STUB_C="$WORK_DIR/csu_stubs.c"
168+
STUB_O="$WORK_DIR/csu_stubs.o"
169+
cat > "$STUB_C" <<'EOF'
170+
/* glibc 2.34+ removed __libc_csu_init / __libc_csu_fini. FPC 3.2.2's
171+
cprt0.o still references them. Provide empty stubs so the linker
172+
is satisfied. */
173+
void __libc_csu_init(int argc, char **argv, char **envp) { (void)argc; (void)argv; (void)envp; }
174+
void __libc_csu_fini(void) {}
175+
EOF
176+
cc -c -fPIC -o "$STUB_O" "$STUB_C"
177+
# Merge stubs into cprt0.o using `ld -r` (relocatable link).
178+
# cprt0.o references __libc_csu_*; the stub provides them; the
179+
# merged object resolves both within itself.
180+
ld -r -o "$CPRT0.new" "$CPRT0" "$STUB_O"
181+
mv "$CPRT0.new" "$CPRT0"
182+
echo "Patched $CPRT0 with glibc 2.34+ stubs."
183+
fi
184+
fi
185+
186+
# ── Windows-specific: generate fpc.cfg ───────────────────────────────
187+
#
188+
# install.sh's final step calls $LIBDIR/samplecfg, which is a POSIX
189+
# shell script that does not ship in the Windows tarball. The Windows
190+
# replacement is fpcmkcfg.exe at $PREFIX/bin/. Without an fpc.cfg,
191+
# the compiler can't find its own units. Note: fpc.cfg goes next to
192+
# fpc.exe in $PREFIX/bin/<target>/, since FPC searches there first.
193+
if [ "$IS_WINDOWS" = "1" ]; then
194+
FPCMKCFG="$FPC_UTIL_DIR/fpcmkcfg.exe"
195+
if [ ! -f "$FPCMKCFG" ]; then
196+
echo "ERROR: fpcmkcfg.exe not found at $FPCMKCFG" >&2
197+
exit 1
198+
fi
199+
"$FPCMKCFG" -d "basepath=$INSTALL_PREFIX/lib/fpc/$FPC_VERSION" \
200+
-o "$FPC_BIN_DIR/fpc.cfg"
201+
fi
202+
203+
# Add to GitHub Actions PATH so subsequent steps see fpc.
204+
# On Windows, GITHUB_PATH expects native Windows paths (C:\foo\bin),
205+
# not MSYS/Git Bash paths (/c/foo/bin). cygpath converts both ways.
206+
# Both bin/<target> and bin/ go on PATH so subsequent steps find both
207+
# fpc.exe (the compiler) and instantfpc.exe (the utility).
208+
if [ -n "${GITHUB_PATH:-}" ]; then
209+
if [ "$IS_WINDOWS" = "1" ]; then
210+
cygpath -w "$FPC_BIN_DIR" >> "$GITHUB_PATH"
211+
cygpath -w "$FPC_UTIL_DIR" >> "$GITHUB_PATH"
212+
else
213+
echo "$FPC_BIN_DIR" >> "$GITHUB_PATH"
214+
fi
215+
fi
216+
217+
fpc -iV
218+
fpc -iSO
219+
fpc -iSP
220+
221+
# ── Build Lazarus from source ────────────────────────────────────────
222+
#
223+
# We always build lazbuild from source rather than using a packaged
224+
# Lazarus. Reasons:
225+
# - Cross-platform consistency: same code path on all 10 targets.
226+
# - The packaged Lazarus on Linux/Windows pulls in the full IDE
227+
# (GTK / Qt / native widgets), which we don't need.
228+
# - lazbuild builds in ~1–2 minutes; cheap relative to the FPC fetch.
229+
230+
git clone --depth 1 --branch "$LAZARUS_BRANCH" "$LAZARUS_REPO" "$LAZARUS_DIR"
231+
232+
# DragonFlyBSD: Lazarus is missing include/dragonfly/lazconf.inc.
233+
# DragonFlyBSD is a FreeBSD derivative, so the FreeBSD include works
234+
# as-is. Patch it in before building.
235+
if [ "$(uname -s)" = "DragonFly" ]; then
236+
DF_INC="$LAZARUS_DIR/ide/packages/ideconfig/include/dragonfly"
237+
if [ ! -f "$DF_INC/lazconf.inc" ]; then
238+
mkdir -p "$DF_INC"
239+
cp "$LAZARUS_DIR/ide/packages/ideconfig/include/freebsd/lazconf.inc" \
240+
"$DF_INC/lazconf.inc"
241+
fi
242+
fi
243+
244+
# On Windows, gmake.exe is a native PE binary that may not understand
245+
# Git Bash's /c/Users/... path style. Pass a Windows-format path.
246+
if [ "$IS_WINDOWS" = "1" ]; then
247+
$MAKE_CMD -C "$(cygpath -w "$LAZARUS_DIR")" lazbuild
248+
else
249+
$MAKE_CMD -C "$LAZARUS_DIR" lazbuild
250+
fi
251+
252+
# ── Write Lazarus environmentoptions.xml ─────────────────────────────
253+
# lazbuild reads this on startup to locate the Lazarus source tree
254+
# and the FPC compiler. Without it, lazbuild emits:
255+
# Error: (lazbuild) Invalid Lazarus directory "": directory lcl not found
256+
#
257+
# The location lazbuild looks at is platform-specific:
258+
# Unix: $HOME/.lazarus/ (dotted)
259+
# Windows: %LOCALAPPDATA%\lazarus\ — NOT %APPDATA%, NOT dotted
260+
#
261+
# The Windows path comes from Lazarus's lazbaseconf.inc:
262+
# PrimaryConfigPath := ExtractFilePath(ChompPathDelim(
263+
# GetAppConfigDirUTF8(False))) + 'lazarus';
264+
# where GetAppConfigDir(False) on Windows resolves via
265+
# CSIDL_LOCAL_APPDATA to %LOCALAPPDATA%\<appname>\, so the parent +
266+
# 'lazarus' is %LOCALAPPDATA%\lazarus\ (no leading dot).
267+
#
268+
# On Windows, lazbuild is a native PE binary and expects Windows-
269+
# style paths in the XML values, so we cygpath them to backslash form.
270+
if [ "$IS_WINDOWS" = "1" ]; then
271+
# On Windows runners $LOCALAPPDATA is set; fall back to the
272+
# well-known location if it isn't.
273+
WIN_LOCALAPPDATA="${LOCALAPPDATA:-$USERPROFILE/AppData/Local}"
274+
LAZ_CFG_DIR="$(cygpath -u "$WIN_LOCALAPPDATA")/lazarus"
275+
LAZ_DIR_NATIVE="$(cygpath -w "$LAZARUS_DIR")"
276+
FPC_EXE_NATIVE="$(cygpath -w "$FPC_EXE")"
277+
else
278+
LAZ_CFG_DIR="${HOME}/.lazarus"
279+
LAZ_DIR_NATIVE="$LAZARUS_DIR"
280+
FPC_EXE_NATIVE="$FPC_EXE"
281+
fi
282+
283+
mkdir -p "$LAZ_CFG_DIR"
284+
285+
cat > "$LAZ_CFG_DIR/environmentoptions.xml" <<EOF
286+
<?xml version="1.0" encoding="UTF-8"?>
287+
<CONFIG>
288+
<EnvironmentOptions>
289+
<LazarusDirectory Value="$LAZ_DIR_NATIVE"/>
290+
<CompilerFilename Value="$FPC_EXE_NATIVE"/>
291+
</EnvironmentOptions>
292+
</CONFIG>
293+
EOF
294+
295+
# Put lazbuild on PATH for subsequent steps.
296+
export PATH="$LAZARUS_DIR:$PATH"
297+
if [ -n "${GITHUB_PATH:-}" ]; then
298+
if [ "$IS_WINDOWS" = "1" ]; then
299+
cygpath -w "$LAZARUS_DIR" >> "$GITHUB_PATH"
300+
else
301+
echo "$LAZARUS_DIR" >> "$GITHUB_PATH"
302+
fi
303+
fi
304+
305+
lazbuild --version
306+
echo "FPC + Lazarus installation complete."

0 commit comments

Comments
 (0)