Platform Compatibility Matrix

Last Updated: 2025-11-23 Status: Living Document - Updated as tools/runners change Related: Multi-Platform Workflows, Hybrid Runner System


Overview

This document provides a comprehensive reference for tool, command, and runtime availability across the three GitHub Actions runner types used in the Archery Apprentice project. Use this matrix when writing or debugging workflow files to ensure cross-platform compatibility.

Quick Reference:

  • ✓ Available by default
  • ⚠️ Available with caveats
  • ✗ Not available
  • 🔧 Requires installation

Runner Platform Summary

Attributeubuntu-latest (GitHub)Windows 11 (Self-hosted)macOS Sonoma (Self-hosted)
OS VersionUbuntu 22.04 LTSWindows 11 PromacOS 14.x (Sonoma)
Architecturex86_64x86_64ARM64 (Apple Silicon)
Default ShellbashPowerShell 5.1bash (zsh interactive)
HostingGitHub CloudLocal machineLocal machine
Quota ImpactYes (minutes)NoNo
PersistenceEphemeral (clean slate)PersistentPersistent
Root/Adminsudo availableAdmin rightssudo available

Shell Availability

Detailed Shell Matrix

Shellubuntu-latestWindows Self-hostedmacOS Self-hostedNotes
bash✓ Default⚠️ Git Bash✓ DefaultWindows requires Git for Windows
shPOSIX shell (linked to bash on Ubuntu)
PowerShell 5.1✓ DefaultWindows-only legacy PowerShell
pwsh (PowerShell Core)✓ Installed✓ Installed🔧 Not defaultCross-platform PowerShell 7+
zsh🔧 Install needed✓ InteractivemacOS default interactive shell
cmdWindows Command Prompt

Shell Recommendations

For cross-platform workflows:

# RECOMMENDED - Works on all three platforms
- name: Cross-platform step
  shell: bash
  run: echo "This works everywhere"

For PowerShell Core workflows:

# Works if pwsh is installed on all runners
- name: PowerShell Core step
  shell: pwsh
  run: Write-Host "Cross-platform PowerShell"

Platform-specific shells:

# Ubuntu/macOS only
- name: POSIX shell step
  if: runner.os != 'Windows'
  shell: sh
  run: echo "POSIX only"
 
# Windows only
- name: Windows CMD step
  if: runner.os == 'Windows'
  shell: cmd
  run: echo "Windows only"

Language Runtimes

Python

Commandubuntu-latestWindows Self-hostedmacOS Self-hostedVersion Range
python⚠️ Points to python3✓ Python 3.x✗ Not available3.10 - 3.12
python3✓ Default⚠️ May not exist✓ Default3.10 - 3.12
pip⚠️ Points to pip3✓ Available✗ Not availableLatest
pip3✓ Default⚠️ May not exist✓ DefaultLatest
python2🔧 Not default🔧 Not defaultDeprecated

Critical Discovery (November 2025):

  • macOS has python3 and pip3 but NO python or pip symlinks
  • Windows may have python and pip but not python3 or pip3
  • Ubuntu typically has all four commands (symlinks)

Defensive Python Detection:

# Detect Python command
if command -v python3 &> /dev/null; then
  PYTHON=python3
elif command -v python &> /dev/null; then
  PYTHON=python
else
  echo "ERROR: No Python installation found"
  exit 1
fi
 
# Detect pip command
if command -v pip3 &> /dev/null; then
  PIP=pip3
elif command -v pip &> /dev/null; then
  PIP=pip
else
  echo "ERROR: No pip installation found"
  exit 1
fi
 
echo "Using Python: $PYTHON ($($PYTHON --version))"
echo "Using pip: $PIP ($($PIP --version))"
 
# Use in commands
$PYTHON script.py
$PIP install requests

Workflow Environment Variables:

- name: Set Python environment
  shell: bash
  run: |
    if command -v python3 &> /dev/null; then
      echo "PYTHON_CMD=python3" >> $GITHUB_ENV
      echo "PIP_CMD=pip3" >> $GITHUB_ENV
    elif command -v python &> /dev/null; then
      echo "PYTHON_CMD=python" >> $GITHUB_ENV
      echo "PIP_CMD=pip" >> $GITHUB_ENV
    else
      echo "ERROR: No Python found"
      exit 1
    fi
 
