#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DIST_DIR="${DIST_DIR:-$ROOT_DIR/dist}" MAIN_PKG="./cmd/sing-box" GO_BIN="${GO_BIN:-go}" CGO_ENABLED_VALUE="${CGO_ENABLED_VALUE:-0}" BUILD_JOBS="${GO_BUILD_JOBS:-}" DEFAULT_TARGETS=( "linux-amd64" "linux-arm64" "linux-armv7" "windows-amd64" "windows-arm64" "darwin-amd64" "darwin-arm64" ) usage() { cat <<'EOF' Usage: ./build.sh Build common targets ./build.sh all Build common targets ./build.sh linux-amd64 Build a single target ./build.sh linux-amd64 windows-amd64 ./build.sh current Build current host platform Environment variables: GO_BIN Go binary path, default: go DIST_DIR Output directory, default: ./dist CGO_ENABLED_VALUE CGO_ENABLED value, default: 0 GO_BUILD_JOBS Go build parallel jobs, default: detected CPU core count VERSION Embedded version string, default: git describe --tags --always BUILD_TAGS_OTHERS Override tags for non-Windows builds BUILD_TAGS_WINDOWS Override tags for Windows builds EOF } require_file() { local file="$1" if [[ ! -f "$file" ]]; then echo "Missing required file: $file" >&2 exit 1 fi } trim_file() { local file="$1" awk 'BEGIN{ORS=""} {gsub(/\r/, ""); print}' "$file" } resolve_version() { if [[ -n "${VERSION:-}" ]]; then printf '%s' "$VERSION" return fi if git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then git -C "$ROOT_DIR" describe --tags --always 2>/dev/null || git -C "$ROOT_DIR" rev-parse --short HEAD return fi printf '%s' "custom" } resolve_build_jobs() { if [[ -n "$BUILD_JOBS" ]]; then printf '%s' "$BUILD_JOBS" return fi if command -v nproc >/dev/null 2>&1; then nproc return fi if command -v getconf >/dev/null 2>&1; then getconf _NPROCESSORS_ONLN return fi printf '%s' "1" } build_target() { local target="$1" local goos goarch goarm="" output tags case "$target" in linux-amd64) goos="linux" goarch="amd64" output="$DIST_DIR/sing-box-linux-amd64" tags="$BUILD_TAGS_OTHERS" ;; linux-arm64) goos="linux" goarch="arm64" output="$DIST_DIR/sing-box-linux-arm64" tags="$BUILD_TAGS_OTHERS" ;; linux-armv7) goos="linux" goarch="arm" goarm="7" output="$DIST_DIR/sing-box-linux-armv7" tags="$BUILD_TAGS_OTHERS" ;; windows-amd64) goos="windows" goarch="amd64" output="$DIST_DIR/sing-box-windows-amd64.exe" tags="$BUILD_TAGS_WINDOWS" ;; windows-arm64) goos="windows" goarch="arm64" output="$DIST_DIR/sing-box-windows-arm64.exe" tags="$BUILD_TAGS_WINDOWS" ;; darwin-amd64) goos="darwin" goarch="amd64" output="$DIST_DIR/sing-box-darwin-amd64" tags="$BUILD_TAGS_OTHERS" ;; darwin-arm64) goos="darwin" goarch="arm64" output="$DIST_DIR/sing-box-darwin-arm64" tags="$BUILD_TAGS_OTHERS" ;; current) goos="$("$GO_BIN" env GOOS)" goarch="$("$GO_BIN" env GOARCH)" output="$DIST_DIR/sing-box-${goos}-${goarch}" if [[ "$goos" == "windows" ]]; then output="${output}.exe" tags="$BUILD_TAGS_WINDOWS" else tags="$BUILD_TAGS_OTHERS" fi ;; *) echo "Unsupported target: $target" >&2 exit 1 ;; esac echo "==> Building $target (jobs: $RESOLVED_BUILD_JOBS)" ( cd "$ROOT_DIR" export CGO_ENABLED="$CGO_ENABLED_VALUE" export GOOS="$goos" export GOARCH="$goarch" if [[ -n "$goarm" ]]; then export GOARM="$goarm" else unset GOARM 2>/dev/null || true fi "$GO_BIN" build -v -p "$RESOLVED_BUILD_JOBS" -trimpath \ -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$VERSION_VALUE' $LDFLAGS_SHARED -s -w -buildid=" \ -tags "$tags" \ -o "$output" \ "$MAIN_PKG" ) } if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage exit 0 fi require_file "$ROOT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS" require_file "$ROOT_DIR/release/DEFAULT_BUILD_TAGS_WINDOWS" require_file "$ROOT_DIR/release/LDFLAGS" if ! command -v "$GO_BIN" >/dev/null 2>&1; then echo "Go binary not found: $GO_BIN" >&2 exit 1 fi BUILD_TAGS_OTHERS="${BUILD_TAGS_OTHERS:-$(trim_file "$ROOT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS")}" BUILD_TAGS_WINDOWS="${BUILD_TAGS_WINDOWS:-$(trim_file "$ROOT_DIR/release/DEFAULT_BUILD_TAGS_WINDOWS")}" LDFLAGS_SHARED="$(trim_file "$ROOT_DIR/release/LDFLAGS")" VERSION_VALUE="$(resolve_version)" RESOLVED_BUILD_JOBS="$(resolve_build_jobs)" mkdir -p "$DIST_DIR" if [[ "$#" -eq 0 || "${1:-}" == "all" ]]; then TARGETS=("${DEFAULT_TARGETS[@]}") else TARGETS=("$@") fi for target in "${TARGETS[@]}"; do build_target "$target" done echo echo "Build completed." echo "Output directory: $DIST_DIR"