Files
Sebastian Poxhofer 183ee9caac fix(manager/rust-toolchain): report skipReason instead of dropping deps (#45144)
The manager emitted `logger.warn` four times during extraction and
returned `null` for content that is valid but not updatable. A
`rust-toolchain.toml` using `path` instead of `channel` is a legitimate
file, yet it failed schema validation and produced a maintainer-facing
warning.

Per the logging guidance in `docs/development/best-practices.md`, WARN
is consumed by metrics and error catching services, so it should not
fire for user content Renovate simply cannot update. The manager guide
also asks for a `skipReason` rather than silently dropping a dependency.

The schema now accepts an optional `channel` and an optional `path`, and
extraction reports:

- `invalid-version` when a channel cannot be parsed
- `path-dependency` when the file points at a local toolchain
- `unspecified-version` when neither field is set

`channel` takes precedence when both are present, since rustup rejects
that combination and `channel` is the actionable field.

Files that are not toolchain files at all still return `null`: TOML that
does not parse, TOML without a `[toolchain]` table, and legacy plain
text files that are empty or hold more than one line. The file pattern
matches any file named `rust-toolchain`, so these are treated as false
matches. All remaining logs are DEBUG.
2026-08-10 07:52:31 +00:00

87 lines
2.5 KiB
TypeScript

import { isNonEmptyString } from '@sindresorhus/is';
import { logger } from '../../../logger/index.ts';
import { newlineRegex } from '../../../util/regex.ts';
import { RustVersionDatasource } from '../../datasource/rust-version/index.ts';
import * as rustVersioning from '../../versioning/rust-release-channel/index.ts';
import type { PackageDependency, PackageFileContent } from '../types.ts';
import { RustToolchain } from './schema.ts';
export function extractPackageFile(
content: string,
packageFile: string,
): PackageFileContent | null {
logger.trace(`rust-toolchain.extractPackageFile(${packageFile})`);
// Try TOML parsing first
const parsedResult = RustToolchain.safeParse(content);
if (parsedResult.success) {
const { channel, path } = parsedResult.data.toolchain;
if (isNonEmptyString(channel)) {
return { deps: [createDependency(channel)] };
}
if (isNonEmptyString(path)) {
logger.debug(
`rust-toolchain.toml file at ${packageFile} uses a local path toolchain, which cannot be updated`,
);
return {
deps: [{ ...baseDependency(), skipReason: 'path-dependency' }],
};
}
logger.debug(
`rust-toolchain.toml file at ${packageFile} has no toolchain channel or path specified`,
);
return {
deps: [{ ...baseDependency(), skipReason: 'unspecified-version' }],
};
}
// For .toml files, TOML parsing must succeed
if (packageFile.endsWith('.toml')) {
logger.debug(
{ err: parsedResult.error, packageFile },
'Failed to parse rust-toolchain.toml file',
);
return null;
}
// Fall back to legacy format for files without .toml extension
logger.trace({ packageFile }, 'TOML parsing failed, trying legacy format');
const lines = content
.split(newlineRegex)
.map((l) => l.trim())
.filter((l) => l.length > 0);
if (lines.length !== 1) {
logger.debug(
{ packageFile },
'rust-toolchain file is empty or contains multiple lines',
);
return null;
}
return { deps: [createDependency(lines[0])] };
}
function baseDependency(): PackageDependency {
return {
depName: 'rust',
depType: 'toolchain',
datasource: RustVersionDatasource.id,
};
}
function createDependency(channel: string): PackageDependency {
const dep: PackageDependency = {
...baseDependency(),
currentValue: channel,
};
if (!rustVersioning.api.isValid(channel)) {
logger.debug(`Unsupported rust-toolchain channel value "${channel}"`);
dep.skipReason = 'invalid-version';
}
return dep;
}