Skip to content

Commit 376c6eb

Browse files
avi-starkwareclaude
andcommitted
scripts: add install_compiler_binaries.sh
New script to install Sierra compiler binaries (starknet-sierra-compile, starknet-native-compile). Reads versions from .txt files, handles LLVM env vars for native compile, and skips gracefully when LLVM 19 is missing. Includes compiler_versions.sh helper that reads version .txt files. Not yet called from install_cargo_tools.sh — wired up in a follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e2d42bf commit 376c6eb

5 files changed

Lines changed: 307 additions & 35 deletions

File tree

deployments/images/base/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ FROM ubuntu:24.04 AS base
88

99
COPY scripts/install_build_tools.sh scripts/install_build_tools.sh
1010
COPY scripts/install_cargo_tools.sh scripts/install_cargo_tools.sh
11+
COPY scripts/install_llvm19.sh scripts/install_llvm19.sh
1112
COPY scripts/dependencies.sh scripts/dependencies.sh
1213
COPY scripts/apt_utils.sh scripts/apt_utils.sh
1314
COPY scripts/cargo_tool_utils.sh scripts/cargo_tool_utils.sh
@@ -39,4 +40,4 @@ RUN ${VIRTUAL_ENV}/bin/pip install -r scripts/requirements.txt
3940
ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
4041

4142
# Cleanup.
42-
RUN rm -f scripts/install_build_tools.sh scripts/install_cargo_tools.sh scripts/cargo_tool_utils.sh scripts/dependencies.sh scripts/apt_utils.sh scripts/requirements.txt
43+
RUN rm -f scripts/install_build_tools.sh scripts/install_cargo_tools.sh scripts/install_llvm19.sh scripts/cargo_tool_utils.sh scripts/dependencies.sh scripts/apt_utils.sh scripts/requirements.txt

scripts/compiler_versions.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/env bash
2+
# Reads compiler binary versions from version files (the single source of truth).
3+
# Source this script to get $SIERRA_COMPILE_VERSION and $NATIVE_COMPILE_VERSION.
4+
5+
COMPILER_VERSIONS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
6+
REPO_ROOT="$(cd "${COMPILER_VERSIONS_SCRIPT_DIR}/.." && pwd)"
7+
8+
# Reads and validates a semver-shaped version from a one-line text file.
9+
# Trims surrounding whitespace; rejects anything that isn't `<num>.<num>.<num>`
10+
# with an optional `-<pre-release>` suffix. The validation matters because the
11+
# returned value flows into both `cargo install --version "$VAR"` and per-version
12+
# install-root paths; a whitespace-separated value would be split by cargo's
13+
# argument parser and `..` would let path construction escape the install root.
14+
_compiler_versions_semver_re='^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$'
15+
_compiler_versions_read() {
16+
# Sets the named-by-arg-1 variable to the validated version read from arg 2.
17+
# Returns 1 (and prints to stderr) on a malformed file. The caller checks
18+
# the return code; we cannot rely on `exit` because this function is
19+
# invoked from `source compiler_versions.sh`, where an exit inside a
20+
# subshell wouldn't propagate to the parent.
21+
local out_var=$1 file=$2 raw value
22+
raw=$(<"$file")
23+
value=$(printf '%s' "$raw" | tr -d '[:space:]')
24+
if [[ ! "$value" =~ $_compiler_versions_semver_re ]]; then
25+
echo "Error: invalid version in $file: '$raw'" >&2
26+
return 1
27+
fi
28+
printf -v "$out_var" '%s' "$value"
29+
}
30+
31+
_compiler_versions_read SIERRA_COMPILE_VERSION \
32+
"$REPO_ROOT/crates/apollo_infra_utils/src/cairo_compiler_version.txt" || return 1
33+
_compiler_versions_read NATIVE_COMPILE_VERSION \
34+
"$REPO_ROOT/crates/apollo_compile_to_native/src/native_compiler_version.txt" || return 1

