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
| Attribute | ubuntu-latest (GitHub) | Windows 11 (Self-hosted) | macOS Sonoma (Self-hosted) |
|---|---|---|---|
| OS Version | Ubuntu 22.04 LTS | Windows 11 Pro | macOS 14.x (Sonoma) |
| Architecture | x86_64 | x86_64 | ARM64 (Apple Silicon) |
| Default Shell | bash | PowerShell 5.1 | bash (zsh interactive) |
| Hosting | GitHub Cloud | Local machine | Local machine |
| Quota Impact | Yes (minutes) | No | No |
| Persistence | Ephemeral (clean slate) | Persistent | Persistent |
| Root/Admin | sudo available | Admin rights | sudo available |
Shell Availability
Detailed Shell Matrix
| Shell | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Notes |
|---|---|---|---|---|
| bash | ✓ Default | ⚠️ Git Bash | ✓ Default | Windows requires Git for Windows |
| sh | ✓ | ✗ | ✓ | POSIX shell (linked to bash on Ubuntu) |
| PowerShell 5.1 | ✗ | ✓ Default | ✗ | Windows-only legacy PowerShell |
| pwsh (PowerShell Core) | ✓ Installed | ✓ Installed | 🔧 Not default | Cross-platform PowerShell 7+ |
| zsh | 🔧 Install needed | ✗ | ✓ Interactive | macOS default interactive shell |
| cmd | ✗ | ✓ | ✗ | Windows 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
| Command | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Version Range |
|---|---|---|---|---|
| python | ⚠️ Points to python3 | ✓ Python 3.x | ✗ Not available | 3.10 - 3.12 |
| python3 | ✓ Default | ⚠️ May not exist | ✓ Default | 3.10 - 3.12 |
| pip | ⚠️ Points to pip3 | ✓ Available | ✗ Not available | Latest |
| pip3 | ✓ Default | ⚠️ May not exist | ✓ Default | Latest |
| python2 | 🔧 Not default | ✗ | 🔧 Not default | Deprecated |
Critical Discovery (November 2025):
- macOS has
python3andpip3but NOpythonorpipsymlinks - Windows may have
pythonandpipbut notpython3orpip3 - 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 requestsWorkflow 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.txtJava / Gradle
| Tool | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Version |
|---|---|---|---|---|
| java | ✓ Multiple versions | ✓ JDK 17+ | ✓ JDK 17+ | 11, 17, 21 |
| javac | ✓ | ✓ | ✓ | Matches java |
| gradle | ⚠️ Use gradlew | ⚠️ Use gradlew | ⚠️ Use gradlew | Via wrapper |
| ./gradlew | ✓ | ⚠️ gradlew.bat | ✓ | Project-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
fiJava 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
| Tool | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Version |
|---|---|---|---|---|
| node | ✓ | ✓ | ✓ | 18.x LTS |
| npm | ✓ | ✓ | ✓ | 9.x+ |
| npx | ✓ | ✓ | ✓ | Bundled with npm |
| yarn | 🔧 | 🔧 | 🔧 | Install if needed |
No significant compatibility issues - Node.js is highly portable.
Unix Core Utilities
sed (Stream Editor)
| Feature | ubuntu-latest (GNU) | Windows (Git Bash GNU) | macOS (BSD) | Compatibility |
|---|---|---|---|---|
| Basic substitution | ✓ | ✓ | ✓ | Portable |
| In-place edit (-i) | ⚠️ sed -i 's/old/new/' | ⚠️ sed -i 's/old/new/' | ✗ Different syntax | NOT PORTABLE |
| In-place BSD style | ✗ | ✗ | ⚠️ sed -i '' 's/old/new/' | macOS only |
| Extended regex (-E) | ✓ | ✓ | ✓ | Portable |
| Extended regex (-r) | ✓ GNU only | ✓ GNU only | ✗ Use -E | Not 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.txtPlatform-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.txtWorkflow 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)
| Feature | ubuntu-latest | Windows Git Bash | macOS | Compatibility |
|---|---|---|---|---|
| Basic patterns | ✓ | ✓ | ✓ | Portable |
| Extended regex (-E) | ✓ | ✓ | ✓ | Portable |
| Perl regex (-P) | ✓ GNU only | ✓ GNU only | ✗ Not available | NOT 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.txtfind (File Search)
| Feature | ubuntu-latest | Windows Git Bash | macOS | Compatibility |
|---|---|---|---|---|
| Basic search | ✓ | ✓ | ✓ | Portable |
| -name pattern | ✓ | ✓ | ✓ | Portable |
| -type f/d | ✓ | ✓ | ✓ | Portable |
| -exec command | ✓ | ⚠️ Path issues | ✓ | Mostly portable |
| -delete | ✓ | ⚠️ Use -exec rm | ✓ | Use with caution |
| -printf | ✓ GNU only | ✓ GNU only | ✗ Not available | NOT 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)
| Feature | ubuntu-latest | Windows Git Bash | macOS | Compatibility |
|---|---|---|---|---|
| Basic awk | ✓ GNU awk | ✓ GNU awk | ✓ BSD awk | Mostly portable |
| Field processing | ✓ | ✓ | ✓ | Portable |
| Built-in functions | ✓ | ✓ | ⚠️ Fewer functions | Mostly portable |
| POSIX compliance | ✓ | ✓ | ✓ | Portable |
Generally portable - Stick to POSIX awk features.
tar (Archive)
| Feature | ubuntu-latest | Windows Git Bash | macOS | Compatibility |
|---|---|---|---|---|
| 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.gzcurl / wget
| Tool | ubuntu-latest | Windows Git Bash | macOS | Compatibility |
|---|---|---|---|---|
| curl | ✓ | ✓ | ✓ | Portable |
| wget | ✓ | 🔧 May need install | 🔧 May need install | curl 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.txtText Encoding
Character Encoding Support
| Encoding | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Notes |
|---|---|---|---|---|
| UTF-8 | ✓ Default | ⚠️ cp1252 default | ✓ Default | Windows requires explicit declaration |
| ASCII | ✓ | ✓ | ✓ | Universal |
| Latin-1 | ✓ | ✓ | ✓ | Common |
| cp1252 | ✓ | ✓ Default | ✓ | Windows 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.pyGlobal 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-8File System Differences
Path Separators
| Platform | Separator | Shell Behavior | Notes |
|---|---|---|---|
| ubuntu-latest | / | Forward slash only | POSIX standard |
| Windows (bash) | / or \ | Bash uses /, CMD uses \ | Use / in bash scripts |
| macOS | / | Forward slash only | POSIX 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 bashCase Sensitivity
| Platform | Case Sensitive | Impact |
|---|---|---|
| ubuntu-latest | ✓ Yes | File.txt ≠ file.txt |
| Windows | ✗ No | File.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
| Platform | Default Line Ending | Git Behavior |
|---|---|---|
| ubuntu-latest | LF (\n) | Checkout LF |
| Windows | CRLF (\r\n) | Checkout CRLF (autocrlf=true) |
| macOS | LF (\n) | Checkout LF |
Git Configuration:
# Recommended .gitattributes for cross-platform
* text=auto
*.sh text eol=lf
*.bat text eol=crlfShell Script Compatibility:
# Ensure LF endings for shell scripts
dos2unix script.sh # Convert CRLF to LF if needed
bash script.shEnvironment Variables
Standard Environment Variables
| Variable | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Notes |
|---|---|---|---|---|
| HOME | ✓ /home/runner | ⚠️ Use USERPROFILE | ✓ /Users/runner | Windows uses USERPROFILE |
| USER | ✓ runner | ⚠️ Use USERNAME | ✓ runner | Windows uses USERNAME |
| SHELL | ✓ /bin/bash | ⚠️ PowerShell path | ✓ /bin/bash | Platform-specific |
| PATH | ✓ Colon-separated | ⚠️ Semicolon-separated | ✓ Colon-separated | Different separators |
| TEMP/TMP | ✓ /tmp | ✓ C:\Users\...\Temp | ✓ /tmp | Different 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
fiGitHub Actions Variables
All GitHub Actions environment variables are available on all platforms:
| Variable | Description | Example Value |
|---|---|---|
| GITHUB_WORKSPACE | Checkout directory | /home/runner/work/repo/repo |
| GITHUB_REPOSITORY | Repository name | owner/repo |
| GITHUB_SHA | Commit SHA | a1b2c3d... |
| GITHUB_REF | Git ref | refs/heads/main |
| GITHUB_ACTOR | User who triggered | username |
| RUNNER_OS | Operating system | Linux, Windows, macOS |
| RUNNER_TEMP | Temp directory | Platform-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
| Tool | ubuntu-latest | Windows Self-hosted | macOS Self-hosted | Version |
|---|---|---|---|---|
| ANDROID_HOME | ⚠️ Set in workflow | ✓ Pre-configured | ✓ Pre-configured | SDK 33+ |
| sdkmanager | ✓ | ✓ | ✓ | Latest |
| adb | ✓ | ✓ | ✓ | Latest |
| emulator | ⚠️ No hardware accel | ✓ | ✓ | Latest |
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.shQuick 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; fised 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
fiEncoding Pattern
env:
PYTHONIOENCODING: utf-8 # Always set for Python on WindowsShell Declaration Pattern
- name: Any cross-platform step
shell: bash # Always specify shell
run: |
# Commands hereTroubleshooting 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
fiIssue: “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.txtIssue: “UnicodeEncodeError” in Python on Windows
Cause: Windows defaults to cp1252 encoding, not UTF-8
Solution:
env:
PYTHONIOENCODING: utf-8
run: python script.pyIssue: “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 powershellRelated Documentation
- Multi-Platform Workflows - Defensive programming patterns
- Hybrid Runner System - Runner selection logic
- Troubleshooting Guide - Common CI/CD issues
Document Revision History:
- 2025-11-23: Initial creation based on November 2025 multi-platform compatibility audit