diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index eb9241f..bf98a7c 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -237,6 +237,60 @@ jobs: $version = & dotnet --version Write-Host "Installed version: $version" if (-not ($version.Contains("preview") -or $version.Contains("rc"))) { throw "Unexpected version" } + + test-dotnet-version-output-during-single-version-installation: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + + - name: Setup dotnet 6.0.401 + uses: ./ + id: step1 + with: + dotnet-version: "6.0.401" + + - name: Verify value of the dotnet-version output + shell: pwsh + run: | + $version = & dotnet --version + Write-Host "Installed version: $version" + if (-not ($version -eq '${{steps.step1.outputs.dotnet-version}}')) { throw "Unexpected output value" } + + test-dotnet-version-output-during-multiple-version-installation: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + + - name: Setup dotnet 6.0.401, 5.0.408, 7.0.100-rc.1.22431.12 + uses: ./ + id: step2 + with: + dotnet-version: | + 7.0.100-rc.1.22431.12 + 6.0.401 + 5.0.408 + + - name: Verify value of the dotnet-version output + shell: pwsh + run: | + $version = "7.0.100-rc.1.22431.12" + if (-not ($version -eq '${{steps.step2.outputs.dotnet-version}}')) { throw "Unexpected output value" } test-proxy: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 0870974..c802711 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,54 @@ steps: ``` > **Note**: It's the only way to push a package to nuget.org feed for macOS/Linux machines due to API key config store limitations. +# Outputs and environment variables + +## Outputs + +### `dotnet-version` + +Using the **dotnet-version** output it's possible to get the installed by the action .NET SDK version. + +**Single version installation** + +In case of a single version installation, the `dotnet-version` output contains the version that is installed by the action. + +```yaml + - uses: actions/setup-dotnet@v3 + id: cp310 + with: + dotnet-version: 3.1.422 + - run: echo '${{ steps.cp310.outputs.dotnet-version }}' # outputs 3.1.422 +``` + +**Multiple version installation** + +In case of a multiple version installation, the `dotnet-version` output contains the latest version that is installed by the action. + +```yaml + - uses: actions/setup-dotnet@v3 + id: cp310 + with: + dotnet-version: | + 3.1.422 + 5.0.408 + - run: echo '${{ steps.cp310.outputs.dotnet-version }}' # outputs 5.0.408 +``` +**Installation from global.json** + +When the `dotnet-version` input is used along with the `global-json-file` input, the `dotnet-version` output contains the version resolved from the `global.json`. + +```yaml + - uses: actions/setup-dotnet@v3 + id: cp310 + with: + dotnet-version: | + 3.1.422 + 5.0.408 + global-json-file: "./global.json" # contains version 2.2.207 + - run: echo '${{ steps.cp310.outputs.dotnet-version }}' # outputs 2.2.207 +``` + ## Environment variables Some environment variables may be necessary for your particular case or to improve logging. Some examples are listed below, but the full list with complete details can be found here: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 9d94eca..79a90c3 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -107,6 +107,15 @@ describe('DotnetCoreInstaller tests', () => { expect(process.env.PATH?.startsWith(toolDir)).toBe(true); }, 600000); //This needs some time to download on "slower" internet connections + it('Returns string with installed SDK version', async () => { + const version = '3.1.120'; + let installedVersion: string; + + installedVersion = await getDotnet(version); + + expect(installedVersion).toBe('3.1.120'); + }, 600000); + it('Throws if no location contains correct dotnet version', async () => { await expect(async () => { await getDotnet('1000.0.0'); @@ -267,11 +276,15 @@ function normalizeFileContents(contents: string): string { .replace(new RegExp('\r', 'g'), '\n'); } -async function getDotnet(version: string, quality: string = ''): Promise { +async function getDotnet( + version: string, + quality: string = '' +): Promise { const dotnetInstaller = new installer.DotnetCoreInstaller( version, quality as QualityOptions ); - await dotnetInstaller.installDotnet(); + const installedVersion = await dotnetInstaller.installDotnet(); installer.DotnetCoreInstaller.addToPath(); + return installedVersion; } diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 4cd84b6..1b2e3cf 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -1,4 +1,5 @@ import * as io from '@actions/io'; +import * as core from '@actions/core'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -20,6 +21,12 @@ if (IS_WINDOWS) { const tempDir = path.join(__dirname, 'runner', 'temp2'); describe('setup-dotnet tests', () => { + let getInputSpy = jest.spyOn(core, 'getInput'); + let getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); + let setOutputSpy = jest.spyOn(core, 'setOutput'); + + let inputs = {} as any; + beforeAll(async () => { process.env.RUNNER_TOOL_CACHE = toolDir; process.env.DOTNET_INSTALL_DIR = toolDir; @@ -59,4 +66,33 @@ describe('setup-dotnet tests', () => { expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); } }, 400000); + + it("Sets output with the latest installed by action version if global.json file isn't specified", async () => { + inputs['dotnet-version'] = ['3.1.201', '6.0.401']; + + getMultilineInputSpy.mockImplementation(input => inputs[input]); + + await setup.run(); + + expect(setOutputSpy).toBeCalledWith('dotnet-version', '6.0.401'); + }, 400000); + + it("Sets output with the version specified in global.json, if it's present", async () => { + const globalJsonPath = path.join(process.cwd(), 'global.json'); + const jsonContents = `{${os.EOL}"sdk": {${os.EOL}"version": "3.0.103"${os.EOL}}${os.EOL}}`; + if (!fs.existsSync(globalJsonPath)) { + fs.writeFileSync(globalJsonPath, jsonContents); + } + + inputs['dotnet-version'] = ['3.1.201', '6.0.401']; + inputs['global-json-file'] = './global.json'; + + getMultilineInputSpy.mockImplementation(input => inputs[input]); + + getInputSpy.mockImplementation(input => inputs[input]); + + await setup.run(); + + expect(setOutputSpy).toBeCalledWith('dotnet-version', '3.0.103'); + }, 400000); }); diff --git a/action.yml b/action.yml index f259c3b..dafec86 100644 --- a/action.yml +++ b/action.yml @@ -17,6 +17,9 @@ inputs: description: 'Optional OWNER for using packages from GitHub Package Registry organizations/users other than the current repository''s owner. Only used if a GPR URL is also provided in source-url' config-file: description: 'Optional NuGet.config location, if your NuGet.config isn''t located in the root of the repo.' +outputs: + dotnet-version: + description: 'Contains the installed by action .NET SDK version for reuse.' runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 9e6f7b4..2176821 100644 --- a/dist/index.js +++ b/dist/index.js @@ -189,6 +189,7 @@ const exec = __importStar(__nccwpck_require__(1514)); const io = __importStar(__nccwpck_require__(7436)); const hc = __importStar(__nccwpck_require__(6255)); const fs_1 = __nccwpck_require__(7147); +const promises_1 = __nccwpck_require__(3292); const path_1 = __importDefault(__nccwpck_require__(1017)); const semver_1 = __importDefault(__nccwpck_require__(5911)); const utils_1 = __nccwpck_require__(918); @@ -284,8 +285,8 @@ class DotnetCoreInstaller { } else { // This is the default set in install-dotnet.sh - core.addPath(path_1.default.join(process.env['HOME'] + '', '.dotnet')); - core.exportVariable('DOTNET_ROOT', path_1.default.join(process.env['HOME'] + '', '.dotnet')); + core.addPath(DotnetCoreInstaller.installationDirectoryMac); + core.exportVariable('DOTNET_ROOT', DotnetCoreInstaller.installationDirectoryMac); } } } @@ -332,11 +333,11 @@ class DotnetCoreInstaller { if (process.env['no_proxy'] != null) { scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); } - scriptArguments.push(`-InstallDir '${DotnetCoreInstaller.installationDirectoryWindows}'`); + scriptArguments.push('-InstallDir', `'${DotnetCoreInstaller.installationDirectoryWindows}'`); // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used scriptPath = (yield io.which('pwsh', false)) || (yield io.which('powershell', true)); - scriptArguments = [...windowsDefaultOptions, scriptArguments.join(' ')]; + scriptArguments = windowsDefaultOptions.concat(scriptArguments); } else { fs_1.chmodSync(escapedScript, '777'); @@ -351,17 +352,31 @@ class DotnetCoreInstaller { if (utils_1.IS_LINUX) { scriptArguments.push('--install-dir', DotnetCoreInstaller.installationDirectoryLinux); } + if (utils_1.IS_MAC) { + scriptArguments.push('--install-dir', DotnetCoreInstaller.installationDirectoryMac); + } } const { exitCode, stdout } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, { ignoreReturnCode: true }); if (exitCode) { throw new Error(`Failed to install dotnet ${exitCode}. ${stdout}`); } + return this.outputDotnetVersion(dotnetVersion.value, scriptArguments[scriptArguments.length - 1]); + }); + } + outputDotnetVersion(version, installationPath) { + return __awaiter(this, void 0, void 0, function* () { + let versionsOnRunner = yield promises_1.readdir(path_1.default.join(installationPath.replace(/'/g, ''), 'sdk')); + let installedVersion = semver_1.default.maxSatisfying(versionsOnRunner, version, { + includePrerelease: true + }); + return installedVersion; }); } } exports.DotnetCoreInstaller = DotnetCoreInstaller; DotnetCoreInstaller.installationDirectoryWindows = path_1.default.join(process.env['PROGRAMFILES'] + '', 'dotnet'); DotnetCoreInstaller.installationDirectoryLinux = '/usr/share/dotnet'; +DotnetCoreInstaller.installationDirectoryMac = path_1.default.join(process.env['HOME'] + '', '.dotnet'); /***/ }), @@ -408,6 +423,7 @@ const core = __importStar(__nccwpck_require__(2186)); const installer_1 = __nccwpck_require__(1480); const fs = __importStar(__nccwpck_require__(7147)); const path_1 = __importDefault(__nccwpck_require__(1017)); +const semver_1 = __importDefault(__nccwpck_require__(5911)); const auth = __importStar(__nccwpck_require__(8527)); const qualityOptions = [ 'daily', @@ -429,6 +445,7 @@ function run() { // Proxy, auth, (etc) are still set up, even if no version is identified // const versions = core.getMultilineInput('dotnet-version'); + const installedDotnetVersions = []; const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { const globalJsonPath = path_1.default.join(process.cwd(), globalJsonFileInput); @@ -454,7 +471,8 @@ function run() { const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { dotnetInstaller = new installer_1.DotnetCoreInstaller(version, quality); - yield dotnetInstaller.installDotnet(); + const installedVersion = yield dotnetInstaller.installDotnet(); + installedDotnetVersions.push(installedVersion); } installer_1.DotnetCoreInstaller.addToPath(); } @@ -463,6 +481,13 @@ function run() { if (sourceUrl) { auth.configAuthentication(sourceUrl, configFile); } + const comparisonRange = globalJsonFileInput + ? versions[versions.length - 1] + : '*'; + const versionToOutput = semver_1.default.maxSatisfying(installedDotnetVersions, comparisonRange, { + includePrerelease: true + }); + core.setOutput('dotnet-version', versionToOutput); const matchersPath = path_1.default.join(__dirname, '..', '.github'); core.info(`##[add-matcher]${path_1.default.join(matchersPath, 'csc.json')}`); } @@ -498,9 +523,10 @@ run(); "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.IS_LINUX = exports.IS_WINDOWS = void 0; +exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0; exports.IS_WINDOWS = process.platform === 'win32'; exports.IS_LINUX = process.platform === 'linux'; +exports.IS_MAC = process.platform === 'darwin'; /***/ }), @@ -25688,6 +25714,14 @@ module.exports = require("fs"); /***/ }), +/***/ 3292: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs/promises"); + +/***/ }), + /***/ 3685: /***/ ((module) => { diff --git a/src/installer.ts b/src/installer.ts index 24bab1b..041b655 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -4,9 +4,10 @@ import * as exec from '@actions/exec'; import * as io from '@actions/io'; import * as hc from '@actions/http-client'; import {chmodSync} from 'fs'; +import {readdir} from 'fs/promises'; import path from 'path'; import semver from 'semver'; -import {IS_LINUX, IS_WINDOWS} from './utils'; +import {IS_LINUX, IS_WINDOWS, IS_MAC} from './utils'; import {QualityOptions} from './setup-dotnet'; export interface DotnetVersion { @@ -116,6 +117,10 @@ export class DotnetCoreInstaller { 'dotnet' ); private static readonly installationDirectoryLinux = '/usr/share/dotnet'; + private static readonly installationDirectoryMac = path.join( + process.env['HOME'] + '', + '.dotnet' + ); static addToPath() { if (process.env['DOTNET_INSTALL_DIR']) { @@ -136,10 +141,10 @@ export class DotnetCoreInstaller { ); } else { // This is the default set in install-dotnet.sh - core.addPath(path.join(process.env['HOME'] + '', '.dotnet')); + core.addPath(DotnetCoreInstaller.installationDirectoryMac); core.exportVariable( 'DOTNET_ROOT', - path.join(process.env['HOME'] + '', '.dotnet') + DotnetCoreInstaller.installationDirectoryMac ); } } @@ -164,7 +169,7 @@ export class DotnetCoreInstaller { } } - public async installDotnet() { + public async installDotnet(): Promise { const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -204,12 +209,13 @@ export class DotnetCoreInstaller { } scriptArguments.push( - `-InstallDir '${DotnetCoreInstaller.installationDirectoryWindows}'` + '-InstallDir', + `'${DotnetCoreInstaller.installationDirectoryWindows}'` ); // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used scriptPath = (await io.which('pwsh', false)) || (await io.which('powershell', true)); - scriptArguments = [...windowsDefaultOptions, scriptArguments.join(' ')]; + scriptArguments = windowsDefaultOptions.concat(scriptArguments); } else { chmodSync(escapedScript, '777'); scriptPath = await io.which(escapedScript, true); @@ -229,6 +235,13 @@ export class DotnetCoreInstaller { DotnetCoreInstaller.installationDirectoryLinux ); } + + if (IS_MAC) { + scriptArguments.push( + '--install-dir', + DotnetCoreInstaller.installationDirectoryMac + ); + } } const {exitCode, stdout} = await exec.getExecOutput( `"${scriptPath}"`, @@ -238,5 +251,25 @@ export class DotnetCoreInstaller { if (exitCode) { throw new Error(`Failed to install dotnet ${exitCode}. ${stdout}`); } + + return this.outputDotnetVersion( + dotnetVersion.value, + scriptArguments[scriptArguments.length - 1] + ); + } + + private async outputDotnetVersion( + version, + installationPath + ): Promise { + let versionsOnRunner: string[] = await readdir( + path.join(installationPath.replace(/'/g, ''), 'sdk') + ); + + let installedVersion = semver.maxSatisfying(versionsOnRunner, version, { + includePrerelease: true + })!; + + return installedVersion; } } diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 2306362..ba2e419 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import {DotnetCoreInstaller} from './installer'; import * as fs from 'fs'; import path from 'path'; +import semver from 'semver'; import * as auth from './authutil'; const qualityOptions = [ @@ -26,6 +27,7 @@ export async function run() { // Proxy, auth, (etc) are still set up, even if no version is identified // const versions = core.getMultilineInput('dotnet-version'); + const installedDotnetVersions: string[] = []; const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { @@ -60,7 +62,8 @@ export async function run() { const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { dotnetInstaller = new DotnetCoreInstaller(version, quality); - await dotnetInstaller.installDotnet(); + const installedVersion = await dotnetInstaller.installDotnet(); + installedDotnetVersions.push(installedVersion); } DotnetCoreInstaller.addToPath(); } @@ -71,6 +74,20 @@ export async function run() { auth.configAuthentication(sourceUrl, configFile); } + const comparisonRange: string = globalJsonFileInput + ? versions[versions.length - 1]! + : '*'; + + const versionToOutput = semver.maxSatisfying( + installedDotnetVersions, + comparisonRange, + { + includePrerelease: true + } + ); + + core.setOutput('dotnet-version', versionToOutput); + const matchersPath = path.join(__dirname, '..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`); } catch (error) { diff --git a/src/utils.ts b/src/utils.ts index 77886ce..ff20ee3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,2 +1,3 @@ export const IS_WINDOWS = process.platform === 'win32'; export const IS_LINUX = process.platform === 'linux'; +export const IS_MAC = process.platform === 'darwin';