Skip to content

Commit 1eb4018

Browse files
committed
Enhance build scripts: add SnapshotRepo switch to Build.ps1 for temporary repo builds and improve DockerBuild.ps1 with dynamic CPU allocation and memory settings.
1 parent c94bf3c commit 1eb4018

File tree

5 files changed

+308
-25
lines changed

5 files changed

+308
-25
lines changed

Build.ps1

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ param(
88
[switch]$StartVsmon, # Enable the remote debugger.
99
[switch]$NoCache, # Bypass the build cache for `eng`, and force a rebuild.
1010
[switch]$Snapshot, # Copy built output to temp directory to avoid file locking during execution.
11+
[switch]$SnapshotRepo, # Copy the repo to a temp directory and execute the build from there.
1112
[Parameter(ValueFromRemainingArguments)]
1213
[string[]]$BuildArgs # Arguments passed to `Build.ps1` within the container.
1314
)
@@ -60,9 +61,28 @@ if (-not $Interactive -or $BuildArgs)
6061
Set-Location $engSrcPath
6162

6263
$snapshotDir = $null
64+
$snapshotRepoDir = $null
6365

6466
try
6567
{
68+
# SnapshotRepo mode: copy the repo to a temp directory before building
69+
if ($SnapshotRepo)
70+
{
71+
$snapshotRepoDir = Join-Path $env:TEMP "eng-snapshot-repo-$([Guid]::NewGuid().ToString('N').Substring(0,8))"
72+
Write-Host "Creating snapshot of repository at $snapshotRepoDir..." -ForegroundColor Cyan
73+
$snapshotStopwatch = [Diagnostics.Stopwatch]::StartNew()
74+
& robocopy $PSScriptRoot $snapshotRepoDir /E /XD bin obj artifacts /NJH /NJS /NP | Out-Null
75+
if ($LASTEXITCODE -ge 8)
76+
{
77+
throw "robocopy failed with exit code $LASTEXITCODE"
78+
}
79+
$snapshotStopwatch.Stop()
80+
Write-Host "Snapshot created in $($snapshotStopwatch.Elapsed.TotalSeconds.ToString('F1'))s." -ForegroundColor Cyan
81+
# Redirect paths to the snapshot
82+
$engSrcPath = Join-Path $snapshotRepoDir $EngPath "src"
83+
Set-Location $engSrcPath
84+
}
85+
6686
# Build caching: check if we need to rebuild
6787
$projectPath = Join-Path $engSrcPath "Build$ProductName.csproj"
6888

@@ -165,7 +185,7 @@ if (-not $Interactive -or $BuildArgs)
165185
}
166186

167187
# Set the repository directory via environment variable (needed when running from snapshot)
168-
$env:ENG_REPO_DIRECTORY = $PSScriptRoot
188+
$env:ENG_REPO_DIRECTORY = if ($snapshotRepoDir) { $snapshotRepoDir } else { $PSScriptRoot }
169189
& dotnet exec $execDll $BuildArgs
170190

171191
if ($StartVsmon)
@@ -186,6 +206,23 @@ if (-not $Interactive -or $BuildArgs)
186206
Remove-Item -Path $snapshotDir -Recurse -Force -ErrorAction SilentlyContinue
187207
}
188208

209+
# Copy artifacts back from repo snapshot
210+
if ($snapshotRepoDir -and (Test-Path $snapshotRepoDir))
211+
{
212+
$snapshotArtifacts = Join-Path $snapshotRepoDir "artifacts"
213+
$repoArtifacts = Join-Path $PSScriptRoot "artifacts"
214+
215+
if (Test-Path $snapshotArtifacts)
216+
{
217+
Write-Host "Copying artifacts from snapshot back to repository..." -ForegroundColor Cyan
218+
if (Test-Path $repoArtifacts)
219+
{
220+
Remove-Item -Path $repoArtifacts -Recurse -Force
221+
}
222+
Copy-Item -Path $snapshotArtifacts -Destination $repoArtifacts -Recurse -Force
223+
}
224+
}
225+
189226
# Reset environment variable
190227
$env:ENG_REPO_DIRECTORY = ""
191228
}

