Compare commits

...
Author SHA1 Message Date
Sampark SharmaandGitHub c76b5b3a03 Release new actions/cache minor version 2022-12-22 07:35:02 +00:00
Sampark SharmaandGitHub fd47902ff8 Merge branch 'main' of https://github.com/actions/toolkit into releases/cache-v3-beta 2022-12-22 07:14:30 +00:00
Sampark SharmaandGitHub 3959b7966e Merge branch 'releases/cache-v3-beta' of https://github.com/actions/toolkit into releases/cache-v3-beta 2022-12-22 07:13:06 +00:00
Sampark SharmaandGitHub c24b93f4b7 Merge pull request #1279 from actions/phantsure/bug-bash
Address bugbash issues
2022-12-22 12:38:16 +05:30
Sampark SharmaandGitHub 2a4f3544ad Merge pull request #1234 from actions/phantsure/version-logging
Add logs for cache version on miss
2022-12-22 11:48:58 +05:30
Sampark SharmaandGitHub 8e69225720 Fix tests 2022-12-21 11:50:03 +00:00
Sampark SharmaandGitHub a20e7c1a03 Address bugbash issues 2022-12-21 10:53:00 +00:00
Sampark SharmaandGitHub c23fe4b81f FIx test 2022-12-21 10:30:22 +00:00
Sampark SharmaandGitHub 034d154f88 Merge branch 'phantsure/version-logging' of https://github.com/actions/toolkit into phantsure/version-logging 2022-12-21 10:27:13 +00:00
Sampark SharmaandGitHub ccfa36f304 Address review comments 2022-12-21 10:24:52 +00:00
e96dc8a69a Update packages/cache/src/internal/cacheHttpClient.ts
Co-authored-by: Bishal Prasad <[email protected]>
2022-12-16 18:17:37 +05:30
Sampark SharmaandGitHub b8c50aa82d Fix response code 2022-12-16 05:55:53 +00:00
Sampark SharmaandGitHub 24685611e2 Add current scope from github ref 2022-12-16 05:54:48 +00:00
Sampark SharmaandGitHub e559a15ca6 Fix test 2022-12-13 11:36:10 +00:00
Sampark SharmaandGitHub 816c1b3760 Fix tests 2022-12-13 11:25:40 +00:00
Sampark SharmaandGitHub 0ff35ed4a1 Update for new beta cache package release 2022-12-12 12:45:28 +00:00
Sampark SharmaandGitHub 8b695c1f30 Merge pull request #1260 from actions/phantsure/cache-testing
Fix known issues for cache
2022-12-12 18:09:19 +05:30
Sampark SharmaandGitHub 2d3c79e6fe Address review comments 2022-12-12 12:18:07 +00:00
e7e19845ca Update packages/cache/src/internal/cacheHttpClient.ts
Co-authored-by: Bishal Prasad <[email protected]>
2022-12-12 17:44:34 +05:30
Sampark SharmaandGitHub cad074ceef Add better comments 2022-12-12 10:58:58 +00:00
Sampark SharmaandGitHub d7ae8cd1ef Fix tests 2022-12-12 07:51:54 +00:00
Sampark SharmaandGitHub 6bc5dc5a23 Fix test 2022-12-12 07:25:36 +00:00
Sampark SharmaandGitHub bbf5659dfa Fix test 2022-12-12 07:04:15 +00:00
Sampark SharmaandGitHub d175a181a0 Add end to end test for cache using bsd on windows
and address review comments
2022-12-12 06:53:11 +00:00
Sampark SharmaandGitHub 0690c10515 Fix test 2022-12-09 11:47:00 +00:00
Sampark SharmaandGitHub 0c23c38c68 Add debug logging for gzip fall back 2022-12-09 11:10:28 +00:00
Sampark SharmaandGitHub 7a532d03f4 Reconfigure catch block 2022-12-09 11:10:18 +00:00
Sampark SharmaandGitHub d31c2dd88d Fix issues 2022-12-09 11:09:54 +00:00
Sampark SharmaandGitHub 27d5148f03 Merge pull request #1257 from actions/phantsure/release-beta-2
Release 3.1.0-beta.2 cache package
2022-12-08 15:05:26 +05:30
Sampark SharmaandGitHub 8e39d78020 Release 3.1.0-beta.2 cache package 2022-12-08 07:13:15 +00:00
Sampark SharmaandGitHub cb3dc49fec Merge pull request #1249 from actions/phantsure/gzip-fallback
Add fallback to gzip compression if cache not found
2022-12-08 10:59:17 +05:30
Sampark SharmaandGitHub de8fda6976 Merge pull request #1244 from actions/phantsure/release-beta-1
Update for beta release of cache package
2022-12-05 13:01:30 +05:30
Sampark SharmaandGitHub 61e630822a Fix audit issues 2022-12-05 06:09:05 +00:00
Sampark SharmaandGitHub c207fbd5db Update for beta release 2022-12-01 10:08:48 +00:00
Sampark SharmaandGitHub b9d1dd898e Address review comments 2022-11-15 11:04:24 +00:00
Sampark SharmaandGitHub aaac0e6c98 Address review comments 2022-11-15 10:32:24 +00:00
Sampark SharmaandGitHub a735d9bcd4 Add logs for cache version on miss 2022-11-14 06:31:26 +00:00
10 changed files with 280 additions and 81 deletions
+90
View File
@@ -0,0 +1,90 @@
name: cache-windows-bsd-unit-tests
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
build:
name: Build
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- shell: bash
run: |
rm "C:\Program Files\Git\usr\bin\tar.exe"
- name: Set Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
# In order to save & restore cache from a shell script, certain env variables need to be set that are only available in the
# node context. This runs a local action that gets and sets the necessary env variables that are needed
- name: Set env variables
uses: ./packages/cache/__tests__/__fixtures__/
# Need root node_modules because certain npm packages like jest are configured for the entire repository and it won't be possible
# without these to just compile the cache package
- name: Install root npm packages
run: npm ci
- name: Compile cache package
run: |
npm ci
npm run tsc
working-directory: packages/cache
- name: Generate files in working directory
shell: bash
run: packages/cache/__tests__/create-cache-files.sh ${{ runner.os }} test-cache
- name: Generate files outside working directory
shell: bash
run: packages/cache/__tests__/create-cache-files.sh ${{ runner.os }} ~/test-cache
# We're using node -e to call the functions directly available in the @actions/cache package
- name: Save cache using saveCache()
run: |
node -e "Promise.resolve(require('./packages/cache/lib/cache').saveCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}'))"
- name: Delete cache folders before restoring
shell: bash
run: |
rm -rf test-cache
rm -rf ~/test-cache
- name: Restore cache using restoreCache() with http-client
run: |
node -e "Promise.resolve(require('./packages/cache/lib/cache').restoreCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}',[],{useAzureSdk: false}))"
- name: Verify cache restored with http-client
shell: bash
run: |
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} test-cache
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
- name: Delete cache folders before restoring
shell: bash
run: |
rm -rf test-cache
rm -rf ~/test-cache
- name: Restore cache using restoreCache() with Azure SDK
run: |
node -e "Promise.resolve(require('./packages/cache/lib/cache').restoreCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}'))"
- name: Verify cache restored with Azure SDK
shell: bash
run: |
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} test-cache
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
+14
View File
@@ -91,3 +91,17 @@
### 3.0.6 ### 3.0.6
- Added `@azure/abort-controller` to dependencies to fix compatibility issue with ESM [#1208](https://github.com/actions/toolkit/issues/1208) - Added `@azure/abort-controller` to dependencies to fix compatibility issue with ESM [#1208](https://github.com/actions/toolkit/issues/1208)
### 3.1.0-beta.1
- Update actions/cache on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984))
### 3.1.0-beta.2
- Added support for fallback to gzip to restore old caches on windows.
### 3.1.0-beta.3
- Bug Fixes for fallback to gzip to restore old caches on windows and bsdtar if gnutar is not available.
### 3.1.0
- Update actions/cache on windows to use gnu tar and zstd by default
- Update actions/cache on windows to fallback to bsdtar and zstd if gnu tar is not available.
- Added support for fallback to gzip to restore old caches on windows.
+1 -1
View File
@@ -174,7 +174,7 @@ test('restore with zstd as default but gzip compressed cache found on windows',
const getCacheMock = jest.spyOn(cacheHttpClient, 'getCacheEntry') const getCacheMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
getCacheMock getCacheMock
.mockImplementationOnce(async () => { .mockImplementationOnce(async () => {
throw new Error('Cache not found.') return Promise.resolve(null)
}) })
.mockImplementationOnce(async () => { .mockImplementationOnce(async () => {
return Promise.resolve(cacheEntry) return Promise.resolve(cacheEntry)
+68 -23
View File
@@ -73,7 +73,9 @@ test('zstd extract tar', async () => {
'--use-compress-program', '--use-compress-program',
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30' IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
]) ])
.join(' ') .join(' '),
undefined,
{cwd: undefined}
) )
}) })
@@ -92,20 +94,31 @@ test('zstd extract tar with windows BSDtar', async () => {
await tar.extractTar(archivePath, CompressionMethod.Zstd) await tar.extractTar(archivePath, CompressionMethod.Zstd)
expect(mkdirMock).toHaveBeenCalledWith(workspace) expect(mkdirMock).toHaveBeenCalledWith(workspace)
expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledTimes(2)
expect(execMock).toHaveBeenCalledWith(
expect(execMock).toHaveBeenNthCalledWith(
1,
[ [
'zstd -d --long=30 -o', 'zstd -d --long=30 --force -o',
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
'&&', ].join(' '),
undefined,
{cwd: undefined}
)
expect(execMock).toHaveBeenNthCalledWith(
2,
[
`"${tarPath}"`, `"${tarPath}"`,
'-xf', '-xf',
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
'-P', '-P',
'-C', '-C',
workspace?.replace(/\\/g, '/') workspace?.replace(/\\/g, '/')
].join(' ') ].join(' '),
undefined,
{cwd: undefined}
) )
} }
}) })
@@ -135,7 +148,9 @@ test('gzip extract tar', async () => {
.concat(IS_WINDOWS ? ['--force-local'] : []) .concat(IS_WINDOWS ? ['--force-local'] : [])
.concat(IS_MAC ? ['--delay-directory-restore'] : []) .concat(IS_MAC ? ['--delay-directory-restore'] : [])
.concat(['-z']) .concat(['-z'])
.join(' ') .join(' '),
undefined,
{cwd: undefined}
) )
}) })
@@ -162,7 +177,9 @@ test('gzip extract GNU tar on windows with GNUtar in path', async () => {
workspace?.replace(/\\/g, '/'), workspace?.replace(/\\/g, '/'),
'--force-local', '--force-local',
'-z' '-z'
].join(' ') ].join(' '),
undefined,
{cwd: undefined}
) )
} }
}) })
@@ -230,8 +247,10 @@ test('zstd create tar with windows BSDtar', async () => {
const tarPath = SystemTarPathOnWindows const tarPath = SystemTarPathOnWindows
expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledTimes(2)
expect(execMock).toHaveBeenCalledWith(
expect(execMock).toHaveBeenNthCalledWith(
1,
[ [
`"${tarPath}"`, `"${tarPath}"`,
'--posix', '--posix',
@@ -243,9 +262,18 @@ test('zstd create tar with windows BSDtar', async () => {
'-C', '-C',
workspace?.replace(/\\/g, '/'), workspace?.replace(/\\/g, '/'),
'--files-from', '--files-from',
ManifestFilename, ManifestFilename
'&&', ].join(' '),
'zstd -T0 --long=30 -o', undefined, // args
{
cwd: archiveFolder
}
)
expect(execMock).toHaveBeenNthCalledWith(
2,
[
'zstd -T0 --long=30 --force -o',
CacheFilename.Zstd.replace(/\\/g, '/'), CacheFilename.Zstd.replace(/\\/g, '/'),
TarFilename.replace(/\\/g, '/') TarFilename.replace(/\\/g, '/')
].join(' '), ].join(' '),
@@ -320,7 +348,9 @@ test('zstd list tar', async () => {
'--use-compress-program', '--use-compress-program',
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30' IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
]) ])
.join(' ') .join(' '),
undefined,
{cwd: undefined}
) )
}) })
@@ -335,18 +365,29 @@ test('zstd list tar with windows BSDtar', async () => {
await tar.listTar(archivePath, CompressionMethod.Zstd) await tar.listTar(archivePath, CompressionMethod.Zstd)
const tarPath = SystemTarPathOnWindows const tarPath = SystemTarPathOnWindows
expect(execMock).toHaveBeenCalledTimes(1) expect(execMock).toHaveBeenCalledTimes(2)
expect(execMock).toHaveBeenCalledWith(
expect(execMock).toHaveBeenNthCalledWith(
1,
[ [
'zstd -d --long=30 -o', 'zstd -d --long=30 --force -o',
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
'&&', ].join(' '),
undefined,
{cwd: undefined}
)
expect(execMock).toHaveBeenNthCalledWith(
2,
[
`"${tarPath}"`, `"${tarPath}"`,
'-tf', '-tf',
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
'-P' '-P'
].join(' ') ].join(' '),
undefined,
{cwd: undefined}
) )
} }
}) })
@@ -372,7 +413,9 @@ test('zstdWithoutLong list tar', async () => {
.concat(IS_WINDOWS ? ['--force-local'] : []) .concat(IS_WINDOWS ? ['--force-local'] : [])
.concat(IS_MAC ? ['--delay-directory-restore'] : []) .concat(IS_MAC ? ['--delay-directory-restore'] : [])
.concat(['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd']) .concat(['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd'])
.join(' ') .join(' '),
undefined,
{cwd: undefined}
) )
}) })
@@ -396,6 +439,8 @@ test('gzip list tar', async () => {
.concat(IS_WINDOWS ? ['--force-local'] : []) .concat(IS_WINDOWS ? ['--force-local'] : [])
.concat(IS_MAC ? ['--delay-directory-restore'] : []) .concat(IS_MAC ? ['--delay-directory-restore'] : [])
.concat(['-z']) .concat(['-z'])
.join(' ') .join(' '),
undefined,
{cwd: undefined}
) )
}) })
+8 -8
View File
@@ -1,12 +1,12 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.0.6", "version": "3.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.0.6", "version": "3.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
@@ -457,9 +457,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@@ -998,9 +998,9 @@
} }
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.0.6", "version": "3.1.0",
"preview": true, "preview": true,
"description": "Actions cache lib", "description": "Actions cache lib",
"keywords": [ "keywords": [
+13 -14
View File
@@ -91,14 +91,12 @@ export async function restoreCache(
let compressionMethod = await utils.getCompressionMethod() let compressionMethod = await utils.getCompressionMethod()
let archivePath = '' let archivePath = ''
try { try {
try { // path are needed to compute version
// path are needed to compute version cacheEntry = await cacheHttpClient.getCacheEntry(keys, paths, {
cacheEntry = await cacheHttpClient.getCacheEntry(keys, paths, { compressionMethod
compressionMethod })
}) if (!cacheEntry?.archiveLocation) {
} catch (error) { // This is to support the old cache entry created by gzip on windows.
// This is to support the old cache entry created
// by the old version of the cache action on windows.
if ( if (
process.platform === 'win32' && process.platform === 'win32' &&
compressionMethod !== CompressionMethod.Gzip compressionMethod !== CompressionMethod.Gzip
@@ -108,17 +106,18 @@ export async function restoreCache(
compressionMethod compressionMethod
}) })
if (!cacheEntry?.archiveLocation) { if (!cacheEntry?.archiveLocation) {
throw error return undefined
} }
core.info(
"Couldn't find cache entry with zstd compression, falling back to gzip compression."
)
} else { } else {
throw error // Cache not found
return undefined
} }
} }
if (!cacheEntry?.archiveLocation) {
// Cache not found
return undefined
}
archivePath = path.join( archivePath = path.join(
await utils.createTempDirectory(), await utils.createTempDirectory(),
utils.getCacheFileName(compressionMethod) utils.getCacheFileName(compressionMethod)
+33 -1
View File
@@ -17,7 +17,8 @@ import {
CommitCacheRequest, CommitCacheRequest,
ReserveCacheRequest, ReserveCacheRequest,
ReserveCacheResponse, ReserveCacheResponse,
ITypedResponseWithError ITypedResponseWithError,
ArtifactCacheList
} from './contracts' } from './contracts'
import {downloadCacheHttpClient, downloadCacheStorageSDK} from './downloadUtils' import {downloadCacheHttpClient, downloadCacheStorageSDK} from './downloadUtils'
import { import {
@@ -103,7 +104,12 @@ export async function getCacheEntry(
const response = await retryTypedResponse('getCacheEntry', async () => const response = await retryTypedResponse('getCacheEntry', async () =>
httpClient.getJson<ArtifactCacheEntry>(getCacheApiUrl(resource)) httpClient.getJson<ArtifactCacheEntry>(getCacheApiUrl(resource))
) )
// Cache not found
if (response.statusCode === 204) { if (response.statusCode === 204) {
// List cache for primary key only if cache miss occurs
if (core.isDebug()) {
await printCachesListForDiagnostics(keys[0], httpClient, version)
}
return null return null
} }
if (!isSuccessStatusCode(response.statusCode)) { if (!isSuccessStatusCode(response.statusCode)) {
@@ -113,6 +119,7 @@ export async function getCacheEntry(
const cacheResult = response.result const cacheResult = response.result
const cacheDownloadUrl = cacheResult?.archiveLocation const cacheDownloadUrl = cacheResult?.archiveLocation
if (!cacheDownloadUrl) { if (!cacheDownloadUrl) {
// Cache achiveLocation not found. This should never happen, and hence bail out.
throw new Error('Cache not found.') throw new Error('Cache not found.')
} }
core.setSecret(cacheDownloadUrl) core.setSecret(cacheDownloadUrl)
@@ -122,6 +129,31 @@ export async function getCacheEntry(
return cacheResult return cacheResult
} }
async function printCachesListForDiagnostics(
key: string,
httpClient: HttpClient,
version: string
): Promise<void> {
const resource = `caches?key=${encodeURIComponent(key)}`
const response = await retryTypedResponse('listCache', async () =>
httpClient.getJson<ArtifactCacheList>(getCacheApiUrl(resource))
)
if (response.statusCode === 200) {
const cacheListResult = response.result
const totalCount = cacheListResult?.totalCount
if (totalCount && totalCount > 0) {
core.debug(
`No matching cache found for cache key '${key}', version '${version} and scope ${process.env['GITHUB_REF']}. There exist one or more cache(s) with similar key but they have different version or scope. See more info on cache matching here: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key \nOther caches with similar key:`
)
for (const cacheEntry of cacheListResult?.artifactCaches || []) {
core.debug(
`Cache Key: ${cacheEntry?.cacheKey}, Cache Version: ${cacheEntry?.cacheVersion}, Cache Scope: ${cacheEntry?.scope}, Cache Created: ${cacheEntry?.creationTime}`
)
}
}
}
}
export async function downloadCache( export async function downloadCache(
archiveLocation: string, archiveLocation: string,
archivePath: string, archivePath: string,
+6
View File
@@ -9,10 +9,16 @@ export interface ITypedResponseWithError<T> extends TypedResponse<T> {
export interface ArtifactCacheEntry { export interface ArtifactCacheEntry {
cacheKey?: string cacheKey?: string
scope?: string scope?: string
cacheVersion?: string
creationTime?: string creationTime?: string
archiveLocation?: string archiveLocation?: string
} }
export interface ArtifactCacheList {
totalCount: number
artifactCaches?: ArtifactCacheEntry[]
}
export interface CommitCacheRequest { export interface CommitCacheRequest {
size: number size: number
} }
+46 -33
View File
@@ -14,7 +14,7 @@ import {
const IS_WINDOWS = process.platform === 'win32' const IS_WINDOWS = process.platform === 'win32'
// Function also mutates the args array. For non-mutation call with passing an empty array. // Returns tar path and type: BSD or GNU
async function getTarPath(): Promise<ArchiveTool> { async function getTarPath(): Promise<ArchiveTool> {
switch (process.platform) { switch (process.platform) {
case 'win32': { case 'win32': {
@@ -43,6 +43,7 @@ async function getTarPath(): Promise<ArchiveTool> {
default: default:
break break
} }
// Default assumption is GNU tar is present in path
return <ArchiveTool>{ return <ArchiveTool>{
path: await io.which('tar', true), path: await io.which('tar', true),
type: ArchiveToolType.GNU type: ArchiveToolType.GNU
@@ -60,6 +61,7 @@ async function getTarArgs(
const cacheFileName = utils.getCacheFileName(compressionMethod) const cacheFileName = utils.getCacheFileName(compressionMethod)
const tarFile = 'cache.tar' const tarFile = 'cache.tar'
const workingDirectory = getWorkingDirectory() const workingDirectory = getWorkingDirectory()
// Speficic args for BSD tar on windows for workaround
const BSD_TAR_ZSTD = const BSD_TAR_ZSTD =
tarPath.type === ArchiveToolType.BSD && tarPath.type === ArchiveToolType.BSD &&
compressionMethod !== CompressionMethod.Gzip && compressionMethod !== CompressionMethod.Gzip &&
@@ -122,11 +124,14 @@ async function getTarArgs(
return args return args
} }
async function getArgs( // Returns commands to run tar and compression program
async function getCommands(
compressionMethod: CompressionMethod, compressionMethod: CompressionMethod,
type: string, type: string,
archivePath = '' archivePath = ''
): Promise<string> { ): Promise<string[]> {
let args
const tarPath = await getTarPath() const tarPath = await getTarPath()
const tarArgs = await getTarArgs( const tarArgs = await getTarArgs(
tarPath, tarPath,
@@ -142,11 +147,18 @@ async function getArgs(
tarPath.type === ArchiveToolType.BSD && tarPath.type === ArchiveToolType.BSD &&
compressionMethod !== CompressionMethod.Gzip && compressionMethod !== CompressionMethod.Gzip &&
IS_WINDOWS IS_WINDOWS
if (BSD_TAR_ZSTD && type !== 'create') { if (BSD_TAR_ZSTD && type !== 'create') {
return [...compressionArgs, ...tarArgs].join(' ') args = [[...compressionArgs].join(' '), [...tarArgs].join(' ')]
} else { } else {
return [...tarArgs, ...compressionArgs].join(' ') args = [[...tarArgs].join(' '), [...compressionArgs].join(' ')]
} }
if (BSD_TAR_ZSTD) {
return args
}
return [args.join(' ')]
} }
function getWorkingDirectory(): string { function getWorkingDirectory(): string {
@@ -171,10 +183,9 @@ async function getDecompressionProgram(
case CompressionMethod.Zstd: case CompressionMethod.Zstd:
return BSD_TAR_ZSTD return BSD_TAR_ZSTD
? [ ? [
'zstd -d --long=30 -o', 'zstd -d --long=30 --force -o',
TarFilename, TarFilename,
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
'&&'
] ]
: [ : [
'--use-compress-program', '--use-compress-program',
@@ -183,10 +194,9 @@ async function getDecompressionProgram(
case CompressionMethod.ZstdWithoutLong: case CompressionMethod.ZstdWithoutLong:
return BSD_TAR_ZSTD return BSD_TAR_ZSTD
? [ ? [
'zstd -d -o', 'zstd -d --force -o',
TarFilename, TarFilename,
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
'&&'
] ]
: ['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd'] : ['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd']
default: default:
@@ -194,6 +204,7 @@ async function getDecompressionProgram(
} }
} }
// Used for creating the archive
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores. // -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
// zstdmt is equivalent to 'zstd -T0' // zstdmt is equivalent to 'zstd -T0'
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit. // --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
@@ -212,8 +223,7 @@ async function getCompressionProgram(
case CompressionMethod.Zstd: case CompressionMethod.Zstd:
return BSD_TAR_ZSTD return BSD_TAR_ZSTD
? [ ? [
'&&', 'zstd -T0 --long=30 --force -o',
'zstd -T0 --long=30 -o',
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
TarFilename TarFilename
] ]
@@ -224,8 +234,7 @@ async function getCompressionProgram(
case CompressionMethod.ZstdWithoutLong: case CompressionMethod.ZstdWithoutLong:
return BSD_TAR_ZSTD return BSD_TAR_ZSTD
? [ ? [
'&&', 'zstd -T0 --force -o',
'zstd -T0 -o',
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
TarFilename TarFilename
] ]
@@ -235,18 +244,29 @@ async function getCompressionProgram(
} }
} }
// Executes all commands as separate processes
async function execCommands(commands: string[], cwd?: string): Promise<void> {
for (const command of commands) {
try {
await exec(command, undefined, {cwd})
} catch (error) {
throw new Error(
`${command.split(' ')[0]} failed with error: ${error?.message}`
)
}
}
}
// List the contents of a tar
export async function listTar( export async function listTar(
archivePath: string, archivePath: string,
compressionMethod: CompressionMethod compressionMethod: CompressionMethod
): Promise<void> { ): Promise<void> {
const args = await getArgs(compressionMethod, 'list', archivePath) const commands = await getCommands(compressionMethod, 'list', archivePath)
try { await execCommands(commands)
await exec(args)
} catch (error) {
throw new Error(`Tar failed with error: ${error?.message}`)
}
} }
// Extract a tar
export async function extractTar( export async function extractTar(
archivePath: string, archivePath: string,
compressionMethod: CompressionMethod compressionMethod: CompressionMethod
@@ -254,14 +274,11 @@ export async function extractTar(
// Create directory to extract tar into // Create directory to extract tar into
const workingDirectory = getWorkingDirectory() const workingDirectory = getWorkingDirectory()
await io.mkdirP(workingDirectory) await io.mkdirP(workingDirectory)
const args = await getArgs(compressionMethod, 'extract', archivePath) const commands = await getCommands(compressionMethod, 'extract', archivePath)
try { await execCommands(commands)
await exec(args)
} catch (error) {
throw new Error(`Tar failed with error: ${error?.message}`)
}
} }
// Create a tar
export async function createTar( export async function createTar(
archiveFolder: string, archiveFolder: string,
sourceDirectories: string[], sourceDirectories: string[],
@@ -272,10 +289,6 @@ export async function createTar(
path.join(archiveFolder, ManifestFilename), path.join(archiveFolder, ManifestFilename),
sourceDirectories.join('\n') sourceDirectories.join('\n')
) )
const args = await getArgs(compressionMethod, 'create') const commands = await getCommands(compressionMethod, 'create')
try { await execCommands(commands, archiveFolder)
await exec(args, undefined, {cwd: archiveFolder})
} catch (error) {
throw new Error(`Tar failed with error: ${error?.message}`)
}
} }