Skip to content

Fix for vcpkg run time issue in windows runner #4717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 10, 2025

Conversation

jcbrill
Copy link
Contributor

@jcbrill jcbrill commented May 7, 2025

Fix for vcpkg run time in windows runner (VS2022 Enterprise).

Changes to msvc batch file invocation environment:

  • Add Powershell 7 to PATH
  • Import VCPKG_DISABLE_METRICS if defined
  • Import VCPKG_ROOT if defined

Contributor Checklist:

  • I have created a new test or updated the unit tests to cover the new/changed functionality.
  • I have updated CHANGES.txt and RELEASE.txt (and read the README.rst).
  • I have updated the appropriate documentation

jcbrill added 2 commits May 7, 2025 13:11
Changes for msvc batch file invocation environment:
* Add Powershell 7 to PATH
* Add PSModulePath for Powershell 7 and 5
* Import VCPKG_DISABLE_METRICS if defined
* Import VCPKG_ROOT if defined
@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

@mwichmann The VS2017/PY37 environment is reporting that the MSVS 2015 license has expired.

Image: Visual Studio 2017; Environment: WINPYTHON=Python37 Test failure:

720/1305 ( 55.17%) C:\Python37\python.exe test\MSVS\vs-14.0-exec.py
C:\projects\scons\scripts\scons.py returned 1
STDOUT =========================================================================
Microsoft Visual Studio 2015 Version 14.0.25420.1.
Copyright (C) Microsoft Corp. All rights reserved.
The license for Visual Studio has expired.
Your license for this product is expired and could not be refreshed. Please connect to the internet to continue to use this product.
STDERR =========================================================================
FAILED test of C:\projects\scons\scripts\scons.py
	at line 753 of C:\projects\scons\testing\framework\TestCommon.py (_complete)
	from line 867 of C:\projects\scons\testing\framework\TestCommon.py (run)
	from line 485 of C:\projects\scons\testing\framework\TestSCons.py (run)
	from line 941 of C:\projects\scons\testing\framework\TestSConsMSVS.py (run)
	from line 104 of test\MSVS\vs-14.0-exec.py
Test execution time: 9.0 seconds

@mwichmann
Copy link
Collaborator

yes. @bdbaddog has contacted them about that (a couple of weeks ago, I think). they were supposed to be fixing.

@mwichmann
Copy link
Collaborator

does this relate at all to #4226 ?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

does this relate at all to #4226 ?

I'm going to pretend like I understood the details of #4226 and answer confidently with "only tangentially?"

My understanding of #4226, is the intent is to use vcpkg via the "standalone" vcpkg installation.

The windows runner contains two vcpkg installations:

  1. a standalone vcpkg installation (C:\vcpkg and VCPKG_INSTALLATION_ROOT=C:\vcpkg)
  2. an embedded vcpkg installation in the VS2022 Enterprise tree

Note: it *may be possible to have the both installations use the same package root. This needs to be investigated.

Additional VS components may put initialization batch files in the <VSROOT>\Common7\Tools\vsdevcmd\ext directory. The vcvarsall.bat calls VsDevCmd.bat which in turn calls all of the batch files in the ext folder.

A simplified view of the VS directory contents for vcpkg is:

  • <VSROOT>\Common7\Tools\vsdevcmd\ext:
    • vcpkg.bat
  • <VSROOT>\VC\vcpkg:
    • vcpkg.exe
    • vcpkg-init.cmd

The initial call sequence is to setup the embedded VS vcpkg installation which is done using powershell and may download from the GitHub vcpkg repository.

The embedded VS vcpkg batch files and powershell scripts run within the limited SCons msvc environment as it is kicked off from the msvc batch files.

