gulp-msbuild
Version:
msbuild plugin for gulp. Inspired by grunt-msbuild.
169 lines (136 loc) • 6.21 kB
text/typescript
import { execSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import { MSBUILD_VERSIONS } from './constants';
import { MSBuildOptions } from './msbuild-options';
import { ArchitectureService } from '../utilities/ArchitectureService';
export class MSBuildFinder {
private buildIs64Bit: boolean;
private version: string;
private windir: string;
public constructor(options: MSBuildOptions)
{
this.buildIs64Bit = ArchitectureService.Architecture === 'x64';
this.version = options.toolsVersion ?? 'auto'
this.windir = options.windir ?? 'C:\Windows';
}
public findVersion(): (string | null)[] | null {
if (this.version == 'auto')
return this.getLatestAvailableVersion();
const parsedNumber = parseFloat(this.version);
if (isNaN(parsedNumber))
throw new Error('version unknown');
// Pre-v12: MSBuild ships with the .NET Framework and does not require vswhere
if (parsedNumber < 12) {
const frameworkPath = this.concatPreV12(this.version);
if (fs.existsSync(frameworkPath)) {
return [frameworkPath, this.version];
}
return null;
}
const vswherePath = path.join(process.env['ProgramFiles(x86)'] || '', 'Microsoft Visual Studio', 'Installer', 'vswhere.exe');
if (!fs.existsSync(vswherePath)) {
console.error('vswhere.exe not found');
return null;
}
try {
const installationsBuffer = execSync(`"${vswherePath}" -products * -requires Microsoft.Component.MSBuild -format json`);
const installations = JSON.parse(installationsBuffer.toString());
const major = (Number)(this.version.split('.')[0]);
if (major >= 16) {
// For v16+, match by the major part of installationVersion rather than
// relying on the installation path containing a particular substring.
// This correctly handles VS 18 (and any future VS version) regardless
// of the year/name used in the installation directory.
const matchingInstallation = installations.find((installation: any) => {
const installMajor = parseInt(
(installation.installationVersion || '').split('.')[0] || '0'
);
return installMajor === major;
});
if (!matchingInstallation) {
return [null, this.version];
}
const msbuildPath = path.join(
matchingInstallation.installationPath,
'MSBuild', 'Current', 'Bin',
this.getx64_dir(),
'MSBuild.exe'
);
return [fs.existsSync(msbuildPath) ? msbuildPath : null, this.version];
}
// v12–v15: use path-based detection via vswhere installation paths
const installationPaths = installations.map(
(installation: any) => this.concatCorrectPath(installation.installationPath)
) as string[];
const installedPath = this.getInstalledVersion(installationPaths);
return [
installedPath,
this.version
]
} catch (error: any) {
console.error(`Error executing vswhere: ${error.message}`);
return null;
}
}
concatCorrectPath(installationPath: string): string {
const major = (Number)(this.version.split('.')[0]);
if (major >= 16) {
return this.concatV16AndAbove(installationPath, this.version);
} else if (major >= 12 && major <= 15) {
return this.concatV15AndBelow(installationPath, this.version);
}
return this.concatPreV12(this.version);
}
concatPreV12(version: string): string {
let toolVersion = MSBUILD_VERSIONS[version];
const framework = this.buildIs64Bit ? 'Framework64' : 'Framework';
return path.join(this.windir, 'Microsoft.Net', framework, toolVersion, 'MSBuild.exe');
}
concatV15AndBelow(installationPath: string, version: string): string {
return path.join(installationPath, 'MSBuild', version, 'Bin', this.getx64_dir(), 'MSBuild.exe');
}
concatV16AndAbove(installationPath: string, version: string): string {
if (!installationPath.includes(MSBUILD_VERSIONS[version]))
return '';
return path.join(installationPath, 'MSBuild', 'Current', 'Bin', this.getx64_dir(), 'MSBuild.exe');
}
getInstalledVersion(installationPaths: string[]): string | null {
let installedPath: string | null = null;
for (let i = 0; i < installationPaths.length; i++) {
const installationPath = installationPaths[i];
if (installationPath == '')
continue;
if (fs.existsSync(installationPath)) {
installedPath = installationPath;
break;
}
}
return installedPath;
}
getLatestAvailableVersion(): (string | null)[] | null {
let installationPaths: string[] = [];
let versions: string[] = [];
for(const key in MSBUILD_VERSIONS)
{
this.version = key;
const installationPath = this.findVersion();
if (installationPath != null)
{
if (installationPath[0] != null) {
installationPaths.push(installationPath[0]);
versions.push(key)
}
}
}
if (installationPaths.length === 0)
return null;
return [
installationPaths[installationPaths.length -1],
versions[versions.length -1]
];
}
getx64_dir() {
return this.buildIs64Bit ? 'amd64' : '';
}
}