552eb628b7
Sprint 4 — qualità codice: - scripts/_Common.psm1: New-CISessionOption, Resolve-VmrunPath, Invoke-Vmrun - PSScriptAnalyzerSettings.psd1: project-wide PSSA rules (security + formatting) - Remove-BuildVM.ps1: SupportsShouldProcess (-WhatIf support) - Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1: use New-CISessionOption from module - New-BuildVM.ps1: use Resolve-VmrunPath + Invoke-Vmrun from module Sprint 5 — affidabilità: - Backup-CITemplate.ps1: stop runner, timestamped VMDK backup, prune old, restart - Watch-RunnerHealth.ps1: auto-restart act_runner, cooldown 3/h, EventLog + webhook - Register-CIScheduledTasks.ps1: add CI-RunnerHealth task (every 15 min) Sprint 6 — perf baseline: - Set-TemplateSharedFolders.ps1: idempotent VMX editor for NuGet/pip shared folders - Invoke-RemoteBuild.ps1: -UseSharedCache injects NUGET_PACKAGES + PIP_CACHE_DIR in guest - Measure-CIBenchmark.ps1: times clone/start/IP/WinRM/destroy, appends to benchmark.jsonl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
220 lines
9.1 KiB
PowerShell
220 lines
9.1 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Copies pre-cloned source from host into a build VM and runs dotnet build.
|
|
|
|
.DESCRIPTION
|
|
The caller (Invoke-CIJob.ps1) is responsible for cloning the repository
|
|
on the HOST before calling this script. This script then:
|
|
1. Compresses the local source tree on the host, copies the archive into
|
|
the VM via WinRM (single file = much faster than recursive Copy-Item),
|
|
then expands it inside the VM.
|
|
2. Runs dotnet restore and dotnet build inside the VM
|
|
3. Packages the output as C:\CI\output\artifacts.zip inside the VM
|
|
|
|
The build VM does NOT need internet access or git. All network I/O happens
|
|
on the host before this script is called.
|
|
|
|
.PARAMETER IPAddress
|
|
IP of the build VM (e.g. 192.168.11.101).
|
|
|
|
.PARAMETER Credential
|
|
PSCredential for the build user inside the VM.
|
|
Retrieve from Windows Credential Manager in production:
|
|
$cred = Get-StoredCredential -Target 'BuildVMGuest'
|
|
|
|
.PARAMETER HostSourceDir
|
|
Full path on the HOST to the already-cloned repository directory.
|
|
This directory is copied into the VM as the build working directory.
|
|
Example: C:\Users\user\AppData\Local\Temp\ci-src-run-42
|
|
|
|
.PARAMETER Configuration
|
|
MSBuild/dotnet build configuration. Default: Release
|
|
|
|
.PARAMETER GuestWorkDir
|
|
Working directory inside the VM where source will be placed.
|
|
Default: C:\CI\build
|
|
|
|
.PARAMETER GuestArtifactZip
|
|
Full path inside the VM where the artifact archive will be written.
|
|
Default: C:\CI\output\artifacts.zip
|
|
|
|
.EXAMPLE
|
|
$cred = Get-Credential
|
|
.\Invoke-RemoteBuild.ps1 `
|
|
-IPAddress "192.168.11.101" `
|
|
-Credential $cred `
|
|
-HostSourceDir "C:\Temp\ci-src-run-42"
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[ValidatePattern('^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$')]
|
|
[string] $IPAddress,
|
|
|
|
[Parameter(Mandatory)]
|
|
[System.Management.Automation.PSCredential] $Credential,
|
|
|
|
[Parameter(Mandatory)]
|
|
[ValidateScript({ Test-Path $_ -PathType Container })]
|
|
[string] $HostSourceDir,
|
|
|
|
[string] $Configuration = 'Release',
|
|
|
|
# Custom build command to run inside the VM (working dir = GuestWorkDir).
|
|
# When empty, defaults to: dotnet restore + dotnet build --configuration $Configuration
|
|
# Example: 'python build_plugin.py --final'
|
|
[string] $BuildCommand = '',
|
|
|
|
# Glob pattern (relative to GuestWorkDir) of files to include in the artifact zip.
|
|
# Used only when BuildCommand is set. Default: collect everything under 'dist'.
|
|
# Example: 'dist\**' or 'Contrib\nsInnoUnp\Build\**'
|
|
[string] $GuestArtifactSource = 'dist',
|
|
|
|
[string] $GuestWorkDir = 'C:\CI\build',
|
|
|
|
[string] $GuestArtifactZip = 'C:\CI\output\artifacts.zip',
|
|
|
|
# Redirect dotnet NuGet package store and pip download cache to VMware shared
|
|
# folders on the host (F:\CI\Cache\NuGet and F:\CI\Cache\pip).
|
|
# Requires the template VMX to have been configured by Set-TemplateSharedFolders.ps1
|
|
# and VMware Tools HGFS driver present in the guest.
|
|
[switch] $UseSharedCache
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
Import-Module (Join-Path $PSScriptRoot '_Common.psm1') -Force
|
|
|
|
$sessionOptions = New-CISessionOption
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Connecting to VM at $IPAddress..."
|
|
|
|
$session = New-PSSession `
|
|
-ComputerName $IPAddress `
|
|
-Credential $Credential `
|
|
-SessionOption $sessionOptions `
|
|
-UseSSL `
|
|
-Port 5986 `
|
|
-Authentication Basic `
|
|
-ErrorAction Stop
|
|
|
|
try {
|
|
# ── Ensure working directories exist on guest ────────────────────────
|
|
Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $outputDir)
|
|
# Ensure artifact output parent exists
|
|
$outParent = Split-Path $outputDir -Parent
|
|
if (-not (Test-Path $outParent)) {
|
|
New-Item -ItemType Directory -Path $outParent -Force | Out-Null
|
|
}
|
|
# Clean and recreate build dir (ephemeral VM, but guard against partial prior run)
|
|
if (Test-Path $workDir) { Remove-Item $workDir -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
|
|
} -ArgumentList $GuestWorkDir, $GuestArtifactZip
|
|
|
|
# ── Copy source tree: compress on host, transfer zip, expand in guest ──
|
|
# Single-file transfer over WinRM is far faster than recursive Copy-Item.
|
|
$hostZip = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.zip'
|
|
try {
|
|
Write-Host "[Invoke-RemoteBuild] Compressing source on host..."
|
|
Compress-Archive -Path "$HostSourceDir\*" -DestinationPath $hostZip -CompressionLevel Fastest -Force
|
|
$zipSize = [math]::Round((Get-Item $hostZip).Length / 1MB, 1)
|
|
Write-Host "[Invoke-RemoteBuild] Transferring archive to guest ($zipSize MB)..."
|
|
$guestZip = 'C:\CI\src-transfer.zip'
|
|
Copy-Item -Path $hostZip -Destination $guestZip -ToSession $session -Force
|
|
Write-Host "[Invoke-RemoteBuild] Expanding archive in guest..."
|
|
Invoke-Command -Session $session -ScriptBlock {
|
|
param($zip, $dest)
|
|
Expand-Archive -Path $zip -DestinationPath $dest -Force
|
|
Remove-Item $zip -Force
|
|
} -ArgumentList $guestZip, $GuestWorkDir
|
|
Write-Host "[Invoke-RemoteBuild] Source transfer complete."
|
|
}
|
|
finally {
|
|
Remove-Item $hostZip -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# ── Build ──────────────────────────────────────────────────────────
|
|
if ($BuildCommand -ne '') {
|
|
# ── Custom build command (e.g. python, msbuild, cmake…) ──────────
|
|
Write-Host "[Invoke-RemoteBuild] Running build command: $BuildCommand"
|
|
$buildExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $cmd, $artifactZip, $artifactSrc, $useCache)
|
|
# Unbuffered Python output — lines appear in real-time instead of being held in buffer
|
|
$env:PYTHONUNBUFFERED = '1'
|
|
if ($useCache) {
|
|
$env:PIP_CACHE_DIR = '\\vmware-host\Shared Folders\pip-cache'
|
|
}
|
|
Set-Location $workDir
|
|
# Stream each output line via Write-Host — WinRM forwards Write-Host in real-time
|
|
& cmd /c "$cmd 2>&1" | ForEach-Object { Write-Host $_ }
|
|
$exit = $LASTEXITCODE
|
|
|
|
if ($exit -eq 0) {
|
|
$archiveDir = Split-Path $artifactZip -Parent
|
|
if (-not (Test-Path $archiveDir)) {
|
|
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
|
}
|
|
$srcPath = Join-Path $workDir $artifactSrc
|
|
Compress-Archive -Path $srcPath -DestinationPath $artifactZip -Force
|
|
}
|
|
|
|
return $exit
|
|
} -ArgumentList $GuestWorkDir, $BuildCommand, $GuestArtifactZip, $GuestArtifactSource, $UseSharedCache.IsPresent
|
|
|
|
if ($buildExit -ne 0) {
|
|
throw "Build command failed (exit $buildExit)"
|
|
}
|
|
}
|
|
else {
|
|
# ── Default: dotnet restore + dotnet build ────────────────────────
|
|
Write-Host "[Invoke-RemoteBuild] Running dotnet restore..."
|
|
$restoreExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $useCache)
|
|
if ($useCache) {
|
|
$env:NUGET_PACKAGES = '\\vmware-host\Shared Folders\nuget-cache'
|
|
}
|
|
Set-Location $workDir
|
|
dotnet restore 2>&1 | ForEach-Object { Write-Host $_ }
|
|
return $LASTEXITCODE
|
|
} -ArgumentList $GuestWorkDir, $UseSharedCache.IsPresent
|
|
|
|
if ($restoreExit -ne 0) {
|
|
throw "dotnet restore failed (exit $restoreExit)"
|
|
}
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Running dotnet build (configuration: $Configuration)..."
|
|
$buildExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $config, $artifactZip, $useCache)
|
|
if ($useCache) {
|
|
$env:NUGET_PACKAGES = '\\vmware-host\Shared Folders\nuget-cache'
|
|
}
|
|
Set-Location $workDir
|
|
$outputDir = Join-Path $workDir 'bin\CIOutput'
|
|
dotnet build --configuration $config --output $outputDir 2>&1 | ForEach-Object { Write-Host $_ }
|
|
$exit = $LASTEXITCODE
|
|
|
|
if ($exit -eq 0) {
|
|
$archiveDir = Split-Path $artifactZip -Parent
|
|
if (-not (Test-Path $archiveDir)) {
|
|
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
|
}
|
|
Compress-Archive -Path "$outputDir\*" -DestinationPath $artifactZip -Force
|
|
}
|
|
|
|
return $exit
|
|
} -ArgumentList $GuestWorkDir, $Configuration, $GuestArtifactZip, $UseSharedCache.IsPresent
|
|
|
|
if ($buildExit -ne 0) {
|
|
throw "dotnet build failed (exit $buildExit)"
|
|
}
|
|
}
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Build succeeded. Artifact at $GuestArtifactZip"
|
|
}
|
|
finally {
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
}
|