- name: Use Python
  shell: bash
  run: |
    $PYTHON_CMD --version
    $PIP_CMD install -r requirements.txt

Java / Gradle

Toolubuntu-latestWindows Self-hostedmacOS Self-hostedVersion
java✓ Multiple versions✓ JDK 17+✓ JDK 17+11, 17, 21
javacMatches java
gradle⚠️ Use gradlew⚠️ Use gradlew⚠️ Use gradlewVia wrapper
./gradlew⚠️ gradlew.batProject-specific

Gradle Wrapper Usage:

# Linux/macOS - Use ./gradlew directly
./gradlew assembleRelease
 
# Windows bash - Use .bat wrapper
./gradlew.bat assembleRelease
 
# Cross-platform detection
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
  ./gradlew.bat assembleRelease
else
  ./gradlew assembleRelease
fi

Java Version Management:

- name: Set up JDK
  uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'
 
- name: Verify Java
  shell: bash
  run: |
    java -version
    echo "JAVA_HOME: $JAVA_HOME"

Node.js / npm

Toolubuntu-latestWindows Self-hostedmacOS Self-hostedVersion
node18.x LTS
npm9.x+
npxBundled with npm
yarn🔧🔧🔧Install if needed

No significant compatibility issues - Node.js is highly portable.


Unix Core Utilities

sed (Stream Editor)

Featureubuntu-latest (GNU)Windows (Git Bash GNU)macOS (BSD)Compatibility
Basic substitutionPortable
In-place edit (-i)⚠️ sed -i 's/old/new/'⚠️ sed -i 's/old/new/'Different syntaxNOT PORTABLE
In-place BSD style⚠️ sed -i '' 's/old/new/'macOS only
Extended regex (-E)Portable
Extended regex (-r)✓ GNU only✓ GNU only✗ Use -ENot portable

Critical Incompatibility:

# GNU sed (Linux, Git Bash on Windows)
sed -i 's/old/new/' file.txt
 
# BSD sed (macOS) - REQUIRES EMPTY STRING
sed -i '' 's/old/new/' file.txt

Platform-Safe sed Function:

# Create portable sed in-place function
portable_sed_inplace() {
  local pattern="$1"
  local file="$2"
 
  if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS (BSD sed)
    sed -i '' "$pattern" "$file"
  else
    # Linux and Windows Git Bash (GNU sed)
    sed -i "$pattern" "$file"
  fi
}
 
# Usage
portable_sed_inplace 's/version=1.0/version=2.0/' version.txt

Workflow Implementation:

- name: Update version with portable sed
  shell: bash
  run: |
    VERSION_FILE="version.properties"
    NEW_VERSION="2.0.0"
 
    if [[ "$OSTYPE" == "darwin"* ]]; then
      # BSD sed (macOS)
      sed -i '' "s/^version=.*/version=$NEW_VERSION/" "$VERSION_FILE"
    else
      # GNU sed (Linux/Windows Git Bash)
      sed -i "s/^version=.*/version=$NEW_VERSION/" "$VERSION_FILE"
    fi
 
    echo "Updated version: $(grep '^version=' $VERSION_FILE)"

grep (Pattern Matching)

Featureubuntu-latestWindows Git BashmacOSCompatibility
Basic patternsPortable
Extended regex (-E)Portable
Perl regex (-P)✓ GNU only✓ GNU only✗ Not availableNOT portable
Quiet mode (-q)Portable
Recursive (-r)Portable
Context (-A, -B, -C)Portable

Portable grep Usage:

# PORTABLE - Works everywhere
if echo "$COMMIT_MSG" | grep -q '\[skip-ci\]'; then
  echo "Skipping CI"
fi
 
# PORTABLE - Extended regex
grep -E 'pattern1|pattern2' file.txt
 
# NOT PORTABLE - Perl regex (macOS lacks -P)
grep -P '\d{3}-\d{4}' file.txt  # FAILS on macOS
 
# PORTABLE ALTERNATIVE - Use extended regex instead
grep -E '[0-9]{3}-[0-9]{4}' file.txt

