Notcurses-Native.git | scripts/ci/ | build-libunistring.sh edit


#!/usr/bin/env bash
# Build + install GNU libunistring — the Unicode string library
# notcurses uses for grapheme-cluster segmentation (unigbrk.h),
# word breaking (uniwbrk.h) and character property lookups
# (unictype.h). notcurses' CMakeLists.txt hard-requires it:
# `find_path(unigbrk.h)` + `find_library(unistring unistring REQUIRED)`,
# with a FATAL_ERROR if either misses.
#
# All four platforms source-build it now. It was the last
# package-manager-sourced LGPL library left in the shipped packs, and
# package-manager sourcing means we cannot point a user at the exact
# corresponding source for the binary we handed them: dnf/apk/brew/
# pacman all move their package versions under us and garbage-collect
# old builds, so "the source for the libunistring.so.5 in this
# archive" would have been unanswerable a few months after any given
# release. libunistring is LGPL-3.0-or-later OR GPL-2.0-or-later and
# we convey it under the LGPL, which obliges us to be able to hand
# over that source. A pinned tarball + recorded SHA-256 in
# resources/third-party.json makes that answerable for good:
#
#   * Linux manylinux_2_28 — was `dnf install libunistring-devel`
#     (0.9.9, so the packs shipped libunistring.so.2).
#   * Linux alpine 3.20 — was `apk add libunistring-dev`.
#   * macOS arm64 — was `brew install libunistring`. macOS x86_64
#     already source-built it here, because brew's x86_64 bottles
#     target macOS 14+ and would fail that lane's 10.15
#     deployment-target audit.
#   * Windows MSYS2 — was `pacman -S mingw-w64-*-libunistring`.
#
# Plain autotools, no unusual configure surface. Honours $PREFIX
# (default /usr/local) so the install can target a workspace-relative
# cache dir that actions/cache persists between runs, and honours
# $MACOSX_DEPLOYMENT_TARGET on macOS — libtool passes it through to
# clang, which stamps LC_BUILD_VERSION minos on the produced dylib.
#
# Platform notes:
#   * SONAME major is 5 for the whole 1.x series (lib/Makefile.am
#     pins -version-info 7:R:2, and 7 - 2 = 5), so the shipped file
#     is libunistring.so.5 / libunistring.5.dylib / libunistring-5.dll.
#     Bumping to a libunistring 2.x would change that, and the
#     belt-and-braces sweep list in .github/actions/bundle-dll plus
#     the basename patterns in resources/third-party.json both spell
#     the major out — grep for `libunistring-5` before bumping.
#   * MSYS2/mingw: libtool installs the DLL under $PREFIX/bin and the
#     import library under $PREFIX/lib, the same split ffmpeg's
#     mingw32 target uses. `find_library(unistring unistring)`
#     consumes $PREFIX/lib/libunistring.dll.a; bundle-dll picks the
#     DLL up out of $PREFIX/bin via its extra-search-path.
#   * iconv is left to configure's own detection on the POSIX lanes,
#     where glibc, musl and libSystem all provide it inside libc so
#     GNU libiconv never enters the picture. On MSYS2 there is no
#     iconv in the C runtime, AM_ICONV finds GNU libiconv, and
#     libunistring-5.dll ends up importing libiconv-2.dll — which is
#     why the Windows lanes now source-build libiconv too and this
#     script points AM_ICONV at it explicitly (see the
#     --with-libiconv-prefix block below). notcurses itself uses
#     unistr / unigbrk / unictype / uniwbrk and never touches
#     uniconv, so what libunistring does with iconv is a
#     redistribution question rather than a functional one.
set -euxo pipefail

