184 lines
11 KiB
Python
Executable File
184 lines
11 KiB
Python
Executable File
#!/usr/bin/env modbuild
|
|
|
|
# Olex2 - official open-source (BSD) Linux x86_64 distribution from OlexSys.
|
|
# https://www.olexsys.org/olex2/docs/getting-started/installing-olex2/
|
|
#
|
|
# Upstream ships NO installer and NOTHING to compile for Linux: a single
|
|
# olex2-linux64.zip that already contains
|
|
# olex2 GUI (wxWidgets) olex2c console/"headless" Olex2
|
|
# unirun vendor auto-updater (NOT used here) start vendor launcher (NOT used here)
|
|
# cctbx/ lib/ (private CPython 3.8 + libs) ilib/ util/ etc/ sample_data/ ...
|
|
#
|
|
# BUT: since 2026 OlexSys builds the Linux binaries on an Ubuntu 24.04-class
|
|
# toolchain: olex2c/olex2/unirun/NoSpherA2/libopenblas/cctbx *.so need
|
|
# GLIBC_2.38 + GLIBCXX_3.4.32, while RHEL9 has glibc 2.34 / GLIBCXX_3.4.29
|
|
# (RHEL8: 2.28 / 3.4.25). A newer glibc cannot be supplied via LD_LIBRARY_PATH
|
|
# (the dynamic loader is hard-wired into each ELF), so this recipe ships a
|
|
# private glibc 2.39 + GCC 14 libstdc++/libgcc under $PREFIX/sysroot (frozen
|
|
# Fedora 40 rpms from kojipkgs) and re-points the ELF executables at that
|
|
# loader with patchelf (--set-interpreter + DT_RPATH). GTK2/X11/GL still come
|
|
# from the host: older libs run fine on a newer glibc. The sysroot libc dir is
|
|
# NEVER put on LD_LIBRARY_PATH - that would crash every system tool Olex2 spawns.
|
|
#
|
|
# Default pbuild::prep downloads every file (urls: in files/config.yaml),
|
|
# checks the pinned sha256 (shasums:) and copies it into SRC_DIR (unpacker:
|
|
# none). configure unpacks, install copies the vendor tree 1:1 into $PREFIX
|
|
# (Olex2's BaseDir() is wherever the binaries sit) and adds $PREFIX/bin launchers.
|
|
#
|
|
# No errexit in modbuild hooks -> every critical step is die-guarded.
|
|
|
|
# files fetched by prep (names must match urls:/shasums: in files/config.yaml)
|
|
OLEX2_SYSROOT_RPMS=(
|
|
glibc-2.39-37.fc40.x86_64.rpm
|
|
libstdc++-14.2.1-3.fc40.x86_64.rpm
|
|
libgcc-14.2.1-3.fc40.x86_64.rpm
|
|
)
|
|
OLEX2_PATCHELF_TGZ="patchelf-0.18.0-x86_64.tar.gz"
|
|
# ELF executables in the vendor tree that get the bundled loader (static ones,
|
|
# e.g. hart, are detected and skipped)
|
|
OLEX2_PATCH_EXES=(olex2c olex2 unirun NoSpherA2 hart_mpi)
|
|
|
|
pbuild::configure() {
|
|
local -r zip="${SRC_DIR}/olex2-${V}-linux64.zip"
|
|
if [[ -f "${SRC_DIR}/olex2/olex2c" && -f "${SRC_DIR}/olex2/olex2" ]]; then
|
|
# re-run after a failed configure/install: prep's stamp is set, the
|
|
# tree is already unpacked - reuse it (the zip may be gone from SRC_DIR)
|
|
echo "olex2: reusing unpacked tree in ${SRC_DIR}/olex2"
|
|
else
|
|
[[ -s "${zip}" ]] || std::die 42 "olex2: ${zip} not found - prep did not fetch it. \
|
|
Pre-stage the archive by hand as <distdir>/olex2-${V}-linux64.zip (see README) and re-run (-f)."
|
|
command -v unzip >/dev/null || std::die 42 "olex2: 'unzip' not on PATH on this build host"
|
|
# partial downloads reused from distfiles are the classic failure: test the archive first
|
|
unzip -tq "${zip}" >/dev/null || \
|
|
std::die 42 "olex2: archive is corrupt/truncated - rm <distdir>/olex2-${V}-linux64.zip and re-run"
|
|
# ~1 GB unpacked (cctbx + python + sample data); SRC_DIR is under --tmpdir
|
|
unzip -q -o "${zip}" -d "${SRC_DIR}" || std::die 42 "olex2: unzip failed (disk/quota at --tmpdir?)"
|
|
[[ -f "${SRC_DIR}/olex2/olex2c" && -f "${SRC_DIR}/olex2/olex2" ]] || \
|
|
std::die 42 "olex2: unexpected archive layout - expected ${SRC_DIR}/olex2/{olex2,olex2c}"
|
|
rm -f "${zip}" # 255 MB copy; the distfiles cache keeps the original
|
|
fi
|
|
|
|
# --- private glibc 2.39 + libstdc++ 14 sysroot (Fedora 40 rpms) ----------
|
|
command -v rpm2cpio >/dev/null && command -v cpio >/dev/null || \
|
|
std::die 42 "olex2: need rpm2cpio and cpio on the build host (dnf install rpm cpio)"
|
|
local -r sysroot="${SRC_DIR}/sysroot"
|
|
local rpm
|
|
rm -rf "${sysroot}"
|
|
mkdir -p "${sysroot}"
|
|
for rpm in "${OLEX2_SYSROOT_RPMS[@]}"; do
|
|
[[ -s "${SRC_DIR}/${rpm}" ]] || std::die 42 "olex2: ${rpm} missing in SRC_DIR - prep did not fetch it"
|
|
( cd "${sysroot}" && rpm2cpio "${SRC_DIR}/${rpm}" | cpio -idm --quiet ) || \
|
|
std::die 42 "olex2: extracting ${rpm} failed"
|
|
done
|
|
# Fedora's glibc + libstdc++ install to /usr/lib64 but libgcc still to
|
|
# /lib64, which on a real system is the filesystem package's symlink to
|
|
# usr/lib64 - we do not extract that package, so recreate the merge here.
|
|
if [[ -d "${sysroot}/lib64" && ! -L "${sysroot}/lib64" ]]; then
|
|
mkdir -p "${sysroot}/usr/lib64"
|
|
cp -r --preserve=mode,timestamps,links "${sysroot}/lib64/." "${sysroot}/usr/lib64/" && \
|
|
rm -rf "${sysroot}/lib64" && ln -s usr/lib64 "${sysroot}/lib64" || \
|
|
std::die 42 "olex2: merging sysroot/lib64 into sysroot/usr/lib64 failed"
|
|
fi
|
|
local ld libc libdir
|
|
ld="$(find "${sysroot}" -type f -name 'ld-linux-x86-64.so.2' -print -quit)"
|
|
libc="$(find "${sysroot}" -type f -name 'libc.so.6' -print -quit)"
|
|
[[ -n "${ld}" && -n "${libc}" ]] || std::die 42 "olex2: ld-linux-x86-64.so.2 / libc.so.6 not found in the extracted glibc rpm"
|
|
libdir="$(dirname "${libc}")"
|
|
[[ -e "${libdir}/libstdc++.so.6" && -e "${libdir}/libgcc_s.so.1" ]] || \
|
|
std::die 42 "olex2: libstdc++.so.6 / libgcc_s.so.1 not next to libc.so.6 in ${libdir}"
|
|
# the loader is static: it must run on any x86_64 host
|
|
"${ld}" --version > "${SRC_DIR}/ldso.version" 2>&1 || \
|
|
std::die 42 "olex2: the bundled ld.so does not run on this host"
|
|
echo "olex2: bundled loader: $(head -n1 "${SRC_DIR}/ldso.version")"
|
|
|
|
# --- patchelf (static release binary) --------------------------------------
|
|
[[ -s "${SRC_DIR}/${OLEX2_PATCHELF_TGZ}" ]] || std::die 42 "olex2: ${OLEX2_PATCHELF_TGZ} missing in SRC_DIR - prep did not fetch it"
|
|
mkdir -p "${SRC_DIR}/patchelf"
|
|
tar -xzf "${SRC_DIR}/${OLEX2_PATCHELF_TGZ}" -C "${SRC_DIR}/patchelf" || std::die 42 "olex2: unpacking patchelf failed"
|
|
"${SRC_DIR}/patchelf/bin/patchelf" --version >/dev/null || std::die 42 "olex2: patchelf binary does not run on this host"
|
|
}
|
|
|
|
pbuild::compile() {
|
|
:
|
|
}
|
|
|
|
pbuild::install() {
|
|
local -r src="${SRC_DIR}/olex2"
|
|
local -r patchelf="${SRC_DIR}/patchelf/bin/patchelf"
|
|
|
|
# 1. vendor tree, flat into $PREFIX (= BaseDir). Slow on /opt/psi: 12k files.
|
|
# NOT `cp -a`: on /opt/psi chown is refused, so -a ends with thousands of
|
|
# "failed to preserve ownership ... Permission denied" and a non-zero exit
|
|
# (that killed the first build). Keep mode/timestamps/links only.
|
|
cp -r --preserve=mode,timestamps,links "${src}/." "${PREFIX}/" || \
|
|
std::die 42 "olex2: copying the vendor tree into ${PREFIX} failed"
|
|
|
|
# 2. private glibc/libstdc++ sysroot
|
|
rm -rf "${PREFIX}/sysroot"
|
|
cp -r --preserve=mode,timestamps,links "${SRC_DIR}/sysroot" "${PREFIX}/sysroot" || \
|
|
std::die 42 "olex2: copying the sysroot into ${PREFIX} failed"
|
|
|
|
# 3. zip loses nothing, but make sure the ELF binaries are executable and
|
|
# everything is world-readable on the shared install
|
|
chmod 755 "${PREFIX}"/{olex2,olex2c,unirun,start,NoSpherA2,hart,hart_mpi} 2>/dev/null || true
|
|
chmod -R a+rX "${PREFIX}" || true
|
|
|
|
# 4. never auto-update a shared, read-only install (vendor default is
|
|
# update=Always + ask_update=true, driven by unirun). Best effort - the
|
|
# launchers never call unirun anyway.
|
|
if [[ -f "${PREFIX}/usettings.dat" ]]; then
|
|
sed -i -e 's/^update=.*/update=Never/' -e 's/^ask_update=.*/ask_update=false/' \
|
|
"${PREFIX}/usettings.dat" || true
|
|
fi
|
|
|
|
# 5. re-point the ELF executables at the bundled loader. DT_RPATH (not
|
|
# RUNPATH, hence --force-rpath) so that everything dlopen()ed later by the
|
|
# embedded Python (cctbx *.so, lib-dynload, _ac7util) resolves libc,
|
|
# libstdc++, libopenblas ... through the same list. Host dirs (/lib64)
|
|
# follow via the loader's defaults + /etc/ld.so.cache.
|
|
local ld libdir rpath exe
|
|
ld="$(find "${PREFIX}/sysroot" -type f -name 'ld-linux-x86-64.so.2' -print -quit)"
|
|
libdir="$(dirname "$(find "${PREFIX}/sysroot" -type f -name 'libc.so.6' -print -quit)")"
|
|
[[ -x "${ld}" && -d "${libdir}" ]] || std::die 42 "olex2: sysroot loader/libdir not found under ${PREFIX}/sysroot"
|
|
rpath="${libdir}:${PREFIX}/lib:${PREFIX}/ilib:${PREFIX}/cctbx/cctbx_build/lib:${PREFIX}:\$ORIGIN"
|
|
for exe in "${OLEX2_PATCH_EXES[@]}"; do
|
|
[[ -f "${PREFIX}/${exe}" ]] || continue
|
|
if ! "${patchelf}" --print-interpreter "${PREFIX}/${exe}" >/dev/null 2>&1; then
|
|
echo "olex2: ${exe} is statically linked - left untouched"
|
|
continue
|
|
fi
|
|
"${patchelf}" --set-interpreter "${ld}" --force-rpath --set-rpath "${rpath}" "${PREFIX}/${exe}" || \
|
|
std::die 42 "olex2: patchelf failed on ${exe}"
|
|
echo "olex2: ${exe} -> interpreter ${ld}"
|
|
done
|
|
|
|
# 6. launchers: $PREFIX/bin/olex2 (GUI) and $PREFIX/bin/olex2c (console).
|
|
# Two copies of one script (it dispatches on basename $0, so no symlinks).
|
|
# Only $PREFIX/bin goes on PATH - the raw binaries and the vendor
|
|
# 'start' (auto-updater) stay off it.
|
|
mkdir -p "${PREFIX}/bin"
|
|
install -m 755 "${BUILDBLOCK_DIR}/files/olex2-launcher" "${PREFIX}/bin/olex2" || \
|
|
std::die 42 "olex2: installing bin/olex2 launcher failed"
|
|
install -m 755 "${BUILDBLOCK_DIR}/files/olex2-launcher" "${PREFIX}/bin/olex2c" || \
|
|
std::die 42 "olex2: installing bin/olex2c launcher failed"
|
|
|
|
# 7. prove the patched olex2c resolves through the bundled glibc. Missing
|
|
# HOST libs (gtk2, GL ...) are only a warning here: they must be
|
|
# installed on the execution hosts, the build host may differ.
|
|
"${ld}" --list "${PREFIX}/olex2c" > "${SRC_DIR}/olex2c.ldd" 2>&1 || \
|
|
std::die 42 "olex2: bundled loader cannot resolve olex2c - see ${SRC_DIR}/olex2c.ldd"
|
|
grep -q "${libdir}/libc.so.6" "${SRC_DIR}/olex2c.ldd" || \
|
|
std::die 42 "olex2: olex2c does not pick up the bundled glibc (see ${SRC_DIR}/olex2c.ldd) - patchelf step broken"
|
|
if grep -q 'not found' "${SRC_DIR}/olex2c.ldd"; then
|
|
echo "olex2: WARNING - host libraries missing on THIS host; dnf install them on the execution hosts:"
|
|
grep 'not found' "${SRC_DIR}/olex2c.ldd"
|
|
fi
|
|
|
|
# 8. what the modulefile / launchers rely on
|
|
local f
|
|
for f in olex2 olex2c cctbx/cctbx_build/lib lib/libpython3.8.so.1.0 lib/python3.8 util/pyUtil/initpy.py bin/olex2c sysroot; do
|
|
[[ -e "${PREFIX}/${f}" ]] || std::die 42 "olex2: ${PREFIX}/${f} missing after install - module would be broken"
|
|
done
|
|
echo "olex2: installed upstream build '$(cat "${PREFIX}/version.txt" 2>/dev/null)' (tag $(cat "${PREFIX}/olex2.tag" 2>/dev/null))"
|
|
}
|