From 81250cc5a98ea4b28db65d7574c1583b6b5065f2 Mon Sep 17 00:00:00 2001 From: Mario Loriedo Date: Fri, 5 Jul 2024 16:14:46 +0200 Subject: [PATCH] Visual Studio BuildTools as a MinGW alternative Building the MSI hook on Windows (`contrib/win-installer/podman-msihooks/check.c`) currently requires MinGW. This commit updates the build script so that, when MinGW is absent but the C compiler included in Visual Studio BuildTools is installed, the latter is used to build the MSI hook. Other than that, `winmake.ps1` has a new `installertest` target to run the Windows installer tests that are currently verified by Cirrus CI. Signed-off-by: Mario Loriedo --- build_windows.md | 28 +++++++ contrib/cirrus/win-installer-main.ps1 | 56 +++----------- contrib/win-installer/build-hooks.bat | 6 -- contrib/win-installer/build-hooks.ps1 | 67 +++++++++++++++++ contrib/win-installer/build.ps1 | 3 +- contrib/win-installer/test-installer.ps1 | 93 ++++++++++++++++++++++++ winmake.ps1 | 48 ++++++++++++ 7 files changed, 248 insertions(+), 53 deletions(-) delete mode 100644 contrib/win-installer/build-hooks.bat create mode 100644 contrib/win-installer/build-hooks.ps1 create mode 100644 contrib/win-installer/test-installer.ps1 diff --git a/build_windows.md b/build_windows.md index e523e62ffc..98fb9d40f9 100644 --- a/build_windows.md +++ b/build_windows.md @@ -11,6 +11,7 @@ Windows. - [Git and go](#git-and-go) - [Pandoc](#pandoc) - [.NET SDK](#net-sdk) + - [Visual Studio Build Tools](#visual-studio-build-tools) - [Virtualization Provider](#virtualization-provider) - [WSL](#wsl) - [Hyper-V](#hyper-v) @@ -87,6 +88,30 @@ used too and can be installed using `dotnet install`: dotnet tool install --global wix ``` +### Visual Studio Build Tools + +The installer includes a C program that checks the installation of the +pre-required virtualization providers (WSL or Hyper-V). Building this program +requires the +[Microsoft C/C++ compiler](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170) and the +[PowerShell Moduel VSSetup](https://github.com/microsoft/vssetup.powershell): + +1. Download the Build Tools for Visual Studio 2022 installer +```pwsh +Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile "$env:TEMP\vs_BuildTools.exe" +``` +2. Run the installer with the parameter to include the optional C/C++ Tools +```pwsh +& "$env:TEMP\vs_BuildTools.exe" --passive --wait ` + --add Microsoft.VisualStudio.Workload.VCTools ` + --includeRecommended ` + --remove Microsoft.VisualStudio.Component.VC.CMake.Project +``` +3. Install the PowerShell Module VSSetup +```pwsh +Install-Module VSSetup +``` + ### Virtualization Provider Running Podman on Windows requires a virtualization provider. The supported @@ -340,6 +365,9 @@ otherwise): contrib\win-installer\podman-5.1.0-dev-setup.exe /install /log podman-setup.log /quiet MachineProvider=wsl WSLCheckbox=0 HyperVCheckbox=0 ``` +:information_source: The `winmake.ps1` target `installertest` automatically +tests installing and uninstalling Podman. + ### Build and test the standalone `podman.msi` file Building and testing the standalone `podman.msi` package during development may diff --git a/contrib/cirrus/win-installer-main.ps1 b/contrib/cirrus/win-installer-main.ps1 index 54b55e5521..3b79385320 100644 --- a/contrib/cirrus/win-installer-main.ps1 +++ b/contrib/cirrus/win-installer-main.ps1 @@ -12,55 +12,21 @@ if ($Env:CI -eq "true") { $ENV:CONTAINERS_MACHINE_PROVIDER = "wsl" } -$ConfFilePath = "$env:ProgramData\containers\containers.conf.d\99-podman-machine-provider.conf" -$WindowsPathsToTest = @("C:\Program Files\RedHat\Podman\podman.exe", - "C:\Program Files\RedHat\Podman\win-sshproxy.exe", - "$ConfFilePath", - "HKLM:\SOFTWARE\Red Hat\Podman") - Push-Location $WIN_INST_FOLDER # Build Installer # Note: consumes podman-remote-release-windows_amd64.zip from repo.tbz2 Run-Command ".\build.ps1 $Env:WIN_INST_VER dev `"$RELEASE_DIR`"" -# Run the installer silently and WSL/HyperV install options disabled (prevent reboots) -# We need AllowOldWin=1 for server 2019 (cirrus image), can be dropped after server 2022 -$ret = Start-Process -Wait -PassThru ".\podman-${ENV:WIN_INST_VER}-dev-setup.exe" -ArgumentList "/install /quiet MachineProvider=$ENV:CONTAINERS_MACHINE_PROVIDER WSLCheckbox=0 HyperVCheckbox=0 AllowOldWin=1 /log inst.log" -if ($ret.ExitCode -ne 0) { - Write-Host "Install failed, dumping log" - Get-Content inst.log - Pop-Location - throw "Exit code is $($ret.ExitCode)" -} -$WindowsPathsToTest | ForEach-Object { - if (! (Test-Path -Path $_) ) { - Pop-Location - throw "Expected $_ but it's not present after uninstall" - } -} -$machineProvider = Get-Content $ConfFilePath | Select-Object -Skip 1 | ConvertFrom-StringData | ForEach-Object { $_.provider } -if ( $machineProvider -ne "`"$ENV:CONTAINERS_MACHINE_PROVIDER`"" ) { - Pop-Location - throw "Expected `"$ENV:CONTAINERS_MACHINE_PROVIDER`" as default machine provider but got $machineProvider" -} - -Write-Host "Installer verification successful!" - -# Run the uninstaller silently to verify that it cleans up properly -$ret = Start-Process -Wait -PassThru ".\podman-${ENV:WIN_INST_VER}-dev-setup.exe" -ArgumentList "/uninstall /quiet /log uninst.log" -if ($ret.ExitCode -ne 0) { - Write-Host "Uninstall failed, dumping log" - Get-Content uninst.log - Pop-Location - throw "Exit code is $($ret.ExitCode)" -} -$WindowsPathsToTest | ForEach-Object { - if ( Test-Path -Path $_ ) { - Pop-Location - throw "Path $_ is still present after uninstall" - } -} - -Write-Host "Uninstaller verification successful!" Pop-Location + +# Run the installer silently and WSL/HyperV install options disabled (prevent reboots) +# We need -skipWinVersionCheck for server 2019 (cirrus image), can be dropped after server 2022 +$command = "$WIN_INST_FOLDER\test-installer.ps1 " +$command += "-operation all " +$command += "-provider $ENV:CONTAINERS_MACHINE_PROVIDER " +$command += "-setupExePath `"$WIN_INST_FOLDER\podman-$ENV:WIN_INST_VER-dev-setup.exe`" " +$command += "-installWSL:`$false " +$command += "-installHyperV:`$false " +$command += "-skipWinVersionCheck:`$true" +Run-Command "${command}" diff --git a/contrib/win-installer/build-hooks.bat b/contrib/win-installer/build-hooks.bat deleted file mode 100644 index 50b24d532b..0000000000 --- a/contrib/win-installer/build-hooks.bat +++ /dev/null @@ -1,6 +0,0 @@ -cd ../.. -set GOARCH=amd64 -go build -ldflags -H=windowsgui -o contrib/win-installer/artifacts/podman-wslkerninst.exe ./cmd/podman-wslkerninst || exit /b 1 -cd contrib/win-installer -@rem Build using x86 toolchain, see comments in check.c for rationale and details -x86_64-w64-mingw32-gcc podman-msihooks/check.c -shared -lmsi -mwindows -o artifacts/podman-msihooks.dll || exit /b 1 diff --git a/contrib/win-installer/build-hooks.ps1 b/contrib/win-installer/build-hooks.ps1 new file mode 100644 index 0000000000..0d862c5e4a --- /dev/null +++ b/contrib/win-installer/build-hooks.ps1 @@ -0,0 +1,67 @@ +function Build-WSLKernelInstaller { + param ( + [string]$wslkerninstFolder, + [string]$artifactsFolder + ); + Set-Variable GOARCH=amd64 + go build -ldflags -H=windowsgui -o "$artifactsFolder\podman-wslkerninst.exe" "$wslkerninstFolder" +} + +function Build-MSIHooks { + param ( + [string]$msiHooksFolder, + [string]$artifactsFolder + ); + + # Build using x86 toolchain, see comments in check.c for rationale and details + if ( Get-MingW ) { + Build-MSIHooks-Using-MingW $msiHooksFolder $artifactsFolder + } elseif ( Get-VSBuildTools ) { + $vsinstance = Get-VSSetupInstance | Select-VSSetupInstance -Product Microsoft.VisualStudio.Product.BuildTools + Build-MSIHooks-Using-VSBuildTools $msiHooksFolder $artifactsFolder $vsinstance + } else { + $msg = "A C/C++ compiler is required to build `"$msiHooksFolder\check.c`". " + $msg += "Supported compilers are MinGW CC (`"x86_64-w64-mingw32-gcc`") and the " + $msg += "`"Microsoft.VisualStudio.Product.BuildTools`" with `"VSSetup`" PowerShell extension." + Write-Error -Message $msg -ErrorAction Stop + } +} + +function Get-MingW { + return Get-Command "x86_64-w64-mingw32-gcc" -errorAction SilentlyContinue +} + +function Get-VSBuildTools { + return ((Get-Command "Get-VSSetupInstance" -errorAction SilentlyContinue) -and ` + (@(Get-VSSetupInstance | Select-VSSetupInstance -Product "Microsoft.VisualStudio.Product.BuildTools").Count -gt 0)) +} + +function Build-MSIHooks-Using-MingW { + param ( + [string]$msiHooksFolder, + [string]$artifactsFolder + ); + Set-Variable GOARCH=amd64 + x86_64-w64-mingw32-gcc $msiHooksFolder/check.c -shared -lmsi -mwindows -o $artifactsFolder/podman-msihooks.dll +} + +function Build-MSIHooks-Using-VSBuildTools { + param ( + [string]$msiHooksFolder, + [string]$artifactsFolder, + [Microsoft.VisualStudio.Setup.Instance]$vsinstance + ); + $vspath = $vsinstance.InstallationPath + $vsinstanceid = $vsinstance.InstanceId + + Import-Module "$vspath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell $vsinstanceid -DevCmdArguments '-arch=amd64 -host_arch=amd64' + cl.exe /W4 /Fo$artifactsFolder\ $msiHooksFolder\check.c Advapi32.lib Msi.lib /link /DLL /out:$artifactsFolder\podman-msihooks.dll +} + +$wslkerninstFolder="$PSScriptRoot\..\..\cmd\podman-wslkerninst" +$msiHooksFolder="$PSScriptRoot\podman-msihooks" +$artifactsFolder="$PSScriptRoot\artifacts" + +Build-WSLKernelInstaller $wslkerninstFolder $artifactsFolder +Build-MSIHooks $msiHooksFolder $artifactsFolder diff --git a/contrib/win-installer/build.ps1 b/contrib/win-installer/build.ps1 index 710748f35c..a6bc61ec92 100644 --- a/contrib/win-installer/build.ps1 +++ b/contrib/win-installer/build.ps1 @@ -45,7 +45,6 @@ function CheckCommand() { } function CheckRequirements() { - CheckCommand "gcc" "MingW CC" CheckCommand "wix" "WiX Toolset" CheckCommand "go" "Golang" } @@ -104,7 +103,7 @@ if ($ENV:INSTVER -eq "") { Exit 1 } -.\build-hooks.bat; ExitOnError +.\build-hooks.ps1; ExitOnError SignItem @("artifacts/win-sshproxy.exe", "artifacts/podman.exe", "artifacts/podman-msihooks.dll", diff --git a/contrib/win-installer/test-installer.ps1 b/contrib/win-installer/test-installer.ps1 new file mode 100644 index 0000000000..7560ddd994 --- /dev/null +++ b/contrib/win-installer/test-installer.ps1 @@ -0,0 +1,93 @@ +#!/usr/bin/env pwsh + +# The Param statement must be the first statement, except for comments and any #Require statements. +param ( + [Parameter(Mandatory)] + [ValidateSet("install", "uninstall", "all")] + [string]$operation, + [Parameter(Mandatory)] + [ValidateScript({Test-Path $_ -PathType Leaf})] + [string]$setupExePath, + [ValidateSet("wsl", "hyperv")] + [string]$provider="wsl", + [switch]$installWSL=$false, + [switch]$installHyperV=$false, + [switch]$skipWinVersionCheck=$false +) + +$ConfFilePath = "$env:ProgramData\containers\containers.conf.d\99-podman-machine-provider.conf" +$WindowsPathsToTest = @("C:\Program Files\RedHat\Podman\podman.exe", + "C:\Program Files\RedHat\Podman\win-sshproxy.exe", + "$ConfFilePath", + "HKLM:\SOFTWARE\Red Hat\Podman") + +function Test-Installation { + if ($installWSL) {$wslCheckboxVar = "1"} else {$wslCheckboxVar = "0"} + if ($installHyperV) {$hypervCheckboxVar = "1"} else {$hypervCheckboxVar = "0"} + if ($skipWinVersionCheck) {$allowOldWinVar = "1"} else {$allowOldWinVar = "0"} + + Write-Host "Running the installer (provider=`"$provider`")..." + $ret = Start-Process -Wait ` + -PassThru "$setupExePath" ` + -ArgumentList "/install /quiet ` + MachineProvider=${provider} ` + WSLCheckbox=${wslCheckboxVar} ` + HyperVCheckbox=${hypervCheckboxVar} ` + AllowOldWin=${allowOldWinVar} ` + /log $PSScriptRoot\podman-setup.log" + if ($ret.ExitCode -ne 0) { + Write-Host "Install failed, dumping log" + Get-Content $PSScriptRoot\podman-setup.log + throw "Exit code is $($ret.ExitCode)" + } + + Write-Host "Verifying that the installer has created the expected files, folders and registry entries..." + $WindowsPathsToTest | ForEach-Object { + if (! (Test-Path -Path $_) ) { + throw "Expected $_ but it's not present after uninstall" + } + } + + Write-Host "Verifying that the machine provider configuration is correct..." + $machineProvider = Get-Content $ConfFilePath | Select-Object -Skip 1 | ConvertFrom-StringData | ForEach-Object { $_.provider } + if ( $machineProvider -ne "`"$provider`"" ) { + throw "Expected `"$provider`" as default machine provider but got $machineProvider" + } + + Write-Host "The installation verification was successful!`n" +} + +function Test-Uninstallation { + Write-Host "Running the uninstaller" + $ret = Start-Process -Wait ` + -PassThru "$setupExePath" ` + -ArgumentList "/uninstall ` + /quiet /log $PSScriptRoot\podman-setup-uninstall.log" + if ($ret.ExitCode -ne 0) { + Write-Host "Uninstall failed, dumping log" + Get-Content $PSScriptRoot\podman-setup-uninstall.log + throw "Exit code is $($ret.ExitCode)" + } + + Write-Host "Verifying that the uninstaller has removed files, folders and registry entries as expected..." + $WindowsPathsToTest | ForEach-Object { + if ( Test-Path -Path $_ ) { + throw "Path $_ is still present after uninstall" + } + } + + Write-Host "The uninstallation verification was successful!`n" +} + +switch ($operation) { + 'install' { + Test-Installation + } + 'uninstall' { + Test-Uninstallation + } + 'all' { + Test-Installation + Test-Uninstallation + } +} diff --git a/winmake.ps1 b/winmake.ps1 index d1a61b8425..3a24541f03 100644 --- a/winmake.ps1 +++ b/winmake.ps1 @@ -85,6 +85,44 @@ function Installer{ Pop-Location } +function Test-Installer{ + param ( + [string]$version, + [ValidateSet("dev", "prod")] + [string]$flavor = "dev", + [ValidateSet("wsl", "hyperv")] + [string]$provider = "wsl" + ); + + if (-Not $version) { + # Get Podman version from local source code + $version = Get-Podman-Version + } + + if ($flavor -eq "prod") { + $suffix = "" + } else { + $suffix = "-dev" + } + + $setupExePath = "$PSScriptRoot\contrib\win-installer\podman-${version}${suffix}-setup.exe" + if (!(Test-Path -Path $setupExePath -PathType Leaf)) { + Write-Host "Setup executable not found in path $setupExePath." + Write-Host "Make 'installer' before making the installer test:" + Write-Host " .\winmake.ps1 installer" + Exit 1 + } + + $command = "$PSScriptRoot\contrib\win-installer\test-installer.ps1" + $command += " -operation all" + $command += " -provider $provider" + $command += " -setupExePath $setupExePath" + $command += " -installWSL:`$false" + $command += " -installHyperV:`$false" + $command += " -skipWinVersionCheck:`$true" + Run-Command "${command}" +} + function Documentation{ Write-Host "Generating the documentation artifacts" # Check that pandoc is installed @@ -249,6 +287,13 @@ switch ($target) { 'installer' { Installer } + 'installertest' { + if ($args.Count -gt 1) { + Test-Installer -provider $args[1] + } else { + Test-Installer + } + } 'docs' { Documentation } @@ -276,6 +321,9 @@ switch ($target) { Write-Host "Example: Build the windows installer" Write-Host " .\winmake installer" Write-Host + Write-Host "Example: Run windows installer tests" + Write-Host " .\winmake installertest" + Write-Host Write-Host "Example: Generate the documetation artifacts" Write-Host " .\winmake docs" Write-Host