import * as glob from '@actions/glob'; import * as io from '@actions/io'; import * as path from 'path'; import * as exec from '@actions/exec'; import * as core from '@actions/core'; import CacheDistributor from './cache-distributor'; import {logWarning} from '../utils'; class PoetryCache extends CacheDistributor { constructor( private pythonVersion: string, protected patterns: string = '**/poetry.lock', protected poetryProjects: Set = new Set() ) { super('poetry', patterns); } protected async getCacheGlobalDirectories() { // Same virtualenvs path may appear for different projects, hence we use a Set const paths = new Set(); const globber = await glob.create(this.patterns); for await (const file of globber.globGenerator()) { const basedir = path.dirname(file); core.debug(`Processing Poetry project at ${basedir}`); this.poetryProjects.add(basedir); const poetryConfig = await this.getPoetryConfiguration(basedir); const cacheDir = poetryConfig['cache-dir']; const virtualenvsPath = poetryConfig['virtualenvs.path'].replace( '{cache-dir}', cacheDir ); paths.add(virtualenvsPath); if (poetryConfig['virtualenvs.in-project']) { paths.add(path.join(basedir, '.venv')); } } return [...paths]; } protected async computeKeys() { const hash = await glob.hashFiles(this.patterns); // "v2" is here to invalidate old caches of this cache distributor, which were created broken: const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-v2-${hash}`; const restoreKey = undefined; return { primaryKey, restoreKey }; } protected async handleLoadedCache() { await super.handleLoadedCache(); // After the cache is loaded -- make sure virtualenvs use the correct Python version (the one that we have just installed). // This will handle invalid caches, recreating virtualenvs if necessary. const pythonLocation = await io.which('python'); if (pythonLocation) { core.debug(`pythonLocation is ${pythonLocation}`); } else { logWarning('python binaries were not found in PATH'); return; } for (const poetryProject of this.poetryProjects) { const {exitCode, stderr} = await exec.getExecOutput( 'poetry', ['env', 'use', pythonLocation], {ignoreReturnCode: true, cwd: poetryProject} ); if (exitCode) { logWarning(stderr); } } } private async getPoetryConfiguration(basedir: string) { const {stdout, stderr, exitCode} = await exec.getExecOutput( 'poetry', ['config', '--list'], {cwd: basedir} ); if (exitCode && stderr) { throw new Error( 'Could not get cache folder path for poetry package manager' ); } const lines = stdout.trim().split('\n'); const config: any = {}; for (let line of lines) { line = line.replace(/#.*$/gm, ''); const [key, value] = line.split('=').map(part => part.trim()); config[key] = JSON.parse(value); } return config as { 'cache-dir': string; 'virtualenvs.in-project': boolean; 'virtualenvs.path': string; }; } } export default PoetryCache;