Compare commits

...

45 Commits

Author SHA1 Message Date
HarithaVattikuti
4d6c8fcf3c Upgrade Node to v20 (#484)
* Node Version upgrade to 20

* 4.0.0

* Action Files upgrade to 20

* Updated version for outdated

* Update Format check

* Auth Test file update
2023-12-04 19:47:27 +05:30
Dmitry Shibanov
2216f56ae1 Update semver (#468) 2023-09-27 13:21:23 +02:00
Nikolai Laevskii
736bc6dea6 Merge pull request #462 from akv-platform/update-installers
Update installers
2023-09-14 11:01:31 +02:00
Nikolai Laevskii
f5526c2d1b Update installers 2023-09-14 10:50:07 +02:00
dependabot[bot]
4dba25fa31 Bump word-wrap from 1.2.3 to 1.2.4 (#444)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-27 13:49:28 +02:00
dependabot[bot]
ada8800330 Bump semver from 6.3.0 to 6.3.1 (#443) 2023-07-18 15:41:43 +02:00
Ivan
e1749123a8 Update dependencies (#440)
* chore: update version of the fast-xml-parser

* chore: update tough-cookie and @azure/ms-rest-js

* chore: update license for the @azure/ms-rest-js
2023-07-10 17:20:56 +02:00
Ivan
a6be55a915 Update fast-xml-parser and dotnet installer scripts (#437)
* chore: update fast-xlm-parser and dotnet installer scripts

* chore: update license for the fast-xml-parser
2023-06-23 09:47:16 +02:00
Nikolai Laevskii
9b40770825 Merge pull request #433 from akv-platform/sequential-version-install-fix
Sequential version install fix
2023-06-06 13:43:07 +02:00
Nikolai Laevskii
48277343a5 Reduce number of e2e tests 2023-06-06 13:23:53 +02:00
Nikolai Laevskii
70fa3206c9 Merge pull request #431 from akv-platform/allow-json-comments
Allow json comments
2023-06-06 13:17:59 +02:00
Nikolai Laevskii
7ed547ca75 Merge pull request #432 from akv-platform/refactor-installer
Refactor installer
2023-06-06 13:17:23 +02:00
Nikolai Laevskii
b5ebe9e10a Update license 2023-05-31 12:28:26 +02:00
Nikolai Laevskii
89f1766d87 Add test for global.json with comments 2023-05-31 12:28:10 +02:00
Nikolai Laevskii
d1c99df34e Parse global.json with JSON5 2023-05-31 12:27:52 +02:00
Nikolai Laevskii
faa708d00b Fix e2e tests 2023-05-31 11:21:34 +02:00
Nikolai Laevskii
ea6d1c26e7 Add more comprehensive testing 2023-05-31 11:21:34 +02:00
Nikolai Laevskii
9471c5df0b Update e2e tests 2023-05-31 11:21:34 +02:00
Nikolai Laevskii
6eb2af61b6 Update tests to accomodate for changes 2023-05-31 11:21:34 +02:00
Nikolai Laevskii
8f71719d12 Add dotnet runtime installation before main script run 2023-05-31 11:21:29 +02:00
Nikolai Laevskii
3cdb09485a Build 2023-05-31 11:19:26 +02:00
Nikolai Laevskii
6019612129 Merge remote-tracking branch 'github/main' into refactor-installer 2023-05-31 11:18:52 +02:00
Nikolai Laevskii
addb470701 Make setup script functions synchronous 2023-05-30 13:19:37 +02:00
Nikolai Laevskii
820f30d332 Make assigning of the script path intuitive 2023-05-30 12:54:41 +02:00
Nikolai Laevskii
427804d76a Update tests 2023-05-30 12:45:38 +02:00
Nikolai Laevskii
89b480a0df Call addToPath method on DotnetInstallDir directly 2023-05-30 12:19:28 +02:00
Nikolai Laevskii
defac2491f Rename initialize to setEnvironmentVariable 2023-05-30 12:18:10 +02:00
Nikolai Laevskii
916aec40c9 Rename DotnetInstallDir.path to DotnetInstallDir.dirPath 2023-05-30 12:14:34 +02: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
Nikolai Laevskii
2785e21d5e Update build 2023-05-25 13:27:30 +02:00
Nikolai Laevskii
80a318b8b8 Change PLATFORM to constant instead of function 2023-05-25 13:11:13 +02:00
Nikolai Laevskii
eb0b7f8852 Update build 2023-05-24 17:11:47 +02:00
Nikolai Laevskii
5c7ae4f903 Apply consistent naming 2023-05-24 16:59:05 +02:00
Nikolai Laevskii
b7461a1b69 Remove excessive whitespace 2023-05-24 16:41:16 +02:00
Nikolai Laevskii
cce8e1bad1 Merge branch 'main' into refactor-installer 2023-05-24 16:40:29 +02:00
Nikolai Laevskii
df506c2d9c Refactor convertInstallPathToAbsolute 2023-05-24 15:28:40 +02:00
Nikolai Laevskii
abf9166e44 build: Fix DOTNET_INSTALL_DIR enviornment variable 2023-05-24 15:27:58 +02:00
Nikolai Laevskii
7e164d3c3b Fix DOTNET_INSTALL_DIR enviornment variable 2023-05-24 15:27:12 +02:00
Nikolai Laevskii
3dfe2673eb Format: Refactor installer 2023-05-24 15:22:01 +02:00
Nikolai Laevskii
aa85432603 Refactor installer 2023-05-12 16:28:16 +02:00
Nikolai Laevskii
6d92b9bd53 Refactor install dir computation 2023-05-12 14:07:46 +02:00
Nikolai Laevskii
dd32dd730c Update installers 2023-05-12 13:35:45 +02:00
109 changed files with 183438 additions and 28833 deletions

View File

@@ -15,3 +15,5 @@ jobs:
call-basic-validation:
name: Basic validation
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main
with:
node-version: '20'

View File

@@ -15,3 +15,5 @@ jobs:
call-check-dist:
name: Check dist/
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
with:
node-version: '20'

View File

@@ -229,6 +229,31 @@ jobs:
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^2.2"
test-global-json-with-comments:
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: Write global.json
shell: bash
run: |
mkdir subdirectory
echo '/* should support comments */ {"sdk":{"version": "2.2.207","rollForward": "latestFeature"}} // should support comments' > ./subdirectory/global.json
- name: Setup dotnet
uses: ./
with:
global-json-file: ./subdirectory/global.json
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^2.2"
test-setup-with-dotnet-quality:
runs-on: ${{ matrix.operating-system }}
strategy:
@@ -251,6 +276,66 @@ jobs:
shell: pwsh
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-latest, 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-latest, 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:
runs-on: ${{ matrix.operating-system }}
strategy:
@@ -366,3 +451,33 @@ jobs:
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$" -CheckNugetConfig
test-sequential-version-installation:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest]
lower-version: ['3.1.426']
higher-version: ['7.0.203']
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
# Install one version, use it for something, then switch to next version
- name: Setup dotnet (lower version)
uses: ./
with:
dotnet-version: ${{ matrix.lower-version }}
- name: Verify dotnet (lower version)
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.lower-version }}$"
- name: Setup dotnet (higher version)
uses: ./
with:
dotnet-version: ${{ matrix.higher-version }}
- name: Verify dotnet (higher version)
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.lower-version }}$", "^${{ matrix.higher-version }}$"

View File

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

Binary file not shown.

Binary file not shown.

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

BIN
.licenses/npm/@opentelemetry/api.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/balanced-match.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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.

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-4.0.0.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/process.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.

Binary file not shown.

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.6.2.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/undici-types.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/yallist.dep.yml generated Normal file

Binary file not shown.

View File

@@ -82,6 +82,63 @@ steps:
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
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
@@ -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
```
### `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
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_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*|
| 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:
| **Operation system** | **Default value** |
| ----------- | ----------- |
| **Windows** | `C:\Program Files\dotnet` |
| **Ubuntu** | `/usr/share/dotnet` |
| **macOS** | `/Users/runner/.dotnet` |
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** | `DOTNET_INSTALL_DIR` | `NUGET_PACKAGES` |
| ----------- | ----------- | ----------- |
| **Windows** | `C:\Program Files\dotnet` | `%userprofile%\.nuget\packages` |
| **Ubuntu** | `/usr/share/dotnet` | `~/.nuget/packages` |
| **macOS** | `/Users/runner/.dotnet` | `~/.nuget/packages` |
**Example usage**:
**Example usage of environment variable**:
```yml
build:
runs-on: ubuntu-latest
env:
DOTNET_INSTALL_DIR: "path/to/directory"
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- uses: actions/checkout@main
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '3.1.x'
cache: true
```
## License

View File

@@ -1,184 +1,184 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`authutil tests existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSourceCredentials>
<GPR>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</GPR>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json\\"/>
<add key="defaultPushSource" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json"/>
</config>
<packageSourceCredentials>
<AzureArtifacts>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</AzureArtifacts>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSourceCredentials>
<GPR>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</GPR>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSources>
<add key=\\"Source\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="Source" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</packageSources>
<packageSourceCredentials>
<Source>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</Source>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSources>
<add key=\\"Source\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="Source" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</packageSources>
<packageSourceCredentials>
<Source>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</Source>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json\\"/>
<add key="defaultPushSource" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json"/>
</config>
<packageSourceCredentials>
<AzureArtifacts>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</AzureArtifacts>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSourceCredentials>
<GPR>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</GPR>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com"/>
</config>
<packageSourceCredentials>
<GPR-GitHub>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</GPR-GitHub>
<GPR-Actions>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</GPR-Actions>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests no existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/otherorg/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/otherorg/index.json"/>
</config>
<packageSources>
<add key=\\"Source\\" value=\\"https://nuget.pkg.github.com/otherorg/index.json\\"/>
<add key="Source" value="https://nuget.pkg.github.com/otherorg/index.json"/>
</packageSources>
<packageSourceCredentials>
<Source>
<add key=\\"Username\\" value=\\"otherorg\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="otherorg"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</Source>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests no existing config, sets up a full NuGet.config with URL and token for other source 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json\\"/>
<add key="defaultPushSource" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json"/>
</config>
<packageSources>
<add key=\\"Source\\" value=\\"https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json\\"/>
<add key="Source" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json"/>
</packageSources>
<packageSourceCredentials>
<Source>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</Source>
</packageSourceCredentials>
</configuration>"
`;
exports[`authutil tests no existing config, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?>
"<?xml version="1.0"?>
<configuration>
<config>
<add key=\\"defaultPushSource\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="defaultPushSource" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</config>
<packageSources>
<add key=\\"Source\\" value=\\"https://nuget.pkg.github.com/OwnerName/index.json\\"/>
<add key="Source" value="https://nuget.pkg.github.com/OwnerName/index.json"/>
</packageSources>
<packageSourceCredentials>
<Source>
<add key=\\"Username\\" value=\\"OwnerName\\"/>
<add key=\\"ClearTextPassword\\" value=\\"TEST_FAKE_AUTH_TOKEN\\"/>
<add key="Username" value="OwnerName"/>
<add key="ClearTextPassword" value="TEST_FAKE_AUTH_TOKEN"/>
</Source>
</packageSourceCredentials>
</configuration>"

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>
<TargetFramework>$(TEST_TARGET_FRAMEWORK)</TargetFramework>
<IsPackable>false</IsPackable>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,5 +1,7 @@
import each from 'jest-each';
import semver from 'semver';
import fs from 'fs';
import fspromises from 'fs/promises';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as io from '@actions/io';
@@ -21,14 +23,25 @@ describe('installer tests', () => {
const warningSpy = jest.spyOn(core, 'warning');
const whichSpy = jest.spyOn(io, 'which');
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
const chmodSyncSpy = jest.spyOn(fs, 'chmodSync');
const readdirSpy = jest.spyOn(fspromises, 'readdir');
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 () => {
const inputVersion = '3.1.100';
const inputQuality = '' as QualityOptions;
const errorMessage = 'fictitious error message!';
getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 1,
@@ -36,6 +49,7 @@ describe('installer tests', () => {
stderr: errorMessage
});
});
const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
@@ -88,8 +102,15 @@ describe('installer tests', () => {
await dotnetInstaller.installDotnet();
/**
* First time script would be called to
* install runtime, here we checking only the
* second one that installs actual SDK. i.e. 1
*/
const callIndex = 1;
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
getExecOutputSpy.mock.calls[callIndex][1] as string[]
).join(' ');
const expectedArgument = IS_WINDOWS
? `-Version ${inputVersion}`
@@ -171,8 +192,15 @@ describe('installer tests', () => {
await dotnetInstaller.installDotnet();
/**
* First time script would be called to
* install runtime, here we checking only the
* second one that installs actual SDK. i.e. 1
*/
const callIndex = 1;
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
getExecOutputSpy.mock.calls[callIndex][1] as string[]
).join(' ');
const expectedArgument = IS_WINDOWS
? `-Quality ${inputQuality}`
@@ -204,8 +232,15 @@ describe('installer tests', () => {
await dotnetInstaller.installDotnet();
/**
* First time script would be called to
* install runtime, here we checking only the
* second one that installs actual SDK. i.e. 1
*/
const callIndex = 1;
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
getExecOutputSpy.mock.calls[callIndex][1] as string[]
).join(' ');
const expectedArgument = IS_WINDOWS
? `-Channel 6.0`
@@ -238,8 +273,15 @@ describe('installer tests', () => {
await dotnetInstaller.installDotnet();
/**
* First time script would be called to
* install runtime, here we checking only the
* second one that installs actual SDK. i.e. 1
*/
const callIndex = 1;
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
getExecOutputSpy.mock.calls[callIndex][1] as string[]
).join(' ');
expect(scriptArguments).toContain(
@@ -269,8 +311,15 @@ describe('installer tests', () => {
await dotnetInstaller.installDotnet();
/**
* First time script would be called to
* install runtime, here we checking only the
* second one that installs actual SDK. i.e. 1
*/
const callIndex = 1;
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
getExecOutputSpy.mock.calls[callIndex][1] as string[]
).join(' ');
expect(scriptArguments).toContain(
@@ -283,14 +332,14 @@ describe('installer tests', () => {
describe('addToPath() tests', () => {
it(`should export DOTNET_ROOT env.var with value from DOTNET_INSTALL_DIR env.var`, async () => {
process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir';
installer.DotnetCoreInstaller.addToPath();
installer.DotnetInstallDir.addToPath();
const dotnet_root = process.env['DOTNET_ROOT'];
expect(dotnet_root).toBe(process.env['DOTNET_INSTALL_DIR']);
});
it(`should export value from DOTNET_INSTALL_DIR env.var to the PATH`, async () => {
process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir';
installer.DotnetCoreInstaller.addToPath();
installer.DotnetInstallDir.addToPath();
const path = process.env['PATH'];
expect(path).toContain(process.env['DOTNET_INSTALL_DIR']);
});
@@ -298,7 +347,7 @@ describe('installer tests', () => {
});
describe('DotnetVersionResolver tests', () => {
describe('createDotNetVersion() tests', () => {
describe('createDotnetVersion() tests', () => {
each([
'3.1',
'3.x',
@@ -315,7 +364,7 @@ describe('installer tests', () => {
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
await dotnetVersionResolver.createDotnetVersion();
expect(!!versionObject.value).toBe(true);
}
@@ -354,7 +403,7 @@ describe('installer tests', () => {
);
await expect(
async () => await dotnetVersionResolver.createDotNetVersion()
async () => await dotnetVersionResolver.createDotnetVersion()
).rejects.toThrow();
}
);
@@ -366,7 +415,7 @@ describe('installer tests', () => {
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
await dotnetVersionResolver.createDotnetVersion();
expect(versionObject.type.toLowerCase().includes('channel')).toBe(
true
@@ -381,7 +430,7 @@ describe('installer tests', () => {
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
await dotnetVersionResolver.createDotnetVersion();
expect(versionObject.type.toLowerCase().includes('channel')).toBe(
true
@@ -397,7 +446,7 @@ describe('installer tests', () => {
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
await dotnetVersionResolver.createDotnetVersion();
expect(versionObject.type.toLowerCase().includes('version')).toBe(
true
@@ -413,7 +462,7 @@ describe('installer tests', () => {
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
await dotnetVersionResolver.createDotnetVersion();
const windowsRegEx = new RegExp(/^-(Version|Channel)/);
const nonWindowsRegEx = new RegExp(/^--(version|channel)/);
@@ -433,7 +482,7 @@ describe('installer tests', () => {
version
);
await expect(
async () => await dotnetVersionResolver.createDotNetVersion()
async () => await dotnetVersionResolver.createDotnetVersion()
).rejects.toThrow(
`'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.`
);

View File

@@ -4,13 +4,16 @@ import semver from 'semver';
import * as auth from '../src/authutil';
import * as setup from '../src/setup-dotnet';
import {DotnetCoreInstaller} from '../src/installer';
import {DotnetCoreInstaller, DotnetInstallDir} from '../src/installer';
import * as cacheUtils from '../src/cache-utils';
import * as cacheRestore from '../src/cache-restore';
describe('setup-dotnet tests', () => {
const inputs = {} as any;
const getInputSpy = jest.spyOn(core, 'getInput');
const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput');
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
const setFailedSpy = jest.spyOn(core, 'setFailed');
const warningSpy = jest.spyOn(core, 'warning');
const debugSpy = jest.spyOn(core, 'debug');
@@ -25,17 +28,25 @@ describe('setup-dotnet tests', () => {
DotnetCoreInstaller.prototype,
'installDotnet'
);
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 addToPathOriginal = DotnetInstallDir.addToPath;
describe('run() tests', () => {
beforeEach(() => {
DotnetInstallDir.addToPath = jest.fn();
getMultilineInputSpy.mockImplementation(input => inputs[input as string]);
getInputSpy.mockImplementation(input => inputs[input as string]);
getBooleanInputSpy.mockImplementation(input => inputs[input as string]);
});
afterEach(() => {
DotnetInstallDir.addToPath = addToPathOriginal;
jest.clearAllMocks();
jest.resetAllMocks();
});
@@ -96,10 +107,9 @@ describe('setup-dotnet tests', () => {
inputs['dotnet-quality'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
addToPathSpy.mockImplementation(() => {});
await setup.run();
expect(addToPathSpy).toHaveBeenCalledTimes(1);
expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1);
});
it('should call auth.configAuthentication() if source-url input is provided', async () => {
@@ -140,10 +150,9 @@ describe('setup-dotnet tests', () => {
installDotnetSpy.mockImplementation(() =>
Promise.resolve(`${inputs['dotnet-version']}`)
);
addToPathSpy.mockImplementation(() => {});
await setup.run();
expect(setOutputSpy).toHaveBeenCalledTimes(1);
expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1);
});
it(`shouldn't call setOutput() if parsing dotnet-installer logs failed`, async () => {
@@ -151,7 +160,6 @@ describe('setup-dotnet tests', () => {
const warningMessage = `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`;
installDotnetSpy.mockImplementation(() => Promise.resolve(null));
addToPathSpy.mockImplementation(() => {});
await setup.run();
expect(warningSpy).toHaveBeenCalledWith(warningMessage);
@@ -162,12 +170,56 @@ describe('setup-dotnet tests', () => {
inputs['dotnet-version'] = [];
const warningMessage = `The 'dotnet-version' output will not be set.`;
addToPathSpy.mockImplementation(() => {});
await setup.run();
expect(infoSpy).toHaveBeenCalledWith(warningMessage);
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(''));
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(''));
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(''));
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
}
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'
config-file:
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:
cache-hit:
description: 'A boolean value to indicate if a cache was hit.'
dotnet-version:
description: 'Contains the installed by action .NET SDK version for reuse.'
runs:
using: 'node16'
main: 'dist/index.js'
using: 'node20'
main: 'dist/setup/index.js'
post: 'dist/cache-save/index.js'
post-if: success()

82493
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

95421
dist/setup/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -98,6 +98,10 @@
.PARAMETER DownloadTimeout
Determines timeout duration in seconds for dowloading of the SDK file
Default: 1200 seconds (20 minutes)
.PARAMETER KeepZip
If set, downloaded file is kept
.PARAMETER ZipPath
Use that path to store installer, generated by default
#>
[cmdletbinding()]
param(
@@ -121,7 +125,9 @@ param(
[string[]]$ProxyBypassList=@(),
[switch]$SkipNonVersionedFiles,
[switch]$NoCdn,
[int]$DownloadTimeout=1200
[int]$DownloadTimeout=1200,
[switch]$KeepZip,
[string]$ZipPath=[System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
)
Set-StrictMode -Version Latest
@@ -176,6 +182,23 @@ function Measure-Action($name, $block) {
Say-Verbose "⏱ Action '$name' took $totalSeconds seconds"
}
function Get-Remote-File-Size($zipUri) {
try {
$response = Invoke-WebRequest -Uri $zipUri -Method Head
$fileSize = $response.Headers["Content-Length"]
if ((![string]::IsNullOrEmpty($fileSize))) {
Say "Remote file $zipUri size is $fileSize bytes."
return $fileSize
}
}
catch {
Say-Verbose "Content-Length header was not extracted for $zipUri."
}
return $null
}
function Say-Invocation($Invocation) {
$command = $Invocation.MyCommand;
$args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ")
@@ -862,13 +885,15 @@ function DownloadFile($Source, [string]$OutPath) {
}
$Stream = $null
try {
$Response = GetHTTPResponse -Uri $Source
$Stream = $Response.Content.ReadAsStreamAsync().Result
$File = [System.IO.File]::Create($OutPath)
$Stream.CopyTo($File)
$File.Close()
ValidateRemoteLocalFileSizes -LocalFileOutPath $OutPath -SourceUri $Source
}
finally {
if ($null -ne $Stream) {
@@ -877,19 +902,40 @@ function DownloadFile($Source, [string]$OutPath) {
}
}
function ValidateRemoteLocalFileSizes([string]$LocalFileOutPath, $SourceUri) {
try {
$remoteFileSize = Get-Remote-File-Size -zipUri $SourceUri
$fileSize = [long](Get-Item $LocalFileOutPath).Length
Say "Downloaded file $SourceUri size is $fileSize bytes."
if ((![string]::IsNullOrEmpty($remoteFileSize)) -and !([string]::IsNullOrEmpty($fileSize)) ) {
if ($remoteFileSize -ne $fileSize) {
Say "The remote and local file sizes are not equal. Remote file size is $remoteFileSize bytes and local size is $fileSize bytes. The local package may be corrupted."
}
else {
Say "The remote and local file sizes are equal."
}
}
else {
Say "Either downloaded or local package size can not be measured. One of them may be corrupted."
}
}
catch {
Say "Either downloaded or local package size can not be measured. One of them may be corrupted."
}
}
function SafeRemoveFile($Path) {
try {
if (Test-Path $Path) {
Remove-Item $Path
Say-Verbose "The temporary file `"$Path`" was removed."
}
else
{
else {
Say-Verbose "The temporary file `"$Path`" does not exist, therefore is not removed."
}
}
catch
{
catch {
Say-Warning "Failed to remove the temporary file: `"$Path`", remove it manually."
}
}
@@ -1100,17 +1146,25 @@ function Resolve-AssetName-And-RelativePath([string] $Runtime) {
}
function Prepare-Install-Directory {
$diskSpaceWarning = "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space.";
if ($PSVersionTable.PSVersion.Major -lt 7) {
Say-Verbose $diskSpaceWarning
return
}
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
$installDrive = $((Get-Item $InstallRoot -Force).PSDrive.Name);
$diskInfo = $null
try{
try {
$diskInfo = Get-PSDrive -Name $installDrive
}
catch{
Say-Warning "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space."
catch {
Say-Warning $diskSpaceWarning
}
# The check is relevant for PS version >= 7, the result can be irrelevant for older versions. See https://github.com/PowerShell/PowerShell/issues/12442.
if ( ($null -ne $diskInfo) -and ($diskInfo.Free / 1MB -le 100)) {
throw "There is not enough disk space on drive ${installDrive}:"
}
@@ -1216,7 +1270,6 @@ if ($DryRun) {
Measure-Action "Installation directory preparation" { Prepare-Install-Directory }
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
Say-Verbose "Zip path: $ZipPath"
$DownloadSucceeded = $false
@@ -1289,7 +1342,9 @@ if (!$isAssetInstalled) {
throw "`"$assetName`" with version = $($DownloadedLink.effectiveVersion) failed to install with an unknown error."
}
SafeRemoveFile -Path $ZipPath
if (-not $KeepZip) {
SafeRemoveFile -Path $ZipPath
}
Measure-Action "Setting up shell environment" { Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot }
@@ -1300,40 +1355,40 @@ Say "Installation finished"
# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhfTi3SRn7+vyy
# uCXKPjhiawegWZ493EcaOEycbgkZcKCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCACRu+yvG+6rftW
# 7639o2K9YFU32HKgY4Dqe9C3db/p7qCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
@@ -1376,64 +1431,64 @@ Say "Installation finished"
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFmuaTXYQ37AFvsEol24fdW+
# nRqHcc1fr+VQVdqhXc/vMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPHo6D4ixtuX2mtmXYtzP7Xh
# 5SbbHtBt9hwIKfR9nNCHMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAjY5XW5Ly7TJ1OTbeIR98xU+2dmtw7L71ws+ICnQCGhj2xJDUK+5yrTfO
# 8C98l/P4ynFi33Dl8z2YElqUCuqEXbiCzz06lIL4NuibC5DV/X80ZmICR/NYd2v1
# ww7IH+7dpsHAowBBindCYpVwQ3Ea3kDWgsjPAinAysFFushSOnNWFvrF6vi2smrs
# smbrAAhEhSfLd1Pxxdw73hQ0YjM/D3F3opaybMQ0blpHhOaqtbiyYzvk0doIzBEc
# trSH4NDIc3yLNj5VbjSczpexE+hyQNY4xCtwco4bVtXhONUihv08AIKR8+sIaI7A
# mM/SWrrwGYSSSxydKqDei7biKG4jDqGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# BQAEggEAKaYy0/f2nIWjmd2w2g7hU/pz6ahK3cIahIejHpTW8JXUR3neUB9oFm8x
# GiAtgKY6zzxKsMGRJfULOEB+jV8y1TK5aAUtNWog8o7i9hl/W3JLsRtcduGhqvR8
# oYFq4xkYPDwAjklDN96cWNqWmqsUULs/jxx4Ef0o9/2Cy9FWYwvyDK/o0bdfotsl
# +cr3Aj1fIOSkrMKjEoScITOvfGCDgNqVsu+62itzX0QvIq7yW8aqJ5xd2r94IOry
# u6iMdQFYSxR7xpIaDjKLHCH8tTmKAlrFFekhaxe1WuTvNBt154Zl1U7ukSO12s1N
# ezHYEW4AoLd4MO9zmXwDZmo3RLzFHKGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB6Hzt2gUb/WZK8fvVnOocriE4rYr6mscZi3gZnBCpiigIGZBr2iMZU
# GBMyMDIzMDMzMTE1MjEwNi41MTZaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# AwQCAQUABCCiX6fcUDSacytCBP6o92QnwRIQCE6w6Se15jgm1UebNAIGZN/N9Z2v
# GBMyMDIzMDkxODEwMDUxOS4zMjJaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ
# OkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG6Hz8Z98F1vXwAAQAAAbowDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# OTIwMjAyMjE5WhcNMjMxMjE0MjAyMjE5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu
# q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f
# x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY
# r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg
# 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV
# XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq
# 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N
# VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb
# nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M
# K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5
# I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB
# ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP
# kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpEMDgyLTRC
# RkQtRUVCQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIhOFYMzkjWAE9UVnXF9hRGv
# 0xBRxc+I5Hu3hxVFXyK3u38xusEb0pLkwjgGtDsaLLbrlMxqX3tFb/3BgEPEC3L0
# wX76gD8zHt+wiBV5mq5BWop29qRrgMJKKCPcpQnSjs9B/4XMFFvrpdPicZDv43FL
# gz9fHqMq0LJDw5JAHGDS30TCY9OF43P4d44Z9lE7CaVS2pJMF3L453MXB5yYK/KD
# bilhERP1jxn2yl+tGCRguIAsMG0oeOhXaw8uSGOhS6ACSHb+ebi0038MFHyoTNhK
# f+SYo4OpSY3xP4+swBBTKDoYP1wH+CfxG6h9fymBJQPQZaqfl0riiDLjmDunQtH1
# GD64Air5k9Jdwhq5wLmSWXjyFVL+IDfOpdixJ6f5o+MhE6H4t31w+prygHmd2UHQ
# 657UGx6FNuzwC+SpAHmV76MZYac4uAhTgaP47P2eeS1ockvyhl9ya+9JzPfMkug3
# xevzFADWiLRMr066EMV7q3JSRAsnCS9GQ08C4FKPbSh8OPM33Lng0ffxANnHAAX/
# DE7cHcx7l9jaV3Acmkj7oqir4Eh2u5YxwiaTE37XaMumX2ES3PJ5NBaXq7YdLJwy
# SD+U9pk/tl4dQ1t/Eeo7uDTliOyQkD8I74xpVB0T31/67KHfkBkFVvy6wye21V+9
# IC8uSD++RgD3RwtN2kE/AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUimLm8QMeJa25
# j9MWeabI2HSvZOUwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW
# qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI
# WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY
# Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A
# tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe
# KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN
# d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3
# 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR
# CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u
# DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0
# irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci
# xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAF/I8U6hbZhvDcn9
# 6nZ6tkbSEjXPvKZ6wroaXcgstEhpgaeEwleLuPXHLzEWtuJuYz4eshmhXqFr49lb
# AcX5SN5/cEsP0xdFayb7U5P94JZd3HjFvpWRNoNBhF3SDM0A38sI2H+hjhB/VfX1
# XcZiei1ROPAyCHcBgHLyQrEu6mnb3HhbIdr8h0Ta7WFylGhLSFW6wmzKusP6aOlm
# nGSac5NMfla6lRvTYHd28rbbCgfSm1RhTgoZj+W8DTKtiEMwubHJ3mIPKmo8xtJI
# WXPnXq6XKgldrL5cynLMX/0WX65OuWbHV5GTELdfWvGV3DaZrHPUQ/UP31Keqb2x
# jVCb30LVwgbjIvYS77N1dARkN8F/9pJ1gO4IvZWMwyMlKKFGojO1f1wbjSWcA/57
# tsc+t2blrMWgSNHgzDr01jbPSupRjy3Ht9ZZs4xN02eiX3eG297NrtC6l4c/gzn2
# 0eqoqWx/uHWxmTgB0F5osBuTHOe77DyEA0uhArGlgKP91jghgt/OVHoH65g0QqCt
# gZ+36mnCEg6IOhFoFrCc0fJFGVmb1+17gEe+HRMM7jBk4O06J+IooFrI3e3PJjPr
# Qano/MyE3h+zAuBWGMDRcUlNKCDU7dGnWvH3XWwLrCCIcz+3GwRUMsLsDdPW2OVv
# 7v1eEJiMSIZ2P+M7L20Q8aznU4OAMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
@@ -1476,39 +1531,39 @@ Say "Installation finished"
# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpE
# MDgyLTRCRkQtRUVCQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAdqNHe113gCJ87aZIGa5QBUqIwvKggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOfRUdUwIhgPMjAyMzAzMzEyMDM0MjlaGA8yMDIzMDQwMTIwMzQyOVowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA59FR1QIBADAHAgEAAgIKJDAHAgEAAgIRLzAKAgUA
# 59KjVQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJlOESCa/uRR1x6GunE8
# K/WgHWTpSE31EITDOfTMvDcF4ptngCS5aOc4gfzmhNNehWfP6EOrgoSQzJYZ4YCh
# fYbHNMk56f18sq8t7y2hgR7KixcEo/4HVzeSdaOclHNc4Gn7kCGpMvpT3Xz9Lzc7
# UKWDZ0zkNKnbS8TZLNueVQwfMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# AOiyf1swIhgPMjAyMzA5MTgxNTQ4NDNaGA8yMDIzMDkxOTE1NDg0M1owdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA6LJ/WwIBADAHAgEAAgIJSjAHAgEAAgISJDAKAgUA
# 6LPQ2wIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAGK+6UVMbVgt4qWdPk/1
# tYxGjavQWgZ3LPfp9l3mh/tQK2RhpjsBgKJO+VVBXcUW3YQb5qP9g40+jrcIFlfy
# vrAK3UpbfuIZ6DJ6AayEF30fseVPvwaqjl/BJlKUL3ofsjEMcZPdpfHQv4Zdj3rr
# cWGEIG68RqDIePRRKRZEJtI0MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAAAbIwDQYJYIZIAWUDBAIB
# U3RhbXAgUENBIDIwMTACEzMAAAG6Hz8Z98F1vXwAAQAAAbowDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgXhJRuHCXk3arJvifIY3DBe9Ce9EmlP1y6U4XkgL31DkwgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX7PVyLo/nzu+htJPCG04+
# NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# IgQgYadrVhYugkNn/ywjh6tJ37ntH5tUO1WvoJ2sa5Mz6LIwgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCApVb08M25w+tYGWsmlGtp1gy1nPcqWfqgMF3nlWYVz
# BTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsm5A
# A39uqZSSAAEAAAGyMCIEIGGWlnNnYHrB5HguWG0/nJd/WvSrCogze+QCpenu3IM5
# MA0GCSqGSIb3DQEBCwUABIICADVOLTuNxeEnBOfZpb7Nv4uf91W/Ho5i99zenDSJ
# x5QHVs+bKXmgc3a7/SSsliAT3zygHc7cH4zARbCZePLTivByKmeG08Ka35eyR+FK
# awSNrI/X+eVIC6nw/egCwviBC1NAG8jHGkuScbHeiiGajvS6lp3ORML7UexMuE4w
# 9SEumoghljCLZMwCSvw+3WxhQoBEZroR8u+PID2RdD0vi85FjKPWcZZijVLqHeFi
# TnuFqwRCLTV0MV+dDCbjwXneIqV+AVlnqb9iDMr3ZhISlRcy9XJNpY5vQBj/wqUW
# vefrmpdz0LNkdtXYThPkyl3mha2KsoQi5SA9zSjlAjFgY3ppmXvi3Frbfqk+iL+f
# l/Qc4+B71jG4t28lTWKteJiHqo+6AUXK2rlAl0d74yvhO6N8lMMtXhdJc8JABYn1
# v2/KKZn5RvPFF8QP7Ac1saIe1+gUFNcsYOLaMm/xl8E6kefWwZnm5Rhm606g1AC/
# N5Wo08aAs0ymTPH91dEbmOURXLbA3vCyG7kbfgnhCs/j7oQHWaFDzEYuXDIA4ICT
# dxPUTltbq3OWdp0PAS8JSEKPQFaOoQEnPa4adrXWxMvOmel8IGqJiQ+BPOaLQG64
# Qu2tMkH/5szb1fsEnCe8SJmy5ESF+kmpnLBtJ17Y9o+9nJHF5ddFmvzy+LUaIqDN
# cOfH
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABuh8/
# GffBdb18AAEAAAG6MCIEIMHkOGC427PqmUI7Oe7xVuezks+e+hMM+17Nfgn9Gbmw
# MA0GCSqGSIb3DQEBCwUABIICAEKE7ZkmQ1xDsee8ZSZP8Kkt2YJLG3nLR32JBRu3
# uX7TPTDw9phd40N2ryva3Xjzht/JOPa0F4mg++YIwylXVIR6EqKNVLsIA/X8AGFa
# ti+AJp6qNe9grV8DBK00whojtMK8JZhufOb7LEon5rBFEnJx3g8JhCvAqXFzxw+M
# ctqJFm6+1ynuI7mKayA89TOLBmI4RviICjMZlsW3kNXRS1GryKt7H+C8y9kiLEMX
# efauGyoMO8sToIxgrq2HZF88/b+y8c3cX+Q5iazWLzMYeWUUPrqWcIbjGjIFBMl9
# weOXEAZVo6TSGDZOQkYi/FZxKWllnxVRN1S2Al5IUUvgXGl9ZpsW2DyM1S8Qxe+a
# VrxwkOWKzHlnFo1qGz0Iq9ImHVqr2dOC5bDVMu+jlOA1LiZC5aHxuxaHWBN73Wp7
# Hjy8h73drsmmiXovOWly7lWLatIuPJh00iiyBXdDtjmeDjso3aadUII5FQ1QWZ4F
# 4VWo161Gx+TxGlUt//4Hns5bn4UEGE43g9OCQuQ/WFMqdb3dHCzkkHDhWHbdpBy7
# oHHEsAdgjMdQHWfnxhCj0ZHEOupc9j1CXpQtN/B6uzsQQ/Mp34Rhsgn+/REVAwpS
# O7G69KWZrePZJiNrV/+eRn8ya6s8WNQAGB5zIQc8o+K9RGctLBOWcsRya9sqvL6r
# xUQ2
# SIG # End signature block

View File

@@ -310,6 +310,14 @@ get_machine_architecture() {
echo "s390x"
return 0
;;
ppc64le)
echo "ppc64le"
return 0
;;
loongarch64)
echo "loongarch64"
return 0
;;
esac
fi
@@ -347,6 +355,14 @@ get_normalized_architecture_from_architecture() {
echo "s390x"
return 0
;;
ppc64le)
echo "ppc64le"
return 0
;;
loongarch64)
echo "loongarch64"
return 0
;;
esac
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
@@ -407,8 +423,13 @@ get_normalized_os() {
echo "$osname"
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
;;
esac
@@ -538,6 +559,40 @@ is_dotnet_package_installed() {
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:
# azure_feed - $1
# channel - $2
@@ -906,14 +961,39 @@ copy_files_or_dirs_from_list() {
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:
# zip_path - $1
# out_path - $2
# remote_file_size - $3
extract_dotnet_package() {
eval $invocation
local zip_path="$1"
local out_path="$2"
local remote_file_size="$3"
local temp_out_path="$(mktemp -d "$temporary_file_template")"
@@ -923,9 +1003,13 @@ extract_dotnet_package() {
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 -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 -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 zip file $zip_path was removed"
fi
if [ "$failed" = true ]; then
say_err "Extraction failed"
@@ -1419,9 +1503,10 @@ install_dotnet() {
eval $invocation
local download_failed=false
local download_completed=false
local remote_file_size=0
mkdir -p "$install_root"
zip_path="$(mktemp "$temporary_file_template")"
zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
say_verbose "Zip path: $zip_path"
for link_index in "${!download_links[@]}"
@@ -1459,8 +1544,10 @@ install_dotnet() {
return 1
fi
remote_file_size="$(get_remote_file_size "$download_link")"
say "Extracting zip from $download_link"
extract_dotnet_package "$zip_path" "$install_root" || return 1
extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
# 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.
@@ -1610,6 +1697,14 @@ do
override_non_versioned_files=false
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)
script_name="$(basename "$0")"
echo ".NET Tools Installer"
@@ -1655,7 +1750,7 @@ do
echo " -InstallDir"
echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
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 " 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."
@@ -1680,6 +1775,8 @@ do
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 " 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 ""
echo "Install Location:"
@@ -1735,4 +1832,4 @@ fi
say "Note that the script does not resolve dependencies during installation."
say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
say "Installation finished successfully."
say "Installation finished successfully."

Some files were not shown because too many files have changed in this diff Show More