scripts/dependencies.sh

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,43 +35,10 @@ function install_essential_deps_linux() {
3535
log_step "dependencies" "Essential Linux dependencies installed successfully"
3636
}
3737

38-
function setup_llvm_deps() {
39-
log_step "dependencies" "Setting up LLVM 19 dependencies..."
40-
case "$(uname)" in
41-
Darwin)
42-
echo "Detected macOS, using Homebrew..."
43-
brew update
44-
brew install llvm@19
45-
;;
46-
Linux)
47-
echo "Detected Linux, using apt..."
48-
$SUDO bash -c "$(declare -f apt_update_with_retry); $(declare -f apt_install_with_retry)"'
49-
echo "Downloading LLVM installation script..."
50-
curl https://apt.llvm.org/llvm.sh -Lo llvm.sh
51-
echo "Running LLVM 19 installation script..."
52-
bash ./llvm.sh 19 all
53-
rm -f ./llvm.sh
54-
echo "Installing LLVM-related packages (MLIR, Polly, etc.)..."
55-
apt_update_with_retry && apt_install_with_retry -y \
56-
libgmp3-dev \
57-
libmlir-19-dev \
58-
libpolly-19-dev \
59-
libzstd-dev \
60-
mlir-19-tools
61-
'
62-
;;
63-
*)
64-
echo "Error: Unsupported operating system"
65-
exit 1
66-
;;
67-
esac
68-
log_step "dependencies" "LLVM 19 dependencies setup completed"
69-
}
70-
7138
function main() {
7239
log_step "dependencies" "Starting dependencies installation..."
7340
[ "$(uname)" = "Linux" ] && install_essential_deps_linux
74-
setup_llvm_deps
41+
"${SCRIPT_DIR}/install_llvm19.sh"
7542
log_step "dependencies" "All dependencies installed successfully!"
7643
}
7744

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/bin/env bash
2+
# Installs Sierra compiler binaries (starknet-sierra-compile, starknet-native-compile).
3+
# Versions are read from plain text files (the single source of truth for both Rust and shell).
4+
#
5+
# Each version is installed under its own --root so multiple versions can coexist:
6+
# ${CARGO_TOOLS_ROOT:-$CARGO_HOME/tools}/<binary>-<version>/bin/<binary>
7+
# The script does not put anything on $PATH; callers that need the binary on
8+
# $PATH should use --dest.
9+
#
10+
# Usage:
11+
# scripts/install_compiler_binaries.sh # Install both
12+
# scripts/install_compiler_binaries.sh --sierra # Sierra only
13+
# scripts/install_compiler_binaries.sh --native # Native only
14+
# scripts/install_compiler_binaries.sh --dest /path/to/dir # Stage at fixed path
15+
# scripts/install_compiler_binaries.sh --auto-install-llvm # If LLVM 19 missing,
16+
# # run install_llvm19.sh
17+
#
18+
# --dest stages a copy at a caller-specified fixed path. Use for artifact
19+
# pipelines that require a stable known location (e.g. binaries to be uploaded
20+
# as build artifacts with a fixed object key).
21+
#
22+
# --auto-install-llvm opts into running scripts/install_llvm19.sh (which uses
23+
# sudo apt) when LLVM 19 is missing. Default is to fail with a clear message;
24+
# this avoids surprising callers with a system-package install side-effect.
25+
#
26+
# Stdout: absolute path of each installed binary, one per line.
27+
# Stderr: progress logs (so stdout stays parseable).
28+
29+
set -e
30+
31+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
32+
33+
# Source common apt utilities (for log_step).
34+
if [ -f "${SCRIPT_DIR}/apt_utils.sh" ]; then
35+
source "${SCRIPT_DIR}/apt_utils.sh"
36+
elif [ -f "./apt_utils.sh" ]; then
37+
source "./apt_utils.sh"
38+
else
39+
echo "Error: apt_utils.sh not found in ${SCRIPT_DIR} or current directory" >&2
40+
exit 1
41+
fi
42+
43+
# Source compiler version variables (provides $REPO_ROOT, $SIERRA_COMPILE_VERSION,
44+
# $NATIVE_COMPILE_VERSION). The sourced script validates the version strings.
45+
source "${SCRIPT_DIR}/compiler_versions.sh"
46+
47+
# Parse arguments.
48+
INSTALL_SIERRA=false
49+
INSTALL_NATIVE=false
50+
DEST_DIR=""
51+
AUTO_INSTALL_LLVM=false
52+
explicit_selection=false
53+
while [ $# -gt 0 ]; do
54+
case "$1" in
55+
--sierra) INSTALL_SIERRA=true; explicit_selection=true ;;
56+
--native) INSTALL_NATIVE=true; explicit_selection=true ;;
57+
--dest) DEST_DIR="$2"; shift ;;
58+
--auto-install-llvm) AUTO_INSTALL_LLVM=true ;;
59+
*) echo "Unknown argument: $1" >&2; exit 1 ;;
60+
esac
61+
shift
62+
done
63+
if ! $explicit_selection; then
64+
INSTALL_SIERRA=true
65+
INSTALL_NATIVE=true
66+
fi
67+
68+
CARGO_TOOLS_ROOT="${CARGO_TOOLS_ROOT:-${CARGO_HOME:-$HOME/.cargo}/tools}"
69+
70+
# LLVM/MLIR env vars are normally set by .cargo/config.toml, but `cargo install`
71+
# runs outside the workspace so they must be exported explicitly. We read the
72+
# three known LLVM-tooling variables from an explicit allow-list and validate
73+
# each value: cargo install runs untrusted build scripts, so any env var
74+
# leakage from this function would let a malicious .cargo/config.toml change
75+
# influence the install environment (e.g. LD_PRELOAD-adjacent attacks).
76+
readonly _LLVM_ENV_VARS=(LLVM_SYS_191_PREFIX MLIR_SYS_190_PREFIX TABLEGEN_190_PREFIX)
77+
readonly _LLVM_PATH_RE='^[A-Za-z0-9_/.:-]+$'
78+
function export_llvm_env_vars() {
79+
# If the caller already provided all three vars in the environment (e.g.
80+
# Dockerfiles use `ENV`), trust them and skip the config.toml lookup.
81+
# Cargo install will receive them via the normal inheritance.
82+
local name all_set=true
83+
for name in "${_LLVM_ENV_VARS[@]}"; do
84+
if [ -z "${!name+x}" ]; then
85+
all_set=false
86+
break
87+
fi
88+
done
89+
if $all_set; then
90+
return 0
91+
fi
92+
93+
# Otherwise read them from .cargo/config.toml. The fields are constrained
94+
# to a known allow-list (no greedy regex), and each value is validated
95+
# against a path-shape pattern: cargo install runs untrusted build
96+
# scripts, so leakage from this function would let a malicious config.toml
97+
# influence the install environment (e.g. LD_PRELOAD-adjacent attacks).
98+
local config_file="$REPO_ROOT/.cargo/config.toml"
99+
if [ ! -f "$config_file" ]; then
100+
echo "Error: missing LLVM env vars and ${config_file} not found." >&2
101+
echo " Set ${_LLVM_ENV_VARS[*]} in the environment, or run from a workspace with .cargo/config.toml." >&2
102+
exit 1
103+
fi
104+
local value
105+
for name in "${_LLVM_ENV_VARS[@]}"; do
106+
value=$(sed -nE "s/^${name}[[:space:]]*=[[:space:]]*\"([^\"]*)\".*/\\1/p" "$config_file")
107+
if [ -z "$value" ]; then
108+
echo "Error: ${name} not found in ${config_file}" >&2
109+
exit 1
110+
fi
111+
if [[ ! "$value" =~ $_LLVM_PATH_RE ]]; then
112+
echo "Error: ${name} has unexpected value in ${config_file}: '${value}'" >&2
113+
exit 1
114+
fi
115+
export "$name=$value"
116+
done
117+
}
118+
119+
# Atomic copy: write to a temp file in the destination directory then rename.
120+
# Avoids a parallel reader catching a half-written binary.
121+
function atomic_install() {
122+
local src=$1 dst=$2
123+
mkdir -p "$(dirname "$dst")"
124+
local tmp="${dst}.tmp.$$"
125+
cp "$src" "$tmp"
126+
mv -f "$tmp" "$dst"
127+
}
128+
129+
# Install <binary> at <version> into a per-version --root.
130+
# Reuses an existing install (caches across branch swaps); stages a copy at
131+
# caller-requested DEST_DIR if requested; prints the absolute installed path
132+
# on stdout.
133+
function install_compiler_if_needed() {
134+
local binary_name=$1
135+
local version=$2
136+
local install_root="${CARGO_TOOLS_ROOT}/${binary_name}-${version}"
137+
local versioned_binary="${install_root}/bin/${binary_name}"
138+
139+
if [ -x "$versioned_binary" ]; then
140+
log_step "install_compiler_binaries" "${binary_name} ${version} already installed, skipping" >&2
141+
else
142+
log_step "install_compiler_binaries" "Installing ${binary_name} ${version}..." >&2
143+
cargo install --locked --root "$install_root" "$binary_name" --version "$version" >&2
144+
log_step "install_compiler_binaries" "Installed ${binary_name} ${version}" >&2
145+
fi
146+
147+
# Optional caller-requested copy at a fixed path (artifact pipelines).
148+
if [ -n "$DEST_DIR" ]; then
149+
atomic_install "$versioned_binary" "$DEST_DIR/${binary_name}"
150+
fi
151+
152+
echo "$versioned_binary"
153+
}
154+
155+
if $INSTALL_SIERRA; then
156+
install_compiler_if_needed "starknet-sierra-compile" "$SIERRA_COMPILE_VERSION"
157+
fi
158+
159+
if $INSTALL_NATIVE; then
160+
# starknet-native-compile requires LLVM 19. By default we fail with a clear
161+
# message rather than silently invoking sudo apt; pass --auto-install-llvm
162+
# to opt into the system-level install via install_llvm19.sh.
163+
if ! command -v llvm-config-19 &>/dev/null; then
164+
if $AUTO_INSTALL_LLVM; then
165+
log_step "install_compiler_binaries" "LLVM 19 not found; --auto-install-llvm set, running install_llvm19.sh..." >&2
166+
"${SCRIPT_DIR}/install_llvm19.sh" >&2
167+
else
168+
echo "Error: LLVM 19 not installed (llvm-config-19 not on PATH)." >&2
169+
echo " Run scripts/install_llvm19.sh first, or pass --auto-install-llvm to run it now." >&2
170+
exit 1
171+
fi
172+
fi
173+
export_llvm_env_vars
174+
install_compiler_if_needed "starknet-native-compile" "$NATIVE_COMPILE_VERSION"
175+
fi

