Skip to content

Commit

Permalink
feat(bazel-modules): support single_version_override (#22610)
Browse files Browse the repository at this point in the history
Co-authored-by: Rhys Arkins <rhys@arkins.net>
  • Loading branch information
cgrindel and rarkins authored Jun 15, 2023
1 parent 0be6dba commit ad61b6c
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 39 deletions.
58 changes: 58 additions & 0 deletions lib/modules/manager/bazel-module/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
]);
});
});
});
31 changes: 31 additions & 0 deletions lib/modules/manager/bazel-module/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
]);
});
});
});
1 change: 1 addition & 0 deletions lib/modules/manager/bazel-module/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const supportedRules = [
'bazel_dep',
'git_override',
'local_path_override',
'single_version_override',
];
const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`);

Expand Down
99 changes: 69 additions & 30 deletions lib/modules/manager/bazel-module/rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import * as fragments from './fragments';
import {
BasePackageDep,
BazelModulePackageDep,
MergePackageDep,
OverridePackageDep,
RuleToBazelModulePackageDep,
overrideToPackageDependency,
bazelModulePackageDepToPackageDependency,
processModulePkgDeps,
toPackageDependencies,
} from './rules';

const customRegistryUrl = 'https://example.com/custom_registry';

const bazelDepPkgDep: BasePackageDep = {
datasource: BazelDatasource.id,
depType: 'bazel_dep',
Expand Down Expand Up @@ -46,6 +49,27 @@ const localPathOverridePkgDep: OverridePackageDep = {
skipReason: 'unsupported-datasource',
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('RuleToBazelModulePackageDep', () => {
Expand Down Expand Up @@ -76,14 +100,27 @@ describe('modules/manager/bazel-module/rules', () => {
module_name: fragments.string('rules_foo'),
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`
msg | a | exp
${'bazel_dep'} | ${bazelDepWithoutDevDep} | ${bazelDepPkgDep}
${'git_override, GitHub host'} | ${gitOverrideWithGihubHost} | ${gitOverrideForGithubPkgDep}
${'git_override, unsupported host'} | ${gitOverrideWithUnsupportedHost} | ${gitOverrideForUnsupportedPkgDep}
${'archive_override'} | ${archiveOverride} | ${archiveOverridePkgDep}
${'local_path_override'} | ${localPathOverride} | ${localPathOverridePkgDep}
msg | a | exp
${'bazel_dep'} | ${bazelDepWithoutDevDep} | ${bazelDepPkgDep}
${'git_override, GitHub host'} | ${gitOverrideWithGihubHost} | ${gitOverrideForGithubPkgDep}
${'git_override, unsupported host'} | ${gitOverrideWithUnsupportedHost} | ${gitOverrideForUnsupportedPkgDep}
${'archive_override'} | ${archiveOverride} | ${archiveOverridePkgDep}
${'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 }) => {
const pkgDep = RuleToBazelModulePackageDep.parse(a);
expect(pkgDep).toEqual(exp);
Expand All @@ -94,43 +131,45 @@ describe('modules/manager/bazel-module/rules', () => {
const expectedBazelDepNoOverrides: PackageDependency[] = [bazelDepPkgDep];
const expectedBazelDepAndGitOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { skipReason: 'git-dependency' }),
overrideToPackageDependency(gitOverrideForGithubPkgDep),
bazelModulePackageDepToPackageDependency(gitOverrideForGithubPkgDep),
];
const expectedBazelDepAndSingleVersionOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, {
skipReason: 'is-pinned',
registryUrls: [customRegistryUrl],
}),
bazelModulePackageDepToPackageDependency(singleVersionOverridePkgDep),
];
const expectedBazelDepAndArchiveOverride: PackageDependency[] = [
deepmerge(bazelDepPkgDep, { skipReason: 'file-dependency' }),
overrideToPackageDependency(archiveOverridePkgDep),
bazelModulePackageDepToPackageDependency(archiveOverridePkgDep),
];
const expectedBazelDepAndLocalPathOverride: PackageDependency[] = [
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`
msg | a | exp
${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides}
${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride}
${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]}
${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride}
${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride}
`('with $msg', ({ a, exp }) => {
msg | a | exp
${'bazel_dep, no overrides'} | ${[bazelDepPkgDep]} | ${expectedBazelDepNoOverrides}
${'bazel_dep & git_override'} | ${[bazelDepPkgDep, gitOverrideForGithubPkgDep]} | ${expectedBazelDepAndGitOverride}
${'git_override, no bazel_dep'} | ${[gitOverrideForGithubPkgDep]} | ${[]}
${'bazel_dep & archive_override'} | ${[bazelDepPkgDep, archiveOverridePkgDep]} | ${expectedBazelDepAndArchiveOverride}
${'bazel_dep & local_path_override'} | ${[bazelDepPkgDep, localPathOverridePkgDep]} | ${expectedBazelDepAndLocalPathOverride}
${'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);
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', () => {
it('returns an empty array if the input is an empty array', () => {
expect(processModulePkgDeps([])).toHaveLength(0);
Expand Down
82 changes: 73 additions & 9 deletions lib/modules/manager/bazel-module/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,49 @@ export interface BasePackageDep extends PackageDependency {
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 {
// This value is set as the skipReason on the bazel_dep PackageDependency.
bazelDepSkipReason: SkipReason;
}

export type BazelModulePackageDep = BasePackageDep | OverridePackageDep;
export type BazelModulePackageDep =
| BasePackageDep
| OverridePackageDep
| MergePackageDep;

function isOverride(value: BazelModulePackageDep): value is OverridePackageDep {
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
// 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
// be surprising to someone outside the bazel-module code to see the extra
// properties.
export function overrideToPackageDependency(
override: OverridePackageDep
export function bazelModulePackageDepToPackageDependency(
bmpd: BazelModulePackageDep
): PackageDependency {
const copy: Partial<OverridePackageDep> = { ...override };
delete copy.bazelDepSkipReason;
const copy: BazelModulePackageDep = structuredClone(bmpd);
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;
}

Expand Down Expand Up @@ -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({
children: z.object({
rule: StringFragmentSchema.extend({
Expand Down Expand Up @@ -117,6 +173,7 @@ const UnsupportedOverrideToPackageDep = RecordFragmentSchema.extend({
export const RuleToBazelModulePackageDep = z.union([
BazelDepToPackageDep,
GitOverrideToPackageDep,
SingleVersionOverrideToPackageDep,
UnsupportedOverrideToPackageDep,
]);

Expand Down Expand Up @@ -151,7 +208,14 @@ export function processModulePkgDeps(
logger.debug(`A 'bazel_dep' was not found for '${moduleName}'.`);
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);
// 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.
Expand All @@ -164,8 +228,8 @@ export function processModulePkgDeps(
return deps;
}
const override = overrides[0];
deps.push(overrideToPackageDependency(override));
bazelDep.skipReason = override.bazelDepSkipReason;
deps.push(bazelModulePackageDepToPackageDependency(override));
bazelDepOut.skipReason = override.bazelDepSkipReason;
return deps;
}

Expand Down

0 comments on commit ad61b6c

Please sign in to comment.