#!/usr/bin/env bash set -Eeuo pipefail # Orange Pi Plus 2E / Armbian Bookworm build helper for fm-rds-tx # # Goals: # - install build + runtime dependencies # - install libiio / Pluto-related userspace bits # - build fmrtx for Linux ARM # - collect binary + shared libraries into dist/orangepi/ # # Notes: # - Linux Pluto build is libiio-first (`-tags pluto`). # - SoapySDR is optional fallback/debug tooling, not the primary Pluto path. # - Windows Pluto path remains separate and untouched by this script. # # Usage: # chmod +x scripts/orangepi-build-libiio.sh # ./scripts/orangepi-build-libiio.sh # # Optional env: # PREFIX=/opt/fm-rds-tx # DIST_DIR=dist/orangepi # GO_VERSION=1.22.12 # SKIP_APT=1 # SKIP_GO_INSTALL=1 # BUILD_TAGS=pluto # # If you want to install the packaged result into a target directory: # PREFIX=/opt/fm-rds-tx ./scripts/orangepi-build-libiio.sh SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" DIST_DIR="${DIST_DIR:-${REPO_DIR}/dist/orangepi}" BUILD_DIR="${DIST_DIR}/build" RUNTIME_DIR="${DIST_DIR}/runtime" PREFIX="${PREFIX:-/opt/fm-rds-tx}" GO_VERSION="${GO_VERSION:-1.22.12}" BUILD_TAGS="${BUILD_TAGS:-pluto}" ARCH="$(dpkg --print-architecture 2>/dev/null || true)" log() { printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*" } need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2 exit 1 } } apt_install_if_missing() { local missing=() for pkg in "$@"; do if ! dpkg -s "$pkg" >/dev/null 2>&1; then missing+=("$pkg") fi done if ((${#missing[@]})); then log "Installing missing packages: ${missing[*]}" sudo apt-get install -y "${missing[@]}" fi } install_go() { if command -v go >/dev/null 2>&1; then log "Go already present: $(go version)" return fi if [[ "${SKIP_GO_INSTALL:-0}" == "1" ]]; then echo "go not found and SKIP_GO_INSTALL=1 set" >&2 exit 1 fi local go_arch case "$ARCH" in armhf) go_arch="armv6l" ;; arm64) go_arch="arm64" ;; amd64) go_arch="amd64" ;; *) echo "Unsupported architecture for automated Go install: $ARCH" >&2 exit 1 ;; esac local tarball="go${GO_VERSION}.linux-${go_arch}.tar.gz" local url="https://go.dev/dl/${tarball}" local tmp="/tmp/${tarball}" log "Installing Go ${GO_VERSION} for ${go_arch}" wget -O "$tmp" "$url" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf "$tmp" export PATH="/usr/local/go/bin:${PATH}" if ! grep -q '/usr/local/go/bin' "$HOME/.profile" 2>/dev/null; then printf '\nexport PATH="/usr/local/go/bin:$PATH"\n' >> "$HOME/.profile" fi log "Go installed: $(/usr/local/go/bin/go version)" } resolve_lib() { local name="$1" local ldconfig_bin="" if command -v ldconfig >/dev/null 2>&1; then ldconfig_bin="$(command -v ldconfig)" elif [[ -x /sbin/ldconfig ]]; then ldconfig_bin="/sbin/ldconfig" elif [[ -x /usr/sbin/ldconfig ]]; then ldconfig_bin="/usr/sbin/ldconfig" fi if [[ -n "$ldconfig_bin" ]]; then "$ldconfig_bin" -p 2>/dev/null | awk -v lib="$name" '$1 == lib { print $NF; exit }' return 0 fi find /lib /usr/lib /usr/local/lib -name "$name" 2>/dev/null | head -n 1 } copy_lib_if_found() { local libname="$1" local path path="$(resolve_lib "$libname" || true)" if [[ -z "$path" ]]; then path="$(find /lib /usr/lib /usr/local/lib -name "$libname" 2>/dev/null | head -n 1 || true)" fi if [[ -n "$path" && -f "$path" ]]; then cp -Lv "$path" "$RUNTIME_DIR/lib/" else log "Library not found: $libname" fi } find_soapy_plugin_path() { local candidate="" if command -v SoapySDRUtil >/dev/null 2>&1; then candidate="$(SoapySDRUtil --info 2>/dev/null | awk -F': ' '/Search path:/ {print $2; exit}')" if [[ -n "$candidate" && -d "$candidate" ]]; then printf '%s\n' "$candidate" return 0 fi fi for candidate in \ /usr/local/lib/SoapySDR/modules0.8-3 \ /usr/local/lib/SoapySDR/modules0.8 \ /usr/lib/arm-linux-gnueabihf/SoapySDR/modules0.8-3 \ /usr/lib/arm-linux-gnueabihf/SoapySDR/modules0.8 \ /usr/lib/SoapySDR/modules0.8-3 \ /usr/lib/SoapySDR/modules0.8 do if [[ -d "$candidate" ]]; then printf '%s\n' "$candidate" return 0 fi done return 1 } copy_soapy_plugins_if_found() { local plugin_path plugin_path="$(find_soapy_plugin_path || true)" if [[ -n "$plugin_path" && -d "$plugin_path" ]]; then mkdir -p "$RUNTIME_DIR/soapy-modules" cp -Lv "$plugin_path"/* "$RUNTIME_DIR/soapy-modules/" 2>/dev/null || true printf '%s\n' "$plugin_path" > "$DIST_DIR/SOAPY_PLUGIN_PATH.txt" log "Copied Soapy plugins from: $plugin_path" else log "Soapy plugin path not found" fi } write_runner() { cat > "${DIST_DIR}/run-fmrtx.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SYSTEM_LIB_DIRS="/usr/local/lib:/usr/lib:/usr/lib/arm-linux-gnueabihf:/lib:/lib/arm-linux-gnueabihf" export LD_LIBRARY_PATH="${SCRIPT_DIR}/runtime/lib:${SYSTEM_LIB_DIRS}:${LD_LIBRARY_PATH:-}" if [[ -d "${SCRIPT_DIR}/runtime/soapy-modules" ]]; then export SOAPY_SDR_PLUGIN_PATH="${SCRIPT_DIR}/runtime/soapy-modules:${SOAPY_SDR_PLUGIN_PATH:-}" elif [[ -f "${SCRIPT_DIR}/SOAPY_PLUGIN_PATH.txt" ]]; then export SOAPY_SDR_PLUGIN_PATH="$(cat "${SCRIPT_DIR}/SOAPY_PLUGIN_PATH.txt"):${SOAPY_SDR_PLUGIN_PATH:-}" fi exec "${SCRIPT_DIR}/runtime/bin/fmrtx" "$@" EOF chmod +x "${DIST_DIR}/run-fmrtx.sh" } write_install_helper() { cat > "${DIST_DIR}/install.sh" </dev/null || true cat <<'EON' Installed. Run with e.g.: LD_LIBRARY_PATH=\$PREFIX/lib \$PREFIX/bin/fmrtx --help EON EOF # replace placeholder introduced to avoid accidental shell expansion confusion sed -i 's/{1:-/\${1:-/g' "${DIST_DIR}/install.sh" chmod +x "${DIST_DIR}/install.sh" } main() { need_cmd bash need_cmd uname need_cmd awk need_cmd sed need_cmd cp need_cmd mkdir need_cmd ldd mkdir -p "$BUILD_DIR" "$RUNTIME_DIR/bin" "$RUNTIME_DIR/lib" if [[ "${SKIP_APT:-0}" != "1" ]]; then log "Refreshing apt metadata" sudo apt-get update apt_install_if_missing \ ca-certificates \ curl \ wget \ git \ build-essential \ pkg-config \ gcc \ g++ \ make \ file \ binutils \ tar \ xz-utils \ libiio0 \ libiio-dev \ libusb-1.0-0 \ libxml2 \ libxml2-dev # Optional / best-effort packages. Not all repos expose them on every arch. sudo apt-get install -y soapysdr-tools libsoapysdr0.8 libsoapysdr-dev 2>/dev/null || true sudo apt-get install -y soapy-module-plutosdr 2>/dev/null || true sudo apt-get install -y iio-oscilloscope 2>/dev/null || true sudo apt-get install -y libusb-1.0-0-dev 2>/dev/null || true fi install_go need_cmd go export PATH="/usr/local/go/bin:${PATH}" export CGO_ENABLED=1 export GOOS=linux case "$ARCH" in armhf) export GOARCH=arm export GOARM=7 ;; arm64) export GOARCH=arm64 ;; amd64) export GOARCH=amd64 ;; *) echo "Unsupported architecture: $ARCH" >&2 exit 1 ;; esac log "Build environment" go version echo "ARCH=${ARCH} GOOS=${GOOS} GOARCH=${GOARCH:-} GOARM=${GOARM:-} CGO_ENABLED=${CGO_ENABLED}" log "Tidying modules" (cd "$REPO_DIR" && go mod tidy) log "Building fmrtx with Linux Pluto/libiio-first backend (tags: $BUILD_TAGS)" (cd "$REPO_DIR" && go build -v -tags "$BUILD_TAGS" -o "$RUNTIME_DIR/bin/fmrtx" ./cmd/fmrtx) log "Collecting runtime libraries" copy_lib_if_found "libiio.so.0" copy_lib_if_found "libSoapySDR.so.0.8" copy_lib_if_found "libusb-1.0.so.0" copy_lib_if_found "libxml2.so.2" copy_lib_if_found "libstdc++.so.6" copy_lib_if_found "libgcc_s.so.1" copy_lib_if_found "libm.so.6" copy_lib_if_found "libc.so.6" if [[ "$BUILD_TAGS" == *soapy* ]]; then copy_soapy_plugins_if_found else log "Skipping Soapy plugin copy (BUILD_TAGS=$BUILD_TAGS)" fi log "Writing helper scripts" write_runner write_install_helper log "Writing build manifest" cat > "${DIST_DIR}/BUILD-INFO.txt" <