Ctrl K
ComunidadGuías…

build.sh

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
set -euo pipefail

# Help function
show_help() {
    cat << EOF
Usage: ./build.sh [OPTIONS] [CMAKE_ARGS...]

Build script for Memgraph using Conan 2 and CMake.

OPTIONS:
    --build-type TYPE       Build type: Release, RelWithDebInfo, or Debug (default: Release)
    --target TARGET...      CMake target(s) to build (default: all targets). Accepts
                            multiple targets in one sequence, e.g.
                            --target memgraph memgraph__unit
    --reserve-cores N       Leave N cores free for other work (default: 0, uses all cores)
    --compile-jobs N        Pin concurrent compile steps (default: derived from memory)
    --link-jobs N           Pin concurrent link steps (default: derived from memory)
    --no-job-memory-cap     Do not cap concurrency by memory; -j alone decides
    --skip-os-deps          Skip OS dependency checks
    --keep-build            Keep existing build directory for incremental builds
    --config-only           Only configure CMake, don't build
    --dev                   Developer mode: enables --skip-os-deps --keep-build
    --update-lockfile       Update conan.lock before installing dependencies
    --graph-info            Generate dependency graph as graph.html and exit
    --split-debug           Extract debug info into sidecar .debug files (requires RelWithDebInfo/Debug)
    --fips                  Build for a FIPS 140-3 approved-mode image (maps to -DMG_FIPS=ON; omits
                            components whose crypto cannot come from the validated OpenSSL)
    --mage MODE             MAGE query modules (C++, Python, Rust). MODE is one of:
                              off  = no MAGE (default)
                              on   = build MAGE together with Memgraph
                              only = build just MAGE, not Memgraph itself
                                     (trims the conan dependency graph)
    --cugraph               Also build MAGE cuGraph GPU modules (implies --mage on)
    --no-python             Build memgraph without the embedded Python interpreter
                            (Python query modules are unavailable; no libpython dependency)
    --profiling MODES       Comma-separated profiling build modes (e.g. --profiling fp,mem):
                              fp  = retain frame pointers for low-overhead 'perf' (MG_PROFILE)
                              mem = memory-profiling build, disables jemalloc (MG_MEMORY_PROFILE)
    --help                  Show this help message

ENVIRONMENT VARIABLES:
    VENV_DIR                Path to Python virtual environment (default: env)
    MG_PYTHON               Python interpreter to use (must be >= 3.10). By
                            default the newest suitable python3/python3.X on
                            PATH is picked automatically.

CMAKE_ARGS:
    Any additional arguments are passed directly to CMake configuration.
    Common examples:
        -DASAN=ON               Enable Address Sanitizer
        -DUBSAN=ON              Enable Undefined Behavior Sanitizer
        -DCMAKE_CXX_FLAGS=...   Additional compiler flags

    Compile and link steps are capped so their peak memory fits the machine (or
    the container's cgroup limit), whatever -j is used. --compile-jobs,
    --link-jobs and --no-job-memory-cap override the derived caps; the memory
    budgeted per step is retunable too:
        -DMG_MEMORY_PER_COMPILE_JOB_MB=N
        -DMG_MEMORY_PER_LINK_JOB_MB=N

EXAMPLES:
    # Standard release build
    ./build.sh

    # Fast developer rebuild (incremental)
    ./build.sh --dev

    # Debug build with sanitizers
    ./build.sh --build-type Debug -DASAN=ON -DUBSAN=ON

    # Build specific target
    ./build.sh --target memgraph

    # Build multiple targets at once
    ./build.sh --target memgraph memgraph__unit

    # Build Memgraph together with the MAGE query modules
    ./build.sh --mage on

    # Build just the MAGE query modules (no Memgraph)
    ./build.sh --mage only

    # MAGE-only with GPU modules against a prebuilt cuGraph
    ./build.sh --mage only --cugraph -DMG_CUGRAPH_ROOT=/opt/conda

    # Build without the embedded Python interpreter
    ./build.sh --no-python

    # Configure only, don't build
    ./build.sh --config-only

    # Keep build directory for faster rebuilds
    ./build.sh --keep-build --skip-os-deps

EOF
    exit 0
}

# Default values
BUILD_TYPE="Release"
TARGETS=()
CMAKE_ARGS=""
config_only=false
keep_build=false
skip_os_deps=false
VENV_DIR="${VENV_DIR:-env}"
offline=false
update_lockfile=false
graph_info=false
RESERVE_CORES=0
COMPILE_JOBS=""
LINK_JOBS=""
JOB_MEMORY_CAP=on
SPLIT_DEBUG=off
FIPS=off
PROFILING=""
MAGE=off
CUGRAPH=off
PYTHON_SUPPORT=on
# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --build-type)
            BUILD_TYPE="$2"
            shift 2
            ;;
        --target)
            shift
            # Consume all following args until the next flag as target names.
            while [[ $# -gt 0 && "$1" != -* ]]; do
                TARGETS+=("$1")
                shift
            done
            if [[ ${#TARGETS[@]} -eq 0 ]]; then
                echo "Error: --target requires at least one target name" >&2
                exit 1
            fi
            ;;
        --config-only)
            config_only=true
            shift
            ;;
        --keep-build)
            keep_build=true
            shift
            ;;
        --skip-os-deps)
            skip_os_deps=true
            shift
            ;;
        --dev)
            # Developer mode: skip os deps checks and keep build directory
            skip_os_deps=true
            keep_build=true
            shift
            ;;
        --offline)
            skip_os_deps=true
            offline=true
            shift
            ;;
        --update-lockfile)
            update_lockfile=true
            shift
            ;;
        --graph-info)
            graph_info=true
            shift
            ;;
        --reserve-cores)
            RESERVE_CORES="$2"
            shift 2
            ;;
        --compile-jobs)
            COMPILE_JOBS="$2"
            shift 2
            ;;
        --link-jobs)
            LINK_JOBS="$2"
            shift 2
            ;;
        --no-job-memory-cap)
            JOB_MEMORY_CAP=off
            shift
            ;;
        --split-debug)
            SPLIT_DEBUG=on
            shift
            ;;
        --fips)
            FIPS=on
            shift
            ;;
        --mage)
            MAGE="$2"
            shift 2
            ;;
        --cugraph)
            CUGRAPH=on
            shift
            ;;
        --no-python)
            PYTHON_SUPPORT=off
            shift
            ;;
        --profiling)
            PROFILING="$2"
            shift 2
            ;;
        --help|-h)
            show_help
            ;;
        *)
            # Capture any other arguments to pass to cmake
            CMAKE_ARGS="$CMAKE_ARGS $1"
            shift
            ;;
    esac
done

# Job pools are cache variables, so an omitted flag keeps whatever the previous
# configure left behind; pass 0 to go back to the memory-derived default.
for jobs in "$COMPILE_JOBS" "$LINK_JOBS"; do
    if [[ -n "$jobs" && ! "$jobs" =~ ^[0-9]+$ ]]; then
        echo "Error: --compile-jobs and --link-jobs take a non-negative integer (got '$jobs')" >&2
        exit 1
    fi
done
if [[ -n "$COMPILE_JOBS" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_COMPILE_JOBS=$COMPILE_JOBS"
fi
if [[ -n "$LINK_JOBS" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_LINK_JOBS=$LINK_JOBS"
fi
if [[ ! "$RESERVE_CORES" =~ ^[0-9]+$ ]]; then
    echo "Error: --reserve-cores takes a non-negative integer (got '$RESERVE_CORES')" >&2
    exit 1
fi
if [[ ! "$CMAKE_ARGS" =~ MG_RESERVE_CORES ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_RESERVE_CORES=$RESERVE_CORES"
fi

# Stated explicitly either way, so dropping --no-job-memory-cap restores the
# cap instead of inheriting the previous configure's OFF. A raw -D for the same
# variable stays authoritative.
if [[ ! "$CMAKE_ARGS" =~ MG_LIMIT_PARALLELISM_BY_MEMORY ]]; then
    if [[ "$JOB_MEMORY_CAP" == "off" ]]; then
        CMAKE_ARGS="$CMAKE_ARGS -DMG_LIMIT_PARALLELISM_BY_MEMORY=OFF"
    else
        CMAKE_ARGS="$CMAKE_ARGS -DMG_LIMIT_PARALLELISM_BY_MEMORY=ON"
    fi
fi

if [[ "$SPLIT_DEBUG" == "on" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_SPLIT_DEBUG=ON"
fi

if [[ "$FIPS" == "on" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_FIPS=ON"
fi

if [[ ! "$CMAKE_ARGS" =~ MG_PYTHON_SUPPORT ]]; then
    if [[ "$PYTHON_SUPPORT" == "off" ]]; then
        CMAKE_ARGS="$CMAKE_ARGS -DMG_PYTHON_SUPPORT=OFF"
    else
        CMAKE_ARGS="$CMAKE_ARGS -DMG_PYTHON_SUPPORT=ON"
    fi
fi

if [[ "$MAGE" != "off" && "$MAGE" != "on" && "$MAGE" != "only" ]]; then
    echo "Error: --mage must be 'off', 'on', or 'only' (got '$MAGE')" >&2
    exit 1
fi

# conan.lock must always be generated from the FULL dependency graph; a
# lockfile created from the trimmed MAGE-only graph would be missing the
# memgraph dependencies and break every full build. (The reverse is fine:
# MAGE-only builds consume the full lockfile as a superset.)
if [[ "$update_lockfile" = true && "$MAGE" == "only" ]]; then
    echo "Error: --update-lockfile requires the full dependency graph; drop '--mage only'" >&2
    exit 1
fi

# cuGraph modules are part of MAGE, so --cugraph implies at least --mage on
# (an explicit --mage only is respected).
if [[ "$CUGRAPH" == "on" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_ENABLE_CUGRAPH=ON"
    if [[ "$MAGE" == "off" ]]; then
        MAGE=on
    fi
fi

if [[ "$MAGE" == "on" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_BUILD_MEMGRAPH=ON -DMG_BUILD_MAGE=ON"
elif [[ "$MAGE" == "off" ]]; then
    CMAKE_ARGS="$CMAKE_ARGS -DMG_BUILD_MEMGRAPH=ON -DMG_BUILD_MAGE=OFF"
fi

# Map comma-separated --profiling tokens to CMake options.
if [[ -n "$PROFILING" ]]; then
    IFS=',' read -ra _profiling_modes <<< "$PROFILING"
    for mode in "${_profiling_modes[@]}"; do
        case "$mode" in
            fp)  CMAKE_ARGS="$CMAKE_ARGS -DMG_PROFILE=ON" ;;
            mem) CMAKE_ARGS="$CMAKE_ARGS -DMG_MEMORY_PROFILE=ON" ;;
            "")  ;;
            *)   echo "Error: unknown --profiling mode '$mode' (valid: fp, mem)"; exit 1 ;;
        esac
    done
fi

# Detect distro
source environment/util.sh
DISTRO="$(operating_system)"
echo "Distro: $DISTRO"

# Resolve a Python >= 3.10 (the floor for every python invocation here and in
# init-dev). Handles distros whose default python3 is older but ship a newer
# versioned binary (e.g. centos-9: python3 = 3.9, python3.12 installed).
PYTHON="$(resolve_python)" || exit 1
export MG_PYTHON="$PYTHON"
echo "Python: $PYTHON ($("$PYTHON" --version 2>&1))"

# Rust (mgcxx) is installed via rustup into ~/.cargo. Login shells get it on
# PATH from ~/.cargo/env via the shell profile, but CI / non-login shells
# don't — source it here so `cargo` resolves in both.
if ! command -v cargo >/dev/null 2>&1 && [[ -f "$HOME/.cargo/env" ]]; then
    source "$HOME/.cargo/env"
fi

# Validate build type
if [[ "$BUILD_TYPE" != "Release" && "$BUILD_TYPE" != "RelWithDebInfo" && "$BUILD_TYPE" != "Debug" ]]; then
    echo "Error: --build-type must be either 'Release', 'RelWithDebInfo', or 'Debug'"
    exit 1
fi

# Which profiles describe this host, and whether it has a bundled toolchain to build
# against. Hosts without one use their system compiler, so nothing here may assume the
# toolchain's directory layout exists.
BUNDLED_TOOLCHAIN=true
case "$DISTRO" in
    freebsd-*)
        BUNDLED_TOOLCHAIN=false
        HOST_PROFILES=("-pr:h" "memgraph_freebsd")
        BUILD_PROFILE="memgraph_freebsd_build"
        ;;
    *)
        HOST_PROFILES=("-pr:h" "memgraph_toolchain_v8")
        BUILD_PROFILE="memgraph_build_profile"
        ;;
esac

CONAN_COMMON_ARGS=(
  -pr:b "$BUILD_PROFILE"
  -s build_type="$BUILD_TYPE"
  -s os.distro="$DISTRO"
)

# Every conan invocation runs through this, so the toolchain root is named once and
# only where there is a bundled toolchain to point at. Whether one is used at all is
# the profile's to say, and conan carries that into the CMake preset it generates.
CONAN_ENV=(env)
if [[ "$BUNDLED_TOOLCHAIN" = true ]]; then
    CONAN_ENV+=("MG_TOOLCHAIN_ROOT=/opt/toolchain-v8")
else
    # Tools taken from the platform appear as references the Linux lockfile does not
    # carry, so the lock can constrain what it knows and no more.
    CONAN_COMMON_ARGS+=("--lockfile-partial")
fi

if [[ "$offline" = true ]]; then
    CONAN_COMMON_ARGS+=("--no-remote")
fi

# MAGE-only mode: trim the conan dependency graph to what MAGE needs. The
# conanfile also flips MG_BUILD_MEMGRAPH=OFF / MG_BUILD_MAGE=ON via the
# generated CMake toolchain (see conanfile.py generate()).
if [[ "$MAGE" == "only" ]]; then
    CONAN_COMMON_ARGS+=("-o" "&:mage_only=True")
fi

# delete existing build directory
if [[ "$keep_build" = false ]]; then
    if [ -d "build" ]; then
        echo "Deleting existing build directory"
        rm -rf build
    fi
else
    echo "Keeping existing build directory"
fi

# run check for operating system dependencies
if [[ "$skip_os_deps" = false ]]; then
    # Hard requirements: without these the build itself fails.
    for deps_group in TOOLCHAIN_RUN_DEPS MEMGRAPH_BUILD_DEPS; do
        if ! ./environment/os/install_deps.sh check "$deps_group"; then
            echo "Error: Dependency check failed for $deps_group"
            exit 1
        fi
    done
    # Not needed to compile — only to run the test suites / the built
    # memgraph. Warn so the gap is visible, but don't block the build.
    for deps_group in MEMGRAPH_TEST_DEPS MEMGRAPH_RUN_DEPS; do
        if ! ./environment/os/install_deps.sh check "$deps_group"; then
            echo "Warning: missing $deps_group packages (needed to run tests / memgraph itself);"
            echo "         install with: sudo ./environment/os/install_deps.sh install $deps_group"
        fi
    done
else
    echo "Skipping OS dependency checks"
fi

DEV_SETUP_ARGS=()
if [[ -n "${CI:-}" ]]; then
    DEV_SETUP_ARGS+=("--ci")
fi
bash ./init-dev "${DEV_SETUP_ARGS[@]}"

if [[ -f "$VENV_DIR/bin/activate" ]]; then
    echo "Using existing virtual environment at $VENV_DIR"
    # A venv created by an older interpreter keeps that version forever —
    # reject it rather than fail later in subtler ways.
    if ! "$VENV_DIR/bin/python" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null; then
        echo "Error: $VENV_DIR uses $("$VENV_DIR/bin/python" --version 2>&1), but >= 3.10 is required." >&2
        echo "Delete it (rm -rf $VENV_DIR) and re-run to recreate it with $PYTHON." >&2
        exit 1
    fi
    source "$VENV_DIR/bin/activate"
    trap 'deactivate 2>/dev/null' EXIT ERR
else
    echo "Creating virtual environment and installing conan"
    "$PYTHON" -m venv "$VENV_DIR"
    source "$VENV_DIR/bin/activate"
    trap 'deactivate 2>/dev/null' EXIT ERR
    pip install --only-binary :all: "conan>=2.26.0"
fi

# check if a conan profile exists
if [[ ! -f "$HOME/.conan2/profiles/default" ]]; then
    echo "Creating conan profile"
    conan profile detect
fi

# Install custom conan settings
conan config install conan_config

# Register vendored recipes as a local-recipes-index remote
# NOTE: also registered in release/package/mgbuild.sh — keep in sync
conan remote add memgraph-recipes "$(pwd)/conan_recipes" -t local-recipes-index --force

# Function to check if a CMake boolean variable is enabled
# Handles various CMake boolean formats: ON, TRUE, YES, 1 (case insensitive)
# Supports both -DVAR=VALUE and -D VAR=VALUE formats
cmake_var_enabled() {
    local var_name="$1"
    local args="$2"
    # Match patterns like -DVAR=ON, -D VAR=ON, -DVAR:BOOL=TRUE, etc.
    # Accepts ON, TRUE, YES, 1 as true values (case insensitive)
    if [[ "$args" =~ -D[[:space:]]*${var_name}([[:space:]]*:[[:alnum:]]+)?[[:space:]]*=[[:space:]]*(ON|TRUE|YES|1) ]]; then
        return 0
    fi
    return 1
}

# Add sanitizer profiles based on CMAKE_ARGS
if cmake_var_enabled "ASAN" "$CMAKE_ARGS"; then
    HOST_PROFILES+=("-pr:h" "add_asan")
    echo "ASAN enabled"
fi
if cmake_var_enabled "UBSAN" "$CMAKE_ARGS"; then
    HOST_PROFILES+=("-pr:h" "add_ubsan")
    echo "UBSAN enabled"
fi
if cmake_var_enabled "TSAN" "$CMAKE_ARGS"; then
    HOST_PROFILES+=("-pr:h" "add_tsan")
    echo "TSAN enabled"
fi

# generate dependency graph and exit early
if [[ "$graph_info" = true ]]; then
    echo "Generating dependency graph -> graph.html"
    "${CONAN_ENV[@]}" conan graph info . \
      "${HOST_PROFILES[@]}" "${CONAN_COMMON_ARGS[@]}" \
      --format=html > graph.html
    echo "Open graph.html in a browser to view the dependency graph"
    exit 0
fi

# update lockfile if requested
if [[ "$update_lockfile" = true ]]; then
    echo "Updating conan.lock"
    # Resolve recipe revisions from remotes (including local-recipes-index),
    # not from any stale local cache export, so lockfiles stay portable.
    "${CONAN_ENV[@]}" conan lock create . \
      "${HOST_PROFILES[@]}" "${CONAN_COMMON_ARGS[@]}" \
      --update \
      --lockfile="" \
      --lockfile-out=conan.lock
fi

# install conan dependencies
"${CONAN_ENV[@]}" conan install . --build=missing \
  "${HOST_PROFILES[@]}" "${CONAN_COMMON_ARGS[@]}"

source build/generators/conanbuild.sh

# Determine preset name based on build type (Conan generates this automatically)
# Convert to lowercase for preset name: Release -> conan-release
PRESET="conan-$(echo "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]')"

# Filter out sanitizer flags from CMAKE_ARGS since conanfile.py handles them automatically
# via compiler settings (compiler.asan, compiler.ubsan, compiler.tsan)
FILTERED_CMAKE_ARGS=""
for arg in $CMAKE_ARGS; do
    if [[ ! "$arg" =~ ^-D(ASAN|UBSAN|TSAN)(=|:|$) ]]; then
        FILTERED_CMAKE_ARGS="$FILTERED_CMAKE_ARGS $arg"
    fi
done

# Configure cmake with additional arguments (sanitizer flags automatically set by Conan)
cmake --preset $PRESET $FILTERED_CMAKE_ARGS

if [[ "$config_only" = true ]]; then
    exit 0
fi

# Build command with optional target
# Ninja's ceiling for steps no job pool covers, such as code generation.
BUILD_JOBS=$(( $(nproc) - RESERVE_CORES ))
if [[ $BUILD_JOBS -lt 1 ]]; then
    BUILD_JOBS=1
fi

TARGET_ARGS=()
if [[ ${#TARGETS[@]} -gt 0 ]]; then
    TARGET_ARGS=(--target "${TARGETS[@]}")
fi

cmake \
  --build build \
  --preset $PRESET \
  "${TARGET_ARGS[@]}" \
  -j "$BUILD_JOBS"