Skip to content

Instantly share code, notes, and snippets.

@aa755
Created July 9, 2026 21:13
Show Gist options
  • Select an option

  • Save aa755/77d18fe96b76bc61b4f155a86e7b076c to your computer and use it in GitHub Desktop.

Select an option

Save aa755/77d18fe96b76bc61b4f155a86e7b076c to your computer and use it in GitHub Desktop.
cpp2v Linux amd64 libstdc++ Docker sysroot demo for BRiCk PR 246

cpp2v Linux libstdc++ demo

This gist demonstrates the CPP2V_* environment variables from SkyLabsAI/BRiCk PR 246 using only public inputs.

It runs on macOS with native cpp2v, creates a Linux amd64 Ubuntu sysroot from Docker, installs GCC/G++ 15 and libstdc++ headers inside that container, and then invokes cpp2v on a small C++ file using std::vector.

Example:

./demo-linux-libstdcxx-macos.sh \
  --sysroot "$HOME/sysroots/ubuntu-25.10-amd64-gcc15" \
  --rebuild-sysroot \
  --cpp2v "$HOME/fv-workspace/workspace/_build/install/default/bin/cpp2v"

The script checks the same target setup with native Clang using -fsyntax-only, then runs cpp2v. The generated AST should contain GCC/libstdc++ implementation names such as std::__glibcxx_assert_fail and std::_Vector_base.

