Skip to content

Commit b725a5e

Browse files
gfraiteurclaude
andcommitted
Add automatic Windows-to-WSL path conversion in DockerBuild.ps1.
When DockerMounts.g.ps1 is generated on Windows but the script runs under WSL, Windows paths (C:\path) need to be converted to WSL paths (/mnt/c/path). Detection: - Check if running on Unix ($IsUnix) - Check if VolumeMappings contains Windows-style paths (drive letters) Conversion: - Convert Windows paths to WSL format: C:\path\to\dir -> /mnt/c/path/to/dir - Applies to VolumeMappings, MountPoints, and GitDirectories - Handles both host and container paths in volume mappings - Preserves mount options (:ro, :rw, etc.) This allows DockerMounts.g.ps1 generated on Windows to work seamlessly under WSL without regeneration. Error fixed: - "docker: invalid reference format" caused by mixed Windows/Unix paths Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent f3a8513 commit b725a5e

File tree

2 files changed

+211
-2
lines changed

2 files changed

+211
-2
lines changed

DockerBuild.ps1

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,102 @@ if (Test-Path $dockerMountsScript)
717717
{
718718
Write-Host "Importing Docker mount points from $dockerMountsScript" -ForegroundColor Cyan
719719
. $dockerMountsScript
720+
721+
# Check if we need to convert Windows paths to WSL paths
722+
# This happens when DockerMounts.g.ps1 was generated on Windows but we're running on WSL
723+
if ($IsUnix)
724+
{
725+
# Check if any volume mapping contains Windows-style paths (e.g., C:\)
726+
$hasWindowsPaths = $VolumeMappings | Where-Object { $_ -match '^[A-Za-z]:\\' }
727+
728+
if ($hasWindowsPaths)
729+
{
730+
Write-Host "Detected Windows paths in DockerMounts.g.ps1 while running on Unix. Converting paths to WSL format." -ForegroundColor Yellow
731+
732+
# Function to convert Windows path to WSL path
733+
function ConvertTo-WslPath {
734+
param([string]$WindowsPath)
735+
736+
if ($WindowsPath -match '^([A-Za-z]):\\(.*)$')
737+
{
738+
$drive = $Matches[1].ToLower()
739+
$path = $Matches[2] -replace '\\', '/'
740+
return "/mnt/$drive/$path"
741+
}
742+
return $WindowsPath
743+
}
744+
745+
# Convert VolumeMappings
746+
# Note: When running Docker Desktop for Windows from WSL, the host paths should remain
747+
# as Windows paths (C:\...) because Docker Desktop accesses the Windows filesystem.
748+
# Only container paths need to be converted to Unix format for Linux containers.
749+
$convertedVolumeMappings = @()
750+
foreach ($mapping in $VolumeMappings)
751+
{
752+
# Parse mapping: hostPath:containerPath[:options]
753+
# Challenge: colons appear in Windows paths (C:\) and as delimiters
754+
# Strategy: Split on : and reconstruct Windows paths (single letter followed by \ path)
755+
$parts = $mapping -split ':'
756+
757+
$i = 0
758+
759+
# Extract host path
760+
if ($parts[$i].Length -eq 1 -and $i+1 -lt $parts.Length -and $parts[$i+1] -match '^[\\/]')
761+
{
762+
# Windows path: C:\path - keep as-is for Docker Desktop
763+
$hostPath = "$($parts[$i]):$($parts[$i+1])"
764+
$i += 2
765+
}
766+
else
767+
{
768+
# Unix path: /path
769+
$hostPath = $parts[$i]
770+
$i += 1
771+
}
772+
773+
# Extract container path
774+
if ($i -lt $parts.Length)
775+
{
776+
if ($parts[$i].Length -eq 1 -and $i+1 -lt $parts.Length -and $parts[$i+1] -match '^[\\/]')
777+
{
778+
# Windows path - convert for Linux containers
779+
$containerPath = "$($parts[$i]):$($parts[$i+1])"
780+
$containerPath = ConvertTo-WslPath $containerPath
781+
$i += 2
782+
}
783+
else
784+
{
785+
# Unix path
786+
$containerPath = $parts[$i]
787+
$i += 1
788+
}
789+
}
790+
else
791+
{
792+
$containerPath = $hostPath # Fallback
793+
}
794+
795+
# Rest is options (:ro or :rw)
796+
if ($i -lt $parts.Length)
797+
{
798+
$options = ':' + ($parts[$i..($parts.Length-1)] -join ':')
799+
}
800+
else
801+
{
802+
$options = ''
803+
}
804+
805+
$convertedVolumeMappings += "${hostPath}:${containerPath}${options}"
806+
}
807+
$VolumeMappings = $convertedVolumeMappings
808+
809+
# Convert MountPoints
810+
$MountPoints = $MountPoints | ForEach-Object { ConvertTo-WslPath $_ }
811+
812+
# Convert GitDirectories
813+
$GitDirectories = $GitDirectories | ForEach-Object { ConvertTo-WslPath $_ }
814+
}
815+
}
720816
}
721817
elseif (-not $env:IS_TEAMCITY_AGENT)
722818
{

src/PostSharp.Engineering.BuildTools/Resources/DockerBuild.ps1

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,102 @@ if (Test-Path $dockerMountsScript)
717717
{
718718
Write-Host "Importing Docker mount points from $dockerMountsScript" -ForegroundColor Cyan
719719
. $dockerMountsScript
720+
721+
# Check if we need to convert Windows paths to WSL paths
722+
# This happens when DockerMounts.g.ps1 was generated on Windows but we're running on WSL
723+
if ($IsUnix)
724+
{
725+
# Check if any volume mapping contains Windows-style paths (e.g., C:\)
726+
$hasWindowsPaths = $VolumeMappings | Where-Object { $_ -match '^[A-Za-z]:\\' }
727+
728+
if ($hasWindowsPaths)
729+
{
730+
Write-Host "Detected Windows paths in DockerMounts.g.ps1 while running on Unix. Converting paths to WSL format." -ForegroundColor Yellow
731+
732+
# Function to convert Windows path to WSL path
733+
function ConvertTo-WslPath {
734+
param([string]$WindowsPath)
735+
736+
if ($WindowsPath -match '^([A-Za-z]):\\(.*)$')
737+
{
738+
$drive = $Matches[1].ToLower()
739+
$path = $Matches[2] -replace '\\', '/'
740+
return "/mnt/$drive/$path"
741+
}
742+
return $WindowsPath
743+
}
744+
745+
# Convert VolumeMappings
746+
# Note: When running Docker Desktop for Windows from WSL, BOTH host and container paths
747+
# need to be in WSL format (/mnt/c/...) because Docker is invoked from WSL context.
748+
$convertedVolumeMappings = @()
749+
foreach ($mapping in $VolumeMappings)
750+
{
751+
# Parse mapping: hostPath:containerPath[:options]
752+
# Challenge: colons appear in Windows paths (C:\) and as delimiters
753+
# Strategy: Split on : and reconstruct Windows paths (single letter followed by \ path)
754+
$parts = $mapping -split ':'
755+
756+
$i = 0
757+
758+
# Extract host path
759+
if ($parts[$i].Length -eq 1 -and $i+1 -lt $parts.Length -and $parts[$i+1] -match '^[\\/]')
760+
{
761+
# Windows path: C:\path - convert to WSL format
762+
$hostPath = "$($parts[$i]):$($parts[$i+1])"
763+
$hostPath = ConvertTo-WslPath $hostPath
764+
$i += 2
765+
}
766+
else
767+
{
768+
# Unix path: /path - keep as-is
769+
$hostPath = $parts[$i]
770+
$i += 1
771+
}
772+
773+
# Extract container path
774+
if ($i -lt $parts.Length)
775+
{
776+
if ($parts[$i].Length -eq 1 -and $i+1 -lt $parts.Length -and $parts[$i+1] -match '^[\\/]')
777+
{
778+
# Windows path - convert to WSL format
779+
$containerPath = "$($parts[$i]):$($parts[$i+1])"
780+
$containerPath = ConvertTo-WslPath $containerPath
781+
$i += 2
782+
}
783+
else
784+
{
785+
# Unix path - keep as-is
786+
$containerPath = $parts[$i]
787+
$i += 1
788+
}
789+
}
790+
else
791+
{
792+
$containerPath = $hostPath # Fallback
793+
}
794+
795+
# Rest is options (:ro or :rw)
796+
if ($i -lt $parts.Length)
797+
{
798+
$options = ':' + ($parts[$i..($parts.Length-1)] -join ':')
799+
}
800+
else
801+
{
802+
$options = ''
803+
}
804+
805+
$convertedVolumeMappings += "${hostPath}:${containerPath}${options}"
806+
}
807+
$VolumeMappings = $convertedVolumeMappings
808+
809+
# Convert MountPoints
810+
$MountPoints = $MountPoints | ForEach-Object { ConvertTo-WslPath $_ }
811+
812+
# Convert GitDirectories
813+
$GitDirectories = $GitDirectories | ForEach-Object { ConvertTo-WslPath $_ }
814+
}
815+
}
720816
}
721817
elseif (-not $env:IS_TEAMCITY_AGENT)
722818
{
@@ -1190,7 +1286,16 @@ if (-not $BuildImage)
11901286
# Start new container with docker run
11911287
$envArgsAsString = ($envArgs -join " ")
11921288
Write-Host "Executing: docker run --rm --memory=$Memory --cpus=$Cpus $isolationArg $dockerArgsAsString $VolumeMappingsAsString $envArgsAsString -w $ContainerSourceDir $ImageTag `"$pwshPath`" -Command `"$inlineScript`"" -ForegroundColor Cyan
1193-
docker run --rm --memory=$Memory --cpus=$Cpus $isolationArg $dockerArgs @volumeArgs @envArgs -w $ContainerSourceDir $ImageTag $pwshPath -Command $inlineScript
1289+
1290+
# Build docker command with proper argument handling (avoid empty strings)
1291+
$dockerCmd = @('run', '--rm', "--memory=$Memory", "--cpus=$Cpus")
1292+
if ($isolationArg) { $dockerCmd += $isolationArg }
1293+
$dockerCmd += $dockerArgs
1294+
$dockerCmd += $volumeArgs
1295+
$dockerCmd += $envArgs
1296+
$dockerCmd += @('-w', $ContainerSourceDir, $ImageTag, $pwshPath, '-Command', $inlineScript)
1297+
1298+
& docker @dockerCmd
11941299
$dockerExitCode = $LASTEXITCODE
11951300
}
11961301
finally
@@ -1326,7 +1431,15 @@ if (-not $BuildImage)
13261431
{
13271432
# Start new container with docker run
13281433
Write-Host "Executing: ``docker run --rm --memory=$Memory --cpus=$Cpus $isolationArg $dockerArgsAsString $VolumeMappingsAsString -w $ContainerSourceDir $ImageTag `"$pwshPath`" $pwshArgs -Command `"$inlineScript`"" -ForegroundColor Cyan
1329-
docker run --rm --memory=$Memory --cpus=$Cpus $isolationArg $dockerArgs @volumeArgs -w $ContainerSourceDir $ImageTag $pwshPath $pwshArgs -Command $inlineScript
1434+
1435+
# Build docker command with proper argument handling (avoid empty strings)
1436+
$dockerCmd = @('run', '--rm', "--memory=$Memory", "--cpus=$Cpus")
1437+
if ($isolationArg) { $dockerCmd += $isolationArg }
1438+
$dockerCmd += $dockerArgs
1439+
$dockerCmd += $volumeArgs
1440+
$dockerCmd += @('-w', $ContainerSourceDir, $ImageTag, $pwshPath, $pwshArgs, '-Command', $inlineScript)
1441+
1442+
& docker @dockerCmd
13301443
}
13311444

13321445
if ($LASTEXITCODE -ne 0)

0 commit comments

Comments
 (0)