# Booley sandbox image — agents and sim/synth run inside this container.
# Host worktree is bind-mounted at /work; no file sync needed.
#
# Build:
#   ./src/booley/data/docker/build.sh          (auto-builds wheel)
#   docker build -t booley-sandbox .           (wheel in dist/ required)

FROM ubuntu:24.04

# Tolerate host clock skew during image build (win_test): on Windows hosts the
# Docker Desktop/WSL2 VM inherits the host clock; a skewed host (common on
# dual-boot RTC-vs-UTC setups) makes apt reject freshly-published Release
# files as "not valid yet" and the build dies in the first layer. Date
# validation adds nothing here — package integrity is still GPG-verified.
RUN printf 'Acquire::Check-Date "false";\n' > /etc/apt/apt.conf.d/99-tolerate-clock-skew

# System deps — Ubuntu 24.04 ships Python 3.12; add deadsnakes for 3.13
RUN echo ">>> Installing system dependencies and Python 3.13..." \
    && apt-get update && apt-get install -y --no-install-recommends \
    git curl ca-certificates build-essential make gcc g++ \
    software-properties-common \
    ripgrep unzip xz-utils jq \
    autoconf flex bison gperf libfl-dev help2man \
    && add-apt-repository -y ppa:deadsnakes/ppa \
    && apt-get update && apt-get install -y --no-install-recommends \
    python3.13 python3.13-venv python3.13-dev \
    && rm -rf /var/lib/apt/lists/* \
    && ln -sf /usr/bin/python3.13 /usr/bin/python3 \
    && ln -sf /usr/bin/python3 /usr/bin/python

# Worktrees are bind-mounted from the host with different ownership;
# git refuses to operate without this.  Wildcard covers /work and any
# path the .git worktree pointer resolves to (e.g. the main repo's
# .git/worktrees/ directory which lives outside the mount).
RUN git config --system --add safe.directory '*' \
    && git config --system user.name "Booley Agent" \
    && git config --system user.email "booley@agent"

# NangateOpenCell PDK liberty (required by Yosys synthesis — static file,
# kept as a direct COPY so the layer is cached independently of code changes)
COPY src/booley/data/docker/pdk/NangateOpenCellLibrary_typical_ccs.lib /opt/pdk/cell/lib/

# Yosys + ABC (built from source — --shallow-submodules ensures the abc
# submodule is fetched even with a shallow parent clone)
ARG YOSYS_VERSION=v0.58
RUN echo ">>> Building Yosys ${YOSYS_VERSION} from source (this takes a while)..." \
    && apt-get update && apt-get install -y --no-install-recommends \
        pkg-config tcl-dev libreadline-dev libffi-dev zlib1g-dev \
    && rm -rf /var/lib/apt/lists/* \
    && git clone --depth 1 --recurse-submodules --shallow-submodules \
        --branch ${YOSYS_VERSION} \
        https://github.com/YosysHQ/yosys.git /tmp/yosys \
    && cd /tmp/yosys \
    && git submodule update --init \
    && make config-gcc \
    && make -j$(nproc) \
    && make install \
    && yosys-abc -c "quit" \
    && rm -rf /tmp/yosys

# OpenSTA for post-synthesis timing reports from the simple Yosys backend.
ARG CUDD_VERSION=3.0.0
ARG OPENSTA_REF=4249ab7b98246180db361b340b43ccfe2053054a
RUN echo ">>> Building OpenSTA ${OPENSTA_REF} from source..." \
    && apt-get update && apt-get install -y --no-install-recommends \
        cmake automake file libtool swig libeigen3-dev \
    && rm -rf /var/lib/apt/lists/* \
    && git clone --depth 1 --branch "${CUDD_VERSION}" \
        https://github.com/cuddorg/cudd.git /tmp/cudd \
    && cd /tmp/cudd \
    && autoreconf -fi \
    && ./configure --prefix=/opt/cudd \
    && make -j$(nproc) \
    && make install \
    && git init /tmp/OpenSTA \
    && cd /tmp/OpenSTA \
    && git remote add origin https://github.com/parallaxsw/OpenSTA.git \
    && git fetch --depth 1 origin "${OPENSTA_REF}" \
    && git checkout FETCH_HEAD \
    && cmake -S /tmp/OpenSTA -B /tmp/OpenSTA/build \
        -DCMAKE_BUILD_TYPE=RELEASE \
        -DCUDD_DIR=/opt/cudd \
    && cmake --build /tmp/OpenSTA/build --parallel $(nproc) \
    && install -m 755 /tmp/OpenSTA/build/sta /usr/local/bin/sta \
    && sta -version \
    && rm -rf /tmp/cudd /tmp/OpenSTA

# Nangate45 physical data for the OpenROAD timing engine (static, cached layer)
COPY src/booley/data/docker/pdk/nangate45/ /opt/pdk/nangate45/

# OpenROAD (pinned prebuilt .deb) — default timing engine: placement +
# buffering/sizing + estimated wire RC. The ubuntu-22.04 deb installs on
# 24.04 (Qt5 t64 Provides old names; deadsnakes ships libpython3.10).
# Newer 24.04-native debs sit behind a registration wall, so pin the last
# public asset.
ARG OPENROAD_RELEASE_TAG=2024-12-14
ARG OPENROAD_VERSION=2.0-17598-ga008522d8
RUN echo ">>> Installing OpenROAD ${OPENROAD_VERSION} (prebuilt deb)..." \
    && curl -fsSL "https://github.com/Precision-Innovations/OpenROAD/releases/download/${OPENROAD_RELEASE_TAG}/openroad_${OPENROAD_VERSION}_amd64-ubuntu-22.04.deb" -o /tmp/openroad.deb \
    && apt-get update && apt-get install -y --no-install-recommends /tmp/openroad.deb \
    && rm -f /tmp/openroad.deb && rm -rf /var/lib/apt/lists/* \
    && openroad -version

# Icarus Verilog is built from source because Ubuntu packages lag releases.
ARG ICARUS_VERSION=v13_0
RUN echo ">>> Building Icarus Verilog ${ICARUS_VERSION} from source..." \
    && apt-get update && apt-get install -y --no-install-recommends \
        libreadline-dev zlib1g-dev libbz2-dev \
    && rm -rf /var/lib/apt/lists/* \
    && git clone --depth 1 --branch ${ICARUS_VERSION} \
        https://github.com/steveicarus/iverilog.git /tmp/iverilog \
    && cd /tmp/iverilog \
    && sh autoconf.sh \
    && ./configure --prefix=/usr/local \
    && make -j$(nproc) \
    && make install \
    && rm -rf /tmp/iverilog

# Verilator is built from source so host installations do not affect results.
# v5.048 currently trips an internal error elaborating some downstream designs (force + unpacked arrays).
ARG VERILATOR_VERSION=v5.046
RUN echo ">>> Building Verilator ${VERILATOR_VERSION} from source (this takes a while)..." \
    && git clone --depth 1 --branch ${VERILATOR_VERSION} \
        https://github.com/verilator/verilator.git /tmp/verilator \
    && cd /tmp/verilator \
    && autoconf && ./configure --prefix=/usr/local \
    && make -j$(nproc) && make install \
    && rm -rf /tmp/verilator

# sv2v (pinned release binary)
ARG SV2V_VERSION=v0.0.12
RUN echo ">>> Installing sv2v ${SV2V_VERSION}..." \
    && curl -fsSL "https://github.com/zachjs/sv2v/releases/download/${SV2V_VERSION}/sv2v-Linux.zip" \
    -o /tmp/sv2v.zip && unzip -o /tmp/sv2v.zip -d /tmp/sv2v \
    && cp /tmp/sv2v/sv2v-Linux/sv2v /usr/local/bin/sv2v \
    && chmod +x /usr/local/bin/sv2v && rm -rf /tmp/sv2v.zip /tmp/sv2v

# Node.js 22 LTS + agent CLIs (pin versions for reproducible builds)
ARG CLAUDE_CODE_VERSION=2
ARG CODEX_VERSION=0
RUN echo ">>> Installing Node.js 22 LTS and agent CLIs..." \
    && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
    && apt-get install -y --no-install-recommends nodejs \
    && npm install -g "@anthropic-ai/claude-code@^${CLAUDE_CODE_VERSION}" "@openai/codex@^${CODEX_VERSION}" \
    && rm -rf /var/lib/apt/lists/*

# Non-root user for agent execution.
# Pin agent to uid/gid 1000 so it matches the typical first host user on native
# Linux; the base ubuntu:24.04 image already occupies 1000 with its default
# "ubuntu" user, so remove that first. Without this, bind-mounted project files
# (owned by host uid 1000) are unwritable by the container and the MCP server
# fails to create its .interactive_logs dir.
RUN userdel -r ubuntu 2>/dev/null || true \
    && groupadd -g 1000 agent \
    && useradd -m -s /bin/bash -u 1000 -g 1000 agent

# Install Booley package as root into system site-packages so it survives
# the booley-pip-local named volume mounting over /home/agent/.local.
# Dependencies also go system-wide to avoid the same masking problem.
RUN echo ">>> Installing Booley Python package (system-wide)..."
# Wheel filename uses the PEP 427-normalized distribution name (booley-rtl -> booley_rtl).
COPY dist/booley_rtl-*.whl /tmp/
RUN python -m ensurepip --default-pip \
    && python -m pip install --break-system-packages --no-cache-dir --ignore-installed /tmp/booley_rtl-*.whl \
    && rm -f /tmp/booley_rtl-*.whl

# EDA invocation/registry layer (ADR 0019 + 0022) — pinned EXACTLY to the
# flow-API-stable pair. These also arrive transitively via the wheel's
# dependencies; pinning them explicitly here makes the contract visible in
# the image and guards against a future loosening of the wheel constraint.
# Edalize only *invokes* the EDA tools built above; it does not manage their
# versions (ADR 0019 decision).
RUN python -m pip install --break-system-packages --no-cache-dir \
    "edalize==0.6.8" "fusesoc==2.4.6"

# Rust toolchain — installed as agent user directly (no root copy)
USER agent
RUN echo ">>> Installing Rust toolchain..." \
    && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/home/agent/.cargo/bin:${PATH}"

# Pre-create cargo cache dirs so named volumes inherit agent ownership
RUN mkdir -p /home/agent/.cargo/registry /home/agent/.cargo/git

# Pre-create pip user-install dirs so named volumes inherit agent ownership
RUN mkdir -p /home/agent/.local/lib/python3.13/site-packages /home/agent/.local/bin
ENV PATH="/home/agent/.local/bin:${PATH}"
ENV PYTHONUSERBASE="/home/agent/.local"

# Venue detection (ADR 0028): marks every process in this image — including
# bare `docker exec` sessions, which devcontainer remoteEnv cannot reach —
# as running inside the Booley Session Runtime. /.dockerenv is the fallback
# for containers from images built before this was added.
ENV BOOLEY_CONTAINER=1

# Claude Code kills an MCP tool call at 60s by default (measured on 2.1.205;
# ADR 0027 amendment 2026-07-09). 2h matches the Codex tool_timeout_sec the
# registrar writes. NOTE: the effective ceiling is still ~300s — the client's
# HTTP layer drops a response whose headers haven't arrived by then, and the
# booley server (json_response=True) sends headers only on completion — so
# server-side waits are capped at 270s; this ENV removes the 60s MCP-level
# kill underneath that. Image-level ENV so it reaches every entry path —
# tabs, docker exec, the Ticket-Mode runner — even when a stale devcontainer
# spec predates it.
ENV MCP_TOOL_TIMEOUT=7200000

# bwave — VCD waveform parser for RTL debug (built at image time so
# containers with --network none can use it without downloading crates).
# Once CI ships pre-built binaries in the wheel, this COPY+build is removed.
COPY --chown=agent:agent src/bwave/ /tmp/bwave/
RUN CARGO_TARGET_DIR=/tmp/bwave-target cargo build --release --locked --manifest-path /tmp/bwave/Cargo.toml \
    && install -m 755 /tmp/bwave-target/release/bwave /home/agent/.cargo/bin/bwave \
    && rm -rf /tmp/bwave /tmp/bwave-target

# bwave wrapper — delegates to the Python debug tool via installed package.
# Lives in /usr/local/bin (not /home/agent/.local/bin) so the
# booley-pip-local named volume can't shadow it at runtime.
USER root
RUN echo '#!/bin/bash\nexec python3 -m booley.tools.bwave "$@"' \
    > /usr/local/bin/bwave && chmod +x /usr/local/bin/bwave
USER agent

# Pre-create writable dirs for agent CLIs (bind-mounting individual files
# into a non-existent dir would create it as root, breaking writes)
RUN mkdir -p /home/agent/.codex /home/agent/.claude

WORKDIR /work
