diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index fb7cf289bbd9a9b637f9e3a91fc7d08c1fda7fb6..5d2a7c5f43592e2117b28ed512598dd1b1c4c86b 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -775,7 +775,7 @@ With the above config, every PR raised by Renovate will have the label `dependen ## lockFileMaintenance -This feature can be used to refresh lock files and keep them up-to-date. "Maintaining" a lock file means recreating it so that every dependency version within it is updated to the latest. Supported lock files are `package-lock.json`, `yarn.lock`, `composer.lock`, `Gemfile.lock` and `poetry.lock`. Others may be added via feature request. +This feature can be used to refresh lock files and keep them up-to-date. "Maintaining" a lock file means recreating it so that every dependency version within it is updated to the latest. Supported lock files are `package-lock.json`, `yarn.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock` and `Cargo.lock`. Others may be added via feature request. This feature is disabled by default. If you wish to enable this feature then you could add this to your configuration: diff --git a/lib/manager/cargo/__snapshots__/artifacts.spec.ts.snap b/lib/manager/cargo/__snapshots__/artifacts.spec.ts.snap index 3bbbb064cd88c11a0adef7c85638296d08e387c9..7e6b944d3ccdcafd27bc3df998212ac21ff50d00 100644 --- a/lib/manager/cargo/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/cargo/__snapshots__/artifacts.spec.ts.snap @@ -57,6 +57,29 @@ Array [ ] `; +exports[`.updateArtifacts() returns updated Cargo.lock for lockfile maintenance 1`] = ` +Array [ + Object { + "cmd": "cargo update --manifest-path Cargo.toml", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "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[`.updateArtifacts() returns updated Cargo.lock with docker 1`] = ` Array [ Object { diff --git a/lib/manager/cargo/artifacts.spec.ts b/lib/manager/cargo/artifacts.spec.ts index 92fb548a641e37f29dac5404019f3c234accc610..0e6f2b1e27714c690ac63cbd735d783735a8bbe2 100644 --- a/lib/manager/cargo/artifacts.spec.ts +++ b/lib/manager/cargo/artifacts.spec.ts @@ -85,6 +85,22 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + + it('returns updated Cargo.lock for lockfile maintenance', async () => { + git.getFile.mockResolvedValueOnce('Old Cargo.lock'); + const execSnapshots = mockExecAll(exec); + fs.readFile.mockResolvedValueOnce('New Cargo.lock' as any); + expect( + await cargo.updateArtifacts({ + packageFileName: 'Cargo.toml', + updatedDeps: [], + newPackageFileContent: '{}', + config: { ...config, updateType: 'lockFileMaintenance' }, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + it('returns updated Cargo.lock with docker', async () => { jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce(); await setExecConfig({ ...config, binarySource: BinarySource.Docker }); diff --git a/lib/manager/cargo/artifacts.ts b/lib/manager/cargo/artifacts.ts index c94ff14e40329a0b55d69f072d22bc22fc1137e9..83c2faf8a0c5741495e74f0e4c24955ff2f35c46 100644 --- a/lib/manager/cargo/artifacts.ts +++ b/lib/manager/cargo/artifacts.ts @@ -8,6 +8,44 @@ import { } from '../../util/fs'; import { UpdateArtifact, UpdateArtifactsResult } from '../common'; +async function cargoUpdate( + manifestPath: string, + packageName?: string +): Promise<void> { + let cmd = `cargo update --manifest-path ${quote(manifestPath)}`; + if (packageName) { + cmd += ` --package ${quote(packageName)}`; + } + + const execOptions: ExecOptions = { + docker: { + image: 'renovate/rust', + }, + }; + try { + await exec(cmd, execOptions); + } catch (err) /* istanbul ignore next */ { + // Two different versions of one dependency can be present in the same + // crate, and when that happens an attempt to update it with --package ${dep} + // key results in cargo exiting with error code `101` and an error mssage: + // "error: There are multiple `${dep}` packages in your project". + // + // If exception `err` was caused by this, we execute `updateAll` function + // instead of returning an error. `updateAll` function just executes + // "cargo update --manifest-path ${localPackageFileName}" without the `--package` key. + // + // If exception `err` was not caused by this, we just rethrow it. It will be caught + // by the outer try { } catch {} and processed normally. + const msgStart = 'error: There are multiple'; + if (err.code === 101 && err.stderr.startsWith(msgStart)) { + cmd = cmd.replace(/ --package.*/, ''); + await exec(cmd, execOptions); + } else { + throw err; // this is caught below + } + } +} + export async function updateArtifacts({ packageFileName, updatedDeps, @@ -15,10 +53,17 @@ export async function updateArtifacts({ config, }: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> { logger.debug(`cargo.updateArtifacts(${packageFileName})`); - if (updatedDeps === undefined || updatedDeps.length < 1) { + + const isLockFileMaintenance = config.updateType === 'lockFileMaintenance'; + + if ( + !isLockFileMaintenance && + (updatedDeps === undefined || updatedDeps.length < 1) + ) { logger.debug('No updated cargo deps - returning null'); return null; } + const lockFileName = getSiblingFileName(packageFileName, 'Cargo.lock'); const existingLockFileContent = await readLocalFile(lockFileName); if (!existingLockFileContent) { @@ -32,36 +77,10 @@ export async function updateArtifacts({ const dep = updatedDeps[i]; // Update dependency `${dep}` in Cargo.lock file corresponding to Cargo.toml file located // at ${localPackageFileName} path - let cmd = `cargo update --manifest-path ${quote( - packageFileName - )} --package ${quote(dep)}`; - const execOptions: ExecOptions = { - docker: { - image: 'renovate/rust', - }, - }; - try { - await exec(cmd, execOptions); - } catch (err) /* istanbul ignore next */ { - // Two different versions of one dependency can be present in the same - // crate, and when that happens an attempt to update it with --package ${dep} - // key results in cargo exiting with error code `101` and an error mssage: - // "error: There are multiple `${dep}` packages in your project". - // - // If exception `err` was caused by this, we execute `updateAll` function - // instead of returning an error. `updateAll` function just executes - // "cargo update --manifest-path ${localPackageFileName}" without the `--package` key. - // - // If exception `err` was not caused by this, we just rethrow it. It will be caught - // by the outer try { } catch {} and processed normally. - const msgStart = 'error: There are multiple'; - if (err.code === 101 && err.stderr.startsWith(msgStart)) { - cmd = cmd.replace(/ --package.*/, ''); - await exec(cmd, execOptions); - } else { - throw err; // this is caught below - } - } + await cargoUpdate(packageFileName, dep); + } + if (isLockFileMaintenance) { + await cargoUpdate(packageFileName); } logger.debug('Returning updated Cargo.lock'); const newCargoLockContent = await readLocalFile(lockFileName); diff --git a/lib/manager/cargo/index.ts b/lib/manager/cargo/index.ts index 6f19e76b2cd8499a898db4e953c92b872b54ba7a..e18f8527d5244c17f852ef13b128d52b91c4feb2 100644 --- a/lib/manager/cargo/index.ts +++ b/lib/manager/cargo/index.ts @@ -4,8 +4,7 @@ import { updateArtifacts } from './artifacts'; import { extractPackageFile } from './extract'; const language = LANGUAGE_RUST; -// TODO: Support this -export const supportsLockFileMaintenance = false; +export const supportsLockFileMaintenance = true; export { extractPackageFile, updateArtifacts, language }; diff --git a/lib/manager/common.ts b/lib/manager/common.ts index 35fba9818a07f01a82d4b473283aa9fdc5da7d98..64e820e9300ff7b3131fe0e7a02206ada53d16ef 100644 --- a/lib/manager/common.ts +++ b/lib/manager/common.ts @@ -43,7 +43,7 @@ export interface UpdateArtifactsConfig extends ManagerConfig { currentValue?: string; postUpdateOptions?: string[]; ignoreScripts?: boolean; - + updateType?: UpdateType; toVersion?: string; }