|
#!/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 "$@" |