From 84e606dfac347c57a599fe0acefc86daab454178 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 | 55 +++++++++++++++++++++++---- dist/restore/index.js | 87 ++++++++++++++++++++++++++++++++++++++++--- dist/save/index.js | 87 ++++++++++++++++++++++++++++++++++++++++--- src/tar.ts | 48 +++++++++++++++++++----- 4 files changed, 249 insertions(+), 28 deletions(-) diff --git a/__tests__/tar.test.ts b/__tests__/tar.test.ts index 55ff4c7..cc94a4d 100644 --- a/__tests__/tar.test.ts +++ b/__tests__/tar.test.ts @@ -1,5 +1,7 @@ import * as exec from "@actions/exec"; import * as io from "@actions/io"; +import * as fs from "fs"; +import * as path from "path"; import * as tar from "../src/tar"; jest.mock("@actions/exec"); @@ -11,17 +13,19 @@ beforeAll(() => { }); }); -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 targetDirectory = "~/.npm/cache"; await tar.extractTar(archivePath, targetDirectory); expect(mkdirMock).toHaveBeenCalledWith(targetDirectory); - const IS_WINDOWS = process.platform === "win32"; const tarPath = IS_WINDOWS ? `${process.env["windir"]}\\System32\\tar.exe` : "tar"; @@ -29,13 +33,48 @@ test("extract tar", async () => { expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [ "-xz", "-f", - archivePath, + archivePath?.replace(/\\/g, "/"), "-C", - targetDirectory + targetDirectory?.replace(/\\/g, "/"), ]); }); -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 targetDirectory = "~/.npm/cache"; + + await tar.extractTar(archivePath, targetDirectory); + + expect(existsSyncMock).toHaveBeenCalledTimes(1); + expect(isGnuTarMock).toHaveBeenCalledTimes(1); + expect(execMock).toHaveBeenCalledTimes(2); + expect(execMock).toHaveBeenLastCalledWith( + "tar", + [ + "-xz", + "-f", + archivePath?.replace(/\\/g, "/"), + "-C", + targetDirectory?.replace(/\\/g, "/"), + "--force-local" + ], + { cwd: undefined } + ); + } +}); + +test("create BSD tar", async () => { const execMock = jest.spyOn(exec, "exec"); const archivePath = "cache.tar"; @@ -50,9 +89,9 @@ test("create tar", async () => { expect(execMock).toHaveBeenCalledWith(`"${tarPath}"`, [ "-cz", "-f", - archivePath, + archivePath?.replace(/\\/g, "/"), "-C", - sourceDirectory, + sourceDirectory?.replace(/\\/g, "/"), "." ]); }); diff --git a/dist/restore/index.js b/dist/restore/index.js index a3ea855..8ccadfb 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -2928,10 +2928,34 @@ 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); +<<<<<<< HEAD function getTarPath() { +======= +const path = __importStar(__webpack_require__(622)); +const constants_1 = __webpack_require__(694); +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) { +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable return __awaiter(this, void 0, void 0, function* () { // Explicitly use BSD Tar on Windows const IS_WINDOWS = process.platform === "win32"; @@ -2940,38 +2964,91 @@ function getTarPath() { if (fs_1.existsSync(systemTar)) { return systemTar; } + else if (isGnuTar()) { + args.push("--force-local"); + } } return yield io.which("tar", true); }); } +<<<<<<< HEAD function execTar(args) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { yield exec_1.exec(`"${yield getTarPath()}"`, args); +======= +function execTar(args, cwd) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + try { + yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable } 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}`); } }); } +<<<<<<< HEAD function extractTar(archivePath, targetDirectory) { return __awaiter(this, void 0, void 0, function* () { // Create directory to extract tar into yield io.mkdirP(targetDirectory); const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; +======= +function getWorkingDirectory() { + var _a; + 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", + (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), + "-P", + "-C", + (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") + ]; +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable yield execTar(args); }); } exports.extractTar = extractTar; +<<<<<<< HEAD function createTar(archivePath, sourceDirectory) { return __awaiter(this, void 0, void 0, function* () { const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; yield execTar(args); +======= +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"; + fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); + const workingDirectory = getWorkingDirectory(); + 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", + (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), + "--files-from", + manifestFilename + ]; + yield execTar(args, archiveFolder); +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable }); } exports.createTar = createTar; diff --git a/dist/save/index.js b/dist/save/index.js index e7e0eae..33d75c6 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -2909,10 +2909,34 @@ 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); +<<<<<<< HEAD function getTarPath() { +======= +const path = __importStar(__webpack_require__(622)); +const constants_1 = __webpack_require__(694); +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) { +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable return __awaiter(this, void 0, void 0, function* () { // Explicitly use BSD Tar on Windows const IS_WINDOWS = process.platform === "win32"; @@ -2921,38 +2945,91 @@ function getTarPath() { if (fs_1.existsSync(systemTar)) { return systemTar; } + else if (isGnuTar()) { + args.push("--force-local"); + } } return yield io.which("tar", true); }); } +<<<<<<< HEAD function execTar(args) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { yield exec_1.exec(`"${yield getTarPath()}"`, args); +======= +function execTar(args, cwd) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + try { + yield exec_1.exec(`"${yield getTarPath(args)}"`, args, { cwd: cwd }); +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable } 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}`); } }); } +<<<<<<< HEAD function extractTar(archivePath, targetDirectory) { return __awaiter(this, void 0, void 0, function* () { // Create directory to extract tar into yield io.mkdirP(targetDirectory); const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; +======= +function getWorkingDirectory() { + var _a; + 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", + (_a = archivePath) === null || _a === void 0 ? void 0 : _a.replace(/\\/g, "/"), + "-P", + "-C", + (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/") + ]; +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable yield execTar(args); }); } exports.extractTar = extractTar; +<<<<<<< HEAD function createTar(archivePath, sourceDirectory) { return __awaiter(this, void 0, void 0, function* () { const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; yield execTar(args); +======= +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"; + fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join("\n")); + const workingDirectory = getWorkingDirectory(); + 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", + (_b = workingDirectory) === null || _b === void 0 ? void 0 : _b.replace(/\\/g, "/"), + "--files-from", + manifestFilename + ]; + yield execTar(args, archiveFolder); +>>>>>>> 4fa017f... Fallback to GNU tar if BSD tar is unavailable }); } exports.createTar = createTar; diff --git a/src/tar.ts b/src/tar.ts index 1f572d1..c20c15d 100644 --- a/src/tar.ts +++ b/src/tar.ts @@ -1,14 +1,35 @@ +import * as core from "@actions/core"; import { exec } from "@actions/exec"; import * as io from "@actions/io"; import { existsSync } from "fs"; +import * as path from "path"; -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); @@ -16,14 +37,8 @@ async function getTarPath(): Promise { async function execTar(args: string[]): Promise { try { - await exec(`"${await getTarPath()}"`, args); + await exec(`"${await getTarPath(args)}"`, args); } 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}`); } } @@ -34,7 +49,13 @@ export async function extractTar( ): Promise { // Create directory to extract tar into await io.mkdirP(targetDirectory); - const args = ["-xz", "-f", archivePath, "-C", targetDirectory]; + const args = [ + "-xz", + "-f", + archivePath?.replace(/\\/g, "/"), + "-C", + targetDirectory?.replace(/\\/g, "/") + ]; await execTar(args); } @@ -42,6 +63,13 @@ export async function createTar( archivePath: string, sourceDirectory: string ): Promise { - const args = ["-cz", "-f", archivePath, "-C", sourceDirectory, "."]; + const args = [ + "-cz", + "-f", + archivePath?.replace(/\\/g, "/"), + "-C", + sourceDirectory?.replace(/\\/g, "/"), + "." + ]; await execTar(args); }