diff --git a/.github/Dockerfile b/.github/Dockerfile index 5b86e0f711..23c08d8154 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -50,16 +50,16 @@ RUN python3.12 -m venv /opt/MFC/build/venv && \ RUN echo "TARGET=$TARGET CC=$CC_COMPILER FC=$FC_COMPILER" && \ cd /opt/MFC && \ if [ "$TARGET" = "gpu" ]; then \ - ./mfc.sh build --gpu -j $(nproc); \ + ./mfc.sh build -v --gpu -j $(nproc); \ else \ - ./mfc.sh build -j $(nproc); \ + ./mfc.sh build -v -j $(nproc); \ fi RUN cd /opt/MFC && \ if [ "$TARGET" = "gpu" ]; then \ - ./mfc.sh test -a --dry-run --gpu -j $(nproc); \ + ./mfc.sh test -v -a --dry-run --gpu -j $(nproc); \ else \ - ./mfc.sh test -a --dry-run -j $(nproc); \ + ./mfc.sh test -v -a --dry-run -j $(nproc); \ fi WORKDIR /opt/MFC diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f75631b2dd..6279f5f578 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,24 +1,32 @@ name: 'Benchmark' on: - pull_request: - pull_request_review: - types: [submitted] + # Trigger when Test Suite completes (no polling needed) + workflow_run: + workflows: ["Test Suite"] + types: [completed] workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} cancel-in-progress: true jobs: file-changes: name: Detect File Changes + # Only run if Test Suite passed (or manual dispatch) + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }} + pr_number: ${{ steps.pr-info.outputs.pr_number }} + pr_approved: ${{ steps.pr-info.outputs.approved }} + pr_author: ${{ steps.pr-info.outputs.author }} steps: - name: Clone uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} - name: Detect Changes uses: dorny/paths-filter@v3 @@ -26,62 +34,52 @@ jobs: with: filters: ".github/file-filter.yml" - wait-for-tests: - name: Wait for Test Suite - runs-on: ubuntu-latest - steps: - - name: Wait for Test Suite to Pass + - name: Get PR Info + id: pr-info env: GH_TOKEN: ${{ github.token }} run: | - echo "Waiting for Test Suite workflow to complete..." - SHA="${{ github.event.pull_request.head.sha || github.sha }}" - - # Poll every 60 seconds for up to 3 hours - for i in $(seq 1 180); do - # Get the Test Suite workflow runs for this commit - STATUS=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '.check_runs[] | select(.name == "Lint Gate") | .conclusion' | head -1) - - if [ "$STATUS" = "success" ]; then - echo "Lint Gate passed. Checking test jobs..." - - # Check if any Github test jobs failed - FAILED=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == "failure")] | length') - - if [ "$FAILED" != "0" ]; then - echo "::error::Test Suite has failing jobs. Benchmarks will not run." - exit 1 + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "pr_number=" >> $GITHUB_OUTPUT + echo "approved=true" >> $GITHUB_OUTPUT + echo "author=${{ github.actor }}" >> $GITHUB_OUTPUT + else + # Get PR number from workflow_run + PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" + if [ -n "$PR_NUMBER" ]; then + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + + # Fetch actual PR author from API (workflow_run.actor is the re-runner, not PR author) + PR_AUTHOR=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER --jq '.user.login') + echo "author=$PR_AUTHOR" >> $GITHUB_OUTPUT + + # Check if PR is approved + APPROVED=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews \ + --jq '[.[] | select(.state == "APPROVED")] | length') + if [ "$APPROVED" -gt 0 ]; then + echo "approved=true" >> $GITHUB_OUTPUT + else + echo "approved=false" >> $GITHUB_OUTPUT fi - - # Check if Github tests are still running - PENDING=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ - --jq '[.check_runs[] | select(.name | startswith("Github")) | select(.conclusion == null)] | length') - - if [ "$PENDING" = "0" ]; then - echo "All Test Suite jobs completed successfully!" - exit 0 - fi - - echo "Tests still running ($PENDING pending)..." - elif [ "$STATUS" = "failure" ]; then - echo "::error::Lint Gate failed. Benchmarks will not run." - exit 1 else - echo "Lint Gate status: ${STATUS:-pending}..." + echo "pr_number=" >> $GITHUB_OUTPUT + echo "approved=false" >> $GITHUB_OUTPUT + echo "author=" >> $GITHUB_OUTPUT fi - - sleep 60 - done - - echo "::error::Timeout waiting for Test Suite to complete." - exit 1 + fi self: name: "${{ matrix.name }} (${{ matrix.device }}${{ matrix.interface != 'none' && format('-{0}', matrix.interface) || '' }})" - if: ${{ github.repository=='MFlowCode/MFC' && needs.file-changes.outputs.checkall=='true' && ((github.event_name=='pull_request_review' && github.event.review.state=='approved') || (github.event_name=='pull_request' && (github.event.pull_request.user.login=='sbryngelson' || github.event.pull_request.user.login=='wilfonba'))) }} - needs: [file-changes, wait-for-tests] + if: > + github.repository == 'MFlowCode/MFC' && + needs.file-changes.outputs.checkall == 'true' && + ( + github.event_name == 'workflow_dispatch' || + needs.file-changes.outputs.pr_approved == 'true' || + needs.file-changes.outputs.pr_author == 'sbryngelson' || + needs.file-changes.outputs.pr_author == 'wilfonba' + ) + needs: [file-changes] strategy: fail-fast: false matrix: @@ -145,6 +143,7 @@ jobs: - name: Clone - PR uses: actions/checkout@v4 with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} path: pr - name: Clone - Master diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 469048f8a6..a8c4acd1bf 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -39,10 +39,10 @@ jobs: libfftw3-dev libhdf5-dev libblas-dev liblapack-dev - name: Build - run: /bin/bash mfc.sh build -j $(nproc) --gcov + run: /bin/bash mfc.sh build -v -j $(nproc) --gcov - name: Test - run: /bin/bash mfc.sh test -a -j $(nproc) + run: /bin/bash mfc.sh test -v -a -j $(nproc) - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/frontier/bench.sh b/.github/workflows/frontier/bench.sh index 35b4c5950e..6498846b9b 100644 --- a/.github/workflows/frontier/bench.sh +++ b/.github/workflows/frontier/bench.sh @@ -16,7 +16,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier $device_opts -n $n_ranks fi diff --git a/.github/workflows/frontier/build.sh b/.github/workflows/frontier/build.sh index 70c29204c1..ba128b6ca4 100644 --- a/.github/workflows/frontier/build.sh +++ b/.github/workflows/frontier/build.sh @@ -18,9 +18,9 @@ fi if [ "$run_bench" == "bench" ]; then for dir in benchmarks/*/; do dirname=$(basename "$dir") - ./mfc.sh run "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts + ./mfc.sh run -v "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts done else - ./mfc.sh test -a --dry-run --rdma-mpi -j 8 $build_opts + ./mfc.sh test -v -a --dry-run --rdma-mpi -j 8 $build_opts fi diff --git a/.github/workflows/frontier/test.sh b/.github/workflows/frontier/test.sh index dd678d53c9..17fbbaf8e5 100644 --- a/.github/workflows/frontier/test.sh +++ b/.github/workflows/frontier/test.sh @@ -14,7 +14,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh test -a --rdma-mpi --max-attempts 3 -j $ngpus $device_opts -- -c frontier + ./mfc.sh test -v -a --rdma-mpi --max-attempts 3 -j $ngpus $device_opts -- -c frontier else - ./mfc.sh test -a --max-attempts 3 -j 32 --no-gpu -- -c frontier + ./mfc.sh test -v -a --max-attempts 3 -j 32 --no-gpu -- -c frontier fi diff --git a/.github/workflows/frontier_amd/bench.sh b/.github/workflows/frontier_amd/bench.sh index 6e01687e79..48062f60c0 100644 --- a/.github/workflows/frontier_amd/bench.sh +++ b/.github/workflows/frontier_amd/bench.sh @@ -16,7 +16,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $n_ranks -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c frontier_amd $device_opts -n $n_ranks fi diff --git a/.github/workflows/frontier_amd/build.sh b/.github/workflows/frontier_amd/build.sh index b8b1f7051c..8f83666546 100644 --- a/.github/workflows/frontier_amd/build.sh +++ b/.github/workflows/frontier_amd/build.sh @@ -18,9 +18,9 @@ fi if [ "$run_bench" == "bench" ]; then for dir in benchmarks/*/; do dirname=$(basename "$dir") - ./mfc.sh run "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts + ./mfc.sh run -v "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts done else - ./mfc.sh test -a --dry-run -j 8 $build_opts + ./mfc.sh test -v -a --dry-run -j 8 $build_opts fi diff --git a/.github/workflows/frontier_amd/test.sh b/.github/workflows/frontier_amd/test.sh index 10d63fb767..39e37835ac 100644 --- a/.github/workflows/frontier_amd/test.sh +++ b/.github/workflows/frontier_amd/test.sh @@ -14,7 +14,7 @@ if [ "$job_device" = "gpu" ]; then fi if [ "$job_device" = "gpu" ]; then - ./mfc.sh test -a --max-attempts 3 -j $ngpus $device_opts -- -c frontier_amd + ./mfc.sh test -v -a --max-attempts 3 -j $ngpus $device_opts -- -c frontier_amd else - ./mfc.sh test -a --max-attempts 3 -j 32 --no-gpu -- -c frontier_amd + ./mfc.sh test -v -a --max-attempts 3 -j 32 --no-gpu -- -c frontier_amd fi diff --git a/.github/workflows/phoenix/bench.sh b/.github/workflows/phoenix/bench.sh index 99d0a07562..2ec4a02710 100644 --- a/.github/workflows/phoenix/bench.sh +++ b/.github/workflows/phoenix/bench.sh @@ -24,9 +24,9 @@ mkdir -p $currentdir export TMPDIR=$currentdir if [ "$job_device" = "gpu" ]; then - ./mfc.sh bench --mem 12 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 12 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks else - ./mfc.sh bench --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks + ./mfc.sh bench -v --mem 1 -j $(nproc) -o "$job_slug.yaml" -- -c phoenix-bench $device_opts -n $n_ranks fi sleep 10 diff --git a/.github/workflows/phoenix/test.sh b/.github/workflows/phoenix/test.sh index 47a25cf596..5cbb73f809 100644 --- a/.github/workflows/phoenix/test.sh +++ b/.github/workflows/phoenix/test.sh @@ -10,7 +10,7 @@ if [ "$job_device" = "gpu" ]; then fi fi -./mfc.sh test --dry-run -j 8 $build_opts +./mfc.sh test -v --dry-run -j 8 $build_opts n_test_threads=8 @@ -21,5 +21,5 @@ if [ "$job_device" = "gpu" ]; then n_test_threads=`expr $gpu_count \* 2` fi -./mfc.sh test --max-attempts 3 -a -j $n_test_threads $device_opts -- -c phoenix +./mfc.sh test -v --max-attempts 3 -a -j $n_test_threads $device_opts -- -c phoenix diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb16384045..7e7d24f262 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -125,11 +125,11 @@ jobs: - name: Build run: | - /bin/bash mfc.sh test --dry-run -j $(nproc) --${{ matrix.debug }} --${{ matrix.mpi }} --${{ matrix.precision }} + /bin/bash mfc.sh test -v --dry-run -j $(nproc) --${{ matrix.debug }} --${{ matrix.mpi }} --${{ matrix.precision }} - name: Test run: | - /bin/bash mfc.sh test --max-attempts 3 -j $(nproc) $OPT1 $OPT2 + /bin/bash mfc.sh test -v --max-attempts 3 -j $(nproc) $OPT1 $OPT2 env: OPT1: ${{ matrix.mpi == 'mpi' && '--test-all' || '' }} OPT2: ${{ matrix.debug == 'debug' && '-% 20' || '' }} diff --git a/mfc.sh b/mfc.sh index fa3d420df9..bbdbb33adf 100755 --- a/mfc.sh +++ b/mfc.sh @@ -16,32 +16,8 @@ if [ -d "$(pwd)/.git" ] && [ ! -e "$(pwd)/.git/hooks/pre-commit" ] && [ -f "$(pw log "Installed git pre-commit hook (runs$MAGENTA ./mfc.sh precheck$COLOR_RESET before commits)." fi -# Auto-install shell completions (once) -COMPLETION_DIR="$HOME/.local/share/mfc/completions" -if [ ! -d "$COMPLETION_DIR" ]; then - mkdir -p "$COMPLETION_DIR" - cp "$(pwd)/toolchain/completions/mfc.bash" "$COMPLETION_DIR/" - cp "$(pwd)/toolchain/completions/_mfc" "$COMPLETION_DIR/" - - # Add to shell rc file based on current shell - if [[ "$SHELL" == *"zsh"* ]]; then - RC_FILE="$HOME/.zshrc" - RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" - SOURCE_CMD="source $COMPLETION_DIR/_mfc" - else - RC_FILE="$HOME/.bashrc" - RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" - SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" - fi - - if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then - echo "" >> "$RC_FILE" - echo "# MFC shell completion" >> "$RC_FILE" - echo "$RC_LINE" >> "$RC_FILE" - fi - - log "Installed tab completions. Restart shell or run:$MAGENTA $SOURCE_CMD$COLOR_RESET" -fi +# Shell completions auto-install/update +. "$(pwd)/toolchain/bootstrap/completions.sh" "$(pwd)" # Print startup message immediately for user feedback log "Starting..." @@ -72,33 +48,33 @@ if [ "$1" '==' 'load' ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then echo "" exit 1 fi - shift; . "$(pwd)/toolchain/bootstrap/modules.sh" $@; return + shift; . "$(pwd)/toolchain/bootstrap/modules.sh" "$@"; return elif [ "$1" '==' "lint" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/lint.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/lint.sh" "$@"; exit 0 elif [ "$1" '==' "format" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/format.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/format.sh" "$@"; exit 0 elif [ "$1" '==' "venv" ]; then - shift; . "$(pwd)/toolchain/bootstrap/python.sh" $@; return + shift; . "$(pwd)/toolchain/bootstrap/python.sh" "$@"; return elif [ "$1" '==' "clean" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then rm -rf "$(pwd)/build"; exit 0 elif [ "$1" '==' "spelling" ] && [ "$2" != "--help" ] && [ "$2" != "-h" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/spelling.sh" "$@"; exit 0 elif [ "$1" '==' "precheck" ]; then - . "$(pwd)/toolchain/bootstrap/python.sh" + . "$(pwd)/toolchain/bootstrap/python.sh" "$@" - shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" $@; exit 0 + shift; . "$(pwd)/toolchain/bootstrap/precheck.sh" "$@"; exit 0 fi mkdir -p "$(pwd)/build" . "$(pwd)/toolchain/bootstrap/cmake.sh" -. "$(pwd)/toolchain/bootstrap/python.sh" +. "$(pwd)/toolchain/bootstrap/python.sh" "$@" # init command: just bootstrap the environment and exit (no Python command) if [ "$1" '==' 'init' ]; then @@ -108,7 +84,16 @@ fi echo # Run the main.py bootstrap script -python3 "$(pwd)/toolchain/main.py" "$@" +# If only flags given (no command), show help without passing flags +has_command=false +for arg in "$@"; do + case "$arg" in -*) ;; *) has_command=true; break ;; esac +done +if [ "$has_command" = true ]; then + python3 "$(pwd)/toolchain/main.py" "$@" +else + python3 "$(pwd)/toolchain/main.py" +fi code=$? echo diff --git a/toolchain/bootstrap/completions.sh b/toolchain/bootstrap/completions.sh new file mode 100644 index 0000000000..55779731d2 --- /dev/null +++ b/toolchain/bootstrap/completions.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# MFC Shell Completion Auto-installer +# +# This script handles automatic installation and updating of shell completions. +# It is sourced by mfc.sh on startup. +# +# Features: +# - Auto-detects bash vs zsh +# - Installs completions to ~/.local/share/mfc/completions/ +# - Updates completions when source files change +# - Configures shell rc files on first install +# - Sources completions immediately when mfc.sh is sourced + +_mfc_setup_completions() { + local MFC_ROOT="$1" + local COMPLETION_DIR="$HOME/.local/share/mfc/completions" + local COMPLETION_SRC="$MFC_ROOT/toolchain/completions" + local COMPLETION_FILE SOURCE_FILE RC_FILE RC_LINE SOURCE_CMD + + # Detect shell: use ZSH_VERSION (current shell) rather than $SHELL (login shell) + if [ -n "${ZSH_VERSION-}" ]; then + COMPLETION_FILE="$COMPLETION_DIR/_mfc" + SOURCE_FILE="$COMPLETION_SRC/_mfc" + RC_FILE="$HOME/.zshrc" + RC_LINE="fpath=(\"$COMPLETION_DIR\" \$fpath)" + SOURCE_CMD="source $COMPLETION_DIR/_mfc" + else + COMPLETION_FILE="$COMPLETION_DIR/mfc.bash" + SOURCE_FILE="$COMPLETION_SRC/mfc.bash" + RC_FILE="$HOME/.bashrc" + RC_LINE="[ -f \"$COMPLETION_DIR/mfc.bash\" ] && source \"$COMPLETION_DIR/mfc.bash\"" + SOURCE_CMD="source $COMPLETION_DIR/mfc.bash" + fi + + # Check if we need to install or update + local COMPLETIONS_CHANGED=false + + # Only proceed if source completion files exist (they're generated by ./mfc.sh generate) + if [ ! -f "$COMPLETION_SRC/mfc.bash" ] || [ ! -f "$COMPLETION_SRC/_mfc" ]; then + return + fi + + if [ ! -f "$COMPLETION_FILE" ]; then + # Fresh install + mkdir -p "$COMPLETION_DIR" + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + COMPLETIONS_CHANGED=true + + # Add to shell rc file on first install + if [ -f "$RC_FILE" ] && ! grep -q "$COMPLETION_DIR" "$RC_FILE" 2>/dev/null; then + echo "" >> "$RC_FILE" + echo "# MFC shell completion" >> "$RC_FILE" + echo "$RC_LINE" >> "$RC_FILE" + fi + elif [ "$SOURCE_FILE" -nt "$COMPLETION_FILE" ]; then + # Update outdated completions + cp "$COMPLETION_SRC/mfc.bash" "$COMPLETION_DIR/" + cp "$COMPLETION_SRC/_mfc" "$COMPLETION_DIR/" + COMPLETIONS_CHANGED=true + fi + + # Notify user about changes + if [ "$COMPLETIONS_CHANGED" = true ]; then + if [[ "${BASH_SOURCE[1]}" != "${0}" ]] 2>/dev/null; then + # Script is being sourced - activate completions now + # shellcheck disable=SC1090 + source "$COMPLETION_FILE" 2>/dev/null && log "Tab completions activated." + else + # Script is being executed - can't modify parent shell + log "Tab completions updated. Run:$MAGENTA $SOURCE_CMD$COLOR_RESET" + fi + fi +} + +# Run setup if this script is sourced with MFC_ROOT_DIR set +if [ -n "$1" ]; then + _mfc_setup_completions "$1" +fi diff --git a/toolchain/bootstrap/python.sh b/toolchain/bootstrap/python.sh index d2c087a26c..59458edc6a 100644 --- a/toolchain/bootstrap/python.sh +++ b/toolchain/bootstrap/python.sh @@ -1,7 +1,7 @@ #!/bin/bash MFC_PYTHON_MIN_MAJOR=3 -MFC_PYTHON_MIN_MINOR=11 +MFC_PYTHON_MIN_MINOR=10 MFC_PYTHON_MIN_STR="$MFC_PYTHON_MIN_MAJOR.$MFC_PYTHON_MIN_MINOR" is_python_compatible() { @@ -138,6 +138,7 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ next_arg=0 nthreads=1 + verbose=0 for arg in "$@"; do if [ "$arg" == "-j" ] || [ "$arg" == "--jobs" ]; then next_arg=1 @@ -148,6 +149,10 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ nthreads=$arg continue fi + # Check for verbosity flags + if [ "$arg" == "-v" ] || [ "$arg" == "-vv" ] || [ "$arg" == "-vvv" ] || [ "$arg" == "--verbose" ]; then + verbose=1 + fi done # Run package installer and show progress @@ -173,12 +178,12 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ # Use uv if available, otherwise fall back to pip if [ "$USE_UV" = "1" ]; then - # uv is much faster and has its own progress display - show it # UV_LINK_MODE=copy avoids slow hardlink failures on cross-filesystem installs (common on HPC) export UV_LINK_MODE=copy log "(venv) Using$MAGENTA uv$COLOR_RESET for fast installation..." - if [ -t 1 ]; then - # Interactive terminal: show uv's native progress + + if [ "$verbose" = "1" ]; then + # Verbose mode: show full uv output if uv pip install "$(pwd)/toolchain"; then ok "(venv) Installation succeeded." cp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/" @@ -189,13 +194,18 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/ exit 1 fi else - # Non-interactive: capture output for logging - if uv pip install "$(pwd)/toolchain" > "$PIP_LOG" 2>&1; then + # Default: show progress but filter out individual package lines (+ pkg==ver) + uv pip install "$(pwd)/toolchain" > "$PIP_LOG" 2>&1 + UV_EXIT=$? + # Show filtered output (progress info without package list) + # Filter out lines like " + pkg==1.0", " - pkg==1.0", " ~ pkg==1.0" + grep -v '^ [+~-] ' "$PIP_LOG" || true + if [ $UV_EXIT -eq 0 ]; then rm -f "$PIP_LOG" ok "(venv) Installation succeeded." cp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/" else - error "(venv) Installation failed. See output below:" + error "(venv) Installation failed. Full output:" echo "" cat "$PIP_LOG" echo "" diff --git a/toolchain/mfc/build.py b/toolchain/mfc/build.py index 0ee07dc5fa..c0e6b7e478 100644 --- a/toolchain/mfc/build.py +++ b/toolchain/mfc/build.py @@ -266,21 +266,16 @@ def _show_build_error(result: subprocess.CompletedProcess, stage: str): # Show stdout if available (often contains the actual error for CMake) if result.stdout: stdout_text = result.stdout if isinstance(result.stdout, str) else result.stdout.decode('utf-8', errors='replace') - stdout_lines = stdout_text.strip().split('\n') - # Show last 40 lines to capture the relevant error - if len(stdout_lines) > 40: - stdout_lines = ['... (truncated) ...'] + stdout_lines[-40:] - if stdout_lines and stdout_lines != ['']: - cons.raw.print(Panel('\n'.join(stdout_lines), title="Output", border_style="yellow")) + stdout_text = stdout_text.strip() + if stdout_text: + cons.raw.print(Panel(stdout_text, title="Output", border_style="yellow")) # Show stderr if available if result.stderr: stderr_text = result.stderr if isinstance(result.stderr, str) else result.stderr.decode('utf-8', errors='replace') - stderr_lines = stderr_text.strip().split('\n') - if len(stderr_lines) > 40: - stderr_lines = ['... (truncated) ...'] + stderr_lines[-40:] - if stderr_lines and stderr_lines != ['']: - cons.raw.print(Panel('\n'.join(stderr_lines), title="Errors", border_style="red")) + stderr_text = stderr_text.strip() + if stderr_text: + cons.raw.print(Panel(stderr_text, title="Errors", border_style="red")) cons.print() diff --git a/toolchain/mfc/user_guide.py b/toolchain/mfc/user_guide.py index d83597e98d..f30201b892 100644 --- a/toolchain/mfc/user_guide.py +++ b/toolchain/mfc/user_guide.py @@ -379,67 +379,51 @@ def print_help_topics(): # ENHANCED HELP OUTPUT # ============================================================================= +def _truncate_desc(desc: str, max_len: int = 50) -> str: + """Truncate description to fit compact display.""" + if len(desc) <= max_len: + return desc + return desc[:max_len-3] + "..." + + def print_help(): - """Print enhanced, colorized help overview.""" + """Print compact, colorized help overview.""" - # Header + # Header (no box) cons.print() - cons.raw.print(Panel( - "[bold cyan]MFC[/bold cyan] - [dim]Multi-component Flow Code[/dim]\n" - "[dim]Exascale CFD solver for compressible multi-phase flows[/dim]", - box=box.ROUNDED, - padding=(0, 2) - )) + cons.print("[bold cyan]MFC[/bold cyan] - Multi-component Flow Code") + cons.print("[dim]Exascale CFD solver for compressible multi-phase flows[/dim]") cons.print() - # Commands table - table = Table( - title="[bold]Commands[/bold]", - box=box.SIMPLE, - show_header=True, - header_style="bold cyan", - title_justify="left", - padding=(0, 2) - ) - table.add_column("Command", style="green", no_wrap=True) - table.add_column("Alias", style="dim", no_wrap=True) - table.add_column("Description", style="white") - - # Primary commands with aliases - for cmd in ["build", "run", "test", "validate", "new", "clean"]: - alias = COMMANDS[cmd].get("alias", "") - alias_str = alias if alias else "" - table.add_row(cmd, alias_str, COMMANDS[cmd]["description"]) - - table.add_row("", "", "") # Spacer + # Commands section - compact format (using COMMANDS as source of truth) + cons.print("[bold]Commands:[/bold]") - # Secondary commands - for cmd in ["params", "count", "packer", "load"]: - table.add_row(f"[dim]{cmd}[/dim]", "", f"[dim]{COMMANDS[cmd]['description']}[/dim]") - - table.add_row("", "", "") # Spacer - table.add_row("[dim]help[/dim]", "", "[dim]Show help on a topic (gpu, clusters, batch, debugging)[/dim]") + # Primary commands (shown prominently with aliases) + primary = ["build", "run", "test", "validate", "new", "clean"] + for cmd in primary: + if cmd not in COMMANDS: + continue + info = COMMANDS[cmd] + alias = info.get("alias") or "" + alias_str = f" ({alias})" if alias else " " + desc = _truncate_desc(info["description"]) + cons.print(f" [green]{cmd:9}[/green][dim]{alias_str:4}[/dim] {desc}") + + # Secondary commands (dimmed) + secondary = ["params", "load", "help"] + for cmd in secondary: + if cmd not in COMMANDS: + continue + desc = _truncate_desc(COMMANDS[cmd]["description"]) + cons.print(f" [dim]{cmd:13} {desc}[/dim]") - cons.raw.print(table) cons.print() - # Quick start - cons.raw.print(Panel( - "[bold]Quick Start[/bold]\n\n" - " [green]1.[/green] [cyan]./mfc.sh new my_case[/cyan] Create a new case\n" - " [green]2.[/green] [cyan]vim my_case/case.py[/cyan] Edit parameters\n" - " [green]3.[/green] [cyan]./mfc.sh validate my_case/case.py[/cyan] Check for errors\n" - " [green]4.[/green] [cyan]./mfc.sh build -j $(nproc)[/cyan] Build MFC\n" - " [green]5.[/green] [cyan]./mfc.sh run my_case/case.py[/cyan] Run simulation", - box=box.ROUNDED, - border_style="green", - padding=(1, 2) - )) - cons.print() + # Quick start - single line + cons.print("[bold]Quick start:[/bold] [cyan]./mfc.sh new my_case[/cyan] → edit case.py → [cyan]./mfc.sh build[/cyan] → [cyan]./mfc.sh run[/cyan]") # Footer - cons.print("[dim]Run [cyan]./mfc.sh --help[/cyan] for detailed options[/dim]") - cons.print("[dim]Run [cyan]./mfc.sh help [/cyan] for topic help (gpu, clusters, batch, debugging)[/dim]") + cons.print("[dim]Run ./mfc.sh --help for options[/dim]") cons.print() @@ -497,7 +481,7 @@ def after_build_failure(): cons.print() cons.raw.print(Panel( "[bold yellow]Troubleshooting Tips[/bold yellow]\n\n" - " [cyan]1.[/cyan] Run with [green]--debug-log[/green] to see detailed output\n" + " [cyan]1.[/cyan] Rebuild with [green]--debug[/green] for debug compiler flags and verbose output\n" " [cyan]2.[/cyan] Check [green]docs/documentation/troubleshooting.md[/green]\n" " [cyan]3.[/cyan] Ensure required modules are loaded: [green]source ./mfc.sh load -c -m [/green]\n" " [cyan]4.[/cyan] Try [green]./mfc.sh clean[/green] and rebuild", @@ -545,7 +529,7 @@ def after_run_failure(): "[bold yellow]Troubleshooting Tips[/bold yellow]\n\n" " [cyan]1.[/cyan] Validate your case: [green]./mfc.sh validate case.py[/green]\n" " [cyan]2.[/cyan] Check the output in [green]/[/green]\n" - " [cyan]3.[/cyan] Run with [green]--debug-log[/green] for more details\n" + " [cyan]3.[/cyan] Rebuild with [green]--debug[/green] for debug compiler flags\n" " [cyan]4.[/cyan] Check MFC documentation: [green]docs/[/green]", box=box.ROUNDED, border_style="yellow",