diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f70de19..8744255 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -81,11 +81,19 @@ jobs: PACKAGE_DIR="${ARTIFACT_DIR}/${VERSION}" BIN_DIR="${PACKAGE_DIR}/bin" LIB_DIR="${PACKAGE_DIR}/lib" + SCRIPT_DIR="${PACKAGE_DIR}/script" + SCRIPT_PACKAGE_DEB_DIR="${SCRIPT_DIR}/package/deb" SOURCE_LIB_LIST_FILE="build-release/lib-list.txt" + SOURCE_PREREQUISITE_ENTRY_FILE="build-release/prerequisite.sh" + SOURCE_PREREQUISITE_FILE="build-release/prerequisite-deb.sh" + SOURCE_PREREQUISITE_PACKAGE_LIST_FILE="build-release/package/deb/package-list.txt" LIB_LIST_FILE="${LIB_DIR}/lib-list.txt" + PREREQUISITE_ENTRY_FILE="${SCRIPT_DIR}/prerequisite.sh" + PREREQUISITE_FILE="${SCRIPT_DIR}/prerequisite-deb.sh" + PREREQUISITE_PACKAGE_LIST_FILE="${SCRIPT_PACKAGE_DEB_DIR}/package-list.txt" MANIFEST_FILE="${PACKAGE_DIR}/manifest.txt" rm -rf "${ARTIFACT_DIR}" - mkdir -p "${BIN_DIR}" "${LIB_DIR}" + mkdir -p "${BIN_DIR}" "${LIB_DIR}" "${SCRIPT_DIR}" "${SCRIPT_PACKAGE_DEB_DIR}" # Define supported binaries and their descriptions declare -A BIN_MAP @@ -110,6 +118,23 @@ jobs: fi done + # Derive runtime glibc floor from produced ELF binaries by scanning + # referenced GLIBC_* symbol versions and taking the maximum. + MIN_GLIBC="$( + find "${BIN_DIR}" -maxdepth 1 -type f -exec sh -c ' + for f in "$@"; do + readelf --version-info "$f" 2>/dev/null \ + | grep -oE "GLIBC_[0-9]+\.[0-9]+" \ + | sed "s/^GLIBC_//" + done + ' sh {} + | sort -V | tail -1 + )" + if [[ -z "${MIN_GLIBC}" ]]; then + echo "ERROR: unable to determine min_glibc from binaries in ${BIN_DIR}" + exit 1 + fi + echo "Detected min_glibc=${MIN_GLIBC}" + SUFFIX="${{ matrix.asset_suffix }}" ARCHIVE_NAME="mududb-${VERSION}-${SUFFIX}.tar.gz" ARCHIVE_URI="https://github.com/${GITHUB_REPOSITORY}/releases/download/${VERSION}/${ARCHIVE_NAME}" @@ -118,7 +143,24 @@ jobs: echo "ERROR: Dynamic library list not found at ${SOURCE_LIB_LIST_FILE}" exit 1 fi + if [[ ! -f "${SOURCE_PREREQUISITE_FILE}" ]]; then + echo "ERROR: Prerequisite script not found at ${SOURCE_PREREQUISITE_FILE}" + exit 1 + fi + if [[ ! -f "${SOURCE_PREREQUISITE_ENTRY_FILE}" ]]; then + echo "ERROR: Prerequisite entry script not found at ${SOURCE_PREREQUISITE_ENTRY_FILE}" + exit 1 + fi + if [[ ! -f "${SOURCE_PREREQUISITE_PACKAGE_LIST_FILE}" ]]; then + echo "ERROR: Prerequisite package-list not found at ${SOURCE_PREREQUISITE_PACKAGE_LIST_FILE}" + exit 1 + fi cp "${SOURCE_LIB_LIST_FILE}" "${LIB_LIST_FILE}" + cp "${SOURCE_PREREQUISITE_ENTRY_FILE}" "${PREREQUISITE_ENTRY_FILE}" + cp "${SOURCE_PREREQUISITE_FILE}" "${PREREQUISITE_FILE}" + cp "${SOURCE_PREREQUISITE_PACKAGE_LIST_FILE}" "${PREREQUISITE_PACKAGE_LIST_FILE}" + chmod +x "${PREREQUISITE_ENTRY_FILE}" + chmod +x "${PREREQUISITE_FILE}" { echo "name=mududb" @@ -129,6 +171,7 @@ jobs: echo "bin_dir=bin" echo "lib_dir=lib" echo "lib_list=lib/lib-list.txt" + echo "min_glibc=${MIN_GLIBC}" echo "" echo "[files]" find "${PACKAGE_DIR}" -type f -printf "%P\n" | sort diff --git a/build-release/package/deb/package-list.txt b/build-release/package/deb/package-list.txt new file mode 100644 index 0000000..0bff9de --- /dev/null +++ b/build-release/package/deb/package-list.txt @@ -0,0 +1,3 @@ +x86_64-unknown-linux-gnu https://archive.ubuntu.com/ubuntu/pool/main/libu/liburing/liburing2_2.5-1build1_amd64.deb +aarch64-unknown-linux-gnu https://ports.ubuntu.com/ubuntu-ports/pool/main/libu/liburing/liburing2_2.5-1build1_arm64.deb +armv7-unknown-linux-gnueabihf https://ports.ubuntu.com/ubuntu-ports/pool/main/libu/liburing/liburing2_2.5-1build1_armhf.deb diff --git a/build-release/prerequisite-deb.sh b/build-release/prerequisite-deb.sh new file mode 100644 index 0000000..6ef8e13 --- /dev/null +++ b/build-release/prerequisite-deb.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env sh +set -eu + +# Download deb prerequisites from a package list, then extract and install +# package contents into a specified installation directory. +usage() { + cat <<'EOF' +Usage: + prerequisite-deb.sh --install-dir [--package-list ] [--target ] + +Options: + --install-dir Destination directory to install files from deb packages + --package-list Path to package list file (default: script-adjacent package/deb/package-list.txt) + --target Optional Rust target triple used for filtering package-list items + +Package list format: + - Empty lines and lines starting with # are ignored. + - A line with one field is treated as a deb URL for all targets: + https://example.com/pkg.deb + - A line with two fields is treated as: + x86_64-unknown-linux-gnu https://example.com/pkg-amd64.deb +EOF +} + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "error: required command not found: $1" >&2 + exit 1 + fi +} + +TARGET="" +INSTALL_DIR="" +PACKAGE_LIST="" + +while [ "$#" -gt 0 ]; do + case "$1" in + --target) + TARGET="${2:-}" + shift 2 + ;; + --install-dir) + INSTALL_DIR="${2:-}" + shift 2 + ;; + --package-list) + PACKAGE_LIST="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [ -z "${INSTALL_DIR}" ]; then + echo "error: --install-dir is required" >&2 + usage >&2 + exit 1 +fi + +need_cmd curl +need_cmd dpkg-deb +need_cmd mktemp +need_cmd mkdir +need_cmd basename +need_cmd cp +need_cmd find +need_cmd readlink +need_cmd ln + +if [ -z "${PACKAGE_LIST}" ]; then + SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" + PACKAGE_LIST="${SCRIPT_DIR}/package/deb/package-list.txt" +fi + +if [ ! -f "${PACKAGE_LIST}" ]; then + echo "error: package list not found: ${PACKAGE_LIST}" >&2 + exit 1 +fi + +TMP_DIR="$(mktemp -d)" +cleanup() { + rm -rf "${TMP_DIR}" +} +trap cleanup EXIT INT TERM + +mkdir -p "${INSTALL_DIR}" +mkdir -p "${INSTALL_DIR}/lib" +installed_count="0" + +while IFS= read -r raw_line || [ -n "${raw_line}" ]; do + line="${raw_line#"${raw_line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + + [ -z "${line}" ] && continue + case "${line}" in + \#*) continue ;; + esac + + set -- ${line} + if [ "$#" -eq 1 ]; then + entry_target="" + entry_url="$1" + elif [ "$#" -eq 2 ]; then + entry_target="$1" + entry_url="$2" + else + echo "error: invalid package-list line: ${line}" >&2 + exit 1 + fi + + # Two-field line: , filter by --target when provided. + if [ -n "${entry_target}" ] && [ -n "${TARGET}" ] && [ "${entry_target}" != "${TARGET}" ]; then + continue + fi + if [ -n "${entry_target}" ] && [ -z "${TARGET}" ]; then + continue + fi + + pkg_basename="$(basename "${entry_url}")" + deb_path="${TMP_DIR}/${installed_count}-${pkg_basename}" + + echo "Downloading ${entry_url}" + curl -fL "${entry_url}" -o "${deb_path}" + dpkg-deb -x "${deb_path}" "${INSTALL_DIR}" + installed_count=$((installed_count + 1)) +done < "${PACKAGE_LIST}" + +if [ "${installed_count}" = "0" ]; then + echo "error: no deb packages installed from ${PACKAGE_LIST}" >&2 + if [ -n "${TARGET}" ]; then + echo "hint: check whether package-list contains entries for target ${TARGET}" >&2 + fi + exit 1 +fi + +# Keep the original deb filesystem layout, and also place shared libraries +# into /lib for simpler runtime linking. +find "${INSTALL_DIR}/usr/lib" -type f -name '*.so*' 2>/dev/null | while IFS= read -r so_file; do + cp -f "${so_file}" "${INSTALL_DIR}/lib/" +done + +# Recreate shared-library symlinks in /lib, pointing to the +# corresponding .so files within the same directory. +find "${INSTALL_DIR}/usr/lib" -type l -name '*.so*' 2>/dev/null | while IFS= read -r so_link; do + link_name="$(basename "${so_link}")" + link_target="$(readlink "${so_link}")" + target_name="$(basename "${link_target}")" + + # Skip if a non-symlink file already exists with the same name. + if [ -e "${INSTALL_DIR}/lib/${link_name}" ] && [ ! -L "${INSTALL_DIR}/lib/${link_name}" ]; then + continue + fi + + if [ -e "${INSTALL_DIR}/lib/${target_name}" ]; then + ln -sfn "${target_name}" "${INSTALL_DIR}/lib/${link_name}" + fi +done + +echo "Installed ${installed_count} deb package(s) to ${INSTALL_DIR}" diff --git a/build-release/prerequisite.sh b/build-release/prerequisite.sh new file mode 100644 index 0000000..12913b0 --- /dev/null +++ b/build-release/prerequisite.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env sh +set -eu + +usage() { + cat <<'EOF' +Usage: + prerequisite.sh --install-dir [--target ] [--package-list ] + +Description: + Platform dispatcher for prerequisite installation. + - Deb-supported Linux target: execute prerequisite-deb.sh + - Other platforms/targets: return error +EOF +} + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +DEB_SCRIPT="${SCRIPT_DIR}/prerequisite-deb.sh" +INSTALL_DIR="" + +parse_install_dir() { + prev="" + for arg in "$@"; do + if [ "${prev}" = "--install-dir" ]; then + INSTALL_DIR="${arg}" + return 0 + fi + prev="${arg}" + done + return 1 +} + +read_min_glibc_from_manifest() { + manifest_path="$1/manifest.txt" + if [ ! -f "${manifest_path}" ]; then + echo "error: manifest not found: ${manifest_path}" >&2 + return 1 + fi + awk -F= '/^min_glibc=/{print $2; exit}' "${manifest_path}" +} + +detect_host_glibc() { + if command -v getconf >/dev/null 2>&1; then + getconf GNU_LIBC_VERSION 2>/dev/null | awk '{print $2}' + return 0 + fi + if command -v ldd >/dev/null 2>&1; then + ldd --version 2>/dev/null | head -n1 | sed -E 's/.* ([0-9]+\.[0-9]+).*/\1/' + return 0 + fi + return 1 +} + +version_ge() { + required="$1" + actual="$2" + req_major="${required%%.*}" + req_minor="${required#*.}" + act_major="${actual%%.*}" + act_minor="${actual#*.}" + [ "${act_major}" -gt "${req_major}" ] && return 0 + [ "${act_major}" -lt "${req_major}" ] && return 1 + [ "${act_minor}" -ge "${req_minor}" ] +} + +check_min_glibc() { + required="$1" + actual="$(detect_host_glibc || true)" + if [ -z "${actual}" ]; then + echo "error: unable to detect host glibc version (requires getconf or ldd)" >&2 + return 1 + fi + if ! version_ge "${required}" "${actual}"; then + echo "error: host glibc ${actual} is lower than required ${required}" >&2 + return 1 + fi + echo "glibc check passed: host ${actual}, required >= ${required}" +} + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 0 +fi + +if ! parse_install_dir "$@"; then + echo "error: --install-dir is required" >&2 + usage >&2 + exit 1 +fi + +MIN_GLIBC="$(read_min_glibc_from_manifest "${INSTALL_DIR}" || true)" +if [ -n "${MIN_GLIBC}" ]; then + check_min_glibc "${MIN_GLIBC}" +fi + +OS="$(uname -s 2>/dev/null || echo unknown)" +TARGET_ARG="" +prev="" +for arg in "$@"; do + if [ "${prev}" = "--target" ]; then + TARGET_ARG="${arg}" + break + fi + prev="${arg}" +done + +is_deb_supported_target() { + case "$1" in + x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu|armv7-unknown-linux-gnueabihf) + return 0 + ;; + *) + return 1 + ;; + esac +} + +if [ "${OS}" != "Linux" ]; then + echo "error: platform ${OS} is not supported by prerequisite-deb.sh" >&2 + exit 1 +fi + +if [ -n "${TARGET_ARG}" ]; then + if ! is_deb_supported_target "${TARGET_ARG}"; then + echo "error: target ${TARGET_ARG} is not a supported deb platform" >&2 + exit 1 + fi +else + ARCH="$(uname -m 2>/dev/null || echo unknown)" + case "${ARCH}" in + x86_64|amd64) TARGET_ARG="x86_64-unknown-linux-gnu" ;; + aarch64|arm64) TARGET_ARG="aarch64-unknown-linux-gnu" ;; + armv7l|armv7) TARGET_ARG="armv7-unknown-linux-gnueabihf" ;; + *) + echo "error: host architecture ${ARCH} is not a supported deb platform; pass --target explicitly" >&2 + exit 1 + ;; + esac +fi + +if [ ! -x "${DEB_SCRIPT}" ]; then + echo "error: prerequisite deb script not found or not executable: ${DEB_SCRIPT}" >&2 + exit 1 +fi + +exec "${DEB_SCRIPT}" "$@"