feat(bazel-modules): support single_version_override (#22610)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
Chuck Grindel 2023-06-15 09:50:49 -06:00 committed by GitHub
parent 0be6dba296
commit ad61b6c875
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 232 additions and 39 deletions

View file

@ -180,5 +180,63 @@ describe('modules/manager/bazel-module/extract', () => {
]) ])
); );
}); });
it('returns bazel_dep and single_version_override dependencies if a version is specified', async () => {
const input = codeBlock`
bazel_dep(name = "rules_foo", version = "1.2.3")
single_version_override(
module_name = "rules_foo",
version = "1.2.3",
registry = "https://example.com/custom_registry",
)
`;
const result = await extractPackageFile(input, 'MODULE.bazel');
if (!result) {
throw new Error('Expected a result.');
}
expect(result.deps).toHaveLength(2);
expect(result.deps).toEqual(
expect.arrayContaining([
{
datasource: BazelDatasource.id,
depType: 'bazel_dep',
depName: 'rules_foo',
currentValue: '1.2.3',
skipReason: 'is-pinned',
registryUrls: ['https://example.com/custom_registry'],
},
{
depType: 'single_version_override',
depName: 'rules_foo',
currentValue: '1.2.3',
skipReason: 'ignored',
registryUrls: ['https://example.com/custom_registry'],
},
])
);
});
it('returns bazel_dep dependency if single_version_override does not have a version', async () => {
const input = codeBlock`
bazel_dep(name = "rules_foo", version = "1.2.3")
single_version_override(
module_name = "rules_foo",
registry = "https://example.com/custom_registry",
)
`;
const result = await extractPackageFile(input, 'MODULE.bazel');
if (!result) {
throw new Error('Expected a result.');
}
expect(result.deps).toEqual([
{
datasource: BazelDatasource.id,
depType: 'bazel_dep',
depName: 'rules_foo',
currentValue: '1.2.3',
registryUrls: ['https://example.com/custom_registry'],
},
]);
});
}); });
}); });

View file

@ -136,5 +136,36 @@ describe('modules/manager/bazel-module/parser', () => {
), ),
]); ]);
}); });
it('finds single_version_override', () => {
const input = codeBlock`
bazel_dep(name = "rules_foo", version = "1.2.3")
single_version_override(
module_name = "rules_foo",
version = "1.2.3",
registry = "https://example.com/custom_registry",
)
`;
const res = parse(input);
expect(res).toEqual([
fragments.record(
{
rule: fragments.string('bazel_dep'),
name: fragments.string('rules_foo'),
version: fragments.string('1.2.3'),
},
true
),
fragments.record(
{
rule: fragments.string('single_version_override'),
module_name: fragments.string('rules_foo'),
version: fragments.string('1.2.3'),
registry: fragments.string('https://example.com/custom_registry'),
},
true
),
]);
});
}); });
}); });

View file

@ -10,6 +10,7 @@ const supportedRules = [
'bazel_dep', 'bazel_dep',
'git_override', 'git_override',
'local_path_override', 'local_path_override',
'single_version_override',
]; ];
const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`); const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`);

View file