scripts/install_llvm19.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/env bash
2+
# Installs LLVM 19 + MLIR/Polly dev packages required by starknet-native-compile.
3+
# Idempotent: skips if llvm-config-19 is already on PATH.
4+
#
5+
# Sourced by both scripts/dependencies.sh and scripts/install_compiler_binaries.sh
6+
# (the latter as an opt-in self-recovery fallback when LLVM 19 is missing).
7+
8+
set -e
9+
10+
# Pinned SHA-256 of https://apt.llvm.org/llvm.sh. Recorded 2026-05-14.
11+
#
12+
# Why pin: llvm.sh runs as root in our CI / Docker builds. TLS authenticates the
13+
# server, but not the *content* — an apt.llvm.org compromise, hijacked DNS, or
14+
# even an accidental upstream regression could quietly serve a different script.
15+
# Pinning a SHA means we trust this specific reviewed version, not "whatever the
16+
# URL serves today".
17+
#
18+
# If sha256sum -c fails (loud build break), one of two things happened:
19+
# 1. apt.llvm.org legitimately updated llvm.sh. Read the new file, review the
20+
# diff (compare against the previous pinned commit's version), confirm it's
21+
# safe, then bump LLVM_SH_SHA256 below in a reviewed commit. This is the
22+
# change-control event we want.
23+
# 2. The upstream or network is compromised. Do NOT bump the SHA until the
24+
# cause is investigated.
25+
#
26+
# Same supply-chain hygiene as Cargo.lock pinning crate hashes.
27+
LLVM_SH_SHA256="14a4eda1349f23acf9dc0b564ed44b21bce3bd1703c78b5f7488870d7c6fe68f"
28+
29+
[[ ${UID} == "0" ]] || SUDO="sudo"
30+
31+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
32+
if [ -f "${SCRIPT_DIR}/apt_utils.sh" ]; then
33+
source "${SCRIPT_DIR}/apt_utils.sh"
34+
elif [ -f "./apt_utils.sh" ]; then
35+
source "./apt_utils.sh"
36+
else
37+
echo "Error: apt_utils.sh not found in ${SCRIPT_DIR} or current directory" >&2
38+
exit 1
39+
fi
40+
41+
function install_llvm19() {
42+
if command -v llvm-config-19 &>/dev/null; then
43+
log_step "install_llvm19" "LLVM 19 already installed, skipping"
44+
return 0
45+
fi
46+
log_step "install_llvm19" "Setting up LLVM 19 dependencies..."
47+
case "$(uname)" in
48+
Darwin)
49+
echo "Detected macOS, using Homebrew..."
50+
brew update
51+
brew install llvm@19
52+
;;
53+
Linux)
54+
echo "Detected Linux, using apt..."
55+
local workdir llvm_sh
56+
workdir=$(mktemp -d)
57+
llvm_sh="${workdir}/llvm.sh"
58+
# Bash-specific RAII: the RETURN trap fires when this function returns
59+
# (success, explicit return, or set -e early exit), guaranteeing cleanup
60+
# regardless of which step below fails. Equivalent to try/finally scoped
61+
# to the function.
62+
trap 'rm -rf "$workdir"' RETURN
63+
echo "Downloading LLVM installation script..."
64+
curl --proto "=https" --tlsv1.2 --fail -L -o "$llvm_sh" https://apt.llvm.org/llvm.sh
65+
echo "Verifying llvm.sh checksum..."
66+
echo "${LLVM_SH_SHA256} ${llvm_sh}" | sha256sum -c -
67+
echo "Running LLVM 19 installation script..."
68+
$SUDO bash "$llvm_sh" 19 all
69+
echo "Installing LLVM-related packages (MLIR, Polly, etc.)..."
70+
# `bash -c '...'` spawns a fresh shell that doesn't inherit functions
71+
# from the parent shell, only exported env vars. The apt_utils.sh
72+
# helpers sourced above therefore aren't reachable inside the sudo
73+
# subshell. `declare -f` splices the function definitions into the
74+
# subshell's script text so the apt_*_with_retry calls below resolve.
75+
$SUDO bash -c "$(declare -f apt_update_with_retry); $(declare -f apt_install_with_retry)"'
76+
apt_update_with_retry && apt_install_with_retry -y \
77+
libgmp3-dev \
78+
libmlir-19-dev \
79+
libpolly-19-dev \
80+
libzstd-dev \
81+
mlir-19-tools
82+
'
83+
;;
84+
*)
85+
echo "Error: Unsupported operating system" >&2
86+
exit 1
87+
;;
88+
esac
89+
log_step "install_llvm19" "LLVM 19 installed successfully"
90+
}
91+
92+
# Run when invoked directly; allow sourcing without auto-running.
93+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
94+
install_llvm19
95+
fi

0 commit comments

Comments
 (0)