mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
feat(bazel-module): Add support of maven methods (#30884)
Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
parent
d6dd092954
commit
49b7e1fc82
9 changed files with 443 additions and 24 deletions
|
@ -5,6 +5,7 @@ import { GlobalConfig } from '../../../config/global';
|
|||
import type { RepoGlobalConfig } from '../../../config/types';
|
||||
import { BazelDatasource } from '../../datasource/bazel';
|
||||
import { GithubTagsDatasource } from '../../datasource/github-tags';
|
||||
import { MavenDatasource } from '../../datasource/maven';
|
||||
import * as parser from './parser';
|
||||
import { extractPackageFile } from '.';
|
||||
|
||||
|
@ -235,5 +236,106 @@ describe('modules/manager/bazel-module/extract', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns maven.install and maven.artifact dependencies', async () => {
|
||||
const input = codeBlock`
|
||||
maven.artifact(
|
||||
artifact = "core.specs.alpha",
|
||||
exclusions = ["org.clojure:clojure"],
|
||||
group = "org.clojure",
|
||||
version = "0.2.56",
|
||||
)
|
||||
|
||||
maven.install(
|
||||
artifacts = [
|
||||
"junit:junit:4.13.2",
|
||||
"com.google.guava:guava:31.1-jre",
|
||||
],
|
||||
lock_file = "//:maven_install.json",
|
||||
repositories = [
|
||||
"https://repo1.maven.org/maven2/",
|
||||
],
|
||||
version_conflict_policy = "pinned",
|
||||
)
|
||||
`;
|
||||
const result = await extractPackageFile(input, 'MODULE.bazel');
|
||||
if (!result) {
|
||||
throw new Error('Expected a result.');
|
||||
}
|
||||
expect(result.deps).toEqual([
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
depType: 'maven_install',
|
||||
depName: 'junit:junit',
|
||||
currentValue: '4.13.2',
|
||||
registryUrls: ['https://repo1.maven.org/maven2/'],
|
||||
versioning: 'gradle',
|
||||
},
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
depType: 'maven_install',
|
||||
depName: 'com.google.guava:guava',
|
||||
currentValue: '31.1-jre',
|
||||
registryUrls: ['https://repo1.maven.org/maven2/'],
|
||||
versioning: 'gradle',
|
||||
},
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
depType: 'maven_install',
|
||||
depName: 'org.clojure:core.specs.alpha',
|
||||
currentValue: '0.2.56',
|
||||
registryUrls: ['https://repo1.maven.org/maven2/'],
|
||||
versioning: 'gradle',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns maven.install and bazel_dep dependencies together', async () => {
|
||||
const input = codeBlock`
|
||||
bazel_dep(name = "bazel_jar_jar", version = "0.1.0")
|
||||
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
|
||||
maven.install(
|
||||
artifacts = [
|
||||
"junit:junit:4.13.2",
|
||||
"com.google.guava:guava:31.1-jre",
|
||||
],
|
||||
lock_file = "//:maven_install.json",
|
||||
repositories = [
|
||||
"https://repo1.maven.org/maven2/",
|
||||
],
|
||||
version_conflict_policy = "pinned",
|
||||
)
|
||||
`;
|
||||
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: 'bazel_jar_jar',
|
||||
currentValue: '0.1.0',
|
||||
},
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
depType: 'maven_install',
|
||||
depName: 'junit:junit',
|
||||
currentValue: '4.13.2',
|
||||
registryUrls: ['https://repo1.maven.org/maven2/'],
|
||||
versioning: 'gradle',
|
||||
},
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
depType: 'maven_install',
|
||||
depName: 'com.google.guava:guava',
|
||||
currentValue: '31.1-jre',
|
||||
registryUrls: ['https://repo1.maven.org/maven2/'],
|
||||
versioning: 'gradle',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,9 +2,11 @@ import { dirname } from 'upath';
|
|||
import { logger } from '../../../logger';
|
||||
import { isNotNullOrUndefined } from '../../../util/array';
|
||||
import { LooseArray } from '../../../util/schema-utils';
|
||||
import type { PackageFileContent } from '../types';
|
||||
import type { PackageDependency, PackageFileContent } from '../types';
|
||||
import * as bazelrc from './bazelrc';
|
||||
import type { RecordFragment } from './fragments';
|
||||
import { parse } from './parser';
|
||||
import { RuleToMavenPackageDep, fillRegistryUrls } from './parser/maven';
|
||||
import { RuleToBazelModulePackageDep } from './rules';
|
||||
import * as rules from './rules';
|
||||
|
||||
|
@ -14,15 +16,28 @@ export async function extractPackageFile(
|
|||
): Promise<PackageFileContent | null> {
|
||||
try {
|
||||
const records = parse(content);
|
||||
const pfc: PackageFileContent | null = LooseArray(
|
||||
RuleToBazelModulePackageDep,
|
||||
)
|
||||
.transform(rules.toPackageDependencies)
|
||||
.transform((deps) => (deps.length ? { deps } : null))
|
||||
.parse(records);
|
||||
if (!pfc) {
|
||||
const pfc = await extractBazelPfc(records, packageFile);
|
||||
const mavenDeps = extractMavenDeps(records);
|
||||
|
||||
if (mavenDeps.length) {
|
||||
pfc.deps.push(...mavenDeps);
|
||||
}
|
||||
|
||||
return pfc.deps.length ? pfc : null;
|
||||
} catch (err) {
|
||||
logger.debug({ err, packageFile }, 'Failed to parse bazel module file.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function extractBazelPfc(
|
||||
records: RecordFragment[],
|
||||
packageFile: string,
|
||||
): Promise<PackageFileContent> {
|
||||
const pfc: PackageFileContent = LooseArray(RuleToBazelModulePackageDep)
|
||||
.transform(rules.toPackageDependencies)
|
||||
.transform((deps) => ({ deps }))
|
||||
.parse(records);
|
||||
|
||||
const registryUrls = (await bazelrc.read(dirname(packageFile)))
|
||||
// Ignore any entries for custom configurations
|
||||
|
@ -34,8 +49,10 @@ export async function extractPackageFile(
|
|||
}
|
||||
|
||||
return pfc;
|
||||
} catch (err) {
|
||||
logger.debug({ err, packageFile }, 'Failed to parse bazel module file.');
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractMavenDeps(records: RecordFragment[]): PackageDependency[] {
|
||||
return LooseArray(RuleToMavenPackageDep)
|
||||
.transform(fillRegistryUrls)
|
||||
.parse(records);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,11 @@ export const ArrayFragmentSchema = z.object({
|
|||
items: LooseArray(PrimitiveFragmentsSchema),
|
||||
isComplete: z.boolean(),
|
||||
});
|
||||
export const StringArrayFragmentSchema = z.object({
|
||||
type: z.literal('array'),
|
||||
items: LooseArray(StringFragmentSchema),
|
||||
isComplete: z.boolean(),
|
||||
});
|
||||
const ValueFragmentsSchema = z.discriminatedUnion('type', [
|
||||
StringFragmentSchema,
|
||||
BooleanFragmentSchema,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { Category } from '../../../constants';
|
||||
import { BazelDatasource } from '../../datasource/bazel';
|
||||
import { GithubTagsDatasource } from '../../datasource/github-tags';
|
||||
import { MavenDatasource } from '../../datasource/maven';
|
||||
import { extractPackageFile } from './extract';
|
||||
|
||||
export { extractPackageFile };
|
||||
|
@ -14,4 +15,5 @@ export const categories: Category[] = ['bazel'];
|
|||
export const supportedDatasources = [
|
||||
BazelDatasource.id,
|
||||
GithubTagsDatasource.id,
|
||||
MavenDatasource.id,
|
||||
];
|
||||
|
|
|
@ -66,12 +66,12 @@ describe('modules/manager/bazel-module/parser/index', () => {
|
|||
{
|
||||
rule: fragments.string('git_override'),
|
||||
module_name: fragments.string('rules_foo'),
|
||||
remote: fragments.string(
|
||||
'https://github.com/example/rules_foo.git',
|
||||
),
|
||||
commit: fragments.string(
|
||||
'6a2c2e22849b3e6b33d5ea9aa72222d4803a986a',
|
||||
),
|
||||
remote: fragments.string(
|
||||
'https://github.com/example/rules_foo.git',
|
||||
),
|
||||
},
|
||||
true,
|
||||
),
|
||||
|
@ -167,5 +167,116 @@ describe('modules/manager/bazel-module/parser/index', () => {
|
|||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('finds maven.artifact', () => {
|
||||
const input = codeBlock`
|
||||
maven.artifact(
|
||||
artifact = "core.specs.alpha",
|
||||
exclusions = ["org.clojure:clojure"],
|
||||
group = "org.clojure",
|
||||
version = "0.2.56",
|
||||
)
|
||||
|
||||
maven_1.artifact(
|
||||
artifact = "core.specs.alpha1",
|
||||
group = "org.clojure1",
|
||||
version = "0.2.561",
|
||||
)
|
||||
`;
|
||||
const res = parse(input);
|
||||
expect(res).toEqual([
|
||||
fragments.record(
|
||||
{
|
||||
rule: fragments.string('maven_artifact'),
|
||||
group: fragments.string('org.clojure'),
|
||||
artifact: fragments.string('core.specs.alpha'),
|
||||
version: fragments.string('0.2.56'),
|
||||
exclusions: fragments.array(
|
||||
[
|
||||
{
|
||||
type: 'string',
|
||||
value: 'org.clojure:clojure',
|
||||
isComplete: true,
|
||||
},
|
||||
],
|
||||
true,
|
||||
),
|
||||
},
|
||||
true,
|
||||
),
|
||||
fragments.record(
|
||||
{
|
||||
rule: fragments.string('maven_artifact'),
|
||||
group: fragments.string('org.clojure1'),
|
||||
artifact: fragments.string('core.specs.alpha1'),
|
||||
version: fragments.string('0.2.561'),
|
||||
},
|
||||
true,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('finds maven.install and maven.artifact', () => {
|
||||
const input = codeBlock`
|
||||
maven.install(
|
||||
artifacts = [
|
||||
"junit:junit:4.13.2",
|
||||
"com.google.guava:guava:31.1-jre",
|
||||
],
|
||||
repositories = [
|
||||
"https://repo1.maven.org/maven2/"
|
||||
]
|
||||
)
|
||||
|
||||
maven.artifact(
|
||||
artifact = "core.specs.alpha",
|
||||
group = "org.clojure",
|
||||
version = "0.2.56",
|
||||
)
|
||||
`;
|
||||
const res = parse(input);
|
||||
expect(res).toEqual([
|
||||
fragments.record(
|
||||
{
|
||||
rule: fragments.string('maven_install'),
|
||||
artifacts: fragments.array(
|
||||
[
|
||||
{
|
||||
type: 'string',
|
||||
value: 'junit:junit:4.13.2',
|
||||
isComplete: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
value: 'com.google.guava:guava:31.1-jre',
|
||||
isComplete: true,
|
||||
},
|
||||
],
|
||||
true,
|
||||
),
|
||||
repositories: fragments.array(
|
||||
[
|
||||
{
|
||||
type: 'string',
|
||||
value: 'https://repo1.maven.org/maven2/',
|
||||
isComplete: true,
|
||||
},
|
||||
],
|
||||
true,
|
||||
),
|
||||
},
|
||||
true,
|
||||
),
|
||||
fragments.record(
|
||||
{
|
||||
rule: fragments.string('maven_artifact'),
|
||||
group: fragments.string('org.clojure'),
|
||||
artifact: fragments.string('core.specs.alpha'),
|
||||
version: fragments.string('0.2.56'),
|
||||
},
|
||||
true,
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { lang, query as q } from 'good-enough-parser';
|
||||
import { Ctx } from '../context';
|
||||
import type { RecordFragment } from '../fragments';
|
||||
import { mavenRules } from './maven';
|
||||
import { moduleRules } from './module';
|
||||
|
||||
const rule = q.alt<Ctx>(moduleRules);
|
||||
const rule = q.alt<Ctx>(moduleRules, mavenRules);
|
||||
|
||||
const query = q.tree<Ctx>({
|
||||
type: 'root-tree',
|
||||
|
|
153
lib/modules/manager/bazel-module/parser/maven.ts
Normal file
153
lib/modules/manager/bazel-module/parser/maven.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import { query as q } from 'good-enough-parser';
|
||||
import { z } from 'zod';
|
||||
import { regEx } from '../../../../util/regex';
|
||||
import { MavenDatasource } from '../../../datasource/maven';
|
||||
import { id as versioning } from '../../../versioning/gradle';
|
||||
import type { PackageDependency } from '../../types';
|
||||
import type { Ctx } from '../context';
|
||||
import {
|
||||
RecordFragmentSchema,
|
||||
StringArrayFragmentSchema,
|
||||
StringFragmentSchema,
|
||||
} from '../fragments';
|
||||
|
||||
const artifactMethod = 'artifact';
|
||||
const installMethod = 'install';
|
||||
const commonDepType = 'maven_install';
|
||||
const mavenVariableRegex = regEx(/^maven.*/);
|
||||
const bzlmodMavenMethods = [installMethod, artifactMethod];
|
||||
const methodRegex = regEx(`^${bzlmodMavenMethods.join('|')}$`);
|
||||
|
||||
function getParsedRuleByMethod(method: string): string {
|
||||
return `maven_${method}`;
|
||||
}
|
||||
|
||||
const ArtifactSpec = z.object({
|
||||
group: z.string(),
|
||||
artifact: z.string(),
|
||||
version: z.string(),
|
||||
});
|
||||
type ArtifactSpec = z.infer<typeof ArtifactSpec>;
|
||||
|
||||
const MavenArtifactTarget = RecordFragmentSchema.extend({
|
||||
children: z.object({
|
||||
rule: StringFragmentSchema.extend({
|
||||
value: z.literal(getParsedRuleByMethod(artifactMethod)),
|
||||
}),
|
||||
artifact: StringFragmentSchema,
|
||||
group: StringFragmentSchema,
|
||||
version: StringFragmentSchema,
|
||||
}),
|
||||
}).transform(
|
||||
({ children: { rule, artifact, group, version } }): PackageDependency[] => [
|
||||
{
|
||||
datasource: MavenDatasource.id,
|
||||
versioning,
|
||||
depName: `${group.value}:${artifact.value}`,
|
||||
currentValue: version.value,
|
||||
depType: rule.value,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const MavenInstallTarget = RecordFragmentSchema.extend({
|
||||
children: z.object({
|
||||
rule: StringFragmentSchema.extend({
|
||||
value: z.literal(getParsedRuleByMethod(installMethod)),
|
||||
}),
|
||||
artifacts: StringArrayFragmentSchema.transform((artifacts) => {
|
||||
const result: ArtifactSpec[] = [];
|
||||
for (const { value } of artifacts.items) {
|
||||
const [group, artifact, version] = value.split(':');
|
||||
if (group && artifact && version) {
|
||||
result.push({ group, artifact, version });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}),
|
||||
repositories: StringArrayFragmentSchema,
|
||||
}),
|
||||
}).transform(
|
||||
({ children: { rule, artifacts, repositories } }): PackageDependency[] =>
|
||||
artifacts.map(({ group, artifact, version: currentValue }) => ({
|
||||
datasource: MavenDatasource.id,
|
||||
versioning,
|
||||
depName: `${group}:${artifact}`,
|
||||
currentValue,
|
||||
depType: rule.value,
|
||||
registryUrls: repositories.items.map((i) => i.value),
|
||||
})),
|
||||
);
|
||||
|
||||
export const RuleToMavenPackageDep = z.union([
|
||||
MavenArtifactTarget,
|
||||
MavenInstallTarget,
|
||||
]);
|
||||
|
||||
export function fillRegistryUrls(
|
||||
packageDeps: PackageDependency[][],
|
||||
): PackageDependency[] {
|
||||
const artifactRules: PackageDependency[] = [];
|
||||
const registryUrls: string[] = [];
|
||||
const result: PackageDependency[] = [];
|
||||
|
||||
// registry urls are specified only in maven.install, not in maven.artifact
|
||||
packageDeps.flat().forEach((dep) => {
|
||||
if (dep.depType === getParsedRuleByMethod(installMethod)) {
|
||||
if (Array.isArray(dep.registryUrls)) {
|
||||
registryUrls.push(...dep.registryUrls);
|
||||
result.push(dep);
|
||||
}
|
||||
} else if (dep.depType === getParsedRuleByMethod(artifactMethod)) {
|
||||
artifactRules.push(dep);
|
||||
}
|
||||
});
|
||||
|
||||
const uniqUrls = [...new Set(registryUrls)];
|
||||
|
||||
for (const artifactRule of artifactRules) {
|
||||
artifactRule.registryUrls = uniqUrls;
|
||||
artifactRule.depType = commonDepType;
|
||||
result.push(artifactRule);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const kvParams = q
|
||||
.sym<Ctx>((ctx, token) => ctx.startAttribute(token.value))
|
||||
.op('=')
|
||||
.alt(
|
||||
q.str((ctx, token) => ctx.addString(token.value)),
|
||||
q.tree({
|
||||
type: 'wrapped-tree',
|
||||
maxDepth: 1,
|
||||
startsWith: '[',
|
||||
endsWith: ']',
|
||||
postHandler: (ctx) => ctx.endArray(),
|
||||
preHandler: (ctx) => ctx.startArray(),
|
||||
search: q.many(q.str<Ctx>((ctx, token) => ctx.addString(token.value))),
|
||||
}),
|
||||
);
|
||||
|
||||
export const mavenRules = q
|
||||
.sym<Ctx>(mavenVariableRegex, (ctx, token) => {
|
||||
return ctx.startRule(token.value);
|
||||
})
|
||||
.op('.')
|
||||
.sym(methodRegex, (ctx, token) => {
|
||||
const rule = ctx.currentRecord.children.rule;
|
||||
if (rule.type === 'string') {
|
||||
rule.value = getParsedRuleByMethod(token.value);
|
||||
}
|
||||
return ctx;
|
||||
})
|
||||
.join(
|
||||
q.tree({
|
||||
type: 'wrapped-tree',
|
||||
maxDepth: 1,
|
||||
search: kvParams,
|
||||
postHandler: (ctx) => ctx.endRule(),
|
||||
}),
|
||||
);
|
|
@ -16,6 +16,7 @@ const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`);
|
|||
/**
|
||||
* Matches key-value pairs:
|
||||
* - `name = "foobar"`
|
||||
* - `name = True`
|
||||
**/
|
||||
const kvParams = q
|
||||
.sym<Ctx>((ctx, token) => ctx.startAttribute(token.value))
|
||||
|
@ -32,6 +33,6 @@ export const moduleRules = q
|
|||
type: 'wrapped-tree',
|
||||
maxDepth: 1,
|
||||
search: kvParams,
|
||||
postHandler: (ctx, tree) => ctx.endRule(),
|
||||
postHandler: (ctx) => ctx.endRule(),
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -1 +1,28 @@
|
|||
The `bazel-module` manager can update [Bazel module (bzlmod)](https://bazel.build/external/module) enabled workspaces.
|
||||
|
||||
It also takes care about maven artifacts initalized with [bzlmod](https://github.com/bazelbuild/rules_jvm_external/blob/master/docs/bzlmod.md). For simplicity the name of extension variable is limited to `maven*`. E.g.:
|
||||
|
||||
```
|
||||
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
```
|
||||
|
||||
```
|
||||
maven_1 = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
|
||||
```
|
||||
|
||||
Both `install` and `artifact` methods are supported:
|
||||
|
||||
```
|
||||
maven.install(
|
||||
artifacts = [
|
||||
"org.seleniumhq.selenium:selenium-java:4.4.0",
|
||||
],
|
||||
)
|
||||
|
||||
maven.artifact(
|
||||
artifact = "javapoet",
|
||||
group = "com.squareup",
|
||||
neverlink = True,
|
||||
version = "1.11.1",
|
||||
)
|
||||
```
|
||||
|
|
Loading…
Reference in a new issue