diff --git a/__tests__/setup-python.test.ts b/__tests__/setup-python.test.ts index bb27289d..318d9a97 100644 --- a/__tests__/setup-python.test.ts +++ b/__tests__/setup-python.test.ts @@ -33,45 +33,26 @@ describe('cacheDependencies', () => { process.env.GITHUB_WORKSPACE = '/github/workspace'; mockedCore.getInput.mockReturnValue('nested/deps.lock'); - - // Simulate file exists by resolving access without error - mockedFsPromises.access.mockImplementation(async p => { - const pathStr = typeof p === 'string' ? p : p.toString(); - if (pathStr === '/github/action/nested/deps.lock') { - return Promise.resolve(); - } - // Simulate directory doesn't exist to test mkdir - if (pathStr === path.dirname('/github/workspace/nested/deps.lock')) { - return Promise.reject(new Error('no dir')); - } - return Promise.resolve(); - }); - - // Simulate mkdir success - mockedFsPromises.mkdir.mockResolvedValue(undefined); - - // Simulate copyFile success - mockedFsPromises.copyFile.mockResolvedValue(undefined); + mockedCore.getBooleanInput.mockReturnValue(false); mockedGetCacheDistributor.mockReturnValue({restoreCache: mockRestoreCache}); + + mockedFsPromises.mkdir.mockResolvedValue(undefined); + mockedFsPromises.copyFile.mockResolvedValue(undefined); }); - it('copies the dependency file and resolves the path with directory structure', async () => { + it('copies the file if source exists and target does not', async () => { + mockedFsPromises.access.mockImplementation(async filePath => { + if (filePath === '/github/action/nested/deps.lock') + return Promise.resolve(); // source + throw new Error('target does not exist'); // target + }); + await cacheDependencies('pip', '3.12'); - const sourcePath = path.resolve('/github/action', 'nested/deps.lock'); - const targetPath = path.resolve('/github/workspace', 'nested/deps.lock'); + const sourcePath = '/github/action/nested/deps.lock'; + const targetPath = '/github/workspace/nested/deps.lock'; - expect(mockedFsPromises.access).toHaveBeenCalledWith( - sourcePath, - fs.constants.F_OK - ); - expect(mockedFsPromises.mkdir).toHaveBeenCalledWith( - path.dirname(targetPath), - { - recursive: true - } - ); expect(mockedFsPromises.copyFile).toHaveBeenCalledWith( sourcePath, targetPath @@ -79,15 +60,45 @@ describe('cacheDependencies', () => { 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 () => { - // Simulate file does not exist by rejecting access - mockedFsPromises.access.mockRejectedValue(new Error('file not found')); + it('overwrites file if target exists and overwrite is true', async () => { + mockedCore.getBooleanInput.mockReturnValue(true); + mockedFsPromises.access.mockResolvedValue(); // both source and target exist + + await cacheDependencies('pip', '3.12'); + + const sourcePath = '/github/action/nested/deps.lock'; + const targetPath = '/github/workspace/nested/deps.lock'; + + expect(mockedFsPromises.copyFile).toHaveBeenCalledWith( + sourcePath, + targetPath + ); + expect(mockedCore.info).toHaveBeenCalledWith( + `Overwrote ${sourcePath} to ${targetPath}` + ); + }); + + it('skips copy if file exists and overwrite is false', async () => { + mockedCore.getBooleanInput.mockReturnValue(false); + mockedFsPromises.access.mockResolvedValue(); // both source and target exist + + await cacheDependencies('pip', '3.12'); + + expect(mockedFsPromises.copyFile).not.toHaveBeenCalled(); + expect(mockedCore.info).toHaveBeenCalledWith( + expect.stringContaining('Skipped copying') + ); + }); + + it('logs warning if source file does not exist', async () => { + mockedFsPromises.access.mockImplementation(async filePath => { + if (filePath === '/github/action/nested/deps.lock') { + throw new Error('source not found'); + } + return Promise.resolve(); // fallback for others + }); await cacheDependencies('pip', '3.12'); @@ -95,11 +106,15 @@ describe('cacheDependencies', () => { expect.stringContaining('does not exist') ); expect(mockedFsPromises.copyFile).not.toHaveBeenCalled(); - expect(mockRestoreCache).toHaveBeenCalled(); }); - it('warns if file copy fails', async () => { - // Simulate copyFile failure + it('logs warning if copyFile fails', async () => { + mockedFsPromises.access.mockImplementation(async filePath => { + if (filePath === '/github/action/nested/deps.lock') + return Promise.resolve(); + throw new Error('target does not exist'); + }); + mockedFsPromises.copyFile.mockRejectedValue(new Error('copy failed')); await cacheDependencies('pip', '3.12'); @@ -107,43 +122,30 @@ describe('cacheDependencies', () => { expect(mockedCore.warning).toHaveBeenCalledWith( expect.stringContaining('Failed to copy file') ); - expect(mockRestoreCache).toHaveBeenCalled(); }); - it('skips path logic if no input is provided', async () => { + it('skips everything if cache-dependency-path is not provided', async () => { mockedCore.getInput.mockReturnValue(''); await cacheDependencies('pip', '3.12'); expect(mockedFsPromises.copyFile).not.toHaveBeenCalled(); expect(mockedCore.warning).not.toHaveBeenCalled(); - expect(mockRestoreCache).toHaveBeenCalled(); }); - it('does not copy if dependency file is already inside the workspace but still sets resolved path', async () => { - // Simulate cacheDependencyPath inside workspace + it('does not copy if source and target are the same path', async () => { mockedCore.getInput.mockReturnValue('deps.lock'); + process.env.GITHUB_ACTION_PATH = '/github/workspace'; + process.env.GITHUB_WORKSPACE = '/github/workspace'; - // Override sourcePath and targetPath to be equal - const actionPath = '/github/workspace'; // same path for action and workspace - process.env.GITHUB_ACTION_PATH = actionPath; - process.env.GITHUB_WORKSPACE = actionPath; - - // access resolves to simulate file exists mockedFsPromises.access.mockResolvedValue(); await cacheDependencies('pip', '3.12'); - const sourcePath = path.resolve(actionPath, 'deps.lock'); - const targetPath = sourcePath; // same path - + const sourcePath = '/github/workspace/deps.lock'; expect(mockedFsPromises.copyFile).not.toHaveBeenCalled(); expect(mockedCore.info).toHaveBeenCalledWith( `Dependency file is already inside the workspace: ${sourcePath}` ); - expect(mockedCore.info).toHaveBeenCalledWith( - `Resolved cache-dependency-path: deps.lock` - ); - expect(mockRestoreCache).toHaveBeenCalled(); }); }); diff --git a/action.yml b/action.yml index e469b7b2..e870e8dc 100644 --- a/action.yml +++ b/action.yml @@ -20,6 +20,10 @@ inputs: default: ${{ github.server_url == 'https://github.com' && github.token || '' }} cache-dependency-path: description: "Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies." + overwrite: + description: Whether to overwrite existing file in workspace + required: false + default: 'false' update-environment: description: "Set this option if you want the action to update environment variables." default: true @@ -29,8 +33,6 @@ inputs: freethreaded: description: "When 'true', use the freethreaded version of Python." default: false - pip-version: - description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]." outputs: python-version: description: "The installed Python or PyPy version. Useful when given a version range as input." @@ -45,4 +47,4 @@ runs: post-if: success() branding: icon: 'code' - color: 'yellow' + color: 'yellow' \ No newline at end of file diff --git a/dist/setup/index.js b/dist/setup/index.js index f8c5d4e7..7852eee3 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -96883,8 +96883,10 @@ function isGraalPyVersion(versionSpec) { } function cacheDependencies(cache, pythonVersion) { return __awaiter(this, void 0, void 0, function* () { + var _a; const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; let resolvedDependencyPath = undefined; + const overwrite = (_a = core.getBooleanInput('overwrite', { required: false })) !== null && _a !== void 0 ? _a : false; if (cacheDependencyPath) { const actionPath = process.env.GITHUB_ACTION_PATH || ''; const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); @@ -96902,11 +96904,18 @@ function cacheDependencies(cache, pythonVersion) { else { if (sourcePath !== targetPath) { const targetDir = path.dirname(targetPath); - // Create target directory if it doesn't exist yield fs_1.default.promises.mkdir(targetDir, { recursive: true }); - // Copy file asynchronously - yield fs_1.default.promises.copyFile(sourcePath, targetPath); - core.info(`Copied ${sourcePath} to ${targetPath}`); + const targetExists = yield fs_1.default.promises + .access(targetPath, fs_1.default.constants.F_OK) + .then(() => true) + .catch(() => false); + if (!targetExists || overwrite) { + yield fs_1.default.promises.copyFile(sourcePath, targetPath); + core.info(`${targetExists ? 'Overwrote' : 'Copied'} ${sourcePath} to ${targetPath}`); + } + else { + core.info(`Skipped copying ${sourcePath} — target already exists at ${targetPath}`); + } } else { core.info(`Dependency file is already inside the workspace: ${sourcePath}`); diff --git a/src/setup-python.ts b/src/setup-python.ts index 106b415a..ea77897a 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -26,7 +26,8 @@ export async function cacheDependencies(cache: string, pythonVersion: string) { const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; let resolvedDependencyPath: string | undefined = undefined; - + const overwrite = + core.getBooleanInput('overwrite', {required: false}) ?? false; if (cacheDependencyPath) { const actionPath = process.env.GITHUB_ACTION_PATH || ''; const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); @@ -48,11 +49,23 @@ export async function cacheDependencies(cache: string, pythonVersion: string) { } else { if (sourcePath !== targetPath) { const targetDir = path.dirname(targetPath); - // Create target directory if it doesn't exist await fs.promises.mkdir(targetDir, {recursive: true}); - // Copy file asynchronously - await fs.promises.copyFile(sourcePath, targetPath); - core.info(`Copied ${sourcePath} to ${targetPath}`); + + const targetExists = await fs.promises + .access(targetPath, fs.constants.F_OK) + .then(() => true) + .catch(() => false); + + if (!targetExists || overwrite) { + await fs.promises.copyFile(sourcePath, targetPath); + core.info( + `${targetExists ? 'Overwrote' : 'Copied'} ${sourcePath} to ${targetPath}` + ); + } else { + core.info( + `Skipped copying ${sourcePath} — target already exists at ${targetPath}` + ); + } } else { core.info( `Dependency file is already inside the workspace: ${sourcePath}`