From f36c8371b98a48dfb7d9d25b5080236e2d1e806b Mon Sep 17 00:00:00 2001 From: Aparna Jyothi Date: Mon, 9 Jun 2025 13:39:53 +0530 Subject: [PATCH] ehnace cache dependency path handling --- __tests__/setup-python.test.ts | 104 +++++++++++++++++++++++++++++++++ dist/setup/index.js | 30 ++++++++++ docs/advanced-usage.md | 2 +- src/setup-python.ts | 38 +++++++++++- 4 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 __tests__/setup-python.test.ts diff --git a/__tests__/setup-python.test.ts b/__tests__/setup-python.test.ts new file mode 100644 index 00000000..5d68482b --- /dev/null +++ b/__tests__/setup-python.test.ts @@ -0,0 +1,104 @@ +import * as core from '@actions/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import {cacheDependencies} from '../src/setup-python'; +import {getCacheDistributor} from '../src/cache-distributions/cache-factory'; + +jest.mock('fs', () => { + const actualFs = jest.requireActual('fs'); + return { + ...actualFs, + copyFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn(), + promises: { + access: jest.fn(), + writeFile: jest.fn(), + appendFile: jest.fn() + } + }; +}); +jest.mock('@actions/core'); +jest.mock('../src/cache-distributions/cache-factory'); + +const mockedFs = fs as jest.Mocked; +const mockedCore = core as jest.Mocked; +const mockedGetCacheDistributor = getCacheDistributor as jest.Mock; + +describe('cacheDependencies', () => { + const mockRestoreCache = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + process.env.GITHUB_ACTION_PATH = '/github/action'; + process.env.GITHUB_WORKSPACE = '/github/workspace'; + + mockedCore.getInput.mockReturnValue('nested/deps.lock'); + + mockedFs.existsSync.mockImplementation((p: any) => { + const pathStr = typeof p === 'string' ? p : p.toString(); + if (pathStr === '/github/action/nested/deps.lock') return true; + if (pathStr === '/github/workspace/nested') return false; // Simulate missing dir + return true; + }); + + mockedFs.copyFileSync.mockImplementation(() => undefined); + mockedFs.mkdirSync.mockImplementation(() => undefined); + mockedGetCacheDistributor.mockReturnValue({restoreCache: mockRestoreCache}); + }); + + it('copies the dependency file and resolves the path with directory structure', async () => { + await cacheDependencies('pip', '3.12'); + + const sourcePath = path.resolve('/github/action', 'nested/deps.lock'); + const targetPath = path.resolve('/github/workspace', 'nested/deps.lock'); + + expect(mockedFs.existsSync).toHaveBeenCalledWith(sourcePath); + expect(mockedFs.mkdirSync).toHaveBeenCalledWith(path.dirname(targetPath), { + recursive: true + }); + expect(mockedFs.copyFileSync).toHaveBeenCalledWith(sourcePath, targetPath); + expect(mockedCore.info).toHaveBeenCalledWith( + `Copied ${sourcePath} to ${targetPath}` + ); + expect(mockedCore.info).toHaveBeenCalledWith( + `Resolved cache-dependency-path: nested/deps.lock` + ); + expect(mockRestoreCache).toHaveBeenCalled(); + }); + + it('warns if the dependency file does not exist', async () => { + mockedFs.existsSync.mockReturnValue(false); + + await cacheDependencies('pip', '3.12'); + + expect(mockedCore.warning).toHaveBeenCalledWith( + expect.stringContaining('does not exist') + ); + expect(mockedFs.copyFileSync).not.toHaveBeenCalled(); + expect(mockRestoreCache).toHaveBeenCalled(); + }); + + it('warns if file copy fails', async () => { + mockedFs.copyFileSync.mockImplementation(() => { + throw new Error('copy failed'); + }); + + await cacheDependencies('pip', '3.12'); + + expect(mockedCore.warning).toHaveBeenCalledWith( + expect.stringContaining('Failed to copy file') + ); + expect(mockRestoreCache).toHaveBeenCalled(); + }); + + it('skips path logic if no input is provided', async () => { + mockedCore.getInput.mockReturnValue(''); + + await cacheDependencies('pip', '3.12'); + + expect(mockedFs.copyFileSync).not.toHaveBeenCalled(); + expect(mockedCore.warning).not.toHaveBeenCalled(); + expect(mockRestoreCache).toHaveBeenCalled(); + }); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index 4e3f2673..854f9490 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -96905,6 +96905,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.cacheDependencies = void 0; const core = __importStar(__nccwpck_require__(7484)); const finder = __importStar(__nccwpck_require__(6843)); const finderPyPy = __importStar(__nccwpck_require__(2625)); @@ -96923,10 +96924,39 @@ function isGraalPyVersion(versionSpec) { function cacheDependencies(cache, pythonVersion) { return __awaiter(this, void 0, void 0, function* () { const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; + let resolvedDependencyPath = undefined; + if (cacheDependencyPath) { + const actionPath = process.env.GITHUB_ACTION_PATH || ''; + const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); + const sourcePath = path.resolve(actionPath, cacheDependencyPath); + const relativePath = path.relative(actionPath, sourcePath); + const targetPath = path.resolve(workspace, relativePath); + if (!fs_1.default.existsSync(sourcePath)) { + core.warning(`The resolved cache-dependency-path does not exist: ${sourcePath}`); + } + else { + if (sourcePath !== targetPath) { + try { + const targetDir = path.dirname(targetPath); + if (!fs_1.default.existsSync(targetDir)) { + fs_1.default.mkdirSync(targetDir, { recursive: true }); + } + fs_1.default.copyFileSync(sourcePath, targetPath); + core.info(`Copied ${sourcePath} to ${targetPath}`); + } + catch (error) { + core.warning(`Failed to copy file from ${sourcePath} to ${targetPath}: ${error}`); + } + } + } + resolvedDependencyPath = path.relative(workspace, targetPath); + core.info(`Resolved cache-dependency-path: ${resolvedDependencyPath}`); + } const cacheDistributor = (0, cache_factory_1.getCacheDistributor)(cache, pythonVersion, cacheDependencyPath); yield cacheDistributor.restoreCache(); }); } +exports.cacheDependencies = cacheDependencies; function resolveVersionInputFromDefaultFile() { const couples = [ ['.python-version', utils_1.getVersionsInputFromPlainFile] diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 72b35016..4353b341 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -411,7 +411,7 @@ steps: - run: pip install -e . # Or pip install -e '.[test]' to install test dependencies ``` - +Note: cache-dependency-path supports files located outside the workspace root by copying them into the workspace to enable proper caching. # Outputs and environment variables ## Outputs diff --git a/src/setup-python.ts b/src/setup-python.ts index 5d585d73..ed76469c 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -22,9 +22,45 @@ function isGraalPyVersion(versionSpec: string) { return versionSpec.startsWith('graalpy'); } -async function cacheDependencies(cache: string, pythonVersion: string) { +export async function cacheDependencies(cache: string, pythonVersion: string) { const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; + let resolvedDependencyPath: string | undefined = undefined; + + if (cacheDependencyPath) { + const actionPath = process.env.GITHUB_ACTION_PATH || ''; + const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); + + const sourcePath = path.resolve(actionPath, cacheDependencyPath); + const relativePath = path.relative(actionPath, sourcePath); + const targetPath = path.resolve(workspace, relativePath); + + if (!fs.existsSync(sourcePath)) { + core.warning( + `The resolved cache-dependency-path does not exist: ${sourcePath}` + ); + } else { + if (sourcePath !== targetPath) { + try { + const targetDir = path.dirname(targetPath); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, {recursive: true}); + } + + fs.copyFileSync(sourcePath, targetPath); + core.info(`Copied ${sourcePath} to ${targetPath}`); + } catch (error) { + core.warning( + `Failed to copy file from ${sourcePath} to ${targetPath}: ${error}` + ); + } + } + } + + resolvedDependencyPath = path.relative(workspace, targetPath); + core.info(`Resolved cache-dependency-path: ${resolvedDependencyPath}`); + } + const cacheDistributor = getCacheDistributor( cache, pythonVersion,