Featureubuntu-latestWindows Git BashmacOSCompatibility
Basic searchPortable
-name patternPortable
-type f/dPortable
-exec command⚠️ Path issuesMostly portable
-delete⚠️ Use -exec rmUse with caution
-printf✓ GNU only✓ GNU only✗ Not availableNOT portable

Portable find Usage:

# PORTABLE - Basic file search
find . -name "*.log" -type f
 
# PORTABLE - Execute command on results
find . -name "*.tmp" -type f -exec rm {} \;
 
# NOT PORTABLE - GNU printf (fails on macOS)
find . -name "*.txt" -printf "%f\n"
 
# PORTABLE ALTERNATIVE
find . -name "*.txt" -exec basename {} \;

awk (Text Processing)

Featureubuntu-latestWindows Git BashmacOSCompatibility
Basic awk✓ GNU awk✓ GNU awk✓ BSD awkMostly portable
Field processingPortable
Built-in functions⚠️ Fewer functionsMostly portable
POSIX compliancePortable

Generally portable - Stick to POSIX awk features.


tar (Archive)

Featureubuntu-latestWindows Git BashmacOSCompatibility
Create archive (-c)Portable
Extract (-x)Portable
Gzip (-z)Portable
Bzip2 (-j)Portable
Verbose (-v)Portable

Fully portable - No significant compatibility issues.

# Works on all platforms
tar -czf archive.tar.gz directory/
tar -xzf archive.tar.gz

curl / wget

Toolubuntu-latestWindows Git BashmacOSCompatibility
curlPortable
wget🔧 May need install🔧 May need installcurl preferred

Recommendation: Use curl for cross-platform HTTP operations.

# PORTABLE - curl is available everywhere
curl -fsSL https://example.com/file.txt -o output.txt
 
# NOT PORTABLE - wget may not be installed
wget https://example.com/file.txt

Text Encoding

Character Encoding Support

Encodingubuntu-latestWindows Self-hostedmacOS Self-hostedNotes
UTF-8✓ Default⚠️ cp1252 default✓ DefaultWindows requires explicit declaration
ASCIIUniversal
Latin-1Common
cp1252✓ DefaultWindows Western European

Critical Issue: Windows uses cp1252 (Western European) by default, which cannot encode emoji or many Unicode characters.

November 2025 Incident:

# This code CRASHED on Windows self-hosted runner
print("Build successful ✅")
 
# Error:
# UnicodeEncodeError: 'charmap' codec can't encode character '\u2705'
# in position 43: character maps to <undefined>

Solution:

- name: Run Python with UTF-8 encoding
  shell: bash
  env:
    PYTHONIOENCODING: utf-8  # CRITICAL for Windows
  run: python script.py

Global Job-Level Encoding:

jobs:
  build:
    runs-on: ${{ matrix.os }}
    env:
      PYTHONIOENCODING: utf-8  # Applies to ALL steps in job
    steps:
      - name: Python step 1
        run: python script1.py  # Inherits UTF-8
 
      - name: Python step 2
        run: python script2.py  # Inherits UTF-8

File System Differences

Path Separators

PlatformSeparatorShell BehaviorNotes
ubuntu-latest/Forward slash onlyPOSIX standard
Windows (bash)/ or \Bash uses /, CMD uses \Use / in bash scripts
macOS/Forward slash onlyPOSIX standard

Portable Path Usage:

# PORTABLE - Use forward slashes in bash
./gradlew assembleRelease
cd src/main/java
 
# AVOID - Backslashes (Windows CMD only)
cd src\main\java  # Fails in bash

Case Sensitivity

PlatformCase SensitiveImpact
ubuntu-latest✓ YesFile.txtfile.txt
Windows✗ NoFile.txt = file.txt
macOS⚠️ No (default)APFS can be case-sensitive but default is insensitive

Implication: Code that works on Windows/macOS may break on Linux if file names differ only by case.

Best Practice:

  • Use consistent casing in all file references
  • Never create files that differ only by case
  • Use lowercase for all file names when possible

Line Endings

PlatformDefault Line EndingGit Behavior
ubuntu-latestLF (\n)Checkout LF
WindowsCRLF (\r\n)Checkout CRLF (autocrlf=true)
macOSLF (\n)Checkout LF

Git Configuration:

