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

feat(manager/pep621): support pdm lock files #22244

Merged
merged 5 commits into from May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 89 additions & 0 deletions lib/modules/manager/pep621/artifacts.spec.ts
@@ -0,0 +1,89 @@
import { join } from 'upath';
import { mockExecAll } from '../../../../test/exec-util';
import { fs, mockedFunction } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import type { RepoGlobalConfig } from '../../../config/types';
import { getPkgReleases as _getPkgReleases } from '../../datasource';
import type { UpdateArtifactsConfig } from '../types';
import { updateArtifacts } from './artifacts';

jest.mock('../../../util/fs');
jest.mock('../../datasource');

const getPkgReleases = mockedFunction(_getPkgReleases);

const config: UpdateArtifactsConfig = {};
const adminConfig: RepoGlobalConfig = {
localDir: join('/tmp/github/some/repo'),
cacheDir: join('/tmp/cache'),
containerbaseDir: join('/tmp/cache/containerbase'),
};

describe('modules/manager/pep621/artifacts', () => {
describe('updateArtifacts()', () => {
it('return null if all processors returns are empty', async () => {
const updatedDeps = [{ depName: 'dep1' }];
const result = await updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config,
updatedDeps,
});
expect(result).toBeNull();
});

it('return processor result', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('old test content');
fs.readLocalFile.mockResolvedValueOnce('new test content');
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});

const updatedDeps = [{ depName: 'dep1' }];
const result = await updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
});
expect(result).toEqual([
{
file: {
contents: 'new test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([

Check failure on line 62 in lib/modules/manager/pep621/artifacts.spec.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

modules/manager/pep621/artifacts › updateArtifacts() › return processor result

expect(received).toMatchObject(expected) - Expected - 1 + Received + 1 @@ -10,11 +10,11 @@ "options": Object { "encoding": "utf-8", }, }, Object { - "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \"/tmp/github/some/repo\":\"/tmp/github/some/repo\" -v \"/tmp/cache\":\"/tmp/cache\" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w \"/tmp/github/some/repo\" containerbase/sidecar bash -l -c \"install-tool pdm v2.5.0 && pdm update dep1\"", + "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \"/tmp/github/some/repo\":\"/tmp/github/some/repo\" -v \"/tmp/cache\":\"/tmp/cache\" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w \"/tmp/github/some/repo\" containerbase/sidecar bash -l -c \"install-tool pdm v2.5.0 && pdm update \"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", "env": Object { "BUILDPACK_CACHE_DIR": "/tmp/cache/containerbase", at Object.<anonymous> (lib/modules/manager/pep621/artifacts.spec.ts:62:29)
{
cmd: 'docker pull containerbase/sidecar',
options: {
encoding: 'utf-8',
},
},
{
cmd: 'docker ps --filter name=renovate_sidecar -aq',
options: {
encoding: 'utf-8',
},
},
{
cmd: 'docker run --rm --name=renovate_sidecar --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w "/tmp/github/some/repo" containerbase/sidecar bash -l -c "install-tool pdm v2.5.0 && pdm update dep1"',
secustor marked this conversation as resolved.
Show resolved Hide resolved
options: {
cwd: '/tmp/github/some/repo',
encoding: 'utf-8',
env: {
BUILDPACK_CACHE_DIR: '/tmp/cache/containerbase',
CONTAINERBASE_CACHE_DIR: '/tmp/cache/containerbase',
},
},
},
]);
});
});
});
23 changes: 23 additions & 0 deletions lib/modules/manager/pep621/artifacts.ts
@@ -0,0 +1,23 @@
import is from '@sindresorhus/is';
import { writeLocalFile } from '../../../util/fs';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import { processors } from './processors';

export async function updateArtifacts(
updateArtifact: UpdateArtifact
): Promise<UpdateArtifactsResult[] | null> {
const { packageFileName, newPackageFileContent } = updateArtifact;

await writeLocalFile(packageFileName, newPackageFileContent);
secustor marked this conversation as resolved.
Show resolved Hide resolved

// process specific tool sets
const result: UpdateArtifactsResult[] = [];
for (const processor of processors) {
const artifactUpdates = await processor.updateArtifacts(updateArtifact);
if (is.array(artifactUpdates)) {
result.push(...artifactUpdates);
}
}

return result.length > 0 ? result : null;
}
3 changes: 3 additions & 0 deletions lib/modules/manager/pep621/index.ts
@@ -1,8 +1,11 @@
import { PypiDatasource } from '../../datasource/pypi';
export { extractPackageFile } from './extract';
export { updateArtifacts } from './artifacts';