# 1.4.2 — current stable.
VERSION='1.4.2'
# ftpmirror.gnu.org redirects to a random mirror and intermittently
# 502s; retry it, then fall back to the canonical (slower) host.
# Both spellings serve byte-identical tarballs — the SHA-256 recorded
# in resources/third-party.json is checked against whichever answered.
URL="https://ftpmirror.gnu.org/libunistring/libunistring-${VERSION}.tar.gz"
FALLBACK_URL="https://ftp.gnu.org/gnu/libunistring/libunistring-${VERSION}.tar.gz"
PREFIX="${PREFIX:-/usr/local}"
mkdir -p "$PREFIX"

# Portable parallelism: GNU nproc on Linux, sysctl on macOS, fall
# back to 4 if neither.
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"

cd /tmp
curl -fSL --retry 5 --retry-delay 10 -o libunistring.tar.gz "$URL" \
    || curl -fSL --retry 5 --retry-delay 10 -o libunistring.tar.gz "$FALLBACK_URL"
tar -xzf libunistring.tar.gz
cd "libunistring-${VERSION}"

# If a libiconv has been source-built into this prefix — which only
# happens on the Windows lanes, where build-libiconv.sh runs before
# this script — make AM_ICONV resolve THAT one rather than searching
# the compiler's default paths and finding the msystem's pacman copy
# under the identical basename. --with-libiconv-prefix is gnulib's
# own lever for this (AC_LIB_LINKFLAGS_BODY): it puts
# $PREFIX/include on the probe's -I list and $PREFIX/lib on its -L
# list, so both the header and the import library come from us.
#
# Conditional, and keyed on the header actually being there, so the
# POSIX lanes see a configure line byte-identical to the one they have
# always run: they have iconv in libc, nothing installs an iconv.h
# into their prefix, and pointing gnulib at a directory with no
# libiconv in it would only add a pointless probe.
iconv_flags=()
if [[ -f "$PREFIX/include/iconv.h" ]]; then
    iconv_flags+=( "--with-libiconv-prefix=$PREFIX" )
fi

# --enable-shared / --disable-static — bundleable .so / .dylib / .dll,
# matching every other library in the pack. libunistring has no NLS
# and no --disable-doc equivalent worth passing; the docs it installs
# are info/man pages under $PREFIX/share, which nothing bundles.
./configure \
    --prefix="$PREFIX" \
    --enable-shared \
    --disable-static \
    ${iconv_flags[@]+"${iconv_flags[@]}"}

make -j"$JOBS"
make install

# libunistring ships no pkg-config file, so there is no
# `pkg-config --modversion` self-check to make like the codec
# scripts do. Verify the two artefacts the consumers actually need
# instead, and fail loudly if either is missing — a silent
# half-install here surfaces ten minutes later as notcurses' cmake
# saying "Couldn't find unigbrk.h from GNU libunistring", with no
# hint that this script is where it went wrong.
missing=0
if [[ ! -f "$PREFIX/include/unigbrk.h" ]]; then
    echo "::error::libunistring install left no \$PREFIX/include/unigbrk.h"
    missing=1
fi
# The linkable artefact: libunistring.so.N / libunistring.N.dylib on
# POSIX, libunistring.dll.a (import lib, with the DLL in bin/) on
# mingw. `find` rather than a glob so an unmatched pattern is a
# count of 0 rather than a literal-string test.
if [[ $(find "$PREFIX/lib" -maxdepth 1 -name 'libunistring.*' | wc -l) -eq 0 ]]; then
    echo "::error::libunistring install left nothing matching \$PREFIX/lib/libunistring.*"
    missing=1
fi
if (( missing != 0 )); then
    echo "❌ libunistring ${VERSION} did not install into $PREFIX as expected."
    ls -la "$PREFIX/lib" "$PREFIX/include" 2>/dev/null || true
    exit 1
fi
echo "--- installed libunistring artefacts ---"
find "$PREFIX/lib" "$PREFIX/bin" -maxdepth 1 -name 'libunistring*' 2>/dev/null || true

cd /
rm -rf "/tmp/libunistring-${VERSION}" /tmp/libunistring.tar.gz