# Recommended .gitattributes for cross-platform
* text=auto
*.sh text eol=lf
*.bat text eol=crlf

Shell Script Compatibility:

# Ensure LF endings for shell scripts
dos2unix script.sh  # Convert CRLF to LF if needed
bash script.sh

Environment Variables

Standard Environment Variables

Variableubuntu-latestWindows Self-hostedmacOS Self-hostedNotes
HOME/home/runner⚠️ Use USERPROFILE/Users/runnerWindows uses USERPROFILE
USERrunner⚠️ Use USERNAMErunnerWindows uses USERNAME
SHELL/bin/bash⚠️ PowerShell path/bin/bashPlatform-specific
PATH✓ Colon-separated⚠️ Semicolon-separated✓ Colon-separatedDifferent separators
TEMP/TMP/tmpC:\Users\...\Temp/tmpDifferent paths

Portable Environment Variable Access:

# HOME directory (cross-platform)
if [ -n "$HOME" ]; then
  CONFIG_DIR="$HOME/.config"
elif [ -n "$USERPROFILE" ]; then
  CONFIG_DIR="$USERPROFILE/.config"
else
  echo "ERROR: Cannot determine home directory"
  exit 1
fi

GitHub Actions Variables

All GitHub Actions environment variables are available on all platforms:

VariableDescriptionExample Value
GITHUB_WORKSPACECheckout directory/home/runner/work/repo/repo
GITHUB_REPOSITORYRepository nameowner/repo
GITHUB_SHACommit SHAa1b2c3d...
GITHUB_REFGit refrefs/heads/main
GITHUB_ACTORUser who triggeredusername
RUNNER_OSOperating systemLinux, Windows, macOS
RUNNER_TEMPTemp directoryPlatform-specific

Usage:

- name: Use GitHub variables
  shell: bash
  run: |
    echo "Repository: $GITHUB_REPOSITORY"
    echo "Runner OS: $RUNNER_OS"
    echo "Workspace: $GITHUB_WORKSPACE"

Android Development Tools

Android SDK

Toolubuntu-latestWindows Self-hostedmacOS Self-hostedVersion
ANDROID_HOME⚠️ Set in workflow✓ Pre-configured✓ Pre-configuredSDK 33+
sdkmanagerLatest
adbLatest
emulator⚠️ No hardware accelLatest

Self-hosted runners have persistent Android SDK installations. GitHub-hosted requires setup-android action.

- name: Set up Android SDK (GitHub-hosted only)
  if: runner.os == 'Linux' && contains(needs.decide_runner.outputs.runner, 'ubuntu')
  uses: android-actions/setup-android@v3
 
- name: Verify Android SDK
  shell: bash
  run: |
    echo "ANDROID_HOME: $ANDROID_HOME"
    echo "SDK location: $ANDROID_SDK_ROOT"

Testing Commands Across Platforms

Validation Script

Use this script to test tool availability on any runner:

#!/bin/bash
# platform-test.sh - Test platform compatibility
 
echo "=== Platform Compatibility Test ==="
echo ""
 
echo "Platform Information:"
echo "  OS: $(uname -s)"
echo "  Architecture: $(uname -m)"
echo "  OSTYPE: $OSTYPE"
echo "  Runner OS: ${RUNNER_OS:-Not in GitHub Actions}"
echo ""
 
echo "Shell Information:"
echo "  SHELL: $SHELL"
echo "  bash: $(command -v bash || echo 'NOT FOUND')"
echo "  sh: $(command -v sh || echo 'NOT FOUND')"
if command -v pwsh &> /dev/null; then
  echo "  pwsh: $(command -v pwsh) ($(pwsh --version))"
else
  echo "  pwsh: NOT FOUND"
fi
echo ""
 
echo "Python Runtime:"
if command -v python3 &> /dev/null; then
  echo "  python3: $(command -v python3) ($(python3 --version))"
else
  echo "  python3: NOT FOUND"
fi
if command -v python &> /dev/null; then
  echo "  python: $(command -v python) ($(python --version 2>&1))"
else
  echo "  python: NOT FOUND"
fi
if command -v pip3 &> /dev/null; then
  echo "  pip3: $(command -v pip3) ($(pip3 --version))"
else
  echo "  pip3: NOT FOUND"
