Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

366 satır
9.6KB

  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # Orange Pi Plus 2E / Armbian Bookworm build helper for fm-rds-tx
  4. #
  5. # Goals:
  6. # - install build + runtime dependencies
  7. # - install libiio / Pluto-related userspace bits
  8. # - build fmrtx for Linux ARM
  9. # - collect binary + shared libraries into dist/orangepi/
  10. #
  11. # Notes:
  12. # - Linux Pluto build is libiio-first (`-tags pluto`).
  13. # - SoapySDR is optional fallback/debug tooling, not the primary Pluto path.
  14. # - Windows Pluto path remains separate and untouched by this script.
  15. #
  16. # Usage:
  17. # chmod +x scripts/orangepi-build-libiio.sh
  18. # ./scripts/orangepi-build-libiio.sh
  19. #
  20. # Optional env:
  21. # PREFIX=/opt/fm-rds-tx
  22. # DIST_DIR=dist/orangepi
  23. # GO_VERSION=1.22.12
  24. # SKIP_APT=1
  25. # SKIP_GO_INSTALL=1
  26. # BUILD_TAGS=pluto
  27. #
  28. # If you want to install the packaged result into a target directory:
  29. # PREFIX=/opt/fm-rds-tx ./scripts/orangepi-build-libiio.sh
  30. SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
  31. REPO_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
  32. DIST_DIR="${DIST_DIR:-${REPO_DIR}/dist/orangepi}"
  33. BUILD_DIR="${DIST_DIR}/build"
  34. RUNTIME_DIR="${DIST_DIR}/runtime"
  35. PREFIX="${PREFIX:-/opt/fm-rds-tx}"
  36. GO_VERSION="${GO_VERSION:-1.22.12}"
  37. BUILD_TAGS="${BUILD_TAGS:-pluto}"
  38. ARCH="$(dpkg --print-architecture 2>/dev/null || true)"
  39. log() {
  40. printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*"
  41. }
  42. need_cmd() {
  43. command -v "$1" >/dev/null 2>&1 || {
  44. echo "Missing required command: $1" >&2
  45. exit 1
  46. }
  47. }
  48. apt_install_if_missing() {
  49. local missing=()
  50. for pkg in "$@"; do
  51. if ! dpkg -s "$pkg" >/dev/null 2>&1; then
  52. missing+=("$pkg")
  53. fi
  54. done
  55. if ((${#missing[@]})); then
  56. log "Installing missing packages: ${missing[*]}"
  57. sudo apt-get install -y "${missing[@]}"
  58. fi
  59. }
  60. install_go() {
  61. if command -v go >/dev/null 2>&1; then
  62. log "Go already present: $(go version)"
  63. return
  64. fi
  65. if [[ "${SKIP_GO_INSTALL:-0}" == "1" ]]; then
  66. echo "go not found and SKIP_GO_INSTALL=1 set" >&2
  67. exit 1
  68. fi
  69. local go_arch
  70. case "$ARCH" in
  71. armhf) go_arch="armv6l" ;;
  72. arm64) go_arch="arm64" ;;
  73. amd64) go_arch="amd64" ;;
  74. *)
  75. echo "Unsupported architecture for automated Go install: $ARCH" >&2
  76. exit 1
  77. ;;
  78. esac
  79. local tarball="go${GO_VERSION}.linux-${go_arch}.tar.gz"
  80. local url="https://go.dev/dl/${tarball}"
  81. local tmp="/tmp/${tarball}"
  82. log "Installing Go ${GO_VERSION} for ${go_arch}"
  83. wget -O "$tmp" "$url"
  84. sudo rm -rf /usr/local/go
  85. sudo tar -C /usr/local -xzf "$tmp"
  86. export PATH="/usr/local/go/bin:${PATH}"
  87. if ! grep -q '/usr/local/go/bin' "$HOME/.profile" 2>/dev/null; then
  88. printf '\nexport PATH="/usr/local/go/bin:$PATH"\n' >> "$HOME/.profile"
  89. fi
  90. log "Go installed: $(/usr/local/go/bin/go version)"
  91. }
  92. resolve_lib() {
  93. local name="$1"
  94. local ldconfig_bin=""
  95. if command -v ldconfig >/dev/null 2>&1; then
  96. ldconfig_bin="$(command -v ldconfig)"
  97. elif [[ -x /sbin/ldconfig ]]; then
  98. ldconfig_bin="/sbin/ldconfig"
  99. elif [[ -x /usr/sbin/ldconfig ]]; then
  100. ldconfig_bin="/usr/sbin/ldconfig"
  101. fi
  102. if [[ -n "$ldconfig_bin" ]]; then
  103. "$ldconfig_bin" -p 2>/dev/null | awk -v lib="$name" '$1 == lib { print $NF; exit }'
  104. return 0
  105. fi
  106. find /lib /usr/lib /usr/local/lib -name "$name" 2>/dev/null | head -n 1
  107. }
  108. copy_lib_if_found() {
  109. local libname="$1"
  110. local path
  111. path="$(resolve_lib "$libname" || true)"
  112. if [[ -z "$path" ]]; then
  113. path="$(find /lib /usr/lib /usr/local/lib -name "$libname" 2>/dev/null | head -n 1 || true)"
  114. fi
  115. if [[ -n "$path" && -f "$path" ]]; then
  116. cp -Lv "$path" "$RUNTIME_DIR/lib/"
  117. else
  118. log "Library not found: $libname"
  119. fi
  120. }
  121. find_soapy_plugin_path() {
  122. local candidate=""
  123. if command -v SoapySDRUtil >/dev/null 2>&1; then
  124. candidate="$(SoapySDRUtil --info 2>/dev/null | awk -F': ' '/Search path:/ {print $2; exit}')"
  125. if [[ -n "$candidate" && -d "$candidate" ]]; then
  126. printf '%s\n' "$candidate"
  127. return 0
  128. fi
  129. fi
  130. for candidate in \
  131. /usr/local/lib/SoapySDR/modules0.8-3 \
  132. /usr/local/lib/SoapySDR/modules0.8 \
  133. /usr/lib/arm-linux-gnueabihf/SoapySDR/modules0.8-3 \
  134. /usr/lib/arm-linux-gnueabihf/SoapySDR/modules0.8 \
  135. /usr/lib/SoapySDR/modules0.8-3 \
  136. /usr/lib/SoapySDR/modules0.8
  137. do
  138. if [[ -d "$candidate" ]]; then
  139. printf '%s\n' "$candidate"
  140. return 0
  141. fi
  142. done
  143. return 1
  144. }
  145. copy_soapy_plugins_if_found() {
  146. local plugin_path
  147. plugin_path="$(find_soapy_plugin_path || true)"
  148. if [[ -n "$plugin_path" && -d "$plugin_path" ]]; then
  149. mkdir -p "$RUNTIME_DIR/soapy-modules"
  150. cp -Lv "$plugin_path"/* "$RUNTIME_DIR/soapy-modules/" 2>/dev/null || true
  151. printf '%s\n' "$plugin_path" > "$DIST_DIR/SOAPY_PLUGIN_PATH.txt"
  152. log "Copied Soapy plugins from: $plugin_path"
  153. else
  154. log "Soapy plugin path not found"
  155. fi
  156. }
  157. write_runner() {
  158. cat > "${DIST_DIR}/run-fmrtx.sh" <<'EOF'
  159. #!/usr/bin/env bash
  160. set -euo pipefail
  161. SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
  162. SYSTEM_LIB_DIRS="/usr/local/lib:/usr/lib:/usr/lib/arm-linux-gnueabihf:/lib:/lib/arm-linux-gnueabihf"
  163. export LD_LIBRARY_PATH="${SCRIPT_DIR}/runtime/lib:${SYSTEM_LIB_DIRS}:${LD_LIBRARY_PATH:-}"
  164. if [[ -d "${SCRIPT_DIR}/runtime/soapy-modules" ]]; then
  165. export SOAPY_SDR_PLUGIN_PATH="${SCRIPT_DIR}/runtime/soapy-modules:${SOAPY_SDR_PLUGIN_PATH:-}"
  166. elif [[ -f "${SCRIPT_DIR}/SOAPY_PLUGIN_PATH.txt" ]]; then
  167. export SOAPY_SDR_PLUGIN_PATH="$(cat "${SCRIPT_DIR}/SOAPY_PLUGIN_PATH.txt"):${SOAPY_SDR_PLUGIN_PATH:-}"
  168. fi
  169. exec "${SCRIPT_DIR}/runtime/bin/fmrtx" "$@"
  170. EOF
  171. chmod +x "${DIST_DIR}/run-fmrtx.sh"
  172. }
  173. write_install_helper() {
  174. cat > "${DIST_DIR}/install.sh" <<EOF
  175. #!/usr/bin/env bash
  176. set -euo pipefail
  177. PREFIX="{1:-$PREFIX}"
  178. sudo mkdir -p "\$PREFIX/bin" "\$PREFIX/lib"
  179. sudo cp -v "${DIST_DIR}/runtime/bin/fmrtx" "\$PREFIX/bin/"
  180. sudo cp -v ${DIST_DIR}/runtime/lib/* "\$PREFIX/lib/" 2>/dev/null || true
  181. cat <<'EON'
  182. Installed.
  183. Run with e.g.:
  184. LD_LIBRARY_PATH=\$PREFIX/lib \$PREFIX/bin/fmrtx --help
  185. EON
  186. EOF
  187. # replace placeholder introduced to avoid accidental shell expansion confusion
  188. sed -i 's/{1:-/\${1:-/g' "${DIST_DIR}/install.sh"
  189. chmod +x "${DIST_DIR}/install.sh"
  190. }
  191. main() {
  192. need_cmd bash
  193. need_cmd uname
  194. need_cmd awk
  195. need_cmd sed
  196. need_cmd cp
  197. need_cmd mkdir
  198. need_cmd ldd
  199. mkdir -p "$BUILD_DIR" "$RUNTIME_DIR/bin" "$RUNTIME_DIR/lib"
  200. if [[ "${SKIP_APT:-0}" != "1" ]]; then
  201. log "Refreshing apt metadata"
  202. sudo apt-get update
  203. apt_install_if_missing \
  204. ca-certificates \
  205. curl \
  206. wget \
  207. git \
  208. build-essential \
  209. pkg-config \
  210. gcc \
  211. g++ \
  212. make \
  213. file \
  214. binutils \
  215. tar \
  216. xz-utils \
  217. libiio0 \
  218. libiio-dev \
  219. libusb-1.0-0 \
  220. libxml2 \
  221. libxml2-dev
  222. # Optional / best-effort packages. Not all repos expose them on every arch.
  223. sudo apt-get install -y soapysdr-tools libsoapysdr0.8 libsoapysdr-dev 2>/dev/null || true
  224. sudo apt-get install -y soapy-module-plutosdr 2>/dev/null || true
  225. sudo apt-get install -y iio-oscilloscope 2>/dev/null || true
  226. sudo apt-get install -y libusb-1.0-0-dev 2>/dev/null || true
  227. fi
  228. install_go
  229. need_cmd go
  230. export PATH="/usr/local/go/bin:${PATH}"
  231. export CGO_ENABLED=1
  232. export GOOS=linux
  233. case "$ARCH" in
  234. armhf)
  235. export GOARCH=arm
  236. export GOARM=7
  237. ;;
  238. arm64)
  239. export GOARCH=arm64
  240. ;;
  241. amd64)
  242. export GOARCH=amd64
  243. ;;
  244. *)
  245. echo "Unsupported architecture: $ARCH" >&2
  246. exit 1
  247. ;;
  248. esac
  249. log "Build environment"
  250. go version
  251. echo "ARCH=${ARCH} GOOS=${GOOS} GOARCH=${GOARCH:-} GOARM=${GOARM:-} CGO_ENABLED=${CGO_ENABLED}"
  252. log "Tidying modules"
  253. (cd "$REPO_DIR" && go mod tidy)
  254. log "Building fmrtx with Linux Pluto/libiio-first backend (tags: $BUILD_TAGS)"
  255. (cd "$REPO_DIR" && go build -v -tags "$BUILD_TAGS" -o "$RUNTIME_DIR/bin/fmrtx" ./cmd/fmrtx)
  256. log "Collecting runtime libraries"
  257. copy_lib_if_found "libiio.so.0"
  258. copy_lib_if_found "libSoapySDR.so.0.8"
  259. copy_lib_if_found "libusb-1.0.so.0"
  260. copy_lib_if_found "libxml2.so.2"
  261. copy_lib_if_found "libstdc++.so.6"
  262. copy_lib_if_found "libgcc_s.so.1"
  263. copy_lib_if_found "libm.so.6"
  264. copy_lib_if_found "libc.so.6"
  265. if [[ "$BUILD_TAGS" == *soapy* ]]; then
  266. copy_soapy_plugins_if_found
  267. else
  268. log "Skipping Soapy plugin copy (BUILD_TAGS=$BUILD_TAGS)"
  269. fi
  270. log "Writing helper scripts"
  271. write_runner
  272. write_install_helper
  273. log "Writing build manifest"
  274. cat > "${DIST_DIR}/BUILD-INFO.txt" <<EOF
  275. fm-rds-tx Orange Pi build
  276. ========================
  277. Date: $(date -Is)
  278. Host: $(uname -a)
  279. Repo: $REPO_DIR
  280. Dist: $DIST_DIR
  281. Architecture: $ARCH
  282. Go: $(go version)
  283. Build command:
  284. go build -tags $BUILD_TAGS -o runtime/bin/fmrtx ./cmd/fmrtx
  285. Important note:
  286. Linux Pluto builds should prefer the native libiio path (`-tags pluto`).
  287. SoapySDR is optional fallback/debug infrastructure only.
  288. Windows Pluto handling is separate and intentionally untouched by this script.
  289. EOF
  290. log "Binary info"
  291. file "$RUNTIME_DIR/bin/fmrtx" || true
  292. log "Dynamic dependencies of built binary"
  293. ldd "$RUNTIME_DIR/bin/fmrtx" || true
  294. if [[ "$BUILD_TAGS" == *soapy* ]]; then
  295. log "Wrapper self-test: fmrtx --list-devices"
  296. "${DIST_DIR}/run-fmrtx.sh" --list-devices || true
  297. else
  298. log "Wrapper self-test: fmrtx --print-config"
  299. "${DIST_DIR}/run-fmrtx.sh" --print-config || true
  300. fi
  301. log "Done. Artifacts are in: $DIST_DIR"
  302. cat <<EOF
  303. Next steps:
  304. 1. Copy/use: $DIST_DIR/runtime/bin/fmrtx
  305. 2. Libraries are in: $DIST_DIR/runtime/lib/
  306. 3. Launch via wrapper:
  307. $DIST_DIR/run-fmrtx.sh --help
  308. 4. Install to target prefix:
  309. $DIST_DIR/install.sh $PREFIX
  310. Reminder:
  311. Default Linux packaging now prefers the native libiio Pluto backend.
  312. Use BUILD_TAGS=soapy only when you explicitly want the Soapy path.
  313. Windows Pluto support is intentionally left on its separate path.
  314. EOF
  315. }
  316. main "$@"