setup-dotnet/src/installer.ts

269 lines
8.0 KiB
TypeScript
Raw Normal View History

2019-09-09 17:27:23 +00:00
// Load tempDirectory before it gets wiped by tool-cache
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
2020-01-26 06:37:54 +00:00
import hc = require('@actions/http-client');
2019-09-09 17:27:23 +00:00
import {chmodSync} from 'fs';
import * as path from 'path';
import {ExecOptions} from '@actions/exec/lib/interfaces';
2019-09-09 17:27:23 +00:00
import * as semver from 'semver';
const IS_WINDOWS = process.platform === 'win32';
/**
* Represents the inputted version information
*/
export class DotNetVersionInfo {
public inputVersion: string;
2020-04-09 17:38:39 +00:00
private fullversion: string;
private isExactVersionSet: boolean = false;
2019-09-09 17:27:23 +00:00
constructor(version: string) {
this.inputVersion = version;
// Check for exact match
2020-04-09 17:38:39 +00:00
if (semver.valid(semver.clean(version) || '') != null) {
this.fullversion = semver.clean(version) as string;
this.isExactVersionSet = true;
return;
}
//Note: No support for previews when using generic
let parts: string[] = version.split('.');
2020-04-09 17:38:39 +00:00
if (parts.length < 2 || parts.length > 3) this.throwInvalidVersionFormat();
2020-04-09 17:38:39 +00:00
if (parts.length == 3 && parts[2] !== 'x' && parts[2] !== '*') {
this.throwInvalidVersionFormat();
}
let major = this.getVersionNumberOrThrow(parts[0]);
let minor = this.getVersionNumberOrThrow(parts[1]);
this.fullversion = major + '.' + minor;
}
2020-04-09 17:38:39 +00:00
private getVersionNumberOrThrow(input: string): number {
try {
if (!input || input.trim() === '') this.throwInvalidVersionFormat();
let number = Number(input);
2020-04-09 17:38:39 +00:00
if (Number.isNaN(number) || number < 0) this.throwInvalidVersionFormat();
return number;
} catch {
this.throwInvalidVersionFormat();
return -1;
}
}
private throwInvalidVersionFormat() {
2020-04-09 17:38:39 +00:00
throw 'Invalid version format! Supported: 1.2.3, 1.2, 1.2.x, 1.2.*';
}
/**
* If true exacatly one version should be resolved
*/
2020-04-09 17:38:39 +00:00
public isExactVersion(): boolean {
return this.isExactVersionSet;
}
2020-04-09 17:38:39 +00:00
public version(): string {
return this.fullversion;
}
}
export class DotnetCoreInstaller {
constructor(version: string) {
this.version = version;
2019-09-09 17:27:23 +00:00
}
public async installDotnet() {
let output = '';
let resultCode = 0;
2019-09-09 17:27:23 +00:00
let calculatedVersion = await this.resolveVersion(
new DotNetVersionInfo(this.version)
);
var envVariables: {[key: string]: string} = {};
for (let key in process.env) {
if (process.env[key]) {
let value: any = process.env[key];
envVariables[key] = value;
}
2019-09-09 17:27:23 +00:00
}
if (IS_WINDOWS) {
let escapedScript = path
.join(__dirname, '..', 'externals', 'install-dotnet.ps1')
2019-09-09 17:27:23 +00:00
.replace(/'/g, "''");
let command = `& '${escapedScript}'`;
if (calculatedVersion) {
command += ` -Version ${calculatedVersion}`;
}
if (process.env['https_proxy'] != null) {
command += ` -ProxyAddress ${process.env['https_proxy']}`;
}
// This is not currently an option
if (process.env['no_proxy'] != null) {
command += ` -ProxyBypassList ${process.env['no_proxy']}`;
}
2019-09-09 17:27:23 +00:00
// process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
2019-09-09 17:27:23 +00:00
const powershellPath = await io.which('powershell', true);
var options: ExecOptions = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
},
env: envVariables
};
2019-09-09 17:27:23 +00:00
resultCode = await exec.exec(
`"${powershellPath}"`,
[
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
command
],
options
2019-09-09 17:27:23 +00:00
);
} else {
let escapedScript = path
.join(__dirname, '..', 'externals', 'install-dotnet.sh')
.replace(/'/g, "''");
chmodSync(escapedScript, '777');
const scriptPath = await io.which(escapedScript, true);
2019-09-09 17:27:23 +00:00
let scriptArguments: string[] = [];
if (this.version) {
scriptArguments.push('--version', this.version);
}
// process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
resultCode = await exec.exec(`"${scriptPath}"`, scriptArguments, {
2019-09-09 17:27:23 +00:00
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
},
env: envVariables
2019-09-09 17:27:23 +00:00
});
}
if (process.env['DOTNET_INSTALL_DIR']) {
core.addPath(process.env['DOTNET_INSTALL_DIR']);
} else {
if (IS_WINDOWS) {
// This is the default set in install-dotnet.ps1
core.addPath(
path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
);
core.exportVariable(
'DOTNET_ROOT',
path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
);
} else {
// This is the default set in install-dotnet.sh
core.addPath(path.join(process.env['HOME'] + '', '.dotnet'));
}
2019-09-09 17:27:23 +00:00
}
console.log(process.env['PATH']);
2019-09-09 17:27:23 +00:00
if (resultCode != 0) {
throw `Failed to install dotnet ${resultCode}. ${output}`;
2019-09-09 17:27:23 +00:00
}
}
// versionInfo - versionInfo of the SDK/Runtime
async resolveVersion(versionInfo: DotNetVersionInfo): Promise<string> {
if (versionInfo.isExactVersion()) {
return versionInfo.version();
2019-09-09 17:27:23 +00:00
}
2020-01-26 06:37:54 +00:00
const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
allowRetries: true,
maxRetries: 3
});
2019-11-08 16:15:28 +00:00
const releasesJsonUrl: string = await this.getReleasesJsonUrl(
2020-01-26 06:37:54 +00:00
httpClient,
versionInfo.version().split('.')
2019-11-08 16:15:28 +00:00
);
2020-01-26 06:37:54 +00:00
const releasesResponse = await httpClient.getJson<any>(releasesJsonUrl);
const releasesResult = releasesResponse.result || {};
let releasesInfo: any[] = releasesResult['releases'];
2019-09-09 17:27:23 +00:00
releasesInfo = releasesInfo.filter((releaseInfo: any) => {
return (
2020-04-09 17:38:39 +00:00
semver.satisfies(
releaseInfo['sdk']['version'],
versionInfo.version()
) ||
semver.satisfies(
releaseInfo['sdk']['version-display'],
versionInfo.version()
)
2019-09-09 17:27:23 +00:00
);
});
// Exclude versions that are newer than the latest if using not exact
let latestSdk: string = releasesResult['latest-sdk'];
releasesInfo = releasesInfo.filter((releaseInfo: any) =>
semver.lte(releaseInfo['sdk']['version'], latestSdk)
);
// Sort for latest version
2020-04-09 17:38:39 +00:00
releasesInfo = releasesInfo.sort((a, b) =>
semver.rcompare(a['sdk']['version'], b['sdk']['version'])
);
if (releasesInfo.length == 0) {
throw `Could not find dotnet core version. Please ensure that specified version ${versionInfo.inputVersion} is valid.`;
2019-09-09 17:27:23 +00:00
}
let release = releasesInfo[0];
return release['sdk']['version'];
2019-09-09 17:27:23 +00:00
}
2019-11-08 16:15:28 +00:00
private async getReleasesJsonUrl(
2020-01-26 06:37:54 +00:00
httpClient: hc.HttpClient,
2019-11-08 16:15:28 +00:00
versionParts: string[]
): Promise<string> {
2020-01-26 06:37:54 +00:00
const response = await httpClient.getJson<any>(DotNetCoreIndexUrl);
const result = response.result || {};
let releasesInfo: any[] = result['releases-index'];
2019-11-08 16:15:28 +00:00
releasesInfo = releasesInfo.filter((info: any) => {
// channel-version is the first 2 elements of the version (e.g. 2.1), filter out versions that don't match 2.1.x.
const sdkParts: string[] = info['channel-version'].split('.');
if (versionParts.length >= 2 && versionParts[1] != 'x') {
return versionParts[0] == sdkParts[0] && versionParts[1] == sdkParts[1];
}
return versionParts[0] == sdkParts[0];
});
if (releasesInfo.length === 0) {
throw `Could not find info for version ${versionParts.join(
'.'
)} at ${DotNetCoreIndexUrl}`;
}
return releasesInfo[0]['releases.json'];
2019-09-09 17:27:23 +00:00
}
private version: string;
2019-09-09 17:27:23 +00:00
}
2019-11-08 16:15:28 +00:00
const DotNetCoreIndexUrl: string =
'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json';