diff --git a/build-cthulhu.sh b/build-cthulhu.sh index e889b8c..e18e015 100755 --- a/build-cthulhu.sh +++ b/build-cthulhu.sh @@ -1,9 +1,132 @@ #!/usr/bin/env bash - +# Build both Cthulhu binaries without replacing the system Go installation. set -euo pipefail +projectDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -go build -o cthulhu ./cmd/cthulhu -go build -tags brlapi -o cthulhu-braille ./cmd/cthulhu-braille -echo "Cthulhu has been built." -exit 0 +fail() { + echo "Error: $*" >&2 + exit 1 +} + +usage() { + cat <<'EOF' +Usage: build-cthulhu.sh [-o DIR | --output-dir DIR] [-- GO_BUILD_FLAGS...] + +Build cthulhu and cthulhu-braille, obtaining a suitable Go toolchain if needed. + + -o, --output-dir DIR Write both binaries to DIR (default: checkout root). + Relative paths start at the caller's directory. + Missing directories are created. + -h, --help Show help without checking build dependencies. + -- Pass remaining flags to both go build commands. + +GOTOOLCHAIN defaults to auto. Set GOTOOLCHAIN=local to require installed Go +and prevent toolchain downloads. Other Go settings, compiler/linker flags, +and cache paths are inherited. CGO must be enabled (the default here). +If Go is absent or older than 1.21, the bootstrap toolchain is cached under +${XDG_CACHE_HOME:-$HOME/.cache}/cthulhu/toolchains; curl, tar and sha256sum +are needed only for this download. Installation belongs in the package recipe. +EOF +} + +outputDir=$projectDir +buildFlags=() +while (( $# )); do + case $1 in + -o|--output-dir) + (( $# >= 2 )) || fail "$1 requires a directory." + [[ -n $2 && $2 != -* ]] || fail "$1 requires a directory (use ./ for a name starting with '-')." + outputDir=$2 + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + buildFlags=("$@") + break + ;; + *) fail "Unknown argument: $1. See --help." ;; + esac +done +if [[ $outputDir != /* ]]; then + outputDir="$PWD/$outputDir" +fi +[[ ! -e $outputDir || -d $outputDir ]] || fail "Output directory is an existing file: $outputDir" +for binary in cthulhu cthulhu-braille; do + [[ ! -d $outputDir/$binary ]] || fail "Binary output path is a directory: $outputDir/$binary" +done +cd -- "$projectDir" + +[[ $(uname -s) == Linux ]] || fail "Cthulhu requires Linux." +export CGO_ENABLED=${CGO_ENABLED:-1} +[[ $CGO_ENABLED == 1 ]] || fail "Cthulhu requires CGO_ENABLED=1 and a C compiler." +command -v pkg-config >/dev/null || fail "Install pkg-config and the development libraries listed in CONTRIBUTING.md." +missingLibraries=() +for library in gtk+-3.0 atspi-2 gio-2.0 gstreamer-1.0 x11 xtst xfixes xres xkbcommon liblouis; do + if ! pkg-config --exists "$library"; then + missingLibraries+=("$library") + fi +done +if (( ${#missingLibraries[@]} )); then + fail "Missing development libraries (pkg-config names): ${missingLibraries[*]}. See CONTRIBUTING.md; BRLTTY/BrlAPI headers and library are also required." +fi + +requiredVersion=$(awk '$1 == "go" { print $2; exit }' go.mod) +[[ $requiredVersion =~ ^1\.[0-9]+\.[0-9]+$ ]] || fail "Cannot read the required Go version from go.mod." +export GOTOOLCHAIN=${GOTOOLCHAIN:-auto} +goCommand="" +if command -v go >/dev/null; then + installedVersion=$(GOTOOLCHAIN=local go version 2>/dev/null || true) + if [[ $installedVersion =~ go1\.([0-9]+) ]] && (( 10#${BASH_REMATCH[1]} >= 21 )); then + goCommand=$(command -v go) + fi +fi + +if [[ -z $goCommand ]]; then + [[ $GOTOOLCHAIN == auto ]] || fail "Install Go $requiredVersion or newer, or use GOTOOLCHAIN=auto to allow bootstrapping." + case $(uname -m) in + x86_64) archiveArch=amd64 ;; + aarch64|arm64) archiveArch=arm64 ;; + armv6l|armv7l) archiveArch=armv6l ;; + i386|i486|i586|i686) archiveArch=386 ;; + ppc64le|riscv64|s390x|loong64) archiveArch=$(uname -m) ;; + loongarch64) archiveArch=loong64 ;; + *) fail "No Go bootstrap archive configured for $(uname -m). Install Go 1.21 or newer." + esac + toolchainDir="${XDG_CACHE_HOME:-$HOME/.cache}/cthulhu/toolchains/go${requiredVersion}.linux-${archiveArch}" + goCommand="$toolchainDir/go/bin/go" + if [[ ! -x $goCommand ]]; then + for dependency in curl tar sha256sum; do + command -v "$dependency" >/dev/null || fail "Install $dependency to bootstrap Go, or install Go $requiredVersion or newer." + done + mkdir -p -- "$(dirname -- "$toolchainDir")" + downloadDir=$(mktemp -d "${toolchainDir}.download.XXXXXX") + trap 'rm -rf -- "$downloadDir"' EXIT + archiveURL="https://dl.google.com/go/go${requiredVersion}.linux-${archiveArch}.tar.gz" + echo "Downloading Go $requiredVersion for Linux/$archiveArch..." + curlFlags=(--fail --location --silent --show-error --proto '=https' --proto-redir '=https') + expectedChecksum=$(curl "${curlFlags[@]}" "$archiveURL.sha256") + [[ $expectedChecksum =~ ^[[:xdigit:]]{64}$ ]] || fail "Invalid Go archive checksum." + curl "${curlFlags[@]}" --output "$downloadDir/go.tar.gz" "$archiveURL" + printf '%s %s\n' "$expectedChecksum" "$downloadDir/go.tar.gz" | sha256sum --check --status || fail "Go archive checksum mismatch." + tar -xzf "$downloadDir/go.tar.gz" -C "$downloadDir" + [[ -x $downloadDir/go/bin/go ]] || fail "The downloaded archive does not contain Go." + # Publish a complete tree; another build may have populated the cache. + if [[ ! -x $goCommand ]]; then + mkdir -p -- "$toolchainDir" + mv -T -- "$downloadDir/go" "$toolchainDir/go" || [[ -x $goCommand ]] + fi + fi + unset GOROOT +fi + +goVersion=$("$goCommand" version) || fail "The selected Go toolchain could not run." +echo "Using $goVersion" +mkdir -p -- "$outputDir" +"$goCommand" build "${buildFlags[@]}" -o "$outputDir/cthulhu" ./cmd/cthulhu +"$goCommand" build "${buildFlags[@]}" -tags brlapi -o "$outputDir/cthulhu-braille" ./cmd/cthulhu-braille +echo "Cthulhu has been built: $outputDir/cthulhu and $outputDir/cthulhu-braille"