DockerBuild.ps1

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@
7878
Memory and CPU limits only apply to hyperv isolation.
7979
8080
.PARAMETER Memory
81-
Docker memory limit (e.g., "8g"). Only used with hyperv isolation. Defaults to 24g.
81+
Docker memory limit (e.g., "8g"). Only used with hyperv isolation.
82+
Defaults to $env:BuildAgentMemory (an integer in GB) if set, otherwise 24g.
8283
8384
.PARAMETER Cpus
84-
Docker CPU limit. Defaults to the host processor count. Only used with hyperv isolation.
85+
Docker CPU limit. Use a positive integer for a static limit, or "dynamic" for
86+
automatic allocation that rebalances CPUs across all managed containers.
87+
Only used with hyperv isolation (static) or any isolation (dynamic).
88+
Defaults to $env:BuildAgentCpus if set, otherwise the host processor count.
8589
8690
.PARAMETER Mount
8791
Additional directories to mount from the host (readonly by default, append :w for writable).
@@ -143,8 +147,8 @@ param(
143147
[string]$RegistryImage, # Use a pre-built image from a registry, skipping Dockerfile build entirely.
144148
[switch]$NoInit, # Do not generate or call Init.g.ps1 (skips git config, safe.directory, etc).
145149
[string]$Isolation = 'hyperv', # Docker isolation mode (process or hyperv). Memory/CPU limits only apply to hyperv.
146-
[string]$Memory = '24g', # Docker memory limit (e.g., "8g"). Only used with hyperv isolation.
147-
[int]$Cpus = [Environment]::ProcessorCount, # Docker CPU limit. Only used with hyperv isolation.
150+
[string]$Memory = $(if ($env:BuildAgentMemory) { "${env:BuildAgentMemory}g" } else { '24g' }), # Docker memory limit (e.g., "8g"). Only used with hyperv isolation. Defaults to $env:BuildAgentMemory (in GB) or 24g.
151+
[string]$Cpus = $(if ($env:BuildAgentCpus) { $env:BuildAgentCpus } else { [Environment]::ProcessorCount }), # Docker CPU limit. Use a positive integer or "dynamic". Defaults to $env:BuildAgentCpus or host processor count.
148152
[string[]]$Mount, # Additional directories to mount from host (readonly by default, append :w for writable). Supports * and ** glob patterns.
149153
[string[]]$Env, # Additional environment variables to pass from host to container.
150154
[string[]]$Ports, # Port mappings from host to container (e.g., "8080:80", "3000").
@@ -164,6 +168,7 @@ if ($PSVersionTable.PSVersion -lt [Version]'7.5')
164168
# These settings are replaced by the generate-scripts command.
165169
$EngPath = 'eng'
166170
$EnvironmentVariables = 'AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AZ_IDENTITY_USERNAME,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_DEVOPS_TOKEN,AZURE_DEVOPS_USER,AZURE_TENANT_ID,CLAUDE_CODE_OAUTH_TOKEN,DOC_API_KEY,DOWNLOADS_API_KEY,ENG_USERNAME,GIT_USER_EMAIL,GIT_USER_NAME,GITHUB_AUTHOR_EMAIL,GITHUB_REVIEWER_TOKEN,GITHUB_TOKEN,IS_POSTSHARP_OWNED,IS_TEAMCITY_AGENT,MetalamaLicense,NUGET_ORG_API_KEY,PostSharpLicense,SIGNSERVER_SECRET,TEAMCITY_CLOUD_TOKEN,TYPESENSE_API_KEY,VS_MARKETPLACE_ACCESS_TOKEN,VSS_NUGET_EXTERNAL_FEED_ENDPOINTS'
171+
$OvercommitRatio = 1.0
167172
####
168173

169174
$ErrorActionPreference = "Stop"
@@ -238,11 +243,92 @@ try
238243
exit 1
239244
}
240245

246+
# Validate and parse -Cpus parameter
247+
$isDynamicCpus = $false
248+
if ($Cpus -eq 'dynamic')
249+
{
250+
$isDynamicCpus = $true
251+
$TotalCpus = if ($env:BuildAgentCpus) { [int]$env:BuildAgentCpus } else { [Environment]::ProcessorCount }
252+
Write-Host "Dynamic CPU allocation enabled. Total CPUs: $TotalCpus, Overcommit ratio: $OvercommitRatio" -ForegroundColor Cyan
253+
}
254+
else
255+
{
256+
$cpuInt = 0
257+
if (-not [int]::TryParse($Cpus, [ref]$cpuInt) -or $cpuInt -le 0)
258+
{
259+
Write-Error "-Cpus must be a positive integer or 'dynamic'. Got: '$Cpus'"
260+
exit 1
261+
}
262+
$Cpus = $cpuInt
263+
}
264+
241265
if ($env:IS_TEAMCITY_AGENT)
242266
{
243267
Write-Host "Running on TeamCity agent at '$BuildAgentPath'" -ForegroundColor Cyan
244268
}
245269

