ci: completely remove Windows build infrastructure

Per request, removed all traces of Windows container builds from the project.
This simplifies the CI pipeline to be Linux-only.

- Deleted `book/docker/windows/` directory and its Dockerfile
- Deleted `.github/workflows/infra-container-windows.yml`
- Removed Windows matrix jobs and steps from `book-build-container.yml`
- Removed Windows inputs and outputs from `book-build-container.yml`
- Removed Windows health checks from `infra-health-check.yml`
- Removed Windows references from `book-publish-live.yml`
- Removed Windows references from `book-validate-dev.yml`
This commit is contained in:
Vijay Janapa Reddi
2026-03-06 10:04:39 -05:00
parent 78b88fa9ce
commit a76aab4676
10 changed files with 4 additions and 1727 deletions

View File

@@ -130,26 +130,6 @@ on:
linux_epub_vol2_artifact:
description: "Linux EPUB artifact name (Volume II)"
value: ${{ jobs.collect-outputs.outputs.linux_epub_vol2_artifact }}
# Windows Volume I artifacts
windows_html_vol1_artifact:
description: "Windows HTML artifact name (Volume I)"
value: ${{ jobs.collect-outputs.outputs.windows_html_vol1_artifact }}
windows_pdf_vol1_artifact:
description: "Windows PDF artifact name (Volume I)"
value: ${{ jobs.collect-outputs.outputs.windows_pdf_vol1_artifact }}
windows_epub_vol1_artifact:
description: "Windows EPUB artifact name (Volume I)"
value: ${{ jobs.collect-outputs.outputs.windows_epub_vol1_artifact }}
# Windows Volume II artifacts
windows_html_vol2_artifact:
description: "Windows HTML artifact name (Volume II)"
value: ${{ jobs.collect-outputs.outputs.windows_html_vol2_artifact }}
windows_pdf_vol2_artifact:
description: "Windows PDF artifact name (Volume II)"
value: ${{ jobs.collect-outputs.outputs.windows_pdf_vol2_artifact }}
windows_epub_vol2_artifact:
description: "Windows EPUB artifact name (Volume II)"
value: ${{ jobs.collect-outputs.outputs.windows_epub_vol2_artifact }}
permissions:
contents: read
@@ -377,19 +357,6 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🐳 Ensure Docker daemon (Windows)
if: matrix.platform == 'windows' && matrix.enabled
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$dockerService = Get-Service -Name docker -ErrorAction Stop
if ($dockerService.Status -ne 'Running') {
Write-Host "Starting Docker service..."
Start-Service -Name docker
} else {
Write-Host "Docker service already running."
}
$maxAttempts = 12
for ($i = 1; $i -le $maxAttempts; $i++) {
@@ -405,9 +372,6 @@ jobs:
}
}
- name: 🐳 Pull Docker Image
if: matrix.platform == 'windows' && matrix.enabled
run: docker pull ${{ env.CONTAINER_IMAGE }}
- name: 🧪 Preflight toolchain (Linux)
if: matrix.platform == 'linux' && matrix.enabled
@@ -454,84 +418,6 @@ jobs:
echo "✅ Linux toolchain preflight checks passed"
- name: 🧪 Preflight toolchain (Windows)
if: matrix.platform == 'windows' && matrix.enabled
shell: pwsh
run: |
Write-Host "🧪 Running Windows toolchain preflight checks..."
$preflightScript = @"
`$ErrorActionPreference = 'Stop'
`$PSNativeCommandUseErrorActionPreference = `$true
function Invoke-Check {
param(
[string]`$Name,
[scriptblock]`$Action
)
Write-Host \"▶ preflight: `$Name\"
try {
& `$Action
if (-not `$?) { throw 'Command returned non-zero status' }
Write-Host \"✅ preflight: `$Name\"
} catch {
`$errorMessage = `$_.Exception.Message
Write-Error (\"❌ preflight failed: `$Name -- {0}\" -f `$errorMessage)
throw
}
}
Invoke-Check 'quarto on PATH' { `$resolved = Get-Command quarto -ErrorAction Stop; Write-Host (\" quarto -> {0}\" -f `$resolved.Source) }
Invoke-Check 'quarto version' { quarto --version | Select-Object -First 1 }
Invoke-Check 'pandoc available' {
`$pandocCmd = Get-Command pandoc -ErrorAction SilentlyContinue
if (`$pandocCmd) {
Write-Host (\" pandoc -> {0}\" -f `$pandocCmd.Source)
} else {
quarto pandoc --version | Select-Object -First 1 | Out-Null
Write-Host \" pandoc -> bundled via quarto\"
}
}
Invoke-Check 'pandoc version' {
`$pandocCmd = Get-Command pandoc -ErrorAction SilentlyContinue
if (`$pandocCmd) {
pandoc --version | Select-Object -First 1
} else {
quarto pandoc --version | Select-Object -First 1
}
}
Invoke-Check 'python on PATH' { `$resolved = Get-Command python -ErrorAction Stop; Write-Host (\" python -> {0}\" -f `$resolved.Source) }
Invoke-Check 'python version' { python --version }
Invoke-Check 'python3 on PATH' { `$resolved = Get-Command python3 -ErrorAction Stop; Write-Host (\" python3 -> {0}\" -f `$resolved.Source) }
Invoke-Check 'python3 version' { python3 --version }
Invoke-Check 'mlsysim import' { python -c 'import mlsysim,sys; print(\"mlsysim:\", mlsysim.__file__); print(\"python:\", sys.executable)' }
Invoke-Check 'Rscript on PATH' { `$resolved = Get-Command Rscript -ErrorAction Stop; Write-Host (\" Rscript -> {0}\" -f `$resolved.Source) }
Invoke-Check 'Rscript version' { Rscript --version | Select-Object -First 1 }
Invoke-Check 'inkscape on PATH' { `$resolved = Get-Command inkscape -ErrorAction Stop; Write-Host (\" inkscape -> {0}\" -f `$resolved.Source) }
Invoke-Check 'inkscape version' { inkscape --version | Select-Object -First 1 }
if ('${{ matrix.format_name }}' -eq 'PDF') {
Invoke-Check 'lualatex on PATH' { `$resolved = Get-Command lualatex -ErrorAction Stop; Write-Host (\" lualatex -> {0}\" -f `$resolved.Source) }
Invoke-Check 'lualatex version' { lualatex --version | Select-Object -First 1 }
Invoke-Check 'ghostscript on PATH' {
`$gsCmd = Get-Command gs -ErrorAction SilentlyContinue
if (-not `$gsCmd) { `$gsCmd = Get-Command gswin64c -ErrorAction SilentlyContinue }
if (-not `$gsCmd) { throw 'Ghostscript not found (expected gs or gswin64c)' }
Write-Host (\" ghostscript -> {0}\" -f `$gsCmd.Source)
}
Invoke-Check 'ghostscript version' {
`$gsCmd = Get-Command gs -ErrorAction SilentlyContinue
if (-not `$gsCmd) { `$gsCmd = Get-Command gswin64c -ErrorAction SilentlyContinue }
& `$gsCmd.Source --version
}
}
if ('${{ matrix.format_name }}' -eq 'EPUB') {
Invoke-Check 'Pillow import' { python -c 'import PIL; print(\"Pillow:\", PIL.__version__)' }
}
"@
$preflightScript | docker run --rm -e PYTHONPATH=C:\workspace -v "$($PWD.Path):C:\workspace" -w "C:\workspace\book\quarto" ${{ env.CONTAINER_IMAGE }} -i pwsh -NoLogo -Command -
Write-Host "✅ Windows toolchain preflight checks passed"
- name: 🔨 Build ${{ matrix.format_name }} (Linux)
if: matrix.platform == 'linux' && matrix.enabled
@@ -545,19 +431,6 @@ jobs:
quarto render --to ${{ matrix.render_target }} --output-dir "${{ matrix.output_dir }}"
echo "✅ ${{ matrix.format_name }} build completed"
- name: 🔨 Build ${{ matrix.format_name }} (Windows)
if: matrix.platform == 'windows' && matrix.enabled
shell: pwsh
run: |
Write-Host "🔨 Building ${{ matrix.format_name }} on Windows container..."
$buildScript = @"
if (Test-Path '_quarto.yml') { Remove-Item '_quarto.yml' -Force }
Copy-Item 'config\${{ matrix.config }}' '_quarto.yml' -Force
quarto render --to ${{ matrix.render_target }} --output-dir '${{ matrix.output_dir }}'
"@
$buildScript | docker run --rm -e PYTHONPATH=C:\workspace -v "$($PWD.Path):C:\workspace" -w "C:\workspace\book\quarto" ${{ env.CONTAINER_IMAGE }} -i pwsh -NoLogo -Command -
Write-Host "✅ ${{ matrix.format_name }} build completed"
- name: 📉 Compress PDF (Linux)
if: matrix.platform == 'linux' && matrix.format_name == 'PDF' && matrix.enabled
@@ -594,45 +467,6 @@ jobs:
echo "⚠️ PDF file not found for compression"
fi
- name: 📉 Compress PDF (Windows)
if: matrix.platform == 'windows' && matrix.format_name == 'PDF' && matrix.enabled
shell: pwsh
run: |
Write-Host "🔨 Compressing PDF on Windows container..."
$compressPdfScript = @"
if (Test-Path 'Machine-Learning-Systems.pdf') {
Write-Host '📉 Compressing PDF with professional compression tool...'
Write-Host '🔍 DEBUG: Current directory:'
Get-Location
Write-Host '🔍 DEBUG: Windows environment info:'
Write-Host ("OS: {0}" -f [System.Environment]::OSVersion.VersionString)
Write-Host ("Architecture: {0}" -f [System.Environment]::Is64BitOperatingSystem)
Write-Host '🔍 DEBUG: Verifying Python and Pillow installation:'
`$py_version = (python --version 2>&1).Trim()
Write-Host " 🐍 Python Version: `$py_version"
`$py_path = (python -c 'import sys; print(sys.executable)').Trim()
Write-Host " 🐍 Python Path: `$py_path"
`$pillow_version = (python -c 'import PIL; print(PIL.__version__)').Trim()
Write-Host " ✅ Pillow Version: `$pillow_version"
Write-Host '🔍 DEBUG: Checking for script at relative path:'
if (Test-Path '..\\..\\publish\\compress_pdf.py') {
Write-Host '✅ Found script at ..\\..\\publish\\compress_pdf.py'
python ..\..\publish\compress_pdf.py --input 'Machine-Learning-Systems.pdf' --output 'compressed.pdf' --quality minimal --verbose
} else {
Write-Host '🔄 Trying fallback path: C:\workspace\book\quarto\publish\compress_pdf.py'
python C:\workspace\book\quarto\publish\compress_pdf.py --input 'Machine-Learning-Systems.pdf' --output 'compressed.pdf' --quality minimal --verbose
}
if (Test-Path 'compressed.pdf') {
Move-Item -Force 'compressed.pdf' 'Machine-Learning-Systems.pdf'
Write-Host '✅ PDF compression completed'
}
} else {
Write-Warning '⚠️ Machine-Learning-Systems.pdf not found for compression.'
}
"@
$compressPdfScript | docker run --rm -e PYTHONPATH=C:\workspace -v "$($PWD.Path):C:\workspace" -w "C:\workspace\book\quarto\${{ matrix.output_dir }}" ${{ env.CONTAINER_IMAGE }} -i pwsh -NoLogo -Command -
Write-Host "✅ PDF compression completed."
- name: 📚 Compress EPUB (Linux)
if: matrix.platform == 'linux' && matrix.format_name == 'EPUB' && matrix.enabled
@@ -671,45 +505,6 @@ jobs:
echo "⚠️ EPUB file not found for compression"
fi
- name: 📚 Compress EPUB (Windows)
if: matrix.platform == 'windows' && matrix.format_name == 'EPUB' && matrix.enabled
shell: pwsh
run: |
Write-Host "🔨 Compressing EPUB on Windows container..."
$compressEpubScript = @"
if (Test-Path 'Machine-Learning-Systems.epub') {
Write-Host '📚 Compressing EPUB with optimized compression tool...'
Write-Host '🔍 DEBUG: Current directory:'
Get-Location
Write-Host '🔍 DEBUG: Windows environment info:'
Write-Host ("OS: {0}" -f [System.Environment]::OSVersion.VersionString)
Write-Host ("Architecture: {0}" -f [System.Environment]::Is64BitOperatingSystem)
Write-Host '🔍 DEBUG: Verifying Python and Pillow installation:'
`$py_version = (python --version 2>&1).Trim()
Write-Host " 🐍 Python Version: `$py_version"
`$py_path = (python -c 'import sys; print(sys.executable)').Trim()
Write-Host " 🐍 Python Path: `$py_path"
`$pillow_version = (python -c 'import PIL; print(PIL.__version__)').Trim()
Write-Host " ✅ Pillow Version: `$pillow_version"
Write-Host '🔍 DEBUG: Checking for script at relative path:'
if (Test-Path '..\\..\\publish\\compress_epub.py') {
Write-Host '✅ Found script at ..\\..\\publish\\compress_epub.py'
python ..\..\publish\compress_epub.py --input 'Machine-Learning-Systems.epub' --output 'compressed.epub' --verbose
} else {
Write-Host '🔄 Trying fallback path: C:\workspace\book\quarto\publish\compress_epub.py'
python C:\workspace\book\quarto\publish\compress_epub.py --input 'Machine-Learning-Systems.epub' --output 'compressed.epub' --verbose
}
if (Test-Path 'compressed.epub') {
Move-Item -Force 'compressed.epub' 'Machine-Learning-Systems.epub'
Write-Host '✅ EPUB compression completed (using optimized defaults: quality=50, max-size=1000px)'
}
} else {
Write-Warning '⚠️ Machine-Learning-Systems.epub not found for compression.'
}
"@
$compressEpubScript | docker run --rm -e PYTHONPATH=C:\workspace -v "$($PWD.Path):C:\workspace" -w "C:\workspace\book\quarto\${{ matrix.output_dir }}" ${{ env.CONTAINER_IMAGE }} -i pwsh -NoLogo -Command -
Write-Host "✅ EPUB compression completed."
- name: 📤 Upload artifact
uses: actions/upload-artifact@v6

