#!/usr/bin/env bash # libwai for C on Linux: the static library, wai.h and the pkg-config file wai.pc, for one target. # # bash wai/quickstart/build-libwai.sh [--target ] [--version ] # # Builds wai/wai-rs's library, release and --locked, with the feature native_sink alone: the C # interface of the envelope (pack, unpack and select, the WAI2 rendition table, an integer # payload's declared cost) and of the native sink (extensions/native-sink.md ยง9), the interface the # Swift package's WaiFFI.xcframework and the Android library carry. Pure Rust: it needs nothing but # the C runtime. wai.h also declares the codec wrappers (src/ffi_codecs.rs, feature codecs) and the # neural decoders (src/ffi_neural.rs, feature neural), which need codec and model libraries this # library does not hold: its README says so, and the library is held below to exactly the # functions of src/ffi.rs and src/ffi_sink.rs. # # The library is static, lib/libwai.a, for a glibc target (x86_64- or aarch64-unknown-linux-gnu), # built without crt-static. A program that links it carries it: nothing of libwai is looked for at # run time, and the C runtime's symbols bind when the program is linked, to the glibc it is linked # against. wai.pc names it with the system libraries it needs (rustc's native-static-libs), so # `pkg-config --libs wai` links it as it is; its prefix is the directory it is in (${pcfiledir}), # so the archive works wherever it is unpacked. # # Writes /libwai--.tar.gz holding include/wai.h, lib/libwai.a, # lib/pkgconfig/wai.pc, README.md and LICENSE, with .sha256 beside it. Before it packages: # - the header the build generates (build.rs regenerates include/wai.h) is the committed one, # byte for byte, so the archive's header is the repository's (run in a fresh target directory, # as a release does, so the build script runs); # - the header declares nothing but wai_* and WAI_* names, and every function it declares is one # of the five files of the C interface; the library defines exactly the wai_* functions of # src/ffi.rs and src/ffi_sink.rs (wai/quickstart/libwai_interface.py). # Where the target is this machine's, wai/quickstart/test/c-library.sh then compiles a C program # against the archive through pkg-config, links it and runs it. set -euo pipefail usage() { echo "usage: bash wai/quickstart/build-libwai.sh [--target ] [--version ]" >&2; exit 2; } die() { echo "build-libwai.sh: $*" >&2; exit 1; } [ $# -ge 1 ] || usage ROOT=$(cd "$(dirname "$0")/../.." && pwd) RS="$ROOT/wai/wai-rs" OUT=$(mkdir -p "$1" && cd "$1" && pwd); shift TRIPLE="" VERSION="" while [ $# -gt 0 ]; do case "$1" in --target) TRIPLE="$2"; shift 2 ;; --version) VERSION="$2"; shift 2 ;; *) usage ;; esac done HOST=$(rustc -vV | sed -n 's/^host: //p') TRIPLE=${TRIPLE:-$HOST} case "$TRIPLE" in x86_64-unknown-linux-gnu | aarch64-unknown-linux-gnu) ;; *) echo "build-libwai.sh: $TRIPLE: the archive is built for x86_64- or aarch64-unknown-linux-gnu" >&2; exit 2 ;; esac case "${RUSTFLAGS:-} ${CARGO_ENCODED_RUSTFLAGS:-}" in *crt-static*) die "RUSTFLAGS asks for crt-static: libwai.a is built against the dynamic C runtime" ;; esac VERSION=${VERSION:-$(sed -n 's/^version = "\(.*\)"$/\1/p' "$RS/Cargo.toml" | head -1)} NAME="libwai-$VERSION-$TRIPLE" FEATURES=(--no-default-features --features native_sink) # A target directory of its own: wai also builds as a cdylib, so its rlib's file name carries no # hash, and another feature set of the crate in the same directory would overwrite this one. TD="${CARGO_TARGET_DIR:-$RS/target}/libwai-c" STAGE=$(mktemp -d) trap 'rm -rf "$STAGE"' EXIT cp "$RS/include/wai.h" "$STAGE/wai.h.committed" # rustc names the system libraries a static library needs when it links one # (--print=native-static-libs); a build that compiles nothing prints nothing, so then the crate is # compiled again. build() { ( cd "$RS" && CARGO_TARGET_DIR="$TD" cargo rustc --locked --release --lib "${FEATURES[@]}" --target "$TRIPLE" \ -- --print=native-static-libs ) 2>&1 | tee "$STAGE/build.log" >&2 } native_libs() { sed -n 's/^.*note: native-static-libs: //p' "$STAGE/build.log" | tail -1; } build || die "the build of libwai failed" NATIVE=$(native_libs) if [ -z "$NATIVE" ]; then ( cd "$RS" && CARGO_TARGET_DIR="$TD" cargo clean --release --target "$TRIPLE" -p wai ) >&2 build || die "the build of libwai failed" NATIVE=$(native_libs) fi [ -n "$NATIVE" ] || die "rustc named no native-static-libs for libwai.a" LIB="$TD/$TRIPLE/release/libwai.a" [ -f "$LIB" ] || die "no $LIB" cmp -s "$STAGE/wai.h.committed" "$RS/include/wai.h" || { diff -u "$STAGE/wai.h.committed" "$RS/include/wai.h" | head -60 >&2 || true die "the build regenerated wai/wai-rs/include/wai.h and it is not the committed header: commit the regenerated one" } D="$STAGE/$NAME" mkdir -p "$D/include" "$D/lib/pkgconfig" cp "$RS/include/wai.h" "$D/include/wai.h" # The library without its debugging sections (the standard library's among them): a program links # the same code from it, and the symbols it links by are kept. cp "$LIB" "$D/lib/libwai.a" strip --strip-debug "$D/lib/libwai.a" || die "strip could not take the debugging sections out of libwai.a" nm -g --defined-only "$D/lib/libwai.a" > "$STAGE/nm.txt" 2>/dev/null || die "nm could not read libwai.a" python3 -I "$ROOT/wai/quickstart/libwai_interface.py" --root "$ROOT" --nm "$STAGE/nm.txt" >&2 \ || die "the header or the library is not the C interface it should be" cat > "$D/lib/pkgconfig/wai.pc" < "$OUT/$NAME.tar.gz" ( cd "$OUT" && sha256sum "$NAME.tar.gz" > "$NAME.tar.gz.sha256" ) echo "packaged $OUT/$NAME.tar.gz ($(cut -d' ' -f1 "$OUT/$NAME.tar.gz.sha256"), $(du -h "$OUT/$NAME.tar.gz" | cut -f1)); wai.pc links: -lwai $NATIVE" if [ "$TRIPLE" = "$HOST" ]; then bash "$ROOT/wai/quickstart/test/c-library.sh" "$OUT/$NAME.tar.gz" fi