@ -6,13 +6,16 @@ import * as fragments from './fragments';
import { import {
BasePackageDep, BasePackageDep,
BazelModulePackageDep, BazelModulePackageDep,
MergePackageDep,
OverridePackageDep, OverridePackageDep,
RuleToBazelModulePackageDep, RuleToBazelModulePackageDep,
overrideToPackageDependency, bazelModulePackageDepToPackageDependency,
processModulePkgDeps, processModulePkgDeps,
toPackageDependencies, toPackageDependencies,
} from './rules'; } from './rules';
const customRegistryUrl = 'https://example.com/custom_registry';
const bazelDepPkgDep: BasePackageDep = { const bazelDepPkgDep: BasePackageDep = {
datasource: BazelDatasource.id, datasource: BazelDatasource.id,
depType: 'bazel_dep', depType: 'bazel_dep',
@ -46,6 +49,27 @@ const localPathOverridePkgDep: OverridePackageDep = {
skipReason: 'unsupported-datasource', skipReason: 'unsupported-datasource',
bazelDepSkipReason: 'local-dependency', bazelDepSkipReason: 'local-dependency',
}; };
const singleVersionOverridePkgDep: OverridePackageDep & MergePackageDep = {
depType: 'single_version_override',
depName: 'rules_foo',
skipReason: 'ignored',
bazelDepSkipReason: 'is-pinned',
currentValue: '1.2.3',
bazelDepMergeFields: ['registryUrls'],
registryUrls: [customRegistryUrl],
};
const singleVersionOverrideWithRegistryPkgDep: MergePackageDep = {
depType: 'single_version_override',
depName: 'rules_foo',
skipReason: 'ignored',
bazelDepMergeFields: ['registryUrls'],
registryUrls: [customRegistryUrl],
};
const singleVersionOverrideWithoutVersionAndRegistryPkgDep: BasePackageDep = {
depType: 'single_version_override',
depName: 'rules_foo',
skipReason: 'ignored',
};
describe('modules/manager/bazel-module/rules', () => { describe('modules/manager/bazel-module/rules', () => {
describe('RuleToBazelModulePackageDep', () => { describe('RuleToBazelModulePackageDep', () => {
@ -76,14 +100,27 @@ describe('modules/manager/bazel-module/rules', () => {
module_name: fragments.string('rules_foo'), module_name: fragments.string('rules_foo'),
path: fragments.string('/path/to/module'), path: fragments.string('/path/to/module'),
}); });
const singleVersionOverride = fragments.record({
rule: fragments.string('single_version_override'),
module_name: fragments.string('rules_foo'),
version: fragments.string('1.2.3'),
registry: fragments.string(customRegistryUrl),
});
const singleVersionOverrideWithRegistry = fragments.record({
rule: fragments.string('single_version_override'),
module_name: fragments.string('rules_foo'),
registry: fragments.string(customRegistryUrl),
});
it.each` it.each`
msg | a | exp msg | a | exp
${'bazel_dep'} | ${bazelDepWithoutDevDep} | ${bazelDepPkgDep} ${'bazel_dep'} | ${bazelDepWithoutDevDep} | ${bazelDepPkgDep}
${'git_override, GitHub host'} | ${gitOverrideWithGihubHost} | ${gitOverrideForGithubPkgDep} ${'git_override, GitHub host'} | ${gitOverrideWithGihubHost} | ${gitOverrideForGithubPkgDep}
${'git_override, unsupported host'} | ${gitOverrideWithUnsupportedHost} | ${gitOverrideForUnsupportedPkgDep} ${'git_override, unsupported host'} | ${gitOverrideWithUnsupportedHost} | ${gitOverrideForUnsupportedPkgDep}
${'archive_override'} | ${archiveOverride} | ${archiveOverridePkgDep} ${'archive_override'} | ${archiveOverride} | ${archiveOverridePkgDep}
${'local_path_override'} | ${localPathOverride} | ${localPathOverridePkgDep} ${'local_path_override'} | ${localPathOverride} | ${localPathOverridePkgDep}
${'single_version_override with version and registry'} | ${singleVersionOverride} | ${singleVersionOverridePkgDep}
${'single_version_override with registry'} | ${singleVersionOverrideWithRegistry} | ${singleVersionOverrideWithRegistryPkgDep}
`('.parse() with $msg', ({ a, exp }) => { `('.parse() with $msg', ({ a, exp }) => {
const pkgDep = RuleToBazelModulePackageDep.parse(a); const pkgDep = RuleToBazelModulePackageDep.parse(a);
expect(pkgDep).toEqual(exp); expect(pkgDep).toEqual(exp);
@ -94,43 +131,45 @@ describe('modules/manager/bazel-module/rules', () => {
const expectedBazelDepNoOverrides: PackageDependency[] = [bazelDepPkgDep]; const expectedBazelDepNoOverrides: PackageDependency[] = [bazelDepPkgDep];
const expectedBazelDepAndGitOverride: PackageDependency[] = [ const expectedBazelDepAndGitOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { skipReason: 'git-dependency' }), deepmerge(bazelDepPkgDep, { skipReason: 'git-dependency' }),
overrideToPackageDependency(gitOverrideForGithubPkgDep), bazelModulePackageDepToPackageDependency(gitOverrideForGithubPkgDep),
];
const expectedBazelDepAndSingleVersionOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, {
skipReason: 'is-pinned',
registryUrls: [customRegistryUrl],
}),
bazelModulePackageDepToPackageDependency(singleVersionOverridePkgDep),
]; ];
const expectedBazelDepAndArchiveOverride: PackageDependency[] = [ const expectedBazelDepAndArchiveOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { skipReason: 'file-dependency' }), deepmerge(bazelDepPkgDep, { skipReason: 'file-dependency' }),
overrideToPackageDependency(archiveOverridePkgDep), bazelModulePackageDepToPackageDependency(archiveOverridePkgDep),
]; ];
const expectedBazelDepAndLocalPathOverride: PackageDependency[] = [ const expectedBazelDepAndLocalPathOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { skipReason: 'local-dependency' }), deepmerge(bazelDepPkgDep, { skipReason: 'local-dependency' }),
overrideToPackageDependency(localPathOverridePkgDep), bazelModulePackageDepToPackageDependency(localPathOverridePkgDep),
];
// If a registry is specified and a version is not specified for a
// single_version_override, it is merely providing a registry URL for the bazel_dep.
const expectedBazelDepWithRegistry: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { registryUrls: [customRegistryUrl] }),
]; ];
it.each` it.each`
msg | a | exp msg | a | exp
${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides} ${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides}
${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride} ${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride}
${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]} ${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]}
${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride} ${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride}
${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride} ${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride}
`('with $msg', ({ a, exp }) => { ${'single_version_override, with version and registry'} | ${[bazelDepPkgDep, singleVersionOverridePkgDep]} | ${expectedBazelDepAndSingleVersionOverride}
${'single_version_override, with registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithRegistryPkgDep]} | ${expectedBazelDepWithRegistry}
${'single_version_override, without version and registry'} | ${[bazelDepPkgDep, singleVersionOverrideWithoutVersionAndRegistryPkgDep]} | ${[bazelDepPkgDep]}
`('with $msg', ({ msg, a, exp }) => {
const result = toPackageDependencies(a); const result = toPackageDependencies(a);
expect(result).toEqual(exp); expect(result).toEqual(exp);
}); });
}); });
describe('.overrideToPackageDependency()', () => {
it('removes the properties specific to OverridePackageDep', () => {
const result = overrideToPackageDependency(gitOverrideForGithubPkgDep);
expect(result).toEqual({
datasource: GithubTagsDatasource.id,
depType: 'git_override',
depName: 'rules_foo',
packageName: 'example/rules_foo',
currentDigest: '850cb49c8649e463b80ef7984e7c744279746170',
});
});
});
describe('.processModulePkgDeps', () => { describe('.processModulePkgDeps', () => {
it('returns an empty array if the input is an empty array', () => { it('returns an empty array if the input is an empty array', () => {
expect(processModulePkgDeps([])).toHaveLength(0); expect(processModulePkgDeps([])).toHaveLength(0);

View file

@ -16,27 +16,49 @@ export interface BasePackageDep extends PackageDependency {
depName: string; depName: string;
} }
type BasePackageDepMergeKeys = Extract<keyof BasePackageDep, 'registryUrls'>;
export interface MergePackageDep extends BasePackageDep {
// The fields that should be copied from this struct to the bazel_dep
// PackageDependency.
bazelDepMergeFields: BasePackageDepMergeKeys[];
}
export interface OverridePackageDep extends BasePackageDep { export interface OverridePackageDep extends BasePackageDep {
// This value is set as the skipReason on the bazel_dep PackageDependency. // This value is set as the skipReason on the bazel_dep PackageDependency.
bazelDepSkipReason: SkipReason; bazelDepSkipReason: SkipReason;
} }
export type BazelModulePackageDep = BasePackageDep | OverridePackageDep; export type BazelModulePackageDep =
| BasePackageDep
| OverridePackageDep
| MergePackageDep;
function isOverride(value: BazelModulePackageDep): value is OverridePackageDep { function isOverride(value: BazelModulePackageDep): value is OverridePackageDep {
return 'bazelDepSkipReason' in value; return 'bazelDepSkipReason' in value;
} }
function isMerge(value: BazelModulePackageDep): value is MergePackageDep {
return 'bazelDepMergeFields' in value;
}
// This function exists to remove properties that are specific to // This function exists to remove properties that are specific to
// OverridePackageDep. In theory, there is no harm in leaving the properties // BazelModulePackageDep. In theory, there is no harm in leaving the properties
// as it does not invalidate the PackageDependency interface. However, it might // as it does not invalidate the PackageDependency interface. However, it might
// be surprising to someone outside the bazel-module code to see the extra // be surprising to someone outside the bazel-module code to see the extra
// properties. // properties.
export function overrideToPackageDependency( export function bazelModulePackageDepToPackageDependency(
override: OverridePackageDep bmpd: BazelModulePackageDep
): PackageDependency { ): PackageDependency {
const copy: Partial<OverridePackageDep> = { ...override }; const copy: BazelModulePackageDep = structuredClone(bmpd);
delete copy.bazelDepSkipReason; if (isOverride(copy)) {
const partial = copy as Partial<OverridePackageDep>;
delete partial.bazelDepSkipReason;
}
if (isMerge(copy)) {
const partial = copy as Partial<MergePackageDep>;
delete partial.bazelDepMergeFields;
}
return copy; return copy;
} }
@ -87,6 +109,40 @@ const GitOverrideToPackageDep = RecordFragmentSchema.extend({
} }
); );
const SingleVersionOverrideToPackageDep = RecordFragmentSchema.extend({
children: z.object({
rule: StringFragmentSchema.extend({
value: z.literal('single_version_override'),
}),
module_name: StringFragmentSchema,
version: StringFragmentSchema.optional(),
registry: StringFragmentSchema.optional(),
}),
}).transform(
({
children: { rule, module_name: moduleName, version, registry },
}): BasePackageDep => {
const base: BasePackageDep = {
depType: rule.value,
depName: moduleName.value,
skipReason: 'ignored',
};
// If a version is specified, then add a skipReason to bazel_dep
if (version) {
const override = base as OverridePackageDep;
override.bazelDepSkipReason = 'is-pinned';
override.currentValue = version.value;
}
// If a registry is specified, then merge it into the bazel_dep
if (registry) {
const merge = base as MergePackageDep;
merge.bazelDepMergeFields = ['registryUrls'];
merge.registryUrls = [registry.value];
}
return base;
}
);
const UnsupportedOverrideToPackageDep = RecordFragmentSchema.extend({ const UnsupportedOverrideToPackageDep = RecordFragmentSchema.extend({
children: z.object({ children: z.object({
rule: StringFragmentSchema.extend({ rule: StringFragmentSchema.extend({
@ -117,6 +173,7 @@ const UnsupportedOverrideToPackageDep = RecordFragmentSchema.extend({
export const RuleToBazelModulePackageDep = z.union([ export const RuleToBazelModulePackageDep = z.union([
BazelDepToPackageDep, BazelDepToPackageDep,
GitOverrideToPackageDep, GitOverrideToPackageDep,
SingleVersionOverrideToPackageDep,
UnsupportedOverrideToPackageDep, UnsupportedOverrideToPackageDep,
]); ]);
@ -151,7 +208,14 @@ export function processModulePkgDeps(
logger.debug(`A 'bazel_dep' was not found for '${moduleName}'.`); logger.debug(`A 'bazel_dep' was not found for '${moduleName}'.`);
return []; return [];
} }
const deps: PackageDependency[] = [bazelDep]; // Create a new bazelDep that will be modified. We do not want to change the
// input.
const bazelDepOut = { ...bazelDep };
const deps: PackageDependency[] = [bazelDepOut];
const merges = packageDeps.filter(isMerge);
for (const merge of merges) {
merge.bazelDepMergeFields.forEach((k) => (bazelDepOut[k] = merge[k]));
}
const overrides = packageDeps.filter(isOverride); const overrides = packageDeps.filter(isOverride);
// It is an error for more than one override to exist for a module. We will // It is an error for more than one override to exist for a module. We will
// ignore the overrides if there is more than one. // ignore the overrides if there is more than one.
@ -164,8 +228,8 @@ export function processModulePkgDeps(
return deps; return deps;
} }
const override = overrides[0]; const override = overrides[0];
deps.push(overrideToPackageDependency(override)); deps.push(bazelModulePackageDepToPackageDependency(override));
bazelDep.skipReason = override.bazelDepSkipReason; bazelDepOut.skipReason = override.bazelDepSkipReason;
return deps; return deps;
} }