View File

@@ -38,9 +38,6 @@ env:
# main-html-linux: Contains build/html/ (web version)
# main-pdf-linux: Contains build/pdf/Machine-Learning-Systems.pdf
# main-epub-linux: Contains build/epub/Machine-Learning-Systems.epub
# main-html-windows: Contains build/html/ (web version)
# main-pdf-windows: Contains build/pdf/Machine-Learning-Systems.pdf
# main-epub-windows: Contains build/epub/Machine-Learning-Systems.epub
#
# This workflow downloads the artifact manifest first to get the exact names,
# then downloads the HTML, PDF, and EPUB artifacts using those names for coordination.
@@ -757,7 +754,6 @@ jobs:
uses: ./.github/workflows/book-build-container.yml
with:
build_linux: true # Production builds Linux only for now
build_windows: false
build_html: true # HTML + PDF + EPUB for production
build_pdf: true
build_epub: true

View File

@@ -111,7 +111,6 @@ jobs:
uses: ./.github/workflows/infra-health-check.yml # Shared workflow - no book- prefix
with:
test_linux: true
test_windows: false
container_registry: 'ghcr.io'
container_tag: 'latest'
@@ -217,7 +216,6 @@ jobs:
uses: ./.github/workflows/book-build-container.yml
with:
build_linux: ${{ needs.build-config.outputs.build_linux == 'true' }}
build_windows: false
build_html: ${{ needs.build-config.outputs.build_html == 'true' }}
build_pdf: ${{ needs.build-config.outputs.build_pdf == 'true' }}
build_epub: ${{ needs.build-config.outputs.build_epub == 'true' }}

View File

@@ -1,245 +0,0 @@
name: '🔧 Infra · 🐳 Container (Windows)'
# This workflow builds the Windows Quarto build container
# Windows containers are more complex but provide performance benefits
# Prevent multiple builds running simultaneously
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
inputs:
force_rebuild:
description: 'Force rebuild even if no changes'
required: false
default: false
type: boolean
no_cache:
description: 'Disable Docker build cache (fresh build)'
required: false
default: false
type: boolean
container_registry:
description: 'Container registry URL'
required: false
default: 'ghcr.io'
type: string
container_name:
description: 'Container image name'
required: false
default: 'quarto-windows'
type: string
container_tag:
description: 'Container tag'
required: false
default: 'latest'
type: string
workflow_call:
inputs:
force_rebuild:
required: false
default: false
type: boolean
no_cache:
required: false
default: false
type: boolean
container_registry:
required: false
default: 'ghcr.io'
type: string
container_name:
required: false
default: 'quarto-windows'
type: string
container_tag:
required: false
default: 'latest'
type: string
outputs:
build-status:
description: "Container build status (success/failure/skipped)"
value: ${{ jobs.build.outputs.build-status }}
image-name:
description: "Full container image name with registry"
value: ${{ jobs.build.outputs.image-name }}
image-digest:
description: "Container image digest (SHA256)"
value: ${{ jobs.build.outputs.image-digest }}
cache-hit:
description: "Whether build used cache (true/false)"
value: ${{ jobs.build.outputs.cache-hit }}
# Re-enable automatic triggers
schedule:
- cron: '0 2 * * 0' # Weekly rebuild (Sunday at 2am - after Linux container)
push:
branches: [dev] # Only trigger on dev branch, not main
paths:
- 'book/tools/dependencies/**'
- 'book/docker/windows/**'
- '.github/workflows/book-build-windows-container.yml'
env:
# =============================================================================
# PATH CONFIGURATION - Uses GitHub Repository Variables (Settings > Variables)
# =============================================================================
# MLSysBook content lives under book/ to accommodate TinyTorch at root
# Use ${{ vars.BOOK_ROOT }}, ${{ vars.BOOK_DOCKER }}, etc. in workflow steps
# Variables: BOOK_ROOT, BOOK_DOCKER, BOOK_TOOLS, BOOK_QUARTO, BOOK_DEPS
# Container Registry Configuration (configurable via inputs)
REGISTRY: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_registry) || 'ghcr.io' }}
IMAGE_NAME: ${{ github.repository }}/${{ (github.event_name == 'workflow_dispatch' && inputs.container_name) || 'quarto-windows' }}
CONTAINER_TAG: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_tag) || 'latest' }}
# Container Build Configuration
# Using vars.BOOK_DOCKER (repository variable) - works in all contexts
DOCKERFILE_PATH: ./${{ vars.BOOK_DOCKER }}/windows/Dockerfile
CONTEXT_PATH: .
jobs:
build:
runs-on: windows-latest
if: github.repository_owner == 'harvard-edge'
timeout-minutes: 180 # takes about 2 hours to build on Windows
permissions:
contents: read
packages: write
outputs:
build-status: ${{ steps.build.outputs.build-status }}
image-name: ${{ steps.build.outputs.image-name }}
image-digest: ${{ steps.build.outputs.image-digest }}
cache-hit: ${{ steps.build.outputs.cache-hit }}
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v6
# Skip Docker Buildx for Windows containers - use native Docker engine
# Buildx doesn't properly support Windows containers on GitHub Actions
# - name: 🛠️ Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
- name: 🔐 Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🏷️ Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ env.CONTAINER_TAG }}
- name: 🐳 Build and Push Windows container
id: build
shell: pwsh
# Use native Docker instead of book/docker/build-push-action for Windows containers
# Buildx has compatibility issues with Windows containers on GitHub Actions
run: |
# Extract image name and tag from metadata
$IMAGE_TAG = "${{ steps.meta.outputs.tags }}"
$NO_CACHE = "${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}"
Write-Host "🔨 Building Windows container..."
Write-Host "📊 Image: $IMAGE_TAG"
Write-Host "📊 Context: ${{ env.CONTEXT_PATH }}"
Write-Host "📊 Dockerfile: ${{ env.DOCKERFILE_PATH }}"
Write-Host "📊 No Cache: $NO_CACHE"
# Build the container using native Docker
$buildArgs = @(
"build",
"--file", "${{ env.DOCKERFILE_PATH }}",
"--tag", $IMAGE_TAG
)
# Add no-cache flag if requested
if ($NO_CACHE -eq "true") {
$buildArgs += "--no-cache"
Write-Host "🚫 Cache disabled - building from scratch"
} else {
Write-Host "💾 Using Docker cache"
}
# Add labels from metadata
$labels = "${{ steps.meta.outputs.labels }}"
if ($labels) {
$labels -split "`n" | ForEach-Object {
if ($_.Trim()) {
$buildArgs += "--label", $_.Trim()
}
}
}
# Add context path
$buildArgs += "${{ env.CONTEXT_PATH }}"
Write-Host "🔨 Running: docker $($buildArgs -join ' ')"
& docker @buildArgs
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Docker build failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host "✅ Build completed successfully"
# Push the container
Write-Host "📤 Pushing container to registry..."
& docker push $IMAGE_TAG
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Docker push failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host "✅ Push completed successfully"
# Get image digest for output
$DIGEST = & docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_TAG 2>$null
if ($DIGEST -match '@(.+)$') {
$DIGEST = $matches[1]
} else {
$DIGEST = "unknown"
}
# Set outputs for build summary
"digest=$DIGEST" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"cache-hit=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: 📊 Build Summary
id: build-summary
if: always()
shell: pwsh
run: |
# Determine build status
if ("${{ steps.build.outcome }}" -eq "success") {
$BUILD_STATUS = "success"
} else {
$BUILD_STATUS = "failure"
}
# Extract build information
$IMAGE_NAME = "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.CONTAINER_TAG }}"
$IMAGE_DIGEST = "${{ steps.build.outputs.digest }}"
$CACHE_HIT = "${{ steps.build.outputs.cache-hit }}"
"build-status=$BUILD_STATUS" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"image-name=$IMAGE_NAME" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"image-digest=$IMAGE_DIGEST" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"cache-hit=$CACHE_HIT" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Write-Host "📊 Build Status: $BUILD_STATUS"
Write-Host "🐳 Image: $IMAGE_NAME"
Write-Host "🔍 Digest: $IMAGE_DIGEST"
Write-Host "💾 Cache Hit: $CACHE_HIT"

View File

@@ -66,10 +66,8 @@ env:
REGISTRY: ${{ inputs.container_registry || 'ghcr.io' }}
CONTAINER_TAG: ${{ inputs.container_tag || 'latest' }}
LINUX_CONTAINER_NAME: 'quarto-linux'
WINDOWS_CONTAINER_NAME: 'quarto-windows'
# Computed full image names
LINUX_IMAGE: ${{ inputs.container_registry || 'ghcr.io' }}/${{ github.repository }}/quarto-linux:${{ inputs.container_tag || 'latest' }}
WINDOWS_IMAGE: ${{ inputs.container_registry || 'ghcr.io' }}/${{ github.repository }}/quarto-windows:${{ inputs.container_tag || 'latest' }}
jobs:
# Matrix strategy for both container platforms
@@ -85,9 +83,6 @@ jobs:
platform: linux
container_name: quarto-linux
shell: bash
- os: windows-latest
platform: windows
container_name: quarto-windows
shell: pwsh
env:
@@ -100,7 +95,6 @@ jobs:
- name: 📥 Checkout repository
if: |
(matrix.platform == 'linux' && inputs.test_linux != false) ||
(matrix.platform == 'windows' && inputs.test_windows != false)
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -108,7 +102,6 @@ jobs:
- name: 🔑 Log in to GitHub Container Registry
if: |
(matrix.platform == 'linux' && inputs.test_linux != false) ||
(matrix.platform == 'windows' && inputs.test_windows != false)
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
@@ -118,13 +111,11 @@ jobs:
- name: 🐳 Pull Docker Image
if: |
(matrix.platform == 'linux' && inputs.test_linux != false) ||
(matrix.platform == 'windows' && inputs.test_windows != false)
run: docker pull ${{ env.CONTAINER_IMAGE }}
- name: 📊 Container Information
if: |
(matrix.platform == 'linux' && inputs.test_linux != false) ||
(matrix.platform == 'windows' && inputs.test_windows != false)
run: |
echo "📊 === CONTAINER INFORMATION ==="
echo "📋 Platform: ${{ matrix.platform }}"
@@ -299,239 +290,6 @@ jobs:
echo "📋 Linux tool versions saved to linux-tool-versions.log"
- name: 🪟 Windows Container - Tool Version Check
if: matrix.platform == 'windows' && inputs.test_windows != false
run: |
Write-Output "🪟 === WINDOWS CONTAINER TOOL VERSIONS ==="
Write-Output "📋 Testing Windows tools with enhanced path detection using 'scoop which':"
Write-Output "=============================================="
docker run --rm ${{ env.CONTAINER_IMAGE }} pwsh -Command "
Write-Output '🔍 === WINDOWS CONTAINER TOOL VERSIONS ==='
Write-Output ''
# Check Scoop environment
Write-Output '📦 SCOOP ENVIRONMENT:'
Write-Output '--------------------'
if (Test-Path 'C:\Users\ContainerAdministrator\scoop') {
Write-Output '✅ Scoop directory found'
if (Test-Path 'C:\Users\ContainerAdministrator\scoop\shims') {
`$shimCount = (Get-ChildItem 'C:\Users\ContainerAdministrator\scoop\shims' -File | Measure-Object).Count
Write-Output `"📁 Scoop shims directory: `$shimCount files`"
}
} else {
Write-Output '❌ Scoop directory not found'
}
Write-Output ''
# --- Tool Checks ---
Write-Output '📊 QUARTO:'
Write-Output '----------'
try {
`$quartoPath = (scoop which quarto 2>`$null).Trim()
if (`$LASTEXITCODE -ne 0) { throw }
Write-Output `"📍 Location: `$quartoPath`"
Write-Output '📦 Method: scoop which'
`$quartoVersion = & `$quartoPath --version 2>&1
Write-Output `"📋 Version: `$quartoVersion`"
Write-Output '✅ Status: OK'
} catch {
try {
`$quartoPath = (Get-Command quarto -ErrorAction Stop).Source
Write-Output `"📍 Location: `$quartoPath`"
Write-Output '📦 Method: Get-Command (PATH)'
`$quartoVersion = & `$quartoPath --version 2>&1
Write-Output `"📋 Version: `$quartoVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
}
Write-Output ''
Write-Output '📊 PYTHON:'
Write-Output '-----------'
try {
`$pythonPath = (scoop which python 2>`$null).Trim()
if (`$LASTEXITCODE -ne 0) { throw }
Write-Output `"📍 Location: `$pythonPath`"
Write-Output '📦 Method: scoop which'
`$pythonVersion = & `$pythonPath --version 2>&1
Write-Output `"📋 Version: `$pythonVersion`"
Write-Output '✅ Status: OK'
} catch {
try {
`$pythonPath = (Get-Command python -ErrorAction Stop).Source
Write-Output `"📍 Location: `$pythonPath`"
Write-Output '📦 Method: Get-Command (PATH)'
`$pythonVersion = & `$pythonPath --version 2>&1
Write-Output `"📋 Version: `$pythonVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
}
Write-Output ''
Write-Output '📊 R:'
Write-Output '------'
try {
`$rCmd = Get-Command R.exe -ErrorAction SilentlyContinue
if (-not `$rCmd) { `$rCmd = Get-Command R -ErrorAction Stop }
`$rPath = `$rCmd.Source
Write-Output `"📍 Location: `$rPath`"
Write-Output '📦 Method: Get-Command (PATH)'
`$rVersion = & `$rCmd.Name --version 2>&1 | Select-Object -First 1
Write-Output `"📋 Version: `$rVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
Write-Output ''
Write-Output '📊 LUALATEX:'
Write-Output '------------'
try {
`$lualatexPath = (Get-Command lualatex -ErrorAction Stop).Source
Write-Output `"📍 Location: `$lualatexPath`"
`$lualatexVersion = & `$lualatexPath --version 2>&1 | Select-Object -First 1
Write-Output `"📋 Version: `$lualatexVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
Write-Output ''
Write-Output '📊 GHOSTSCRIPT:'
Write-Output '---------------'
try {
`$gsPath = (scoop which gswin64c 2>`$null).Trim()
if (`$LASTEXITCODE -ne 0) { `$gsPath = (scoop which gs 2>`$null).Trim() }
if (`$LASTEXITCODE -ne 0) { throw }
Write-Output `"📍 Location: `$gsPath`"
Write-Output '📦 Method: scoop which'
`$gsVersion = & `$gsPath --version 2>&1
Write-Output `"📋 Version: `$gsVersion`"
Write-Output '✅ Status: OK'
} catch {
try {
`$gsCmd = Get-Command gswin64c -ErrorAction SilentlyContinue
if (-not `$gsCmd) { `$gsCmd = Get-Command gs -ErrorAction Stop }
`$gsPath = `$gsCmd.Source
Write-Output `"📍 Location: `$gsPath`"
Write-Output '📦 Method: Get-Command (PATH)'
`$gsVersion = & `$gsCmd.Name --version 2>&1
Write-Output `"📋 Version: `$gsVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
}
Write-Output ''
Write-Output '📊 INKSCAPE:'
Write-Output '------------'
try {
`$inkscapePath = (scoop which inkscape 2>`$null).Trim()
if (`$LASTEXITCODE -ne 0) { throw }
Write-Output `"📍 Location: `$inkscapePath`"
Write-Output '📦 Method: scoop which'
`$inkscapeVersion = & `$inkscapePath --version 2>&1 | Select-Object -First 1
Write-Output `"📋 Version: `$inkscapeVersion`"
Write-Output '✅ Status: OK'
} catch {
try {
`$inkscapePath = (Get-Command inkscape -ErrorAction Stop).Source
Write-Output `"📍 Location: `$inkscapePath`"
Write-Output '📦 Method: Get-Command (PATH)'
`$inkscapeVersion = & `$inkscapePath --version 2>&1 | Select-Object -First 1
Write-Output `"📋 Version: `$inkscapeVersion`"
Write-Output '✅ Status: OK'
} catch {
Write-Output '❌ Status: NOT FOUND'
}
}
Write-Output ''
Write-Output '🎯 === WINDOWS TOOL CHECK COMPLETE ==='
Write-Output ''
# Summary of all tool statuses
Write-Output '📋 WINDOWS TOOL STATUS SUMMARY:'
Write-Output '==============================='
# Check each tool and show status
if ((scoop which quarto 2>`$null) -or (Get-Command quarto -ErrorAction SilentlyContinue)) {
Write-Output '✅ Quarto: AVAILABLE'
} else {
Write-Output '❌ Quarto: MISSING'
}
if ((scoop which python 2>`$null) -or (Get-Command python -ErrorAction SilentlyContinue)) {
Write-Output '✅ Python: AVAILABLE'
} else {
Write-Output '❌ Python: MISSING'
}
if ((Get-Command R.exe -ErrorAction SilentlyContinue) -or (Get-Command R -ErrorAction SilentlyContinue)) {
Write-Output '✅ R: AVAILABLE'
} else {
Write-Output '❌ R: MISSING'
}
if (Get-Command lualatex -ErrorAction SilentlyContinue) {
Write-Output '✅ LuaLaTeX: AVAILABLE'
} else {
Write-Output '❌ LuaLaTeX: MISSING'
}
if ((scoop which gswin64c 2>`$null) -or (scoop which gs 2>`$null) -or (Get-Command gswin64c -ErrorAction SilentlyContinue) -or (Get-Command gs -ErrorAction SilentlyContinue)) {
Write-Output '✅ Ghostscript: AVAILABLE'
} else {
Write-Output '❌ Ghostscript: MISSING'
}
if ((scoop which inkscape 2>`$null) -or (Get-Command inkscape -ErrorAction SilentlyContinue)) {
Write-Output '✅ Inkscape: AVAILABLE'
} else {
Write-Output '❌ Inkscape: MISSING'
}
Write-Output ''
# Check if any tools are missing and fail if so
`$FailedTools = 0
if (-not ((scoop which quarto 2>`$null) -or (Get-Command quarto -ErrorAction SilentlyContinue))) { `$FailedTools++ }
if (-not ((scoop which python 2>`$null) -or (Get-Command python -ErrorAction SilentlyContinue))) { `$FailedTools++ }
if (-not ((Get-Command R.exe -ErrorAction SilentlyContinue) -or (Get-Command R -ErrorAction SilentlyContinue))) { `$FailedTools++ }
if (-not (Get-Command lualatex -ErrorAction SilentlyContinue)) { `$FailedTools++ }
if (-not ((scoop which gswin64c 2>`$null) -or (scoop which gs 2>`$null) -or (Get-Command gswin64c -ErrorAction SilentlyContinue) -or (Get-Command gs -ErrorAction SilentlyContinue))) { `$FailedTools++ }
if (-not ((scoop which inkscape 2>`$null) -or (Get-Command inkscape -ErrorAction SilentlyContinue))) { `$FailedTools++ }
if (`$FailedTools -eq 0) {
Write-Output '🎯 ✅ Windows container tool validation: PASSED'
Write-Output 'All essential Quarto build tools are available!'
} else {
Write-Output '🎯 ❌ Windows container tool validation: FAILED'
Write-Output "Missing `$FailedTools essential tools - container not ready!"
Write-Output 'This container is NOT ready for Quarto builds.'
exit 1
}
Write-Output ''
# List all Scoop apps for debugging
Write-Output '📦 SCOOP INSTALLED APPS:'
Write-Output '------------------------'
if (Get-Command scoop -ErrorAction SilentlyContinue) {
try {
`$scoopApps = & scoop list 2>&1
Write-Output `$scoopApps
} catch {
Write-Output 'Failed to list Scoop apps'
}
} else {
Write-Output 'Scoop command not found'
}
" | Tee-Object -FilePath "windows-tool-versions.log"
Write-Output "📋 Windows tool versions saved to windows-tool-versions.log"
- name: 🐧 Linux Container - Quarto Check
if: matrix.platform == 'linux' && inputs.test_linux != false
@@ -599,146 +357,6 @@ jobs:
fi
fi
- name: 🪟 Windows Container - Quarto Check
if: matrix.platform == 'windows' && inputs.test_windows != false
continue-on-error: true # Don't fail workflow if quarto check fails
run: |
Write-Output "🪟 === WINDOWS QUARTO CHECK (COMPREHENSIVE) ==="
Write-Output "📋 Running quarto check to validate full installation:"
Write-Output "====================================================="
# 1) Emit the inner script to a file (no escaping headaches)
$inner = @'
Write-Output "🔍 Running enhanced Quarto diagnostics..."
Write-Output "📋 Capturing comprehensive diagnostic information..."
Write-Output ""
Write-Output '--- SYSTEM DIAGNOSTIC INFO START ---'
Write-Output '🔧 Environment Variables:'
Write-Output ("PATH: {0}" -f $env:PATH)
Write-Output ("QUARTO_LOG_LEVEL: {0}" -f $env:QUARTO_LOG_LEVEL)
Write-Output ""
Write-Output '🔧 Visual C++ Redistributable DLLs:'
Get-ChildItem 'C:\Windows\System32' -Filter 'msvcp*.dll' -ErrorAction SilentlyContinue |
Select-Object Name, Length |
Format-Table -AutoSize | Out-String | Write-Output
Get-ChildItem 'C:\Windows\System32' -Filter 'vcruntime*.dll' -ErrorAction SilentlyContinue |
Select-Object Name, Length |
Format-Table -AutoSize | Out-String | Write-Output
Write-Output ""
Write-Output '🔧 Quarto Installation Info:'
try {
$quartoCmd = Get-Command quarto.exe -ErrorAction Stop
Write-Output ("Quarto.exe location: {0}" -f $quartoCmd.Source)
Write-Output ("Quarto.exe file size: {0} bytes" -f (Get-Item $quartoCmd.Source).Length)
$quartoVersion = & $quartoCmd.Source --version 2>&1
Write-Output ("Quarto version: {0}" -f $quartoVersion.Trim())
} catch {
Write-Output 'quarto.exe not found in PATH'
}
Write-Output '--- SYSTEM DIAGNOSTIC INFO END ---'
Write-Output ""
# Run quarto check with enhanced error handling and raw output capture
$quartoExitCode = 0
try {
Write-Output '--- QUARTO CHECK OUTPUT START ---'
Write-Output '🔍 Setting maximum verbosity for raw output capture...'
$env:QUARTO_LOG_LEVEL = 'DEBUG'
$env:QUARTO_PRINT_STACK = 'true'
# Capture raw output with timestamps using a more robust method
Write-Output "⏰ Quarto check started at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$stdOutPath = [System.IO.Path]::GetTempFileName()
$stdErrPath = [System.IO.Path]::GetTempFileName()
$quartoExitCode = 0
try {
$process = Start-Process -FilePath "quarto.exe" -ArgumentList "check", "--log-level=debug" -Wait -PassThru -RedirectStandardOutput $stdOutPath -RedirectStandardError $stdErrPath
$quartoExitCode = $process.ExitCode
} catch {
$quartoExitCode = 1 # Indicate failure
Write-Output "‼️ Error executing Start-Process: $($_.Exception.Message)"
}
$rawOutput = Get-Content $stdOutPath -Raw -ErrorAction SilentlyContinue
$errorOutput = Get-Content $stdErrPath -Raw -ErrorAction SilentlyContinue
# Combine stdout and stderr for the log
if (![string]::IsNullOrEmpty($errorOutput)) {
$rawOutput += "`n--- STDERR ---`n" + $errorOutput
}
Remove-Item $stdOutPath, $stdErrPath -Force -ErrorAction SilentlyContinue
Write-Output "⏰ Quarto check completed at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
# Display raw output line by line with prefixes for clarity
Write-Output '📋 RAW QUARTO CHECK OUTPUT:'
Write-Output '┌─────────────────────────────────────────────────────────────────────────────┐'
if (-not [string]::IsNullOrEmpty($rawOutput)) {
foreach ($line in $rawOutput.Split("`n")) {
Write-Output "│ $line"
}
}
Write-Output '└─────────────────────────────────────────────────────────────────────────────┘'
Write-Output '--- QUARTO CHECK OUTPUT END ---'
} catch {
Write-Output '--- QUARTO CHECK EXCEPTION ---'
Write-Output ("Exception Type: {0}" -f $($_.Exception.GetType().FullName))
Write-Output ("Exception Message: {0}" -f $($_.Exception.Message))
if ($_.Exception.InnerException) {
Write-Output ("Inner Exception: {0}" -f $($_.Exception.InnerException.Message))
}
Write-Output '--- QUARTO CHECK EXCEPTION END ---'
$quartoExitCode = 1
}
Write-Output ""
if ($quartoExitCode -eq 0) {
Write-Output '✅ Quarto check: PASSED - All components verified!'
$QuartoStatus = 'PASSED'
} else {
Write-Output '❌ Quarto check: FAILED - Issues detected!'
Write-Output '🔍 This indicates potential container configuration issues.'
Write-Output '📋 Check the diagnostic information above for details.'
Write-Output ("📊 Exit Code: {0}" -f $quartoExitCode)
switch ($quartoExitCode) {
-1073741515 { Write-Output '🔍 Exit code -1073741515 (0xC0000135): DLL not found - Missing Visual C++ Runtime or dependencies' }
1 { Write-Output '🔍 Exit code 1: General error - Check Quarto dependencies' }
default { Write-Output ("🔍 Exit code {0}: Unknown error - Check logs above" -f $quartoExitCode) }
}
$QuartoStatus = 'FAILED'
}
Write-Output ""
Write-Output ("📊 QUARTO CHECK SUMMARY: {0}" -f $QuartoStatus)
'@
$scriptPath = Join-Path $PWD 'windows-quarto-check.ps1'
Set-Content -Path $scriptPath -Value $inner -Encoding UTF8
# 2) Run it inside the Windows container and tee the output
docker run --rm `
-v "${PWD}:C:\work" `
${{ env.CONTAINER_IMAGE }} `
pwsh -NoProfile -ExecutionPolicy Bypass -File "C:\work\windows-quarto-check.ps1" `
| Tee-Object -FilePath "windows-quarto-check.log"
Write-Output "📋 Windows quarto check results saved to windows-quarto-check.log"
# Also save just the raw quarto check output to a separate file for easy viewing
if (Test-Path "windows-quarto-check.log") {
$logContent = Get-Content "windows-quarto-check.log" -Raw
if ($logContent -match '📋 RAW QUARTO CHECK OUTPUT:(.*?)--- QUARTO CHECK OUTPUT END ---') {
$rawOutput = $matches[1].Trim()
$rawOutput | Out-File "windows-quarto-raw-output.txt" -Encoding UTF8
Write-Output "📄 Raw Quarto output extracted to windows-quarto-raw-output.txt"
}
}
@@ -753,27 +371,16 @@ jobs:
echo "✅ Tool validation enforced - will FAIL if any essential tool missing"
echo "LINUX CONTAINER TESTS COMPLETE"
- name: 📊 Windows Container Analysis
if: matrix.platform == 'windows' && inputs.test_windows != false
run: |
Write-Output "WINDOWS CONTAINER TEST SUMMARY"
Write-Output "✅ Container pulled successfully"
Write-Output "✅ Essential tools validated with pass/fail status (Quarto, Python, R, LaTeX, Ghostscript, Inkscape)"
Write-Output "✅ Quarto check completed"
Write-Output "✅ Container size displayed"
Write-Output "✅ Tool validation enforced - will FAIL if any essential tool missing"
Write-Output "WINDOWS CONTAINER TESTS COMPLETE"
- name: 📤 Upload Test Artifacts
if: always() && ((matrix.platform == 'linux' && inputs.test_linux != false) || (matrix.platform == 'windows' && inputs.test_windows != false))
if: always() && matrix.platform == 'linux' && inputs.test_linux != false
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.platform }}-container-test-results
path: |
${{ matrix.platform == 'linux' && 'linux-tool-versions.log' || 'windows-tool-versions.log' }}
${{ matrix.platform == 'linux' && 'linux-quarto-check.log' || 'windows-quarto-check.log' }}
${{ matrix.platform == 'linux' && 'linux-quarto-raw-output.txt' || 'windows-quarto-raw-output.txt' }}
${{ matrix.platform == 'windows' && 'windows-quarto-check.ps1' || '' }}
linux-tool-versions.log
linux-quarto-check.log
linux-quarto-raw-output.txt
if-no-files-found: warn
# Final summary job
@@ -791,14 +398,11 @@ jobs:
# Get container sizes
LINUX_IMAGE="${{ inputs.container_registry || 'ghcr.io' }}/${{ github.repository }}/quarto-linux:${{ inputs.container_tag || 'latest' }}"
WINDOWS_IMAGE="${{ inputs.container_registry || 'ghcr.io' }}/${{ github.repository }}/quarto-windows:${{ inputs.container_tag || 'latest' }}"
LINUX_SIZE=$(docker images "$LINUX_IMAGE" --format "{{.Size}}" 2>/dev/null || echo "Not available")
WINDOWS_SIZE=$(docker images "$WINDOWS_IMAGE" --format "{{.Size}}" 2>/dev/null || echo "Not available")
echo "📦 CONTAINER SIZES:"
echo "------------------"
echo "🐧 Linux: $LINUX_SIZE"
echo "🪟 Windows: $WINDOWS_SIZE"
echo ""
echo "🔍 TEST RESULTS:"