@@ -319,6 +319,33 @@ function Get-TimestampFile
319319 return $timestampFile
320320}
321321
322+ # Dictionary to track volume mounts with "writable wins" logic
323+ $script :VolumeMountDict = @ {}
324+
325+ function Add-VolumeMount {
326+ param (
327+ [Parameter (Mandatory = $true )]
328+ [string ]$Path ,
329+ [switch ]$Writable
330+ )
331+
332+ $normalizedPath = $Path.TrimEnd (' \' , ' /' )
333+ $normalizedKey = $normalizedPath.ToLower ()
334+ $isGitDirectory = Test-Path (Join-Path $normalizedPath " .git" )
335+
336+ if ($script :VolumeMountDict.ContainsKey ($normalizedKey )) {
337+ if ($Writable ) {
338+ $script :VolumeMountDict [$normalizedKey ].Writable = $true
339+ }
340+ } else {
341+ $script :VolumeMountDict [$normalizedKey ] = @ {
342+ HostPath = $normalizedPath
343+ Writable = [bool ]$Writable
344+ IsGitDirectory = $isGitDirectory
345+ }
346+ }
347+ }
348+
322349if ($env: RUNNING_IN_DOCKER )
323350{
324351 Write-Error " Already running in Docker."
@@ -452,18 +479,20 @@ if (-not (Test-Path $dockerContextDirectory))
452479# Container user profile (matches actual user in container)
453480$containerUserProfile = if ($IsUnix ) { " /root" } else { " C:\Users\ContainerAdministrator" }
454481
455- # Prepare volume mappings (stored as mapping strings, "-v" flags added later)
456- $VolumeMappings = @ (" ${SourceDirName} :${SourceDirName} " )
457- $MountPoints = @ ($SourceDirName )
458- $GitDirectories = @ ($SourceDirName )
482+ # Initialize arrays for special mounts (those with different host/container paths)
483+ $VolumeMappings = @ ()
484+ $MountPoints = @ ()
485+ $GitDirectories = @ ()
486+
487+ # Prepare volume mappings using the dictionary
488+ Add-VolumeMount - Path $SourceDirName - Writable
459489
460490# Define static Git system directory for mapping. This used by Teamcity as an LFS parent repo.
461491$gitSystemDir = " $BuildAgentPath \system\git"
462492
463493if (Test-Path $gitSystemDir )
464494{
465- $VolumeMappings += " ${gitSystemDir} :${gitSystemDir} :ro"
466- $MountPoints += $gitSystemDir
495+ Add-VolumeMount - Path $gitSystemDir
467496}
468497
469498# Mount the host NuGet cache in the container.
@@ -491,8 +520,7 @@ if (-not $NoNuGetCache)
491520 }
492521
493522 # Mount to the same path in the container (will be transformed by Get-ContainerPath later)
494- $VolumeMappings += " ${nugetCacheDir} :${nugetCacheDir} "
495- $MountPoints += $nugetCacheDir
523+ Add-VolumeMount - Path $nugetCacheDir - Writable
496524}
497525
498526# Mount PostSharp.Engineering data directory (for version counters)
@@ -555,9 +583,7 @@ if (Test-Path $sourceDependenciesDir)
555583 if (-not [string ]::IsNullOrEmpty($targetPath ) -and (Test-Path $targetPath ))
556584 {
557585 Write-Host " Found symbolic link '$ ( $link.Name ) ' -> '$targetPath '" - ForegroundColor Cyan
558- $VolumeMappings += " ${targetPath} :${targetPath} :ro"
559- $MountPoints += $targetPath
560- $GitDirectories += $targetPath
586+ Add-VolumeMount - Path $targetPath
561587 }
562588 else
563589 {
@@ -587,9 +613,7 @@ if ($parentDir -and (Test-Path $parentDir) -and ($parentDirName -like "PostSharp
587613 {
588614 $siblingPath = $sibling.FullName
589615 Write-Host " Mounting product family sibling: $siblingPath " - ForegroundColor Cyan
590- $VolumeMappings += " ${siblingPath} :${siblingPath} :ro"
591- $MountPoints += $siblingPath
592- $GitDirectories += $siblingPath
616+ Add-VolumeMount - Path $siblingPath
593617 }
594618}
595619
@@ -605,9 +629,7 @@ if ($grandparentDir -and (Test-Path $grandparentDir))
605629 {
606630 $engDirPath = $engDir.FullName
607631 Write-Host " Mounting engineering repo: $engDirPath " - ForegroundColor Cyan
608- $VolumeMappings += " ${engDirPath} :${engDirPath} :ro"
609- $MountPoints += $engDirPath
610- $GitDirectories += $engDirPath
632+ Add-VolumeMount - Path $engDirPath
611633 }
612634}
613635
@@ -628,8 +650,6 @@ if ($Mount -and $Mount.Count -gt 0)
628650 # Trim trailing slashes
629651 $pattern = $pattern.TrimEnd (' \' , ' /' )
630652
631- $mountOption = if ($isWritable ) { " " } else { " :ro" }
632-
633653 # Check if pattern contains glob characters
634654 if ($pattern -match ' \*' )
635655 {
@@ -694,8 +714,7 @@ if ($Mount -and $Mount.Count -gt 0)
694714 $dirPath = $dir.FullName
695715 $rwStatus = if ($isWritable ) { " writable" } else { " readonly" }
696716 Write-Host " Mounting from -Mount pattern '$pattern ': $dirPath ($rwStatus )" - ForegroundColor Cyan
697- $VolumeMappings += " ${dirPath} :${dirPath}${mountOption} "
698- $MountPoints += $dirPath
717+ Add-VolumeMount - Path $dirPath - Writable:$isWritable
699718 }
700719 }
701720 }
@@ -711,8 +730,7 @@ if ($Mount -and $Mount.Count -gt 0)
711730 {
712731 $rwStatus = if ($isWritable ) { " writable" } else { " readonly" }
713732 Write-Host " Mounting from -Mount: $pattern ($rwStatus )" - ForegroundColor Cyan
714- $VolumeMappings += " ${pattern} :${pattern}${mountOption} "
715- $MountPoints += $pattern
733+ Add-VolumeMount - Path $pattern - Writable:$isWritable
716734 }
717735 else
718736 {
@@ -722,6 +740,18 @@ if ($Mount -and $Mount.Count -gt 0)
722740 }
723741}
724742
743+ # Convert dictionary entries to arrays (with "writable wins" deduplication already applied)
744+ # Sort by key for deterministic ordering to optimize Docker image layer reuse
745+ foreach ($key in $script :VolumeMountDict.Keys | Sort-Object ) {
746+ $entry = $script :VolumeMountDict [$key ]
747+ $mountOption = if ($entry.Writable ) { " " } else { " :ro" }
748+ $VolumeMappings += " $ ( $entry.HostPath ) :$ ( $entry.HostPath ) $mountOption "
749+ $MountPoints += $entry.HostPath
750+ if ($entry.IsGitDirectory ) {
751+ $GitDirectories += $entry.HostPath
752+ }
753+ }
754+
725755# Execute auto-generated DockerMounts.g.ps1 script to add more directory mounts.
726756$dockerMountsScript = Join-Path $EngPath ' DockerMounts.g.ps1'
727757if (Test-Path $dockerMountsScript )
0 commit comments