fi
if command -v pip &> /dev/null; then
  echo "  pip: $(command -v pip) ($(pip --version))"
else
  echo "  pip: NOT FOUND"
fi
echo ""
 
echo "Java Runtime:"
if command -v java &> /dev/null; then
  echo "  java: $(command -v java)"
  java -version 2>&1 | head -n 1
else
  echo "  java: NOT FOUND"
fi
echo "  JAVA_HOME: ${JAVA_HOME:-NOT SET}"
echo ""
 
echo "Unix Tools:"
echo "  sed: $(command -v sed || echo 'NOT FOUND')"
echo "  grep: $(command -v grep || echo 'NOT FOUND')"
echo "  awk: $(command -v awk || echo 'NOT FOUND')"
echo "  find: $(command -v find || echo 'NOT FOUND')"
echo "  tar: $(command -v tar || echo 'NOT FOUND')"
echo "  curl: $(command -v curl || echo 'NOT FOUND')"
echo "  wget: $(command -v wget || echo 'NOT FOUND')"
echo ""
 
echo "sed In-Place Test:"
TEST_FILE="/tmp/sed-test-$$.txt"
echo "test" > "$TEST_FILE"
if [[ "$OSTYPE" == "darwin"* ]]; then
  echo "  Platform: macOS (BSD sed)"
  sed -i '' 's/test/success/' "$TEST_FILE"
else
  echo "  Platform: Linux/Windows (GNU sed)"
  sed -i 's/test/success/' "$TEST_FILE"
fi
RESULT=$(cat "$TEST_FILE")
rm "$TEST_FILE"
if [ "$RESULT" = "success" ]; then
  echo "  Result: ✓ sed in-place works"
else
  echo "  Result: ✗ sed in-place FAILED"
fi
echo ""
 
echo "Android SDK:"
echo "  ANDROID_HOME: ${ANDROID_HOME:-NOT SET}"
echo "  adb: $(command -v adb || echo 'NOT FOUND')"
echo ""
 
echo "=== Test Complete ==="

Run in workflow:

- name: Test platform compatibility
  shell: bash
  run: |
    chmod +x scripts/platform-test.sh
    ./scripts/platform-test.sh

Quick Reference Cheat Sheet

Command Detection Pattern

# Python
PYTHON=$(command -v python3 || command -v python || echo "NONE")
if [ "$PYTHON" = "NONE" ]; then echo "ERROR: No Python"; exit 1; fi
 
# pip
PIP=$(command -v pip3 || command -v pip || echo "NONE")
if [ "$PIP" = "NONE" ]; then echo "ERROR: No pip"; exit 1; fi

sed In-Place Pattern

# Portable sed in-place
if [[ "$OSTYPE" == "darwin"* ]]; then
  sed -i '' 's/old/new/' file.txt
else
  sed -i 's/old/new/' file.txt
fi

Encoding Pattern

env:
  PYTHONIOENCODING: utf-8  # Always set for Python on Windows

Shell Declaration Pattern

- name: Any cross-platform step
  shell: bash  # Always specify shell
  run: |
    # Commands here

Troubleshooting Guide

Issue: “pip: command not found” on macOS

Cause: macOS Python provides pip3 but not pip symlink

Solution:

# Use pip3 on macOS
if command -v pip3 &> /dev/null; then
  pip3 install requests
fi

Issue: “sed: invalid command code” on macOS

Cause: BSD sed requires empty string after -i for in-place edit

Solution:

# macOS requires -i ''
sed -i '' 's/old/new/' file.txt

Issue: “UnicodeEncodeError” in Python on Windows

Cause: Windows defaults to cp1252 encoding, not UTF-8

Solution:

env:
  PYTHONIOENCODING: utf-8
run: python script.py

Issue: “PowerShell: command not found” on Ubuntu

Cause: GitHub-hosted ubuntu-latest does not have PowerShell 5.1

Solution:

# Use bash or install pwsh
shell: bash
run: echo "Use bash instead"
 
# OR install PowerShell Core
- name: Install pwsh
  run: |
    sudo apt-get update
    sudo apt-get install -y powershell


Document Revision History:

  • 2025-11-23: Initial creation based on November 2025 multi-platform compatibility audit