Skip to content

Commit

Permalink
feat: detect visual studio installation using VSSetup module and Get-…
Browse files Browse the repository at this point in the history
…VSSetupInstance method, it works even in systems with Constrained language mode of the powershell
  • Loading branch information
jarig committed Dec 23, 2023
1 parent cff9ac2 commit d54e550
Show file tree
Hide file tree
Showing 6 changed files with 894 additions and 27 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ Install tools and configuration manually:

To use the native ARM64 C++ compiler on Windows on ARM, ensure that you have Visual Studio 2022 [17.4 or later](https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/) installed.

It's advised to install following Powershell module: [VSSetup](https://github.com/microsoft/vssetup.powershell) using `Install-Module VSSetup -Scope CurrentUser`.
This will make Visual Studio detection logic to use more flexible and accessible method, avoiding Powershell's `ConstrainedLanguage` mode.

### Configuring Python Dependency

`node-gyp` requires that you have installed a [supported version of Python](https://devguide.python.org/versions/).
Expand Down
85 changes: 74 additions & 11 deletions lib/find-visualstudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const log = require('./log')
const { existsSync } = require('fs')
const { win32: path } = require('path')
const { path } = require('path')
const { regSearchKeys, execFile } = require('./util')

class VisualStudioFinder {
Expand Down Expand Up @@ -54,6 +54,7 @@ class VisualStudioFinder {
}

const checks = [
() => this.findVisualStudio2017OrNewerUsingSetupModule(this.envVcInstallDir),
() => this.findVisualStudio2017OrNewer(),
() => this.findVisualStudio2015(),
() => this.findVisualStudio2013()
Expand Down Expand Up @@ -113,6 +114,48 @@ class VisualStudioFinder {
throw new Error('Could not find any Visual Studio installation to use')
}

async findVisualStudio2017OrNewerUsingSetupModule (vcInstallDir) {
const ps = path.join(process.env.SystemRoot, 'System32',
'WindowsPowerShell', 'v1.0', 'powershell.exe')

const checkModuleArgs = [
'-NoProfile',
'-Command',
'&{@(Get-Module -ListAvailable -Name VSSetup).Version.ToString()}'
]
const [cErr] = await this.execFile(ps, checkModuleArgs, { encoding: 'utf8' })
if (cErr) {
this.addLog('VSSetup module doesn\'t seem to exist. You can install it via: "Install-Module VSSetup -Scope CurrentUser"')
return null
}
const filterArg = vcInstallDir !== undefined ? `| where {$_.InstallationPath -eq '${vcInstallDir}' }` : ''
const psArgs = [
'-NoProfile',
'-Command',
`&{Get-VSSetupInstance ${filterArg} | ConvertTo-Json -Depth 3}`
]

this.log.silly('Running', ps, psArgs)
const [err, stdout, stderr] = await this.execFile(ps, psArgs, { encoding: 'utf8' })
let parsedData = this.parseData(err, stdout, stderr)
if (parsedData === null) {
return null
}
if (!Array.isArray(parsedData)) {
// if there are only 1 result, then Powershell will output non-array
parsedData = [parsedData]
}
// normalize output
parsedData = parsedData.map((info) => {
info.path = info.InstallationPath
info.version = `${info.InstallationVersion.Major}.${info.InstallationVersion.Minor}.${info.InstallationVersion.Build}.${info.InstallationVersion.Revision}`
info.packages = info.Packages.map((p) => p.Id)
return info
})
// pass for further processing
return this.processData(parsedData)
}

// Invoke the PowerShell script to get information about Visual Studio 2017
// or newer installations
async findVisualStudio2017OrNewer () {
Expand All @@ -128,24 +171,35 @@ class VisualStudioFinder {
]

this.log.silly('Running', ps, psArgs)
const [err, stdout, stderr] = await execFile(ps, psArgs, { encoding: 'utf8' })
return this.parseData(err, stdout, stderr)
const [err, stdout, stderr] = await this.execFile(ps, psArgs, { encoding: 'utf8' })
const parsedData = this.parseData(err, stdout, stderr, { checkIsArray: true })
if (parsedData === null) {
return null
}
return this.processData(parsedData)
}

// Parse the output of the PowerShell script and look for an installation
// of Visual Studio 2017 or newer to use
parseData (err, stdout, stderr) {
// Parse the output of the PowerShell script, make sanity checks
parseData (err, stdout, stderr, sanityCheckOptions) {
const defaultOptions = {
checkIsArray: false
}

// Merging provided options with the default options
const sanityOptions = { ...defaultOptions, ...sanityCheckOptions }

this.log.silly('PS stderr = %j', stderr)

const failPowershell = () => {
const failPowershell = (failureDetails) => {
this.addLog(
'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details')
`could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details. \n
Failure details: ${failureDetails}`)
return null
}

if (err) {
this.log.silly('PS err = %j', err && (err.stack || err))
return failPowershell()
return failPowershell(`${err}`.substring(0, 15))
}

let vsInfo
Expand All @@ -157,11 +211,16 @@ class VisualStudioFinder {
return failPowershell()
}

if (!Array.isArray(vsInfo)) {
if (sanityOptions.checkIsArray && !Array.isArray(vsInfo)) {
this.log.silly('PS stdout = %j', stdout)
return failPowershell()
return failPowershell('Expected array as output of the PS script')
}
return vsInfo
}

// Process parsed data containing information about VS installations
// Look for the required parts, extract and output them back
processData (vsInfo) {
vsInfo = vsInfo.map((info) => {
this.log.silly(`processing installation: "${info.path}"`)
info.path = path.resolve(info.path)
Expand Down Expand Up @@ -438,6 +497,10 @@ class VisualStudioFinder {

return true
}

async execFile (exec, args) {
return await execFile(exec, args, { encoding: 'utf8' })
}
}

module.exports = VisualStudioFinder
Loading

0 comments on commit d54e550

Please sign in to comment.