blob: e49e4cf5ce8a340b13e05e0bbe571d76aa699703 [file] [log] [blame]
# iree-bazel-lib - Shared functions for iree-bazel-* tools
#
# This file is meant to be sourced, not executed directly.
# Usage in scripts:
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# source "$SCRIPT_DIR/iree-bazel-lib"
# iree_bazel_init "iree-bazel-build"
# Prevent double-sourcing.
[[ -n "${_IREE_BAZEL_LIB_LOADED:-}" ]] && return 0
_IREE_BAZEL_LIB_LOADED=1
# -----------------------------------------------------------------------------
# Colors
# -----------------------------------------------------------------------------
_IREE_BAZEL_COLOR_GREEN='\033[0;32m'
_IREE_BAZEL_COLOR_YELLOW='\033[0;33m'
_IREE_BAZEL_COLOR_RED='\033[0;31m'
_IREE_BAZEL_COLOR_CYAN='\033[0;36m'
_IREE_BAZEL_COLOR_NC='\033[0m'
# Tool name for logging (set via iree_bazel_init).
_IREE_BAZEL_TOOL_NAME=""
# Bazel startup args for info/cquery (e.g., --output_base).
# Tools can set via iree_bazel_set_startup_args.
declare -a IREE_BAZEL_STARTUP_ARGS=()
IREE_BAZEL_EXECROOT=""
# -----------------------------------------------------------------------------
# Logging functions
# -----------------------------------------------------------------------------
# Print info message with tool name prefix (to stderr, never pollutes stdout).
iree_info() {
printf "${_IREE_BAZEL_COLOR_GREEN}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}
# Print warning message with tool name prefix (to stderr).
iree_warn() {
printf "${_IREE_BAZEL_COLOR_YELLOW}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}
# Print error message with tool name prefix to stderr.
iree_error() {
printf "${_IREE_BAZEL_COLOR_RED}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}
# Print debug message (only if IREE_BAZEL_VERBOSE is set).
iree_debug() {
[[ "${IREE_BAZEL_VERBOSE:-0}" == "1" ]] && \
printf "${_IREE_BAZEL_COLOR_CYAN}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
return 0
}
# -----------------------------------------------------------------------------
# Agent documentation
# -----------------------------------------------------------------------------
# Print a concise snippet for CLAUDE.md/AGENT.md files describing the bazel suite.
# Called by all iree-bazel-* tools via --agent-md flag.
iree_show_agent_md() {
cat << 'EOF'
## iree-bazel-* Tools
Use `iree-bazel-*` wrappers instead of raw `bazel` commands. They handle
configuration, provide better defaults, and work from any subdirectory.
**Setup (ask user before first use):**
- Run `iree-bazel-configure` once per worktree to set up Bazel configuration
- Tools will error with helpful message if not configured yet
**Main tools:**
- `iree-bazel-build <target> [bazel-args]` - Build targets
- `iree-bazel-test <target> [bazel-args]` - Run tests
- `iree-bazel-run <target> [bazel-args] [-- program-args]` - Build and run binary
- `iree-bazel-try [options] -e 'code' [-- program-args]` - Quick C/C++ experiments
**Additional tools:** `iree-bazel-query`, `iree-bazel-cquery`, `iree-bazel-fuzz` (see `--help`)
**Examples:**
```shell
iree-bazel-build //tools:iree-compile
iree-bazel-test //runtime/src/iree/base:status_test
iree-bazel-run //tools:iree-compile -- --help
iree-bazel-try -e 'int main() { return 0; }'
```
**Output directories** (at repo root):
- `bazel-bin/` - built executables and libraries
- `bazel-testlogs/` - test outputs and logs
**Important:** Use absolute labels (`//tools:target` not `:target`). Try to stay
in the worktree directory when running commands.
Run `<tool> --help` for full documentation.
EOF
}
# -----------------------------------------------------------------------------
# Worktree detection
# -----------------------------------------------------------------------------
# Find the IREE worktree root (directory with .git and .bazelversion).
# Outputs the path to stdout, returns 1 if not found.
iree_find_worktree_root() {
local directory="${1:-${PWD}}"
while [[ "${directory}" != "/" ]]; do
if [[ -e "${directory}/.git" ]] && [[ -f "${directory}/.bazelversion" ]]; then
echo "${directory}"
return 0
fi
directory=$(dirname "${directory}")
done
return 1
}
# Require being in a worktree, exit with error if not.
# Sets IREE_BAZEL_WORKTREE_DIR on success.
iree_require_worktree() {
IREE_BAZEL_WORKTREE_DIR=$(iree_find_worktree_root)
if [[ -z "${IREE_BAZEL_WORKTREE_DIR}" ]]; then
iree_error "Not in an IREE worktree (no .bazelversion found)"
iree_error "Must be in a directory with .git and .bazelversion files"
exit 1
fi
}
# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
# Check that bazel is configured, error if not.
# Must be called after cd to worktree root.
iree_ensure_configured() {
if [[ ! -f "configured.bazelrc" ]]; then
iree_error "Bazel is not configured for this worktree"
echo ""
echo "Run this command to configure:"
echo " iree-bazel-configure"
echo ""
echo "For configuration options, run:"
echo " iree-bazel-configure --help"
exit 1
fi
}
# -----------------------------------------------------------------------------
# Initialization
# -----------------------------------------------------------------------------
# Initialize the library for a specific tool.
# Args:
# $1 - Tool name (e.g., "iree-bazel-build")
#
# This sets up:
# - Tool name for logging
# - IREE_BAZEL_ORIG_CWD (original working directory before any cd)
# - IREE_BAZEL_DRY_RUN (default 0)
# - IREE_BAZEL_VERBOSE (default 0)
iree_bazel_init() {
_IREE_BAZEL_TOOL_NAME="${1}"
IREE_BAZEL_ORIG_CWD="${PWD}"
IREE_BAZEL_DRY_RUN="${IREE_BAZEL_DRY_RUN:-0}"
IREE_BAZEL_VERBOSE="${IREE_BAZEL_VERBOSE:-0}"
}
# Set startup args for bazel info/cquery and clear cached execroot.
iree_bazel_set_startup_args() {
IREE_BAZEL_STARTUP_ARGS=("$@")
IREE_BAZEL_EXECROOT=""
}
# Set up the worktree environment.
# Combines: require worktree + cd to it + ensure configured.
# Must be called after parsing args (so --help can work outside worktree).
iree_setup_worktree() {
iree_require_worktree
cd "${IREE_BAZEL_WORKTREE_DIR}"
iree_ensure_configured
}
# Check if dry-run mode is enabled.
iree_is_dry_run() {
[[ "${IREE_BAZEL_DRY_RUN}" == "1" ]]
}
# Check if verbose mode is enabled.
iree_is_verbose() {
[[ "${IREE_BAZEL_VERBOSE}" == "1" ]]
}
# Get the bazel command to use (bazel or ibazel for watch mode).
# Assumes bazelisk is installed as 'bazel' (standard installation pattern).
iree_get_bazel_command() {
local watch_mode="${1:-0}"
if [[ "${watch_mode}" == "1" ]]; then
if ! command -v ibazel >/dev/null 2>&1; then
iree_error "ibazel not found. Install with: go install github.com/bazelbuild/bazel-watcher/cmd/ibazel@latest"
exit 1
fi
echo "ibazel"
else
echo "bazel"
fi
}
# -----------------------------------------------------------------------------
# Argument preprocessing
# -----------------------------------------------------------------------------
# Expand combined short flags (e.g., -nv -> -n -v).
# This allows Unix-style combined flags as documented in help text.
#
# Usage: Call before argument parsing loop to expand combined flags.
# eval "set -- $(iree_expand_combined_flags "$@")"
#
# Examples:
# -nv -> -n -v
# -nvk -> -n -v -k
# --foo -> --foo (unchanged)
# -n -> -n (unchanged)
iree_expand_combined_flags() {
local result=()
while [[ $# -gt 0 ]]; do
local arg="${1}"
shift
# Match single dash followed by 2+ lowercase letters (no '=' or other chars).
if [[ "${arg}" =~ ^-([a-z]{2,})$ ]]; then
local flags="${BASH_REMATCH[1]}"
for (( i=0; i<${#flags}; i++ )); do
result+=("-${flags:${i}:1}")
done
else
result+=("${arg}")
fi
done
# Print quoted args suitable for eval.
printf '%q ' "${result[@]}"
}
# -----------------------------------------------------------------------------
# Default configuration flags
# -----------------------------------------------------------------------------
# Build the list of default bazel config flags.
# These are inserted before user-provided flags so users can override.
# Populates IREE_BAZEL_DEFAULT_CONFIGS array.
#
# Current defaults:
# --config=localdev Local dev optimizations (disk cache, skymeld)
# --verbose_failures (only in verbose mode) Show full command on failure
#
# Future: auto-detect hardware and enable drivers:
# - HIP_PATH -> --config=hip
# - VULKAN_SDK -> --config=vulkan
# - CUDA_PATH -> --config=cuda
iree_bazel_build_default_configs() {
IREE_BAZEL_DEFAULT_CONFIGS=()
# Always use localdev for interactive development.
IREE_BAZEL_DEFAULT_CONFIGS+=(--config=localdev)
# Verbose mode: show full commands on failure.
if iree_is_verbose; then
IREE_BAZEL_DEFAULT_CONFIGS+=(--verbose_failures)
fi
# Use mold linker if available (significantly faster than ld/gold).
# TODO(benvanik): this breaks --features=thin_lto, so leaving it opt-in for
# now (--config=mold) until I can find a good way to choose.
# if command -v mold &>/dev/null; then
# IREE_BAZEL_DEFAULT_CONFIGS+=(--config=mold)
# iree_debug "Using mold linker"
# fi
# TODO: Auto-detect available hardware/SDKs.
# if [[ -n "${HIP_PATH:-}" ]]; then
# IREE_BAZEL_DEFAULT_CONFIGS+=(--config=hip)
# fi
# if [[ -n "${VULKAN_SDK:-}" ]]; then
# IREE_BAZEL_DEFAULT_CONFIGS+=(--config=vulkan)
# fi
iree_debug "Default configs: ${IREE_BAZEL_DEFAULT_CONFIGS[*]}"
}
# -----------------------------------------------------------------------------
# Build and execution
# -----------------------------------------------------------------------------
# Get execroot for the current Bazel server (cached).
# Must be called from the worktree root directory.
iree_bazel_get_execroot() {
if [[ -n "${IREE_BAZEL_EXECROOT}" ]]; then
echo "${IREE_BAZEL_EXECROOT}"
return 0
fi
local BAZEL_BIN
BAZEL_BIN=$(iree_get_bazel_command)
local info_output execroot
info_output=$("${BAZEL_BIN}" "${IREE_BAZEL_STARTUP_ARGS[@]}" info execution_root 2>/dev/null || true)
execroot=$(echo "${info_output}" | sed -n 's/^execution_root: //p')
if [[ -z "${execroot}" ]]; then
execroot="${info_output}"
fi
if [[ -n "${execroot}" ]]; then
IREE_BAZEL_EXECROOT="${execroot}"
echo "${IREE_BAZEL_EXECROOT}"
fi
}
# Get the output file path for a bazel target.
# Must be called from the worktree root directory.
# Args:
# $1 - Bazel target label
# $2... - Bazel args (configs, etc.) - MUST match the build args
# Outputs: Absolute path to built binary
iree_bazel_get_binary_path() {
local target="${1}"
shift
local BAZEL_BIN
BAZEL_BIN=$(iree_get_bazel_command)
local relative_paths relative_path execroot
# Pass all bazel args to cquery so it uses the same configuration as build.
relative_paths=$("${BAZEL_BIN}" "${IREE_BAZEL_STARTUP_ARGS[@]}" cquery --output=files "${target}" "$@" 2>/dev/null)
relative_path="${relative_paths%%$'\n'*}"
if [[ -n "${relative_path}" ]]; then
execroot=$(iree_bazel_get_execroot)
if [[ -n "${execroot}" ]]; then
echo "${execroot}/${relative_path}"
else
# Fallback to workspace-relative path if execroot is unavailable.
echo "${PWD}/${relative_path}"
fi
fi
}
# Build a bazel target and execute it from the original working directory.
# This solves the problem where bazel run changes cwd to the execroot.
#
# Args:
# $1 - Bazel target label
# $2 - Original working directory to run from
# $3... - Bazel args (everything before --)
# After finding "--" in args, remaining args go to the program
#
# Example:
# iree_bazel_build_and_exec "//tools:iree-compile" "${IREE_BAZEL_ORIG_CWD}" \
# --config=debug -- input.mlir -o output.vmfb
iree_bazel_build_and_exec() {
local target="${1}"
local run_cwd="${2}"
shift 2
local -a bazel_args=()
local -a program_args=()
local parsing_program_args=0
# Split args into bazel args and program args.
while [[ $# -gt 0 ]]; do
if [[ "${parsing_program_args}" == "1" ]]; then
program_args+=("${1}")
elif [[ "${1}" == "--" ]]; then
parsing_program_args=1
else
bazel_args+=("${1}")
fi
shift
done
iree_debug "Bazel args: ${bazel_args[*]:-<none>}"
iree_debug "Program args: ${program_args[*]:-<none>}"
# Build the target quietly (no output on success).
if ! iree_bazel_build_quiet "${target}" "${bazel_args[@]}"; then
return $?
fi
# Get the binary path (pass same args so cquery uses same configuration).
local binary_path
binary_path=$(iree_bazel_get_binary_path "${target}" "${bazel_args[@]}")
if [[ -z "${binary_path}" ]] || [[ ! -x "${binary_path}" ]]; then
iree_error "Could not find built binary for ${target}"
return 1
fi
iree_debug "Binary: ${binary_path}"
iree_debug "Running from: ${run_cwd}"
# Run from original directory.
cd "${run_cwd}"
exec "${binary_path}" "${program_args[@]}"
}
# Build a bazel target only (no execution).
# Returns the binary path via stdout.
#
# Args:
# $1 - Bazel target label
# $2... - Bazel args
#
# Example:
# binary_path=$(iree_bazel_build_only "//tools:iree-compile" --config=debug)
iree_bazel_build_only() {
local target="${1}"
shift
local BAZEL_BIN
BAZEL_BIN=$(iree_get_bazel_command)
local -a bazel_args=("$@")
iree_debug "Building ${target}..."
if ! "${BAZEL_BIN}" build "${target}" "${bazel_args[@]}"; then
return $?
fi
# Pass same args so cquery uses same configuration.
iree_bazel_get_binary_path "${target}" "${bazel_args[@]}"
}
# Build a bazel target quietly - no output on success, full output on failure.
# Use this for run/try tools where we want clean output by default.
#
# Args:
# $1 - Bazel target label
# $2... - Bazel args
#
# Returns: exit code from bazel build
iree_bazel_build_quiet() {
local target="${1}"
shift
local BAZEL_BIN
BAZEL_BIN=$(iree_get_bazel_command)
iree_debug "Building ${target}..."
if iree_is_verbose; then
# Verbose mode: show everything.
"${BAZEL_BIN}" build "${target}" "$@"
return $?
fi
# Quiet mode: capture output, only show on failure.
# Force colors since we'll display to terminal on failure.
local log_file
log_file=$(mktemp)
trap "rm -f '${log_file}'" RETURN
if "${BAZEL_BIN}" build --color=yes "${target}" "$@" >"${log_file}" 2>&1; then
# Check for warnings in log and report count.
local warning_count
warning_count=$(grep -c -E '(warning:|WARNING:)' "${log_file}" 2>/dev/null || echo 0)
if [[ "${warning_count}" -gt 0 ]]; then
iree_warn "Build succeeded with ${warning_count} warning(s) (use -v to see)"
fi
return 0
else
local exit_code=$?
# Build failed - show the captured output.
cat "${log_file}" >&2
return ${exit_code}
fi
}