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' import {CompressionMethod, SystemTarPathOnWindows} from './constants' const IS_WINDOWS = process.platform === 'win32' // Function also mutates the args array. For non-mutation call with passing an empty array. async function getTarPath(args: string[]): Promise { switch (process.platform) { case 'win32': { const gnuTar = await utils.getGnuTarPathOnWindows() const systemTar = `${process.env['windir']}\\System32\\tar.exe` if (gnuTar) { // Use GNUtar as default on windows if (args.length > 0) { args.push('--force-local') } return gnuTar } else if (existsSync(systemTar)) { return systemTar } 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 if (args.length > 0) { args.push('--delay-directory-restore') } return gnuTar } break } default: break } return await io.which('tar', true) } async function execTar(args: string[], cwd?: string): Promise { try { await exec(`"${await getTarPath(args)}"`, args, {cwd}) } catch (error) { throw new Error(`Tar failed with error: ${error?.message}`) } } function getWorkingDirectory(): string { return process.env['GITHUB_WORKSPACE'] ?? process.cwd() } // Common function for extractTar and listTar to get the compression method async function getCompressionProgram( compressionMethod: CompressionMethod ): Promise { // -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. const tarPath = await getTarPath([]) const BSD_TAR_ZSTD = IS_WINDOWS && tarPath === SystemTarPathOnWindows switch (compressionMethod) { case CompressionMethod.Zstd: if (BSD_TAR_ZSTD) { return ['-a'] // auto-detect compression } return [ '--use-compress-program', IS_WINDOWS ? 'zstd -d --long=30' : 'unzstd --long=30' ] case CompressionMethod.ZstdWithoutLong: if (BSD_TAR_ZSTD) { return ['a'] // auto-detect compression } return ['--use-compress-program', IS_WINDOWS ? 'zstd -d' : 'unzstd'] default: return ['-z'] } } export async function listTar( archivePath: string, compressionMethod: CompressionMethod ): Promise { const args = [ ...(await getCompressionProgram(compressionMethod)), '-tf', archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P' ] await execTar(args) } export async function extractTar( archivePath: string, compressionMethod: CompressionMethod ): Promise { // Create directory to extract tar into const workingDirectory = getWorkingDirectory() await io.mkdirP(workingDirectory) const args = [ ...(await getCompressionProgram(compressionMethod)), '-xf', archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/') ] await execTar(args) } export async function createTar( archiveFolder: string, sourceDirectories: string[], compressionMethod: CompressionMethod ): Promise { // Write source directories to manifest.txt to avoid command length limits const manifestFilename = 'manifest.txt' const cacheFileName = utils.getCacheFileName(compressionMethod) writeFileSync( path.join(archiveFolder, manifestFilename), sourceDirectories.join('\n') ) const workingDirectory = getWorkingDirectory() // -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' // --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. // Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd. async function getCompressionProgram(): Promise { const tarPath = await getTarPath([]) const BSD_TAR_ZSTD = IS_WINDOWS && tarPath === SystemTarPathOnWindows switch (compressionMethod) { case CompressionMethod.Zstd: if (BSD_TAR_ZSTD) { return ['-O', '|', 'zstd -T0 --long=30 -o'] } return [ '--use-compress-program', IS_WINDOWS ? 'zstd -T0 --long=30' : 'zstdmt --long=30', '-cf' ] case CompressionMethod.ZstdWithoutLong: if (BSD_TAR_ZSTD) { return ['-O', '|', 'zstd -T0 -o'] } return [ '--use-compress-program', IS_WINDOWS ? 'zstd -T0' : 'zstdmt', '-cf' ] default: return ['-z', '-cf'] } } const args = [ '--posix', '--exclude', cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '--files-from', manifestFilename, ...(await getCompressionProgram()), cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/') ] await execTar(args, archiveFolder) }