From 6f3f068ed6681bfe0a27ce87200cd498b68689a9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 7 Feb 2026 20:45:23 +0100 Subject: [PATCH] chore(build): make build script portable and configurable --- .github/workflows/ci.yml | 4 + README.md | 21 ++- build.sh | 10 +- scripts/build.sh | 327 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 355 insertions(+), 7 deletions(-) create mode 100755 scripts/build.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81d799c..2484eda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,10 @@ jobs: echo "Clang_DIR=$(brew --prefix llvm@20)/lib/cmake/clang" >> $GITHUB_ENV echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH + - name: Configure via build.sh (quick) + run: | + ./build.sh --build-dir build-script --type Release --configure-only + - name: Configure and Build (Linux/macOS) if: runner.os == 'Linux' || runner.os == 'macOS' run: | diff --git a/README.md b/README.md index 5d95751..f2f10eb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,30 @@ # coretrace-stack-analyzer -#### BUILD +#### BUILD (macOS/Linux) ```zsh ./build.sh ``` +The build script auto-detects LLVM/Clang using Homebrew (macOS) or +`llvm-config` (Linux). If detection fails, set `LLVM_DIR` and `Clang_DIR`. + +Options: +- `--build-dir ` (default: `build`) +- `--type ` (default: `Release`) +- `--generator ` +- `--jobs ` +- `--llvm-dir ` / `--clang-dir ` +- `--clean` +- `--configure-only` + +Examples: +```zsh +./build.sh --type Release +./build.sh --type Debug --build-dir out/build +LLVM_DIR=/opt/llvm/lib/cmake/llvm Clang_DIR=/opt/llvm/lib/cmake/clang ./build.sh --generator Ninja +``` + ### Code style (clang-format) - Target version: `clang-format` 20 (used in CI). diff --git a/build.sh b/build.sh index 600beea..a90af4f 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,5 @@ -mkdir -p build && cd build +#!/usr/bin/env bash +set -euo pipefail -cmake .. \ - -DLLVM_DIR=$(brew --prefix llvm)/lib/cmake/llvm \ - -DClang_DIR=$(brew --prefix llvm)/lib/cmake/clang \ - -DCMAKE_BUILD_TYPE=Release \ -&& make -j$(sysctl -n hw.logicalcpu) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$SCRIPT_DIR/scripts/build.sh" "$@" diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..1c99bee --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,327 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +BUILD_DIR="build" +BUILD_TYPE="Release" +GENERATOR="" +JOBS="" +CLEAN=0 +CONFIGURE_ONLY=0 + +LLVM_DIR_ENV="${LLVM_DIR:-}" +CLANG_DIR_ENV="${Clang_DIR:-}" +LLVM_DIR="${LLVM_DIR_ENV}" +Clang_DIR="${CLANG_DIR_ENV}" +LLVM_DIR_EXPLICIT=0 +CLANG_DIR_EXPLICIT=0 + +if [ -n "$LLVM_DIR" ]; then + LLVM_DIR_EXPLICIT=1 +fi +if [ -n "$Clang_DIR" ]; then + CLANG_DIR_EXPLICIT=1 +fi + +usage() { + cat <<'USAGE' +Usage: build.sh [options] + +Options: + --build-dir Build directory (default: build) + --type + Build type (default: Release) + --generator + CMake generator (default: Ninja if available) + --llvm-dir LLVM CMake directory (or set LLVM_DIR) + --clang-dir Clang CMake directory (or set Clang_DIR) + --jobs Parallel build jobs + --clean Delete build directory before configuring + --configure-only Only run CMake configure step + -h, --help Show this help + +Examples: + ./build.sh --type Release + ./build.sh --clean --build-dir out/build + LLVM_DIR=/opt/llvm/lib/cmake/llvm Clang_DIR=/opt/llvm/lib/cmake/clang ./build.sh +USAGE +} + +die() { + echo "error: $*" >&2 + exit 1 +} + +note() { + echo "==> $*" +} + +require_arg() { + local flag="$1" + local value="${2:-}" + if [ -z "$value" ]; then + die "Missing value for $flag" + fi +} + +while [ $# -gt 0 ]; do + case "$1" in + --build-dir) + require_arg "$1" "${2:-}" + BUILD_DIR="$2" + shift 2 + ;; + --type) + require_arg "$1" "${2:-}" + BUILD_TYPE="$2" + shift 2 + ;; + --generator) + require_arg "$1" "${2:-}" + GENERATOR="$2" + shift 2 + ;; + --llvm-dir) + require_arg "$1" "${2:-}" + LLVM_DIR="$2" + LLVM_DIR_EXPLICIT=1 + shift 2 + ;; + --clang-dir) + require_arg "$1" "${2:-}" + Clang_DIR="$2" + CLANG_DIR_EXPLICIT=1 + shift 2 + ;; + --jobs) + require_arg "$1" "${2:-}" + JOBS="$2" + shift 2 + ;; + --clean) + CLEAN=1 + shift + ;; + --configure-only) + CONFIGURE_ONLY=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "Unknown argument: $1" + ;; + esac +done + +case "$BUILD_TYPE" in + Release|Debug|RelWithDebInfo) + ;; + *) + die "Invalid build type: $BUILD_TYPE" + ;; +esac + +if [ -z "$BUILD_DIR" ]; then + die "Build directory cannot be empty" +fi + +if [ "$BUILD_DIR" != /* ]; then + BUILD_DIR="${ROOT_DIR}/${BUILD_DIR}" +fi + +if [ "$CLEAN" -eq 1 ]; then + if [ "$BUILD_DIR" = "/" ] || [ "$BUILD_DIR" = "$ROOT_DIR" ]; then + die "Refusing to clean build directory: $BUILD_DIR" + fi + note "Cleaning build directory: $BUILD_DIR" + rm -rf "$BUILD_DIR" +fi + +if ! command -v cmake >/dev/null 2>&1; then + die "cmake not found in PATH" +fi + +OS_NAME="$(uname -s)" + +if [ -z "$GENERATOR" ]; then + if command -v ninja >/dev/null 2>&1; then + GENERATOR="Ninja" + else + GENERATOR="Unix Makefiles" + fi +fi + +if [ "$GENERATOR" = "Ninja" ] && ! command -v ninja >/dev/null 2>&1; then + die "Generator 'Ninja' requested but ninja is not installed" +fi + +if [ -n "$JOBS" ]; then + if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [ "$JOBS" -lt 1 ]; then + die "Invalid jobs count: $JOBS" + fi +else + case "$OS_NAME" in + Darwin) + if command -v sysctl >/dev/null 2>&1; then + JOBS="$(sysctl -n hw.logicalcpu 2>/dev/null || true)" + fi + ;; + Linux) + if command -v nproc >/dev/null 2>&1; then + JOBS="$(nproc 2>/dev/null || true)" + fi + ;; + esac + if [ -z "${JOBS:-}" ] && command -v getconf >/dev/null 2>&1; then + JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || true)" + fi + if ! [[ "${JOBS:-}" =~ ^[0-9]+$ ]] || [ "${JOBS:-0}" -lt 1 ]; then + JOBS=1 + fi +fi + +normalize_dirs() { + if [ -n "$LLVM_DIR" ] && [ -z "$Clang_DIR" ]; then + local candidate + candidate="$(dirname "$LLVM_DIR")/clang" + if [ -d "$candidate" ]; then + Clang_DIR="$candidate" + fi + fi + + if [ -n "$Clang_DIR" ] && [ -z "$LLVM_DIR" ]; then + local candidate + candidate="$(dirname "$Clang_DIR")/llvm" + if [ -d "$candidate" ]; then + LLVM_DIR="$candidate" + fi + fi +} + +validate_explicit_dirs() { + if [ "$LLVM_DIR_EXPLICIT" -eq 1 ] && [ -n "$LLVM_DIR" ] && [ ! -d "$LLVM_DIR" ]; then + die "LLVM_DIR does not exist: $LLVM_DIR" + fi + if [ "$CLANG_DIR_EXPLICIT" -eq 1 ] && [ -n "$Clang_DIR" ] && [ ! -d "$Clang_DIR" ]; then + die "Clang_DIR does not exist: $Clang_DIR" + fi +} + +detect_from_brew() { + [ "$OS_NAME" = "Darwin" ] || return 0 + command -v brew >/dev/null 2>&1 || return 0 + + local formula + local prefix + for formula in llvm llvm@20 llvm@19 llvm@18; do + prefix="$(brew --prefix "$formula" 2>/dev/null || true)" + if [ -n "$prefix" ] && [ -d "$prefix" ]; then + if [ -z "$LLVM_DIR" ] && [ -d "$prefix/lib/cmake/llvm" ]; then + LLVM_DIR="$prefix/lib/cmake/llvm" + fi + if [ -z "$Clang_DIR" ] && [ -d "$prefix/lib/cmake/clang" ]; then + Clang_DIR="$prefix/lib/cmake/clang" + fi + if [ -n "$LLVM_DIR" ] && [ -n "$Clang_DIR" ]; then + return 0 + fi + fi + done +} + +detect_from_llvm_config() { + command -v llvm-config >/dev/null 2>&1 || return 0 + + local cmake_dir + cmake_dir="$(llvm-config --cmakedir 2>/dev/null || true)" + if [ -n "$cmake_dir" ] && [ -d "$cmake_dir" ]; then + if [ -z "$LLVM_DIR" ]; then + LLVM_DIR="$cmake_dir" + fi + if [ -z "$Clang_DIR" ]; then + local clang_dir + clang_dir="$(dirname "$cmake_dir")/clang" + if [ -d "$clang_dir" ]; then + Clang_DIR="$clang_dir" + fi + fi + fi +} + +detect_from_prefixes() { + local prefixes=() + shopt -s nullglob + prefixes+=(/usr/lib/llvm-*) + prefixes+=(/usr/lib64/llvm-*) + prefixes+=(/usr/lib/llvm) + prefixes+=(/usr/local/llvm*) + prefixes+=(/opt/llvm*) + shopt -u nullglob + + local prefix + for prefix in "${prefixes[@]}"; do + [ -d "$prefix" ] || continue + if [ -z "$LLVM_DIR" ] && [ -d "$prefix/lib/cmake/llvm" ]; then + LLVM_DIR="$prefix/lib/cmake/llvm" + fi + if [ -z "$Clang_DIR" ] && [ -d "$prefix/lib/cmake/clang" ]; then + Clang_DIR="$prefix/lib/cmake/clang" + fi + if [ -n "$LLVM_DIR" ] && [ -n "$Clang_DIR" ]; then + return 0 + fi + done +} + +normalize_dirs +validate_explicit_dirs + +if [ "$LLVM_DIR_EXPLICIT" -eq 0 ] && [ "$CLANG_DIR_EXPLICIT" -eq 0 ]; then + if [ -z "$LLVM_DIR" ] || [ -z "$Clang_DIR" ]; then + detect_from_brew + detect_from_llvm_config + detect_from_prefixes + normalize_dirs + fi +fi + +if [ -z "$LLVM_DIR" ]; then + die "LLVM_DIR not found. Set LLVM_DIR or pass --llvm-dir (e.g., LLVM_DIR=/opt/llvm/lib/cmake/llvm)." +fi +if [ -z "$Clang_DIR" ]; then + die "Clang_DIR not found. Set Clang_DIR or pass --clang-dir (e.g., Clang_DIR=/opt/llvm/lib/cmake/clang)." +fi +if [ ! -d "$LLVM_DIR" ]; then + die "LLVM_DIR does not exist: $LLVM_DIR" +fi +if [ ! -d "$Clang_DIR" ]; then + die "Clang_DIR does not exist: $Clang_DIR" +fi + +note "Configuring" +note " Root: $ROOT_DIR" +note " Build dir: $BUILD_DIR" +note " Type: $BUILD_TYPE" +note " Generator: $GENERATOR" +note " Jobs: $JOBS" +note " LLVM_DIR: $LLVM_DIR" +note " Clang_DIR: $Clang_DIR" + +cmake -S "$ROOT_DIR" -B "$BUILD_DIR" \ + -G "$GENERATOR" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DLLVM_DIR="$LLVM_DIR" \ + -DClang_DIR="$Clang_DIR" + +if [ "$CONFIGURE_ONLY" -eq 1 ]; then + note "Configure-only requested; skipping build" + exit 0 +fi + +note "Building" +cmake --build "$BUILD_DIR" -j "$JOBS"