270+
# Dynamic CPU allocation helpers
271+
$DynamicCpuLabel = 'managed-by=DockerBuild'
272+
273+
function Get-DynamicCpuAllocation
274+
{
275+
param(
276+
[int]$AdditionalContainers = 0
277+
)
278+
279+
$budget = $TotalCpus * (1.0 + $OvercommitRatio)
280+
281+
# Count running containers with the dynamic CPU label
282+
$containerIds = @(docker ps -q --filter "label=$DynamicCpuLabel" 2>$null)
283+
# Filter out empty strings from docker output
284+
$containerIds = @($containerIds | Where-Object { $_ -and $_.Trim() -ne '' })
285+
$runningCount = $containerIds.Count
286+
287+
$totalContainers = $runningCount + $AdditionalContainers
288+
if ($totalContainers -le 0) { $totalContainers = 1 }
289+
290+
$allocation = [Math]::Min($TotalCpus, [Math]::Floor($budget / $totalContainers))
291+
if ($allocation -lt 1) { $allocation = 1 }
292+
293+
return @{
294+
Allocation = [int]$allocation
295+
ContainerIds = $containerIds
296+
}
297+
}
298+
299+
function Invoke-DynamicCpuRebalance
300+
{
301+
param(
302+
[int]$AdditionalContainers = 0
303+
)
304+
305+
$result = Get-DynamicCpuAllocation -AdditionalContainers $AdditionalContainers
306+
$allocation = $result.Allocation
307+
$containerIds = $result.ContainerIds
308+
309+
if ($containerIds.Count -gt 0)
310+
{
311+
Write-Host "Rebalancing $( $containerIds.Count ) managed container(s) to $allocation CPUs each" -ForegroundColor Cyan
312+
foreach ($cid in $containerIds)
313+
{
314+
try
315+
{
316+
docker update --cpus=$allocation $cid 2>$null | Out-Null
317+
}
318+
catch
319+
{
320+
Write-Warning "Failed to rebalance container $cid`: $_"
321+
}
322+
}
323+
}
324+
else
325+
{
326+
Write-Host "Dynamic CPU allocation: $allocation CPUs (no other managed containers)" -ForegroundColor Cyan
327+
}
328+
329+
return $allocation
330+
}
331+
246332
# Function to collect environment variables for container
247333
function New-EnvHashtable
248334
{
@@ -1776,13 +1862,22 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \
17761862
# Build docker command with proper argument handling (avoid empty strings)
17771863
$dockerCmd = @('run', '--rm')
17781864

1779-
# Only add --memory and --cpus when NOT using process isolation
1780-
if ($Isolation -ne 'process')
1865+
# Memory limit: only add when NOT using process isolation
1866+
if ($Isolation -ne 'process' -and $Memory)
1867+
{
1868+
$dockerCmd += "--memory=$Memory"
1869+
}
1870+
1871+
# CPU limit: dynamic or static
1872+
if ($isDynamicCpus)
1873+
{
1874+
$dynamicAllocation = Invoke-DynamicCpuRebalance -AdditionalContainers 1
1875+
$dockerCmd += "--cpus=$dynamicAllocation"
1876+
$dockerCmd += @('-e', "DOTNET_PROCESSOR_COUNT=$dynamicAllocation")
1877+
$dockerCmd += @('--label', "$DynamicCpuLabel")
1878+
}
1879+
elseif ($Isolation -ne 'process')
17811880
{
1782-
if ($Memory)
1783-
{
1784-
$dockerCmd += "--memory=$Memory"
1785-
}
17861881
$dockerCmd += "--cpus=$Cpus"
17871882
}
17881883

@@ -1823,6 +1918,13 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \
18231918
}
18241919
$dockerExitCode = $LASTEXITCODE
18251920

1921+
# Post-exit rebalance: when our container exits (--rm removes it),
1922+
# redistribute CPUs to remaining managed containers
1923+
if ($isDynamicCpus -and -not $existingContainerId)
1924+
{
1925+
Invoke-DynamicCpuRebalance -AdditionalContainers 0 | Out-Null
1926+
}
1927+
18261928
# Check exit code
18271929
if ($dockerExitCode -ne 0)
18281930
{
@@ -1882,6 +1984,12 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \
18821984
}
18831985
finally
18841986
{
1987+
# Safety-net rebalance on Ctrl+C or unexpected exit
1988+
if ($isDynamicCpus)
1989+
{
1990+
try { Invoke-DynamicCpuRebalance -AdditionalContainers 0 | Out-Null } catch { }
1991+
}
1992+
18851993
# Restore original location
18861994
Pop-Location
18871995
}

