diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 55b2adf8d4..ae66a06ed5 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -32,33 +32,68 @@ jobs: large-packages: true docker-images: false swap-storage: true + - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5.0.0 + - name: Set up Buildx + uses: docker/setup-buildx-action@v3.11.1 + + # ----- PR and non-main branch steps ----- + # For PRs: Build image but do not push and run smoke tests - name: Build Docker Image (No Push) if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref != 'refs/heads/main') - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: ./Dockerfile push: false + load: true + tags: arc:test + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Smoke Tests + if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref != 'refs/heads/main') + run: | + docker run --rm arc:test bash -lc \ + "cd /home/mambauser/Code/ARC && micromamba run -n arc_env pytest dockerfiles/docker_tests/test_docker_smoke.py -m smoke -q" + + # ----- Main branch only steps ----- + # For pushes to main: Build, run smoke tests, and push to Docker Hub + - name: Build test stage (main) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/build-push-action@v6.18.0 + with: + context: . + file: ./Dockerfile + load: true + tags: arc:final-${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run smoke (main, test stage) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: | + docker run --rm arc:final-${{ github.sha }} bash -lc \ + "cd /home/mambauser/Code/ARC && micromamba run -n arc_env pytest dockerfiles/docker_tests/test_docker_smoke.py -m smoke -q" - name: Login to Docker Hub if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.5.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Docker Buildx - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/setup-buildx-action@v3.0.0 - - - name: Build and Push Docker Image + - name: Build final and push (main) if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: ./Dockerfile push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/arc:latest + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/arc:latest + ${{ secrets.DOCKERHUB_USERNAME }}/arc:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 643470ad58..48be03f05f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,243 +1,95 @@ -# Stage 1: RMG setup -# RMG Dockerfile +# Stage 1: RMG setup & ARC setup # The parent image is the base image that the Dockerfile builds upon. # The RMG installation instructions suggest Anaconda for installation by source, however, we use micromamba for the Docker image due to its smaller size and less overhead. -# https://hub.docker.com/layers/mambaorg/micromamba/1.4.3-jammy/images/sha256-0c7c97be938c5522dcb9e1737bfa4499c53f6cf9e32e53897607a57ba8b148d5?context=explore -# We are using the sha256 hash to ensure that the image is not updated without our knowledge. It considered best practice to use the sha256 hash -FROM --platform=linux/amd64 mambaorg/micromamba@sha256:20fb02f2d1160265f7fabaf1601707a902ae65c6dc9e053d305441182450c368 AS rmg-stage +# Installation of ARC will also be done in this stage. +FROM --platform=linux/amd64 mambaorg/micromamba:2.2-ubuntu24.04 AS builder -# Set the user as root +# Set ARGS +ARG RMG_PY_BRANCH=main +ARG RMG_DATABASE_BRANCH=main +ARG ARC_BRANCH=main + +# Set Global ENV +ENV MAMBA_ROOT_PREFIX=/opt/conda +ENV PATH=$MAMBA_ROOT_PREFIX/bin:$PATH +ENV MAMBA_DOCKERFILE_ACTIVATE=1 + +# Switch to root to install dependencies USER root -# Create a login user named rmguser -# Create a login user named rmguser -ARG NEW_MAMBA_USER=rmguser -ARG NEW_MAMBA_USER_ID=1000 -ARG NEW_MAMBA_USER_GID=1000 -RUN usermod "--login=${NEW_MAMBA_USER}" "--home=/home/${NEW_MAMBA_USER}" \ - --move-home "-u ${NEW_MAMBA_USER_ID}" "${MAMBA_USER}" && \ - groupmod "--new-name=${NEW_MAMBA_USER}" \ - "-g ${NEW_MAMBA_USER_GID}" "${MAMBA_USER}" && \ - echo "${NEW_MAMBA_USER}" > "/etc/arg_mamba_user" && \ - : - -# Set the environment variables -ARG MAMBA_ROOT_PREFIX=/opt/conda -ENV MAMBA_USER=$NEW_MAMBA_USER -ENV BASE=$MAMBA_ROOT_PREFIX - -# Install system dependencies -# -# List of deps and why they are needed: -# - make, gcc, g++ for building RMG -# - git for downloading RMG respoitories -# - wget for downloading conda install script -# - libxrender1 required by RDKit -# Clean up the apt cache to reduce the size of the image RUN apt-get update && apt-get install -y \ - git \ - gcc \ - g++ \ - make \ - libgomp1\ - libxrender1 \ - sudo \ - nano \ - && apt-get clean \ - && apt-get autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo "${NEW_MAMBA_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers -# Change user to the non-root user -USER $MAMBA_USER - -# Make directory for RMG-Py and RMG-database -RUN mkdir -p /home/rmguser/Code - -# Change working directory to Code -WORKDIR /home/rmguser/Code - -# ------------------------------------------------------------------ clone & checkout -WORKDIR /home/rmguser/Code -RUN git clone --filter=blob:none --no-checkout https://github.com/ReactionMechanismGenerator/RMG-Py.git RMG-Py \ - && git -C RMG-Py checkout --detach 55464c54d1fa61b531e865682df598d33718597d \ - && git clone --filter=blob:none --depth 1 https://github.com/ReactionMechanismGenerator/RMG-database.git RMG-database - - - - -ENV PATH=/opt/conda/envs/rmg_env/bin:/home/rmguser/Code/RMG-Py:$PATH \ - PYTHONPATH=/home/rmguser/Code/RMG-Py - - -ENV JULIA_DEPOT_PATH="/home/rmguser/julia-ver/packages" -ENV JULIA_HISTORY=/home/rmguser/repl_history.jl -# Since 1.9.0 Julia, the CPU target is set to "native" by default. This is not ideal for a Docker image, so we set it to a list of common CPU targets -# This avoids the need to compile the Julia packages for the specific CPU architecture of the host machine -ENV JULIA_CPU_TARGET="x86-64,haswell,skylake,broadwell,znver1,znver2,znver3,cascadelake,icelake-client,cooperlake,generic,native" -# Install RMS -# The extra arguments are required to install PyCall and RMS in this Dockerfile. Will not work without them. -# Final command is to compile the RMS during Docker build - This will reduce the time it takes to run RMS for the first time -# Julia + PyCall + RMS in rmg_env -ENV CONDA_JL_CONDA_EXE=/opt/conda/envs/rmg_env/bin/conda -# ------------------------------------------------------------------ env create -WORKDIR /home/rmguser/Code/RMG-Py -RUN micromamba create -y -n rmg_env -f environment.yml \ - && touch /opt/conda/envs/rmg_env/condarc-julia.yml \ - && micromamba install -y -n rmg_env -c conda-forge conda \ - && micromamba clean -a -y \ - && echo "export PYTHONPATH=/home/rmguser/Code/RMG-Py" >> ~/.bashrc \ - && echo "export PATH=/home/rmguser/Code/RMG-Py:$PATH" >> ~/.bashrc \ - && micromamba run -n rmg_env make -j"$(nproc)" \ - && micromamba run -n rmg_env bash -lc "\ - julia -e 'ENV[\"CONDA_JL_CONDA_EXE\"]=\"${CONDA_JL_CONDA_EXE}\"; using Pkg; \ - Pkg.add(PackageSpec(name=\"PyCall\", rev=\"master\")); Pkg.build(\"PyCall\"); \ - Pkg.add(PackageSpec(name=\"ReactionMechanismSimulator\", url=\"https://github.com/ReactionMechanismGenerator/ReactionMechanismSimulator.jl\", rev=\"8dbb07eedebaacf9b9f77081623c572d054b9a6f\")); \ - using ReactionMechanismSimulator'; \ - python -c 'import julia, diffeqpy; julia.install(); diffeqpy.install()'; \ - python-jl -c 'from pyrms import rms' \ - " - -RUN micromamba run -n rmg_env python-jl /home/rmguser/Code/RMG-Py/rmg.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py \ -# delete the results, preserve input.py -&& mv /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py /home/rmguser/Code/RMG-Py/examples/input.py \ -&& rm -rf /home/rmguser/Code/RMG-Py/examples/rmg/minimal/* \ -&& mv /home/rmguser/Code/RMG-Py/examples/input.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py - -# Installing ARC -# Change directory to Code -WORKDIR /home/rmguser/Code - -# Clone main branch ARC repository from GitHub and set as working directory -RUN git clone -b main https://github.com/ReactionMechanismGenerator/ARC.git -WORKDIR /home/rmguser/Code/ARC - -# Set environment variables for the Docker run and container -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/ARC" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/AutoTST" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/TS-GCN" -ENV PATH=/home/rmguser/Code/ARC:$PATH - -# Install ARC Environment -COPY --chown=rmguser:rmguser ./environment.yml /home/rmguser/Code/ARC/environment.yml + git gcc g++ make wget libxrender1 ca-certificates sudo nano make && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + mkdir -p /home/mambauser/Code && \ + chown -R mambauser:mambauser /home/mambauser && \ + chown -R mambauser:mambauser /opt/conda && \ + chown -R mambauser:mambauser /home/mambauser + +# Change to unprivileged user +USER mambauser +ENV MAMBA_USER=mambauser + +# Set JuliaUp PATH and install Julia 1.10 as req. by RMG +ENV PATH="/home/mambauser/.juliaup/bin:$PATH" +RUN wget -qO- https://install.julialang.org | sh -s -- --yes --default-channel 1.10 + +# Switch directory to Code and RMG clone +WORKDIR /home/mambauser/Code +RUN git clone --branch ${RMG_PY_BRANCH} https://github.com/ReactionMechanismGenerator/RMG-Py.git && \ + git clone --branch ${RMG_DATABASE_BRANCH} https://github.com/ReactionMechanismGenerator/RMG-database.git && \ + git clone --branch ${ARC_BRANCH} https://github.com/ReactionMechanismGenerator/ARC.git + +# Create RMG-Py environment +RUN micromamba create -y -n rmg_env -f /home/mambauser/Code/RMG-Py/environment.yml && \ + micromamba run -n rmg_env micromamba install -y -c conda-forge pyjuliacall conda && \ + micromamba clean --all --yes && \ + micromamba run -n rmg_env make -C /home/mambauser/Code/RMG-Py -j"$(nproc)" && \ + micromamba run -n rmg_env bash -c "\ + cd /home/mambauser/Code/RMG-Py && \ + source install_rms.sh \ + " + +WORKDIR /home/mambauser/Code/ARC RUN micromamba create -y -n arc_env -f environment.yml --channel-priority flexible && \ + micromamba install -y -n arc_env -c conda-forge pytest && \ micromamba clean --all -f -y && \ -bash -euxo pipefail <<'EOF' -PYDIR=$(echo /opt/conda/envs/arc_env/lib/python*/site-packages) - -# cache -rm -rf /home/rmguser/.cache/pip /home/rmguser/.cache/yarn - -# strip compiled cruft -find "$PYDIR" -type f \( -name "*.a" -o -name "*.py[co]" \) -delete -find "$PYDIR" -type d -name "__pycache__" -prune -exec rm -rf {} + + micromamba run -n arc_env bash -euxo pipefail -c \ + "make compile && bash ./devtools/install_pyrdl.sh" && \ + micromamba clean --all --yes + +# Stage 2: Final image +# The final image is based on the same micromamba image, but we copy over the installed RMG and ARC from the builder stage. +# This keeps the final image size smaller and avoids unnecessary layers. +FROM --platform=linux/amd64 mambaorg/micromamba:2.2-ubuntu24.04 + +ENV MAMBA_ROOT_PREFIX=/opt/conda +ENV PATH=$MAMBA_ROOT_PREFIX/bin:/home/mambauser/.juliaup/bin:/home/mambauser/Code/RMG-Py:/home/mambauser/Code/ARC:$PATH +ENV PYTHONPATH="/home/mambauser/Code/RMG-Py:/home/mambauser/Code/ARC" +ENV RMG_PY_DIR="/home/mambauser/Code/RMG-Py" +ENV ARC_DIR="/home/mambauser/Code/ARC" +ENV MAMBA_DOCKERFILE_ACTIVATE=1 -# drop test directories in one traversal -find "$PYDIR" -type d \( -path "*/scipy/tests" -o -path "*/numpy/tests" -o -path "*/pandas/tests" \) \ - -prune -exec rm -rf {} + - -# remove cython sources -find "$PYDIR" -name "*.pyx" -delete -rm -f "$PYDIR/uvloop/loop.c" -EOF - -FROM --platform=linux/amd64 mambaorg/micromamba@sha256:20fb02f2d1160265f7fabaf1601707a902ae65c6dc9e053d305441182450c368 AS arc-stage USER root -ARG NEW_MAMBA_USER=rmguser -ARG NEW_MAMBA_USER_ID=1000 -ARG NEW_MAMBA_USER_GID=1000 -RUN usermod "--login=${NEW_MAMBA_USER}" "--home=/home/${NEW_MAMBA_USER}" \ - --move-home "-u ${NEW_MAMBA_USER_ID}" "${MAMBA_USER}" && \ - groupmod "--new-name=${NEW_MAMBA_USER}" \ - "-g ${NEW_MAMBA_USER_GID}" "${MAMBA_USER}" && \ - echo "${NEW_MAMBA_USER}" > "/etc/arg_mamba_user" && \ - : - -# Set the environment variables -ARG MAMBA_ROOT_PREFIX=/opt/conda -ENV MAMBA_USER=$NEW_MAMBA_USER -ENV BASE=$MAMBA_ROOT_PREFIX - -# Install system dependencies -# -# List of deps and why they are needed: -# - make, gcc, g++ for building RMG -# - git for downloading RMG respoitories -# - wget for downloading conda install script -# - libxrender1 required by RDKit -# Clean up the apt cache to reduce the size of the image -RUN apt-get update && apt-get install -y \ - git \ - gcc \ - g++ \ - make \ - libgomp1\ - libxrender1 \ - sudo \ - nano \ - && apt-get clean \ - && apt-get autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && echo "${NEW_MAMBA_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ - && printf '\n# print cheat-sheet once per interactive Bash\nif [[ $- == *i* && $SHLVL -eq 1 ]]; then\n aliases\nfi\n' \ - >> /etc/bash.bashrc -# Change user to the non-root user -USER $MAMBA_USER - -# Make directory for RMG-Py and RMG-database -RUN mkdir -p /home/rmguser/Code - -COPY --from=rmg-stage --chown=rmguser:rmguser /opt/conda /opt/conda -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/RMG-Py /home/rmguser/Code/RMG-Py -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/RMG-database /home/rmguser/Code/RMG-database -COPY --from=rmg-stage --chown=rmguser:rmguser /home/rmguser/Code/ARC /home/rmguser/Code/ARC - - -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/ARC" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/AutoTST" -ENV PYTHONPATH="${PYTHONPATH}:/home/rmguser/Code/TS-GCN" -ENV PATH=/home/rmguser/Code/ARC:/home/rmguser/Code/RMG-Py:/home/rmguser/Code/RMG-database:$PATH -ENV PYTHONPATH=/home/rmguser/Code/ARC:/home/rmguser/Code/RMG-Py:/home/rmguser/Code/RMG-database:$PYTHONPATH - -ENV JULIA_DEPOT_PATH="/home/rmguser/julia-ver/packages" -ENV JULIA_HISTORY=/home/rmguser/repl_history.jl -# Since 1.9.0 Julia, the CPU target is set to "native" by default. This is not ideal for a Docker image, so we set it to a list of common CPU targets -# This avoids the need to compile the Julia packages for the specific CPU architecture of the host machine -ENV JULIA_CPU_TARGET="x86-64,haswell,skylake,broadwell,znver1,znver2,znver3,cascadelake,icelake-client,cooperlake,generic" -# Install RMS -# The extra arguments are required to install PyCall and RMS in this Dockerfile. Will not work without them. -# Final command is to compile the RMS during Docker build - This will reduce the time it takes to run RMS for the first time -# Julia + PyCall + RMS in rmg_env -ENV CONDA_JL_CONDA_EXE=/opt/conda/envs/rmg_env/bin/conda - -WORKDIR /home/rmguser/ - -RUN mkdir -p /home/rmguser/.arc && \ - cp /home/rmguser/Code/ARC/arc/settings/settings.py /home/rmguser/.arc/settings.py && \ - cp /home/rmguser/Code/ARC/arc/settings/submit.py /home/rmguser/.arc/submit.py - -# Copy alias_print.sh and entrywrapper.sh to the container -# Copy your login‐wide aliases into /etc/profile.d -COPY --chown=rmguser:rmguser dockerfiles/aliases.sh /etc/profile.d/99-rmg-aliases.sh - -# Copy the cheat‐sheet and entrypoint -COPY --chown=rmguser:rmguser dockerfiles/aliases_print.sh /usr/local/bin/aliases -COPY --chown=rmguser:rmguser dockerfiles/entrywrapper.sh /home/rmguser/entrywrapper.sh - -# Fix permissions & make the scripts executable & ensure rms is run once -RUN chmod 644 /etc/profile.d/99-rmg-aliases.sh \ - && chmod +x /home/rmguser/entrywrapper.sh \ - && chmod +x /usr/local/bin/aliases \ - && micromamba run -n rmg_env python-jl /home/rmguser/Code/RMG-Py/rmg.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py \ - # delete the results, preserve input.py - && mv /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py /home/rmguser/Code/RMG-Py/examples/input.py \ - && rm -rf /home/rmguser/Code/RMG-Py/examples/rmg/minimal/* \ - && mv /home/rmguser/Code/RMG-Py/examples/input.py /home/rmguser/Code/RMG-Py/examples/rmg/minimal/input.py - -# Use a login shell so /etc/profile and /etc/profile.d/* are sourced automatically -ENTRYPOINT ["bash","-l","/home/rmguser/entrywrapper.sh"] - -# Activate the ARC environment -ARG MAMBA_DOCKERFILE_ACTIVATE=1 -ENV ENV_NAME=arc_env +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git \ + libxrender1 \ + ca-certificates \ + nano \ + make \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +USER mambauser + +COPY --from=builder --chown=mambauser:mambauser /opt/conda /opt/conda +COPY --from=builder --chown=mambauser:mambauser /home/mambauser/.juliaup /home/mambauser/.juliaup +COPY --from=builder --chown=mambauser:mambauser /home/mambauser/Code /home/mambauser/Code + +# Need to copy the tests separately as they are not in the ARC git by default +COPY --chown=mambauser:mambauser dockerfiles/docker_tests /home/mambauser/Code/ARC/dockerfiles/docker_tests + +# --- Entry wrapper ---------------------------------------------------------- +COPY --chmod=755 dockerfiles/entrywrapper.sh /usr/local/bin/entrywrapper.sh +COPY --chmod=644 dockerfiles/aliases.sh /etc/profile.d/aliases.sh +COPY --chmod=755 dockerfiles/aliases_print.sh /usr/local/bin/aliases + +ENTRYPOINT ["/usr/local/bin/entrywrapper.sh"] diff --git a/dockerfiles/aliases.sh b/dockerfiles/aliases.sh index 7e56be1fb7..072762c53c 100644 --- a/dockerfiles/aliases.sh +++ b/dockerfiles/aliases.sh @@ -13,9 +13,9 @@ alias rmge='micromamba activate rmg_env' alias arce='micromamba activate arc_env' # code roots (set once at image build) -export rmgpy_path=/home/rmguser/Code/RMG-Py -export rmgdb_path=/home/rmguser/Code/RMG-database -export arc_path=/home/rmguser/Code/ARC +export rmgpy_path=/home/mambauser/Code/RMG-Py +export rmgdb_path=/home/mambauser/Code/RMG-database +export arc_path=/home/mambauser/Code/ARC alias rmgcode='cd "$rmgpy_path"' alias dbcode='cd "$rmgdb_path"' diff --git a/dockerfiles/aliases_print.sh b/dockerfiles/aliases_print.sh index 44ed7eb6aa..d3532506e9 100644 --- a/dockerfiles/aliases_print.sh +++ b/dockerfiles/aliases_print.sh @@ -22,7 +22,7 @@ cat <<'EOF' arcode → cd $arc_path ‣ One-liner runners - rmg → python-jl $rmgpy_path/rmg.py input.py + rmg → python $rmgpy_path/rmg.py input.py arkane → python $rmgpy_path/Arkane.py input.py arc → python $arc_path/ARC.py input.yml arcrestart → python $arc_path/ARC.py restart.yml diff --git a/dockerfiles/docker_tests/test_docker_smoke.py b/dockerfiles/docker_tests/test_docker_smoke.py new file mode 100644 index 0000000000..fddb032879 --- /dev/null +++ b/dockerfiles/docker_tests/test_docker_smoke.py @@ -0,0 +1,75 @@ +import pytest +import subprocess +import tempfile +import pathlib +import textwrap + + +@pytest.mark.smoke +def test_import_arc(): + """Test that ARC can be imported in the docker image.""" + try: + import arc + assert hasattr(arc, '__file__') + except ImportError as e: + pytest.fail(f"ImportError: {e}") + + +@pytest.mark.smoke +def test_arc_cli_help_runs(): + """Test that ARC CLI help runs in the docker image.""" + cmd = ["bash", "-lc", "micromamba run -n arc_env python -m ARC --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "help" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_arkane_cli_help_runs(): + """Test that Arkane CLI help runs in the docker image.""" + cmd = ["bash", "-lc","micromamba run -n rmg_env python -m arkane --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "arkane" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_arc_can_execute_arkane_minimal(): + """Test that ARC can execute Arkane with a minimal input in the docker image.""" + arkane_input = textwrap.dedent("""\ + #!/usr/bin/env python + modelChemistry = 'wb97m-v/def2-tzvpd' # irrelevant here, just parseable + useHinderedRotors = False + thermo('H2', 'H298') + """) + with tempfile.TemporaryDirectory() as td: + inp = pathlib.Path(td, "input.py") + inp.write_text(arkane_input) + # Call arkane via rmg_env the same way ARC would (subprocess) + cmd = ["bash", "-lc", f"micromamba run -n rmg_env python -m arkane {inp} || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # We only assert it runs and produces any Arkane header/output (no heavy calc) + assert "arkane" in (p.stdout + p.stderr).lower() + + +@pytest.mark.smoke +def test_rmgpy_imports(): + """Test that RMG-Py can be imported in the docker image.""" + code = r""" +import importlib, sys +m = importlib.import_module('rmgpy') +print('rmgpy OK', getattr(m, '__version__', 'unknown')) +""" + cmd = ["bash", "-lc", f"micromamba run -n rmg_env python - <<'PY'\n{code}\nPY"] + p = subprocess.run(cmd, capture_output=True, text=True) + assert p.returncode == 0, p.stderr + assert "rmgpy OK" in p.stdout + + +@pytest.mark.smoke +def test_rmg_cli_help_runs(): + """Test that RMG CLI help runs in the docker image.""" + cmd = ["bash", "-lc", "micromamba run -n rmg_env rmg --help || true"] + p = subprocess.run(cmd, capture_output=True, text=True) + # Just ensure it executes and prints usage/help + assert "rmg" in (p.stdout + p.stderr).lower() diff --git a/dockerfiles/entrywrapper.sh b/dockerfiles/entrywrapper.sh index 388315afc2..516783ba15 100644 --- a/dockerfiles/entrywrapper.sh +++ b/dockerfiles/entrywrapper.sh @@ -1,34 +1,96 @@ #!/usr/bin/env bash set -euo pipefail -# 1) Micromamba setup (for both interactive & non-interactive) -eval "$(micromamba shell hook --shell bash)" +# 0) Only try chown if we're root (bind-mounts may be root-owned) +if [[ "$(id -u)" -eq 0 ]] && [[ ! -O /home/mambauser/Code ]]; then + chown -R mambauser:mambauser /home/mambauser/Code || true +fi -# 1.1) Ensure the Code directory is owned by rmguser -if [ ! -O /home/rmguser/Code ]; then - chown -R rmguser:rmguser /home/rmguser/Code +# If running non-interactively at container root and /work exists; it will go there +# This helps when users forget `-w /work` +if [[ -d /work && "${PWD:-/}" == "/" && ! -t 0 ]]; then + cd /work || true fi -# 2) Interactive mode: login shells or docker exec … bash -if [ -t 0 ] && [ -t 1 ]; then -# # Show the aliases cheat-sheet -# /home/rmguser/.aliases_print.sh - # Drop into an interactive Bash +# 1) If no args → interactive shell (when run with -it) +if [[ $# -eq 0 ]]; then exec /bin/bash -l fi -# 3) Non-interactive command mode: rmg or arc -case "${1:-}" in - rmg) - micromamba activate rmg_env - exec python-jl /home/rmguser/Code/RMG-Py/rmg.py "${2:-input.py}" - ;; - arc) - micromamba activate arc_env - exec python /home/rmguser/Code/ARC/ARC.py "${2:-input.yml}" +usage() { + cat >&2 <<'USAGE' +Usage: + arc [flags] + rmg [flags] + +Run with a bind mount so the container can read your input file, e.g. + + # ARC + docker run --rm -v "$PWD:/work" -w /work IMAGE arc my_case/input.yml + + # RMG + docker run --rm -v "$PWD:/work" -w /work IMAGE rmg my_case/input.py + +Notes: +- / must be a non-flag argument +- if you pass flags (e.g. -n 8), put them before the file: rmg -n 8 input.py +USAGE +} + + +# Return the first non-flag arg +first_nonflag() { + local after_ddash="no" + for a in "$@"; do + if [[ "$after_ddash" == "no" && "$a" == "--" ]]; then + after_ddash="yes" + continue + fi + if [[ "$after_ddash" == "yes" || "$a" != -* ]]; then + echo "$a" + return 0 + fi + done + return 1 +} + +# Show usage on -h/--help for arc/rmg if no file was given +wants_help_no_file() { + for a in "$@"; do + [[ "$a" == "-h" || "$a" == "--help" ]] && return 0 + done + return 1 +} + +# 2) Subcommands: rmg / arc +cmd="$1"; shift || true +case "$cmd" in + arc|rmg) + if wants_help_no_file "$@" && ! first_nonflag "$@" >/dev/null; then + usage + exit 0 + fi + if ! file_arg="$(first_nonflag "$@")"; then + usage + exit 64 # EX_USAGE + fi + + if [[ ! -f "$file_arg" ]]; then + echo "Error: input file not found inside container: $file_arg" >&2 + echo "Tip: mount and set workdir, e.g.: docker run -v \"\$PWD:/work\" -w /work IMAGE $cmd $file_arg" >&2 + exit 66 # EX_NOINPUT + fi + + # no defaults: user must provide their file path + if [[ "$cmd" == "arc" ]]; then + exec micromamba run -n arc_env python /home/mambauser/Code/ARC/ARC.py "$@" + else + exec micromamba run -n rmg_env python /home/mambauser/Code/RMG-Py/rmg.py "$@" + fi ;; + *) - echo "Usage: [input-file]" >&2 - exit 1 + # Pass-through for CI or ad-hoc shell commands + exec "$cmd" "$@" ;; esac