522 lines
15 KiB
Bash
Executable File
522 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_DIR="$SCRIPT_DIR/sing-box-rr"
|
|
GO_BIN="${GO_BIN:-/usr/local/go/bin/go}"
|
|
GO_CACHE_DIR="${GO_CACHE_DIR:-/tmp/sing-box-rr-go-build}"
|
|
GO_MODCACHE_DIR="${GO_MODCACHE_DIR:-/tmp/sing-box-rr-go-mod}"
|
|
GO_PROXY_VALUE="${GOPROXY:-https://proxy.golang.org|direct}"
|
|
LD_LLD_BIN="${LD_LLD_BIN:-}"
|
|
UPX_BIN="${UPX_BIN:-upx}"
|
|
ENABLE_UPX="${ENABLE_UPX:-0}"
|
|
SDK_ARM64="/home/fresa/sdk/openwrt-sdk-25.12.5-mediatek-filogic_gcc-14.3.0_musl.Linux-x86_64"
|
|
SDK_X86="/home/fresa/sdk/openwrt-sdk-25.12.5-x86-64_gcc-14.3.0_musl.Linux-x86_64"
|
|
PACKAGES_DIR="$SCRIPT_DIR/packages/25.12"
|
|
PKG_NAME="sing-box-rr"
|
|
FORCE_REBUILD=0
|
|
|
|
LOG_DIR="$SCRIPT_DIR/build-logs"
|
|
mkdir -p "$LOG_DIR"
|
|
LOG_FILE="$LOG_DIR/build-apk-$(date +%Y%m%d-%H%M%S).log"
|
|
LOG_LATEST="$LOG_DIR/build-apk-latest.log"
|
|
: > "$LOG_FILE"
|
|
ln -sf "$(basename "$LOG_FILE")" "$LOG_LATEST"
|
|
ls -1t "$LOG_DIR"/build-apk-*.log 2>/dev/null | tail -n +11 | xargs -r rm -f
|
|
|
|
echo "=== Logging to $LOG_FILE ==="
|
|
|
|
run() { "$@" 2>&1 | tee -a "$LOG_FILE"; return "${PIPESTATUS[0]}"; }
|
|
|
|
project_version() {
|
|
if [ -f "$SCRIPT_DIR/VERSION" ]; then
|
|
cat "$SCRIPT_DIR/VERSION"
|
|
else
|
|
echo "0.0.0-unknown-r1"
|
|
fi
|
|
}
|
|
|
|
project_source_date_epoch() {
|
|
if [ -f "$SCRIPT_DIR/SOURCE_DATE_EPOCH" ]; then
|
|
cat "$SCRIPT_DIR/SOURCE_DATE_EPOCH"
|
|
else
|
|
date +%s
|
|
fi
|
|
}
|
|
|
|
project_version_base() {
|
|
project_version | sed -E 's/-r[0-9]+$//'
|
|
}
|
|
|
|
package_release() {
|
|
local release
|
|
release="$(project_version | sed -nE 's/^.*-r([0-9]+)$/\1/p' | head -n1)"
|
|
if [ -n "$release" ]; then
|
|
echo "$release"
|
|
else
|
|
echo "1"
|
|
fi
|
|
}
|
|
|
|
apk_version() {
|
|
local base version_tail apk_suffix
|
|
base="$(project_version_base | sed -nE 's/^([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p' | head -n1)"
|
|
[ -n "$base" ] || base="0.0.0"
|
|
apk_suffix="$(project_version_base | awk -F- '{
|
|
out = ""
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i ~ /^[0-9]+([.][0-9]+)*$/) {
|
|
if (out != "") {
|
|
out = out "."
|
|
}
|
|
out = out $i
|
|
}
|
|
}
|
|
print out
|
|
}')"
|
|
if [ -n "$apk_suffix" ]; then
|
|
echo "${base}.${apk_suffix}-r$(package_release)"
|
|
else
|
|
echo "${base}-r$(package_release)"
|
|
fi
|
|
}
|
|
|
|
build_tags() {
|
|
tr -d '\n' < "$PROJECT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS"
|
|
}
|
|
|
|
has_build_tag() {
|
|
printf '%s' "$(build_tags)" | tr ',' '\n' | grep -qx "$1"
|
|
}
|
|
|
|
uses_musl_toolchain() {
|
|
has_build_tag "with_musl"
|
|
}
|
|
|
|
prefers_lld_linker() {
|
|
has_build_tag "with_naive_outbound"
|
|
}
|
|
|
|
goarch_to_sdk_cc_name() {
|
|
case "$1" in
|
|
arm64) echo "aarch64-openwrt-linux-musl-gcc" ;;
|
|
amd64) echo "x86_64-openwrt-linux-musl-gcc" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
goarch_to_sdk_cxx_name() {
|
|
case "$1" in
|
|
arm64) echo "aarch64-openwrt-linux-musl-g++" ;;
|
|
amd64) echo "x86_64-openwrt-linux-musl-g++" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
find_toolchain_binary() {
|
|
local sdk_dir="$1" binary_name="$2"
|
|
find "$sdk_dir/staging_dir" -type f -path "*/bin/$binary_name" | head -n1
|
|
}
|
|
|
|
goarch_to_sdk_ld_bfd_name() {
|
|
case "$1" in
|
|
arm64) echo "aarch64-openwrt-linux-musl-ld.bfd" ;;
|
|
amd64) echo "x86_64-openwrt-linux-musl-ld.bfd" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
goarch_to_sdk_ld_name() {
|
|
case "$1" in
|
|
arm64) echo "aarch64-openwrt-linux-musl-ld" ;;
|
|
amd64) echo "x86_64-openwrt-linux-musl-ld" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
find_linker_binary() {
|
|
local sdk_dir="$1" goarch="$2"
|
|
local ld_bfd_name ld_name ld_bin
|
|
|
|
if [ -n "$LD_LLD_BIN" ]; then
|
|
if [ -x "$LD_LLD_BIN" ]; then
|
|
echo "$LD_LLD_BIN"
|
|
return 0
|
|
fi
|
|
echo "configured LD_LLD_BIN is not executable: $LD_LLD_BIN" | tee -a "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
ld_bfd_name="$(goarch_to_sdk_ld_bfd_name "$goarch")" || return 1
|
|
ld_name="$(goarch_to_sdk_ld_name "$goarch")" || return 1
|
|
|
|
if prefers_lld_linker; then
|
|
ld_bin="$(find_lld_binary "$sdk_dir")"
|
|
if [ -n "$ld_bin" ]; then
|
|
echo "$ld_bin"
|
|
return 0
|
|
fi
|
|
else
|
|
ld_bin="$(find_toolchain_binary "$sdk_dir" "$ld_bfd_name")"
|
|
if [ -n "$ld_bin" ]; then
|
|
echo "$ld_bin"
|
|
return 0
|
|
fi
|
|
|
|
ld_bin="$(find_toolchain_binary "$sdk_dir" "$ld_name")"
|
|
if [ -n "$ld_bin" ]; then
|
|
echo "$ld_bin"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if ! prefers_lld_linker; then
|
|
find_lld_binary "$sdk_dir"
|
|
return 0
|
|
fi
|
|
|
|
ld_bin="$(find_toolchain_binary "$sdk_dir" "$ld_bfd_name")"
|
|
if [ -n "$ld_bin" ]; then
|
|
echo "$ld_bin"
|
|
return 0
|
|
fi
|
|
|
|
find_toolchain_binary "$sdk_dir" "$ld_name"
|
|
}
|
|
|
|
find_lld_binary() {
|
|
local sdk_dir="$1"
|
|
local lld_bin
|
|
|
|
lld_bin="$(find_external_lld_binary)"
|
|
if [ -n "$lld_bin" ]; then
|
|
echo "$lld_bin"
|
|
return 0
|
|
fi
|
|
|
|
lld_bin="$(find "$sdk_dir/staging_dir/host" -maxdepth 3 -name 'ld.lld' | head -n1)"
|
|
if [ -n "$lld_bin" ]; then
|
|
echo "$lld_bin"
|
|
return 0
|
|
fi
|
|
find "$sdk_dir/staging_dir/host" -maxdepth 3 -type f -name 'lld' | head -n1
|
|
}
|
|
|
|
find_external_lld_binary() {
|
|
local candidate
|
|
for candidate in /tmp/lld*-download/pkg/usr/lib/llvm-*/bin/ld.lld /tmp/lld*-download/pkg/usr/lib/llvm-*/bin/lld; do
|
|
if [ -x "$candidate" ]; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
prepare_musl_toolchain() {
|
|
local sdk_dir="$1" goarch="$2" toolchain_dir="$3"
|
|
local cc_name cxx_name cc_bin cxx_bin ld_bin staging_dir
|
|
|
|
staging_dir="$sdk_dir/staging_dir"
|
|
cc_name="$(goarch_to_sdk_cc_name "$goarch")" || { echo "unsupported GOARCH for musl toolchain: $goarch" | tee -a "$LOG_FILE"; exit 1; }
|
|
cxx_name="$(goarch_to_sdk_cxx_name "$goarch")" || { echo "unsupported GOARCH for musl toolchain: $goarch" | tee -a "$LOG_FILE"; exit 1; }
|
|
cc_bin="$(find_toolchain_binary "$sdk_dir" "$cc_name")"
|
|
cxx_bin="$(find_toolchain_binary "$sdk_dir" "$cxx_name")"
|
|
ld_bin="$(find_linker_binary "$sdk_dir" "$goarch")"
|
|
[ -n "$cc_bin" ] || { echo "$cc_name not found under $staging_dir" | tee -a "$LOG_FILE"; exit 1; }
|
|
[ -n "$cxx_bin" ] || { echo "$cxx_name not found under $staging_dir" | tee -a "$LOG_FILE"; exit 1; }
|
|
[ -n "$ld_bin" ] || { echo "no suitable linker found under $sdk_dir/staging_dir" | tee -a "$LOG_FILE"; exit 1; }
|
|
|
|
mkdir -p "$toolchain_dir"
|
|
cat > "$toolchain_dir/ld" <<EOF
|
|
#!/bin/sh
|
|
exec '$ld_bin' "\$@"
|
|
EOF
|
|
|
|
cat > "$toolchain_dir/cc" <<EOF
|
|
#!/bin/sh
|
|
export STAGING_DIR='$staging_dir'
|
|
exec '$cc_bin' -B'$toolchain_dir' "\$@"
|
|
EOF
|
|
cat > "$toolchain_dir/cxx" <<EOF
|
|
#!/bin/sh
|
|
export STAGING_DIR='$staging_dir'
|
|
exec '$cxx_bin' -B'$toolchain_dir' "\$@"
|
|
EOF
|
|
chmod +x "$toolchain_dir/ld" "$toolchain_dir/cc" "$toolchain_dir/cxx"
|
|
}
|
|
|
|
build_ldflags() {
|
|
local base_ldflags
|
|
base_ldflags="$(tr -d '\n' < "$PROJECT_DIR/release/LDFLAGS")"
|
|
echo "${base_ldflags} -X github.com/sagernet/sing-box/constant.Version=$(project_version) -s -w"
|
|
}
|
|
|
|
go_toolchain_value() {
|
|
local required_version
|
|
|
|
if [ -n "${GOTOOLCHAIN:-}" ]; then
|
|
printf '%s\n' "$GOTOOLCHAIN"
|
|
return 0
|
|
fi
|
|
|
|
required_version="$(sed -nE 's/^go ([0-9]+([.][0-9]+){1,2}).*$/\1/p' "$PROJECT_DIR/go.mod" | head -n1)"
|
|
if [ -n "$required_version" ]; then
|
|
printf 'go%s+auto\n' "$required_version"
|
|
else
|
|
printf 'auto\n'
|
|
fi
|
|
}
|
|
|
|
require_tools() {
|
|
[ -x "$GO_BIN" ] || { echo "go not found at $GO_BIN" | tee -a "$LOG_FILE"; exit 1; }
|
|
if [ "$ENABLE_UPX" = 1 ]; then
|
|
command -v "$UPX_BIN" >/dev/null || { echo "upx not found: $UPX_BIN" | tee -a "$LOG_FILE"; exit 1; }
|
|
fi
|
|
}
|
|
|
|
reset_go_cache_if_needed() {
|
|
if [ "$FORCE_REBUILD" = 1 ]; then
|
|
chmod -R u+w "$GO_CACHE_DIR" "$GO_MODCACHE_DIR" 2>/dev/null || true
|
|
rm -rf "$GO_CACHE_DIR" "$GO_MODCACHE_DIR"
|
|
fi
|
|
mkdir -p "$GO_CACHE_DIR" "$GO_MODCACHE_DIR"
|
|
}
|
|
|
|
build_binary() {
|
|
local goarch="$1" output="$2" sdk_dir="$3" toolchain_dir="$4"
|
|
local tags ldflags gotoolchain
|
|
local -a env_cmd
|
|
|
|
tags="$(build_tags)"
|
|
ldflags="$(build_ldflags)"
|
|
gotoolchain="$(go_toolchain_value)"
|
|
env_cmd=(
|
|
env
|
|
"PATH=$(dirname "$GO_BIN"):$PATH"
|
|
"GOTOOLCHAIN=$gotoolchain"
|
|
"GOCACHE=$GO_CACHE_DIR"
|
|
"GOMODCACHE=$GO_MODCACHE_DIR"
|
|
"GOPROXY=$GO_PROXY_VALUE"
|
|
"GOOS=linux"
|
|
"GOARCH=$goarch"
|
|
)
|
|
if uses_musl_toolchain; then
|
|
prepare_musl_toolchain "$sdk_dir" "$goarch" "$toolchain_dir"
|
|
env_cmd+=(
|
|
"CGO_ENABLED=1"
|
|
"CC=$toolchain_dir/cc"
|
|
"CXX=$toolchain_dir/cxx"
|
|
)
|
|
else
|
|
env_cmd+=("CGO_ENABLED=0")
|
|
fi
|
|
if [ "$goarch" = "amd64" ]; then
|
|
env_cmd+=("GOAMD64=v1")
|
|
fi
|
|
|
|
echo "=== Building sing-box for GOARCH=$goarch ===" | tee -a "$LOG_FILE"
|
|
(
|
|
cd "$PROJECT_DIR"
|
|
run "${env_cmd[@]}" "$GO_BIN" build \
|
|
-buildvcs=false \
|
|
-trimpath \
|
|
-tags "$tags" \
|
|
-ldflags "$ldflags" \
|
|
-o "$output" \
|
|
./cmd/sing-box
|
|
)
|
|
|
|
if [ "$ENABLE_UPX" = 1 ]; then
|
|
# Experimental UPX compression for evaluating flash savings on routers.
|
|
echo "=== Experimental UPX compression (default) ===" | tee -a "$LOG_FILE"
|
|
run "$UPX_BIN" "$output"
|
|
fi
|
|
}
|
|
|
|
stage_common_tree() {
|
|
local root_dir="$1" binary_path="$2"
|
|
install -d "$root_dir/usr/bin" \
|
|
"$root_dir/usr/share/sing-box" \
|
|
"$root_dir/etc/sing-box" \
|
|
"$root_dir/etc/config" \
|
|
"$root_dir/etc/init.d" \
|
|
"$root_dir/lib/upgrade/keep.d" \
|
|
"$root_dir/lib/apk/packages"
|
|
install -m755 "$binary_path" "$root_dir/usr/bin/sing-box"
|
|
install -m644 "$PROJECT_DIR/release/config/config.json" "$root_dir/etc/sing-box/config.json"
|
|
install -m644 "$PROJECT_DIR/release/config/openwrt.conf" "$root_dir/etc/config/sing-box"
|
|
install -m755 "$PROJECT_DIR/release/config/openwrt.init" "$root_dir/etc/init.d/sing-box"
|
|
install -m644 "$PROJECT_DIR/release/config/openwrt.keep" "$root_dir/lib/upgrade/keep.d/sing-box"
|
|
}
|
|
|
|
stage_apk_metadata() {
|
|
local root_dir="$1"
|
|
local packages_meta="$root_dir/lib/apk/packages"
|
|
local list_file="$packages_meta/${PKG_NAME}.list"
|
|
local tmp_list_file
|
|
local conffiles_file="$packages_meta/${PKG_NAME}.conffiles"
|
|
local conffiles_static_file="$packages_meta/${PKG_NAME}.conffiles_static"
|
|
|
|
echo "sing-box=5566:sing-box=5566" > "$packages_meta/${PKG_NAME}.rusers"
|
|
tmp_list_file="$(mktemp /tmp/sing-box-rr-apk-list.XXXXXX)"
|
|
(
|
|
cd "$root_dir"
|
|
find . \( -type f -o -type l \) | sed 's|^\./|/|' | sort > "$tmp_list_file"
|
|
)
|
|
mv "$tmp_list_file" "$list_file"
|
|
cat > "$conffiles_file" <<'EOF'
|
|
/etc/config/sing-box
|
|
/etc/sing-box/config.json
|
|
EOF
|
|
while IFS= read -r conffile; do
|
|
[ -f "$root_dir$conffile" ] || continue
|
|
printf '%s %s\n' "$conffile" "$(sha256sum "$root_dir$conffile" | cut -d' ' -f1)"
|
|
done < "$conffiles_file" > "$conffiles_static_file"
|
|
}
|
|
|
|
prepare_apk_root() {
|
|
local apk_root_dir="$1"
|
|
|
|
mkdir -p "$apk_root_dir/etc"
|
|
cat > "$apk_root_dir/etc/passwd" <<'EOF'
|
|
root:x:0:0:root:/root:/sbin/nologin
|
|
EOF
|
|
cat > "$apk_root_dir/etc/group" <<'EOF'
|
|
root:x:0:root
|
|
EOF
|
|
}
|
|
|
|
package_apk() {
|
|
local pkg_root="$1" sdk_dir="$2" pkg_arch="$3" out_dir="$4"
|
|
local apk_bin apk_root_dir fakeroot_bin output_file source_epoch
|
|
|
|
apk_bin="$sdk_dir/staging_dir/host/bin/apk"
|
|
fakeroot_bin="$sdk_dir/staging_dir/host/bin/fakeroot"
|
|
[ -x "$apk_bin" ] || {
|
|
echo "apk host tool not found at $apk_bin" | tee -a "$LOG_FILE"
|
|
exit 1
|
|
}
|
|
[ -x "$fakeroot_bin" ] || {
|
|
echo "fakeroot host tool not found at $fakeroot_bin" | tee -a "$LOG_FILE"
|
|
exit 1
|
|
}
|
|
|
|
mkdir -p "$out_dir"
|
|
rm -f "$out_dir"/${PKG_NAME}-*.apk
|
|
apk_root_dir="$(mktemp -d /tmp/sing-box-rr-apk-root.XXXXXX)"
|
|
output_file="$out_dir/${PKG_NAME}-$(apk_version).apk"
|
|
source_epoch="$(project_source_date_epoch)"
|
|
prepare_apk_root "$apk_root_dir"
|
|
|
|
run env \
|
|
"SOURCE_DATE_EPOCH=$source_epoch" \
|
|
"STAGING_DIR_HOST=$sdk_dir/staging_dir/host" \
|
|
"$fakeroot_bin" sh -c '
|
|
set -e
|
|
pkg_dir="$1"
|
|
apk_root="$2"
|
|
apk_tool="$3"
|
|
output="$4"
|
|
arch="$5"
|
|
version="$6"
|
|
script="$7"
|
|
chown -R 0:0 "$pkg_dir"
|
|
"$apk_tool" --root "$apk_root" mkpkg \
|
|
--info "name:sing-box-rr" \
|
|
--info "version:$version" \
|
|
--info "description:RR Edition trimmed network-only sing-box fork for OpenWrt." \
|
|
--info "arch:$arch" \
|
|
--info "license:GPL-3.0-or-later" \
|
|
--info "origin:sing-box-rr-edition" \
|
|
--info "url:https://git.my-handbook.ru/fresa/sing-box-rr-edition" \
|
|
--info "maintainer:RR Edition" \
|
|
--info "provides:sing-box sing-box-tiny" \
|
|
--info "depends:ca-bundle kmod-inet-diag kmod-tun" \
|
|
--script "pre-deinstall:$script" \
|
|
--files "$pkg_dir" \
|
|
--output "$output"
|
|
' sh "$pkg_root" "$apk_root_dir" "$apk_bin" "$output_file" "$pkg_arch" "$(apk_version)" "$PROJECT_DIR/release/config/openwrt.prerm"
|
|
|
|
rm -rf "$apk_root_dir"
|
|
}
|
|
|
|
build_target() {
|
|
local label="$1" goarch="$2" pkg_arch="$3" sdk_dir="$4" out_dir="$5"
|
|
local temp_dir binary_path pkg_root toolchain_dir
|
|
|
|
temp_dir="$(mktemp -d /tmp/sing-box-rr-apk.XXXXXX)"
|
|
trap 'rm -rf "$temp_dir"' RETURN
|
|
binary_path="$temp_dir/sing-box"
|
|
pkg_root="$temp_dir/pkg"
|
|
toolchain_dir="$temp_dir/toolchain"
|
|
|
|
echo "=== Building $PKG_NAME $label for OpenWrt 25.12 ===" | tee -a "$LOG_FILE"
|
|
build_binary "$goarch" "$binary_path" "$sdk_dir" "$toolchain_dir"
|
|
stage_common_tree "$pkg_root" "$binary_path"
|
|
stage_apk_metadata "$pkg_root"
|
|
package_apk "$pkg_root" "$sdk_dir" "$pkg_arch" "$out_dir"
|
|
run ls -lh "$out_dir"/${PKG_NAME}-*.apk
|
|
|
|
trap - RETURN
|
|
rm -rf "$temp_dir"
|
|
}
|
|
|
|
build_arm64() {
|
|
[ -d "$SDK_ARM64" ] || { echo "arm64 SDK not found at $SDK_ARM64" | tee -a "$LOG_FILE"; exit 1; }
|
|
build_target "arm64" "arm64" "aarch64_cortex-a53" "$SDK_ARM64" "$PACKAGES_DIR/arm64"
|
|
}
|
|
|
|
build_x86() {
|
|
[ -d "$SDK_X86" ] || { echo "x86_64 SDK not found at $SDK_X86" | tee -a "$LOG_FILE"; exit 1; }
|
|
build_target "x86_64" "amd64" "x86_64" "$SDK_X86" "$PACKAGES_DIR/x86_64"
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
[ "$arg" = "--force" ] || [ "$arg" = "-f" ] && FORCE_REBUILD=1
|
|
done
|
|
args=()
|
|
for arg in "$@"; do
|
|
[ "$arg" != "--force" ] && [ "$arg" != "-f" ] && args+=("$arg")
|
|
done
|
|
set -- "${args[@]}"
|
|
|
|
require_tools
|
|
reset_go_cache_if_needed
|
|
|
|
case "${1:-arm64}" in
|
|
arm64)
|
|
build_arm64
|
|
;;
|
|
x86|x86_64)
|
|
build_x86
|
|
;;
|
|
all)
|
|
rm -rf "$PACKAGES_DIR/arm64" "$PACKAGES_DIR/x86_64" 2>/dev/null || true
|
|
build_arm64
|
|
build_x86
|
|
;;
|
|
*)
|
|
echo "Usage: $0 [arm64|x86|all] [--force|-f]"
|
|
echo " arm64 - build APK package for arm64 (default)"
|
|
echo " x86 - build APK package for x86_64"
|
|
echo " all - build APK packages for arm64 and x86_64"
|
|
echo ""
|
|
echo " --force|-f Rebuild with a clean Go cache"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "=== $PKG_NAME $(apk_version) ==="
|
|
ls -la "$PACKAGES_DIR"/arm64/*.apk "$PACKAGES_DIR"/x86_64/*.apk 2>/dev/null || true
|
|
|
|
echo ""
|
|
echo "=== Full log: $LOG_FILE ==="
|
|
ERRS=$(grep -E "error:|undefined reference|No such file|failed to build" "$LOG_FILE" \
|
|
| grep -v "ignored)" | head -20 || true)
|
|
if [ -n "$ERRS" ]; then
|
|
echo "=== Errors detected: ==="
|
|
echo "$ERRS"
|
|
fi
|