Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for caching uv #818

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions __tests__/cache-restore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('restore-cache', () => {
'2d0ff7f46b0e120e3d3294db65768b474934242637b9899b873e6283dfd16d7c';
const poetryLockHash =
'f24ea1ad73968e6c8d80c16a093ade72d9332c433aeef979a0dd943e6a99b2ab';
const uvLockHash = 'efe9f18aef431b3f1dbe13bee790b00095e74fb19aa5ced5ace96d063f03258d';
const poetryConfigOutput = `
cache-dir = "/Users/patrick/Library/Caches/pypoetry"
experimental.new-installer = false
Expand Down Expand Up @@ -153,6 +154,13 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
path.join(__dirname, 'data', 'inner', '.venv'),
path.join(__dirname, 'data', '.venv')
]
],
[
'uv',
'3.12.0',
'__tests__/data/**/pyproject.toml',
uvLockHash,
undefined,
]
])(
'restored dependencies for %s by primaryKey',
Expand Down Expand Up @@ -188,6 +196,10 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
result => result.value
);

if(!restoredKeys.length) {
throw new Error("No restored keys found, this probably means there's something wrong with the test");
}

restoredKeys.forEach(restoredKey => {
if (restoredKey) {
if (process.platform === 'linux' && packageManager === 'pip') {
Expand Down
29 changes: 29 additions & 0 deletions __tests__/cache-save.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('run', () => {
'2d0ff7f46b0e120e3d3294db65768b474934242637b9899b873e6283dfd16d7c';
const poetryLockHash =
'571bf984f8d210e6a97f854e479fdd4a2b5af67b5fdac109ec337a0ea16e7836';
const uvLockHash = 'TODO'; // TODO: what should be the correct value?

// core spy
let infoSpy: jest.SpyInstance;
Expand Down Expand Up @@ -202,6 +203,34 @@ describe('run', () => {
expect(setFailedSpy).not.toHaveBeenCalled();
});

it('saves cache from uv', async () => {
inputs['cache'] = 'uv';
inputs['python-version'] = '3.12.0';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
console.log(name);
return uvLockHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return requirementsHash;
}
});

await run();

expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${uvLockHash}, not saving cache.`
);
expect(saveCacheSpy).toHaveBeenCalled();
expect(infoSpy).toHaveBeenLastCalledWith(
`Cache saved with the key: ${requirementsHash}`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});

it('saves with -1 cacheId , should not fail workflow', async () => {
inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0';
Expand Down
2 changes: 1 addition & 1 deletion __tests__/data/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ pyinstaller = "5.13.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
build-backend = "poetry.core.masonry.api"
86 changes: 86 additions & 0 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90291,11 +90291,13 @@ exports.getCacheDistributor = exports.PackageManagers = void 0;
const pip_cache_1 = __importDefault(__nccwpck_require__(5546));
const pipenv_cache_1 = __importDefault(__nccwpck_require__(238));
const poetry_cache_1 = __importDefault(__nccwpck_require__(1993));
const uv_cache_1 = __importDefault(__nccwpck_require__(8795));
var PackageManagers;
(function (PackageManagers) {
PackageManagers["Pip"] = "pip";
PackageManagers["Pipenv"] = "pipenv";
PackageManagers["Poetry"] = "poetry";
PackageManagers["Uv"] = "uv";
})(PackageManagers || (exports.PackageManagers = PackageManagers = {}));
function getCacheDistributor(packageManager, pythonVersion, cacheDependencyPath) {
switch (packageManager) {
Expand All @@ -90305,6 +90307,8 @@ function getCacheDistributor(packageManager, pythonVersion, cacheDependencyPath)
return new pipenv_cache_1.default(pythonVersion, cacheDependencyPath);
case PackageManagers.Poetry:
return new poetry_cache_1.default(pythonVersion, cacheDependencyPath);
case PackageManagers.Uv:
return new uv_cache_1.default(pythonVersion, cacheDependencyPath);
default:
throw new Error(`Caching for '${packageManager}' is not supported`);
}
Expand Down Expand Up @@ -90679,6 +90683,88 @@ class PoetryCache extends cache_distributor_1.default {
exports["default"] = PoetryCache;


/***/ }),

/***/ 8795:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const glob = __importStar(__nccwpck_require__(8090));
const os = __importStar(__nccwpck_require__(2037));
const path = __importStar(__nccwpck_require__(1017));
const cache_distributor_1 = __importDefault(__nccwpck_require__(8953));
class UvCache extends cache_distributor_1.default {
constructor(pythonVersion, patterns = '**/requirements.txt') {
super('uv', patterns);
this.pythonVersion = pythonVersion;
this.patterns = patterns;
}
getCacheGlobalDirectories() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if (process.platform === 'win32') {
// `LOCALAPPDATA` should always be defined,
// but we can't just join `undefined`
// into the path in case it's not.
return [
path.join((_a = process.env['LOCALAPPDATA']) !== null && _a !== void 0 ? _a : os.homedir(), 'uv', 'cache')
];
}
return [path.join(os.homedir(), '.cache/uv')];
});
}
computeKeys() {
return __awaiter(this, void 0, void 0, function* () {
const hash = yield glob.hashFiles(this.patterns);
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = undefined;
return {
primaryKey,
restoreKey
};
});
}
}
exports["default"] = UvCache;


/***/ }),

/***/ 8040:
Expand Down
6 changes: 5 additions & 1 deletion src/cache-distributions/cache-factory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import PipCache from './pip-cache';
import PipenvCache from './pipenv-cache';
import PoetryCache from './poetry-cache';
import UvCache from './uv-cache';

export enum PackageManagers {
Pip = 'pip',
Pipenv = 'pipenv',
Poetry = 'poetry'
Poetry = 'poetry',
Uv = 'uv'
}

export function getCacheDistributor(
Expand All @@ -20,6 +22,8 @@ export function getCacheDistributor(
return new PipenvCache(pythonVersion, cacheDependencyPath);
case PackageManagers.Poetry:
return new PoetryCache(pythonVersion, cacheDependencyPath);
case PackageManagers.Uv:
return new UvCache(pythonVersion, cacheDependencyPath);
default:
throw new Error(`Caching for '${packageManager}' is not supported`);
}
Expand Down
36 changes: 36 additions & 0 deletions src/cache-distributions/uv-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as glob from '@actions/glob';
import * as os from 'os';
import * as path from 'path';

import CacheDistributor from './cache-distributor';

export default class UvCache extends CacheDistributor {
constructor(
private pythonVersion: string,
protected patterns: string = '**/requirements.txt'
) {
super('uv', patterns);
}

protected async getCacheGlobalDirectories() {
if (process.platform === 'win32') {
// `LOCALAPPDATA` should always be defined,
// but we can't just join `undefined`
// into the path in case it's not.
return [
path.join(process.env['LOCALAPPDATA'] ?? os.homedir(), 'uv', 'cache')
];
}
return [path.join(os.homedir(), '.cache/uv')];
}

protected async computeKeys() {
const hash = await glob.hashFiles(this.patterns);
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = undefined;
return {
primaryKey,
restoreKey
};
}
}