src/PostSharp.Engineering.BuildTools/BaseCommand.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ public sealed override int Execute( CommandContext context, T settings )
6060

6161
MSBuildHelper.InitializeLocator();
6262

63-
if ( buildContext.IsRunningUnderContainer )
64-
{
65-
buildContext.Console.WriteMessage( "Docker detected." );
66-
}
67-
6863
// Validate custom properties.
6964
if ( settings.ListProperties )
7065
{

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ param(
88
[switch]$StartVsmon, # Enable the remote debugger.
99
[switch]$NoCache, # Bypass the build cache for `eng`, and force a rebuild.
1010
[switch]$Snapshot, # Copy built output to temp directory to avoid file locking during execution.
11+
[switch]$SnapshotRepo, # Copy the repo to a temp directory and execute the build from there.
1112
[Parameter(ValueFromRemainingArguments)]
1213
[string[]]$BuildArgs # Arguments passed to `Build.ps1` within the container.
1314
)
@@ -60,9 +61,28 @@ if (-not $Interactive -or $BuildArgs)
6061
Set-Location $engSrcPath
6162

6263
$snapshotDir = $null
64+
$snapshotRepoDir = $null
6365

6466
try
6567
{
68+
# SnapshotRepo mode: copy the repo to a temp directory before building
69+
if ($SnapshotRepo)
70+
{
71+
$snapshotRepoDir = Join-Path $env:TEMP "eng-snapshot-repo-$([Guid]::NewGuid().ToString('N').Substring(0,8))"
72+
Write-Host "Creating snapshot of repository at $snapshotRepoDir..." -ForegroundColor Cyan
73+
$snapshotStopwatch = [Diagnostics.Stopwatch]::StartNew()
74+
& robocopy $PSScriptRoot $snapshotRepoDir /E /XD bin obj artifacts /NJH /NJS /NP | Out-Null
75+
if ($LASTEXITCODE -ge 8)
76+
{
77+
throw "robocopy failed with exit code $LASTEXITCODE"
78+
}
79+
$snapshotStopwatch.Stop()
80+
Write-Host "Snapshot created in $($snapshotStopwatch.Elapsed.TotalSeconds.ToString('F1'))s." -ForegroundColor Cyan
81+
# Redirect paths to the snapshot
82+
$engSrcPath = Join-Path $snapshotRepoDir $EngPath "src"
83+
Set-Location $engSrcPath
84+
}
85+
6686
# Build caching: check if we need to rebuild
6787
$projectPath = Join-Path $engSrcPath "Build$ProductName.csproj"
6888

@@ -165,7 +185,7 @@ if (-not $Interactive -or $BuildArgs)
165185
}
166186

167187
# Set the repository directory via environment variable (needed when running from snapshot)
168-
$env:ENG_REPO_DIRECTORY = $PSScriptRoot
188+
$env:ENG_REPO_DIRECTORY = if ($snapshotRepoDir) { $snapshotRepoDir } else { $PSScriptRoot }
169189
& dotnet exec $execDll $BuildArgs
170190

171191
if ($StartVsmon)
@@ -186,6 +206,23 @@ if (-not $Interactive -or $BuildArgs)
186206
Remove-Item -Path $snapshotDir -Recurse -Force -ErrorAction SilentlyContinue
187207
}
188208

209+
# Copy artifacts back from repo snapshot
210+
if ($snapshotRepoDir -and (Test-Path $snapshotRepoDir))
211+
{
212+
$snapshotArtifacts = Join-Path $snapshotRepoDir "artifacts"
213+
$repoArtifacts = Join-Path $PSScriptRoot "artifacts"
214+
215+
if (Test-Path $snapshotArtifacts)
216+
{
217+
Write-Host "Copying artifacts from snapshot back to repository..." -ForegroundColor Cyan
218+
if (Test-Path $repoArtifacts)
219+
{
220+
Remove-Item -Path $repoArtifacts -Recurse -Force
221+
}
222+
Copy-Item -Path $snapshotArtifacts -Destination $repoArtifacts -Recurse -Force
223+
}
224+
}
225+
189226
# Reset environment variable
190227
$env:ENG_REPO_DIRECTORY = ""
191228
}

0 commit comments

Comments
 (0)