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/cleanliness.yml b/.github/workflows/cleanliness.yml index bcbe35caaa..778c876e32 100644 --- a/.github/workflows/cleanliness.yml +++ b/.github/workflows/cleanliness.yml @@ -1,6 +1,10 @@ name: Cleanliness -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [master] + pull_request: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 469048f8a6..6cd6db11b8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,10 @@ name: Coverage Check -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [master] + pull_request: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -39,10 +43,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/docs.yml b/.github/workflows/docs.yml index e81371d8c2..91a44e0eaf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ on: - cron: '0 0 * * *' # This runs every day at midnight UTC workflow_dispatch: push: + branches: [master] pull_request: jobs: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 16043daa95..d3f1bae715 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,6 +1,10 @@ name: Pretty -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [master] + pull_request: + workflow_dispatch: jobs: docs: diff --git a/.github/workflows/frontier/build.sh b/.github/workflows/frontier/build.sh index 70c29204c1..18cddc96ca 100644 --- a/.github/workflows/frontier/build.sh +++ b/.github/workflows/frontier/build.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Ignore SIGHUP to survive login node session drops +trap '' HUP + job_device=$1 job_interface=$2 run_bench=$3 @@ -15,12 +18,39 @@ fi . ./mfc.sh load -c f -m g -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 - done -else - ./mfc.sh test -a --dry-run --rdma-mpi -j 8 $build_opts -fi +max_attempts=3 +attempt=1 +while [ $attempt -le $max_attempts ]; do + echo "Build attempt $attempt of $max_attempts..." + if [ "$run_bench" == "bench" ]; then + build_cmd_ok=true + for dir in benchmarks/*/; do + dirname=$(basename "$dir") + if ! ./mfc.sh run -v "$dir/case.py" --case-optimization -j 8 --dry-run $build_opts; then + build_cmd_ok=false + break + fi + done + else + if ./mfc.sh test -v -a --dry-run --rdma-mpi -j 8 $build_opts; then + build_cmd_ok=true + else + build_cmd_ok=false + fi + fi + + if [ "$build_cmd_ok" = true ]; then + echo "Build succeeded on attempt $attempt." + exit 0 + fi + + if [ $attempt -lt $max_attempts ]; then + echo "Build failed on attempt $attempt. Cleaning and retrying in 30s..." + ./mfc.sh clean + sleep 30 + fi + attempt=$((attempt + 1)) +done +echo "Build failed after $max_attempts attempts." +exit 1 diff --git a/.github/workflows/frontier/submit-bench.sh b/.github/workflows/frontier/submit-bench.sh index fba8249df2..81b9b274e6 100644 --- a/.github/workflows/frontier/submit-bench.sh +++ b/.github/workflows/frontier/submit-bench.sh @@ -29,7 +29,7 @@ job_slug="`basename "$1" | sed 's/\.sh$//' | sed 's/[^a-zA-Z0-9]/-/g'`-$2-$3" sbatch </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..74092a9b24 --- /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 zsh: prefer current shell (ZSH_VERSION), fall back to $SHELL only if not in bash + if [ -n "${ZSH_VERSION-}" ] || { [ -z "${BASH_VERSION-}" ] && [[ "${SHELL-}" == *"zsh" ]]; }; 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..6430f7ad35 100644 --- a/toolchain/mfc/build.py +++ b/toolchain/mfc/build.py @@ -46,21 +46,20 @@ def _run_build_with_progress(command: typing.List[str], target_name: str, stream all_stdout = [] all_stderr = [] - # Start the process (can't use 'with' since process is used in multiple branches) - process = subprocess.Popen( # pylint: disable=consider-using-with - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1 # Line buffered - ) - if streaming: - # Streaming mode (-v): print build progress lines as they happen + # Streaming mode (-v): merge stderr into stdout to avoid pipe deadlock + process = subprocess.Popen( # pylint: disable=consider-using-with + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1 # Line buffered + ) + cons.print(f" [bold blue]Building[/bold blue] [magenta]{target_name}[/magenta] [dim](-v)[/dim]...") start_time = time.time() - # Read stdout and print matching lines + # Read merged stdout+stderr and print matching lines for line in iter(process.stdout.readline, ''): all_stdout.append(line) stripped = line.strip() @@ -95,16 +94,22 @@ def _run_build_with_progress(command: typing.List[str], target_name: str, stream filename = filename[:37] + "..." cons.print(f" [dim][{percent:>3}%][/dim] {filename}") - # Read any remaining stderr - stderr = process.stderr.read() - all_stderr.append(stderr) process.wait() elapsed = time.time() - start_time if elapsed > 5: cons.print(f" [dim](build took {elapsed:.1f}s)[/dim]") - return subprocess.CompletedProcess(cmd, process.returncode, ''.join(all_stdout), ''.join(all_stderr)) + return subprocess.CompletedProcess(cmd, process.returncode, ''.join(all_stdout), '') + + # Start the process for non-streaming modes + process = subprocess.Popen( # pylint: disable=consider-using-with + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 # Line buffered + ) if not is_tty: # Non-interactive, non-streaming: show message with elapsed time @@ -266,21 +271,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",