export const supportedDatasources = [PypiDatasource.id];

export const supportsLockFileMaintenance = true;

export const defaultConfig = {
fileMatch: ['(^|/)pyproject\\.toml$'],
};
160 changes: 160 additions & 0 deletions lib/modules/manager/pep621/processors/pdm.spec.ts
@@ -0,0 +1,160 @@
import { join } from 'upath';
import { mockExecAll } from '../../../../../test/exec-util';
import { fs, mockedFunction } from '../../../../../test/util';
import { GlobalConfig } from '../../../../config/global';
import type { RepoGlobalConfig } from '../../../../config/types';
import { getPkgReleases as _getPkgReleases } from '../../../datasource';
import type { UpdateArtifactsConfig } from '../../types';
import { PdmProcessor } from './pdm';

jest.mock('../../../../util/fs');
jest.mock('../../../datasource');

const getPkgReleases = mockedFunction(_getPkgReleases);

const config: UpdateArtifactsConfig = {};
const adminConfig: RepoGlobalConfig = {
localDir: join('/tmp/github/some/repo'),
cacheDir: join('/tmp/cache'),
containerbaseDir: join('/tmp/cache/containerbase'),
};

const processor = new PdmProcessor();

describe('modules/manager/pep621/processors/pdm', () => {
describe('updateArtifacts()', () => {
it('return null if there is no lock file', async () => {
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
const updatedDeps = [{ depName: 'dep1' }];
const result = await processor.updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config,
updatedDeps,
});
expect(result).toBeNull();
});

it('return null if the lock file is unchanged', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('test content');
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});

const updatedDeps = [{ depName: 'dep1' }];
const result = await processor.updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
});
expect(result).toBeNull();
expect(execSnapshots).toMatchObject([

Check failure on line 57 in lib/modules/manager/pep621/processors/pdm.spec.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

modules/manager/pep621/processors/pdm › updateArtifacts() › return null if the lock file is unchanged

expect(received).toMatchObject(expected) - Expected - 1 + Received + 1 @@ -4,8 +4,8 @@ }, Object { "cmd": "docker ps --filter name=renovate_sidecar -aq", }, Object { - "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \"/tmp/github/some/repo\":\"/tmp/github/some/repo\" -v \"/tmp/cache\":\"/tmp/cache\" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w \"/tmp/github/some/repo\" containerbase/sidecar bash -l -c \"install-tool pdm v2.5.0 && pdm update dep1\"", + "cmd": "docker run --rm --name=renovate_sidecar --label=renovate_child -v \"/tmp/github/some/repo\":\"/tmp/github/some/repo\" -v \"/tmp/cache\":\"/tmp/cache\" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w \"/tmp/github/some/repo\" containerbase/sidecar bash -l -c \"install-tool pdm v2.5.0 && pdm update \"", }, ] at Object.<anonymous> (lib/modules/manager/pep621/processors/pdm.spec.ts:57:29)
{
cmd: 'docker pull containerbase/sidecar',
},
{
cmd: 'docker ps --filter name=renovate_sidecar -aq',
},
{
cmd: 'docker run --rm --name=renovate_sidecar --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e BUILDPACK_CACHE_DIR -e CONTAINERBASE_CACHE_DIR -w "/tmp/github/some/repo" containerbase/sidecar bash -l -c "install-tool pdm v2.5.0 && pdm update dep1"',
secustor marked this conversation as resolved.
Show resolved Hide resolved
},
]);
});

it('returns artifact error', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockImplementationOnce(() => {
throw new Error('test error');
});

const updatedDeps = [{ depName: 'dep1' }];
const result = await processor.updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
});
expect(result).toEqual([
{ artifactError: { lockFile: 'pdm.lock', stderr: 'test error' } },
]);
expect(execSnapshots).toEqual([]);
});

it('return update dep update', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('changed test content');
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});

const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }];
const result = await processor.updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {},
updatedDeps,
});
expect(result).toEqual([
{
file: {
contents: 'changed test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([

Check failure on line 118 in lib/modules/manager/pep621/processors/pdm.spec.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

modules/manager/pep621/processors/pdm › updateArtifacts() › return update dep update

expect(received).toMatchObject(expected) - Expected - 1 + Received + 1 Array [ Object { - "cmd": "pdm update dep1 dep2", + "cmd": "pdm update ", }, ] at Object.<anonymous> (lib/modules/manager/pep621/processors/pdm.spec.ts:118:29)
{
cmd: 'pdm update dep1 dep2',
},
]);
});

it('return update on lockfileMaintenance', async () => {
const execSnapshots = mockExecAll();
GlobalConfig.set(adminConfig);
fs.getSiblingFileName.mockReturnValueOnce('pdm.lock');
fs.readLocalFile.mockResolvedValueOnce('test content');
fs.readLocalFile.mockResolvedValueOnce('changed test content');
// pdm
getPkgReleases.mockResolvedValueOnce({
releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
});

const result = await processor.updateArtifacts({
packageFileName: 'pyproject.toml',
newPackageFileContent: '',
config: {
updateType: 'lockFileMaintenance',
},
updatedDeps: [],
});
expect(result).toEqual([
{
file: {
contents: 'changed test content',
path: 'pdm.lock',
type: 'addition',
},
},
]);
expect(execSnapshots).toMatchObject([

Check failure on line 153 in lib/modules/manager/pep621/processors/pdm.spec.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

modules/manager/pep621/processors/pdm › updateArtifacts() › return update on lockfileMaintenance

expect(received).toMatchObject(expected) - Expected - 1 + Received + 1 Array [ Object { - "cmd": "pdm update ", + "cmd": "pdm update", }, ] at Object.<anonymous> (lib/modules/manager/pep621/processors/pdm.spec.ts:153:29)
{
cmd: 'pdm update ',
},
]);
});
});
});
82 changes: 81 additions & 1 deletion lib/modules/manager/pep621/processors/pdm.ts
@@ -1,6 +1,15 @@
import is from '@sindresorhus/is';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { exec } from '../../../../util/exec';
import type { ExecOptions, ToolConstraint } from '../../../../util/exec/types';
import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
import { PypiDatasource } from '../../../datasource/pypi';
import type { PackageDependency } from '../../types';
import type {
PackageDependency,
UpdateArtifact,
UpdateArtifactsResult,
} from '../../types';
import type { PyProject } from '../schema';
import { parseDependencyGroupRecord } from '../utils';
import type { PyProjectProcessor } from './types';
Expand Down Expand Up @@ -39,4 +48,75 @@ export class PdmProcessor implements PyProjectProcessor {

return deps;
}

async updateArtifacts(
updateArtifact: UpdateArtifact
): Promise<UpdateArtifactsResult[] | null> {
const { config, updatedDeps, packageFileName } = updateArtifact;

const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';

// abort if no lockfile is defined
const lockFileName = getSiblingFileName(packageFileName, 'pdm.lock');
try {
const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
if (is.nullOrUndefined(existingLockFileContent)) {
logger.debug('No pdm.lock found');
return null;
}

const toolConstraint: ToolConstraint = {
toolName: 'pdm',
constraint: config.constraints?.pdm,
};

const execOptions: ExecOptions = {
docker: {},
toolConstraints: [toolConstraint],
};

// on lockFileMaintenance do not specify any packages and update the complete lock file
secustor marked this conversation as resolved.
Show resolved Hide resolved
// else only update specific packages
let packageList = '';
if (!isLockFileMaintenance) {
packageList = ' ' + updatedDeps
.map((value) => value.packageName)
.join(' ');
}
const cmd = `pdm update${packageList}`;
await exec(cmd, execOptions);

// check for changes
const fileChanges: UpdateArtifactsResult[] = [];
const newLockContent = await readLocalFile(lockFileName, 'utf8');
const isLockFileChanged = existingLockFileContent !== newLockContent;
if (isLockFileChanged) {
fileChanges.push({
file: {
type: 'addition',
path: lockFileName,
contents: newLockContent,
},
});
} else {
logger.debug('pdm.lock is unchanged');
}

return fileChanges.length ? fileChanges : null;
} catch (err) {
// istanbul ignore if
if (err.message === TEMPORARY_ERROR) {
throw err;
}
logger.debug({ err }, 'Failed to update PDM lock file');
return [
{
artifactError: {
lockFile: lockFileName,
stderr: err.message,
},
},
];
}
}
}
10 changes: 9 additions & 1 deletion lib/modules/manager/pep621/processors/types.ts
@@ -1,7 +1,15 @@
import type { PackageDependency } from '../../types';
import type {
PackageDependency,
UpdateArtifact,
UpdateArtifactsResult,
} from '../../types';
import type { PyProject } from '../schema';

export interface PyProjectProcessor {
updateArtifacts(
updateArtifact: UpdateArtifact
): Promise<UpdateArtifactsResult[] | null>;

/**
* Extracts additional dependencies and/or modifies existing ones based on the tool configuration.
* If no relevant section for the processor exists, then it should return the received dependencies unmodified.
Expand Down