diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3ec658b21..92b2167e5 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -320,3 +320,33 @@ jobs: - name: Verify dotnet shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$" -CheckNugetConfig + + test-sequential-version-installation: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + lower-version: ['2.2.402', '3.0.103', '3.1.426', '6.0.408', '7.0.100'] + higher-version: ['6.0.408', '7.0.203'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + # Install one version, use it for something, then switch to next version + - name: Setup dotnet (lower version) + uses: ./ + with: + dotnet-version: ${{ matrix.lower-version }} + - name: Verify dotnet (lower version) + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.lower-version }}$" + - name: Setup dotnet (higher version) + uses: ./ + with: + dotnet-version: ${{ matrix.higher-version }} + - name: Verify dotnet (higher version) + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.lower-version }}$", "^${{ matrix.higher-version }}$" diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index b26d91562..75cad47d2 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -79,7 +79,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); const scriptArguments = ( - getExecOutputSpy.mock.calls[0][1] as string[] + getExecOutputSpy.mock.calls[1][1] as string[] ).join(' '); const expectedArgument = IS_WINDOWS ? `-Version ${inputVersion}` @@ -130,6 +130,37 @@ describe('installer tests', () => { ); }); + it('should call install script 2 times: for the runtime and for the sdk', async () => { + const inputVersion = '6.0.300'; + const inputQuality = '' as QualityOptions; + + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); + + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + expect(getExecOutputSpy.mock.calls.length).toEqual(2); + + expect(getExecOutputSpy.mock.calls[0][1]?.join(' ')).toContain( + IS_WINDOWS ? '-Channel LTS' : '--channel LTS' + ); + + expect(getExecOutputSpy.mock.calls[0][1]?.join(' ')).toContain( + IS_WINDOWS ? '-Runtime dotnet' : '--runtime dotnet' + ); + + expect(getExecOutputSpy.mock.calls[1][1]?.join(' ')).toContain( + IS_WINDOWS ? `-Version ${inputVersion}` : `--version ${inputVersion}` + ); + }); + each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test( `should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`, async inputVersion => { @@ -152,7 +183,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); const scriptArguments = ( - getExecOutputSpy.mock.calls[0][1] as string[] + getExecOutputSpy.mock.calls[1][1] as string[] ).join(' '); const expectedArgument = IS_WINDOWS ? `-Quality ${inputQuality}` @@ -184,7 +215,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); const scriptArguments = ( - getExecOutputSpy.mock.calls[0][1] as string[] + getExecOutputSpy.mock.calls[1][1] as string[] ).join(' '); const expectedArgument = IS_WINDOWS ? `-Channel 6.0` @@ -267,7 +298,7 @@ describe('installer tests', () => { }); describe('DotnetVersionResolver tests', () => { - describe('createDotNetVersion() tests', () => { + describe('createDotnetVersion() tests', () => { each([ '3.1', '3.x', @@ -283,7 +314,7 @@ describe('installer tests', () => { version ); const versionObject = - await dotnetVersionResolver.createDotNetVersion(); + await dotnetVersionResolver.createDotnetVersion(); expect(!!versionObject.value).toBe(true); } @@ -322,7 +353,7 @@ describe('installer tests', () => { ); await expect( - async () => await dotnetVersionResolver.createDotNetVersion() + async () => await dotnetVersionResolver.createDotnetVersion() ).rejects.toThrow(); } ); @@ -334,7 +365,7 @@ describe('installer tests', () => { version ); const versionObject = - await dotnetVersionResolver.createDotNetVersion(); + await dotnetVersionResolver.createDotnetVersion(); expect(versionObject.type.toLowerCase().includes('channel')).toBe( true @@ -349,7 +380,7 @@ describe('installer tests', () => { version ); const versionObject = - await dotnetVersionResolver.createDotNetVersion(); + await dotnetVersionResolver.createDotnetVersion(); expect(versionObject.type.toLowerCase().includes('channel')).toBe( true @@ -365,7 +396,7 @@ describe('installer tests', () => { version ); const versionObject = - await dotnetVersionResolver.createDotNetVersion(); + await dotnetVersionResolver.createDotnetVersion(); expect(versionObject.type.toLowerCase().includes('version')).toBe( true @@ -381,7 +412,7 @@ describe('installer tests', () => { version ); const versionObject = - await dotnetVersionResolver.createDotNetVersion(); + await dotnetVersionResolver.createDotnetVersion(); const windowsRegEx = new RegExp(/^-(Version|Channel)/); const nonWindowsRegEx = new RegExp(/^--(version|channel)/); diff --git a/dist/index.js b/dist/index.js index 8e20d7f57..ab1c28de6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -229,9 +229,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; -var _a; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.DotnetCoreInstaller = exports.DotnetVersionResolver = void 0; +exports.DotnetCoreInstaller = exports.DotnetInstallDir = exports.DotnetInstallScript = exports.DotnetVersionResolver = void 0; // Load tempDirectory before it gets wiped by tool-cache const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); @@ -279,7 +278,7 @@ class DotnetVersionResolver { isNumericTag(versionTag) { return /^\d+$/.test(versionTag); } - createDotNetVersion() { + createDotnetVersion() { return __awaiter(this, void 0, void 0, function* () { yield this.resolveVersionInput(); if (!this.resolvedArgument.type) { @@ -314,39 +313,21 @@ class DotnetVersionResolver { } exports.DotnetVersionResolver = DotnetVersionResolver; DotnetVersionResolver.DotNetCoreIndexUrl = 'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json'; -class DotnetCoreInstaller { - constructor(version, quality) { - this.version = version; - this.quality = quality; - } - static convertInstallPathToAbsolute(installDir) { - let transformedPath; - if (path_1.default.isAbsolute(installDir)) { - transformedPath = installDir; - } - else { - transformedPath = installDir.startsWith('~') - ? path_1.default.join(os_1.default.homedir(), installDir.slice(1)) - : (transformedPath = path_1.default.join(process.cwd(), installDir)); - } - return path_1.default.normalize(transformedPath); - } - static addToPath() { - core.addPath(process.env['DOTNET_INSTALL_DIR']); - core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); - } - setQuality(dotnetVersion, scriptArguments) { - const option = utils_1.IS_WINDOWS ? '-Quality' : '--quality'; - if (dotnetVersion.qualityFlag) { - scriptArguments.push(option, this.quality); - } - else { - core.warning(`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.`); - } +class DotnetInstallScript { + constructor() { + this.scriptName = utils_1.IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; + this.scriptArguments = []; + this.scriptPath = ''; + this.escapedScript = path_1.default + .join(__dirname, '..', 'externals', this.scriptName) + .replace(/'/g, "''"); + this.scriptReady = utils_1.IS_WINDOWS + ? this.setupScriptPowershell() + : this.setupScriptBash(); } - installDotnet() { + setupScriptPowershell() { return __awaiter(this, void 0, void 0, function* () { - const windowsDefaultOptions = [ + this.scriptArguments = [ '-NoLogo', '-Sta', '-NoProfile', @@ -355,52 +336,119 @@ class DotnetCoreInstaller { 'Unrestricted', '-Command' ]; - const scriptName = utils_1.IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; - const escapedScript = path_1.default - .join(__dirname, '..', 'externals', scriptName) - .replace(/'/g, "''"); - let scriptArguments; - let scriptPath = ''; - const versionResolver = new DotnetVersionResolver(this.version); - const dotnetVersion = yield versionResolver.createDotNetVersion(); - if (utils_1.IS_WINDOWS) { - scriptArguments = ['&', `'${escapedScript}'`]; - if (dotnetVersion.type) { - scriptArguments.push(dotnetVersion.type, dotnetVersion.value); - } - if (this.quality) { - this.setQuality(dotnetVersion, scriptArguments); - } - if (process.env['https_proxy'] != null) { - scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`); - } - // This is not currently an option - if (process.env['no_proxy'] != null) { - scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); - } - scriptPath = - (yield io.which('pwsh', false)) || (yield io.which('powershell', true)); - scriptArguments = windowsDefaultOptions.concat(scriptArguments); + this.scriptArguments.push('&', `'${this.escapedScript}'`); + if (process.env['https_proxy'] != null) { + this.scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`); } - else { - (0, fs_1.chmodSync)(escapedScript, '777'); - scriptPath = yield io.which(escapedScript, true); - scriptArguments = []; - if (dotnetVersion.type) { - scriptArguments.push(dotnetVersion.type, dotnetVersion.value); - } - if (this.quality) { - this.setQuality(dotnetVersion, scriptArguments); - } + // This is not currently an option + if (process.env['no_proxy'] != null) { + this.scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); } - // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used + this.scriptPath = + (yield io.which('pwsh', false)) || (yield io.which('powershell', true)); + }); + } + setupScriptBash() { + return __awaiter(this, void 0, void 0, function* () { + (0, fs_1.chmodSync)(this.escapedScript, '777'); + this.scriptPath = yield io.which(this.escapedScript, true); + }); + } + useArguments(...args) { + this.scriptArguments.push(...args); + return this; + } + useVersion(dotnetVersion, quality) { + if (dotnetVersion.type) { + this.useArguments(dotnetVersion.type, dotnetVersion.value); + } + if (quality && !dotnetVersion.qualityFlag) { + core.warning(`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${dotnetVersion.value}. 'dotnet-quality' input is ignored.`); + return this; + } + if (quality) { + this.useArguments(utils_1.IS_WINDOWS ? '-Quality' : '--quality', quality); + } + return this; + } + execute() { + return __awaiter(this, void 0, void 0, function* () { const getExecOutputOptions = { ignoreReturnCode: true, env: process.env }; - const { exitCode, stderr } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, getExecOutputOptions); - if (exitCode) { - throw new Error(`Failed to install dotnet, exit code: ${exitCode}. ${stderr}`); + yield this.scriptReady; + return exec.getExecOutput(`"${this.scriptPath}"`, this.scriptArguments, getExecOutputOptions); + }); + } +} +exports.DotnetInstallScript = DotnetInstallScript; +class DotnetInstallDir { + static convertInstallPathToAbsolute(installDir) { + if (path_1.default.isAbsolute(installDir)) + return path_1.default.normalize(installDir); + const transformedPath = installDir.startsWith('~') + ? path_1.default.join(os_1.default.homedir(), installDir.slice(1)) + : path_1.default.join(process.cwd(), installDir); + return path_1.default.normalize(transformedPath); + } + static addToPath() { + core.addPath(process.env['DOTNET_INSTALL_DIR']); + core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); + } + static initialize() { + process.env['DOTNET_INSTALL_DIR'] = DotnetInstallDir.path; + } +} +exports.DotnetInstallDir = DotnetInstallDir; +DotnetInstallDir.default = { + linux: '/usr/share/dotnet', + mac: path_1.default.join(process.env['HOME'] + '', '.dotnet'), + windows: path_1.default.join(process.env['PROGRAMFILES'] + '', 'dotnet') +}; +DotnetInstallDir.path = process.env['DOTNET_INSTALL_DIR'] + ? DotnetInstallDir.convertInstallPathToAbsolute(process.env['DOTNET_INSTALL_DIR']) + : DotnetInstallDir.default[(0, utils_1.getPlatform)()]; +class DotnetCoreInstaller { + constructor(version, quality) { + this.version = version; + this.quality = quality; + } + installDotnet() { + return __awaiter(this, void 0, void 0, function* () { + const versionResolver = new DotnetVersionResolver(this.version); + const dotnetVersion = yield versionResolver.createDotnetVersion(); + /** + * Install dotnet runitme first in order to get + * the latest stable version of dotnet CLI + */ + const runtimeInstallScript = new DotnetInstallScript() + // If dotnet CLI is already installed - avoid overwriting it + .useArguments(utils_1.IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files') + // Install only runtime + CLI + .useArguments(utils_1.IS_WINDOWS ? '-Runtime' : '--runtime', 'dotnet') + // Use latest stable version + .useArguments(utils_1.IS_WINDOWS ? '-Channel' : '--channel', 'LTS'); + const runtimeInstall = yield runtimeInstallScript.execute(); + if (runtimeInstall.exitCode) { + /** + * dotnetInstallScript will install CLI and runtime if previous script haven't succeded, + * so at this point it's too early to throw an error + */ + core.warning(`Failed to install dotnet runtime + cli, exit code: ${runtimeInstall.exitCode}. ${runtimeInstall.stderr}`); + } + /** + * Install dotnet over the latest version of + * dotnet CLI + */ + const dotnetInstallScript = new DotnetInstallScript() + // Don't overwrite CLI because it should be already installed + .useArguments(utils_1.IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files') + // Use version provided by user + .useVersion(dotnetVersion, this.quality); + const dotnetInstall = yield dotnetInstallScript.execute(); + if (dotnetInstall.exitCode) { + throw new Error(`Failed to install dotnet, exit code: ${dotnetInstall.exitCode}. ${dotnetInstall.stderr}`); } return this.outputDotnetVersion(dotnetVersion.value); }); @@ -417,26 +465,9 @@ class DotnetCoreInstaller { } } exports.DotnetCoreInstaller = DotnetCoreInstaller; -_a = DotnetCoreInstaller; +DotnetCoreInstaller.addToPath = DotnetInstallDir.addToPath; (() => { - const installationDirectoryWindows = path_1.default.join(process.env['PROGRAMFILES'] + '', 'dotnet'); - const installationDirectoryLinux = '/usr/share/dotnet'; - const installationDirectoryMac = path_1.default.join(process.env['HOME'] + '', '.dotnet'); - const dotnetInstallDir = process.env['DOTNET_INSTALL_DIR']; - if (dotnetInstallDir) { - process.env['DOTNET_INSTALL_DIR'] = - _a.convertInstallPathToAbsolute(dotnetInstallDir); - } - else { - if (utils_1.IS_WINDOWS) { - process.env['DOTNET_INSTALL_DIR'] = installationDirectoryWindows; - } - else { - process.env['DOTNET_INSTALL_DIR'] = utils_1.IS_LINUX - ? installationDirectoryLinux - : installationDirectoryMac; - } - } + DotnetInstallDir.initialize(); })(); @@ -591,9 +622,17 @@ run(); "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.IS_LINUX = exports.IS_WINDOWS = void 0; +exports.getPlatform = exports.IS_LINUX = exports.IS_WINDOWS = void 0; exports.IS_WINDOWS = process.platform === 'win32'; exports.IS_LINUX = process.platform === 'linux'; +const getPlatform = () => { + if (exports.IS_WINDOWS) + return 'windows'; + if (exports.IS_LINUX) + return 'linux'; + return 'mac'; +}; +exports.getPlatform = getPlatform; /***/ }), diff --git a/src/installer.ts b/src/installer.ts index ef9e99304..44610435f 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -8,7 +8,7 @@ import {readdir} from 'fs/promises'; import path from 'path'; import os from 'os'; import semver from 'semver'; -import {IS_LINUX, IS_WINDOWS} from './utils'; +import {IS_WINDOWS, getPlatform} from './utils'; import {QualityOptions} from './setup-dotnet'; export interface DotnetVersion { @@ -61,7 +61,7 @@ export class DotnetVersionResolver { return /^\d+$/.test(versionTag); } - public async createDotNetVersion(): Promise<{ + public async createDotnetVersion(): Promise<{ type: string; value: string; qualityFlag: boolean; @@ -110,74 +110,25 @@ export class DotnetVersionResolver { 'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json'; } -export class DotnetCoreInstaller { - private version: string; - private quality: QualityOptions; - - static { - const installationDirectoryWindows = path.join( - process.env['PROGRAMFILES'] + '', - 'dotnet' - ); - const installationDirectoryLinux = '/usr/share/dotnet'; - const installationDirectoryMac = path.join( - process.env['HOME'] + '', - '.dotnet' - ); - const dotnetInstallDir: string | undefined = - process.env['DOTNET_INSTALL_DIR']; - if (dotnetInstallDir) { - process.env['DOTNET_INSTALL_DIR'] = - this.convertInstallPathToAbsolute(dotnetInstallDir); - } else { - if (IS_WINDOWS) { - process.env['DOTNET_INSTALL_DIR'] = installationDirectoryWindows; - } else { - process.env['DOTNET_INSTALL_DIR'] = IS_LINUX - ? installationDirectoryLinux - : installationDirectoryMac; - } - } - } - - constructor(version: string, quality: QualityOptions) { - this.version = version; - this.quality = quality; - } +export class DotnetInstallScript { + private scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; + private escapedScript: string; + private scriptArguments: string[] = []; + private scriptPath = ''; + private scriptReady: Promise; - private static convertInstallPathToAbsolute(installDir: string): string { - let transformedPath; - if (path.isAbsolute(installDir)) { - transformedPath = installDir; - } else { - transformedPath = installDir.startsWith('~') - ? path.join(os.homedir(), installDir.slice(1)) - : (transformedPath = path.join(process.cwd(), installDir)); - } - return path.normalize(transformedPath); - } + constructor() { + this.escapedScript = path + .join(__dirname, '..', 'externals', this.scriptName) + .replace(/'/g, "''"); - static addToPath() { - core.addPath(process.env['DOTNET_INSTALL_DIR']!); - core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); + this.scriptReady = IS_WINDOWS + ? this.setupScriptPowershell() + : this.setupScriptBash(); } - private setQuality( - dotnetVersion: DotnetVersion, - scriptArguments: string[] - ): void { - const option = IS_WINDOWS ? '-Quality' : '--quality'; - if (dotnetVersion.qualityFlag) { - scriptArguments.push(option, this.quality); - } else { - core.warning( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` - ); - } - } - - public async installDotnet(): Promise { - const windowsDefaultOptions = [ + private async setupScriptPowershell() { + this.scriptArguments = [ '-NoLogo', '-Sta', '-NoProfile', @@ -186,64 +137,155 @@ export class DotnetCoreInstaller { 'Unrestricted', '-Command' ]; - const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; - const escapedScript = path - .join(__dirname, '..', 'externals', scriptName) - .replace(/'/g, "''"); - let scriptArguments: string[]; - let scriptPath = ''; - const versionResolver = new DotnetVersionResolver(this.version); - const dotnetVersion = await versionResolver.createDotNetVersion(); + this.scriptArguments.push('&', `'${this.escapedScript}'`); - if (IS_WINDOWS) { - scriptArguments = ['&', `'${escapedScript}'`]; + if (process.env['https_proxy'] != null) { + this.scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`); + } + // This is not currently an option + if (process.env['no_proxy'] != null) { + this.scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); + } - if (dotnetVersion.type) { - scriptArguments.push(dotnetVersion.type, dotnetVersion.value); - } + this.scriptPath = + (await io.which('pwsh', false)) || (await io.which('powershell', true)); + } - if (this.quality) { - this.setQuality(dotnetVersion, scriptArguments); - } + private async setupScriptBash() { + chmodSync(this.escapedScript, '777'); + this.scriptPath = await io.which(this.escapedScript, true); + } - if (process.env['https_proxy'] != null) { - scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`); - } - // This is not currently an option - if (process.env['no_proxy'] != null) { - scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); - } + public useArguments(...args: string[]) { + this.scriptArguments.push(...args); + return this; + } - scriptPath = - (await io.which('pwsh', false)) || (await io.which('powershell', true)); - scriptArguments = windowsDefaultOptions.concat(scriptArguments); - } else { - chmodSync(escapedScript, '777'); - scriptPath = await io.which(escapedScript, true); - scriptArguments = []; + public useVersion(dotnetVersion: DotnetVersion, quality?: QualityOptions) { + if (dotnetVersion.type) { + this.useArguments(dotnetVersion.type, dotnetVersion.value); + } - if (dotnetVersion.type) { - scriptArguments.push(dotnetVersion.type, dotnetVersion.value); - } + if (quality && !dotnetVersion.qualityFlag) { + core.warning( + `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${dotnetVersion.value}. 'dotnet-quality' input is ignored.` + ); + return this; + } - if (this.quality) { - this.setQuality(dotnetVersion, scriptArguments); - } + if (quality) { + this.useArguments(IS_WINDOWS ? '-Quality' : '--quality', quality); } - // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used + + return this; + } + + public async execute() { const getExecOutputOptions = { ignoreReturnCode: true, env: process.env as {string: string} }; - const {exitCode, stderr} = await exec.getExecOutput( - `"${scriptPath}"`, - scriptArguments, + + await this.scriptReady; + + return exec.getExecOutput( + `"${this.scriptPath}"`, + this.scriptArguments, getExecOutputOptions ); - if (exitCode) { + } +} + +export abstract class DotnetInstallDir { + private static readonly default = { + linux: '/usr/share/dotnet', + mac: path.join(process.env['HOME'] + '', '.dotnet'), + windows: path.join(process.env['PROGRAMFILES'] + '', 'dotnet') + }; + + public static readonly path = process.env['DOTNET_INSTALL_DIR'] + ? DotnetInstallDir.convertInstallPathToAbsolute( + process.env['DOTNET_INSTALL_DIR'] + ) + : DotnetInstallDir.default[getPlatform()]; + + private static convertInstallPathToAbsolute(installDir: string): string { + if (path.isAbsolute(installDir)) return path.normalize(installDir); + + const transformedPath = installDir.startsWith('~') + ? path.join(os.homedir(), installDir.slice(1)) + : path.join(process.cwd(), installDir); + + return path.normalize(transformedPath); + } + + public static addToPath() { + core.addPath(process.env['DOTNET_INSTALL_DIR']!); + core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']); + } + + public static initialize() { + process.env['DOTNET_INSTALL_DIR'] = DotnetInstallDir.path; + } +} + +export class DotnetCoreInstaller { + static addToPath = DotnetInstallDir.addToPath; + + static { + DotnetInstallDir.initialize(); + } + + constructor(private version: string, private quality: QualityOptions) {} + + public async installDotnet(): Promise { + const versionResolver = new DotnetVersionResolver(this.version); + const dotnetVersion = await versionResolver.createDotnetVersion(); + + /** + * Install dotnet runitme first in order to get + * the latest stable version of dotnet CLI + */ + const runtimeInstallScript = new DotnetInstallScript() + // If dotnet CLI is already installed - avoid overwriting it + .useArguments( + IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files' + ) + // Install only runtime + CLI + .useArguments(IS_WINDOWS ? '-Runtime' : '--runtime', 'dotnet') + // Use latest stable version + .useArguments(IS_WINDOWS ? '-Channel' : '--channel', 'LTS'); + + const runtimeInstall = await runtimeInstallScript.execute(); + + if (runtimeInstall.exitCode) { + /** + * dotnetInstallScript will install CLI and runtime if previous script haven't succeded, + * so at this point it's too early to throw an error + */ + core.warning( + `Failed to install dotnet runtime + cli, exit code: ${runtimeInstall.exitCode}. ${runtimeInstall.stderr}` + ); + } + + /** + * Install dotnet over the latest version of + * dotnet CLI + */ + const dotnetInstallScript = new DotnetInstallScript() + // Don't overwrite CLI because it should be already installed + .useArguments( + IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files' + ) + // Use version provided by user + .useVersion(dotnetVersion, this.quality); + + const dotnetInstall = await dotnetInstallScript.execute(); + + if (dotnetInstall.exitCode) { throw new Error( - `Failed to install dotnet, exit code: ${exitCode}. ${stderr}` + `Failed to install dotnet, exit code: ${dotnetInstall.exitCode}. ${dotnetInstall.stderr}` ); } diff --git a/src/utils.ts b/src/utils.ts index 77886ce0e..d671d7042 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,2 +1,7 @@ export const IS_WINDOWS = process.platform === 'win32'; export const IS_LINUX = process.platform === 'linux'; +export const getPlatform = (): 'windows' | 'linux' | 'mac' => { + if (IS_WINDOWS) return 'windows'; + if (IS_LINUX) return 'linux'; + return 'mac'; +};