mirror of https://github.com/renovatebot/renovate
429 lines
11 KiB
TypeScript
429 lines
11 KiB
TypeScript
import { lang, query as q } from 'good-enough-parser';
|
|
import { logger } from '../../../logger';
|
|
import { readLocalFile } from '../../../util/fs';
|
|
import { newlineRegex, regEx } from '../../../util/regex';
|
|
import { parseUrl } from '../../../util/url';
|
|
import { GithubReleasesDatasource } from '../../datasource/github-releases';
|
|
import { MavenDatasource } from '../../datasource/maven';
|
|
import { MAVEN_REPO } from '../../datasource/maven/common';
|
|
import { SbtPackageDatasource } from '../../datasource/sbt-package';
|
|
import {
|
|
SBT_PLUGINS_REPO,
|
|
SbtPluginDatasource,
|
|
} from '../../datasource/sbt-plugin';
|
|
import { get } from '../../versioning';
|
|
import * as mavenVersioning from '../../versioning/maven';
|
|
import * as semverVersioning from '../../versioning/semver';
|
|
import type {
|
|
ExtractConfig,
|
|
PackageDependency,
|
|
PackageFile,
|
|
PackageFileContent,
|
|
} from '../types';
|
|
import { normalizeScalaVersion, sortPackageFiles } from './util';
|
|
|
|
type Vars = Record<string, string>;
|
|
|
|
interface Ctx {
|
|
vars: Vars;
|
|
deps: PackageDependency[];
|
|
registryUrls: string[];
|
|
|
|
scalaVersion?: string;
|
|
packageFileVersion?: string;
|
|
|
|
groupId?: string;
|
|
artifactId?: string;
|
|
currentValue?: string;
|
|
|
|
currentVarName?: string;
|
|
depType?: string;
|
|
useScalaVersion?: boolean;
|
|
variableName?: string;
|
|
}
|
|
|
|
const scala = lang.createLang('scala');
|
|
|
|
const sbtVersionRegex = regEx(
|
|
'sbt\\.version *= *(?<version>\\d+\\.\\d+\\.\\d+)',
|
|
);
|
|
|
|
const sbtProxyUrlRegex = regEx(
|
|
/^\s*(?<repoName>\S+):\s+(?<proxy>https?:\/\/[\w./-]+)/,
|
|
);
|
|
|
|
const scalaVersionMatch = q
|
|
.sym<Ctx>('scalaVersion')
|
|
.op(':=')
|
|
.alt(
|
|
q.str<Ctx>((ctx, { value: scalaVersion }) => ({ ...ctx, scalaVersion })),
|
|
q.sym<Ctx>((ctx, { value: varName }) => {
|
|
const scalaVersion = ctx.vars[varName];
|
|
if (scalaVersion) {
|
|
ctx.scalaVersion = scalaVersion;
|
|
}
|
|
return ctx;
|
|
}),
|
|
)
|
|
.handler((ctx) => {
|
|
if (ctx.scalaVersion) {
|
|
const version = get(mavenVersioning.id);
|
|
|
|
let packageName = 'org.scala-lang:scala-library';
|
|
if (version.getMajor(ctx.scalaVersion) === 3) {
|
|
packageName = 'org.scala-lang:scala3-library_3';
|
|
}
|
|
|
|
const dep: PackageDependency = {
|
|
datasource: MavenDatasource.id,
|
|
depName: 'scala',
|
|
packageName,
|
|
currentValue: ctx.scalaVersion,
|
|
separateMinorPatch: true,
|
|
};
|
|
ctx.scalaVersion = normalizeScalaVersion(ctx.scalaVersion);
|
|
ctx.deps.push(dep);
|
|
}
|
|
return ctx;
|
|
});
|
|
|
|
const packageFileVersionMatch = q
|
|
.sym<Ctx>('version')
|
|
.op(':=')
|
|
.alt(
|
|
q.str<Ctx>((ctx, { value: packageFileVersion }) => ({
|
|
...ctx,
|
|
packageFileVersion,
|
|
})),
|
|
q.sym<Ctx>((ctx, { value: varName }) => {
|
|
const packageFileVersion = ctx.vars[varName];
|
|
if (packageFileVersion) {
|
|
ctx.packageFileVersion = packageFileVersion;
|
|
}
|
|
return ctx;
|
|
}),
|
|
);
|
|
|
|
const variableNameMatch = q
|
|
.sym<Ctx>((ctx, { value: varName }) => ({
|
|
...ctx,
|
|
currentVarName: varName,
|
|
}))
|
|
.opt(q.op<Ctx>(':').sym('String'));
|
|
|
|
const variableValueMatch = q.str<Ctx>((ctx, { value }) => {
|
|
ctx.vars[ctx.currentVarName!] = value;
|
|
delete ctx.currentVarName;
|
|
return ctx;
|
|
});
|
|
|
|
const assignmentMatch = q.sym<Ctx>('val').join(variableNameMatch).op('=');
|
|
|
|
const variableDefinitionMatch = q
|
|
.alt(
|
|
q.sym<Ctx>('lazy').join(assignmentMatch),
|
|
assignmentMatch,
|
|
variableNameMatch.op(':='),
|
|
)
|
|
.join(variableValueMatch);
|
|
|
|
const groupIdMatch = q.alt<Ctx>(
|
|
q.sym<Ctx>((ctx, { value: varName }) => {
|
|
const currentGroupId = ctx.vars[varName];
|
|
if (currentGroupId) {
|
|
ctx.groupId = currentGroupId;
|
|
}
|
|
return ctx;
|
|
}),
|
|
q.str<Ctx>((ctx, { value: groupId }) => ({ ...ctx, groupId })),
|
|
);
|
|
|
|
const artifactIdMatch = q.alt<Ctx>(
|
|
q.sym<Ctx>((ctx, { value: varName }) => {
|
|
const artifactId = ctx.vars[varName];
|
|
if (artifactId) {
|
|
ctx.artifactId = artifactId;
|
|
}
|
|
return ctx;
|
|
}),
|
|
q.str<Ctx>((ctx, { value: artifactId }) => ({ ...ctx, artifactId })),
|
|
);
|
|
|
|
const versionMatch = q.alt<Ctx>(
|
|
q.sym<Ctx>((ctx, { value: varName }) => {
|
|
const currentValue = ctx.vars[varName];
|
|
if (currentValue) {
|
|
ctx.currentValue = currentValue;
|
|
ctx.variableName = varName;
|
|
}
|
|
return ctx;
|
|
}),
|
|
q.str<Ctx>((ctx, { value: currentValue }) => ({ ...ctx, currentValue })),
|
|
);
|
|
|
|
const simpleDependencyMatch = groupIdMatch
|
|
.op('%')
|
|
.join(artifactIdMatch)
|
|
.op('%')
|
|
.join(versionMatch);
|
|
|
|
const versionedDependencyMatch = groupIdMatch
|
|
.op('%%')
|
|
.join(artifactIdMatch)
|
|
.handler((ctx) => ({ ...ctx, useScalaVersion: true }))
|
|
.op('%')
|
|
.join(versionMatch);
|
|
|
|
const crossDependencyMatch = groupIdMatch
|
|
.op('%%%')
|
|
.join(artifactIdMatch)
|
|
.handler((ctx) => ({ ...ctx, useScalaVersion: true }))
|
|
.op('%')
|
|
.join(versionMatch);
|
|
|
|
function depHandler(ctx: Ctx): Ctx {
|
|
const {
|
|
scalaVersion,
|
|
groupId,
|
|
artifactId,
|
|
currentValue,
|
|
useScalaVersion,
|
|
depType,
|
|
variableName,
|
|
} = ctx;
|
|
|
|
delete ctx.groupId;
|
|
delete ctx.artifactId;
|
|
delete ctx.currentValue;
|
|
delete ctx.useScalaVersion;
|
|
delete ctx.depType;
|
|
delete ctx.variableName;
|
|
|
|
const depName = `${groupId!}:${artifactId!}`;
|
|
|
|
const dep: PackageDependency = {
|
|
datasource: SbtPackageDatasource.id,
|
|
depName,
|
|
packageName:
|
|
scalaVersion && useScalaVersion ? `${depName}_${scalaVersion}` : depName,
|
|
currentValue,
|
|
};
|
|
|
|
if (depType) {
|
|
dep.depType = depType;
|
|
}
|
|
|
|
if (depType === 'plugin') {
|
|
dep.datasource = SbtPluginDatasource.id;
|
|
}
|
|
|
|
if (variableName) {
|
|
dep.groupName = variableName;
|
|
dep.variableName = variableName;
|
|
}
|
|
|
|
ctx.deps.push(dep);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
function depTypeHandler(ctx: Ctx, { value: depType }: { value: string }): Ctx {
|
|
return { ...ctx, depType };
|
|
}
|
|
|
|
const sbtPackageMatch = q
|
|
.opt<Ctx>(q.opt(q.sym<Ctx>('lazy')).sym('val').sym().op('='))
|
|
.alt(crossDependencyMatch, simpleDependencyMatch, versionedDependencyMatch)
|
|
.opt(
|
|
q.alt<Ctx>(
|
|
q.sym<Ctx>('classifier').str(depTypeHandler),
|
|
q.op<Ctx>('%').sym(depTypeHandler),
|
|
q.op<Ctx>('%').str(depTypeHandler),
|
|
),
|
|
)
|
|
.handler(depHandler);
|
|
|
|
const sbtPluginMatch = q
|
|
.sym<Ctx>(regEx(/^(?:addSbtPlugin|addCompilerPlugin)$/))
|
|
.tree({
|
|
type: 'wrapped-tree',
|
|
maxDepth: 1,
|
|
search: q
|
|
.begin<Ctx>()
|
|
.alt(simpleDependencyMatch, versionedDependencyMatch)
|
|
.end(),
|
|
})
|
|
.handler((ctx) => ({ ...ctx, depType: 'plugin' }))
|
|
.handler(depHandler);
|
|
|
|
const resolverMatch = q
|
|
.str<Ctx>()
|
|
.sym('at')
|
|
.str((ctx, { value }) => {
|
|
if (parseUrl(value)) {
|
|
ctx.registryUrls.push(value);
|
|
}
|
|
return ctx;
|
|
});
|
|
|
|
const addResolverMatch = q.sym<Ctx>('resolvers').alt(
|
|
q.op<Ctx>('+=').join(resolverMatch),
|
|
q.op<Ctx>('++=').sym('Seq').tree({
|
|
type: 'wrapped-tree',
|
|
maxDepth: 1,
|
|
search: resolverMatch,
|
|
}),
|
|
);
|
|
|
|
function registryUrlHandler(ctx: Ctx): Ctx {
|
|
for (const dep of ctx.deps) {
|
|
dep.registryUrls = [...ctx.registryUrls];
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
const query = q.tree<Ctx>({
|
|
type: 'root-tree',
|
|
maxDepth: 32,
|
|
search: q.alt<Ctx>(
|
|
scalaVersionMatch,
|
|
packageFileVersionMatch,
|
|
sbtPackageMatch,
|
|
sbtPluginMatch,
|
|
addResolverMatch,
|
|
variableDefinitionMatch,
|
|
),
|
|
postHandler: registryUrlHandler,
|
|
});
|
|
|
|
export function extractProxyUrls(
|
|
content: string,
|
|
packageFile: string,
|
|
): string[] {
|
|
const extractedProxyUrls: string[] = [];
|
|
logger.debug(`Parsing proxy repository file ${packageFile}`);
|
|
for (const line of content.split(newlineRegex)) {
|
|
const extraction = sbtProxyUrlRegex.exec(line);
|
|
if (extraction?.groups?.proxy) {
|
|
extractedProxyUrls.push(extraction.groups.proxy);
|
|
} else if (line.trim() === 'maven-central') {
|
|
extractedProxyUrls.push(MAVEN_REPO);
|
|
}
|
|
}
|
|
return extractedProxyUrls;
|
|
}
|
|
|
|
export function extractPackageFile(
|
|
content: string,
|
|
packageFile: string,
|
|
): PackageFileContent | null {
|
|
return extractPackageFileInternal(content, packageFile);
|
|
}
|
|
|
|
function extractPackageFileInternal(
|
|
content: string,
|
|
packageFile: string,
|
|
ctxScalaVersion?: string,
|
|
): PackageFileContent | null {
|
|
if (
|
|
packageFile === 'project/build.properties' ||
|
|
packageFile.endsWith('/project/build.properties')
|
|
) {
|
|
const regexResult = sbtVersionRegex.exec(content);
|
|
const sbtVersion = regexResult?.groups?.version;
|
|
const matchString = regexResult?.[0];
|
|
if (sbtVersion) {
|
|
const sbtDependency: PackageDependency = {
|
|
datasource: GithubReleasesDatasource.id,
|
|
depName: 'sbt/sbt',
|
|
packageName: 'sbt/sbt',
|
|
versioning: semverVersioning.id,
|
|
currentValue: sbtVersion,
|
|
replaceString: matchString,
|
|
extractVersion: '^v(?<version>\\S+)',
|
|
registryUrls: [],
|
|
};
|
|
|
|
return {
|
|
deps: [sbtDependency],
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
let parsedResult: Ctx | null = null;
|
|
|
|
try {
|
|
parsedResult = scala.query(content, query, {
|
|
vars: {},
|
|
deps: [],
|
|
registryUrls: [],
|
|
scalaVersion: ctxScalaVersion,
|
|
});
|
|
} catch (err) /* istanbul ignore next */ {
|
|
logger.debug({ err, packageFile }, 'Sbt parsing error');
|
|
}
|
|
|
|
if (!parsedResult) {
|
|
return null;
|
|
}
|
|
|
|
const { deps, scalaVersion, packageFileVersion } = parsedResult;
|
|
|
|
if (!deps.length) {
|
|
return null;
|
|
}
|
|
|
|
return { deps, packageFileVersion, managerData: { scalaVersion } };
|
|
}
|
|
|
|
export async function extractAllPackageFiles(
|
|
_config: ExtractConfig,
|
|
packageFiles: string[],
|
|
): Promise<PackageFile[]> {
|
|
const packages: PackageFile[] = [];
|
|
const proxyUrls: string[] = [];
|
|
let ctxScalaVersion: string | undefined;
|
|
|
|
const sortedPackageFiles = sortPackageFiles(packageFiles);
|
|
|
|
for (const packageFile of sortedPackageFiles) {
|
|
const content = await readLocalFile(packageFile, 'utf8');
|
|
if (!content) {
|
|
logger.debug({ packageFile }, 'packageFile has no content');
|
|
continue;
|
|
}
|
|
if (packageFile === 'repositories') {
|
|
const urls = extractProxyUrls(content, packageFile);
|
|
proxyUrls.push(...urls);
|
|
} else {
|
|
const pkg = extractPackageFileInternal(
|
|
content,
|
|
packageFile,
|
|
ctxScalaVersion,
|
|
);
|
|
if (pkg) {
|
|
packages.push({ deps: pkg.deps, packageFile });
|
|
if (pkg.managerData?.scalaVersion) {
|
|
ctxScalaVersion = pkg.managerData.scalaVersion;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const pkg of packages) {
|
|
for (const dep of pkg.deps) {
|
|
if (dep.datasource !== GithubReleasesDatasource.id) {
|
|
if (proxyUrls.length > 0) {
|
|
dep.registryUrls!.unshift(...proxyUrls);
|
|
} else if (dep.depType === 'plugin') {
|
|
dep.registryUrls!.unshift(SBT_PLUGINS_REPO, MAVEN_REPO);
|
|
} else {
|
|
dep.registryUrls!.unshift(MAVEN_REPO);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return packages;
|
|
}
|