2020-05-06 11:10:18 -04:00
|
|
|
import {exec} from '@actions/exec'
|
|
|
|
|
import * as io from '@actions/io'
|
|
|
|
|
import {existsSync, writeFileSync} from 'fs'
|
|
|
|
|
import * as path from 'path'
|
|
|
|
|
import * as utils from './cacheUtils'
|
2022-11-17 09:12:53 +00:00
|
|
|
import {CompressionMethod, SystemTarPathOnWindows} from './constants'
|
2022-08-18 11:01:22 +00:00
|
|
|
|
|
|
|
|
const IS_WINDOWS = process.platform === 'win32'
|
2020-05-06 11:10:18 -04:00
|
|
|
|
2022-11-16 20:18:31 +00:00
|
|
|
// Function also mutates the args array. For non-mutation call with passing an empty array.
|
2022-11-23 07:39:15 +00:00
|
|
|
async function getTarPath(): Promise<string> {
|
2021-02-02 20:09:10 +01:00
|
|
|
switch (process.platform) {
|
|
|
|
|
case 'win32': {
|
2022-11-15 08:41:04 +00:00
|
|
|
const gnuTar = await utils.getGnuTarPathOnWindows()
|
2022-11-23 07:39:15 +00:00
|
|
|
const systemTar = SystemTarPathOnWindows
|
2022-11-15 08:41:04 +00:00
|
|
|
if (gnuTar) {
|
2022-11-08 08:47:35 +00:00
|
|
|
// Use GNUtar as default on windows
|
2022-11-15 10:18:46 +00:00
|
|
|
return gnuTar
|
2021-02-02 20:09:10 +01:00
|
|
|
} else if (existsSync(systemTar)) {
|
2022-11-15 10:18:46 +00:00
|
|
|
return systemTar
|
2021-02-02 20:09:10 +01:00
|
|
|
}
|
|
|
|
|
break
|
2020-05-06 11:10:18 -04:00
|
|
|
}
|
2021-02-02 20:09:10 +01:00
|
|
|
case 'darwin': {
|
|
|
|
|
const gnuTar = await io.which('gtar', false)
|
|
|
|
|
if (gnuTar) {
|
2021-04-09 13:38:55 -05:00
|
|
|
// fix permission denied errors when extracting BSD tar archive with GNU tar - https://github.com/actions/cache/issues/527
|
2022-11-15 10:18:46 +00:00
|
|
|
return gnuTar
|
2021-02-02 20:09:10 +01:00
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break
|
2020-05-06 11:10:18 -04:00
|
|
|
}
|
2022-11-15 10:18:46 +00:00
|
|
|
return await io.which('tar', true)
|
2020-05-06 11:10:18 -04:00
|
|
|
}
|
|
|
|
|
|
2022-11-23 07:39:15 +00:00
|
|
|
async function getTarArgs(
|
|
|
|
|
compressionMethod: CompressionMethod,
|
|
|
|
|
type: string,
|
|
|
|
|
archivePath = ''
|
|
|
|
|
): Promise<string[]> {
|
|
|
|
|
const args = []
|
|
|
|
|
const manifestFilename = 'manifest.txt'
|
|
|
|
|
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
|
|
|
|
const tarFile = 'cache.tar'
|
|
|
|
|
const tarPath = await getTarPath()
|
|
|
|
|
const workingDirectory = getWorkingDirectory()
|
|
|
|
|
const BSD_TAR_WINDOWS = IS_WINDOWS && tarPath === SystemTarPathOnWindows
|
|
|
|
|
|
|
|
|
|
// Method specific args
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'create':
|
|
|
|
|
args.push(
|
|
|
|
|
'--posix',
|
|
|
|
|
'-cf',
|
|
|
|
|
BSD_TAR_WINDOWS
|
|
|
|
|
? tarFile
|
|
|
|
|
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'--exclude',
|
|
|
|
|
BSD_TAR_WINDOWS
|
|
|
|
|
? tarFile
|
|
|
|
|
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'-P',
|
|
|
|
|
'-C',
|
|
|
|
|
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'--files-from',
|
|
|
|
|
manifestFilename
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
case 'extract':
|
|
|
|
|
args.push(
|
|
|
|
|
'-xf',
|
|
|
|
|
BSD_TAR_WINDOWS
|
|
|
|
|
? tarFile
|
|
|
|
|
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'-P',
|
|
|
|
|
'-C',
|
|
|
|
|
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
// TODO: Correct the below code especially archivePath for BSD_TAR_WINDOWS
|
|
|
|
|
case 'list':
|
|
|
|
|
args.push(
|
|
|
|
|
'-tf',
|
|
|
|
|
BSD_TAR_WINDOWS
|
|
|
|
|
? tarFile
|
|
|
|
|
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'-P'
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Platform specific args
|
|
|
|
|
switch (process.platform) {
|
|
|
|
|
case 'win32': {
|
|
|
|
|
const gnuTar = await utils.getGnuTarPathOnWindows()
|
|
|
|
|
if (gnuTar) {
|
|
|
|
|
// Use GNUtar as default on windows
|
|
|
|
|
args.push('--force-local')
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case 'darwin': {
|
|
|
|
|
const gnuTar = await io.which('gtar', false)
|
|
|
|
|
if (gnuTar) {
|
|
|
|
|
// fix permission denied errors when extracting BSD tar archive with GNU tar - https://github.com/actions/cache/issues/527
|
|
|
|
|
args.push('--delay-directory-restore')
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 10:18:46 +00:00
|
|
|
async function execTar(args: string[], cwd?: string): Promise<void> {
|
2020-05-06 11:10:18 -04:00
|
|
|
try {
|
2022-11-23 07:39:15 +00:00
|
|
|
await exec(`"${await getTarPath()}"`, args, {cwd})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error(`Tar failed with error: ${error?.message}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function execCommand(
|
|
|
|
|
command: string,
|
|
|
|
|
args: string[],
|
|
|
|
|
cwd?: string
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
await exec(command, args, {cwd})
|
2020-05-06 11:10:18 -04:00
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error(`Tar failed with error: ${error?.message}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getWorkingDirectory(): string {
|
|
|
|
|
return process.env['GITHUB_WORKSPACE'] ?? process.cwd()
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-18 11:01:22 +00:00
|
|
|
// Common function for extractTar and listTar to get the compression method
|
2022-11-17 09:12:53 +00:00
|
|
|
async function getCompressionProgram(
|
|
|
|
|
compressionMethod: CompressionMethod
|
|
|
|
|
): Promise<string[]> {
|
2022-08-18 11:01:22 +00:00
|
|
|
// -d: Decompress.
|
|
|
|
|
// unzstd is equivalent to 'zstd -d'
|
|
|
|
|
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
|
|
|
|
// Using 30 here because we also support 32-bit self-hosted runners.
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarPath = await getTarPath()
|
|
|
|
|
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
|
|
|
|
const tarFile = 'cache.tar'
|
2022-11-21 12:05:03 +00:00
|
|
|
const BSD_TAR_WINDOWS = IS_WINDOWS && tarPath === SystemTarPathOnWindows
|
2022-08-18 11:01:22 +00:00
|
|
|
switch (compressionMethod) {
|
|
|
|
|
case CompressionMethod.Zstd:
|
2022-11-21 12:05:03 +00:00
|
|
|
if (BSD_TAR_WINDOWS) {
|
2022-11-23 07:39:15 +00:00
|
|
|
return [
|
|
|
|
|
'zstd -d --long=30 -o',
|
|
|
|
|
tarFile,
|
|
|
|
|
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'&&'
|
|
|
|
|
]
|
2022-11-16 20:18:31 +00:00
|
|
|
}
|
2022-08-18 11:01:22 +00:00
|
|
|
return [
|
|
|
|
|
'--use-compress-program',
|
2022-11-17 09:12:53 +00:00
|
|
|
IS_WINDOWS ? 'zstd -d --long=30' : 'unzstd --long=30'
|
2022-08-18 11:01:22 +00:00
|
|
|
]
|
|
|
|
|
case CompressionMethod.ZstdWithoutLong:
|
2022-11-21 12:05:03 +00:00
|
|
|
if (BSD_TAR_WINDOWS) {
|
2022-11-23 07:39:15 +00:00
|
|
|
return [
|
|
|
|
|
'zstd -d -o',
|
|
|
|
|
tarFile,
|
|
|
|
|
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
'&&'
|
|
|
|
|
]
|
2022-11-16 20:18:31 +00:00
|
|
|
}
|
2022-11-17 08:53:46 +00:00
|
|
|
return ['--use-compress-program', IS_WINDOWS ? 'zstd -d' : 'unzstd']
|
2022-08-18 11:01:22 +00:00
|
|
|
default:
|
|
|
|
|
return ['-z']
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function listTar(
|
|
|
|
|
archivePath: string,
|
|
|
|
|
compressionMethod: CompressionMethod
|
|
|
|
|
): Promise<void> {
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarPath = await getTarPath()
|
|
|
|
|
const BSD_TAR_WINDOWS = IS_WINDOWS && tarPath === SystemTarPathOnWindows
|
|
|
|
|
const compressionArgs = await getCompressionProgram(compressionMethod)
|
|
|
|
|
const tarArgs = await getTarArgs(compressionMethod, 'list', archivePath)
|
|
|
|
|
// TODO: Add a test for BSD tar on windows
|
|
|
|
|
if (BSD_TAR_WINDOWS) {
|
|
|
|
|
const command = compressionArgs[0]
|
|
|
|
|
const args = compressionArgs.slice(1).concat(tarArgs)
|
|
|
|
|
await execCommand(command, args)
|
|
|
|
|
} else {
|
|
|
|
|
const args = tarArgs.concat(compressionArgs)
|
|
|
|
|
await execTar(args)
|
|
|
|
|
}
|
2022-08-18 11:01:22 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-06 11:10:18 -04:00
|
|
|
export async function extractTar(
|
|
|
|
|
archivePath: string,
|
|
|
|
|
compressionMethod: CompressionMethod
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
// Create directory to extract tar into
|
|
|
|
|
const workingDirectory = getWorkingDirectory()
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarPath = await getTarPath()
|
|
|
|
|
const BSD_TAR_WINDOWS = IS_WINDOWS && tarPath === SystemTarPathOnWindows
|
2020-05-06 11:10:18 -04:00
|
|
|
await io.mkdirP(workingDirectory)
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarArgs = await getTarArgs(compressionMethod, 'extract', archivePath)
|
|
|
|
|
const compressionArgs = await getCompressionProgram(compressionMethod)
|
|
|
|
|
if (BSD_TAR_WINDOWS) {
|
|
|
|
|
const command = compressionArgs[0]
|
|
|
|
|
const args = compressionArgs.slice(1).concat(tarArgs)
|
|
|
|
|
await execCommand(command, args)
|
|
|
|
|
} else {
|
|
|
|
|
const args = tarArgs.concat(compressionArgs)
|
|
|
|
|
await execTar(args)
|
|
|
|
|
}
|
2020-05-06 11:10:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createTar(
|
|
|
|
|
archiveFolder: string,
|
|
|
|
|
sourceDirectories: string[],
|
|
|
|
|
compressionMethod: CompressionMethod
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
// Write source directories to manifest.txt to avoid command length limits
|
|
|
|
|
const manifestFilename = 'manifest.txt'
|
|
|
|
|
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
2022-11-21 12:05:03 +00:00
|
|
|
const tarFile = 'cache.tar'
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarPath = await getTarPath()
|
2022-11-21 12:05:03 +00:00
|
|
|
const BSD_TAR_WINDOWS = IS_WINDOWS && tarPath === SystemTarPathOnWindows
|
2020-05-06 11:10:18 -04:00
|
|
|
writeFileSync(
|
|
|
|
|
path.join(archiveFolder, manifestFilename),
|
|
|
|
|
sourceDirectories.join('\n')
|
|
|
|
|
)
|
2020-05-18 16:33:15 -04:00
|
|
|
const workingDirectory = getWorkingDirectory()
|
|
|
|
|
|
2020-05-06 11:10:18 -04:00
|
|
|
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
2022-08-13 18:01:07 +00:00
|
|
|
// zstdmt is equivalent to 'zstd -T0'
|
2020-05-06 11:10:18 -04:00
|
|
|
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
|
|
|
|
// Using 30 here because we also support 32-bit self-hosted runners.
|
2020-05-18 16:33:15 -04:00
|
|
|
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
2022-11-23 07:39:15 +00:00
|
|
|
function getCompressionProgram(): string[] {
|
2020-05-18 16:33:15 -04:00
|
|
|
switch (compressionMethod) {
|
|
|
|
|
case CompressionMethod.Zstd:
|
2022-11-21 12:05:03 +00:00
|
|
|
if (BSD_TAR_WINDOWS) {
|
2022-11-23 07:39:15 +00:00
|
|
|
return [
|
|
|
|
|
'&&',
|
|
|
|
|
'zstd -T0 --long=30 -o',
|
|
|
|
|
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
tarFile
|
|
|
|
|
]
|
2022-11-16 20:18:31 +00:00
|
|
|
}
|
2022-08-18 11:01:22 +00:00
|
|
|
return [
|
|
|
|
|
'--use-compress-program',
|
2022-11-21 12:05:03 +00:00
|
|
|
IS_WINDOWS ? 'zstd -T0 --long=30' : 'zstdmt --long=30'
|
2022-08-18 11:01:22 +00:00
|
|
|
]
|
2020-05-19 12:38:45 -04:00
|
|
|
case CompressionMethod.ZstdWithoutLong:
|
2022-11-21 12:05:03 +00:00
|
|
|
if (BSD_TAR_WINDOWS) {
|
2022-11-23 07:39:15 +00:00
|
|
|
return [
|
|
|
|
|
'&&',
|
|
|
|
|
'zstd -T0 -o',
|
|
|
|
|
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
|
|
|
|
tarFile
|
|
|
|
|
]
|
2022-11-16 20:18:31 +00:00
|
|
|
}
|
2022-11-21 12:05:03 +00:00
|
|
|
return ['--use-compress-program', IS_WINDOWS ? 'zstd -T0' : 'zstdmt']
|
2020-05-18 16:33:15 -04:00
|
|
|
default:
|
2022-11-21 12:05:03 +00:00
|
|
|
return ['-z']
|
2020-05-18 16:33:15 -04:00
|
|
|
}
|
|
|
|
|
}
|
2022-11-23 07:39:15 +00:00
|
|
|
const tarArgs = await getTarArgs(compressionMethod, 'create')
|
|
|
|
|
const compressionArgs = getCompressionProgram()
|
|
|
|
|
const args = tarArgs.concat(compressionArgs)
|
2022-11-15 10:18:46 +00:00
|
|
|
await execTar(args, archiveFolder)
|
2022-08-18 11:44:15 +00:00
|
|
|
}
|