Compare commits

..

10 Commits
v3.1.0 ... v3

Author SHA1 Message Date
aparnajyothi-y
55ec9447dd V3 - Update the file name publish-immutable-actions to publish-immutable-action (#630)
* add Publish Immutable Action

* file name update
2025-04-17 13:21:33 -05:00
aparnajyothi-y
70918d1a1f add Publish Immutable Action (#627) 2025-04-17 09:04:29 -05:00
mahabaleshwars
a6d1e7b915 update actions/cache (#620) 2025-04-13 22:10:06 -05:00
Zachary Taylor
b2ace4b12f v3 - Remove azureedge.net fallback logic and update install scripts (#573)
* Remove logic for azureedge.net fallback in preparation for install script changes (v3)

* remove fallback in index.js

* fix ci

* add updated install scripts

* Update e2e-tests.yml

* Update e2e-tests.yml to remove outdated URLs in workflow

* Fix audit issues

* Revert audit fixes for heap memory failures

---------

Co-authored-by: HarithaVattikuti <73516759+HarithaVattikuti@users.noreply.github.com>
2025-02-20 12:23:35 -06:00
HarithaVattikuti
ea9897a6e5 V3 - Use new .NET CDN URLs and update to latest install scripts (#567)
* Update new cdn

* new cdn changes

* update latest install scripts

* Format install scripts

* Fix Ubuntu-latest issues

* Fallback logic

* Format check

* Update signed version
2024-12-26 15:42:59 -06:00
Nogic
3447fd6a9f feat: Cache NuGet global-packages folder (#303)
* feat: cache NuGet global-packages folder

* fix: remove unused files

* docs: fix incorrect action

* ci: add e2e test for cache

* docs: accept suggested changes on README

* docs: add simple cache example

* build: change main script path

* fix: change relative path to install scripts

* fix: change relative path to problem matcher

* refactor: accept changes on cache-utils

* fix: revert main script path changes

* test: fix cache-utils unit test

* test: fix cache-utils unit test

* feat: add `cache-dependency-path` variables

* build: change main script dist path

* ci: add `cache-dependency-path` e2e test & missing lock file

* fix: accept change suggestions

* ci: copy NuGet lock file to root

to pass "test-setup-with-cache" e2e test

* docs: change README guide

* fix: apply suggestions from code review

Co-authored-by: Ivan <98037481+IvanZosimov@users.noreply.github.com>

* test: fix some failed unit tests

- fix `restoreCache()` test for 9703c8
- update installer script

* build: rebuild dist

* Update unit-tests
- Additional unit test were added to setup-dotnet.test.ts

* Update unit tests for unix systems

* Format and lint unit tests

* fix: avoid use '/' on `path.join`

* fix: rebuild dist

* fix: apply suggestions from code review

Co-authored-by: Ivan <98037481+IvanZosimov@users.noreply.github.com>

* build: add `DisableImplicitNuGetFallbackFolder` option

also add guide on README

* docs: highlight warnings and notes

* docs: update note about handling NU1403

---------

Co-authored-by: Ivan <98037481+IvanZosimov@users.noreply.github.com>
Co-authored-by: IvanZosimov <ivanzosimov@github.com>
2023-05-29 12:43:18 +02:00
Marko Zivic
916351aac9 Merge pull request #430 from akv-platform/remove-implicit-dependencies
Remove implicit dependencies
2023-05-26 08:30:48 +02:00
Nikolai Laevskii
1ad2e312fa Add missing dependency 2023-05-25 13:40:06 +02:00
Nikolai Laevskii
e3f84b8f7a Install eslint-plugin-node 2023-05-25 13:38:43 +02:00
github-actions[bot]
ba848a34bb Update configuration files 2023-05-24 09:54:37 +00:00
85 changed files with 198054 additions and 24162 deletions

View File

@@ -7,7 +7,7 @@ module.exports = {
'eslint-config-prettier' 'eslint-config-prettier'
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-jest'], plugins: ['@typescript-eslint', 'eslint-plugin-node', 'eslint-plugin-jest'],
rules: { rules: {
'@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
@@ -28,7 +28,8 @@ module.exports = {
} }
], ],
'no-control-regex': 'off', 'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}] 'no-constant-condition': ['error', {checkLoops: false}],
'node/no-extraneous-import': 'error'
}, },
overrides: [ overrides: [
{ {

View File

@@ -17,7 +17,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -40,7 +40,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macos-13]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -69,7 +69,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -94,7 +94,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macos-13]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -114,7 +114,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -138,7 +138,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -159,7 +159,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -183,7 +183,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -209,7 +209,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -234,7 +234,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -251,12 +251,72 @@ jobs:
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-" run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-"
test-setup-with-cache:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-22.04, windows-latest, macos-latest]
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Copy NuGet lock file to root
shell: bash
run: cp ./__tests__/e2e-test-csproj/packages.lock.json ./packages.lock.json
- name: Setup .NET Core 3.1
id: setup-dotnet
uses: ./
with:
dotnet-version: 3.1
cache: true
- name: Verify Cache
if: steps.setup-dotnet.outputs.cache-hit == 'true'
shell: bash
run: if [[ -e ${NUGET_PACKAGES} ]]; then exit 0; else exit 1; fi
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^3.1"
test-setup-with-cache-dependency-path:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-22.04, windows-latest, macos-latest]
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup .NET Core 3.1
id: setup-dotnet
uses: ./
with:
dotnet-version: 3.1
cache: true
cache-dependency-path: './__tests__/e2e-test-csproj/packages.lock.json'
- name: Verify Cache
if: steps.setup-dotnet.outputs.cache-hit == 'true'
shell: bash
run: if [[ -e ${NUGET_PACKAGES} ]]; then exit 0; else exit 1; fi
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^3.1"
test-dotnet-version-output-during-single-version-installation: test-dotnet-version-output-during-single-version-installation:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -282,7 +342,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -306,7 +366,7 @@ jobs:
if (-not ($version -eq '${{steps.step2.outputs.dotnet-version}}')) { throw "Unexpected output value" } if (-not ($version -eq '${{steps.step2.outputs.dotnet-version}}')) { throw "Unexpected output value" }
test-proxy: test-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
container: container:
image: ubuntu:latest image: ubuntu:latest
options: --dns 127.0.0.1 options: --dns 127.0.0.1
@@ -346,10 +406,10 @@ jobs:
__tests__/verify-dotnet.ps1 -Patterns "^6.0" -CheckNugetConfig __tests__/verify-dotnet.ps1 -Patterns "^6.0" -CheckNugetConfig
test-bypass-proxy: test-bypass-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
env: env:
https_proxy: http://no-such-proxy:3128 https_proxy: http://no-such-proxy:3128
no_proxy: github.com,dotnetcli.blob.core.windows.net,download.visualstudio.microsoft.com,api.nuget.org,dotnetcli.azureedge.net no_proxy: github.com,download.visualstudio.microsoft.com,api.nuget.org,builds.dotnet.microsoft.com,ci.dot.net
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@@ -0,0 +1,20 @@
name: 'Publish Immutable Action Version'
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Checking out
uses: actions/checkout@v4
- name: Publish
id: publish
uses: actions/publish-immutable-action@v0.0.4

View File

@@ -0,0 +1,20 @@
name: 'Publish Immutable Action Version'
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Checking out
uses: actions/checkout@v4
- name: Publish
id: publish
uses: actions/publish-immutable-action@v0.0.4

View File

@@ -17,7 +17,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0'] dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0']
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -3,6 +3,7 @@ sources:
allowed: allowed:
- apache-2.0 - apache-2.0
- 0bsd
- bsd-2-clause - bsd-2-clause
- bsd-3-clause - bsd-3-clause
- isc - isc
@@ -11,4 +12,5 @@ allowed:
- unlicense - unlicense
reviewed: reviewed:
npm: npm:
- sax # ISC + MIT

BIN
.licenses/npm/@actions/cache.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/@actions/glob-0.1.2.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@actions/glob-0.3.0.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/@azure/core-auth.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/core-http.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/core-lro.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/core-paging.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/core-tracing.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/core-util.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/logger.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/ms-rest-js.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@azure/storage-blob.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@fastify/busboy.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@opentelemetry/api.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/@protobuf-ts/plugin.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@protobuf-ts/protoc.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/@protobuf-ts/runtime.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@types/node-fetch.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@types/node.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/@types/tunnel.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/abort-controller.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/asynckit.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/balanced-match.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/brace-expansion.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/combined-stream.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/concat-map.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/delayed-stream.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/event-target-shim.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/events.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/form-data-2.5.1.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/form-data-3.0.1.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/form-data-4.0.0.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/ip-regex.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/mime-db.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/mime-types.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/minimatch.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/process.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/psl.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/punycode.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/sax.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/tough-cookie.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/tslib-1.14.1.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/tslib-2.5.0.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/typescript.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/undici.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/xml2js.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/xmlbuilder.dep.yml generated Normal file

Binary file not shown.

View File

@@ -82,6 +82,63 @@ steps:
working-directory: csharp working-directory: csharp
``` ```
## Caching NuGet Packages
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching global packages data but requires less configuration settings. The `cache` input is optional, and caching is turned off by default.
The action searches for [NuGet Lock files](https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies) (`packages.lock.json`) in the repository root, calculates their hash and uses it as a part of the cache key. If lock file does not exist, this action throws error. Use `cache-dependency-path` for cases when multiple dependency files are used, or they are located in different subdirectories.
> **Warning**: Caching NuGet packages is available since .NET SDK 2.1.500 and 2.2.100 as the NuGet lock file [is available](https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies) only for NuGet 4.9 and above.
```yaml
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.x
cache: true
- run: dotnet restore --locked-mode
```
> **Note**: This action will only restore `global-packages` folder, so you will probably get the [NU1403](https://learn.microsoft.com/nuget/reference/errors-and-warnings/nu1403) error when running `dotnet restore`.
> To avoid this, you can use [`DisableImplicitNuGetFallbackFolder`](https://github.com/dotnet/reproducible-builds/blob/abfe986832aa28597d3340b92469d1a702013d23/Documentation/Reproducible-MSBuild/Techniques/DisableImplicitNuGetFallbackFolder.md) option.
```xml
<PropertyGroup>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup>
```
### Reduce caching size
> **Note**: Use [`NUGET_PACKAGES`](https://learn.microsoft.com/nuget/reference/cli-reference/cli-ref-environment-variables) environment variable if available. Some action runners already has huge libraries. (ex. Xamarin)
```yaml
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.x
cache: true
- run: dotnet restore --locked-mode
```
### Caching NuGet packages in monorepos
```yaml
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.x
cache: true
cache-dependency-path: subdir/packages.lock.json
- run: dotnet restore --locked-mode
```
## Matrix Testing ## Matrix Testing
Using `setup-dotnet` it's possible to use [matrix syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) to install several versions of .NET SDK: Using `setup-dotnet` it's possible to use [matrix syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) to install several versions of .NET SDK:
```yml ```yml
@@ -214,6 +271,9 @@ When the `dotnet-version` input is used along with the `global-json-file` input,
- run: echo '${{ steps.stepid.outputs.dotnet-version }}' # outputs 2.2.207 - run: echo '${{ steps.stepid.outputs.dotnet-version }}' # outputs 2.2.207
``` ```
### `cache-hit`
A boolean value to indicate an exact match was found for the cache key (follows [actions/cache](https://github.com/actions/cache#outputs))
## Environment variables ## 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 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
@@ -224,25 +284,28 @@ Some environment variables may be necessary for your particular case or to impro
| DOTNET_NOLOGO |Removes logo and telemetry message from first run of dotnet cli|*false*| | DOTNET_NOLOGO |Removes logo and telemetry message from first run of dotnet cli|*false*|
| DOTNET_CLI_TELEMETRY_OPTOUT |Opt-out of telemetry being sent to Microsoft|*false*| | DOTNET_CLI_TELEMETRY_OPTOUT |Opt-out of telemetry being sent to Microsoft|*false*|
| DOTNET_MULTILEVEL_LOOKUP |Configures whether the global install location is used as a fall-back|*true*| | DOTNET_MULTILEVEL_LOOKUP |Configures whether the global install location is used as a fall-back|*true*|
| NUGET_PACKAGES |Configures a path to the [NuGet `global-packages` folder](https://learn.microsoft.com/nuget/consume-packages/managing-the-global-packages-and-cache-folders)|*default value for each OS* |
The default value of the `DOTNET_INSTALL_DIR` environment variable depends on the operation system which is used on a runner: The default values of the `DOTNET_INSTALL_DIR` and `NUGET_PACKAGES` environment variables depend on the operation system which is used on a runner:
| **Operation system** | **Default value** | | **Operation system** | `DOTNET_INSTALL_DIR` | `NUGET_PACKAGES` |
| ----------- | ----------- | | ----------- | ----------- | ----------- |
| **Windows** | `C:\Program Files\dotnet` | | **Windows** | `C:\Program Files\dotnet` | `%userprofile%\.nuget\packages` |
| **Ubuntu** | `/usr/share/dotnet` | | **Ubuntu** | `/usr/share/dotnet` | `~/.nuget/packages` |
| **macOS** | `/Users/runner/.dotnet` | | **macOS** | `/Users/runner/.dotnet` | `~/.nuget/packages` |
**Example usage**: **Example usage of environment variable**:
```yml ```yml
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DOTNET_INSTALL_DIR: "path/to/directory" DOTNET_INSTALL_DIR: "path/to/directory"
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: actions/setup-dotnet@v3 - uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '3.1.x' dotnet-version: '3.1.x'
cache: true
``` ```
## License ## License

View File

@@ -0,0 +1,101 @@
import {readdir} from 'node:fs/promises';
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as glob from '@actions/glob';
import {restoreCache} from '../src/cache-restore';
import {getNuGetFolderPath} from '../src/cache-utils';
import {lockFilePatterns} from '../src/constants';
jest.mock('node:fs/promises');
jest.mock('@actions/cache');
jest.mock('@actions/core');
jest.mock('@actions/glob');
jest.mock('../src/cache-utils');
describe('cache-restore tests', () => {
describe.each(lockFilePatterns)('restoreCache("%s")', lockFilePattern => {
/** Store original process.env.GITHUB_WORKSPACE */
let githubWorkspace: string | undefined;
beforeAll(() => {
githubWorkspace = process.env.GITHUB_WORKSPACE;
jest.mocked(getNuGetFolderPath).mockResolvedValue({
'global-packages': 'global-packages',
'http-cache': 'http-cache',
temp: 'temp',
'plugins-cache': 'plugins-cache'
});
});
beforeEach(() => {
process.env.GITHUB_WORKSPACE = './';
jest.mocked(glob.hashFiles).mockClear();
jest.mocked(core.saveState).mockClear();
jest.mocked(core.setOutput).mockClear();
jest.mocked(cache.restoreCache).mockClear();
});
afterEach(() => (process.env.GITHUB_WORKSPACE = githubWorkspace));
it('throws error when lock file is not found', async () => {
jest.mocked(glob.hashFiles).mockResolvedValue('');
await expect(restoreCache(lockFilePattern)).rejects.toThrow();
expect(jest.mocked(core.saveState)).not.toHaveBeenCalled();
expect(jest.mocked(core.setOutput)).not.toHaveBeenCalled();
expect(jest.mocked(cache.restoreCache)).not.toHaveBeenCalled();
});
it('does not call core.saveState("CACHE_RESULT") when cache.restoreCache() returns falsy', async () => {
jest.mocked(glob.hashFiles).mockResolvedValue('hash');
jest.mocked(cache.restoreCache).mockResolvedValue(undefined);
await restoreCache(lockFilePattern);
const expectedKey = `dotnet-cache-${process.env.RUNNER_OS}-hash`;
expect(jest.mocked(core.saveState)).toHaveBeenCalledWith(
'CACHE_KEY',
expectedKey
);
expect(jest.mocked(core.saveState)).not.toHaveBeenCalledWith(
'CACHE_RESULT',
expectedKey
);
expect(jest.mocked(core.setOutput)).toHaveBeenCalledWith(
'cache-hit',
false
);
});
it('calls core.saveState("CACHE_RESULT") when cache.restoreCache() returns key', async () => {
const expectedKey = `dotnet-cache-${process.env.RUNNER_OS}-hash`;
jest.mocked(glob.hashFiles).mockResolvedValue('hash');
jest.mocked(cache.restoreCache).mockResolvedValue(expectedKey);
await restoreCache(lockFilePattern);
expect(jest.mocked(core.saveState)).toHaveBeenCalledWith(
'CACHE_KEY',
expectedKey
);
expect(jest.mocked(core.saveState)).toHaveBeenCalledWith(
'CACHE_RESULT',
expectedKey
);
expect(jest.mocked(core.setOutput)).toHaveBeenCalledWith(
'cache-hit',
true
);
});
it('calls glob.hashFiles("/packages.lock.json") if cacheDependencyPath is falsy', async () => {
const expectedKey = `dotnet-cache-${process.env.RUNNER_OS}-hash`;
jest.mocked(glob.hashFiles).mockResolvedValue('hash');
jest.mocked(cache.restoreCache).mockResolvedValue(expectedKey);
jest.mocked(readdir).mockResolvedValue([lockFilePattern] as any);
await restoreCache('');
expect(jest.mocked(glob.hashFiles)).not.toHaveBeenCalledWith('');
expect(jest.mocked(glob.hashFiles)).toHaveBeenCalledWith(lockFilePattern);
});
});
});

View File

@@ -0,0 +1,87 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import fs from 'node:fs';
import {run} from '../src/cache-save';
import {getNuGetFolderPath} from '../src/cache-utils';
jest.mock('@actions/cache');
jest.mock('@actions/core');
jest.mock('node:fs');
jest.mock('../src/cache-utils');
describe('cache-save tests', () => {
beforeAll(() => {
jest.mocked(getNuGetFolderPath).mockResolvedValue({
'global-packages': 'global-packages',
'http-cache': 'http-cache',
temp: 'temp',
'plugins-cache': 'plugins-cache'
});
});
beforeEach(() => {
jest.mocked(core.setFailed).mockClear();
jest.mocked(core.getState).mockClear();
jest.mocked(core.setOutput).mockClear();
jest.mocked(cache.saveCache).mockClear();
jest.mocked(fs.existsSync).mockClear();
});
it('does not save cache when inputs:cache === false', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(false);
await run();
expect(jest.mocked(core.setFailed)).not.toHaveBeenCalled();
expect(jest.mocked(core.getState)).not.toHaveBeenCalled();
expect(jest.mocked(fs.existsSync)).not.toHaveBeenCalled();
expect(jest.mocked(cache.saveCache)).not.toHaveBeenCalled();
});
it('does not save cache when core.getState("CACHE_KEY") returns ""', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(true);
jest.mocked(core.getState).mockReturnValue('');
await run();
expect(jest.mocked(core.setFailed)).not.toHaveBeenCalled();
expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2);
expect(jest.mocked(fs.existsSync)).not.toHaveBeenCalled();
expect(jest.mocked(cache.saveCache)).not.toHaveBeenCalled();
});
it('throws Error when cachePath not exists', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(true);
jest.mocked(core.getState).mockReturnValue('cache-key');
jest.mocked(fs.existsSync).mockReturnValue(false);
await run();
expect(jest.mocked(core.setFailed)).toHaveBeenCalled();
expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2);
expect(jest.mocked(cache.saveCache)).not.toHaveBeenCalled();
});
it('does not save cache when state.CACHE_KEY === state.CACHE_RESULT', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(true);
jest.mocked(core.getState).mockReturnValue('cache-key');
jest.mocked(fs.existsSync).mockReturnValue(true);
await run();
expect(jest.mocked(core.setFailed)).not.toHaveBeenCalled();
expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2);
expect(jest.mocked(cache.saveCache)).not.toHaveBeenCalled();
});
it('saves cache when state.CACHE_KEY !== state.CACHE_RESULT', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(true);
jest.mocked(core.getState).mockImplementation(s => s);
jest.mocked(fs.existsSync).mockReturnValue(true);
await run();
expect(jest.mocked(core.setFailed)).not.toHaveBeenCalled();
expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2);
expect(jest.mocked(cache.saveCache)).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,122 @@
import * as cache from '@actions/cache';
import * as exec from '@actions/exec';
import {getNuGetFolderPath, isCacheFeatureAvailable} from '../src/cache-utils';
jest.mock('@actions/cache');
jest.mock('@actions/core');
jest.mock('@actions/exec');
describe('cache-utils tests', () => {
describe('getNuGetFolderPath()', () => {
it.each([
[
`
http-cache: /home/codespace/.local/share/NuGet/v3-cache
global-packages: /var/nuget
temp: /tmp/NuGetScratch
plugins-cache: /home/codespace/.local/share/NuGet/plugins-cache
`,
{
'http-cache': '/home/codespace/.local/share/NuGet/v3-cache',
'global-packages': '/var/nuget',
temp: '/tmp/NuGetScratch',
'plugins-cache': '/home/codespace/.local/share/NuGet/plugins-cache'
}
],
[
`
http-cache: /home/codespace/.local/share/NuGet/v3-cache
global-packages: /var/nuget
temp: /tmp/NuGetScratch
plugins-cache: /home/codespace/.local/share/NuGet/plugins-cache
`,
{
'http-cache': '/home/codespace/.local/share/NuGet/v3-cache',
'global-packages': '/var/nuget',
temp: '/tmp/NuGetScratch',
'plugins-cache': '/home/codespace/.local/share/NuGet/plugins-cache'
}
],
[
`
http-cache: C:\\Users\\user\\AppData\\Local\\NuGet\\v3-cache
global-packages: C:\\Users\\user\\.nuget\\packages\\
temp: C:\\Users\\user\\AppData\\Local\\Temp\\NuGetScratch
plugins-cache: C:\\Users\\user\\AppData\\Local\\NuGet\\plugins-cache
`,
{
'http-cache': 'C:\\Users\\user\\AppData\\Local\\NuGet\\v3-cache',
'global-packages': 'C:\\Users\\user\\.nuget\\packages\\',
temp: 'C:\\Users\\user\\AppData\\Local\\Temp\\NuGetScratch',
'plugins-cache':
'C:\\Users\\user\\AppData\\Local\\NuGet\\plugins-cache'
}
],
[
`
http-cache: C:\\Users\\user\\AppData\\Local\\NuGet\\v3-cache
global-packages: C:\\Users\\user\\.nuget\\packages\\
temp: C:\\Users\\user\\AppData\\Local\\Temp\\NuGetScratch
plugins-cache: C:\\Users\\user\\AppData\\Local\\NuGet\\plugins-cache
`,
{
'http-cache': 'C:\\Users\\user\\AppData\\Local\\NuGet\\v3-cache',
'global-packages': 'C:\\Users\\user\\.nuget\\packages\\',
temp: 'C:\\Users\\user\\AppData\\Local\\Temp\\NuGetScratch',
'plugins-cache':
'C:\\Users\\user\\AppData\\Local\\NuGet\\plugins-cache'
}
]
])('(stdout: "%s") returns %p', async (stdout, expected) => {
jest
.mocked(exec.getExecOutput)
.mockResolvedValue({stdout, stderr: '', exitCode: 0});
const pathes = await getNuGetFolderPath();
expect(pathes).toStrictEqual(expected);
});
it.each([
`
error: An invalid local resource name was provided. Provide one of the following values: http-cache, temp, global-packages, all.
Usage: dotnet nuget locals [arguments] [options]
Arguments:
Cache Location(s) Specifies the cache location(s) to list or clear.
<all | http-cache | global-packages | temp>
Options:
-h|--help Show help information
--force-english-output Forces the application to run using an invariant, English-based culture.
-c|--clear Clear the selected local resources or cache location(s).
-l|--list List the selected local resources or cache location(s).
`,
'bash: dotnet: command not found',
''
])('(stderr: "%s", exitCode: 1) throws Error', async stderr => {
jest
.mocked(exec.getExecOutput)
.mockResolvedValue({stdout: '', stderr, exitCode: 1});
await expect(getNuGetFolderPath()).rejects.toThrow();
});
});
describe.each(['', 'https://github.com/', 'https://example.com/'])(
'isCacheFeatureAvailable()',
url => {
// Save & Restore env
let serverUrlEnv: string | undefined;
beforeAll(() => (serverUrlEnv = process.env['GITHUB_SERVER_URL']));
beforeEach(() => (process.env['GITHUB_SERVER_URL'] = url));
afterEach(() => (process.env['GITHUB_SERVER_URL'] = serverUrlEnv));
it('returns true when cache.isFeatureAvailable() === true', () => {
jest.mocked(cache.isFeatureAvailable).mockReturnValue(true);
expect(isCacheFeatureAvailable()).toBe(true);
});
it('returns false when cache.isFeatureAvailable() === false', () => {
jest.mocked(cache.isFeatureAvailable).mockReturnValue(false);
expect(isCacheFeatureAvailable()).toBe(false);
});
}
);
});

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>$(TEST_TARGET_FRAMEWORK)</TargetFramework> <TargetFramework>$(TEST_TARGET_FRAMEWORK)</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,5 +1,7 @@
import each from 'jest-each'; import each from 'jest-each';
import semver from 'semver'; import semver from 'semver';
import fs from 'fs';
import fspromises from 'fs/promises';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as io from '@actions/io'; import * as io from '@actions/io';
@@ -21,14 +23,25 @@ describe('installer tests', () => {
const warningSpy = jest.spyOn(core, 'warning'); const warningSpy = jest.spyOn(core, 'warning');
const whichSpy = jest.spyOn(io, 'which'); const whichSpy = jest.spyOn(io, 'which');
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
const chmodSyncSpy = jest.spyOn(fs, 'chmodSync');
const readdirSpy = jest.spyOn(fspromises, 'readdir');
describe('installDotnet() tests', () => { describe('installDotnet() tests', () => {
whichSpy.mockImplementation(() => Promise.resolve('PathToShell')); 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 installation script. The error message should contain logs.', async () => { it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => {
const inputVersion = '3.1.100'; const inputVersion = '3.1.100';
const inputQuality = '' as QualityOptions; const inputQuality = '' as QualityOptions;
const errorMessage = 'fictitious error message!'; const errorMessage = 'fictitious error message!';
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({ return Promise.resolve({
exitCode: 1, exitCode: 1,
@@ -36,6 +49,7 @@ describe('installer tests', () => {
stderr: errorMessage stderr: errorMessage
}); });
}); });
const dotnetInstaller = new installer.DotnetCoreInstaller( const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion, inputVersion,
inputQuality inputQuality

View File

@@ -5,12 +5,15 @@ import * as auth from '../src/authutil';
import * as setup from '../src/setup-dotnet'; import * as setup from '../src/setup-dotnet';
import {DotnetCoreInstaller} from '../src/installer'; import {DotnetCoreInstaller} from '../src/installer';
import * as cacheUtils from '../src/cache-utils';
import * as cacheRestore from '../src/cache-restore';
describe('setup-dotnet tests', () => { describe('setup-dotnet tests', () => {
const inputs = {} as any; const inputs = {} as any;
const getInputSpy = jest.spyOn(core, 'getInput'); const getInputSpy = jest.spyOn(core, 'getInput');
const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput');
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
const setFailedSpy = jest.spyOn(core, 'setFailed'); const setFailedSpy = jest.spyOn(core, 'setFailed');
const warningSpy = jest.spyOn(core, 'warning'); const warningSpy = jest.spyOn(core, 'warning');
const debugSpy = jest.spyOn(core, 'debug'); const debugSpy = jest.spyOn(core, 'debug');
@@ -26,13 +29,18 @@ describe('setup-dotnet tests', () => {
'installDotnet' 'installDotnet'
); );
const addToPathSpy = jest.spyOn(DotnetCoreInstaller, 'addToPath'); const addToPathSpy = jest.spyOn(DotnetCoreInstaller, 'addToPath');
const isCacheFeatureAvailableSpy = jest.spyOn(
cacheUtils,
'isCacheFeatureAvailable'
);
const restoreCacheSpy = jest.spyOn(cacheRestore, 'restoreCache');
const configAuthenticationSpy = jest.spyOn(auth, 'configAuthentication'); const configAuthenticationSpy = jest.spyOn(auth, 'configAuthentication');
describe('run() tests', () => { describe('run() tests', () => {
beforeEach(() => { beforeEach(() => {
getMultilineInputSpy.mockImplementation(input => inputs[input as string]); getMultilineInputSpy.mockImplementation(input => inputs[input as string]);
getInputSpy.mockImplementation(input => inputs[input as string]); getInputSpy.mockImplementation(input => inputs[input as string]);
getBooleanInputSpy.mockImplementation(input => inputs[input as string]);
}); });
afterEach(() => { afterEach(() => {
@@ -169,5 +177,54 @@ describe('setup-dotnet tests', () => {
expect(infoSpy).toHaveBeenCalledWith(warningMessage); expect(infoSpy).toHaveBeenCalledWith(warningMessage);
expect(setOutputSpy).not.toHaveBeenCalled(); expect(setOutputSpy).not.toHaveBeenCalled();
}); });
it(`should get 'cache-dependency-path' and call restoreCache() if input cache is set to true and cache feature is available`, async () => {
inputs['dotnet-version'] = ['6.0.300'];
inputs['dotnet-quality'] = '';
inputs['cache'] = true;
inputs['cache-dependency-path'] = 'fictitious.package.lock.json';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
addToPathSpy.mockImplementation(() => {});
isCacheFeatureAvailableSpy.mockImplementation(() => true);
restoreCacheSpy.mockImplementation(() => Promise.resolve());
await setup.run();
expect(isCacheFeatureAvailableSpy).toHaveBeenCalledTimes(1);
expect(restoreCacheSpy).toHaveBeenCalledWith(
inputs['cache-dependency-path']
);
});
it(`shouldn't call restoreCache() if input cache isn't set to true`, async () => {
inputs['dotnet-version'] = ['6.0.300'];
inputs['dotnet-quality'] = '';
inputs['cache'] = false;
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
addToPathSpy.mockImplementation(() => {});
isCacheFeatureAvailableSpy.mockImplementation(() => true);
restoreCacheSpy.mockImplementation(() => Promise.resolve());
await setup.run();
expect(restoreCacheSpy).not.toHaveBeenCalled();
});
it(`shouldn't call restoreCache() if cache feature isn't available`, async () => {
inputs['dotnet-version'] = ['6.0.300'];
inputs['dotnet-quality'] = '';
inputs['cache'] = true;
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
addToPathSpy.mockImplementation(() => {});
isCacheFeatureAvailableSpy.mockImplementation(() => false);
restoreCacheSpy.mockImplementation(() => Promise.resolve());
await setup.run();
expect(restoreCacheSpy).not.toHaveBeenCalled();
});
}); });
}); });

View File

@@ -114,4 +114,4 @@ foreach ($version in $Versions)
Remove-Item ./global.json Remove-Item ./global.json
} }
Set-Location $workingDir Set-Location $workingDir

View File

@@ -17,9 +17,20 @@ 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' 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: config-file:
description: 'Optional NuGet.config location, if your NuGet.config isn''t located in the root of the repo.' description: 'Optional NuGet.config location, if your NuGet.config isn''t located in the root of the repo.'
cache:
description: 'Optional input to enable caching of the NuGet global-packages folder'
required: false
default: false
cache-dependency-path:
description: 'Used to specify the path to a dependency file: packages.lock.json. Supports wildcards or a list of file names for caching multiple dependencies.'
required: false
outputs: outputs:
cache-hit:
description: 'A boolean value to indicate if a cache was hit.'
dotnet-version: dotnet-version:
description: 'Contains the installed by action .NET SDK version for reuse.' description: 'Contains the installed by action .NET SDK version for reuse.'
runs: runs:
using: 'node16' using: 'node16'
main: 'dist/index.js' main: 'dist/setup/index.js'
post: 'dist/cache-save/index.js'
post-if: success()

89370
dist/cache-save/index.js vendored Normal file

File diff suppressed because one or more lines are too long

21305
dist/index.js vendored

File diff suppressed because one or more lines are too long

102276
dist/setup/index.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -298,11 +298,20 @@ get_machine_architecture() {
if command -v uname > /dev/null; then if command -v uname > /dev/null; then
CPUName=$(uname -m) CPUName=$(uname -m)
case $CPUName in case $CPUName in
armv1*|armv2*|armv3*|armv4*|armv5*|armv6*)
echo "armv6-or-below"
return 0
;;
armv*l) armv*l)
echo "arm" echo "arm"
return 0 return 0
;; ;;
aarch64|arm64) aarch64|arm64)
if [ "$(getconf LONG_BIT)" -lt 64 ]; then
# This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS)
echo "arm"
return 0
fi
echo "arm64" echo "arm64"
return 0 return 0
;; ;;
@@ -310,6 +319,22 @@ get_machine_architecture() {
echo "s390x" echo "s390x"
return 0 return 0
;; ;;
ppc64le)
echo "ppc64le"
return 0
;;
loongarch64)
echo "loongarch64"
return 0
;;
riscv64)
echo "riscv64"
return 0
;;
powerpc|ppc)
echo "ppc"
return 0
;;
esac esac
fi fi
@@ -326,7 +351,13 @@ get_normalized_architecture_from_architecture() {
local architecture="$(to_lowercase "$1")" local architecture="$(to_lowercase "$1")"
if [[ $architecture == \<auto\> ]]; then if [[ $architecture == \<auto\> ]]; then
echo "$(get_machine_architecture)" machine_architecture="$(get_machine_architecture)"
if [[ "$machine_architecture" == "armv6-or-below" ]]; then
say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
return 1
fi
echo $machine_architecture
return 0 return 0
fi fi
@@ -347,6 +378,14 @@ get_normalized_architecture_from_architecture() {
echo "s390x" echo "s390x"
return 0 return 0
;; ;;
ppc64le)
echo "ppc64le"
return 0
;;
loongarch64)
echo "loongarch64"
return 0
;;
esac esac
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
@@ -384,11 +423,17 @@ get_normalized_architecture_for_specific_sdk_version() {
# args: # args:
# version or channel - $1 # version or channel - $1
is_arm64_supported() { is_arm64_supported() {
#any channel or version that starts with the specified versions # Extract the major version by splitting on the dot
case "$1" in major_version="${1%%.*}"
( "1"* | "2"* | "3"* | "4"* | "5"*)
echo false # Check if the major version is a valid number and less than 6
return 0 case "$major_version" in
[0-9]*)
if [ "$major_version" -lt 6 ]; then
echo false
return 0
fi
;;
esac esac
echo true echo true
@@ -407,8 +452,13 @@ get_normalized_os() {
echo "$osname" echo "$osname"
return 0 return 0
;; ;;
macos)
osname='osx'
echo "$osname"
return 0
;;
*) *)
say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
return 1 return 1
;; ;;
esac esac
@@ -538,6 +588,40 @@ is_dotnet_package_installed() {
fi fi
} }
# args:
# downloaded file - $1
# remote_file_size - $2
validate_remote_local_file_sizes()
{
eval $invocation
local downloaded_file="$1"
local remote_file_size="$2"
local file_size=''
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
file_size="$(stat -c '%s' "$downloaded_file")"
elif [[ "$OSTYPE" == "darwin"* ]]; then
# hardcode in order to avoid conflicts with GNU stat
file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
fi
if [ -n "$file_size" ]; then
say "Downloaded file size is $file_size bytes."
if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
if [ "$remote_file_size" -ne "$file_size" ]; then
say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted."
else
say "The remote and local file sizes are equal."
fi
fi
else
say "Either downloaded or local package size can not be measured. One of them may be corrupted."
fi
}
# args: # args:
# azure_feed - $1 # azure_feed - $1
# channel - $2 # channel - $2
@@ -872,6 +956,37 @@ get_absolute_path() {
return 0 return 0
} }
# args:
# override - $1 (boolean, true or false)
get_cp_options() {
eval $invocation
local override="$1"
local override_switch=""
if [ "$override" = false ]; then
override_switch="-n"
# create temporary files to check if 'cp -u' is supported
tmp_dir="$(mktemp -d)"
tmp_file="$tmp_dir/testfile"
tmp_file2="$tmp_dir/testfile2"
touch "$tmp_file"
# use -u instead of -n if it's available
if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then
override_switch="-u"
fi
# clean up
rm -f "$tmp_file" "$tmp_file2"
rm -rf "$tmp_dir"
fi
echo "$override_switch"
}
# args: # args:
# input_files - stdin # input_files - stdin
# root_path - $1 # root_path - $1
@@ -883,15 +998,7 @@ copy_files_or_dirs_from_list() {
local root_path="$(remove_trailing_slash "$1")" local root_path="$(remove_trailing_slash "$1")"
local out_path="$(remove_trailing_slash "$2")" local out_path="$(remove_trailing_slash "$2")"
local override="$3" local override="$3"
local osname="$(get_current_os_name)" local override_switch="$(get_cp_options "$override")"
local override_switch=$(
if [ "$override" = false ]; then
if [ "$osname" = "linux-musl" ]; then
printf -- "-u";
else
printf -- "-n";
fi
fi)
cat | uniq | while read -r file_path; do cat | uniq | while read -r file_path; do
local path="$(remove_beginning_slash "${file_path#$root_path}")" local path="$(remove_beginning_slash "${file_path#$root_path}")"
@@ -906,14 +1013,39 @@ copy_files_or_dirs_from_list() {
done done
} }
# args:
# zip_uri - $1
get_remote_file_size() {
local zip_uri="$1"
if machine_has "curl"; then
file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
elif machine_has "wget"; then
file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
else
say "Neither curl nor wget is available on this system."
return
fi
if [ -n "$file_size" ]; then
say "Remote file $zip_uri size is $file_size bytes."
echo "$file_size"
else
say_verbose "Content-Length header was not extracted for $zip_uri."
echo ""
fi
}
# args: # args:
# zip_path - $1 # zip_path - $1
# out_path - $2 # out_path - $2
# remote_file_size - $3
extract_dotnet_package() { extract_dotnet_package() {
eval $invocation eval $invocation
local zip_path="$1" local zip_path="$1"
local out_path="$2" local out_path="$2"
local remote_file_size="$3"
local temp_out_path="$(mktemp -d "$temporary_file_template")" local temp_out_path="$(mktemp -d "$temporary_file_template")"
@@ -923,9 +1055,13 @@ extract_dotnet_package() {
local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
rm -rf "$temp_out_path" rm -rf "$temp_out_path"
rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed" if [ -z ${keep_zip+x} ]; then
rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed"
fi
if [ "$failed" = true ]; then if [ "$failed" = true ]; then
say_err "Extraction failed" say_err "Extraction failed"
@@ -1136,6 +1272,61 @@ downloadwget() {
return 0 return 0
} }
extract_stem() {
local url="$1"
# extract the protocol
proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
# remove the protocol
url="${1/$proto/}"
# extract the path (if any) - since we know all of our feeds have a first path segment, we can skip the first one. otherwise we'd use -f2- to get the full path
full_path="$(echo $url | grep / | cut -d/ -f2-)"
path="$(echo $full_path | cut -d/ -f2-)"
echo $path
}
check_url_exists() {
eval $invocation
local url="$1"
local code=""
if machine_has "curl"
then
code=$(curl --head -o /dev/null -w "%{http_code}" -s --fail "$url");
elif machine_has "wget"
then
# get the http response, grab the status code
server_response=$(wget -qO- --method=HEAD --server-response "$url" 2>&1)
code=$(echo "$server_response" | grep "HTTP/" | awk '{print $2}')
fi
if [ $code = "200" ]; then
return 0
else
return 1
fi
}
sanitize_redirect_url() {
eval $invocation
local url_stem
url_stem=$(extract_stem "$1")
say_verbose "Checking configured feeds for the asset at ${yellow:-}$url_stem${normal:-}"
for feed in "${feeds[@]}"
do
local trial_url="$feed/$url_stem"
say_verbose "Checking ${yellow:-}$trial_url${normal:-}"
if check_url_exists "$trial_url"; then
say_verbose "Found a match at ${yellow:-}$trial_url${normal:-}"
echo "$trial_url"
return 0
else
say_verbose "No match at ${yellow:-}$trial_url${normal:-}"
fi
done
return 1
}
get_download_link_from_aka_ms() { get_download_link_from_aka_ms() {
eval $invocation eval $invocation
@@ -1172,6 +1363,12 @@ get_download_link_from_aka_ms() {
http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
# They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
# The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused.
# In this case it should not exclude the last.
last_http_code=$( echo "$http_codes" | tail -n 1 )
if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then
broken_redirects=$( echo "$http_codes" | grep -v '301' )
fi
# All HTTP codes are 301 (Moved Permanently), the redirect link exists. # All HTTP codes are 301 (Moved Permanently), the redirect link exists.
if [[ -z "$broken_redirects" ]]; then if [[ -z "$broken_redirects" ]]; then
@@ -1182,6 +1379,11 @@ get_download_link_from_aka_ms() {
return 1 return 1
fi fi
sanitized_redirect_url=$(sanitize_redirect_url "$aka_ms_download_link")
if [[ -n "$sanitized_redirect_url" ]]; then
aka_ms_download_link="$sanitized_redirect_url"
fi
say_verbose "The redirect location retrieved: '$aka_ms_download_link'." say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
return 0 return 0
else else
@@ -1193,23 +1395,16 @@ get_download_link_from_aka_ms() {
get_feeds_to_use() get_feeds_to_use()
{ {
feeds=( feeds=(
"https://dotnetcli.azureedge.net/dotnet" "https://builds.dotnet.microsoft.com/dotnet"
"https://dotnetbuilds.azureedge.net/public" "https://ci.dot.net/public"
) )
if [[ -n "$azure_feed" ]]; then if [[ -n "$azure_feed" ]]; then
feeds=("$azure_feed") feeds=("$azure_feed")
fi fi
if [[ "$no_cdn" == "true" ]]; then if [[ -n "$uncached_feed" ]]; then
feeds=( feeds=("$uncached_feed")
"https://dotnetcli.blob.core.windows.net/dotnet"
"https://dotnetbuilds.blob.core.windows.net/public"
)
if [[ -n "$uncached_feed" ]]; then
feeds=("$uncached_feed")
fi
fi fi
} }
@@ -1341,7 +1536,7 @@ generate_regular_links() {
link_types+=("legacy") link_types+=("legacy")
else else
legacy_download_link="" legacy_download_link=""
say_verbose "Cound not construct a legacy_download_link; omitting..." say_verbose "Could not construct a legacy_download_link; omitting..."
fi fi
# Check if the SDK version is already installed. # Check if the SDK version is already installed.
@@ -1419,10 +1614,11 @@ install_dotnet() {
eval $invocation eval $invocation
local download_failed=false local download_failed=false
local download_completed=false local download_completed=false
local remote_file_size=0
mkdir -p "$install_root" mkdir -p "$install_root"
zip_path="$(mktemp "$temporary_file_template")" zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
say_verbose "Zip path: $zip_path" say_verbose "Archive path: $zip_path"
for link_index in "${!download_links[@]}" for link_index in "${!download_links[@]}"
do do
@@ -1443,10 +1639,10 @@ install_dotnet() {
say "The resource at $link_type link '$download_link' is not available." say "The resource at $link_type link '$download_link' is not available."
;; ;;
*) *)
say "Failed to download $link_type link '$download_link': $download_error_msg" say "Failed to download $link_type link '$download_link': $http_code $download_error_msg"
;; ;;
esac esac
rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed" rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed"
else else
download_completed=true download_completed=true
break break
@@ -1459,8 +1655,10 @@ install_dotnet() {
return 1 return 1
fi fi
say "Extracting zip from $download_link" remote_file_size="$(get_remote_file_size "$download_link")"
extract_dotnet_package "$zip_path" "$install_root" || return 1
say "Extracting archive from $download_link"
extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
# Check if the SDK version is installed; if not, fail the installation. # Check if the SDK version is installed; if not, fail the installation.
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
@@ -1502,7 +1700,6 @@ install_dir="<auto>"
architecture="<auto>" architecture="<auto>"
dry_run=false dry_run=false
no_path=false no_path=false
no_cdn=false
azure_feed="" azure_feed=""
uncached_feed="" uncached_feed=""
feed_credential="" feed_credential=""
@@ -1575,10 +1772,6 @@ do
verbose=true verbose=true
non_dynamic_parameters+=" $name" non_dynamic_parameters+=" $name"
;; ;;
--no-cdn|-[Nn]o[Cc]dn)
no_cdn=true
non_dynamic_parameters+=" $name"
;;
--azure-feed|-[Aa]zure[Ff]eed) --azure-feed|-[Aa]zure[Ff]eed)
shift shift
azure_feed="$1" azure_feed="$1"
@@ -1610,10 +1803,22 @@ do
override_non_versioned_files=false override_non_versioned_files=false
non_dynamic_parameters+=" $name" non_dynamic_parameters+=" $name"
;; ;;
--keep-zip|-[Kk]eep[Zz]ip)
keep_zip=true
non_dynamic_parameters+=" $name"
;;
--zip-path|-[Zz]ip[Pp]ath)
shift
zip_path="$1"
;;
-?|--?|-h|--help|-[Hh]elp) -?|--?|-h|--help|-[Hh]elp)
script_name="$(basename "$0")" script_name="dotnet-install.sh"
echo ".NET Tools Installer" echo ".NET Tools Installer"
echo "Usage: $script_name [-c|--channel <CHANNEL>] [-v|--version <VERSION>] [-p|--prefix <DESTINATION>]" echo "Usage:"
echo " # Install a .NET SDK of a given Quality from a given Channel"
echo " $script_name [-c|--channel <CHANNEL>] [-q|--quality <QUALITY>]"
echo " # Install a .NET SDK of a specific public version"
echo " $script_name [-v|--version <VERSION>]"
echo " $script_name -h|-?|--help" echo " $script_name -h|-?|--help"
echo "" echo ""
echo "$script_name is a simple command line interface for obtaining dotnet cli." echo "$script_name is a simple command line interface for obtaining dotnet cli."
@@ -1655,7 +1860,7 @@ do
echo " -InstallDir" echo " -InstallDir"
echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
echo " --arch,-Architecture,-Arch" echo " --arch,-Architecture,-Arch"
echo " Possible values: x64, arm, arm64 and s390x" echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
echo " --os <system> Specifies operating system to be used when selecting the installer." echo " --os <system> Specifies operating system to be used when selecting the installer."
echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." echo " In case any other value is provided, the platform will be determined by the script based on machine configuration."
@@ -1671,15 +1876,14 @@ do
echo " --verbose,-Verbose Display diagnostics information." echo " --verbose,-Verbose Display diagnostics information."
echo " --azure-feed,-AzureFeed For internal use only." echo " --azure-feed,-AzureFeed For internal use only."
echo " Allows using a different storage to download SDK archives from." echo " Allows using a different storage to download SDK archives from."
echo " This parameter is only used if --no-cdn is false."
echo " --uncached-feed,-UncachedFeed For internal use only." echo " --uncached-feed,-UncachedFeed For internal use only."
echo " Allows using a different storage to download SDK archives from." echo " Allows using a different storage to download SDK archives from."
echo " This parameter is only used if --no-cdn is true."
echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable."
echo " -SkipNonVersionedFiles" echo " -SkipNonVersionedFiles"
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file." echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
echo " Note: global.json must have a value for 'SDK:Version'" echo " Note: global.json must have a value for 'SDK:Version'"
echo " --keep-zip,-KeepZip If set, downloaded file is kept."
echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path."
echo " -?,--?,-h,--help,-Help Shows this help message" echo " -?,--?,-h,--help,-Help Shows this help message"
echo "" echo ""
echo "Install Location:" echo "Install Location:"

3562
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"version": "3.0.2", "version": "3.0.2",
"private": true, "private": true,
"description": "setup dotnet action", "description": "setup dotnet action",
"main": "lib/setup-dotnet.js", "main": "dist/setup/index.js",
"scripts": { "scripts": {
"build": "tsc && ncc build", "build": "ncc build -o dist/setup src/setup-dotnet.ts && ncc build -o dist/cache-save src/cache-save.ts",
"format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"", "format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"", "format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint --config ./.eslintrc.js \"**/*.ts\"", "lint": "eslint --config ./.eslintrc.js \"**/*.ts\"",
@@ -26,27 +26,31 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^4.0.3",
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@actions/exec": "^1.0.4", "@actions/exec": "^1.1.1",
"@actions/github": "^1.1.0", "@actions/github": "^1.1.0",
"@actions/glob": "^0.3.0",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.2", "@actions/io": "^1.0.2",
"fast-xml-parser": "^4.0.10", "fast-xml-parser": "^4.0.10",
"semver": "^6.3.0" "semver": "^6.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^27.5.2",
"@types/node": "^16.11.25", "@types/node": "^16.11.25",
"@types/semver": "^6.2.2", "@types/semver": "^6.2.2",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0", "@typescript-eslint/parser": "^5.54.0",
"@vercel/ncc": "^0.33.4", "@vercel/ncc": "^0.34.0",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-node": "^11.1.0",
"husky": "^8.0.1", "husky": "^8.0.1",
"jest": "^27.2.5", "jest": "^27.5.1",
"jest-circus": "^27.2.5", "jest-circus": "^27.5.1",
"jest-each": "^27.5.1",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"typescript": "^4.8.4", "typescript": "^4.8.4",

50
src/cache-restore.ts Normal file
View File

@@ -0,0 +1,50 @@
import {readdir} from 'node:fs/promises';
import {join} from 'node:path';
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as glob from '@actions/glob';
import {getNuGetFolderPath} from './cache-utils';
import {lockFilePatterns, State, Outputs} from './constants';
export const restoreCache = async (cacheDependencyPath?: string) => {
const lockFilePath = cacheDependencyPath || (await findLockFile());
const fileHash = await glob.hashFiles(lockFilePath);
if (!fileHash) {
throw new Error(
'Some specified paths were not resolved, unable to cache dependencies.'
);
}
const platform = process.env.RUNNER_OS;
const primaryKey = `dotnet-cache-${platform}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
core.saveState(State.CachePrimaryKey, primaryKey);
const {'global-packages': cachePath} = await getNuGetFolderPath();
const cacheKey = await cache.restoreCache([cachePath], primaryKey);
core.setOutput(Outputs.CacheHit, Boolean(cacheKey));
if (!cacheKey) {
core.info('Dotnet cache is not found');
return;
}
core.saveState(State.CacheMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
};
const findLockFile = async () => {
const workspace = process.env.GITHUB_WORKSPACE!;
const rootContent = await readdir(workspace);
const lockFile = lockFilePatterns.find(item => rootContent.includes(item));
if (!lockFile) {
throw new Error(
`Dependencies lock file is not found in ${workspace}. Supported file patterns: ${lockFilePatterns.toString()}`
);
}
return join(workspace, lockFile);
};

57
src/cache-save.ts Normal file
View File

@@ -0,0 +1,57 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import fs from 'node:fs';
import {getNuGetFolderPath} from './cache-utils';
import {State} from './constants';
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
// throw an uncaught exception. Instead of failing this action, just warn.
process.on('uncaughtException', e => {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${e.message}`);
});
export async function run() {
try {
if (core.getBooleanInput('cache')) {
await cachePackages();
}
} catch (error) {
core.setFailed(error.message);
}
}
const cachePackages = async () => {
const state = core.getState(State.CacheMatchedKey);
const primaryKey = core.getState(State.CachePrimaryKey);
if (!primaryKey) {
core.info('Primary key was not generated, not saving cache.');
return;
}
const {'global-packages': cachePath} = await getNuGetFolderPath();
if (!fs.existsSync(cachePath)) {
throw new Error(
`Cache folder path is retrieved for .NET CLI but doesn't exist on disk: ${cachePath}`
);
}
if (primaryKey === state) {
core.info(
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
);
return;
}
const cacheId = await cache.saveCache([cachePath], primaryKey);
if (cacheId == -1) {
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
};
run();

98
src/cache-utils.ts Normal file
View File

@@ -0,0 +1,98 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import {cliCommand} from './constants';
type NuGetFolderName =
| 'http-cache'
| 'global-packages'
| 'temp'
| 'plugins-cache';
/**
* Get NuGet global packages, cache, and temp folders from .NET CLI.
* @returns (Folder Name)-(Path) mappings
* @see https://docs.microsoft.com/nuget/consume-packages/managing-the-global-packages-and-cache-folders
* @example
* Windows
* ```json
* {
* "http-cache": "C:\\Users\\user1\\AppData\\Local\\NuGet\\v3-cache",
* "global-packages": "C:\\Users\\user1\\.nuget\\packages\\",
* "temp": "C:\\Users\\user1\\AppData\\Local\\Temp\\NuGetScratch",
* "plugins-cache": "C:\\Users\\user1\\AppData\\Local\\NuGet\\plugins-cache"
* }
* ```
*
* Mac/Linux
* ```json
* {
* "http-cache": "/home/user1/.local/share/NuGet/v3-cache",
* "global-packages": "/home/user1/.nuget/packages/",
* "temp": "/tmp/NuGetScratch",
* "plugins-cache": "/home/user1/.local/share/NuGet/plugins-cache"
* }
* ```
*/
export const getNuGetFolderPath = async () => {
const {stdout, stderr, exitCode} = await exec.getExecOutput(
cliCommand,
undefined,
{ignoreReturnCode: true, silent: true}
);
if (exitCode) {
throw new Error(
!stderr.trim()
? `The '${cliCommand}' command failed with exit code: ${exitCode}`
: stderr
);
}
const result: Record<NuGetFolderName, string> = {
'http-cache': '',
'global-packages': '',
temp: '',
'plugins-cache': ''
};
const regex = /(?:^|\s)(?<key>[a-z-]+): (?<path>.+[/\\].+)$/gm;
let match: RegExpExecArray | null;
while ((match = regex.exec(stdout)) !== null) {
const key = match.groups!.key;
if ((key as NuGetFolderName) in result) {
result[key] = match.groups!.path;
}
}
return result;
};
export function isCacheFeatureAvailable(): boolean {
if (cache.isFeatureAvailable()) {
return true;
}
if (isGhes()) {
core.warning(
'Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.'
);
return false;
}
core.warning(
'The runner was not able to contact the cache service. Caching will be skipped'
);
return false;
}
/**
* Returns this action runs on GitHub Enterprise Server or not.
* (port from https://github.com/actions/toolkit/blob/457303960f03375db6f033e214b9f90d79c3fe5c/packages/cache/src/internal/cacheUtils.ts#L134)
*/
function isGhes(): boolean {
const url = process.env['GITHUB_SERVER_URL'] || 'https://github.com';
return new URL(url).hostname.toUpperCase() !== 'GITHUB.COM';
}

19
src/constants.ts Normal file
View File

@@ -0,0 +1,19 @@
/** NuGet lock file patterns */
export const lockFilePatterns = ['packages.lock.json'];
/**
* .NET CLI command to list local NuGet resources.
* @see https://docs.microsoft.com/dotnet/core/tools/dotnet-nuget-locals
*/
export const cliCommand =
'dotnet nuget locals all --list --force-english-output';
export enum State {
CachePrimaryKey = 'CACHE_KEY',
CacheMatchedKey = 'CACHE_RESULT'
}
export enum Outputs {
CacheHit = 'cache-hit',
DotnetVersion = 'dotnet-version'
}

View File

@@ -101,9 +101,11 @@ export class DotnetVersionResolver {
allowRetries: true, allowRetries: true,
maxRetries: 3 maxRetries: 3
}); });
const response = await httpClient.getJson<any>( const response = await httpClient.getJson<any>(
DotnetVersionResolver.DotNetCoreIndexUrl DotnetVersionResolver.DotNetCoreIndexUrl
); );
const result = response.result || {}; const result = response.result || {};
const releasesInfo: any[] = result['releases-index']; const releasesInfo: any[] = result['releases-index'];
@@ -122,7 +124,7 @@ export class DotnetVersionResolver {
} }
static DotNetCoreIndexUrl = static DotNetCoreIndexUrl =
'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json'; 'https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json';
} }
export class DotnetCoreInstaller { export class DotnetCoreInstaller {
@@ -203,7 +205,7 @@ export class DotnetCoreInstaller {
]; ];
const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh';
const escapedScript = path const escapedScript = path
.join(__dirname, '..', 'externals', scriptName) .join(__dirname, '..', '..', 'externals', scriptName)
.replace(/'/g, "''"); .replace(/'/g, "''");
let scriptArguments: string[]; let scriptArguments: string[];
let scriptPath = ''; let scriptPath = '';

View File

@@ -4,6 +4,9 @@ import * as fs from 'fs';
import path from 'path'; import path from 'path';
import semver from 'semver'; import semver from 'semver';
import * as auth from './authutil'; import * as auth from './authutil';
import {isCacheFeatureAvailable} from './cache-utils';
import {restoreCache} from './cache-restore';
import {Outputs} from './constants';
const qualityOptions = [ const qualityOptions = [
'daily', 'daily',
@@ -80,7 +83,12 @@ export async function run() {
outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); outputInstalledVersion(installedDotnetVersions, globalJsonFileInput);
const matchersPath = path.join(__dirname, '..', '.github'); if (core.getBooleanInput('cache') && isCacheFeatureAvailable()) {
const cacheDependencyPath = core.getInput('cache-dependency-path');
await restoreCache(cacheDependencyPath);
}
const matchersPath = path.join(__dirname, '..', '..', '.github');
core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`); core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`);
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
@@ -109,20 +117,20 @@ function outputInstalledVersion(
globalJsonFileInput: string globalJsonFileInput: string
): void { ): void {
if (!installedVersions.length) { if (!installedVersions.length) {
core.info(`The 'dotnet-version' output will not be set.`); core.info(`The '${Outputs.DotnetVersion}' output will not be set.`);
return; return;
} }
if (installedVersions.includes(null)) { if (installedVersions.includes(null)) {
core.warning( core.warning(
`Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.` `Failed to output the installed version of .NET. The '${Outputs.DotnetVersion}' output will not be set.`
); );
return; return;
} }
if (globalJsonFileInput) { if (globalJsonFileInput) {
const versionToOutput = installedVersions.at(-1); // .NET SDK version parsed from the global.json file is installed last const versionToOutput = installedVersions.at(-1); // .NET SDK version parsed from the global.json file is installed last
core.setOutput('dotnet-version', versionToOutput); core.setOutput(Outputs.DotnetVersion, versionToOutput);
return; return;
} }
@@ -134,7 +142,7 @@ function outputInstalledVersion(
} }
); );
core.setOutput('dotnet-version', versionToOutput); core.setOutput(Outputs.DotnetVersion, versionToOutput);
} }
run(); run();