mirror of
https://gitea.com/actions/setup-go.git
synced 2026-10-06 23:03:10 +01:00
Compare commits
1 Commits
feat/split
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38e1992e12 |
53
.github/workflows/versions.yml
vendored
53
.github/workflows/versions.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go Stable
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go oldStable
|
||||
@@ -43,43 +43,18 @@ jobs:
|
||||
- name: Verify Go
|
||||
run: go version
|
||||
|
||||
go-env-outputs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go
|
||||
id: setup-go
|
||||
uses: ./
|
||||
with:
|
||||
go-version: stable
|
||||
- name: Show Go environment outputs
|
||||
run: |
|
||||
echo "GOPATH: ${{ steps.setup-go.outputs.GOPATH }}"
|
||||
echo "GOBIN: ${{ steps.setup-go.outputs.GOBIN }}"
|
||||
echo "go-bin-path: ${{ steps.setup-go.outputs.go-bin-path }}"
|
||||
echo "GOROOT: ${{ steps.setup-go.outputs.GOROOT }}"
|
||||
echo "GOCACHE: ${{ steps.setup-go.outputs.GOCACHE }}"
|
||||
echo "GOMODCACHE: ${{ steps.setup-go.outputs.GOMODCACHE }}"
|
||||
echo "GOOS: ${{ steps.setup-go.outputs.GOOS }}"
|
||||
echo "GOARCH: ${{ steps.setup-go.outputs.GOARCH }}"
|
||||
echo "GOTOOLDIR: ${{ steps.setup-go.outputs.GOTOOLDIR }}"
|
||||
|
||||
aliases-arch:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
version: [stable, oldstable]
|
||||
architecture: [x64, x32]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
architecture: x32
|
||||
- os: macos-15-intel
|
||||
- os: macos-latest-large
|
||||
architecture: x32
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -97,7 +72,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-15-intel]
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-latest-large]
|
||||
go: [1.21.13, 1.22.8, 1.23.2]
|
||||
include:
|
||||
- os: windows-latest
|
||||
@@ -123,7 +98,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
go-version: ['1.20', '1.21', '1.22', '1.23']
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -140,7 +115,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
@@ -156,7 +131,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
@@ -172,7 +147,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
@@ -188,7 +163,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go from .go-version file
|
||||
@@ -204,7 +179,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-15-intel]
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-latest-large]
|
||||
go: [1.20.14, 1.21.10, 1.22.8, 1.23.2]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -224,7 +199,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-15-intel]
|
||||
os: [windows-latest, ubuntu-latest, macos-latest-large]
|
||||
go: [1.11.12]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -244,7 +219,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
go-version: [1.20.14, 1.21, 1.22, 1.23]
|
||||
include:
|
||||
- os: macos-latest
|
||||
@@ -253,7 +228,7 @@ jobs:
|
||||
architecture: x64
|
||||
- os: windows-latest
|
||||
architecture: x64
|
||||
- os: macos-15-intel
|
||||
- os: macos-latest-large
|
||||
architecture: x64
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
2
.licenses/npm/@actions/cache.dep.yml
generated
2
.licenses/npm/@actions/cache.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/cache"
|
||||
version: 6.3.0
|
||||
version: 6.2.0
|
||||
type: npm
|
||||
summary: Actions cache lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
|
||||
|
||||
20
.licenses/npm/@actions/glob-0.6.1.dep.yml
generated
Normal file
20
.licenses/npm/@actions/glob-0.6.1.dep.yml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: "@actions/glob"
|
||||
version: 0.6.1
|
||||
type: npm
|
||||
summary: Actions glob lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
notices: []
|
||||
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-rest-pipeline"
|
||||
version: 1.23.0
|
||||
version: 1.24.0
|
||||
type: npm
|
||||
summary: Isomorphic client library for making HTTP requests in node.js and browser.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-rest-pipeline/README.md
|
||||
|
||||
2
.licenses/npm/@azure/storage-blob.dep.yml
generated
2
.licenses/npm/@azure/storage-blob.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/storage-blob"
|
||||
version: 12.31.0
|
||||
version: 12.32.0
|
||||
type: npm
|
||||
summary: Microsoft Azure Storage SDK for JavaScript - Blob
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob/README.md
|
||||
|
||||
55
.licenses/npm/balanced-match-1.0.2.dep.yml
generated
Normal file
55
.licenses/npm/balanced-match-1.0.2.dep.yml
generated
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: balanced-match
|
||||
version: 1.0.2
|
||||
type: npm
|
||||
summary: Match balanced character pairs, like "{" and "}"
|
||||
homepage: https://github.com/juliangruber/balanced-match
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
55
.licenses/npm/brace-expansion-1.1.16.dep.yml
generated
Normal file
55
.licenses/npm/brace-expansion-1.1.16.dep.yml
generated
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: brace-expansion
|
||||
version: 1.1.16
|
||||
type: npm
|
||||
summary: Brace expansion as known from sh/bash
|
||||
homepage: https://github.com/juliangruber/brace-expansion
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
(MIT)
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: brace-expansion
|
||||
version: 5.0.9
|
||||
version: 5.0.8
|
||||
type: npm
|
||||
summary: Brace expansion as known from sh/bash
|
||||
homepage:
|
||||
31
.licenses/npm/concat-map.dep.yml
generated
Normal file
31
.licenses/npm/concat-map.dep.yml
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: concat-map
|
||||
version: 0.0.1
|
||||
type: npm
|
||||
summary: concatenative mapdashery
|
||||
homepage: https://github.com/substack/node-concat-map#readme
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
This software is released under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
- sources: README.markdown
|
||||
text: MIT
|
||||
notices: []
|
||||
26
.licenses/npm/minimatch-3.1.5.dep.yml
generated
Normal file
26
.licenses/npm/minimatch-3.1.5.dep.yml
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: minimatch
|
||||
version: 3.1.5
|
||||
type: npm
|
||||
summary: a glob matcher in javascript
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
||||
2
.licenses/npm/undici.dep.yml
generated
2
.licenses/npm/undici.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: undici
|
||||
version: 6.28.0
|
||||
version: 6.27.0
|
||||
type: npm
|
||||
summary: An HTTP/1.1 client, written from scratch for Node.js
|
||||
homepage: https://undici.nodejs.org
|
||||
|
||||
@@ -127,8 +127,6 @@ The `cache` input is optional, and caching is enabled by default. To disable cac
|
||||
|
||||
By default, the action looks for `go.mod` in the repository root and uses its hash as part of the cache key. Use the `cache-dependency-path` input when you have multiple dependency files, or when they’re located in different subdirectories. This input supports glob patterns.
|
||||
|
||||
Module sources (`GOMODCACHE`) and build outputs (`GOCACHE`) are cached separately. On the same runner OS, module sources can be reused across Go versions and runner architectures, while build outputs remain partitioned by Go version, runner architecture, and Linux runner image. The `cache-hit` output is `true` only when both cache entries are restored on their primary keys.
|
||||
|
||||
If caching cannot be performed for any reason, the action logs a warning and continues workflow execution.
|
||||
|
||||
For examples of using `cache-dependency-path`, see the [Caching](docs/advanced-usage.md#caching) section of the [Advanced usage](docs/advanced-usage.md) guide.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
@@ -53,349 +50,91 @@ const core = await import('@actions/core');
|
||||
const glob = await import('@actions/glob');
|
||||
const cacheRestore = await import('../src/cache-restore.js');
|
||||
const cacheUtils = await import('../src/cache-utils.js');
|
||||
const {run: saveCaches} = await import('../src/cache-save.js');
|
||||
import type {CacheDirectory} from '../src/cache-utils.js';
|
||||
|
||||
function deferred() {
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>(done => {
|
||||
resolve = done;
|
||||
});
|
||||
return {promise, resolve};
|
||||
}
|
||||
import type {PackageManagerInfo} from '../src/package-managers.js';
|
||||
|
||||
describe('restoreCache', () => {
|
||||
const hashFilesSpy = glob.hashFiles as jest.Mock<typeof glob.hashFiles>;
|
||||
const getCacheDirectoryPathSpy =
|
||||
cacheUtils.getCacheDirectoryPath as jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
const restoreCacheSpy = cache.restoreCache as jest.Mock<
|
||||
typeof cache.restoreCache
|
||||
let hashFilesSpy: jest.Mock<typeof glob.hashFiles>;
|
||||
let getCacheDirectoryPathSpy: jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
const saveCacheSpy = cache.saveCache as jest.Mock<typeof cache.saveCache>;
|
||||
const infoSpy = core.info as jest.Mock<typeof core.info>;
|
||||
const setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
let restoreCacheSpy: jest.Mock<typeof cache.restoreCache>;
|
||||
let infoSpy: jest.Mock<typeof core.info>;
|
||||
let setOutputSpy: jest.Mock<typeof core.setOutput>;
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const packageManager = 'default';
|
||||
const cacheDependencyPath = 'path';
|
||||
const modulePath = 'module_cache_directory';
|
||||
const buildPath = 'build_cache_directory';
|
||||
const directories: CacheDirectory[] = [
|
||||
{kind: 'modules', path: modulePath},
|
||||
{kind: 'build', path: buildPath}
|
||||
];
|
||||
const originalEnv = {...process.env};
|
||||
const originalArch = Object.getOwnPropertyDescriptor(process, 'arch')!;
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(
|
||||
process,
|
||||
'platform'
|
||||
)!;
|
||||
|
||||
let originalWorkspace: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
GITHUB_WORKSPACE: '/test/workspace',
|
||||
RUNNER_OS: 'Linux',
|
||||
ImageOS: 'ubuntu24'
|
||||
};
|
||||
Object.defineProperty(process, 'arch', {value: 'x64'});
|
||||
Object.defineProperty(process, 'platform', {value: 'linux'});
|
||||
hashFilesSpy.mockResolvedValue('file_hash');
|
||||
getCacheDirectoryPathSpy.mockResolvedValue(directories);
|
||||
restoreCacheSpy.mockResolvedValue(undefined);
|
||||
saveCacheSpy.mockResolvedValue(1);
|
||||
originalWorkspace = process.env.GITHUB_WORKSPACE;
|
||||
process.env.GITHUB_WORKSPACE = '/test/workspace';
|
||||
//Arrange
|
||||
hashFilesSpy = glob.hashFiles as jest.Mock<typeof glob.hashFiles>;
|
||||
getCacheDirectoryPathSpy = cacheUtils.getCacheDirectoryPath as jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
restoreCacheSpy = cache.restoreCache as jest.Mock<
|
||||
typeof cache.restoreCache
|
||||
>;
|
||||
infoSpy = core.info as jest.Mock<typeof core.info>;
|
||||
setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
|
||||
getCacheDirectoryPathSpy.mockImplementation(
|
||||
(PackageManager: PackageManagerInfo) => {
|
||||
return Promise.resolve([
|
||||
'cache_directory_path',
|
||||
'cache_directory_path'
|
||||
]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = {...originalEnv};
|
||||
Object.defineProperty(process, 'arch', originalArch);
|
||||
Object.defineProperty(process, 'platform', originalPlatform);
|
||||
process.env.GITHUB_WORKSPACE = originalWorkspace;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const restore = (version = versionSpec) =>
|
||||
cacheRestore.restoreCache(version, packageManager, cacheDependencyPath);
|
||||
|
||||
const requestedKeys = () => {
|
||||
const moduleCall = restoreCacheSpy.mock.calls.find(
|
||||
([paths]) => paths[0] === modulePath
|
||||
);
|
||||
const buildCall = restoreCacheSpy.mock.calls.find(
|
||||
([paths]) => paths[0] === buildPath
|
||||
);
|
||||
expect(moduleCall).toBeDefined();
|
||||
expect(buildCall).toBeDefined();
|
||||
return {modules: moduleCall![1], build: buildCall![1]};
|
||||
};
|
||||
|
||||
it('should throw if dependency file path is not valid', async () => {
|
||||
hashFilesSpy.mockResolvedValue('');
|
||||
|
||||
await expect(restore()).rejects.toThrow(
|
||||
// Arrange
|
||||
hashFilesSpy.mockImplementation(() => Promise.resolve(''));
|
||||
// Act + Assert
|
||||
await expect(
|
||||
cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
cacheDependencyPath
|
||||
)
|
||||
).rejects.toThrow(
|
||||
'Some specified paths were not resolved, unable to cache dependencies.'
|
||||
);
|
||||
expect(restoreCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
});
|
||||
|
||||
it('restores modules and build outputs as separate cache entries', async () => {
|
||||
await restore();
|
||||
|
||||
const keys = requestedKeys();
|
||||
expect(keys.modules).not.toBe(keys.build);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith([modulePath], keys.modules);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith([buildPath], keys.build);
|
||||
expect(infoSpy).toHaveBeenCalledWith('modules cache is not found');
|
||||
expect(infoSpy).toHaveBeenCalledWith('build cache is not found');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{platform: 'linux', firstFails: false},
|
||||
{platform: 'linux', firstFails: true},
|
||||
{platform: 'win32', firstFails: false},
|
||||
{platform: 'win32', firstFails: true}
|
||||
])(
|
||||
'starts both restores concurrently on $platform and waits for both (first fails=$firstFails)',
|
||||
async ({platform, firstFails}) => {
|
||||
Object.defineProperty(process, 'platform', {value: platform});
|
||||
const started = [deferred(), deferred()];
|
||||
const release = [deferred(), deferred()];
|
||||
let completed = false;
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) => {
|
||||
const index = paths[0] === modulePath ? 0 : 1;
|
||||
started[index].resolve();
|
||||
await release[index].promise;
|
||||
if (index === 0 && firstFails) {
|
||||
throw new Error('Module cache unavailable');
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
const pending = restore().then(() => {
|
||||
completed = true;
|
||||
});
|
||||
try {
|
||||
await Promise.all(started.map(signal => signal.promise));
|
||||
release[0].resolve();
|
||||
// Let the first completion propagate while the second stays blocked.
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
expect(completed).toBe(false);
|
||||
expect(core.saveState).not.toHaveBeenCalled();
|
||||
expect(setOutputSpy).not.toHaveBeenCalledWith('cache-hit', true);
|
||||
} finally {
|
||||
release.forEach(signal => signal.resolve());
|
||||
await pending;
|
||||
}
|
||||
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', !firstFails);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['linux', 'win32'])(
|
||||
'restores overlapping directories sequentially on %s',
|
||||
async platform => {
|
||||
Object.defineProperty(process, 'platform', {value: platform});
|
||||
getCacheDirectoryPathSpy.mockResolvedValue([
|
||||
directories[0],
|
||||
{kind: 'build', path: path.join(modulePath, 'build')}
|
||||
]);
|
||||
const firstStarted = deferred();
|
||||
const releaseFirst = deferred();
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) => {
|
||||
if (paths[0] === modulePath) {
|
||||
firstStarted.resolve();
|
||||
await releaseFirst.promise;
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
const pending = restore();
|
||||
try {
|
||||
await firstStarted.promise;
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
releaseFirst.resolve();
|
||||
await pending;
|
||||
}
|
||||
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
{modules: false, build: false},
|
||||
{modules: true, build: false},
|
||||
{modules: false, build: true},
|
||||
{modules: true, build: true}
|
||||
])(
|
||||
'reports hits and saves misses independently (modules=$modules, build=$build)',
|
||||
async hits => {
|
||||
const state = new Map<string, string>();
|
||||
(core.saveState as jest.Mock<typeof core.saveState>).mockImplementation(
|
||||
(name, value) => {
|
||||
state.set(name, JSON.stringify(value));
|
||||
}
|
||||
);
|
||||
(core.getState as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
name => state.get(name) || ''
|
||||
);
|
||||
(
|
||||
core.getBooleanInput as jest.Mock<typeof core.getBooleanInput>
|
||||
).mockReturnValue(true);
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) =>
|
||||
hits[paths[0] === modulePath ? 'modules' : 'build'] ? key : undefined
|
||||
);
|
||||
|
||||
await restore();
|
||||
const keys = requestedKeys();
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith(
|
||||
'cache-hit',
|
||||
hits.modules && hits.build
|
||||
);
|
||||
|
||||
// Changing the Go environment later must not change the saved paths.
|
||||
process.env.GOMODCACHE = 'different_module_cache';
|
||||
process.env.GOCACHE = 'different_build_cache';
|
||||
await saveCaches();
|
||||
|
||||
for (const {kind, path} of directories) {
|
||||
if (hits[kind]) {
|
||||
expect(saveCacheSpy).not.toHaveBeenCalledWith([path], keys[kind]);
|
||||
} else {
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([path], keys[kind]);
|
||||
}
|
||||
}
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(
|
||||
Number(!hits.modules) + Number(!hits.build)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('does not report a full hit for a non-primary cache match', async () => {
|
||||
restoreCacheSpy.mockImplementation(async (_paths, key) => `${key}-other`);
|
||||
|
||||
await restore();
|
||||
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
});
|
||||
|
||||
it.each(directories)(
|
||||
'continues restoring when the $kind cache fails',
|
||||
async ({kind, path}) => {
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) => {
|
||||
if (paths[0] === path) {
|
||||
throw new Error('Unable to reach the service');
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
await restore();
|
||||
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
`Restore ${kind} cache failed: Unable to reach the service`
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(directories)(
|
||||
'restores only $kind when the other directory cannot be resolved',
|
||||
async directory => {
|
||||
getCacheDirectoryPathSpy.mockResolvedValue([directory]);
|
||||
restoreCacheSpy.mockImplementation(async (_paths, key) => key);
|
||||
|
||||
await restore();
|
||||
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith(
|
||||
[directory.path],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
}
|
||||
);
|
||||
|
||||
it('reuses modules across Go versions without reusing build outputs', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
await restore('1.26.1');
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: 'runner architecture',
|
||||
change: () => Object.defineProperty(process, 'arch', {value: 'arm64'})
|
||||
},
|
||||
{
|
||||
name: 'runner image',
|
||||
change: () => {
|
||||
process.env.ImageOS = 'ubuntu26';
|
||||
}
|
||||
}
|
||||
])('reuses only modules when the $name changes', async ({change}) => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
change();
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('keeps both cache entries isolated by runner OS', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
process.env.RUNNER_OS = 'Windows';
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).not.toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('invalidates both cache entries when the dependency hash changes', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
hashFilesSpy.mockResolvedValue('different_file_hash');
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).not.toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('hashes the configured dependency paths for both cache entries', async () => {
|
||||
const dependencyPaths = 'go.sum\ntools/go.sum';
|
||||
|
||||
it('should inform if cache hit is not occurred', async () => {
|
||||
// Arrange
|
||||
hashFilesSpy.mockImplementation(() => Promise.resolve('file_hash'));
|
||||
restoreCacheSpy.mockImplementation(() => Promise.resolve(''));
|
||||
// Act + Assert
|
||||
await cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
dependencyPaths
|
||||
cacheDependencyPath
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith('Cache is not found');
|
||||
});
|
||||
|
||||
expect(hashFilesSpy).toHaveBeenCalledWith(dependencyPaths);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
it('should set output if cache hit is occurred', async () => {
|
||||
// Arrange
|
||||
hashFilesSpy.mockImplementation(() => Promise.resolve('file_hash'));
|
||||
restoreCacheSpy.mockImplementation(() => Promise.resolve('cache_key'));
|
||||
// Act + Assert
|
||||
await cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
cacheDependencyPath
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
|
||||
});
|
||||
|
||||
it('should throw if dependency file is not found in workspace', async () => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
@@ -24,22 +22,26 @@ const realFs = (await import('fs')).default;
|
||||
const fsExports = {...realFs, existsSync: jest.fn()};
|
||||
jest.unstable_mockModule('fs', () => ({...fsExports, default: fsExports}));
|
||||
|
||||
// Import real cache-utils (with mocked @actions) before mocking it
|
||||
const realCacheUtils = await import('../src/cache-utils.js');
|
||||
|
||||
jest.unstable_mockModule('../src/cache-utils.js', () => ({
|
||||
...realCacheUtils,
|
||||
getCacheDirectoryPath: jest.fn()
|
||||
}));
|
||||
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
const fs = (await import('fs')).default;
|
||||
const cacheUtils = await import('../src/cache-utils.js');
|
||||
const {run} = await import('../src/cache-save.js');
|
||||
import type {CacheEntry} from '../src/cache-utils.js';
|
||||
|
||||
function deferred() {
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>(done => {
|
||||
resolve = done;
|
||||
});
|
||||
return {promise, resolve};
|
||||
}
|
||||
const {State} = await import('../src/constants.js');
|
||||
|
||||
describe('cache-save', () => {
|
||||
let entries: CacheEntry[];
|
||||
const primaryKey = 'primary-key';
|
||||
|
||||
let primaryKeyValue: string;
|
||||
let matchedKeyValue: string;
|
||||
|
||||
let getBooleanInputSpy: jest.Mock<typeof core.getBooleanInput>;
|
||||
let getStateSpy: jest.Mock<typeof core.getState>;
|
||||
@@ -48,21 +50,14 @@ describe('cache-save', () => {
|
||||
let debugSpy: jest.Mock<typeof core.debug>;
|
||||
let setFailedSpy: jest.Mock<typeof core.setFailed>;
|
||||
let saveCacheSpy: jest.Mock<typeof cache.saveCache>;
|
||||
let getCacheDirectoryPathSpy: jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
let existsSpy: jest.Mock<typeof fs.existsSync>;
|
||||
|
||||
beforeEach(() => {
|
||||
entries = [
|
||||
{
|
||||
kind: 'modules',
|
||||
path: 'module_cache_directory',
|
||||
primaryKey: 'modules-primary-key'
|
||||
},
|
||||
{
|
||||
kind: 'build',
|
||||
path: 'build_cache_directory',
|
||||
primaryKey: 'build-primary-key'
|
||||
}
|
||||
];
|
||||
primaryKeyValue = primaryKey;
|
||||
matchedKeyValue = 'matched-key';
|
||||
|
||||
getBooleanInputSpy = core.getBooleanInput as jest.Mock<
|
||||
typeof core.getBooleanInput
|
||||
@@ -70,7 +65,15 @@ describe('cache-save', () => {
|
||||
getBooleanInputSpy.mockReturnValue(true);
|
||||
|
||||
getStateSpy = core.getState as jest.Mock<typeof core.getState>;
|
||||
getStateSpy.mockImplementation(() => JSON.stringify(entries));
|
||||
getStateSpy.mockImplementation((key: string) => {
|
||||
if (key === State.CachePrimaryKey) {
|
||||
return primaryKeyValue;
|
||||
}
|
||||
if (key === State.CacheMatchedKey) {
|
||||
return matchedKeyValue;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
infoSpy = core.info as jest.Mock<typeof core.info>;
|
||||
infoSpy.mockImplementation(() => undefined);
|
||||
@@ -87,12 +90,18 @@ describe('cache-save', () => {
|
||||
saveCacheSpy = cache.saveCache as jest.Mock<typeof cache.saveCache>;
|
||||
saveCacheSpy.mockImplementation(() => Promise.resolve(0));
|
||||
|
||||
getCacheDirectoryPathSpy = cacheUtils.getCacheDirectoryPath as jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
getCacheDirectoryPathSpy.mockImplementation(() =>
|
||||
Promise.resolve(['cache_directory_path', 'cache_directory_path'])
|
||||
);
|
||||
|
||||
existsSpy = fs.existsSync as jest.Mock<typeof fs.existsSync>;
|
||||
existsSpy.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -106,201 +115,78 @@ describe('cache-save', () => {
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
'starts both saves concurrently and waits before exiting (first fails=%s)',
|
||||
async firstFails => {
|
||||
const started = [deferred(), deferred()];
|
||||
const release = [deferred(), deferred()];
|
||||
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
return undefined as never;
|
||||
});
|
||||
saveCacheSpy.mockImplementation(async paths => {
|
||||
const index = paths[0] === entries[0].path ? 0 : 1;
|
||||
started[index].resolve();
|
||||
await release[index].promise;
|
||||
if (index === 0 && firstFails) {
|
||||
throw new Error('Module cache unavailable');
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
const pending = run(true);
|
||||
try {
|
||||
await Promise.all(started.map(signal => signal.promise));
|
||||
release[0].resolve();
|
||||
// Let the first completion propagate while the second stays blocked.
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
expect(exitSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
release.forEach(signal => signal.resolve());
|
||||
await pending;
|
||||
}
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledTimes(1);
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${entries[1].primaryKey}`
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('does not save cache when there are no cache folders on the disk', async () => {
|
||||
existsSpy.mockImplementation(() => false);
|
||||
|
||||
await run();
|
||||
|
||||
for (const {kind, path} of entries) {
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`[warning]${kind} cache folder doesn't exist on disk: ${path}`
|
||||
);
|
||||
}
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'saves the other cache when the %s directory is missing',
|
||||
async kind => {
|
||||
const missing = entries.find(entry => entry.kind === kind)!;
|
||||
const existing = entries.find(entry => entry.kind !== kind)!;
|
||||
existsSpy.mockImplementation(path => path !== missing.path);
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith(
|
||||
[existing.path],
|
||||
existing.primaryKey
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('does not save cache when no cache entries were prepared', async () => {
|
||||
getStateSpy.mockReturnValue('');
|
||||
|
||||
await run();
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
'Cache keys were not generated. Please check the log messages above for more errors or information'
|
||||
expect(warningSpy).toHaveBeenCalledWith(
|
||||
'There are no cache folders on the disk'
|
||||
);
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not fail the workflow if the saved state is invalid', async () => {
|
||||
getStateSpy.mockReturnValue('{');
|
||||
it('does not save cache when the primary key was not generated', async () => {
|
||||
primaryKeyValue = '';
|
||||
|
||||
await run();
|
||||
|
||||
expect(warningSpy).toHaveBeenCalledWith(expect.any(String));
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
'Primary key was not generated. Please check the log messages above for more errors or information'
|
||||
);
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not save either cache when both primary keys were hit', async () => {
|
||||
for (const entry of entries) {
|
||||
entry.matchedKey = entry.primaryKey;
|
||||
}
|
||||
it('does not save cache when a cache hit occurred on the primary key', async () => {
|
||||
matchedKeyValue = primaryKey;
|
||||
|
||||
await run();
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'skips only the %s cache when its primary key was hit',
|
||||
async kind => {
|
||||
const hit = entries.find(entry => entry.kind === kind)!;
|
||||
const miss = entries.find(entry => entry.kind !== kind)!;
|
||||
hit.matchedKey = hit.primaryKey;
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([miss.path], miss.primaryKey);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('saves both cache directories under separate keys on a cold run', async () => {
|
||||
it('saves cache when the primary key differs from the matched key', async () => {
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
for (const {path, primaryKey} of entries) {
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([path], primaryKey);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${primaryKey}`
|
||||
);
|
||||
}
|
||||
expect(saveCacheSpy).toHaveBeenCalled();
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${primaryKey}`
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('saves caches restored from non-primary matches', async () => {
|
||||
for (const entry of entries) {
|
||||
entry.matchedKey = `${entry.primaryKey}-other`;
|
||||
}
|
||||
it('save with -1 cacheId , should not fail workflow', async () => {
|
||||
saveCacheSpy.mockImplementation(() => Promise.resolve(-1));
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(saveCacheSpy).toHaveBeenCalled();
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
`Cache was not saved for the key: ${primaryKey}`
|
||||
);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${primaryKey}`
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'continues saving when the %s cache reservation fails',
|
||||
async kind => {
|
||||
const failed = entries.find(entry => entry.kind === kind)!;
|
||||
const saved = entries.find(entry => entry.kind !== kind)!;
|
||||
saveCacheSpy.mockImplementation(async paths =>
|
||||
paths[0] === failed.path ? -1 : 1
|
||||
);
|
||||
it('saves with error from toolkit, should not fail workflow', async () => {
|
||||
saveCacheSpy.mockImplementation(() =>
|
||||
Promise.reject(new Error('Unable to reach the service'))
|
||||
);
|
||||
|
||||
await run();
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
`Cache was not saved for the key: ${failed.primaryKey}`
|
||||
);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${failed.primaryKey}`
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${saved.primaryKey}`
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'continues saving when the %s cache upload throws',
|
||||
async kind => {
|
||||
const failed = entries.find(entry => entry.kind === kind)!;
|
||||
const saved = entries.find(entry => entry.kind !== kind)!;
|
||||
saveCacheSpy.mockImplementation(async paths => {
|
||||
if (paths[0] === failed.path) {
|
||||
throw new Error('Unable to reach the service');
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(warningSpy).toHaveBeenCalledWith(
|
||||
`Save ${kind} cache failed: Unable to reach the service`
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${saved.primaryKey}`
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
expect(saveCacheSpy).toHaveBeenCalled();
|
||||
expect(warningSpy).toHaveBeenCalledWith('Unable to reach the service');
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {jest, describe, it, expect, beforeEach, afterAll} from '@jest/globals';
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
@@ -94,10 +84,7 @@ describe('getPackageManagerInfo', () => {
|
||||
const packageManagerName = 'default';
|
||||
const expectedResult = {
|
||||
dependencyFilePattern: 'go.mod',
|
||||
cacheFolderCommandList: [
|
||||
{kind: 'modules', command: 'go env GOMODCACHE'},
|
||||
{kind: 'build', command: 'go env GOCACHE'}
|
||||
]
|
||||
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
|
||||
};
|
||||
|
||||
//Act + Assert
|
||||
@@ -125,29 +112,16 @@ describe('getCacheDirectoryPath', () => {
|
||||
|
||||
const validPackageManager: PackageManagerInfo = {
|
||||
dependencyFilePattern: 'go.mod',
|
||||
cacheFolderCommandList: [
|
||||
{kind: 'modules', command: 'go env GOMODCACHE'},
|
||||
{kind: 'build', command: 'go env GOCACHE'}
|
||||
]
|
||||
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
|
||||
};
|
||||
|
||||
it('should return path to the cache folders which specified package manager uses', async () => {
|
||||
//Arrange
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {
|
||||
exitCode: 0,
|
||||
stdout:
|
||||
commandLine === 'go env GOMODCACHE'
|
||||
? 'path/to/modules'
|
||||
: 'path/to/build',
|
||||
stderr: ''
|
||||
};
|
||||
return {exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''};
|
||||
});
|
||||
|
||||
const expectedResult = [
|
||||
{kind: 'modules', path: 'path/to/modules'},
|
||||
{kind: 'build', path: 'path/to/build'}
|
||||
];
|
||||
const expectedResult = ['path/to/cache/folder', 'path/to/cache/folder'];
|
||||
|
||||
//Act + Assert
|
||||
return cacheUtils
|
||||
@@ -155,43 +129,27 @@ describe('getCacheDirectoryPath', () => {
|
||||
.then(data => expect(data).toEqual(expectedResult));
|
||||
});
|
||||
|
||||
it.each(validPackageManager.cacheFolderCommandList)(
|
||||
'preserves the $kind identity when the other command returns an empty path',
|
||||
async ({kind, command}) => {
|
||||
getExecOutputSpy.mockImplementation(async commandLine => ({
|
||||
exitCode: 0,
|
||||
stdout: commandLine === command ? 'path/to/cache/folder' : '',
|
||||
stderr: ''
|
||||
}));
|
||||
it('should return path to the cache folder if one command return empty str', async () => {
|
||||
//Arrange
|
||||
getExecOutputSpy.mockImplementationOnce(async (commandLine: string) => {
|
||||
return {exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''};
|
||||
});
|
||||
|
||||
await expect(
|
||||
cacheUtils.getCacheDirectoryPath(validPackageManager)
|
||||
).resolves.toEqual([{kind, path: 'path/to/cache/folder'}]);
|
||||
}
|
||||
);
|
||||
getExecOutputSpy.mockImplementationOnce(async (commandLine: string) => {
|
||||
return {exitCode: 0, stdout: '', stderr: ''};
|
||||
});
|
||||
|
||||
it.each(validPackageManager.cacheFolderCommandList)(
|
||||
'preserves the $kind identity when the other command fails',
|
||||
async ({kind, command}) => {
|
||||
getExecOutputSpy.mockImplementation(async commandLine => {
|
||||
if (commandLine !== command) {
|
||||
throw new Error('Could not run go env');
|
||||
}
|
||||
return {exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''};
|
||||
});
|
||||
const expectedResult = ['path/to/cache/folder'];
|
||||
|
||||
await expect(
|
||||
cacheUtils.getCacheDirectoryPath(validPackageManager)
|
||||
).resolves.toEqual([{kind, path: 'path/to/cache/folder'}]);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not run go env')
|
||||
);
|
||||
}
|
||||
);
|
||||
//Act + Assert
|
||||
return cacheUtils
|
||||
.getCacheDirectoryPath(validPackageManager)
|
||||
.then(data => expect(data).toEqual(expectedResult));
|
||||
});
|
||||
|
||||
it('should throw if the both commands return empty str', async () => {
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 0, stdout: '', stderr: ''};
|
||||
return {exitCode: 10, stdout: '', stderr: ''};
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
@@ -200,7 +158,7 @@ describe('getCacheDirectoryPath', () => {
|
||||
}).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw if both commands fail', async () => {
|
||||
it('should throw if the specified package name is invalid', async () => {
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 10, stdout: '', stderr: 'Error message'};
|
||||
});
|
||||
@@ -212,139 +170,6 @@ describe('getCacheDirectoryPath', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('canRestoreCachesInParallel', () => {
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(
|
||||
process,
|
||||
'platform'
|
||||
)!;
|
||||
const directoryStat = fs.lstatSync(process.cwd());
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(process, 'platform', {value: 'linux'});
|
||||
jest.spyOn(fs, 'lstatSync').mockReturnValue(directoryStat);
|
||||
jest
|
||||
.spyOn(fs, 'realpathSync')
|
||||
.mockImplementation(directory => path.resolve(directory.toString()));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', originalPlatform);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{modules: 'cache/modules', build: 'cache/build', parallel: true},
|
||||
{modules: 'cache', build: 'cache-build', parallel: true},
|
||||
{modules: 'cache', build: 'cache', parallel: false},
|
||||
{modules: 'cache', build: 'cache/build', parallel: false},
|
||||
{modules: 'cache/modules', build: 'cache', parallel: false},
|
||||
{modules: 'cache', build: 'cache/../cache', parallel: false}
|
||||
])(
|
||||
'checks overlapping paths: $modules and $build',
|
||||
({modules, build, parallel}) => {
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: modules},
|
||||
{kind: 'build', path: build}
|
||||
])
|
||||
).toBe(parallel);
|
||||
}
|
||||
);
|
||||
|
||||
it('allows concurrent Windows restores with disjoint paths', () => {
|
||||
Object.defineProperty(process, 'platform', {value: 'win32'});
|
||||
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: 'cache/modules'},
|
||||
{kind: 'build', path: 'cache/build'}
|
||||
])
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['darwin', 'win32'])(
|
||||
'handles case-insensitive paths conservatively on %s',
|
||||
platform => {
|
||||
Object.defineProperty(process, 'platform', {value: platform});
|
||||
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: 'cache'},
|
||||
{kind: 'build', path: 'CACHE/build'}
|
||||
])
|
||||
).toBe(false);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([true, false])(
|
||||
'serializes symlink aliases with missing cache directories (overlap=%s)',
|
||||
overlap => {
|
||||
const alias = path.resolve('alias');
|
||||
const target = path.resolve('target');
|
||||
jest.spyOn(fs, 'lstatSync').mockImplementation(directory => {
|
||||
if (directory !== alias && directory !== target) {
|
||||
throw Object.assign(new Error('Missing path'), {code: 'ENOENT'});
|
||||
}
|
||||
return directoryStat;
|
||||
});
|
||||
jest.spyOn(fs, 'realpathSync').mockReturnValue(target);
|
||||
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: path.join(alias, 'modules')},
|
||||
{
|
||||
kind: 'build',
|
||||
path: path.join(target, overlap ? 'modules/build' : 'build')
|
||||
}
|
||||
])
|
||||
).toBe(false);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['nested alias', 'alias through module cache'])(
|
||||
'serializes restores when the %s resolves outside the module cache',
|
||||
scenario => {
|
||||
const modules = path.resolve('modules');
|
||||
const target = path.resolve('external-build');
|
||||
const alias = path.join(modules, 'build-link');
|
||||
const build =
|
||||
scenario === 'nested alias'
|
||||
? path.join(alias, 'cache')
|
||||
: path.resolve('build-alias', 'cache');
|
||||
jest.spyOn(fs, 'realpathSync').mockImplementation(directory => {
|
||||
const absolute = path.resolve(directory.toString());
|
||||
if (absolute === build) {
|
||||
return path.join(target, 'cache');
|
||||
}
|
||||
if (absolute === path.dirname(build)) {
|
||||
return scenario === 'nested alias' ? target : alias;
|
||||
}
|
||||
return absolute;
|
||||
});
|
||||
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: modules},
|
||||
{kind: 'build', path: build}
|
||||
])
|
||||
).toBe(false);
|
||||
}
|
||||
);
|
||||
|
||||
it('falls back to sequential restores when path resolution fails', () => {
|
||||
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
|
||||
throw new Error('Cannot resolve path');
|
||||
});
|
||||
|
||||
expect(
|
||||
cacheUtils.canRestoreCachesInParallel([
|
||||
{kind: 'modules', path: 'cache/modules'},
|
||||
{kind: 'build', path: 'cache/build'}
|
||||
])
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCacheFeatureAvailable', () => {
|
||||
//Arrange
|
||||
const isFeatureAvailableSpy = cache.isFeatureAvailable as jest.Mock<
|
||||
|
||||
@@ -17,7 +17,6 @@ import goTestManifest from './data/versions-manifest.json' with {type: 'json'};
|
||||
|
||||
import type {IGoVersion} from '../src/installer.js';
|
||||
import type {IToolRelease} from '@actions/tool-cache';
|
||||
import {GO_ENV_OUTPUTS} from '../src/constants.js';
|
||||
|
||||
const httpClientGetJson = jest.fn();
|
||||
|
||||
@@ -29,8 +28,6 @@ jest.unstable_mockModule('@actions/core', () => ({
|
||||
getBooleanInput: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
exportVariable: jest.fn()
|
||||
}));
|
||||
|
||||
@@ -113,8 +110,6 @@ describe('setup-go', () => {
|
||||
let findSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock<typeof core.setOutput>;
|
||||
let warningSpy: jest.Mock<typeof core.warning>;
|
||||
let getSpy: jest.Mock;
|
||||
let platSpy: jest.Mock;
|
||||
let archSpy: jest.Mock;
|
||||
@@ -203,8 +198,6 @@ describe('setup-go', () => {
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = core.info as jest.Mock;
|
||||
setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
warningSpy = core.warning as jest.Mock<typeof core.warning>;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
getSpy.mockImplementation(() => goJsonData as IGoVersion[] | null);
|
||||
cnSpy.mockImplementation(() => true);
|
||||
@@ -653,106 +646,6 @@ describe('setup-go', () => {
|
||||
expect(added).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('go env outputs', () => {
|
||||
const goEnv = {
|
||||
GOPATH: '/Users/testuser/go',
|
||||
GOBIN: '',
|
||||
GOROOT: '/usr/local/go',
|
||||
GOCACHE: '/Users/testuser/.cache/go-build',
|
||||
GOMODCACHE: '/Users/testuser/go/pkg/mod',
|
||||
GOOS: 'darwin',
|
||||
GOARCH: 'arm64',
|
||||
GOTOOLDIR: '/usr/local/go/pkg/tool/darwin_arm64'
|
||||
};
|
||||
|
||||
it('parses the output of go env -json', () => {
|
||||
execFileSpy.mockImplementation(() => JSON.stringify(goEnv));
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toEqual(goEnv);
|
||||
});
|
||||
|
||||
it('returns undefined when go env -json is not supported', () => {
|
||||
execFileSpy.mockImplementation(() => {
|
||||
throw new Error('flag provided but not defined: -json');
|
||||
});
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('flag provided but not defined: -json')
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not valid JSON', () => {
|
||||
execFileSpy.mockImplementation(() => 'GOPATH="/Users/testuser/go"');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not a JSON object', () => {
|
||||
execFileSpy.mockImplementation(() => '[]');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets an output for each exposed variable', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(GO_ENV_OUTPUTS).toEqual([
|
||||
'GOPATH',
|
||||
'GOBIN',
|
||||
'GOROOT',
|
||||
'GOCACHE',
|
||||
'GOMODCACHE',
|
||||
'GOOS',
|
||||
'GOARCH',
|
||||
'GOTOOLDIR'
|
||||
]);
|
||||
for (const name of GO_ENV_OUTPUTS) {
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(name, goEnv[name] ?? '');
|
||||
}
|
||||
});
|
||||
|
||||
it('falls back to $GOPATH/bin when GOBIN is empty', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers GOBIN over $GOPATH/bin when it is set', () => {
|
||||
main.setGoEnvOutputs({...goEnv, GOBIN: '/Users/testuser/bin'});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the first entry of a multi-entry GOPATH', () => {
|
||||
const GOPATH = ['/Users/testuser/go', '/Users/testuser/other'].join(
|
||||
path.delimiter
|
||||
);
|
||||
|
||||
main.setGoEnvOutputs({...goEnv, GOPATH});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('GOPATH', GOPATH);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves variables missing from go env -json empty', () => {
|
||||
main.setGoEnvOutputs({});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('GOPATH', '');
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-bin-path', '');
|
||||
});
|
||||
});
|
||||
|
||||
interface Annotation {
|
||||
file: string;
|
||||
line: number;
|
||||
|
||||
20
action.yml
20
action.yml
@@ -25,25 +25,7 @@ outputs:
|
||||
go-version:
|
||||
description: 'The installed Go version. Useful when given a version range as input.'
|
||||
cache-hit:
|
||||
description: 'A boolean value indicating whether both module and build cache entries were restored on their primary keys'
|
||||
GOPATH:
|
||||
description: 'The value of `go env GOPATH`.'
|
||||
GOBIN:
|
||||
description: 'The value of `go env GOBIN`.'
|
||||
go-bin-path:
|
||||
description: 'The base Go binary directory: `GOBIN`, otherwise `bin` under the first `GOPATH` entry.'
|
||||
GOROOT:
|
||||
description: 'The value of `go env GOROOT`.'
|
||||
GOCACHE:
|
||||
description: 'The value of `go env GOCACHE`, the build cache directory.'
|
||||
GOMODCACHE:
|
||||
description: 'The value of `go env GOMODCACHE`, the module cache directory.'
|
||||
GOOS:
|
||||
description: 'The value of `go env GOOS`, the target OS in Go notation (linux, darwin, windows).'
|
||||
GOARCH:
|
||||
description: 'The value of `go env GOARCH`, the target architecture in Go notation (amd64, arm64).'
|
||||
GOTOOLDIR:
|
||||
description: 'The value of `go env GOTOOLDIR`, the directory holding the toolchain binaries.'
|
||||
description: 'A boolean value to indicate if a cache was hit'
|
||||
runs:
|
||||
using: 'node24'
|
||||
main: 'dist/setup/index.js'
|
||||
|
||||
5408
dist/cache-save/index.js
vendored
5408
dist/cache-save/index.js
vendored
File diff suppressed because it is too large
Load Diff
9148
dist/setup/index.js
vendored
9148
dist/setup/index.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,6 @@
|
||||
- [Restore-only caches](advanced-usage.md#restore-only-caches)
|
||||
- [Parallel builds](advanced-usage.md#parallel-builds)
|
||||
- [Outputs](advanced-usage.md#outputs)
|
||||
- [go-version](advanced-usage.md#go-version)
|
||||
- [cache-hit](advanced-usage.md#cache-hit)
|
||||
- [Go environment outputs](advanced-usage.md#go-environment-outputs)
|
||||
- [Custom download URL](advanced-usage.md#custom-download-url)
|
||||
- [Using `setup-go` on GHES](advanced-usage.md#using-setup-go-on-ghes)
|
||||
|
||||
@@ -240,16 +237,7 @@ steps:
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
The action restores and saves two independent cache entries:
|
||||
|
||||
- **Module sources (`GOMODCACHE`)**: keyed by the runner OS and dependency-file hash. These sources can be reused across Go versions, runner architectures, and runner images on the same OS.
|
||||
- **Build outputs (`GOCACHE`)**: keyed by the runner OS, runner architecture, Linux runner image (when applicable), Go version, and dependency-file hash.
|
||||
|
||||
Both entries use the hash of `cache-dependency-path`, or the root `go.mod` by default. Adding source files or build settings to that input invalidates both entries when those files change. Custom cache locations and the cache service's normal access restrictions still apply.
|
||||
|
||||
Each entry is restored and saved independently, with concurrent transfers where safe. An exact hit is not uploaded again, even when the other entry misses. A failure for one entry does not prevent caching the other. The `cache-hit` output is `true` only when both entries are restored on their primary keys.
|
||||
|
||||
|
||||
### Caching in monorepos
|
||||
|
||||
```yaml
|
||||
@@ -290,7 +278,7 @@ steps:
|
||||
|
||||
### Multi-target builds
|
||||
|
||||
`GOOS` and `GOARCH` are not automatically included in the cache keys. `cache-dependency-path` isn’t limited to dependency files (like `go.sum`); it can also include files that capture these build settings. This prevents jobs for different targets from competing to populate the same immutable cache entry. Because both entries use the same dependency-file hash, this workaround separates the module cache as well as the build cache.
|
||||
`cache-dependency-path` isn’t limited to dependency files (like `go.sum`). It can also include files that capture build settings (for example, `GOOS`/`GOARCH`). This allows separate caches per target platform (OS/architecture) and helps avoid reusing caches across incompatible builds.
|
||||
|
||||
```yaml
|
||||
env:
|
||||
@@ -298,15 +286,16 @@ env:
|
||||
GOARCH: ...
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: echo "$GOOS $GOARCH" > env.txt
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
env.txt
|
||||
- run: go build ./...
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
### Cache invalidation on source changes
|
||||
@@ -373,26 +362,20 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: echo "CACHE_OS_SUFFIX=$ImageOS-" >> $GITHUB_ENV
|
||||
- name: Restore Go module cache
|
||||
id: go-module-cache
|
||||
- name: Restore Go cache
|
||||
id: go-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ env.GO_MOD_CACHE }}
|
||||
key: setup-go-modules-${{ runner.os }}-${{ hashFiles('go.mod') }}
|
||||
- name: Restore Go build cache
|
||||
id: go-build-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ env.GO_BUILD_CACHE }}
|
||||
key: setup-go-build-${{ runner.os }}-${{ env.ARCH }}-${{ env.CACHE_OS_SUFFIX }}go-${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
${{ env.GO_MOD_CACHE }}
|
||||
${{ env.GO_BUILD_CACHE }}
|
||||
key: setup-go-${{ runner.os }}-${{ env.ARCH }}-${{ env.CACHE_OS_SUFFIX }}go-${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('**/go.mod') }}
|
||||
- name: Download modules
|
||||
run: go mod download
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
```
|
||||
|
||||
Use the same file patterns in `hashFiles` as the `cache-dependency-path` of the workflow that saves the caches. The example above matches the default root `go.mod` input.
|
||||
|
||||
> If there are several builds on the same repo, it may make sense to create a cache in one build and use it in others. The action [actions/cache/restore](https://github.com/actions/cache/tree/main/restore#only-restore-cache)
|
||||
should be used in this case.
|
||||
|
||||
@@ -421,7 +404,7 @@ jobs:
|
||||
|
||||
### `cache-hit`
|
||||
|
||||
**cache-hit** is `true` only when both the module cache and the build cache are restored on their primary keys. If only one entry hits its primary key, the output is `false`, but that entry is still used and is not saved again. An entry restored from a non-primary key can still be saved under its primary key:
|
||||
**cache-hit** output is available with a boolean value that indicates whether a cache hit occurred on the primary key:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
@@ -434,39 +417,9 @@ jobs:
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
- run: echo "Were both Go caches restored? ${{ steps.go124.outputs.cache-hit }}"
|
||||
- run: echo "Was the Go cache restored? ${{ steps.go124.outputs.cache-hit }}" # true if cache-hit occurred
|
||||
```
|
||||
|
||||
### Go environment outputs
|
||||
|
||||
Once Go is on the `PATH`, the action exposes the most commonly needed `go env` variables as outputs, so workflows don't have to query them in duplicated `bash`/`pwsh` steps — one expression works on Linux, macOS, and Windows. The values are a snapshot from setup time: anything a later step changes, such as setting `GOOS`/`GOARCH` to cross-compile, is not reflected.
|
||||
|
||||
| Output | Notes |
|
||||
| --- | --- |
|
||||
| `GOPATH` | Go workspace root; `bin` and `pkg/mod` live under it |
|
||||
| `GOBIN` | Go 1.27+ reports an implicit `$GOPATH/bin` default; earlier releases are empty unless `GOBIN` is set |
|
||||
| `go-bin-path` | The base Go binary directory (`GOBIN` or `$GOPATH/bin`), using the first `GOPATH` entry. Cross-compiled binaries go one level deeper, in `$GOPATH/bin/$GOOS_$GOARCH` |
|
||||
| `GOROOT` | Installation directory of the Go toolchain in use |
|
||||
| `GOCACHE` | Build cache directory |
|
||||
| `GOMODCACHE` | Module cache directory |
|
||||
| `GOOS` | Go notation (`linux`, `darwin`, `windows`), unlike `runner.os` |
|
||||
| `GOARCH` | Go notation (`amd64`, `arm64`), unlike `runner.arch` or the `x64` in the action's cache key |
|
||||
| `GOTOOLDIR` | Holds `compile`, `link`, `vet` and the other toolchain binaries |
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/setup-go@v7
|
||||
id: setup-go
|
||||
with:
|
||||
go-version: '1.25.5'
|
||||
- run: echo "Modules are cached in ${{ steps.setup-go.outputs.GOMODCACHE }}"
|
||||
```
|
||||
|
||||
Quote `go-bin-path` in shell commands, since it may contain spaces. Variables without a dedicated output are not exposed; run `go env <NAME>` in a step when you need one.
|
||||
|
||||
> [!NOTE]
|
||||
> These outputs need Go 1.9 or newer (`go env -json`); on older releases the action logs a message, leaves them unset and does not fail. `GOCACHE` needs Go 1.10 and `GOMODCACHE` needs Go 1.15.
|
||||
|
||||
## Custom download URL
|
||||
|
||||
The `go-download-base-url` input lets you download Go from a mirror or alternative source instead of the default `https://go.dev/dl`. This can also be set via the `GO_DOWNLOAD_BASE_URL` environment variable; the input takes precedence over the environment variable.
|
||||
@@ -542,4 +495,4 @@ with:
|
||||
|
||||
If the runner is not able to access github.com, any Go versions requested during a workflow run must come from the runner's tool cache.
|
||||
See "[Setting up the tool cache on self-hosted runners without internet access](https://docs.github.com/en/enterprise-server@3.2/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access)"
|
||||
for more information.
|
||||
for more information.
|
||||
|
||||
153
package-lock.json
generated
153
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "7.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^6.3.0",
|
||||
"@actions/cache": "^6.2.0",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"eslint-plugin-n": "^18.1.0",
|
||||
"globals": "^17.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"nock": "^14.0.0",
|
||||
"nock": "^14.0.17",
|
||||
"prettier": "^3.8.4",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^6.0.3"
|
||||
@@ -42,20 +42,58 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-6.3.0.tgz",
|
||||
"integrity": "sha512-SJKaVMqTgetQgJF0wifi5N2+2mffCLy/D4w/3sJtRanBBGECdi1/OD0wswyLVmqEmPbYB/rVJk41Qn/xEbFjAw==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-6.2.0.tgz",
|
||||
"integrity": "sha512-Nv0xWRmbxfDbAn/70flO/F6tj2Nv4XTYMAsQHiDFSojCDfso/Zni+fRKa14ToI9hnmOW/rQcY1WYb6wsM7Pgwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
"@actions/glob": "^0.6.1",
|
||||
"@actions/http-client": "^4.0.1",
|
||||
"@actions/io": "^3.0.2",
|
||||
"@azure/core-rest-pipeline": "~1.23.0",
|
||||
"@azure/storage-blob": "~12.31.0",
|
||||
"@azure/core-rest-pipeline": "^1.23.0",
|
||||
"@azure/storage-blob": "^12.31.0",
|
||||
"@protobuf-ts/runtime-rpc": "^2.11.1",
|
||||
"semver": "^7.8.5"
|
||||
"semver": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache/node_modules/@actions/glob": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.6.1.tgz",
|
||||
"integrity": "sha512-K4+2Ac5ILcf2ySdJCha+Pop9NcKjxqCL4xL4zI50dgB2PbXgC0+AcP011xfH4Of6b4QEJJg8dyZYv7zl4byTsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^3.0.0",
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/cache/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
@@ -204,9 +242,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz",
|
||||
"integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==",
|
||||
"version": "1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz",
|
||||
"integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
@@ -274,9 +312,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob": {
|
||||
"version": "12.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.31.0.tgz",
|
||||
"integrity": "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg==",
|
||||
"version": "12.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.32.0.tgz",
|
||||
"integrity": "sha512-80LzSNnFQye2LCCBFghAJS6jJQJ7N4bfgZ6qDMgVGRtugZ7TLDKQZ2hczMigmZH3jAcMRdma/IygsC5+0gT7Tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
@@ -290,7 +328,7 @@
|
||||
"@azure/core-util": "^1.11.0",
|
||||
"@azure/core-xml": "^1.4.5",
|
||||
"@azure/logger": "^1.1.4",
|
||||
"@azure/storage-common": "^12.3.0",
|
||||
"@azure/storage-common": "^12.4.0",
|
||||
"events": "^3.0.0",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
@@ -2665,9 +2703,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.11.20",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz",
|
||||
"integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==",
|
||||
"version": "2.10.38",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
|
||||
"integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -2678,9 +2716,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
@@ -2690,9 +2728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.8",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
|
||||
"integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
|
||||
"version": "4.28.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
||||
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2710,11 +2748,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.11.12",
|
||||
"caniuse-lite": "^1.0.30001809",
|
||||
"electron-to-chromium": "^1.5.402",
|
||||
"node-releases": "^2.0.53",
|
||||
"update-browserslist-db": "^1.3.0"
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
"electron-to-chromium": "^1.5.328",
|
||||
"node-releases": "^2.0.36",
|
||||
"update-browserslist-db": "^1.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -2774,9 +2812,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001810",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
|
||||
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
|
||||
"version": "1.0.30001799",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
||||
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2964,7 +3002,6 @@
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
@@ -3056,9 +3093,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.418",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.418.tgz",
|
||||
"integrity": "sha512-UzS26r3AEbG5wSoGVpJKqwHIU9zwQN7LHdVIThDrJpS0I5KdlXFMEb8543fhc9dVnIIAST6ar8rhwa00AL5MlA==",
|
||||
"version": "1.5.376",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
|
||||
"integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3837,9 +3874,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4787,9 +4824,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.15.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz",
|
||||
"integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==",
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz",
|
||||
"integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5057,9 +5094,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nock": {
|
||||
"version": "14.0.15",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-14.0.15.tgz",
|
||||
"integrity": "sha512-S0a47C9pLvcYx/Ugf0H30BVBEcUgMMBDk9VJIDlJ8XGrfH2QDUD4Tgdp45qDIiHttokBG+IbsOtsvIjGR/j3bg==",
|
||||
"version": "14.0.17",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-14.0.17.tgz",
|
||||
"integrity": "sha512-EjRr1weMa4ALQX35AgZTEnP+weJJjlW1KGDiNM2IQC2069YDHas4f4B4UUYR+TTLyKWxJvOz2wObDKQs/LNreA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5079,9 +5116,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.54",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
|
||||
"integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==",
|
||||
"version": "2.0.48",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
|
||||
"integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -5920,9 +5957,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6148,9 +6185,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
@@ -6202,9 +6239,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz",
|
||||
"integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
120
package.json
120
package.json
@@ -1,60 +1,60 @@
|
||||
{
|
||||
"name": "setup-go",
|
||||
"version": "7.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "setup go action",
|
||||
"main": "lib/setup-go.js",
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && ncc build -o dist/setup src/setup-go.ts && ncc build -o dist/cache-save src/cache-save.ts",
|
||||
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
|
||||
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"lint:fix": "eslint \"**/*.ts\" --fix",
|
||||
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage",
|
||||
"pre-checkin": "npm run format && npm run lint:fix && npm run build && npm test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/setup-go.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"go",
|
||||
"setup"
|
||||
],
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^6.3.0",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
"@actions/http-client": "^4.0.1",
|
||||
"@actions/io": "^3.0.2",
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-jest": "^29.15.2",
|
||||
"eslint-plugin-n": "^18.1.0",
|
||||
"globals": "^17.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"nock": "^14.0.0",
|
||||
"prettier": "^3.8.4",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "setup-go",
|
||||
"version": "7.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "setup go action",
|
||||
"main": "lib/setup-go.js",
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && ncc build -o dist/setup src/setup-go.ts && ncc build -o dist/cache-save src/cache-save.ts",
|
||||
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
|
||||
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"lint:fix": "eslint \"**/*.ts\" --fix",
|
||||
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage",
|
||||
"pre-checkin": "npm run format && npm run lint:fix && npm run build && npm test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/setup-go.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"go",
|
||||
"setup"
|
||||
],
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^6.2.0",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
"@actions/http-client": "^4.0.1",
|
||||
"@actions/io": "^3.0.2",
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-jest": "^29.15.2",
|
||||
"eslint-plugin-n": "^18.1.0",
|
||||
"globals": "^17.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"nock": "^14.0.17",
|
||||
"prettier": "^3.8.4",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,13 @@ import fs from 'fs';
|
||||
|
||||
import {State, Outputs} from './constants.js';
|
||||
import {PackageManagerInfo} from './package-managers.js';
|
||||
import {
|
||||
CacheEntry,
|
||||
canRestoreCachesInParallel,
|
||||
getCacheDirectoryPath,
|
||||
getPackageManagerInfo
|
||||
} from './cache-utils.js';
|
||||
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils.js';
|
||||
|
||||
export const restoreCache = async (
|
||||
versionSpec: string,
|
||||
packageManager: string,
|
||||
cacheDependencyPath?: string
|
||||
) => {
|
||||
core.setOutput(Outputs.CacheHit, false);
|
||||
const packageManagerInfo = await getPackageManagerInfo(packageManager);
|
||||
const platform = process.env.RUNNER_OS;
|
||||
const arch = process.arch;
|
||||
@@ -38,51 +32,22 @@ export const restoreCache = async (
|
||||
|
||||
const linuxVersion =
|
||||
process.env.RUNNER_OS === 'Linux' ? `${process.env.ImageOS}-` : '';
|
||||
const primaryKeys = {
|
||||
// Downloaded module sources can be reused across Go versions and runner
|
||||
// architectures. Keep OS isolation for filesystem compatibility.
|
||||
modules: `setup-go-modules-${platform}-${fileHash}`,
|
||||
build: `setup-go-build-${platform}-${arch}-${linuxVersion}go-${versionSpec}-${fileHash}`
|
||||
};
|
||||
const entries: CacheEntry[] = cachePaths.map(directory => ({
|
||||
...directory,
|
||||
primaryKey: primaryKeys[directory.kind]
|
||||
}));
|
||||
const primaryKey = `setup-go-${platform}-${arch}-${linuxVersion}go-${versionSpec}-${fileHash}`;
|
||||
core.debug(`primary key is ${primaryKey}`);
|
||||
|
||||
const restoreEntry = async (entry: CacheEntry) => {
|
||||
core.debug(`${entry.kind} primary key is ${entry.primaryKey}`);
|
||||
try {
|
||||
entry.matchedKey = await cache.restoreCache(
|
||||
[entry.path],
|
||||
entry.primaryKey
|
||||
);
|
||||
if (entry.matchedKey) {
|
||||
core.info(`${entry.kind} cache restored from key: ${entry.matchedKey}`);
|
||||
} else {
|
||||
core.info(`${entry.kind} cache is not found`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
core.warning(`Restore ${entry.kind} cache failed: ${message}`);
|
||||
}
|
||||
};
|
||||
core.saveState(State.CachePrimaryKey, primaryKey);
|
||||
|
||||
if (canRestoreCachesInParallel(entries)) {
|
||||
await Promise.all(entries.map(restoreEntry));
|
||||
} else {
|
||||
for (const entry of entries) {
|
||||
await restoreEntry(entry);
|
||||
}
|
||||
const cacheKey = await cache.restoreCache(cachePaths, primaryKey);
|
||||
core.setOutput(Outputs.CacheHit, Boolean(cacheKey));
|
||||
|
||||
if (!cacheKey) {
|
||||
core.info(`Cache is not found`);
|
||||
core.setOutput(Outputs.CacheHit, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve the resolved paths so the post step uses the same cache versions,
|
||||
// even if a later step changes the Go environment.
|
||||
core.saveState(State.CacheEntries, entries);
|
||||
core.setOutput(
|
||||
Outputs.CacheHit,
|
||||
entries.length === packageManagerInfo.cacheFolderCommandList.length &&
|
||||
entries.every(entry => entry.matchedKey === entry.primaryKey)
|
||||
);
|
||||
core.saveState(State.CacheMatchedKey, cacheKey);
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
};
|
||||
|
||||
const findDependencyFile = (packageManager: PackageManagerInfo) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import fs from 'fs';
|
||||
import {State} from './constants.js';
|
||||
import type {CacheEntry} from './cache-utils.js';
|
||||
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils.js';
|
||||
|
||||
// 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
|
||||
@@ -39,43 +39,47 @@ export async function run(earlyExit?: boolean) {
|
||||
}
|
||||
|
||||
const cachePackages = async () => {
|
||||
const entries: CacheEntry[] = JSON.parse(
|
||||
core.getState(State.CacheEntries) || '[]'
|
||||
const packageManager = 'default';
|
||||
|
||||
const state = core.getState(State.CacheMatchedKey);
|
||||
const primaryKey = core.getState(State.CachePrimaryKey);
|
||||
|
||||
const packageManagerInfo = await getPackageManagerInfo(packageManager);
|
||||
|
||||
const cachePaths = await getCacheDirectoryPath(packageManagerInfo);
|
||||
|
||||
const nonExistingPaths = cachePaths.filter(
|
||||
cachePath => !fs.existsSync(cachePath)
|
||||
);
|
||||
if (!entries.length) {
|
||||
|
||||
if (nonExistingPaths.length === cachePaths.length) {
|
||||
core.warning('There are no cache folders on the disk');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nonExistingPaths.length) {
|
||||
logWarning(
|
||||
`Cache folder path is retrieved but doesn't exist on disk: ${nonExistingPaths.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!primaryKey) {
|
||||
core.info(
|
||||
'Cache keys were not generated. Please check the log messages above for more errors or information'
|
||||
'Primary key was not generated. Please check the log messages above for more errors or information'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
entries.map(async entry => {
|
||||
try {
|
||||
await saveCacheEntry(entry);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
core.warning(`Save ${entry.kind} cache failed: ${message}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const saveCacheEntry = async (entry: CacheEntry) => {
|
||||
const {kind, path, primaryKey, matchedKey} = entry;
|
||||
if (primaryKey === matchedKey) {
|
||||
if (primaryKey === state) {
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
logWarning(`${kind} cache folder doesn't exist on disk: ${path}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheId = await cache.saveCache([path], primaryKey);
|
||||
const cacheId = await cache.saveCache(cachePaths, primaryKey);
|
||||
if (cacheId === -1) {
|
||||
// saveCache returns -1 without throwing when the cache was not saved, e.g.
|
||||
// a reserve collision or a read-only token (fork PR). @actions/cache has
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {
|
||||
supportedPackageManagers,
|
||||
PackageManagerInfo,
|
||||
CacheKind
|
||||
PackageManagerInfo
|
||||
} from './package-managers.js';
|
||||
|
||||
export interface CacheDirectory {
|
||||
kind: CacheKind;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface CacheEntry extends CacheDirectory {
|
||||
primaryKey: string;
|
||||
matchedKey?: string;
|
||||
}
|
||||
|
||||
export const getCommandOutput = async (toolCommand: string) => {
|
||||
let {stdout, stderr, exitCode} = await exec.getExecOutput(
|
||||
toolCommand,
|
||||
@@ -49,24 +36,24 @@ export const getPackageManagerInfo = async (packageManager: string) => {
|
||||
|
||||
export const getCacheDirectoryPath = async (
|
||||
packageManagerInfo: PackageManagerInfo
|
||||
): Promise<CacheDirectory[]> => {
|
||||
) => {
|
||||
const pathOutputs = await Promise.allSettled(
|
||||
packageManagerInfo.cacheFolderCommandList.map(async ({kind, command}) => ({
|
||||
kind,
|
||||
path: await getCommandOutput(command)
|
||||
}))
|
||||
packageManagerInfo.cacheFolderCommandList.map(async command =>
|
||||
getCommandOutput(command)
|
||||
)
|
||||
);
|
||||
|
||||
const cachePaths: CacheDirectory[] = [];
|
||||
for (const item of pathOutputs) {
|
||||
const results = pathOutputs.map(item => {
|
||||
if (item.status === 'fulfilled') {
|
||||
if (item.value.path) {
|
||||
cachePaths.push(item.value);
|
||||
}
|
||||
return item.value;
|
||||
} else {
|
||||
core.info(`[warning]getting cache directory path failed: ${item.reason}`);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
const cachePaths = results.filter(item => item);
|
||||
|
||||
if (!cachePaths.length) {
|
||||
throw new Error(`Could not get cache folder paths.`);
|
||||
@@ -75,69 +62,6 @@ export const getCacheDirectoryPath = async (
|
||||
return cachePaths;
|
||||
};
|
||||
|
||||
export function canRestoreCachesInParallel(
|
||||
directories: CacheDirectory[]
|
||||
): boolean {
|
||||
try {
|
||||
const paths = directories.map(directory => {
|
||||
const resolvedPath = resolveCachePath(directory.path);
|
||||
// Conservatively handle case-insensitive Windows/macOS filesystems.
|
||||
return process.platform === 'darwin' || process.platform === 'win32'
|
||||
? resolvedPath.toLowerCase()
|
||||
: resolvedPath;
|
||||
});
|
||||
// A cache archive can replace symlinks traversed by the other restore.
|
||||
// Canonical destinations alone do not capture that extraction footprint.
|
||||
if (
|
||||
paths.some((resolvedPath, index) => {
|
||||
const absolutePath = path.resolve(directories[index].path);
|
||||
const normalizedPath =
|
||||
process.platform === 'darwin' || process.platform === 'win32'
|
||||
? absolutePath.toLowerCase()
|
||||
: absolutePath;
|
||||
return resolvedPath !== normalizedPath;
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return paths.every((parent, index) =>
|
||||
paths.every((child, otherIndex) => {
|
||||
if (index === otherIndex) {
|
||||
return true;
|
||||
}
|
||||
const relative = path.relative(parent, child);
|
||||
return (
|
||||
relative === '..' ||
|
||||
relative.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relative)
|
||||
);
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
// If the paths cannot be resolved safely, avoid concurrent extraction.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCachePath(directory: string): string {
|
||||
const absolutePath = path.resolve(directory);
|
||||
try {
|
||||
fs.lstatSync(absolutePath);
|
||||
} catch (error) {
|
||||
const parent = path.dirname(absolutePath);
|
||||
if (
|
||||
(error as NodeJS.ErrnoException).code !== 'ENOENT' ||
|
||||
parent === absolutePath
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
// Cold cache directories may not exist yet. Resolve their existing
|
||||
// ancestors so symlink aliases still participate in the overlap check.
|
||||
return path.join(resolveCachePath(parent), path.basename(absolutePath));
|
||||
}
|
||||
return fs.realpathSync(absolutePath);
|
||||
}
|
||||
|
||||
export function isGhes(): boolean {
|
||||
const ghUrl = new URL(
|
||||
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
export enum State {
|
||||
CacheEntries = 'CACHE_ENTRIES'
|
||||
CachePrimaryKey = 'CACHE_KEY',
|
||||
CacheMatchedKey = 'CACHE_RESULT'
|
||||
}
|
||||
|
||||
export enum Outputs {
|
||||
CacheHit = 'cache-hit',
|
||||
GoBinPath = 'go-bin-path'
|
||||
CacheHit = 'cache-hit'
|
||||
}
|
||||
|
||||
export const GO_ENV_OUTPUTS = [
|
||||
'GOPATH',
|
||||
'GOBIN',
|
||||
'GOROOT',
|
||||
'GOCACHE',
|
||||
'GOMODCACHE',
|
||||
'GOOS',
|
||||
'GOARCH',
|
||||
'GOTOOLDIR'
|
||||
] as const;
|
||||
|
||||
export type GoEnvVar = (typeof GO_ENV_OUTPUTS)[number];
|
||||
export type GoEnv = Partial<Record<GoEnvVar, string>>;
|
||||
|
||||
116
src/main.ts
116
src/main.ts
@@ -10,7 +10,6 @@ import cp from 'child_process';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import {Architecture} from './types.js';
|
||||
import {GO_ENV_OUTPUTS, GoEnv, Outputs} from './constants.js';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
@@ -22,6 +21,7 @@ export async function run() {
|
||||
setGoToolchain();
|
||||
|
||||
const cache = core.getBooleanInput('cache');
|
||||
core.info(`Setup go version spec ${versionSpec}`);
|
||||
|
||||
let arch = core.getInput('architecture') as Architecture;
|
||||
|
||||
@@ -30,48 +30,41 @@ export async function run() {
|
||||
}
|
||||
|
||||
if (versionSpec) {
|
||||
core.startGroup('Installed version');
|
||||
try {
|
||||
core.info(`Setup go version spec ${versionSpec}`);
|
||||
const token = core.getInput('token');
|
||||
const auth = !token ? undefined : `token ${token}`;
|
||||
|
||||
const token = core.getInput('token');
|
||||
const auth = !token ? undefined : `token ${token}`;
|
||||
const checkLatest = core.getBooleanInput('check-latest');
|
||||
|
||||
const checkLatest = core.getBooleanInput('check-latest');
|
||||
const goDownloadBaseUrl =
|
||||
core.getInput('go-download-base-url') ||
|
||||
process.env['GO_DOWNLOAD_BASE_URL'] ||
|
||||
undefined;
|
||||
|
||||
const goDownloadBaseUrl =
|
||||
core.getInput('go-download-base-url') ||
|
||||
process.env['GO_DOWNLOAD_BASE_URL'] ||
|
||||
undefined;
|
||||
|
||||
if (goDownloadBaseUrl) {
|
||||
core.info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
|
||||
}
|
||||
|
||||
const installDir = await installer.getGo(
|
||||
versionSpec,
|
||||
checkLatest,
|
||||
auth,
|
||||
arch,
|
||||
goDownloadBaseUrl
|
||||
);
|
||||
|
||||
const installDirVersion = path.basename(path.dirname(installDir));
|
||||
|
||||
core.addPath(path.join(installDir, 'bin'));
|
||||
core.info('Added go to the path');
|
||||
|
||||
const version = installer.makeSemver(installDirVersion);
|
||||
// Go versions less than 1.9 require GOROOT to be set
|
||||
if (semver.lt(version, '1.9.0')) {
|
||||
core.info('Setting GOROOT for Go version < 1.9');
|
||||
core.exportVariable('GOROOT', installDir);
|
||||
}
|
||||
|
||||
core.info(`Successfully set up Go version ${versionSpec}`);
|
||||
} finally {
|
||||
core.endGroup();
|
||||
if (goDownloadBaseUrl) {
|
||||
core.info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
|
||||
}
|
||||
|
||||
const installDir = await installer.getGo(
|
||||
versionSpec,
|
||||
checkLatest,
|
||||
auth,
|
||||
arch,
|
||||
goDownloadBaseUrl
|
||||
);
|
||||
|
||||
const installDirVersion = path.basename(path.dirname(installDir));
|
||||
|
||||
core.addPath(path.join(installDir, 'bin'));
|
||||
core.info('Added go to the path');
|
||||
|
||||
const version = installer.makeSemver(installDirVersion);
|
||||
// Go versions less than 1.9 require GOROOT to be set
|
||||
if (semver.lt(version, '1.9.0')) {
|
||||
core.info('Setting GOROOT for Go version < 1.9');
|
||||
core.exportVariable('GOROOT', installDir);
|
||||
}
|
||||
|
||||
core.info(`Successfully set up Go version ${versionSpec}`);
|
||||
} else {
|
||||
core.info(
|
||||
'[warning]go-version input was not specified. The action will try to use pre-installed version.'
|
||||
@@ -83,11 +76,6 @@ export async function run() {
|
||||
|
||||
const goPath = await io.which('go');
|
||||
const goVersion = (cp.execSync(`${goPath} version`) || '').toString();
|
||||
const goEnvJson = readGoEnv(goPath);
|
||||
|
||||
if (goEnvJson) {
|
||||
setGoEnvOutputs(goEnvJson);
|
||||
}
|
||||
|
||||
if (cache && isCacheFeatureAvailable()) {
|
||||
const packageManager = 'default';
|
||||
@@ -125,46 +113,6 @@ export async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
export function readGoEnv(goPath: string): GoEnv | undefined {
|
||||
try {
|
||||
const rawGoEnv = cp.execFileSync(goPath, ['env', '-json'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
const parsed: unknown = JSON.parse(rawGoEnv);
|
||||
|
||||
if (
|
||||
typeof parsed !== 'object' ||
|
||||
parsed === null ||
|
||||
Array.isArray(parsed)
|
||||
) {
|
||||
throw new Error("'go env -json' did not return a JSON object");
|
||||
}
|
||||
|
||||
return parsed as GoEnv;
|
||||
} catch (error) {
|
||||
core.info(
|
||||
`Unable to read 'go env -json', the Go environment outputs will not be set: ${
|
||||
(error as Error).message
|
||||
}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function setGoEnvOutputs(goEnv: GoEnv): void {
|
||||
for (const name of GO_ENV_OUTPUTS) {
|
||||
core.setOutput(name, goEnv[name] ?? '');
|
||||
}
|
||||
|
||||
core.setOutput(Outputs.GoBinPath, goEnv.GOBIN || goPathBin(goEnv));
|
||||
}
|
||||
|
||||
function goPathBin(goEnv: GoEnv): string {
|
||||
const goPath = (goEnv.GOPATH ?? '').split(path.delimiter)[0];
|
||||
return goPath ? path.join(goPath, 'bin') : '';
|
||||
}
|
||||
|
||||
export async function addBinToPath(): Promise<boolean> {
|
||||
let added = false;
|
||||
const g = await io.which('go');
|
||||
|
||||
@@ -2,19 +2,14 @@ type SupportedPackageManagers = {
|
||||
[prop: string]: PackageManagerInfo;
|
||||
};
|
||||
|
||||
export type CacheKind = 'modules' | 'build';
|
||||
|
||||
export interface PackageManagerInfo {
|
||||
dependencyFilePattern: string;
|
||||
cacheFolderCommandList: {kind: CacheKind; command: string}[];
|
||||
cacheFolderCommandList: string[];
|
||||
}
|
||||
|
||||
export const supportedPackageManagers: SupportedPackageManagers = {
|
||||
default: {
|
||||
dependencyFilePattern: 'go.mod',
|
||||
cacheFolderCommandList: [
|
||||
{kind: 'modules', command: 'go env GOMODCACHE'},
|
||||
{kind: 'build', command: 'go env GOCACHE'}
|
||||
]
|
||||
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user