Prerequisites:

  • Docker
  • Homebrew LLVM on macOS, or LLVM_PREFIX pointing at a Clang/LLVM install
  • A cpp2v binary built from a BRiCk version that supports the CPP2V_* variables
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: demo-linux-libstdcxx-macos.sh [options]
Create a Linux amd64 GCC/libstdc++ sysroot from Docker, then run a native
macOS cpp2v binary as if Clang were parsing for Linux amd64.
Options:
--sysroot DIR Sysroot directory. Default: $HOME/sysroots/ubuntu-25.10-amd64-gcc15
--image IMAGE Docker image. Default: ubuntu:25.10
--gcc-version VER GCC major version. Default: 15
--cpp2v PATH cpp2v binary. Default: $CPP2V, then workspace cpp2v.
--rebuild-sysroot Delete and recreate the sysroot.
-h, --help Show this help.
Environment overrides:
LLVM_PREFIX Homebrew LLVM prefix. Default: brew --prefix llvm, then common paths.
CPP2V Same as --cpp2v.
The key exported environment is:
CPP2V_RESOURCE_DIR Host Clang builtin headers.
CPP2V_TARGET x86_64-linux-gnu.
CPP2V_SYSROOT Extracted Linux amd64 sysroot.
CPP2V_GCC_TOOLCHAIN $CPP2V_SYSROOT/usr.
CPP2V_CXX_INCLUDE_DIRS
Colon-separated GCC/libstdc++ include directories.
cpp2v from PR 246 translates those variables into Clang frontend arguments.
EOF
}
SYSROOT="${SYSROOT:-$HOME/sysroots/ubuntu-25.10-amd64-gcc15}"
IMAGE="${IMAGE:-ubuntu:25.10}"
GCC_VERSION="${GCC_VERSION:-15}"
CPP2V_BIN="${CPP2V:-}"
REBUILD_SYSROOT=0
DEMO_CONTAINER=""
while [[ $# -gt 0 ]]; do
case "$1" in
--sysroot)
SYSROOT="$2"
shift 2
;;
--image)
IMAGE="$2"
shift 2
;;
--gcc-version)
GCC_VERSION="$2"
shift 2
;;
--cpp2v)
CPP2V_BIN="$2"
shift 2
;;
--rebuild-sysroot)
REBUILD_SYSROOT=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "error: unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
log() {
printf '\n==> %s\n' "$*"
}
die() {
echo "error: $*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
find_llvm_prefix() {
if [[ -n "${LLVM_PREFIX:-}" ]]; then
printf '%s\n' "$LLVM_PREFIX"
return
fi
if command -v brew >/dev/null 2>&1; then
if prefix="$(brew --prefix llvm 2>/dev/null)"; then
printf '%s\n' "$prefix"
return
fi
if prefix="$(brew --prefix llvm@19 2>/dev/null)"; then
printf '%s\n' "$prefix"
return
fi
fi
for candidate in /opt/homebrew/opt/llvm /usr/local/opt/llvm; do
if [[ -x "$candidate/bin/clang++" ]]; then
printf '%s\n' "$candidate"
return
fi
done
die "could not locate Homebrew LLVM; set LLVM_PREFIX"
}
find_cpp2v() {
if [[ -n "$CPP2V_BIN" ]]; then
[[ -x "$CPP2V_BIN" ]] || die "cpp2v is not executable: $CPP2V_BIN"
printf '%s\n' "$CPP2V_BIN"
return
fi
local workspace_cpp2v="$HOME/fv-workspace/workspace/_build/install/default/bin/cpp2v"
if [[ -x "$workspace_cpp2v" ]]; then
printf '%s\n' "$workspace_cpp2v"
return
fi
die "could not find cpp2v; pass --cpp2v PATH or set CPP2V"
}
extract_sysroot_from_docker() {
if [[ "$REBUILD_SYSROOT" == "1" ]]; then
rm -rf "$SYSROOT"
fi
if [[ -f "$SYSROOT/usr/include/c++/$GCC_VERSION/vector" ]]; then
log "Using existing sysroot: $SYSROOT"
return
fi
require_cmd docker
log "Creating Linux amd64 sysroot from $IMAGE"
mkdir -p "$SYSROOT"
DEMO_CONTAINER="$(docker create --platform linux/amd64 "$IMAGE" sleep infinity)"
cleanup_container() {
if [[ -n "$DEMO_CONTAINER" ]]; then
docker rm -f "$DEMO_CONTAINER" >/dev/null 2>&1 || true
DEMO_CONTAINER=""
fi
}
trap cleanup_container EXIT
docker start "$DEMO_CONTAINER" >/dev/null
docker exec "$DEMO_CONTAINER" bash -lc "
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates \
gcc-$GCC_VERSION \
g++-$GCC_VERSION \
libc6-dev \
linux-libc-dev
test -d /usr/include/c++/$GCC_VERSION
test -d /usr/include/x86_64-linux-gnu/c++/$GCC_VERSION
test -d /usr/lib/gcc/x86_64-linux-gnu/$GCC_VERSION
"
docker export "$DEMO_CONTAINER" |
tar -C "$SYSROOT" -xf - \
usr/include \
"usr/lib/gcc/x86_64-linux-gnu/$GCC_VERSION" \
usr/lib/x86_64-linux-gnu
mkdir -p "$SYSROOT/lib"
if [[ ! -e "$SYSROOT/lib/x86_64-linux-gnu" ]]; then
ln -s ../usr/lib/x86_64-linux-gnu "$SYSROOT/lib/x86_64-linux-gnu"
fi
cleanup_container
trap - EXIT
}
check_sysroot() {
local required=(
"$SYSROOT/usr/include/c++/$GCC_VERSION/vector"
"$SYSROOT/usr/include/x86_64-linux-gnu/c++/$GCC_VERSION/bits/c++config.h"
"$SYSROOT/usr/lib/gcc/x86_64-linux-gnu/$GCC_VERSION/include/immintrin.h"
"$SYSROOT/usr/lib/gcc/x86_64-linux-gnu/$GCC_VERSION/include/avxintrin.h"
"$SYSROOT/usr/lib/x86_64-linux-gnu/libstdc++.so.6"
"$SYSROOT/usr/lib/x86_64-linux-gnu/crt1.o"
)
for path in "${required[@]}"; do
[[ -e "$path" ]] || die "sysroot is missing $path"
done
}
write_demo_files() {
local dir="$1"
cat >"$dir/demo.cpp" <<'CPP'
#include <cstdint>
#include <vector>
#ifndef __linux__
#error "cpp2v should parse this as Linux"
#endif
#ifndef __x86_64__
#error "cpp2v should parse this as x86_64"
#endif
std::uint64_t sum_vector(const std::vector<std::uint64_t> &xs) {
std::uint64_t total = 0;
for (std::uint64_t x : xs) {
total += x;
}
return total;
}
CPP
local escaped_dir
escaped_dir="$(printf '%s' "$dir" | sed 's/\\/\\\\/g; s/"/\\"/g')"
cat >"$dir/compile_commands.json" <<EOF
[
{
"directory": "$escaped_dir",
"command": "clang++ -std=c++20 -march=haswell -c $escaped_dir/demo.cpp",
"file": "$escaped_dir/demo.cpp"
}
]
EOF
}
main() {
require_cmd tar
extract_sysroot_from_docker
check_sysroot
local llvm_prefix
llvm_prefix="$(find_llvm_prefix)"
[[ -x "$llvm_prefix/bin/clang++" ]] || die "missing clang++ under $llvm_prefix"
local cpp2v
cpp2v="$(find_cpp2v)"
export PATH="$llvm_prefix/bin:$PATH"
export CPP2V_RESOURCE_DIR="$("$llvm_prefix/bin/clang++" -print-resource-dir)"
export CPP2V_TARGET="x86_64-linux-gnu"
export CPP2V_SYSROOT="$SYSROOT"
export CPP2V_GCC_TOOLCHAIN="$SYSROOT/usr"
export CPP2V_CXX_INCLUDE_DIRS="$SYSROOT/usr/include/c++/$GCC_VERSION:$SYSROOT/usr/include/x86_64-linux-gnu/c++/$GCC_VERSION:$SYSROOT/usr/include/c++/$GCC_VERSION/backward"
local tmp
tmp="$(mktemp -d "${TMPDIR:-/tmp}/cpp2v-linux-libstdcxx.XXXXXX")"
write_demo_files "$tmp"
local cxx_flags=(
"--target=$CPP2V_TARGET"
"--sysroot=$CPP2V_SYSROOT"
"--gcc-toolchain=$CPP2V_GCC_TOOLCHAIN"
"-nostdinc++"
"-isystem" "$SYSROOT/usr/include/c++/$GCC_VERSION"
"-isystem" "$SYSROOT/usr/include/x86_64-linux-gnu/c++/$GCC_VERSION"
"-isystem" "$SYSROOT/usr/include/c++/$GCC_VERSION/backward"
"-std=c++20"
"-march=haswell"
)
log "Checking the same target setup with native Clang"
"$llvm_prefix/bin/clang++" "${cxx_flags[@]}" -fsyntax-only "$tmp/demo.cpp"
log "Running native cpp2v against Linux amd64 GCC/libstdc++ headers"
"$cpp2v" -p "$tmp" "$tmp/demo.cpp" --no-elaborate -o "$tmp/demo_cpp.v"
cat <<EOF
cpp2v succeeded.
Generated AST:
$tmp/demo_cpp.v
Environment used:
CPP2V_RESOURCE_DIR=$CPP2V_RESOURCE_DIR
CPP2V_TARGET=$CPP2V_TARGET
CPP2V_SYSROOT=$CPP2V_SYSROOT
CPP2V_GCC_TOOLCHAIN=$CPP2V_GCC_TOOLCHAIN
CPP2V_CXX_INCLUDE_DIRS=$CPP2V_CXX_INCLUDE_DIRS
EOF
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment