mirror of
				https://github.com/actions/cache.git
				synced 2025-10-31 10:24:19 +08:00 
			
		
		
		
	Fallback to GNU tar if BSD tar is unavailable
This commit is contained in:
		
							parent
							
								
									78809b91d7
								
							
						
					
					
						commit
						4fa017f2b7
					
				| @ -1,6 +1,6 @@ | ||||
| import * as exec from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import { promises as fs } from "fs"; | ||||
| import * as fs from "fs"; | ||||
| import * as path from "path"; | ||||
| 
 | ||||
| import { CacheFilename } from "../src/constants"; | ||||
| @ -27,37 +27,82 @@ afterAll(async () => { | ||||
|     await jest.requireActual("@actions/io").rmRF(getTempDir()); | ||||
| }); | ||||
| 
 | ||||
| test("extract tar", async () => { | ||||
| test("extract BSD tar", async () => { | ||||
|     const mkdirMock = jest.spyOn(io, "mkdirP"); | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
| 
 | ||||
|     const archivePath = "cache.tar"; | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     const archivePath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\fakepath\\cache.tar` | ||||
|         : "cache.tar"; | ||||
|     const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
| 
 | ||||
|     await tar.extractTar(archivePath); | ||||
| 
 | ||||
|     expect(mkdirMock).toHaveBeenCalledWith(workspace); | ||||
| 
 | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     const tarPath = IS_WINDOWS | ||||
|         ? `${process.env["windir"]}\\System32\\tar.exe` | ||||
|         : "tar"; | ||||
|     expect(execMock).toHaveBeenCalledTimes(1); | ||||
|     expect(execMock).toHaveBeenCalledWith( | ||||
|         `"${tarPath}"`, | ||||
|         ["-xz", "-f", archivePath, "-P", "-C", workspace], | ||||
|         [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             archivePath?.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             workspace?.replace(/\\/g, "/") | ||||
|         ], | ||||
|         { cwd: undefined } | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| test("create tar", async () => { | ||||
| test("extract GNU tar", async () => { | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     if (IS_WINDOWS) { | ||||
|         jest.mock("fs"); | ||||
| 
 | ||||
|         const execMock = jest.spyOn(exec, "exec"); | ||||
|         const existsSyncMock = jest | ||||
|             .spyOn(fs, "existsSync") | ||||
|             .mockReturnValue(false); | ||||
|         const isGnuTarMock = jest | ||||
|             .spyOn(tar, "isGnuTar") | ||||
|             .mockReturnValue(Promise.resolve(true)); | ||||
|         const archivePath = `${process.env["windir"]}\\fakepath\\cache.tar`; | ||||
|         const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
| 
 | ||||
|         await tar.extractTar(archivePath); | ||||
| 
 | ||||
|         expect(existsSyncMock).toHaveBeenCalledTimes(1); | ||||
|         expect(isGnuTarMock).toHaveBeenCalledTimes(1); | ||||
|         expect(execMock).toHaveBeenCalledTimes(2); | ||||
|         expect(execMock).toHaveBeenLastCalledWith( | ||||
|             "tar", | ||||
|             [ | ||||
|                 "-xz", | ||||
|                 "-f", | ||||
|                 archivePath?.replace(/\\/g, "/"), | ||||
|                 "-P", | ||||
|                 "-C", | ||||
|                 workspace?.replace(/\\/g, "/"), | ||||
|                 "--force-local" | ||||
|             ], | ||||
|             { cwd: undefined } | ||||
|         ); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| test("create BSD tar", async () => { | ||||
|     const execMock = jest.spyOn(exec, "exec"); | ||||
| 
 | ||||
|     const archiveFolder = getTempDir(); | ||||
|     const workspace = process.env["GITHUB_WORKSPACE"]; | ||||
|     const sourceDirectories = ["~/.npm/cache", `${workspace}/dist`]; | ||||
| 
 | ||||
|     await fs.mkdir(archiveFolder, { recursive: true }); | ||||
|     await fs.mkdir(archiveFolder, () => void { recursive: true }); | ||||
| 
 | ||||
|     await tar.createTar(archiveFolder, sourceDirectories); | ||||
| 
 | ||||
| @ -72,10 +117,10 @@ test("create tar", async () => { | ||||
|         [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
|             CacheFilename, | ||||
|             CacheFilename?.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             workspace, | ||||
|             workspace?.replace(/\\/g, "/"), | ||||
|             "--files-from", | ||||
|             "manifest.txt" | ||||
|         ], | ||||
|  | ||||
							
								
								
									
										50
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								dist/restore/index.js
									
									
									
									
										vendored
									
									
								
							| @ -4959,12 +4959,30 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
|     return result; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec_1 = __webpack_require__(986); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs_1 = __webpack_require__(747); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const constants_1 = __webpack_require__(694); | ||||
| function getTarPath() { | ||||
| function isGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug("Checking tar --version"); | ||||
|         let versionOutput = ""; | ||||
|         yield exec_1.exec("tar --version", [], { | ||||
|             ignoreReturnCode: true, | ||||
|             silent: true, | ||||
|             listeners: { | ||||
|                 stdout: (data) => (versionOutput += data.toString()), | ||||
|                 stderr: (data) => (versionOutput += data.toString()) | ||||
|             } | ||||
|         }); | ||||
|         core.debug(versionOutput.trim()); | ||||
|         return versionOutput.toUpperCase().includes("GNU TAR"); | ||||
|     }); | ||||
| } | ||||
| exports.isGnuTar = isGnuTar; | ||||
| function getTarPath(args) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows
 | ||||
|         const IS_WINDOWS = process.platform === "win32"; | ||||
| @ -4973,22 +4991,21 @@ function getTarPath() { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
|         return yield io.which("tar", true); | ||||
|     }); | ||||
| } | ||||
| function execTar(args, cwd) { | ||||
|     var _a, _b; | ||||
|     var _a; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath()}"`, args, { cwd: cwd }); | ||||
|             yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|         } | ||||
|         catch (error) { | ||||
|             const IS_WINDOWS = process.platform === "win32"; | ||||
|             if (IS_WINDOWS) { | ||||
|                 throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}. Ensure BSD tar is installed and on the PATH.`); | ||||
|             } | ||||
|             throw new Error(`Tar failed with error: ${(_b = error) === null || _b === void 0 ? void 0 : _b.message}`); | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @ -4997,16 +5014,25 @@ function getWorkingDirectory() { | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         yield io.mkdirP(workingDirectory); | ||||
|         const args = ["-xz", "-f", archivePath, "-P", "-C", workingDirectory]; | ||||
|         const args = [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") | ||||
|         ]; | ||||
|         yield execTar(args); | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits
 | ||||
|         const manifestFilename = "manifest.txt"; | ||||
| @ -5015,10 +5041,14 @@ function createTar(archiveFolder, sourceDirectories) { | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
| <<<<<<< HEAD | ||||
|             constants_1.CacheFilename, | ||||
|             "-P", | ||||
| ======= | ||||
|             (_a = constants_1.CacheFilename) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
| >>>>>>> Fallback to GNU tar if BSD tar is unavailable | ||||
|             "-C", | ||||
|             workingDirectory, | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), | ||||
|             "--files-from", | ||||
|             manifestFilename | ||||
|         ]; | ||||
|  | ||||
							
								
								
									
										50
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								dist/save/index.js
									
									
									
									
										vendored
									
									
								
							| @ -4936,12 +4936,30 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
|     return result; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const exec_1 = __webpack_require__(986); | ||||
| const io = __importStar(__webpack_require__(1)); | ||||
| const fs_1 = __webpack_require__(747); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const constants_1 = __webpack_require__(694); | ||||
| function getTarPath() { | ||||
| function isGnuTar() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         core.debug("Checking tar --version"); | ||||
|         let versionOutput = ""; | ||||
|         yield exec_1.exec("tar --version", [], { | ||||
|             ignoreReturnCode: true, | ||||
|             silent: true, | ||||
|             listeners: { | ||||
|                 stdout: (data) => (versionOutput += data.toString()), | ||||
|                 stderr: (data) => (versionOutput += data.toString()) | ||||
|             } | ||||
|         }); | ||||
|         core.debug(versionOutput.trim()); | ||||
|         return versionOutput.toUpperCase().includes("GNU TAR"); | ||||
|     }); | ||||
| } | ||||
| exports.isGnuTar = isGnuTar; | ||||
| function getTarPath(args) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Explicitly use BSD Tar on Windows
 | ||||
|         const IS_WINDOWS = process.platform === "win32"; | ||||
| @ -4950,22 +4968,21 @@ function getTarPath() { | ||||
|             if (fs_1.existsSync(systemTar)) { | ||||
|                 return systemTar; | ||||
|             } | ||||
|             else if (isGnuTar()) { | ||||
|                 args.push("--force-local"); | ||||
|             } | ||||
|         } | ||||
|         return yield io.which("tar", true); | ||||
|     }); | ||||
| } | ||||
| function execTar(args, cwd) { | ||||
|     var _a, _b; | ||||
|     var _a; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             yield exec_1.exec(`"${yield getTarPath()}"`, args, { cwd: cwd }); | ||||
|             yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|         } | ||||
|         catch (error) { | ||||
|             const IS_WINDOWS = process.platform === "win32"; | ||||
|             if (IS_WINDOWS) { | ||||
|                 throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}. Ensure BSD tar is installed and on the PATH.`); | ||||
|             } | ||||
|             throw new Error(`Tar failed with error: ${(_b = error) === null || _b === void 0 ? void 0 : _b.message}`); | ||||
|             throw new Error(`Tar failed with error: ${(_a = error) === null || _a === void 0 ? void 0 : _a.message}`); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| @ -4974,16 +4991,25 @@ function getWorkingDirectory() { | ||||
|     return _a = process.env["GITHUB_WORKSPACE"], (_a !== null && _a !== void 0 ? _a : process.cwd()); | ||||
| } | ||||
| function extractTar(archivePath) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Create directory to extract tar into
 | ||||
|         const workingDirectory = getWorkingDirectory(); | ||||
|         yield io.mkdirP(workingDirectory); | ||||
|         const args = ["-xz", "-f", archivePath, "-P", "-C", workingDirectory]; | ||||
|         const args = [ | ||||
|             "-xz", | ||||
|             "-f", | ||||
|             (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
|             "-P", | ||||
|             "-C", | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") | ||||
|         ]; | ||||
|         yield execTar(args); | ||||
|     }); | ||||
| } | ||||
| exports.extractTar = extractTar; | ||||
| function createTar(archiveFolder, sourceDirectories) { | ||||
|     var _a, _b; | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         // Write source directories to manifest.txt to avoid command length limits
 | ||||
|         const manifestFilename = "manifest.txt"; | ||||
| @ -4992,10 +5018,14 @@ function createTar(archiveFolder, sourceDirectories) { | ||||
|         const args = [ | ||||
|             "-cz", | ||||
|             "-f", | ||||
| <<<<<<< HEAD | ||||
|             constants_1.CacheFilename, | ||||
|             "-P", | ||||
| ======= | ||||
|             (_a = constants_1.CacheFilename) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), | ||||
| >>>>>>> Fallback to GNU tar if BSD tar is unavailable | ||||
|             "-C", | ||||
|             workingDirectory, | ||||
|             (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), | ||||
|             "--files-from", | ||||
|             manifestFilename | ||||
|         ]; | ||||
|  | ||||
							
								
								
									
										43
									
								
								src/tar.ts
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/tar.ts
									
									
									
									
									
								
							| @ -1,3 +1,4 @@ | ||||
| import * as core from "@actions/core"; | ||||
| import { exec } from "@actions/exec"; | ||||
| import * as io from "@actions/io"; | ||||
| import { existsSync, writeFileSync } from "fs"; | ||||
| @ -5,13 +6,32 @@ import * as path from "path"; | ||||
| 
 | ||||
| import { CacheFilename } from "./constants"; | ||||
| 
 | ||||
| async function getTarPath(): Promise<string> { | ||||
| export async function isGnuTar(): Promise<boolean> { | ||||
|     core.debug("Checking tar --version"); | ||||
|     let versionOutput = ""; | ||||
|     await exec("tar --version", [], { | ||||
|         ignoreReturnCode: true, | ||||
|         silent: true, | ||||
|         listeners: { | ||||
|             stdout: (data: Buffer): string => | ||||
|                 (versionOutput += data.toString()), | ||||
|             stderr: (data: Buffer): string => (versionOutput += data.toString()) | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     core.debug(versionOutput.trim()); | ||||
|     return versionOutput.toUpperCase().includes("GNU TAR"); | ||||
| } | ||||
| 
 | ||||
| async function getTarPath(args: string[]): Promise<string> { | ||||
|     // Explicitly use BSD Tar on Windows
 | ||||
|     const IS_WINDOWS = process.platform === "win32"; | ||||
|     if (IS_WINDOWS) { | ||||
|         const systemTar = `${process.env["windir"]}\\System32\\tar.exe`; | ||||
|         if (existsSync(systemTar)) { | ||||
|             return systemTar; | ||||
|         } else if (isGnuTar()) { | ||||
|             args.push("--force-local"); | ||||
|         } | ||||
|     } | ||||
|     return await io.which("tar", true); | ||||
| @ -19,14 +39,8 @@ async function getTarPath(): Promise<string> { | ||||
| 
 | ||||
| async function execTar(args: string[], cwd?: string): Promise<void> { | ||||
|     try { | ||||
|         await exec(`"${await getTarPath()}"`, args, { cwd: cwd }); | ||||
|         await exec(`"${await getTarPath(args)}"`, args, { cwd: cwd }); | ||||
|     } catch (error) { | ||||
|         const IS_WINDOWS = process.platform === "win32"; | ||||
|         if (IS_WINDOWS) { | ||||
|             throw new Error( | ||||
|                 `Tar failed with error: ${error?.message}. Ensure BSD tar is installed and on the PATH.` | ||||
|             ); | ||||
|         } | ||||
|         throw new Error(`Tar failed with error: ${error?.message}`); | ||||
|     } | ||||
| } | ||||
| @ -39,7 +53,14 @@ export async function extractTar(archivePath: string): Promise<void> { | ||||
|     // Create directory to extract tar into
 | ||||
|     const workingDirectory = getWorkingDirectory(); | ||||
|     await io.mkdirP(workingDirectory); | ||||
|     const args = ["-xz", "-f", archivePath, "-P", "-C", workingDirectory]; | ||||
|     const args = [ | ||||
|         "-xz", | ||||
|         "-f", | ||||
|         archivePath?.replace(/\\/g, "/"), | ||||
|         "-P", | ||||
|         "-C", | ||||
|         workingDirectory?.replace(/\\/g, "/") | ||||
|     ]; | ||||
|     await execTar(args); | ||||
| } | ||||
| 
 | ||||
| @ -58,10 +79,10 @@ export async function createTar( | ||||
|     const args = [ | ||||
|         "-cz", | ||||
|         "-f", | ||||
|         CacheFilename, | ||||
|         CacheFilename?.replace(/\\/g, "/"), | ||||
|         "-P", | ||||
|         "-C", | ||||
|         workingDirectory, | ||||
|         workingDirectory?.replace(/\\/g, "/"), | ||||
|         "--files-from", | ||||
|         manifestFilename | ||||
|     ]; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aiqiao Yan
						Aiqiao Yan