From 4fa017f2b759b4d3277fae3500272b6d5a5f4496 Mon Sep 17 00:00:00 2001 From: Aiqiao Yan Date: Thu, 9 Apr 2020 10:29:33 -0400 Subject: [PATCH] Fallback to GNU tar if BSD tar is unavailable --- __tests__/tar.test.ts | 63 ++++++++++++++++++++++++++++++++++++------- dist/restore/index.js | 50 +++++++++++++++++++++++++++------- dist/save/index.js | 50 +++++++++++++++++++++++++++------- src/tar.ts | 43 +++++++++++++++++++++-------- 4 files changed, 166 insertions(+), 40 deletions(-) diff --git a/__tests__/tar.test.ts b/__tests__/tar.test.ts index ca10f34..2690fa9 100644 --- a/__tests__/tar.test.ts +++ b/__tests__/tar.test.ts @@ -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" ], diff --git a/dist/restore/index.js b/dist/restore/index.js index 2e71724..4015484 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -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 ]; diff --git a/dist/save/index.js b/dist/save/index.js index f807389..0762bfd 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -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 ]; diff --git a/src/tar.ts b/src/tar.ts index 9a1f446..4682854 100644 --- a/src/tar.ts +++ b/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 { +export async function isGnuTar(): Promise { + 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 { // 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 { async function execTar(args: string[], cwd?: string): Promise { 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 { // 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 ];