From 57458e55b24de7c4792e6af83aab993427eb4df1 Mon Sep 17 00:00:00 2001 From: Tobiasz Lewandowski Date: Thu, 28 Mar 2024 20:22:41 +0100 Subject: [PATCH 1/7] feat(conan): Add support for lockfile maintenance --- docs/usage/configuration-options.md | 1 + .../__snapshots__/artifacts.spec.ts.snap | 70 +++++ lib/modules/manager/conan/artifacts.spec.ts | 243 ++++++++++++++++++ lib/modules/manager/conan/artifacts.ts | 92 +++++++ lib/modules/manager/conan/index.ts | 3 + lib/modules/manager/conan/readme.md | 3 +- 6 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap create mode 100644 lib/modules/manager/conan/artifacts.spec.ts create mode 100644 lib/modules/manager/conan/artifacts.ts diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index ada09580b5911e..1e86d5bb54b7ec 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2187,6 +2187,7 @@ Supported lock files: - `Cargo.lock` - `Chart.lock` - `composer.lock` +- `conan.lock` - `flake.lock` - `Gemfile.lock` - `gradle.lockfile` diff --git a/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap b/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap new file mode 100644 index 00000000000000..2009f890e3c0cd --- /dev/null +++ b/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/manager/conan/artifacts returns null if read operation failed for new conan.lock 1`] = ` +[ + { + "cmd": "conan lock create conanfile.py --lockfile=""", + "options": { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`modules/manager/conan/artifacts returns updated conan.lock if isLockFileMaintenance is true 1`] = ` +[ + { + "cmd": "conan lock create conanfile.py --lockfile=""", + "options": { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`modules/manager/conan/artifacts returns updated conan.lock if updateType is lockFileMaintenance 1`] = ` +[ + { + "cmd": "conan lock create conanfile.py --lockfile=""", + "options": { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; diff --git a/lib/modules/manager/conan/artifacts.spec.ts b/lib/modules/manager/conan/artifacts.spec.ts new file mode 100644 index 00000000000000..e813aeb2436a96 --- /dev/null +++ b/lib/modules/manager/conan/artifacts.spec.ts @@ -0,0 +1,243 @@ +import { mockDeep } from 'jest-mock-extended'; +import { join } from 'upath'; +import { envMock, mockExecAll } from '../../../../test/exec-util'; +import { env, fs, mocked } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; +import type { RepoGlobalConfig } from '../../../config/types'; +import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import * as docker from '../../../util/exec/docker'; +import * as _hostRules from '../../../util/host-rules'; +import type { UpdateArtifactsConfig } from '../types'; +import * as conan from '.'; + +jest.mock('../../../util/exec/env'); +jest.mock('../../../util/git'); +jest.mock('../../../util/host-rules', () => mockDeep()); +jest.mock('../../../util/http'); +jest.mock('../../../util/fs'); + +process.env.CONTAINERBASE = 'true'; +const hostRules = mocked(_hostRules); +const config: UpdateArtifactsConfig = {}; + +const adminConfig: RepoGlobalConfig = { + localDir: join('/tmp/github/some/repo'), + cacheDir: join('/tmp/cache'), + containerbaseDir: join('/tmp/cache/containerbase'), + dockerSidecarImage: 'ghcr.io/containerbase/sidecar', +}; + +describe('modules/manager/conan/artifacts', () => { + beforeEach(() => { + env.getChildProcessEnv.mockReturnValue(envMock.basic); + GlobalConfig.set(adminConfig); + docker.resetPrefetchedImages(); + hostRules.getAll.mockReturnValue([]); + }); + + afterEach(() => { + GlobalConfig.reset(); + }); + + it('returns null if conan.lock maintenance is turned off', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config, + }), + ).toBeNull(); + }); + + it('returns null if there is no dependencies to update', async () => { + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps: [], + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toBeNull(); + }); + + it('returns null if conan.lock was not found', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.findLocalSiblingOrParent.mockResolvedValueOnce(null); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toBeNull(); + }); + + it('returns null if conan.lock read operation failed', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce(null); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toBeNull(); + }); + + it('returns null if read operation failed for new conan.lock', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(null); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('returns updated conan.lock if updateType is lockFileMaintenance', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock'); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toEqual([ + { + file: { + contents: 'Updated conan.lock', + path: 'conan.lock', + type: 'addition', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('returns updated conan.lock if isLockFileMaintenance is true', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock'); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, isLockFileMaintenance: true }, + }), + ).toEqual([ + { + file: { + contents: 'Updated conan.lock', + path: 'conan.lock', + type: 'addition', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('rethrows temporary error', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + mockExecAll(new Error(TEMPORARY_ERROR)); + + await expect( + conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).rejects.toThrow(TEMPORARY_ERROR); + }); + + it('returns an artifact error when conan update fails', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + const errorMessage = 'conan.lock update execution failure message'; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + mockExecAll(new Error(errorMessage)); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps, + newPackageFileContent: '', + config: { ...config, updateType: 'lockFileMaintenance' }, + }), + ).toEqual([ + { artifactError: { lockFile: 'conan.lock', stderr: errorMessage } }, + ]); + }); +}); diff --git a/lib/modules/manager/conan/artifacts.ts b/lib/modules/manager/conan/artifacts.ts new file mode 100644 index 00000000000000..9b5520e16162a6 --- /dev/null +++ b/lib/modules/manager/conan/artifacts.ts @@ -0,0 +1,92 @@ +import { quote } from 'shlex'; +import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; +import { exec } from '../../../util/exec'; +import type { ExecOptions } from '../../../util/exec/types'; +import { findLocalSiblingOrParent, readLocalFile } from '../../../util/fs'; +import { getGitEnvironmentVariables } from '../../../util/git/auth'; +import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; + +async function conanUpdate(conanFilePath: string): Promise { + const command = `conan lock create ${quote(conanFilePath)} --lockfile=""`; + + const execOptions: ExecOptions = { + extraEnv: { ...getGitEnvironmentVariables(['conan']) }, + docker: {}, + }; + + await exec(command, execOptions); +} + +export async function updateArtifacts( + updateArtifact: UpdateArtifact, +): Promise { + const { packageFileName, updatedDeps, config } = updateArtifact; + + logger.debug(`conan.updateArtifacts(${packageFileName})`); + + const isLockFileMaintenance = + config.updateType === 'lockFileMaintenance' || + config.isLockFileMaintenance === true; + if (!isLockFileMaintenance) { + logger.debug('conan.lock file maintenance is turned off'); + return null; + } + + if (!updatedDeps?.length) { + logger.debug('No conan.file dependencies to update'); + return null; + } + + const lockFileName = await findLocalSiblingOrParent( + packageFileName, + 'conan.lock', + ); + if (!lockFileName) { + logger.debug('No conan.lock found'); + return null; + } + + const existingLockFileContent = await readLocalFile(lockFileName); + if (!existingLockFileContent) { + logger.debug(lockFileName + ' read operation failed'); + return null; + } + + try { + logger.debug('Updating ' + lockFileName); + await conanUpdate(packageFileName); + logger.debug('Returning updated' + lockFileName); + + const newLockFileContent = await readLocalFile(lockFileName); + if (!newLockFileContent) { + logger.debug('New ' + lockFileName + ' read operation failed'); + return null; + } + + return [ + { + file: { + type: 'addition', + path: lockFileName, + contents: newLockFileContent, + }, + }, + ]; + } catch (err) { + if (err.message === TEMPORARY_ERROR) { + throw err; + } + + logger.debug({ err }, 'Failed to update ' + lockFileName); + + return [ + { + artifactError: { + lockFile: lockFileName, + stderr: err.message, + }, + }, + ]; + } +} diff --git a/lib/modules/manager/conan/index.ts b/lib/modules/manager/conan/index.ts index 79ea56354424fb..6307a1103c1662 100644 --- a/lib/modules/manager/conan/index.ts +++ b/lib/modules/manager/conan/index.ts @@ -1,9 +1,12 @@ export { extractPackageFile } from './extract'; +export { updateArtifacts } from './artifacts'; import type { Category } from '../../../constants'; export { getRangeStrategy } from './range'; import { ConanDatasource } from '../../datasource/conan'; import * as conan from '../../versioning/conan'; +export const supportsLockFileMaintenance = true; + export const defaultConfig = { fileMatch: ['(^|/)conanfile\\.(txt|py)$'], datasource: ConanDatasource.id, diff --git a/lib/modules/manager/conan/readme.md b/lib/modules/manager/conan/readme.md index 83f426db1aa95f..3655b6664c7025 100644 --- a/lib/modules/manager/conan/readme.md +++ b/lib/modules/manager/conan/readme.md @@ -3,7 +3,7 @@ The Conan package manager is disabled by default due to slowness in the Conan API. We recommend you only enable it for low volume experimental purposes until [issue #14170](https://github.com/renovatebot/renovate/issues/14170) is resolved. -Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files. +Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files and also updates `conan.lock` files too if found. How it works: @@ -14,6 +14,7 @@ How it works: - and the `python_requires`, `requires` and `build_requires` variables in the `conanfile.py` format 1. Renovate resolves the dependency's version using the Conan v2 API 1. If Renovate finds an update, Renovate will update `conanfile.txt` or `conanfile.py` +1. Renovate also updates `conan.lock` basing on updated `conanfile` if `conan.lock` exists and if `updateType` is `lockFileMaintenance` or `isLockFileMaintenance` is `true` Enabling Conan updating From b1a640959d8e2661f8f604eac03e0ea1c61ed30e Mon Sep 17 00:00:00 2001 From: TobiaszSML Date: Mon, 8 Apr 2024 18:12:48 +0200 Subject: [PATCH 2/7] Update lib/modules/manager/conan/readme.md Co-authored-by: Damian E --- lib/modules/manager/conan/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/manager/conan/readme.md b/lib/modules/manager/conan/readme.md index 3655b6664c7025..d2fc05738a0647 100644 --- a/lib/modules/manager/conan/readme.md +++ b/lib/modules/manager/conan/readme.md @@ -3,7 +3,7 @@ The Conan package manager is disabled by default due to slowness in the Conan API. We recommend you only enable it for low volume experimental purposes until [issue #14170](https://github.com/renovatebot/renovate/issues/14170) is resolved. -Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files and also updates `conan.lock` files too if found. +Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files and update `conan.lock` appropriately if present. How it works: From ca6f274145625ec48ec873a1df9342f608376dbf Mon Sep 17 00:00:00 2001 From: TobiaszSML Date: Mon, 8 Apr 2024 18:13:05 +0200 Subject: [PATCH 3/7] Update lib/modules/manager/conan/artifacts.spec.ts Co-authored-by: Damian E --- lib/modules/manager/conan/artifacts.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/manager/conan/artifacts.spec.ts b/lib/modules/manager/conan/artifacts.spec.ts index e813aeb2436a96..8090b6dd245ddd 100644 --- a/lib/modules/manager/conan/artifacts.spec.ts +++ b/lib/modules/manager/conan/artifacts.spec.ts @@ -56,7 +56,7 @@ describe('modules/manager/conan/artifacts', () => { ).toBeNull(); }); - it('returns null if there is no dependencies to update', async () => { + it('returns null if there are no dependencies to update', async () => { expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', From ed4fc248ccf9841524eb7888a94dc9317409acef Mon Sep 17 00:00:00 2001 From: Tobiasz Lewandowski Date: Mon, 8 Apr 2024 19:05:17 +0200 Subject: [PATCH 4/7] Simplify readme related to conan.lock --- lib/modules/manager/conan/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/manager/conan/readme.md b/lib/modules/manager/conan/readme.md index d2fc05738a0647..ed7a982eb030bb 100644 --- a/lib/modules/manager/conan/readme.md +++ b/lib/modules/manager/conan/readme.md @@ -14,7 +14,7 @@ How it works: - and the `python_requires`, `requires` and `build_requires` variables in the `conanfile.py` format 1. Renovate resolves the dependency's version using the Conan v2 API 1. If Renovate finds an update, Renovate will update `conanfile.txt` or `conanfile.py` -1. Renovate also updates `conan.lock` basing on updated `conanfile` if `conan.lock` exists and if `updateType` is `lockFileMaintenance` or `isLockFileMaintenance` is `true` +1. Renovate also updates `conan.lock` basing on updated `conanfile` if `conan.lock` exists Enabling Conan updating From dd4b97b38dd6f7b23c4a4041a9a708dabe3a9897 Mon Sep 17 00:00:00 2001 From: Tobiasz Lewandowski Date: Mon, 13 May 2024 10:01:19 +0200 Subject: [PATCH 5/7] Use lockFileMaintenance concept in an appropriate way and other improvements --- .../__snapshots__/artifacts.spec.ts.snap | 70 -------- lib/modules/manager/conan/artifacts.spec.ts | 151 +++++++++++++++--- lib/modules/manager/conan/artifacts.ts | 26 +-- lib/modules/manager/conan/readme.md | 4 +- 4 files changed, 146 insertions(+), 105 deletions(-) delete mode 100644 lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap diff --git a/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap b/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap deleted file mode 100644 index 2009f890e3c0cd..00000000000000 --- a/lib/modules/manager/conan/__snapshots__/artifacts.spec.ts.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`modules/manager/conan/artifacts returns null if read operation failed for new conan.lock 1`] = ` -[ - { - "cmd": "conan lock create conanfile.py --lockfile=""", - "options": { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/conan/artifacts returns updated conan.lock if isLockFileMaintenance is true 1`] = ` -[ - { - "cmd": "conan lock create conanfile.py --lockfile=""", - "options": { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/conan/artifacts returns updated conan.lock if updateType is lockFileMaintenance 1`] = ` -[ - { - "cmd": "conan lock create conanfile.py --lockfile=""", - "options": { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": { - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; diff --git a/lib/modules/manager/conan/artifacts.spec.ts b/lib/modules/manager/conan/artifacts.spec.ts index 8090b6dd245ddd..3d52e15f190087 100644 --- a/lib/modules/manager/conan/artifacts.spec.ts +++ b/lib/modules/manager/conan/artifacts.spec.ts @@ -5,6 +5,7 @@ import { env, fs, mocked } from '../../../../test/util'; import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; import * as docker from '../../../util/exec/docker'; import * as _hostRules from '../../../util/host-rules'; import type { UpdateArtifactsConfig } from '../types'; @@ -39,13 +40,29 @@ describe('modules/manager/conan/artifacts', () => { GlobalConfig.reset(); }); - it('returns null if conan.lock maintenance is turned off', async () => { + it('returns null if updatedDeps are empty and lockFileMaintenance is turned off', async () => { + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps: [], + newPackageFileContent: '', + config, + }), + ).toBeNull(); + expect(logger.debug).toHaveBeenCalledWith( + 'No conan.lock dependencies to update', + ); + }); + + it('returns null if conan.lock was not found', async () => { const updatedDeps = [ { depName: 'dep', }, ]; + fs.findLocalSiblingOrParent.mockResolvedValueOnce(null); + expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', @@ -54,88 +71,142 @@ describe('modules/manager/conan/artifacts', () => { config, }), ).toBeNull(); + expect(logger.debug).toHaveBeenCalledWith('No conan.lock found'); }); - it('returns null if there are no dependencies to update', async () => { + it('returns null if conan.lock read operation failed', async () => { + const updatedDeps = [ + { + depName: 'dep', + }, + ]; + + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce(null); + expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', - updatedDeps: [], + updatedDeps, newPackageFileContent: '', - config: { ...config, updateType: 'lockFileMaintenance' }, + config, }), ).toBeNull(); + expect(logger.debug).toHaveBeenCalledWith( + 'conan.lock read operation failed', + ); }); - it('returns null if conan.lock was not found', async () => { + it('returns null if read operation failed for new conan.lock', async () => { const updatedDeps = [ { depName: 'dep', }, ]; + const expectedInSnapshot = [ + { + cmd: 'conan lock create conanfile.py --lockfile=""', + }, + ]; - fs.findLocalSiblingOrParent.mockResolvedValueOnce(null); + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce(null); expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', updatedDeps, newPackageFileContent: '', - config: { ...config, updateType: 'lockFileMaintenance' }, + config, }), ).toBeNull(); + expect(execSnapshots).toMatchObject(expectedInSnapshot); + expect(logger.debug).toHaveBeenCalledWith( + 'New conan.lock read operation failed', + ); }); - it('returns null if conan.lock read operation failed', async () => { + it('returns null if original and updated conan.lock files are the same', async () => { const updatedDeps = [ { depName: 'dep', }, ]; + const expectedInSnapshot = [ + { + cmd: 'conan lock create conanfile.py --lockfile=""', + }, + ]; + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); - fs.readLocalFile.mockResolvedValueOnce(null); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', updatedDeps, newPackageFileContent: '', - config: { ...config, updateType: 'lockFileMaintenance' }, + config, }), ).toBeNull(); + expect(execSnapshots).toMatchObject(expectedInSnapshot); + expect(logger.debug).toHaveBeenCalledWith('conan.lock is unchanged'); }); - it('returns null if read operation failed for new conan.lock', async () => { + it('returns updated conan.lock for conanfile.txt', async () => { const updatedDeps = [ { depName: 'dep', }, ]; + const expectedInSnapshot = [ + { + cmd: 'conan lock create conanfile.txt --lockfile=""', + }, + ]; fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); const execSnapshots = mockExecAll(); - fs.readLocalFile.mockResolvedValueOnce(null); + fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock'); expect( await conan.updateArtifacts({ - packageFileName: 'conanfile.py', + packageFileName: 'conanfile.txt', updatedDeps, newPackageFileContent: '', - config: { ...config, updateType: 'lockFileMaintenance' }, + config, }), - ).toBeNull(); - expect(execSnapshots).toMatchSnapshot(); + ).toEqual([ + { + file: { + contents: 'Updated conan.lock', + path: 'conan.lock', + type: 'addition', + }, + }, + ]); + expect(execSnapshots).toMatchObject(expectedInSnapshot); }); - it('returns updated conan.lock if updateType is lockFileMaintenance', async () => { + it('returns updated conan.lock when updateType are not empty', async () => { const updatedDeps = [ { depName: 'dep', }, ]; + const expectedInSnapshot = [ + { + cmd: 'conan lock create conanfile.py --lockfile=""', + }, + ]; fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); @@ -148,6 +219,38 @@ describe('modules/manager/conan/artifacts', () => { packageFileName: 'conanfile.py', updatedDeps, newPackageFileContent: '', + config, + }), + ).toEqual([ + { + file: { + contents: 'Updated conan.lock', + path: 'conan.lock', + type: 'addition', + }, + }, + ]); + expect(execSnapshots).toMatchObject(expectedInSnapshot); + }); + + it('returns updated conan.lock when updateType are empty, but updateType is lockFileMaintenance', async () => { + const expectedInSnapshot = [ + { + cmd: 'conan lock create conanfile.py --lockfile=""', + }, + ]; + + fs.statLocalFile.mockResolvedValueOnce({ name: 'conan.lock' } as any); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock'); + fs.readLocalFile.mockResolvedValueOnce('Original conan.lock'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock'); + + expect( + await conan.updateArtifacts({ + packageFileName: 'conanfile.py', + updatedDeps: [], + newPackageFileContent: '', config: { ...config, updateType: 'lockFileMaintenance' }, }), ).toEqual([ @@ -159,13 +262,13 @@ describe('modules/manager/conan/artifacts', () => { }, }, ]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject(expectedInSnapshot); }); - it('returns updated conan.lock if isLockFileMaintenance is true', async () => { - const updatedDeps = [ + it('returns updated conan.lock when updateType are empty, but isLockFileMaintenance is true', async () => { + const expectedInSnapshot = [ { - depName: 'dep', + cmd: 'conan lock create conanfile.py --lockfile=""', }, ]; @@ -178,7 +281,7 @@ describe('modules/manager/conan/artifacts', () => { expect( await conan.updateArtifacts({ packageFileName: 'conanfile.py', - updatedDeps, + updatedDeps: [], newPackageFileContent: '', config: { ...config, isLockFileMaintenance: true }, }), @@ -191,7 +294,7 @@ describe('modules/manager/conan/artifacts', () => { }, }, ]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject(expectedInSnapshot); }); it('rethrows temporary error', async () => { @@ -216,7 +319,7 @@ describe('modules/manager/conan/artifacts', () => { ).rejects.toThrow(TEMPORARY_ERROR); }); - it('returns an artifact error when conan update fails', async () => { + it('returns an artifact error when conan.lock update fails', async () => { const updatedDeps = [ { depName: 'dep', diff --git a/lib/modules/manager/conan/artifacts.ts b/lib/modules/manager/conan/artifacts.ts index 9b5520e16162a6..8f0f2ee287a651 100644 --- a/lib/modules/manager/conan/artifacts.ts +++ b/lib/modules/manager/conan/artifacts.ts @@ -3,7 +3,11 @@ import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import { exec } from '../../../util/exec'; import type { ExecOptions } from '../../../util/exec/types'; -import { findLocalSiblingOrParent, readLocalFile } from '../../../util/fs'; +import { + findLocalSiblingOrParent, + readLocalFile, + writeLocalFile, +} from '../../../util/fs'; import { getGitEnvironmentVariables } from '../../../util/git/auth'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; @@ -21,20 +25,17 @@ async function conanUpdate(conanFilePath: string): Promise { export async function updateArtifacts( updateArtifact: UpdateArtifact, ): Promise { - const { packageFileName, updatedDeps, config } = updateArtifact; + const { packageFileName, updatedDeps, newPackageFileContent, config } = + updateArtifact; logger.debug(`conan.updateArtifacts(${packageFileName})`); const isLockFileMaintenance = config.updateType === 'lockFileMaintenance' || config.isLockFileMaintenance === true; - if (!isLockFileMaintenance) { - logger.debug('conan.lock file maintenance is turned off'); - return null; - } - if (!updatedDeps?.length) { - logger.debug('No conan.file dependencies to update'); + if (updatedDeps.length === 0 && !isLockFileMaintenance) { + logger.debug('No conan.lock dependencies to update'); return null; } @@ -54,9 +55,10 @@ export async function updateArtifacts( } try { + await writeLocalFile(packageFileName, newPackageFileContent); + logger.debug('Updating ' + lockFileName); await conanUpdate(packageFileName); - logger.debug('Returning updated' + lockFileName); const newLockFileContent = await readLocalFile(lockFileName); if (!newLockFileContent) { @@ -64,6 +66,12 @@ export async function updateArtifacts( return null; } + if (existingLockFileContent === newLockFileContent) { + logger.debug(lockFileName + ' is unchanged'); + return null; + } + + logger.debug('Returning updated' + lockFileName); return [ { file: { diff --git a/lib/modules/manager/conan/readme.md b/lib/modules/manager/conan/readme.md index ed7a982eb030bb..ae3da9260bed13 100644 --- a/lib/modules/manager/conan/readme.md +++ b/lib/modules/manager/conan/readme.md @@ -3,7 +3,7 @@ The Conan package manager is disabled by default due to slowness in the Conan API. We recommend you only enable it for low volume experimental purposes until [issue #14170](https://github.com/renovatebot/renovate/issues/14170) is resolved. -Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files and update `conan.lock` appropriately if present. +Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files and also updates `conan.lock` files too if found. How it works: @@ -14,7 +14,7 @@ How it works: - and the `python_requires`, `requires` and `build_requires` variables in the `conanfile.py` format 1. Renovate resolves the dependency's version using the Conan v2 API 1. If Renovate finds an update, Renovate will update `conanfile.txt` or `conanfile.py` -1. Renovate also updates `conan.lock` basing on updated `conanfile` if `conan.lock` exists +1. Renovate also updates `conan.lock` file if exists Enabling Conan updating From d4bfafc980b8feb18fa2a708839b03f43da7d781 Mon Sep 17 00:00:00 2001 From: TobiaszSML Date: Tue, 11 Feb 2025 13:28:00 +0100 Subject: [PATCH 6/7] Distinguish the command of conanLockUpdate depending on the value of isLockFileMaintenance --- lib/modules/manager/conan/artifacts.spec.ts | 8 ++++---- lib/modules/manager/conan/artifacts.ts | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/modules/manager/conan/artifacts.spec.ts b/lib/modules/manager/conan/artifacts.spec.ts index 3d52e15f190087..cb9292159de87c 100644 --- a/lib/modules/manager/conan/artifacts.spec.ts +++ b/lib/modules/manager/conan/artifacts.spec.ts @@ -105,7 +105,7 @@ describe('modules/manager/conan/artifacts', () => { ]; const expectedInSnapshot = [ { - cmd: 'conan lock create conanfile.py --lockfile=""', + cmd: 'conan lock create conanfile.py', }, ]; @@ -137,7 +137,7 @@ describe('modules/manager/conan/artifacts', () => { ]; const expectedInSnapshot = [ { - cmd: 'conan lock create conanfile.py --lockfile=""', + cmd: 'conan lock create conanfile.py', }, ]; @@ -167,7 +167,7 @@ describe('modules/manager/conan/artifacts', () => { ]; const expectedInSnapshot = [ { - cmd: 'conan lock create conanfile.txt --lockfile=""', + cmd: 'conan lock create conanfile.txt', }, ]; @@ -204,7 +204,7 @@ describe('modules/manager/conan/artifacts', () => { ]; const expectedInSnapshot = [ { - cmd: 'conan lock create conanfile.py --lockfile=""', + cmd: 'conan lock create conanfile.py', }, ]; diff --git a/lib/modules/manager/conan/artifacts.ts b/lib/modules/manager/conan/artifacts.ts index 8f0f2ee287a651..314cde0b45a76f 100644 --- a/lib/modules/manager/conan/artifacts.ts +++ b/lib/modules/manager/conan/artifacts.ts @@ -11,8 +11,13 @@ import { import { getGitEnvironmentVariables } from '../../../util/git/auth'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; -async function conanUpdate(conanFilePath: string): Promise { - const command = `conan lock create ${quote(conanFilePath)} --lockfile=""`; +async function conanLockUpdate( + conanFilePath: string, + isLockFileMaintenance: boolean, +): Promise { + const command = + `conan lock create ${quote(conanFilePath)}` + + (isLockFileMaintenance ? ' --lockfile=""' : ''); const execOptions: ExecOptions = { extraEnv: { ...getGitEnvironmentVariables(['conan']) }, @@ -58,7 +63,7 @@ export async function updateArtifacts( await writeLocalFile(packageFileName, newPackageFileContent); logger.debug('Updating ' + lockFileName); - await conanUpdate(packageFileName); + await conanLockUpdate(packageFileName, isLockFileMaintenance); const newLockFileContent = await readLocalFile(lockFileName); if (!newLockFileContent) { From 50dd8430c34eb952d2e771ea83ae7fa2a801c6d2 Mon Sep 17 00:00:00 2001 From: TobiaszSML Date: Tue, 11 Feb 2025 14:39:55 +0100 Subject: [PATCH 7/7] Reduce log level from debug to trace --- lib/modules/manager/conan/artifacts.spec.ts | 6 +++--- lib/modules/manager/conan/artifacts.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/modules/manager/conan/artifacts.spec.ts b/lib/modules/manager/conan/artifacts.spec.ts index cb9292159de87c..e09effd20156f8 100644 --- a/lib/modules/manager/conan/artifacts.spec.ts +++ b/lib/modules/manager/conan/artifacts.spec.ts @@ -49,7 +49,7 @@ describe('modules/manager/conan/artifacts', () => { config, }), ).toBeNull(); - expect(logger.debug).toHaveBeenCalledWith( + expect(logger.trace).toHaveBeenCalledWith( 'No conan.lock dependencies to update', ); }); @@ -71,7 +71,7 @@ describe('modules/manager/conan/artifacts', () => { config, }), ).toBeNull(); - expect(logger.debug).toHaveBeenCalledWith('No conan.lock found'); + expect(logger.trace).toHaveBeenCalledWith('No conan.lock found'); }); it('returns null if conan.lock read operation failed', async () => { @@ -156,7 +156,7 @@ describe('modules/manager/conan/artifacts', () => { }), ).toBeNull(); expect(execSnapshots).toMatchObject(expectedInSnapshot); - expect(logger.debug).toHaveBeenCalledWith('conan.lock is unchanged'); + expect(logger.trace).toHaveBeenCalledWith('conan.lock is unchanged'); }); it('returns updated conan.lock for conanfile.txt', async () => { diff --git a/lib/modules/manager/conan/artifacts.ts b/lib/modules/manager/conan/artifacts.ts index 314cde0b45a76f..ab010be2e5996a 100644 --- a/lib/modules/manager/conan/artifacts.ts +++ b/lib/modules/manager/conan/artifacts.ts @@ -33,14 +33,14 @@ export async function updateArtifacts( const { packageFileName, updatedDeps, newPackageFileContent, config } = updateArtifact; - logger.debug(`conan.updateArtifacts(${packageFileName})`); + logger.trace(`conan.updateArtifacts(${packageFileName})`); const isLockFileMaintenance = config.updateType === 'lockFileMaintenance' || config.isLockFileMaintenance === true; if (updatedDeps.length === 0 && !isLockFileMaintenance) { - logger.debug('No conan.lock dependencies to update'); + logger.trace('No conan.lock dependencies to update'); return null; } @@ -49,7 +49,7 @@ export async function updateArtifacts( 'conan.lock', ); if (!lockFileName) { - logger.debug('No conan.lock found'); + logger.trace('No conan.lock found'); return null; } @@ -62,7 +62,7 @@ export async function updateArtifacts( try { await writeLocalFile(packageFileName, newPackageFileContent); - logger.debug('Updating ' + lockFileName); + logger.trace('Updating ' + lockFileName); await conanLockUpdate(packageFileName, isLockFileMaintenance); const newLockFileContent = await readLocalFile(lockFileName); @@ -72,11 +72,11 @@ export async function updateArtifacts( } if (existingLockFileContent === newLockFileContent) { - logger.debug(lockFileName + ' is unchanged'); + logger.trace(lockFileName + ' is unchanged'); return null; } - logger.debug('Returning updated' + lockFileName); + logger.trace('Returning updated' + lockFileName); return [ { file: {