|
| 1 | +#!/bin/bash |
| 2 | +# Build Julia from source with the polyglot image's settings: |
| 3 | +# - USE_BINARYBUILDER=0 → fully self-contained source build |
| 4 | +# - multi-target sysimage → official JuliaCI x86_64 / aarch64 target list |
| 5 | +# - DISABLE_LIBUNWIND=1 → no libunwind in the bundle (Matlab clash workaround) |
| 6 | +# requires the JuliaLang/julia#61899 fix patched |
| 7 | +# into src/{signals-unix.c,stackwalk.c} |
| 8 | +# |
| 9 | +# Args: $1 = path to extracted Julia source tree (containing Make.inc) |
| 10 | +# Env: TARGET_ARCH (x86_64 | aarch64) — defaults to native |
| 11 | +# BUILD_JOBS — make parallelism (default 4) |
| 12 | +set -euo pipefail |
| 13 | +SRC="${1:?Julia source tree path required}" |
| 14 | +TARGET_ARCH="${TARGET_ARCH:-$(uname -m)}" |
| 15 | +BUILD_JOBS="${BUILD_JOBS:-4}" |
| 16 | + |
| 17 | +cd "$SRC" |
| 18 | + |
| 19 | +# JULIA_CPU_TARGET — multi-target string matching JuliaCI's official binary |
| 20 | +# builds (utilities/build_envs.sh). |
| 21 | +case "$TARGET_ARCH" in |
| 22 | + x86_64) |
| 23 | + JULIA_CPU_TARGET="generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1);x86-64-v4,-rdrnd,base(1)" |
| 24 | + ;; |
| 25 | + aarch64) |
| 26 | + # JuliaCI ships only "generic" for aarch64-linux. |
| 27 | + JULIA_CPU_TARGET="generic" |
| 28 | + ;; |
| 29 | + *) |
| 30 | + echo "Unsupported TARGET_ARCH=$TARGET_ARCH" >&2; exit 1 |
| 31 | + ;; |
| 32 | +esac |
| 33 | + |
| 34 | +cat > Make.user <<EOF |
| 35 | +USE_BINARYBUILDER=0 |
| 36 | +JULIA_CPU_TARGET=${JULIA_CPU_TARGET} |
| 37 | +DISABLE_LIBUNWIND:=1 |
| 38 | +EOF |
| 39 | +# HOSTCC must be a single token. Make.inc line 445 unconditionally does |
| 40 | +# `HOSTCC = \$(CC)` which then gets `-m\$(BINARY)` appended → HOSTCC becomes |
| 41 | +# the two-word "gcc -m64". libwhich.mk's recipe `\$(MAKE) -C scratch/... |
| 42 | +# CC="\$(HOSTCC)" libwhich` then expands to `make ... CC="gcc -m64" libwhich`. |
| 43 | +# Under BuildKit's docker build with -j>1, the sub-make's MAKEFLAGS-driven |
| 44 | +# re-tokenization splits this and a stray ARCH token ("x86_64" or "aarch64") |
| 45 | +# leaks in as a positional arg to gcc — `gcc: error: x86_64: linker input |
| 46 | +# file not found`. Setting HOSTCC in Make.user does NOT work because line |
| 47 | +# 445 overwrites it. The fix is to pass HOSTCC/HOSTCXX on the make command |
| 48 | +# line so they win over makefile assignments. |
| 49 | +MAKE_VARS=(HOSTCC=gcc HOSTCXX=g++) |
| 50 | + |
| 51 | +echo "=== Make.user ===" |
| 52 | +cat Make.user |
| 53 | +echo |
| 54 | +echo "=== Toolchain ===" |
| 55 | +set +o pipefail # SIGPIPE on `head -1` would otherwise abort under `set -e` |
| 56 | +gcc --version 2>/dev/null | head -1 || true |
| 57 | +g++ --version 2>/dev/null | head -1 || true |
| 58 | +gfortran --version 2>/dev/null | head -1 || true |
| 59 | +cmake --version 2>/dev/null | head -1 || true |
| 60 | +make --version 2>/dev/null | head -1 || true |
| 61 | +echo -n "glibc: "; ldd --version 2>&1 | head -1 || true |
| 62 | +set -o pipefail |
| 63 | +echo |
| 64 | + |
| 65 | +echo "=== Env vars Julia's Make.inc reads ===" |
| 66 | +for v in ARCH CC CXX FC HOSTCC HOSTCXX MARCH MCPU MTUNE CFLAGS CXXFLAGS \ |
| 67 | + CPPFLAGS LDFLAGS XC_HOST CROSS_TRIPLE CROSS_ROOT MAKEFLAGS MFLAGS; do |
| 68 | + eval "val=\${$v-<unset>}" |
| 69 | + printf ' %-15s = %s\n' "$v" "$val" |
| 70 | +done |
| 71 | +echo |
| 72 | +echo "=== Make's view (single-shot, no build) ===" |
| 73 | +# Print what Julia's outer make computes for the suspect vars without |
| 74 | +# actually building anything. Helps diagnose the libwhich x86_64 issue. |
| 75 | +make --no-print-directory print-CC print-HOSTCC print-CFLAGS \ |
| 76 | + print-MARCH print-MCPU print-MTUNE 2>&1 \ |
| 77 | + | head -20 || true |
| 78 | +echo |
| 79 | + |
| 80 | +# Apply DISABLE_LIBUNWIND fix patches in-place via python string replacement. |
| 81 | +# This is more robust than `patch` against minor line-number drift across |
| 82 | +# Julia versions (the same fix submitted as JuliaLang/julia#61899). When the |
| 83 | +# upstream Julia version baked in here already has the fix, the script no-ops. |
| 84 | +echo "=== Apply DISABLE_LIBUNWIND fix patches ===" |
| 85 | +python3 <<'PY' |
| 86 | +import re, sys |
| 87 | +
|
| 88 | +# Patch 1 — signals-unix.c: move signal_bt_data/_size declarations out of |
| 89 | +# the libunwind guard so they always exist. signal_listener uses them |
| 90 | +# unconditionally; with libunwind disabled they stay at zero. |
| 91 | +p = 'src/signals-unix.c' |
| 92 | +s = open(p).read() |
| 93 | +needle = ('#if !defined(JL_DISABLE_LIBUNWIND)\n\n' |
| 94 | + 'static jl_bt_element_t signal_bt_data[JL_MAX_BT_SIZE + 1];\n' |
| 95 | + 'static size_t signal_bt_size = 0;\n') |
| 96 | +replacement = ( |
| 97 | + '// polyglot patch (also JuliaLang/julia#61899): declare these\n' |
| 98 | + '// unconditionally — signal_listener uses them outside any libunwind guard.\n' |
| 99 | + 'static jl_bt_element_t signal_bt_data[JL_MAX_BT_SIZE + 1];\n' |
| 100 | + 'static size_t signal_bt_size = 0;\n\n' |
| 101 | + '#if !defined(JL_DISABLE_LIBUNWIND)\n\n' |
| 102 | +) |
| 103 | +if needle in s: |
| 104 | + open(p, 'w').write(s.replace(needle, replacement, 1)) |
| 105 | + print(f'patched {p}') |
| 106 | +elif 'polyglot patch (also JuliaLang/julia#61899)' in s or \ |
| 107 | + 'declare these\n// unconditionally' in s: |
| 108 | + print(f'{p} already patched, skipping') |
| 109 | +else: |
| 110 | + sys.exit(f'PATCH1 FAILED: needle not found in {p}') |
| 111 | +
|
| 112 | +# Patch 2 — stackwalk.c: short-circuit jl_simulate_longjmp under |
| 113 | +# JL_DISABLE_LIBUNWIND (bt_context_t becomes int, the function body's |
| 114 | +# uc_mcontext etc. don't compile). |
| 115 | +p = 'src/stackwalk.c' |
| 116 | +s = open(p).read() |
| 117 | +needle = ('int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT\n' |
| 118 | + '{\n' |
| 119 | + '#if (defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_))\n') |
| 120 | +replacement = ( |
| 121 | + 'int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT\n' |
| 122 | + '{\n' |
| 123 | + '#if defined(JL_DISABLE_LIBUNWIND)\n' |
| 124 | + ' (void)mctx; (void)c;\n' |
| 125 | + ' return 0;\n' |
| 126 | + '#elif (defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_))\n' |
| 127 | +) |
| 128 | +if needle in s: |
| 129 | + open(p, 'w').write(s.replace(needle, replacement, 1)) |
| 130 | + print(f'patched {p}') |
| 131 | +elif '#if defined(JL_DISABLE_LIBUNWIND)\n (void)mctx; (void)c;' in s: |
| 132 | + print(f'{p} already patched, skipping') |
| 133 | +else: |
| 134 | + sys.exit(f'PATCH2 FAILED: needle not found in {p}') |
| 135 | +PY |
| 136 | +echo |
| 137 | + |
| 138 | +echo "=== Build ===" |
| 139 | +date |
| 140 | +time make -j"$BUILD_JOBS" "${MAKE_VARS[@]}" |
| 141 | +date |
| 142 | +echo |
| 143 | + |
| 144 | +echo "=== Smoke test ===" |
| 145 | +./julia -e 'println("Julia ", VERSION, " — LLVM ", Base.libllvm_version); println("CPU ", Sys.CPU_NAME)' |
| 146 | + |
| 147 | +echo "=== libunwind check (must be empty) ===" |
| 148 | +if compgen -G "usr/lib/libunwind*" > /dev/null; then |
| 149 | + echo "ERROR: libunwind made it into usr/lib even with DISABLE_LIBUNWIND=1" >&2 |
| 150 | + ls usr/lib/libunwind* >&2 |
| 151 | + exit 1 |
| 152 | +fi |
| 153 | +echo "(no libunwind in usr/lib — good)" |
| 154 | + |
| 155 | +echo "=== Done ===" |
0 commit comments