Default to auto-detected 64-bit MSBuild on 64-bit hosts (breaking change, v4)

Fixes #88

- msbuild-architecture no longer hardcodes a default of "x86". When the
  input is left unspecified, the action now auto-detects: it prefers x64
  when running on a 64-bit machine and the resolved VS/MSBuild install is
  17.0+ (VS 2022+), and falls back to x86 otherwise. An explicit
  msbuild-architecture value is always respected.
- Extracted resolveMSBuildArchitecture()/parseMajorVersion() as pure,
  exported helpers and added jest tests covering the resolution matrix.
- Bumped package.json version to 4.0.0 (breaking change/new major version).
- Updated action.yml input description and README (usage examples now
  reference @v4, new "Breaking Changes in v4" section, and the
  architecture-selection docs rewritten to describe auto-detection).
- Rebuilt dist/index.js via ncc.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Chet Husk
2026-09-21 16:28:32 -05:00
parent ea1f75da7f
commit fc127f2e68
6 changed files with 215 additions and 14 deletions
+24 -6
View File
@@ -5,11 +5,25 @@ This action will help discover where the `MSBuild` tool is and automatically add
> [!IMPORTANT] > [!IMPORTANT]
> Please note this tool does NOT add other Visual Studio tools (like VSTest, cl, cmake, or others) to `PATH` > Please note this tool does NOT add other Visual Studio tools (like VSTest, cl, cmake, or others) to `PATH`
## Breaking Changes in v4
Starting with `v4`, the default value of `msbuild-architecture` is no longer always `x86`. When
`msbuild-architecture` is not explicitly specified, the action now **auto-detects** the best
architecture:
- `x64` when running on a 64-bit machine **and** the resolved Visual Studio/MSBuild install is
version 17.0 or later (Visual Studio 2022+, which ships a native 64-bit MSBuild).
- `x86` otherwise (older Visual Studio versions, or 32-bit hosts), matching the previous behavior.
If you explicitly set `msbuild-architecture` (e.g. `x86`, `x64`, or `arm64`), that value is always
respected and this auto-detection does not apply. If your workflow depends on always getting the
32-bit MSBuild by default, set `msbuild-architecture: x86` explicitly.
## Example Usage ## Example Usage
```yml ```yml
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v4
- name: Build app for release - name: Build app for release
run: msbuild src\YourProjectFile.csproj -t:rebuild -verbosity:diag -property:Configuration=Release run: msbuild src\YourProjectFile.csproj -t:rebuild -verbosity:diag -property:Configuration=Release
@@ -29,7 +43,7 @@ You may have a situation where your Actions runner has multiple versions of Visu
```yml ```yml
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v4
with: with:
vs-version: '[16.4,16.5)' vs-version: '[16.4,16.5)'
``` ```
@@ -42,18 +56,22 @@ If you need your Actions runner to target a pre-release version of Visual Studio
```yml ```yml
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v4
with: with:
vs-prerelease: true vs-prerelease: true
``` ```
### Specifying MSBuild architecture (optional) ### Specifying MSBuild architecture (optional)
By default the action will use the x86 architecture for MSBuild, but it is possible to target the x64 versions instead. Simply add the `msbuild-architecture` input. Valid input values are `x86` (default), `x64`, and `arm64`. Note that the success of these will rely on the runner OS. By default (as of `v4`), the action auto-detects the preferred MSBuild architecture: it uses `x64`
when running on a 64-bit machine with Visual Studio/MSBuild 17.0 or later installed, and falls back
to `x86` otherwise. You can override this by explicitly setting the `msbuild-architecture` input.
Valid input values are `x86`, `x64`, and `arm64`. Note that the success of these will rely on the
runner OS.
```yml ```yml
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v4
with: with:
msbuild-architecture: x64 msbuild-architecture: x64
``` ```
@@ -64,7 +82,7 @@ This makes use of the vswhere tool which is a tool delivered by Microsoft to hel
```yml ```yml
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v4
with: with:
vswhere-path: 'C:\path\to\your\tools\' vswhere-path: 'C:\path\to\your\tools\'
``` ```
+54
View File
@@ -0,0 +1,54 @@
import {
MIN_VS_VERSION_FOR_X64,
parseMajorVersion,
resolveMSBuildArchitecture
} from '../src/main'
describe('parseMajorVersion', () => {
it('parses a standard installationVersion string', () => {
expect(parseMajorVersion('17.9.34728.123')).toBe(17)
})
it('parses a single-segment version', () => {
expect(parseMajorVersion('16')).toBe(16)
})
it('trims whitespace before parsing', () => {
expect(parseMajorVersion(' 17.0.0.0 ')).toBe(17)
})
it('returns undefined for an empty string', () => {
expect(parseMajorVersion('')).toBeUndefined()
})
it('returns undefined for a non-numeric string', () => {
expect(parseMajorVersion('not-a-version')).toBeUndefined()
})
})
describe('resolveMSBuildArchitecture', () => {
it('always respects an explicit architecture, regardless of OS/version', () => {
expect(resolveMSBuildArchitecture('x86', true, 17)).toBe('x86')
expect(resolveMSBuildArchitecture('x64', false, 15)).toBe('x64')
expect(resolveMSBuildArchitecture('arm64', true, 17)).toBe('arm64')
})
it('defaults to x64 on a 64-bit host with VS/MSBuild 17+', () => {
expect(resolveMSBuildArchitecture('', true, MIN_VS_VERSION_FOR_X64)).toBe(
'x64'
)
expect(resolveMSBuildArchitecture('', true, 18)).toBe('x64')
})
it('defaults to x86 on a 64-bit host with VS/MSBuild older than 17', () => {
expect(resolveMSBuildArchitecture('', true, 16)).toBe('x86')
})
it('defaults to x86 on a non-64-bit host even with VS/MSBuild 17+', () => {
expect(resolveMSBuildArchitecture('', false, 17)).toBe('x86')
})
it('defaults to x86 when the VS/MSBuild version could not be determined', () => {
expect(resolveMSBuildArchitecture('', true, undefined)).toBe('x86')
})
})
+1 -2
View File
@@ -15,9 +15,8 @@ inputs:
description: "Enable searching for pre-release versions of Visual Studio/MSBuild" description: "Enable searching for pre-release versions of Visual Studio/MSBuild"
required: false required: false
msbuild-architecture: msbuild-architecture:
description: 'The preferred processor architecture of MSBuild. Can be either "x86", "x64", or "arm64". "x64" is only available from Visual Studio version 17.0 and later.' description: 'The preferred processor architecture of MSBuild. Can be either "x86", "x64", or "arm64". "x64" is only available from Visual Studio version 17.0 and later. If left unspecified, the action auto-detects the best option: "x64" on 64-bit machines when a Visual Studio/MSBuild 17.0+ install is found, otherwise "x86".'
required: false required: false
default: "x86"
outputs: outputs:
msbuildPath: msbuildPath:
description: "The resulting location of msbuild for your inputs" description: "The resulting location of msbuild for your inputs"
+54 -1
View File
@@ -26,13 +26,45 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(484)); const core = __importStar(__nccwpck_require__(484));
const exec = __importStar(__nccwpck_require__(236)); const exec = __importStar(__nccwpck_require__(236));
const fs = __importStar(__nccwpck_require__(896)); const fs = __importStar(__nccwpck_require__(896));
const os = __importStar(__nccwpck_require__(857));
const path = __importStar(__nccwpck_require__(928)); const path = __importStar(__nccwpck_require__(928));
const io = __importStar(__nccwpck_require__(994)); const io = __importStar(__nccwpck_require__(994));
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
const VS_VERSION = core.getInput('vs-version') || 'latest'; const VS_VERSION = core.getInput('vs-version') || 'latest';
const VSWHERE_PATH = core.getInput('vswhere-path'); const VSWHERE_PATH = core.getInput('vswhere-path');
const ALLOW_PRERELEASE = core.getInput('vs-prerelease') || 'false'; const ALLOW_PRERELEASE = core.getInput('vs-prerelease') || 'false';
let MSBUILD_ARCH = core.getInput('msbuild-architecture') || 'x86'; const MSBUILD_ARCH_INPUT = core.getInput('msbuild-architecture');
let MSBUILD_ARCH = MSBUILD_ARCH_INPUT;
// The minimum Visual Studio/MSBuild major version that ships a native x64 MSBuild.exe
exports.MIN_VS_VERSION_FOR_X64 = 17;
// Determines the effective MSBuild architecture to use.
// If the user explicitly specified `msbuild-architecture`, that choice is always respected.
// Otherwise (breaking change as of v4), the action auto-detects and prefers x64 when
// running on a 64-bit OS and the resolved VS/MSBuild installation is version 17.0 or later.
// If the VS version can't be determined, or the host isn't 64-bit, it falls back to x86.
function resolveMSBuildArchitecture(explicitArch, isWindows64Bit, vsMajorVersion) {
if (explicitArch) {
return explicitArch;
}
if (isWindows64Bit &&
vsMajorVersion !== undefined &&
vsMajorVersion >= exports.MIN_VS_VERSION_FOR_X64) {
return 'x64';
}
return 'x86';
}
exports.resolveMSBuildArchitecture = resolveMSBuildArchitecture;
// Parses the major version number out of a vswhere `installationVersion` string
// such as "17.9.34728.123". Returns undefined if it cannot be parsed.
function parseMajorVersion(installationVersion) {
const match = installationVersion.trim().match(/^(\d+)/);
if (!match) {
return undefined;
}
const major = parseInt(match[1], 10);
return isNaN(major) ? undefined : major;
}
exports.parseMajorVersion = parseMajorVersion;
// if a specific version of VS is requested // if a specific version of VS is requested
let VSWHERE_EXEC = '-products * -requires Microsoft.Component.MSBuild -property installationPath -latest '; let VSWHERE_EXEC = '-products * -requires Microsoft.Component.MSBuild -property installationPath -latest ';
if (ALLOW_PRERELEASE === 'true') { if (ALLOW_PRERELEASE === 'true') {
@@ -75,6 +107,27 @@ function run() {
return; return;
} }
core.debug(`Full tool exe: ${vswhereToolExe}`); core.debug(`Full tool exe: ${vswhereToolExe}`);
// if the user did not explicitly specify an architecture, auto-detect the
// preferred architecture based on the host OS and the resolved VS/MSBuild version
if (!MSBUILD_ARCH_INPUT) {
let vsMajorVersion;
const versionOptions = {};
versionOptions.listeners = {
stdout: (data) => {
const installationVersion = data.toString().trim();
core.debug(`Found installation version: ${installationVersion}`);
const parsed = parseMajorVersion(installationVersion);
if (parsed !== undefined) {
vsMajorVersion = parsed;
}
}
};
const versionExec = VSWHERE_EXEC.replace('-property installationPath', '-property installationVersion');
yield exec.exec(`"${vswhereToolExe}" ${versionExec}`, [], versionOptions);
const isWindows64Bit = os.arch() === 'x64' || os.arch() === 'arm64';
MSBUILD_ARCH = resolveMSBuildArchitecture(MSBUILD_ARCH_INPUT, isWindows64Bit, vsMajorVersion);
core.info(`Auto-detected msbuild-architecture: ${MSBUILD_ARCH} (os.arch=${os.arch()}, vsMajorVersion=${vsMajorVersion})`);
}
let foundToolPath = ''; let foundToolPath = '';
const options = {}; const options = {};
options.listeners = { options.listeners = {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "setup-msbuild", "name": "setup-msbuild",
"version": "3.0.0", "version": "4.0.0",
"private": true, "private": true,
"description": "Helps set up specific MSBuild tool into PATH for later usage.", "description": "Helps set up specific MSBuild tool into PATH for later usage.",
"main": "lib/main.js", "main": "lib/main.js",
+80 -3
View File
@@ -1,6 +1,7 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as io from '@actions/io' import * as io from '@actions/io'
import {ExecOptions} from '@actions/exec/lib/interfaces' import {ExecOptions} from '@actions/exec/lib/interfaces'
@@ -9,13 +10,56 @@ const IS_WINDOWS = process.platform === 'win32'
const VS_VERSION = core.getInput('vs-version') || 'latest' const VS_VERSION = core.getInput('vs-version') || 'latest'
const VSWHERE_PATH = core.getInput('vswhere-path') const VSWHERE_PATH = core.getInput('vswhere-path')
const ALLOW_PRERELEASE = core.getInput('vs-prerelease') || 'false' const ALLOW_PRERELEASE = core.getInput('vs-prerelease') || 'false'
let MSBUILD_ARCH = core.getInput('msbuild-architecture') || 'x86' const MSBUILD_ARCH_INPUT = core.getInput('msbuild-architecture')
let MSBUILD_ARCH = MSBUILD_ARCH_INPUT
// The minimum Visual Studio/MSBuild major version that ships a native x64 MSBuild.exe
export const MIN_VS_VERSION_FOR_X64 = 17
// Determines the effective MSBuild architecture to use.
// If the user explicitly specified `msbuild-architecture`, that choice is always respected.
// Otherwise (breaking change as of v4), the action auto-detects and prefers x64 when
// running on a 64-bit OS and the resolved VS/MSBuild installation is version 17.0 or later.
// If the VS version can't be determined, or the host isn't 64-bit, it falls back to x86.
export function resolveMSBuildArchitecture(
explicitArch: string,
isWindows64Bit: boolean,
vsMajorVersion: number | undefined
): string {
if (explicitArch) {
return explicitArch
}
if (
isWindows64Bit &&
vsMajorVersion !== undefined &&
vsMajorVersion >= MIN_VS_VERSION_FOR_X64
) {
return 'x64'
}
return 'x86'
}
// Parses the major version number out of a vswhere `installationVersion` string
// such as "17.9.34728.123". Returns undefined if it cannot be parsed.
export function parseMajorVersion(
installationVersion: string
): number | undefined {
const match = installationVersion.trim().match(/^(\d+)/)
if (!match) {
return undefined
}
const major = parseInt(match[1], 10)
return isNaN(major) ? undefined : major
}
// if a specific version of VS is requested // if a specific version of VS is requested
let VSWHERE_EXEC = '-products * -requires Microsoft.Component.MSBuild -property installationPath -latest ' let VSWHERE_EXEC =
'-products * -requires Microsoft.Component.MSBuild -property installationPath -latest '
if (ALLOW_PRERELEASE === 'true') { if (ALLOW_PRERELEASE === 'true') {
VSWHERE_EXEC += ' -prerelease ' VSWHERE_EXEC += ' -prerelease '
} }
if (VS_VERSION !== 'latest') { if (VS_VERSION !== 'latest') {
VSWHERE_EXEC += `-version "${VS_VERSION}" ` VSWHERE_EXEC += `-version "${VS_VERSION}" `
@@ -64,6 +108,39 @@ async function run(): Promise<void> {
core.debug(`Full tool exe: ${vswhereToolExe}`) core.debug(`Full tool exe: ${vswhereToolExe}`)
// if the user did not explicitly specify an architecture, auto-detect the
// preferred architecture based on the host OS and the resolved VS/MSBuild version
if (!MSBUILD_ARCH_INPUT) {
let vsMajorVersion: number | undefined
const versionOptions: ExecOptions = {}
versionOptions.listeners = {
stdout: (data: Buffer) => {
const installationVersion = data.toString().trim()
core.debug(`Found installation version: ${installationVersion}`)
const parsed = parseMajorVersion(installationVersion)
if (parsed !== undefined) {
vsMajorVersion = parsed
}
}
}
const versionExec = VSWHERE_EXEC.replace(
'-property installationPath',
'-property installationVersion'
)
await exec.exec(`"${vswhereToolExe}" ${versionExec}`, [], versionOptions)
const isWindows64Bit = os.arch() === 'x64' || os.arch() === 'arm64'
MSBUILD_ARCH = resolveMSBuildArchitecture(
MSBUILD_ARCH_INPUT,
isWindows64Bit,
vsMajorVersion
)
core.info(
`Auto-detected msbuild-architecture: ${MSBUILD_ARCH} (os.arch=${os.arch()}, vsMajorVersion=${vsMajorVersion})`
)
}
let foundToolPath = '' let foundToolPath = ''
const options: ExecOptions = {} const options: ExecOptions = {}
options.listeners = { options.listeners = {