mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-03-09 07:15:51 -05:00
Overhaul Windows container build for PATH and tool reliability
- Add explicit ENV PATH directive (Phase 15) so Docker layers inherit tool paths instead of relying on registry writes - Reorder phases: TeX Live moved last (slowest, fail last) - Create stable symlink C:\texlive\bin\windows for year-agnostic PATH - Skip pip self-upgrade to avoid WinError 3 shim lock - Use gswin64c (correct Scoop binary name) instead of gs - Add rsvg-convert fallback to Chocolatey if Scoop fails - Replace fragile verification loop with Test-Tool function - Relax ErrorActionPreference for Chocolatey TeX Live in baremetal
This commit is contained in:
158
.github/workflows/book-build-baremetal.yml
vendored
158
.github/workflows/book-build-baremetal.yml
vendored
@@ -540,6 +540,28 @@ jobs:
|
||||
inkscape --version
|
||||
Write-Output "✅ Inkscape installation complete"
|
||||
|
||||
- name: 🧩 Install rsvg-convert (Windows)
|
||||
if: matrix.enabled && runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Output "=== INSTALLING RSVG-CONVERT ==="
|
||||
Write-Output "Required for Quarto PDF SVG conversion when use-rsvg-convert: true"
|
||||
|
||||
scoop install rsvg-convert
|
||||
if (-not (Get-Command rsvg-convert -ErrorAction SilentlyContinue)) {
|
||||
Write-Output "⚠️ Scoop install failed; trying Chocolatey fallback..."
|
||||
choco install rsvg-convert -y
|
||||
}
|
||||
|
||||
if (-not (Get-Command rsvg-convert -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "❌ rsvg-convert installation failed via both Scoop and Chocolatey"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "📊 rsvg-convert version:"
|
||||
rsvg-convert --version
|
||||
Write-Output "✅ rsvg-convert installation complete"
|
||||
|
||||
- name: 📦 Install Ghostscript via Scoop (Windows)
|
||||
if: matrix.enabled && runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
@@ -552,7 +574,14 @@ jobs:
|
||||
Write-Output "✅ Ghostscript installed"
|
||||
|
||||
Write-Output "📊 Ghostscript version:"
|
||||
gs --version
|
||||
if (Get-Command gswin64c -ErrorAction SilentlyContinue) {
|
||||
gswin64c --version
|
||||
} elseif (Get-Command gs -ErrorAction SilentlyContinue) {
|
||||
gs --version
|
||||
} else {
|
||||
Write-Error "❌ Ghostscript executable not found (expected gswin64c or gs)"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "✅ Ghostscript installation complete"
|
||||
|
||||
- name: 📦 Install Visual C++ Redistributable (Windows)
|
||||
@@ -776,10 +805,17 @@ jobs:
|
||||
Write-Output "This matches the Windows container setup (Phase 4)"
|
||||
|
||||
Write-Output "📦 Installing TeX Live via Chocolatey (pinned to 2025.20251008.0)..."
|
||||
choco install texlive --version=2025.20251008.0 -y
|
||||
# Temporarily relax ErrorActionPreference — Chocolatey's TeX Live
|
||||
# installer emits non-fatal RemoteException messages that PowerShell's
|
||||
# strict mode would otherwise terminate on.
|
||||
$prevPref = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
choco install texlive --version=2025.20251008.0 -y --no-progress
|
||||
$chocoExit = $LASTEXITCODE
|
||||
$ErrorActionPreference = $prevPref
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "❌ TeX Live installation failed"
|
||||
if ($chocoExit -ne 0) {
|
||||
Write-Error "❌ TeX Live installation failed (exit code: $chocoExit)"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "✅ TeX Live installed via Chocolatey"
|
||||
@@ -844,6 +880,8 @@ jobs:
|
||||
package_file: ${{ vars.BOOK_DEPS }}/tl_packages
|
||||
texlive_version: 2025
|
||||
cache_version: 1
|
||||
env:
|
||||
CTAN_MIRROR: https://ctan.math.illinois.edu/systems/texlive/tlnet
|
||||
|
||||
- name: 🔍 Verify TeX Live Installation
|
||||
if: matrix.enabled
|
||||
@@ -1016,78 +1054,50 @@ jobs:
|
||||
Write-Output "📊 TOOL VERIFICATION:"
|
||||
Write-Output "---------------------"
|
||||
$failures = @()
|
||||
function Test-Tool {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string[]]$Candidates,
|
||||
[string[]]$Args
|
||||
)
|
||||
|
||||
# -- Quarto --
|
||||
Write-Output "Checking Quarto..."
|
||||
try {
|
||||
$qv = & quarto --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "quarto --version exited with $LASTEXITCODE" }
|
||||
Write-Output " Quarto $qv"
|
||||
Write-Output "✅ Quarto verified"
|
||||
} catch {
|
||||
Write-Output "❌ Quarto verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "Quarto"
|
||||
Write-Output "Checking $Name..."
|
||||
$resolved = $null
|
||||
$cmdToRun = $null
|
||||
foreach ($candidate in $Candidates) {
|
||||
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
||||
if ($cmd) {
|
||||
$resolved = $cmd
|
||||
$cmdToRun = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $resolved) {
|
||||
Write-Output "❌ $Name verification FAILED: command not found ($($Candidates -join ', '))"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Output " Resolved: $($resolved.Source)"
|
||||
$out = & $cmdToRun @Args 2>&1 | Select-Object -First 1
|
||||
$exitCode = if ($null -eq $LASTEXITCODE) { if ($?) { 0 } else { 1 } } else { [int]$LASTEXITCODE }
|
||||
if ($exitCode -ne 0) {
|
||||
Write-Output "❌ $Name verification FAILED: $cmdToRun exited with $exitCode"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Output " $out"
|
||||
Write-Output "✅ $Name verified"
|
||||
return $true
|
||||
}
|
||||
|
||||
# -- Python --
|
||||
Write-Output "Checking Python..."
|
||||
try {
|
||||
$pv = & python --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "python --version exited with $LASTEXITCODE" }
|
||||
Write-Output " $pv"
|
||||
Write-Output "✅ Python verified"
|
||||
} catch {
|
||||
Write-Output "❌ Python verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "Python"
|
||||
}
|
||||
|
||||
# -- R --
|
||||
Write-Output "Checking R..."
|
||||
try {
|
||||
$rv = & Rscript --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "Rscript --version exited with $LASTEXITCODE" }
|
||||
Write-Output " $rv"
|
||||
Write-Output "✅ R verified"
|
||||
} catch {
|
||||
Write-Output "❌ R verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "R"
|
||||
}
|
||||
|
||||
# -- LaTeX --
|
||||
Write-Output "Checking LaTeX..."
|
||||
try {
|
||||
$lv = & lualatex --version 2>&1 | Select-Object -First 1
|
||||
if ($LASTEXITCODE -ne 0) { throw "lualatex --version exited with $LASTEXITCODE" }
|
||||
Write-Output " $lv"
|
||||
Write-Output "✅ LaTeX verified"
|
||||
} catch {
|
||||
Write-Output "❌ LaTeX verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "LaTeX"
|
||||
}
|
||||
|
||||
# -- Ghostscript --
|
||||
Write-Output "Checking Ghostscript..."
|
||||
try {
|
||||
$gv = & gs --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "gs --version exited with $LASTEXITCODE" }
|
||||
Write-Output " Ghostscript $gv"
|
||||
Write-Output "✅ Ghostscript verified"
|
||||
} catch {
|
||||
Write-Output "❌ Ghostscript verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "Ghostscript"
|
||||
}
|
||||
|
||||
# -- Inkscape --
|
||||
Write-Output "Checking Inkscape..."
|
||||
try {
|
||||
$iv = & inkscape --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "inkscape --version exited with $LASTEXITCODE" }
|
||||
Write-Output " $iv"
|
||||
Write-Output "✅ Inkscape verified"
|
||||
} catch {
|
||||
Write-Output "❌ Inkscape verification FAILED: $($_.Exception.Message)"
|
||||
$failures += "Inkscape"
|
||||
}
|
||||
if (-not (Test-Tool -Name "Quarto" -Candidates @("quarto") -Args @("--version"))) { $failures += "Quarto" }
|
||||
if (-not (Test-Tool -Name "Python" -Candidates @("python") -Args @("--version"))) { $failures += "Python" }
|
||||
if (-not (Test-Tool -Name "R" -Candidates @("Rscript") -Args @("--version"))) { $failures += "R" }
|
||||
if (-not (Test-Tool -Name "LaTeX" -Candidates @("lualatex") -Args @("--version"))) { $failures += "LaTeX" }
|
||||
if (-not (Test-Tool -Name "Ghostscript" -Candidates @("gswin64c","gs") -Args @("--version"))) { $failures += "Ghostscript" }
|
||||
if (-not (Test-Tool -Name "Inkscape" -Candidates @("inkscape") -Args @("--version"))) { $failures += "Inkscape" }
|
||||
if (-not (Test-Tool -Name "rsvg-convert" -Candidates @("rsvg-convert") -Args @("--version"))) { $failures += "rsvg-convert" }
|
||||
|
||||
# -- Final verdict --
|
||||
Write-Output ""
|
||||
@@ -1378,7 +1388,7 @@ jobs:
|
||||
if: matrix.enabled && always() # Upload logs even if build fails
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-logs-${{ matrix.os_name }}-${{ matrix.format }}-${{ github.run_id }}
|
||||
name: build-logs-${{ matrix.os_name }}-${{ matrix.format }}-${{ matrix.volume }}-${{ github.run_id }}
|
||||
path: logs/
|
||||
if-no-files-found: warn
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# - PowerShell 7 via ZIP (no MSI)
|
||||
# - Quarto via Scoop (extras bucket)
|
||||
# - Python 3.13.1 + requirements
|
||||
# - Ghostscript + Inkscape (Chocolatey)
|
||||
# - Ghostscript + Inkscape (Scoop)
|
||||
# - TeX Live (latest by default; overridable via TEXLIVE_VERSION build arg) + packages from tl_packages
|
||||
# - R 4.5.2 + packages via install_packages.R
|
||||
# - Verifications: versions, kpsewhich font files, TikZ smoke test
|
||||
@@ -109,15 +109,7 @@ COPY book/docker/windows/install_texlive.ps1 C:/temp/install_texlive.ps1
|
||||
RUN Write-Host '✅ Dependency file copy complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 4: Install TeX Live FIRST (Most complex, fail fast)
|
||||
# Direct install-tl approach via standalone script:
|
||||
# - Bypasses Chocolatey's ErrorActionPreference=Stop wrapper
|
||||
# - Pins mirror in profile, tries 3 mirrors in order
|
||||
# ------------------------------------------------------------
|
||||
RUN & 'C:\temp\install_texlive.ps1'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 5: Install Scoop (Package manager setup)
|
||||
# PHASE 4: Install Scoop (Package manager setup)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING SCOOP INSTALLATION ===' ; `
|
||||
Write-Host 'Setting UTF-8 encoding...' ; `
|
||||
@@ -150,7 +142,7 @@ RUN Write-Host '=== STARTING SCOOP INSTALLATION ===' ; `
|
||||
Write-Host '✅ Scoop installation completed!'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 6: Install Quarto via Scoop (consistent PATH with other tools)
|
||||
# PHASE 5: Install Quarto via Scoop (consistent PATH with other tools)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING QUARTO INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Quarto via Scoop (extras bucket)...' ; `
|
||||
@@ -160,18 +152,18 @@ RUN Write-Host '=== STARTING QUARTO INSTALLATION ===' ; `
|
||||
Write-Host '✅ Quarto installation completed!'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 7: Install Ghostscript (required for PDF generation)
|
||||
# PHASE 6: Install Ghostscript (required for PDF generation)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING GHOSTSCRIPT INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Ghostscript via Scoop...' ; `
|
||||
scoop install main/ghostscript ; `
|
||||
Write-Host '📦 Ghostscript installed' ; `
|
||||
Write-Host 'Verifying Ghostscript installation...' ; `
|
||||
gs --version ; `
|
||||
gswin64c --version ; `
|
||||
Write-Host '✅ Ghostscript installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 8: Install Inkscape and rsvg-convert (required for SVG processing)
|
||||
# PHASE 7: Install Inkscape and rsvg-convert (required for SVG processing)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING INKSCAPE INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Inkscape via Scoop...' ; `
|
||||
@@ -183,14 +175,22 @@ RUN Write-Host '=== STARTING INKSCAPE INSTALLATION ===' ; `
|
||||
|
||||
RUN Write-Host '=== STARTING RSVG-CONVERT INSTALLATION ===' ; `
|
||||
Write-Host 'Installing rsvg-convert via Scoop (required by Quarto for SVG-to-PDF)...' ; `
|
||||
scoop install main/rsvg-convert ; `
|
||||
scoop install rsvg-convert ; `
|
||||
if (-not (Get-Command rsvg-convert -ErrorAction SilentlyContinue)) { `
|
||||
Write-Host '⚠️ Scoop install failed or package unavailable from current buckets; trying Chocolatey...' ; `
|
||||
choco install rsvg-convert -y ; `
|
||||
} ; `
|
||||
if (-not (Get-Command rsvg-convert -ErrorAction SilentlyContinue)) { `
|
||||
Write-Host '❌ rsvg-convert install failed via both Scoop and Chocolatey' ; `
|
||||
exit 1 ; `
|
||||
} ; `
|
||||
Write-Host '📦 rsvg-convert installed' ; `
|
||||
Write-Host 'Verifying rsvg-convert installation...' ; `
|
||||
rsvg-convert --version ; `
|
||||
Write-Host '✅ rsvg-convert installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 9: Install Python (Medium complexity)
|
||||
# PHASE 8: Install Python (Medium complexity)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING PYTHON INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Python via Scoop (same as quarto-build workflow)...' ; `
|
||||
@@ -202,13 +202,11 @@ RUN Write-Host '=== STARTING PYTHON INSTALLATION ===' ; `
|
||||
Write-Host '✅ Python installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 10: Install Python packages (Medium complexity)
|
||||
# PHASE 9: Install Python packages (Medium complexity)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING PYTHON PACKAGE INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Python packages from requirements.txt (same as quarto-build workflow)...' ; `
|
||||
Write-Host 'Upgrading pip...' ; `
|
||||
python -m pip install --upgrade pip ; `
|
||||
Write-Host '📦 pip upgraded' ; `
|
||||
Write-Host 'Using bundled pip (skip upgrade to avoid WinError 3 shim lock)...' ; `
|
||||
Write-Host 'Installing packages from requirements.txt...' ; `
|
||||
Write-Host 'Requirements file contents:' ; `
|
||||
Get-Content C:/temp/requirements.txt | Write-Host ; `
|
||||
@@ -216,7 +214,7 @@ RUN Write-Host '=== STARTING PYTHON PACKAGE INSTALLATION ===' ; `
|
||||
Write-Host '✅ Python package installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 11: Install Visual C++ Redistributable (Required for Quarto DLLs)
|
||||
# PHASE 10: Install Visual C++ Redistributable (Required for Quarto DLLs)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING VISUAL C++ REDISTRIBUTABLE INSTALLATION ===' ; `
|
||||
Write-Host 'Installing Microsoft Visual C++ Redistributable...' ; `
|
||||
@@ -226,7 +224,7 @@ RUN Write-Host '=== STARTING VISUAL C++ REDISTRIBUTABLE INSTALLATION ===' ; `
|
||||
Write-Host '✅ Visual C++ Redistributable installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 12: Install R (Medium complexity)
|
||||
# PHASE 11: Install R (Medium complexity)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== STARTING R INSTALLATION ===' ; `
|
||||
Write-Host 'Installing R 4.5.2 via Scoop (main bucket)...' ; `
|
||||
@@ -237,7 +235,7 @@ RUN Write-Host '=== STARTING R INSTALLATION ===' ; `
|
||||
Write-Host '✅ R installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 13: Install R packages (Medium complexity)
|
||||
# PHASE 12: Install R packages (Medium complexity)
|
||||
# ------------------------------------------------------------
|
||||
RUN Write-Host '=== INSTALLING R PACKAGES ===' ; `
|
||||
Write-Host 'Installing R packages from install_packages.R (same as quarto-build workflow)...' ; `
|
||||
@@ -261,6 +259,14 @@ RUN Write-Host '=== INSTALLING R PACKAGES ===' ; `
|
||||
Rscript C:/temp/verify_r_packages.R ; `
|
||||
Write-Host '✅ R package installation complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 13: Install TeX Live LAST (largest/slowest install)
|
||||
# Direct install-tl approach via standalone script:
|
||||
# - Bypasses Chocolatey's ErrorActionPreference=Stop wrapper
|
||||
# - Pins mirror in profile, tries 3 mirrors in order
|
||||
# ------------------------------------------------------------
|
||||
RUN & 'C:\temp\install_texlive.ps1'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 14: Cleanup and Environment Setup
|
||||
# ------------------------------------------------------------
|
||||
@@ -283,6 +289,29 @@ RUN Write-Host '=== STARTING CLEANUP AND ENVIRONMENT SETUP ===' ; `
|
||||
Write-Host '🔧 QUARTO_LOG_LEVEL set to DEBUG' ; `
|
||||
Write-Host '✅ Cleanup and environment setup complete'
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PHASE 15: Commit PATH into Docker image metadata
|
||||
# [Environment]::SetEnvironmentVariable writes to the registry but Docker does
|
||||
# not re-read HKLM between RUN steps — each new layer inherits the *previous
|
||||
# layer's* Docker ENV, not registry changes made mid-layer. Using the Docker
|
||||
# ENV directive here guarantees these paths are committed into the image and
|
||||
# visible to every subsequent RUN step and to containers at runtime.
|
||||
# ------------------------------------------------------------
|
||||
ENV PATH="C:\\Users\\ContainerAdministrator\\scoop\\shims;\
|
||||
C:\\Users\\ContainerAdministrator\\scoop\\apps\\python\\current\\Scripts;\
|
||||
C:\\Users\\ContainerAdministrator\\scoop\\apps\\python\\current;\
|
||||
C:\\Users\\ContainerAdministrator\\scoop\\apps\\ghostscript\\current\\bin;\
|
||||
C:\\Users\\ContainerAdministrator\\scoop\\apps\\r\\current\\bin;\
|
||||
C:\\Users\\ContainerAdministrator\\scoop\\apps\\git\\current\\cmd;\
|
||||
C:\\texlive\\bin\\windows;\
|
||||
C:\\Program Files\\PowerShell\\7;\
|
||||
C:\\ProgramData\\chocolatey\\bin;\
|
||||
C:\\Windows\\system32;\
|
||||
C:\\Windows;\
|
||||
C:\\Windows\\System32\\Wbem;\
|
||||
C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;\
|
||||
C:\\Windows\\System32\\OpenSSH\\"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# FINAL CHECKS: Comprehensive verification with diagnostics
|
||||
# ------------------------------------------------------------
|
||||
@@ -294,27 +323,32 @@ RUN Write-Host '=== FINAL VERIFICATION ===' ; `
|
||||
Write-Host '📊 TOOL VERIFICATION:' ; `
|
||||
Write-Host '---------------------' ; `
|
||||
$failures = @() ; `
|
||||
$tools = @( `
|
||||
@{ Name = 'Quarto'; Cmd = 'quarto'; Args = '--version' }, `
|
||||
@{ Name = 'Python'; Cmd = 'python'; Args = '--version' }, `
|
||||
@{ Name = 'R'; Cmd = 'Rscript'; Args = '--version' }, `
|
||||
@{ Name = 'LaTeX'; Cmd = 'lualatex'; Args = '--version' }, `
|
||||
@{ Name = 'Ghostscript'; Cmd = 'gs'; Args = '--version' }, `
|
||||
@{ Name = 'Inkscape'; Cmd = 'inkscape'; Args = '--version' }, `
|
||||
@{ Name = 'rsvg-convert'; Cmd = 'rsvg-convert'; Args = '--version' } `
|
||||
) ; `
|
||||
foreach ($tool in $tools) { `
|
||||
Write-Host "Checking $($tool.Name)..." ; `
|
||||
try { `
|
||||
$out = & $tool.Cmd $tool.Args 2>&1 | Select-Object -First 1 ; `
|
||||
if ($LASTEXITCODE -ne 0) { throw ('{0} exited with {1}' -f $tool.Cmd, $LASTEXITCODE) } ; `
|
||||
Write-Host " $out" ; `
|
||||
Write-Host "✅ $($tool.Name) verified" ; `
|
||||
} catch { `
|
||||
Write-Host "❌ $($tool.Name) FAILED: $($_.Exception.Message)" ; `
|
||||
$failures += $tool.Name ; `
|
||||
function Test-Tool { `
|
||||
param([string]$Name, [string]$Cmd, [string[]]$Args, [switch]$IgnoreExitCode) `
|
||||
Write-Host "Checking $Name..." ; `
|
||||
$resolved = Get-Command $Cmd -ErrorAction SilentlyContinue ; `
|
||||
if (-not $resolved) { `
|
||||
Write-Host "❌ $Name FAILED: '$Cmd' not found in PATH" ; `
|
||||
return $false ; `
|
||||
} ; `
|
||||
Write-Host " Resolved: $($resolved.Source)" ; `
|
||||
$out = & $Cmd @Args 2>&1 | Select-Object -First 1 ; `
|
||||
$exitCode = if ($null -eq $LASTEXITCODE) { if ($?) { 0 } else { 1 } } else { [int]$LASTEXITCODE } ; `
|
||||
if (-not $IgnoreExitCode -and $exitCode -ne 0) { `
|
||||
Write-Host "❌ $Name FAILED: exited $exitCode" ; `
|
||||
return $false ; `
|
||||
} ; `
|
||||
Write-Host " $out" ; `
|
||||
Write-Host "✅ $Name verified" ; `
|
||||
return $true ; `
|
||||
} ; `
|
||||
if (-not (Test-Tool 'Quarto' 'quarto' '--version')) { $failures += 'Quarto' } ; `
|
||||
if (-not (Test-Tool 'Python' 'python' '--version')) { $failures += 'Python' } ; `
|
||||
if (-not (Test-Tool 'R' 'Rscript' '--version')) { $failures += 'R' } ; `
|
||||
if (-not (Test-Tool 'LaTeX' 'lualatex' '--version' -IgnoreExitCode)) { $failures += 'LaTeX' } ; `
|
||||
if (-not (Test-Tool 'Ghostscript' 'gswin64c' '--version')) { $failures += 'Ghostscript' } ; `
|
||||
if (-not (Test-Tool 'Inkscape' 'inkscape' '--version')) { $failures += 'Inkscape' } ; `
|
||||
if (-not (Test-Tool 'rsvg-convert' 'rsvg-convert' '--version')) { $failures += 'rsvg-convert' } ; `
|
||||
Write-Host '' ; `
|
||||
Write-Host '🎯 FINAL STATUS:' ; `
|
||||
Write-Host '----------------' ; `
|
||||
|
||||
@@ -86,7 +86,20 @@ if (-not (Test-Path $texLiveBin)) {
|
||||
}
|
||||
Write-Host "📁 TeX Live bin: $texLiveBin"
|
||||
|
||||
[Environment]::SetEnvironmentVariable('PATH', ($texLiveBin + ';' + [Environment]::GetEnvironmentVariable('PATH', 'Machine')), 'Machine')
|
||||
# Create a stable symlink so ENV PATH in Docker can use C:\texlive\bin\windows
|
||||
$stableBin = Join-Path $TexLiveRoot 'bin\windows'
|
||||
if ($texLiveBin -ne $stableBin -and -not (Test-Path $stableBin)) {
|
||||
Write-Host "🔗 Creating stable path: $stableBin -> $texLiveBin"
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $TexLiveRoot 'bin') | Out-Null
|
||||
cmd /c mklink /J "$stableBin" "$texLiveBin"
|
||||
if (Test-Path $stableBin) {
|
||||
Write-Host "✅ Stable symlink created"
|
||||
} else {
|
||||
Write-Host "⚠️ Symlink failed, using discovered path directly"
|
||||
}
|
||||
}
|
||||
|
||||
[Environment]::SetEnvironmentVariable('PATH', ($stableBin + ';' + [Environment]::GetEnvironmentVariable('PATH', 'Machine')), 'Machine')
|
||||
Write-Host '✅ PATH updated'
|
||||
|
||||
Write-Host '🔧 Pinning tlmgr repository to stable mirror...'
|
||||
|
||||
Reference in New Issue
Block a user