The windows runner environment has both powershell 7 and powershell 5 installed (different locations so side-by-side). The current SCons implementation adds powershell 5 to msvc environment PATH (i.e., env['ENV']['PATH'] but not powershell 7. In addition, the msvc environment does not contain a PSModulePath entry. PSModulePath is the powershell search path for modules analogeous to the system path.

In the windows runner, adding powershell 7 before powershell 5 in the msvc environment system path and adding a PSModulePath entry containing the All users and system locations for powershell 7 and the All users and system locations for powershell 5 seems to have resolved the runtime issue. My guess is that the newer powershell 7 versions work more effectively than the powershell 5 versions in this context.

The first call tended to be more expensive than later calls. There was something about using powershell 5 without a PSModulePath that caused a problem. The environment in which vcpkg.exe was executed does not contain Windows system environment variables (e.g., ProgramFiles, ProgramData, PROCESSOR_ARCHITECTURE, etc).

It is kind of difficult to debug the msvc batch file and powershell call chain as it is launched via a subprocess and there is limited diagnostic information available.

It may be possible to use the "standalone" package root with the embedded vcpkg.exe by setting VCPKG_ROOT to the standalone package root (e.g., VCPKG_ROOT=C:\vcpkg) in the system environment and "importing" the value into the msvc environment before calling the msvc batch files.

This PR passes along VCPKG_ROOT if it is defined. Note however, the standalone vcpkg appears to use VCPKG_INSTALLATION_ROOT for the location. Effectively doing something like set VCPKG_ROOT=%VCPKG_INSTALLATION_ROOT% might configure the embedded VS vcpkg to use the standalone vcpkg folder. This almost seems like a bad idea.

This PR is intended to be the minimum required to workaround the initialization penalty for the embedded vcpkg.

I am planning on trying a few things locally. Unfortunately, a local instance is not ephemeral like the runner image which makes alternate configurations and testing a bit more work.

Clear as mud?

@mwichmann
Copy link
Collaborator

mwichmann commented May 7, 2025

So is the summary of the summary that things don't work well if vcpkg is used? There was no vcpkg involved in the quickie "examples" build, and there was still that surprising extra minute. Is that something that happens internally?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

Vcpkg isn't even being "used" it was simply installed as a component of Visual Studio.

The vcvars64.bat batch file is initializing the vcpkg component environment and experiences delays due to the aggressively limited environment in which the msvc batch files are run.

I believe that the in-tree vcpkg data after initialization is lost when the runner completes so there is a runtime penalty every time the runner is invoked and to a lesser extend any subsequence invocations in the same session.

The persistent SCons MSVC cache in the windows runner environment limits the penalty to the first invocation of each combination of msvc batch file and arguments. If the msvc cache is not persisted between runs, the runtime penalty applies every time.

What I can't answer is:

  • is the vcpkg component new in the vs2022 runner?
  • is powershell 7 new in the vs2022 runner?

@mwichmann
Copy link
Collaborator

mwichmann commented May 7, 2025

My last question crossed paths, nevermind.

A fully executed "Developer Command Prompt" (that is, all the setup scripts executed) has a LOT of extra variables, which we knew, we were largely guessing at the minimal set. Certainly has a VCPKG_ROOT. That's not new - I see an installed standalone vcpkg in all three current GitHub images, and a Microsoft.VisualStudio.Component.Vcpkg in the 2022 and 2025 images. The available information is here:

https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md

There's a PSModulePath in any windows shell (cmd or ps) I open on a Win11 Pro box. Presumably that only matters if powersehll-y things are done.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

There are two minimal sets of variables:

  1. the set of variables configured for the execution environment to run the msvc batch files
  2. the set of variables to keep from the set command after the batch file is run

The issue at hand is the set of variables going in and not necessarily the set coming out.

In a perfect world, the set of variables kept from the SCons batch file should match exactly the same set of variable values when run from the command-line. However, due to the lack of inclusion of some Windows system variables that is not always the case.

There are known msvc batch scripts that run every time but do not run correctly configure locations due to the lack of Windows system variables (e.g., PROCESSOR_ARCHITECTURE, PROGRAMFILES, etc).

The vcpkg component is different in that it requires powershell to run which has exposed a fundamental difference between the system environment and the environment in which the msvc batch files are invoked.

Aside: The msvc batch files invoke vswhere but it returns no output because ProgramData is not passed into the environment. That is why the banner in the SCons stdout always shows a short version (e.g., v17.0) rather than the full version number when run from the shell environment.

There are days I'm astonished that the msvc batch files work at all within the limited environment.

P.S.: I believe there is a bug in one of msvc batch files (perf_tools.bat). A registry search is looking for a 2019 subkey instead of a 2022 subkey. With only 2022 installed, there is no 2019 subkey. Oof!

@bdbaddog
Copy link
Contributor

bdbaddog commented May 7, 2025

@jcbrill - so it looks like the slowdown is due to vcpkg being initialized by vcvarsall.bat (or equivalent)? Is there a flag to skip that initialization? (

There are two minimal sets of variables:

  1. the set of variables configured for the execution environment to run the msvc batch files
  2. the set of variables to keep from the set command after the batch file is run

The issue at hand is the set of variables going in and not necessarily the set coming out.

In a perfect world, the set of variables kept from the SCons batch file should match exactly the same set of variable values when run from the command-line. However, due to the lack of inclusion of some Windows system variables that is not always the case.

There are known msvc batch scripts that run every time but do not run correctly configure locations due to the lack of Windows system variables (e.g., PROCESSOR_ARCHITECTURE, PROGRAMFILES, etc).

The vcpkg component is different in that it requires powershell to run which has exposed a fundamental difference between the system environment and the environment in which the msvc batch files are invoked.

Aside: The msvc batch files invoke vswhere but it returns no output because ProgramData is not passed into the environment. That is why the banner in the SCons stdout always shows a short version (e.g., v17.0) rather than the full version number when run from the shell environment.

There are days I'm astonished that the msvc batch files work at all within the limited environment.

P.S.: I believe there is a bug in one of msvc batch files (perf_tools.bat). A registry search is looking for a 2019 subkey instead of a 2022 subkey. With only 2022 installed, there is no 2019 subkey. Oof!

Not surprising with the (obscene) level of complexity that MS has put in the MSVC initialization logic..

@bdbaddog
Copy link
Contributor

bdbaddog commented May 7, 2025

@jcbrill - is there anyway to have vcvarsall.bat (or equivalent) skip vcpkg initialization?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

so it looks like the slowdown is due to vcpkg being initialized by vcvarsall.bat (or equivalent)? Is there a flag to skip that initialization?

No such luck.

I'm leaning towards the slowdown was somehow due to running the powershell script with powershell 5 and without the PSModulePath. Without PSModulePath, I believe there are default locations to look for modules. I'm not sure if that requires the Windows system environment variables or not.

It appears that adding powershell 7 in front of powershell 5 in the PATH and setting the PSModulePath for both powershell 7 modules and powershell 5 modules works.

I can try in my copy of the windows runner environment using powershell 5 and setting the PSModulePath path to just the two powershell 5 folders and see what happens.

The vcpkg batch files are written to use whichever version of powershell it finds first on the path.

The vcpkg-init.cmd file is mixture of batch file and powershell script. Effectively the batch portion reads executes itself as a powershell script. Seems unnecessarily complicated but there's probably a good reason. The batch portion jumps to a label in the bottom of the file for initial setup.

vcpkg.bat

...
call "%VSINSTALLDIR%VC\vcpkg\vcpkg-init.cmd" >nul 2>&1
set PATH=%PATH%;%VSINSTALLDIR%VC\vcpkg
...

vcpkg-init.cmd

@(echo off) > $null
if #ftw NEQ '' goto :init
($true){ $Error.clear(); }

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# wrapper script for vcpkg
# this is intended to be dot-sourced and then you can use the vcpkg-shell() function

# Workaround for $IsWindows not existing in Windows PowerShell
if (-Not (Test-Path variable:IsWindows)) {
  $IsWindows = $true
}

function download($url, $path) {
  $wc = New-Object net.webclient
  Write-Host "Downloading '$url' -> '$path'"
  $wc.DownloadFile($url, $path);
  $wc.Dispose();
  if( (get-item $path).Length -ne $wc.ResponseHeaders['Content-Length'] ) {
    throw "Download of '$url' failed.  Check your internet connection."
  }
  if (-Not $IsWindows) {
    chmod +x $path
  }

  return $path
}

# Determine VCPKG_ROOT
if (Test-Path "$PSScriptRoot/.vcpkg-root") {
  $env:VCPKG_ROOT = "$PSScriptRoot"
} elseif (-Not (Test-Path env:VCPKG_ROOT)) {
  $env:VCPKG_ROOT = "$HOME/.vcpkg"
}

$VCPKG = Join-Path $env:VCPKG_ROOT vcpkg
if ($IsWindows) {
  $VCPKG += '.exe'
}

$SCRIPT:VCPKG_VERSION_MARKER = Join-Path $env:VCPKG_ROOT vcpkg-version.txt
$SCRIPT:VCPKG_INIT_VERSION = '2024-12-09'

function bootstrap-vcpkg {
  if (-Not ($VCPKG_INIT_VERSION -eq 'latest') `
    -And (Test-Path $VCPKG_VERSION_MARKER) `
    -And ((Get-Content -Path $VCPKG_VERSION_MARKER -Raw).Trim() -eq $VCPKG_INIT_VERSION)) {
        return $True
  }

  Write-Host "Installing vcpkg to $env:VCPKG_ROOT"
  New-Item -ItemType Directory -Force $env:VCPKG_ROOT | Out-Null

  if ($IsWindows) {
    download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg.exe $VCPKG
  } elseif ($IsMacOS) {
    download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-macos $VCPKG
  } elseif (Test-Path '/etc/alpine-release') {
    download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-muslc $VCPKG
  } else {
    download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-glibc $VCPKG
  }

  & $VCPKG bootstrap-standalone
  if(-Not $?) {
    Write-Error "Bootstrap failed."
    return $False
  }

  Write-Host "Bootstrapped vcpkg: $env:VCPKG_ROOT"
  return $True
}

if(-Not (bootstrap-vcpkg)) {
  throw "Unable to install vcpkg."
}

# Export vcpkg to the current shell.
New-Module -name vcpkg -ArgumentList @($VCPKG) -ScriptBlock {
  param($VCPKG)
  function vcpkg-shell() {
    # setup the postscript file
    # Generate 31 bits of randomness, to avoid clashing with concurrent executions.
    $env:Z_VCPKG_POSTSCRIPT = Join-Path ([System.IO.Path]::GetTempPath()) "VCPKG_tmp_$(Get-Random -SetSeed $PID).ps1"
    & $VCPKG @args
    # dot-source the postscript file to modify the environment
    if (Test-Path $env:Z_VCPKG_POSTSCRIPT) {
      $postscr = Get-Content -Raw $env:Z_VCPKG_POSTSCRIPT
      if( $postscr ) {
        iex $postscr
      }

      Remove-Item -Force -ea 0 $env:Z_VCPKG_POSTSCRIPT
    }

    Remove-Item env:Z_VCPKG_POSTSCRIPT
  }
} | Out-Null

if ($args.Count -ne 0) {
  return vcpkg-shell @args
}

return
<#
:init

:: If the first line of this script created a file named $null, delete it
IF EXIST $null DEL $null

:: Figure out where VCPKG_ROOT is
IF EXIST "%~dp0.vcpkg-root" (
  SET "VCPKG_ROOT=%~dp0"
)

IF "%VCPKG_ROOT:~-1%"=="\" (
  SET "VCPKG_ROOT=%VCPKG_ROOT:~0,-1%"
)

IF "%VCPKG_ROOT%"=="" (
  SET "VCPKG_ROOT=%USERPROFILE%\.vcpkg"
)

:: Call powershell which may or may not invoke bootstrap if there's a version mismatch
SET Z_POWERSHELL_EXE=
FOR %%i IN (pwsh.exe powershell.exe) DO (
  IF EXIST "%%~$PATH:i" (
    SET "Z_POWERSHELL_EXE=%%~$PATH:i"
    GOTO :gotpwsh
  )
)

:gotpwsh
"%Z_POWERSHELL_EXE%" -NoProfile -ExecutionPolicy Unrestricted -Command "iex (get-content \"%~dfp0\" -raw)#"
IF ERRORLEVEL 1 (
  :: leak VCPKG_ROOT
  SET Z_POWERSHELL_EXE=
  EXIT /B 1
)

SET Z_POWERSHELL_EXE=

:: Install the doskey
DOSKEY vcpkg-shell="%VCPKG_ROOT%\vcpkg-cmd.cmd" $*

:: If there were any arguments, also invoke vcpkg with them
IF "%1"=="" GOTO fin
CALL "%VCPKG_ROOT%\vcpkg-cmd.cmd" %*
:fin

EXIT /B
#>

@mwichmann
Copy link
Collaborator

So this kind of illustrates my "fragile" quip from somewhere else in this general thread, which offended @bdbaddog without my meaning to.

Here a working non-SCons environment includes a cascade of scripts that includes picking up ones that have been dropped into the searched directories sort of plugin-like by a non-default install (vcpkg, for example, is installable through the Visual Studio installer, but is not selected by default), which may add expectations on additional environment variables (hopefully this is rare). Since these can potentially even come from third party add-ons, there's not any way to canonically determine what variables would be necessary for any particular project configuration that installs whatever.

Of course the option still remains for the project to add those themselves to env['ENV'] like we suggest, but if that matters for initialization, when the tool setup is run, there's not a really clean way to do that: in order to call Environment(ENV={myenv}) you need to precisely predict what the default ENV will be so you can make a myenv and amend it, rather than replacing it. Is there any way to predict that without instantiating another environment and asking it what it set up? So: scons not passing on the external environment isn't broken, but it's "pick-your-word-if-you-don't-like-fragile".

Sorry, not sure if this added to the PR review.

@bdbaddog
Copy link
Contributor

bdbaddog commented May 7, 2025

Given the above, sounds like we could also remove the bat script in that dir which setup vcpkg on the GHA VM with similar removal of slowdown?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

Of course the option still remains for the project to add those themselves to env['ENV']

The msvc environment is built from a new environment and then only extracts certain variables.

See LeonarddeR/scons-examples@fa9402a#commitcomment-156340060 and the preceding post.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

Observation: the elapsed times can be wildly variable. Guessing due to possible networking and server latency.

Three combinations of runs:

  1. path: powershell 5, psmodulepath: powershell 5 modules [Current SCons]
    env[PATH]=C:\Windows\System32;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0
    env[PSModulePath]=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
    ELAPSED_TIME=27.49, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=56.82, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    
  2. path: powershell 5, psmodulepath: powershell 7 modules, powershell 5 modules [For Fun]
    env[PATH]=C:\Windows\System32;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0
    env[PSModulePath]=C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
    ELAPSED_TIME=2.96, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=2.01, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=6.88, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    
  3. path: powershell 7, psmodulepath: powershell 7 modules, powershell 5 modules [PR]
    env[PATH]=C:\Windows\System32;C:\Windows\System32\Wbem;C:\Program Files\PowerShell\7;C:\Windows\System32\WindowsPowerShell\v1.0
    env[PSModulePath]=C:\Program Files\PowerShell\Modules;C:\Program Files\PowerShell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\System32\WindowsPowerShell\v1.0\Modules
    ELAPSED_TIME=5.79, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=6.12, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=3.68, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    ELAPSED_TIME=4.47, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
    

Having the powershell 7 modules on the PSModulePath appears to make a big difference (2 & 3).

I'm guessing that with a proper experiment configuration, the elapsed times are similar using powershell 7 (3) versus (5).

This PR configures the paths for 3 above. It doesn't make a lot of sense to have the powershell 7 modules first and use powershell 5. Seems like it asking for trouble.

@bdbaddog That is why adding the PSModulePath to the environment worked well in our early tests.

The test results above did not set VCPKG_ROOT which is in this PR.

I can try the tests again early tomorrow morning as the elapsed times tend to be lower.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 7, 2025

Here a working non-SCons environment includes a cascade of scripts that includes picking up ones that have been dropped into the searched directories sort of plugin-like by a non-default install (vcpkg, for example, is installable through the Visual Studio installer, but is not selected by default), which may add expectations on additional environment variables (hopefully this is rare). Since these can potentially even come from third party add-ons, there's not any way to canonically determine what variables would be necessary for any particular project configuration that installs whatever.

Yes.

Although one could enable every option in the VS installer and manually check and see what ends up in the ext script and then follow any additional calls. Manual labor intensive and error prone.

By setting VSCMD_DEBUG=2 and logging the subprocess stdout lines, the batch files called were determined in the windows runner. Then by locally installing VS2022 Enterprise (which has a different set of "default" batch files than Community), components were added until the ext batch file list matched. At that point they were manually inspected.

To your point, it looks like VCPKG_ROOT may need to be imported from the environment. I'm not sure if using two different vcpkg executables with the same downloaded packages folder is a good idea or not.

Some of these features may be important if one is building from ms vsproject files and far less important for SCons builds. I'm am sure to be wrong here though.

Of course the option still remains for the project to add those themselves to env['ENV'] like we suggest, but if that matters for initialization, when the tool setup is run, there's not a really clean way to do that: in order to call Environment(ENV={myenv}) you need to precisely predict what the default ENV will be so you can make a myenv and amend it, rather than replacing it. Is there any way to predict that without instantiating another environment and asking it what it set up? So: scons not passing on the external environment isn't broken, but it's "pick-your-word-if-you-don't-like-fragile".

The msvc batch file environment is not passed the current Environment but creates "a blank environment, for use in launching the tools".

Sorry, not sure if this added to the PR review.

This is all important moving forward.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

Given the above, sounds like we could also remove the bat script in that dir which setup vcpkg on the GHA VM with similar removal of slowdown?

I'm not sure modifying the installation without knowing exactly how it is being used is advisable. It would seem possible that MSVC could be used in other ways in addition to running SCons in the same job.

@bdbaddog
Copy link
Contributor

bdbaddog commented May 8, 2025

Given the above, sounds like we could also remove the bat script in that dir which setup vcpkg on the GHA VM with similar removal of slowdown?

I'm not sure modifying the installation without knowing exactly how it is being used is advisable. It would seem possible that MSVC could be used in other ways in addition to running SCons in the same job.

In the context of running SCons tests on GHA windows VM, it would be safe.
However it would hide the issue. But for the original reporter, that might be an option until we address in SCons and release..?
I wasn't suggesting doing this as a general rule.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

There really is not that much to this PR.

We are adding the following only for the msvc batch file invocation:

  • Adding one path (the powershell 7 path in front of the existing powershell 5 path) to PATH. This does nothing if powershell 7 is not installed.
  • Adding PSModulePath which includes two powershell 7 folders and two powershell 5 folders. Three of the folders are in %ProgramFiles% and the other folder is in %windir%. The two powershell 7 folders do nothing if powershell 7 is not installed.
  • Importing VCPKG_DISABLE_METRICS from os.environ.
  • Possibly importing VCPKG_ROOT from os.environ.

@bdbaddog Is it possible to setup a windows runner (2022) environment to download an scons branch and run scons manually?

For example, this how I run the development branch with SCons locally from a test folder:

python ..\..\jbrill-msvc-slow\scripts\scons.py --debug=time,sconscript

Basically, can we test the proposed branch in a windows runner environment prior to accepting the PR to work out the kinks (if any)?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

I have reason to believe that simply adding powershell 7 to the msvc environment path in front of powershell 5 is the solution and that adding PSModulePath is not necessary.

I believe when a powershell binary is invoked, if PSModulePath is not defined, the binary will search default locations for modules.

Notes:

  • Without powershell 7 on the path, powershell 5 is using the powershell 5 modules which appear to be "slower".
  • With powershell 7 on the path, powershell 7 is using the powershell 7 modules which appear to be "faster".
  • If powershell 7 is not installed, the binary will not be found on the path and powershell 5 is the only alternative.
  • Adding PSModulePath without adding powershell 7 to the path seemed to work as powershell 5 is likely using the powershell 7 modules.

VCPKG_DISABLE_METRICS is added to the msvc environment if it is defined.
VCPKG_ROOT is added to the msvc environment if it is defined.

Sample output from windows runner 2022 test environment (PATH with PS7, No PSModulePATH):

00524ms:SConstruct:_test_scons#1252: env[SystemDrive]=C:
00524ms:SConstruct:_test_scons#1252: env[SystemRoot]=C:\Windows
00524ms:SConstruct:_test_scons#1252: env[TEMP]=C:\Users\RUNNER~1\AppData\Local\Temp
00524ms:SConstruct:_test_scons#1252: env[TMP]=C:\Users\RUNNER~1\AppData\Local\Temp
00524ms:SConstruct:_test_scons#1252: env[USERPROFILE]=C:\Users\runneradmin
00524ms:SConstruct:_test_scons#1252: env[ComSpec]=C:\Windows\system32\cmd.exe
00524ms:SConstruct:_test_scons#1252: env[OS]=Windows_NT
00524ms:SConstruct:_test_scons#1252: env[VSCMD_SKIP_SENDTELEMETRY]=1
00524ms:SConstruct:_test_scons#1252: env[windir]=C:\Windows
00524ms:SConstruct:_test_scons#1252: env[VCPKG_DISABLE_METRICS]=1
00524ms:SConstruct:_test_scons#1252: env[PATH]=C:\Windows\System32;C:\Windows\System32\Wbem;C:\Program Files\PowerShell\7;C:\Windows\System32\WindowsPowerShell\v1.0
00524ms:SConstruct:_test_scons#1252: env[PATHEXT]=.COM;.EXE;.BAT;.CMD
03086ms:SConstruct:msvc_find_valid_batch_script#935: ELAPSED_TIME=2.56, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
04197ms:SConstruct:msvc_find_valid_batch_script#935: ELAPSED_TIME=1.11, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
05228ms:SConstruct:msvc_find_valid_batch_script#935: ELAPSED_TIME=1.03, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
06245ms:SConstruct:msvc_find_valid_batch_script#935: ELAPSED_TIME=1.02, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'
07292ms:SConstruct:msvc_find_valid_batch_script#935: ELAPSED_TIME=1.05, vc_script='C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat'

@mwichmann
Copy link
Collaborator

You removed PSModulePath from this? Looked reasonable to me, even with...

jcbrill referenced this pull request in LeonarddeR/scons-examples May 8, 2025
@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

You removed PSModulePath from this? Looked reasonable to me, even with...

I did remove PSModulePath.

There is a subtle difference between what was set in the PSModulePath and the powershell default paths.

For the earlier version, two module locations for each version of powershell were added: the "All Users" powershell location and the powershell installation location.

I believe that powershell may include a current user location first before the other locations when PSModulePath is not defined.

Argument for taking it out: If it works without PSModulePath, it might be best just to let powershell use it's own defaults. If there is a problem using the powershell defaults, the prior setting of the PSModulePath likely will have the same problem.

The only case that probably doesn't work is if somehow powershell is installed to a different file system location but the modules were in the expected locations. In this case, powershell 5 would need the PSModulePath to find the powershell 7 modules first. Hopefully, this is unlikely.

The vcpkg batch files are simply searching the path for the executable and running invoking it relying on the vcpkg executable to do the heavy lifting for finding modules.

I'm leaning towards not defining PSModulePath and hoping it all works out (i.e., the simplest thing that might work).

Any thoughts on how to test the branch in a windows runner 2022 environment?

Disclaimer: I know diddly squat about powershell.

@mwichmann
Copy link
Collaborator

Share the lack of knowledge about PS.

I think if you make a change to the workflow like this patch (untested, because how do you test it locally?), it should try a build on 2022.

diff --git a/.github/workflows/runtest-win.yml b/.github/workflows/runtest-win.yml
index 2dc2ce118..11d17e48f 100644
--- a/.github/workflows/runtest-win.yml
+++ b/.github/workflows/runtest-win.yml
@@ -18,7 +18,15 @@ env:
 
 jobs:
   runtest-win32:
-    runs-on: windows-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        os: ['windows-2022', 'windows-2025']
+
+    # runs-on: windows-latest
+    runs-on: ${{ matrix.os }}
+
     steps:
       - uses: actions/[email protected]
 

There seems little use bothering with a test on 2019, due to this note:

The Windows 2019 Actions runner image will begin deprecation on 2025-06-01 and will be fully unsupported by 2025-06-30

@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

Thanks.

I'm not really looking to run the full test suite with the branch but to run one simple build with the branch (like one of the SCons examples) to check the build time.

I don't think a new test will work due to the msvc cache.

A separate workflow/action?

Any thoughts?

@mwichmann
Copy link
Collaborator

Sure - we can run whatever, there's a reasonable bit of control in setting up an action. I'm no expert on those things either, just muddled through a few times.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 8, 2025

What I'm thinking is something stupid simple like this:

  • Environment configuration without MSVC cache:
    set VCPKG_DISABLE_METRICS=1
    set VSCMD_SKIP_SENDTELEMETRY=1
    set SCONS_MSCOMMON_DEBUG=-
    
  • SConstruct:
    DefaultEnvironment(tools=[])
    Environment(tools=["msvc"])
    
  • run scons.py from this branch

That should call vcvars64.bat once?

And then the magic happens here...

Changes:
* change raw string with multiple directories comma-separated list of string directories for powershell 7
* move constant list of environment variable names definition from inside function to file level and rename accordingly.
# control execution in interesting ways.
# Note these really should be unified - either controlled by vs.py,
# or synced with the the common_tools_var # settings in vs.py.
_VS_VC_VARS = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove the _ prefix here. There's no major reason to sugguest accessing this from other code should be avoided ?

Copy link
Contributor Author

@jcbrill jcbrill May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an indicator that it shouldn't necessarily be modified. I don't feel strongly either way.

We should probably add a debug logging statement for each of the keys retrieved from the list in normalize_env since the list can now be modified. The mscommon log file is the easiest way to determine if a user modified the list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed change removing the _ prefix and added debug logging in the loop that evaluates the keys.

jcbrill added 2 commits May 9, 2025 15:50
…nt in which the MSVC batch files run. Update CHANGES.txt and RELEASE.txt.
@jcbrill jcbrill changed the title [WIP] Proposed fix for vcpkg run time issue in windows runner Proposed fix for vcpkg run time issue in windows runner May 10, 2025
@jcbrill jcbrill changed the title Proposed fix for vcpkg run time issue in windows runner Fix for vcpkg run time issue in windows runner May 10, 2025
@jcbrill
Copy link
Contributor Author

jcbrill commented May 10, 2025

At present, no additional work planned unless requested.

…y this PR. Also moved list of shell env vars propaagated to msvs configuration environment to top of file as is fairly standard practice
@bdbaddog
Copy link
Contributor

@jcbrill - just pushed a couple (IMHO) minor updates, I wanted to better highlight what's fixed by this PR and then just moved the list of shell variables initialized into the constructed environment to run init script to top of file as we'd discussed above. Assuming tests pass, I'll go ahead and merge this.

@bdbaddog bdbaddog merged commit 4562f2e into SCons:master May 10, 2025
6 of 8 checks passed
@jcbrill
Copy link
Contributor Author

jcbrill commented May 11, 2025

and then just moved the list of shell variables initialized into the constructed environment to run init script to top of file as we'd discussed above.

Evidently without looking at the rest of the file or the other file-level variable list and two constants would have been moved as well.

One list just moved from the only place it is used to the top without a particularly useful name. IMHO, this was not an improvement.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 11, 2025

The runtime improvement is due to using powershell 7 instead of powershell 5 in the windows runner. If powershell 7 is not installed in a similar environment, the runtime penalty may still exist.

@bdbaddog
Copy link
Contributor

and then just moved the list of shell variables initialized into the constructed environment to run init script to top of file as we'd discussed above.

Evidently without looking at the rest of the file or the other file-level variable list and two constants would have been moved as well.

One list just moved from the only place it is used to the top without a particularly useful name. IMHO, this was not an improvement.

I'll move the rest then as well..

@jcbrill
Copy link
Contributor Author

jcbrill commented May 11, 2025

I'll move the rest then as well..

Might as well make all the code harder to read.

@jcbrill jcbrill deleted the jbrill-msvc-slow branch May 11, 2025 11:55
@mwichmann mwichmann added the MSVC Microsoft Visual C++ Support label May 11, 2025
@mwichmann mwichmann moved this to Done in Next Release May 11, 2025
@mwichmann mwichmann added this to the NextRelease milestone May 11, 2025
@jcbrill
Copy link
Contributor Author

jcbrill commented May 12, 2025

The CHANGES.txt text changed from a precise statement about what was changed and the environment in which the change would apply, to something that may not hold in all cases.

-    - The default Windows powershell 7 path is added before the default
-      Windows powershell 5 path in the limited environment in which the
-      MSVC batch files are run.  This appears to fix a runtime issue in
-      the windows runner environment when the embedded Visual Studio vcpkg
-      component initializes without a PSModulePath defined. The OS
+    - MSVS: Fix a significant MSVC/MSVC tool initialization slowdown when
+      vcpkg has been installed and the PSModulePath is not initialized
+      or propagated from the user's shell environment. To resolve this
+      The default Windows Powershell 7 path is added before the default
+      Windows Powershell 5 path in the carefully constructed
+      environment in which the MSVC batch files are run.   The shell

The runtime improvement seen in the windows runner environment was due to PS 7 and VS vcpkg being installed at the same time.

Powershell 7 is an optional component install in Windows (i.e., not installed by default).

As mentioned above, in the windows runner environment:

  • PS7 + vcpkg initialization: improvement
  • PS5 + vcpkg initialization with the PSModulePath containing PS7 modules and PS 5 modules: improvement
  • PS5 + vcpkg initialization: slow

Adding the PS 7 path in an environment in which PS 7 is not installed is unlikely to result in any improvement and there may be possible runtime degradation if the environment is similar to the windows runner environment.

In the future, if the text I've written for work that I performed and is attributed to me is modified, I would appreciate it if:

  • it is true/correct after the modification
  • I have the opportunity to review the changes before being committed and merged

There was a reason the original text was written the way it was.

@bdbaddog
Copy link
Contributor

Please PR an update, but pay attention to the below.

The issue I saw with your text is it was implementation focused (what changed in the implementation) vs what it fixed for a user having an issue. That should always be the focus of the CHANGES.txt and RELEASE.txt.
So anyone who has an issue and is on an older version can look there and see if their issue has been fixed (or not), and try that version.

I updated to be more user focused as I understood the issue, which according to the above was incorrect.

@bdbaddog
Copy link
Contributor

Fair request to review, but I was just short on time and thought the change was correct.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

The issue I saw with your text is it was implementation focused (what changed in the implementation) vs what it fixed for a user having an issue. That should always be the focus of the CHANGES.txt and RELEASE.txt.

Fair request to review, but I was just short on time and thought the change was correct.

The intention was well-meaning.

More could, possibly unintentionally, be read into the modified language that was not necessarily present in the original.

The concern is that the improvement is seen in one specific combination of OS and installed tools and may not translate to other runtime environments. It is fair to say there was a significant improvement in the windows runner 2022 environment. Unfortunately, that is the only environment known to be experiencing a runtime issue which benefits significantly from the fix in the PR.

The runtime issue couldn't be replicated locally with Windows 11 and VS 2022 Enterprise both with and without PS 7 installed. PS 5 seemed to work well enough. The significant slowdowns were not seen locally and the relative elapsed times seemed reasonably close enough.

If one is using the windows runner (2022), there would certainly be a good reason to upgrade to the latest version. For other combinations, there may not be any benefit, or even worse, the initialization delays might be unavoidable. Although, adopting the latest version shouldn't make it worse (I hope).

While a mostly likely cause was identified, there is still uncertainty as to the exact step(s) in the vcpkg initialization sequence that experience the delays in the powershell script(s).

Of concern is what the vcpkg initialization runtime in a windows-runner-like environment would be if PS 7 was not installed. Would users be "stuck" with the initialization slowdown without remedy?

There may also be OS/component combinations in the wild that experience the same initialization slowdown that goes largely unnoticed because it is a minor fraction of the full build time.

For example, the SCons test suite running with the MSVC cache. A 60 second slowdown may be experienced for the first invocation of the msvc batch files. However, the magnitude of the slowdown is almost meaningless when the full test suite is run with the cache. In this example, a significant improvement in vcpkg initialization time has an inconsequential impact on overall runtime.

Without the cache would be a different story. The cost of the slowdown would be additive across all tests that have default tools initialization and/or use of the default compilers.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

Notes for updated text PR.

Observations:

  • Visual Studio VCPKG component was not installed as a component in windows-2019.
  • windows-2022 and windows-2025 are slow with VS VCPKG component and powershell 5
  • windows-2022 and windows-2025 are faster with VS VCPKG component and powershell 7

The runtimes with powershell 5 are an order of magnitude slower than with powershell 7 in windows-2022 and windows-2025.

Visual Studio 2022 VCPKG

Runner Visual Studio 2022 VCPKG Component
windows-2019 Not Installed
windows-2022 Microsoft.VisualStudio.Component.Vcpkg 17.13.35710.127
windows-2025 Microsoft.VisualStudio.Component.Vcpkg 17.13.35710.127

Example SConstruct times with Powershell 5 (runner results sorted low to high in secs):

Runner 1 2 3 4
windows-2019 2.9 4.1 4.7 7.4
windows-2022 39.9 61.3 69.7 99.3
windows-2025 33.0 46.5 52.4 92.6

Example SConstruct times with Powershell 7 (runner results sorted low to high in secs):

Runner 1 2 3 4
windows-2019 2.8 4.8 6.8 8.3
windows-2022 4.3 4.4 7.1 13.4
windows-2025 2.6 2.8 3.0 5.7

@bdbaddog
Copy link
Contributor

@jcbrill better to just start a PR and include the above?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

@bdbaddog Yes. #4720

jcbrill added a commit to jcbrill/scons that referenced this pull request May 13, 2025
Changes:
* Partially restore restore original GH SCons#4717 CHANGES.txt description
* Change MSVS to MSVC in RELEASE.txt
* Remove extraneous two blank lines introduced in common.py when variable moved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MSVC Microsoft Visual C++ Support Performance
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants