Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,42 @@ steps:
9.0.x
- run: dotnet build <my project>
```

## Installing additional runtimes

The `dotnet-runtime` input allows you to install .NET runtimes separately from SDKs. This is useful for multi-targeting scenarios where you need one SDK version but multiple runtime versions.

When `dotnet-runtime` is specified, both the .NET Runtime (Microsoft.NETCore.App) and the ASP.NET Core Runtime (Microsoft.AspNetCore.App) are installed for each specified version.

**Example: Install SDK 10 with runtimes 8 and 9**:
```yml
steps:
- uses: actions/checkout@v5
- name: Setup dotnet
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
dotnet-runtime: |
8.0.x
9.0.x
- run: dotnet build <my project>
- run: dotnet test <my project>
```

## Supported version syntax

The `dotnet-version` input supports following syntax:
The `dotnet-version` and `dotnet-runtime` inputs support following syntax:

- **A.B.C** (e.g 6.0.400, 7.0.100-preview.7.22377.5) - installs exact version of .NET SDK
- **A.B** or **A.B.x** (e.g. 8.0, 8.0.x) - installs the latest patch version of .NET SDK on the channel `8.0`, including prerelease versions (preview, rc)
- **A.B.C** (e.g 6.0.400, 7.0.100-preview.7.22377.5) - installs exact version of .NET SDK or runtime
- **A.B** or **A.B.x** (e.g. 8.0, 8.0.x) - installs the latest patch version on the channel `8.0`, including prerelease versions (preview, rc)
- **A** or **A.x** (e.g. 8, 8.x) - installs the latest minor version of the specified major tag, including prerelease versions (preview, rc)
- **A.B.Cxx** (e.g. 8.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific SDK release, including prerelease versions (preview, rc).
- **A.B.Cxx** (e.g. 8.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific release, including prerelease versions (preview, rc).


## Using the `dotnet-quality` input
This input sets up the action to install the latest build of the specified quality in the channel. The possible values of `dotnet-quality` are: **daily**, **signed**, **validated**, **preview**, **ga**.

> **Note**: `dotnet-quality` input can be used only with .NET SDK version in 'A.B', 'A.B.x', 'A', 'A.x' and 'A.B.Cxx' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored.
> **Note**: `dotnet-quality` input can be used only with .NET SDK or runtime version in 'A.B', 'A.B.x', 'A', 'A.x' and 'A.B.Cxx' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored. The quality setting applies to both SDK and runtime installations.

```yml
steps:
Expand Down
165 changes: 165 additions & 0 deletions __tests__/installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,171 @@ describe('installer tests', () => {
expect(path).toContain(process.env['DOTNET_INSTALL_DIR']);
});
});

describe('installRuntime() tests', () => {
beforeAll(() => {
whichSpy.mockImplementation(() => Promise.resolve('PathToShell'));
chmodSyncSpy.mockImplementation(() => {});
readdirSpy.mockImplementation(() => Promise.resolve([]));
});

afterAll(() => {
jest.resetAllMocks();
});

it('should throw the error in case of non-zero exit code of the .NET runtime installation script. The error message should contain logs.', async () => {
const inputVersion = '8.0.402';
const inputQuality = '' as QualityOptions;
const errorMessage = 'fictitious error message!';

getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 1,
stdout: '',
stderr: errorMessage
});
});

const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
await expect(dotnetInstaller.installRuntime()).rejects.toThrow(
`Failed to install dotnet runtime, exit code: 1. ${errorMessage}`
);
});

it('should throw the error in case of non-zero exit code of the ASP.NET Core runtime installation script. The error message should contain logs.', async () => {
const inputVersion = '8.0.402';
const inputQuality = '' as QualityOptions;
const errorMessage = 'fictitious aspnetcore error message!';

getExecOutputSpy
.mockImplementationOnce(() => {
return Promise.resolve({
exitCode: 0,
stdout: `Fictitious dotnet runtime version ${inputVersion} is installed`,
stderr: ''
});
})
.mockImplementationOnce(() => {
return Promise.resolve({
exitCode: 1,
stdout: '',
stderr: errorMessage
});
});

const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
await expect(dotnetInstaller.installRuntime()).rejects.toThrow(
`Failed to install aspnetcore runtime, exit code: 1. ${errorMessage}`
);
});

it('should return version of .NET runtime after installation complete', async () => {
const inputVersion = '8.0.402';
const inputQuality = '' as QualityOptions;
const stdout = `Fictitious dotnet runtime version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 0,
stdout: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);

const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
const installedVersion = await dotnetInstaller.installRuntime();

expect(installedVersion).toBe(inputVersion);
});

it(`should supply '--runtime dotnet' and '--runtime aspnetcore' arguments to the installation script`, async () => {
const inputVersion = '8.0.402';
const inputQuality = '' as QualityOptions;
const stdout = `Fictitious dotnet runtime version ${inputVersion} is installed`;

getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 0,
stdout: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);

const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);

await dotnetInstaller.installRuntime();

// Check first call installs .NET runtime
const dotnetScriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
const expectedDotnetArgument = IS_WINDOWS
? `-Runtime dotnet`
: `--runtime dotnet`;

expect(dotnetScriptArguments).toContain(expectedDotnetArgument);

// Check second call installs ASP.NET Core runtime
const aspnetcoreScriptArguments = (
getExecOutputSpy.mock.calls[1][1] as string[]
).join(' ');
const expectedAspnetcoreArgument = IS_WINDOWS
? `-Runtime aspnetcore`
: `--runtime aspnetcore`;

expect(aspnetcoreScriptArguments).toContain(expectedAspnetcoreArgument);
});

it(`should supply 'version' argument to both runtime installation scripts if supplied version is in A.B.C syntax`, async () => {
const inputVersion = '8.0.402';
const inputQuality = '' as QualityOptions;
const stdout = `Fictitious dotnet runtime version ${inputVersion} is installed`;

getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 0,
stdout: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);

const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);

await dotnetInstaller.installRuntime();

const expectedArgument = IS_WINDOWS
? `-Version ${inputVersion}`
: `--version ${inputVersion}`;

// Check both calls contain version argument
const dotnetScriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
expect(dotnetScriptArguments).toContain(expectedArgument);

const aspnetcoreScriptArguments = (
getExecOutputSpy.mock.calls[1][1] as string[]
).join(' ');
expect(aspnetcoreScriptArguments).toContain(expectedArgument);
});
});
});

describe('DotnetVersionResolver tests', () => {
Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ branding:
inputs:
dotnet-version:
description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 6.0.2xx'
dotnet-runtime:
description: 'Optional runtime version(s) to install. Supports same version syntax as dotnet-version. Examples: 8.0.x, 9.0.x, 8.0.402'
dotnet-quality:
description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.'
global-json-file:
Expand Down
60 changes: 56 additions & 4 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100716,6 +100716,42 @@ class DotnetCoreInstaller {
}
return this.parseInstalledVersion(dotnetInstallOutput.stdout);
}
async installRuntime() {
const versionResolver = new DotnetVersionResolver(this.version);
const dotnetVersion = await versionResolver.createDotnetVersion();
/**
* Install .NET runtime (Microsoft.NETCore.App)
* Skip non-versioned files to avoid overwriting CLI
*/
const dotnetRuntimeOutput = await new DotnetInstallScript()
// If dotnet CLI is already installed - avoid overwriting it
.useArguments(utils_1.IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files')
// Install .NET runtime (Microsoft.NETCore.App)
.useArguments(utils_1.IS_WINDOWS ? '-Runtime' : '--runtime', 'dotnet')
// Use version provided by user
.useVersion(dotnetVersion, this.quality)
.execute();
if (dotnetRuntimeOutput.exitCode) {
throw new Error(`Failed to install dotnet runtime, exit code: ${dotnetRuntimeOutput.exitCode}. ${dotnetRuntimeOutput.stderr}`);
}
/**
* Install ASP.NET Core runtime (Microsoft.AspNetCore.App)
* Skip non-versioned files to avoid overwriting CLI
*/
const aspnetcoreRuntimeOutput = await new DotnetInstallScript()
// If dotnet CLI is already installed - avoid overwriting it
.useArguments(utils_1.IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files')
// Install ASP.NET Core runtime (Microsoft.AspNetCore.App)
.useArguments(utils_1.IS_WINDOWS ? '-Runtime' : '--runtime', 'aspnetcore')
// Use version provided by user
.useVersion(dotnetVersion, this.quality)
.execute();
if (aspnetcoreRuntimeOutput.exitCode) {
throw new Error(`Failed to install aspnetcore runtime, exit code: ${aspnetcoreRuntimeOutput.exitCode}. ${aspnetcoreRuntimeOutput.stderr}`);
}
// Return the .NET runtime version (both should be the same version)
return this.parseInstalledVersion(dotnetRuntimeOutput.stdout);
}
parseInstalledVersion(stdout) {
const regex = /(?<version>\d+\.\d+\.\d+[a-z0-9._-]*)/gm;
const matchedResult = regex.exec(stdout);
Expand Down Expand Up @@ -100796,14 +100832,17 @@ async function run() {
//
// dotnet-version is optional, but needs to be provided for most use cases.
// If supplied, install / use from the tool cache.
// dotnet-runtime is optional and allows installing runtime-only versions.
// global-version-file may be specified to point to a specific global.json
// and will be used to install an additional version.
// If not supplied, look for version in ./global.json.
// If a valid version still can't be identified, nothing will be installed.
// Proxy, auth, (etc) are still set up, even if no version is identified
//
const versions = core.getMultilineInput('dotnet-version');
const runtimeVersions = core.getMultilineInput('dotnet-runtime');
const installedDotnetVersions = [];
const installedRuntimeVersions = [];
const globalJsonFileInput = core.getInput('global-json-file');
if (globalJsonFileInput) {
const globalJsonPath = path_1.default.resolve(process.cwd(), globalJsonFileInput);
Expand All @@ -100823,11 +100862,11 @@ async function run() {
core.info(`The global.json wasn't found in the root directory. No .NET version will be installed.`);
}
}
const quality = core.getInput('dotnet-quality');
if (quality && !qualityOptions.includes(quality)) {
throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`);
}
if (versions.length) {
const quality = core.getInput('dotnet-quality');
if (quality && !qualityOptions.includes(quality)) {
throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`);
}
let dotnetInstaller;
const uniqueVersions = new Set(versions);
for (const version of uniqueVersions) {
Expand All @@ -100837,6 +100876,19 @@ async function run() {
}
installer_1.DotnetInstallDir.addToPath();
}
if (runtimeVersions.length) {
let dotnetInstaller;
const uniqueRuntimeVersions = new Set(runtimeVersions);
for (const runtimeVersion of uniqueRuntimeVersions) {
dotnetInstaller = new installer_1.DotnetCoreInstaller(runtimeVersion, quality);
const installedRuntimeVersion = await dotnetInstaller.installRuntime();
installedRuntimeVersions.push(installedRuntimeVersion);
}
// Ensure PATH is set (may have been set already by SDK installation)
if (!versions.length) {
installer_1.DotnetInstallDir.addToPath();
}
}
const sourceUrl = core.getInput('source-url');
const configFile = core.getInput('config-file');
if (sourceUrl) {
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading