fix(manager/gradle): ignore catalog deps without version (#11621)

* fix(manager/gradle): support catalog modules without version

* fix: more ignorable deps

* chore: fix type

Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
Michael Kriese 2021-09-07 19:51:39 +02:00 committed by GitHub
parent 1150b8dc3f
commit 87d26472a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 217 additions and 68 deletions

View file

@ -7,6 +7,9 @@ okHttp = "com.squareup.okhttp3:okhttp:4.9.0"
okio = { module = "com.squareup.okio:okio", version = "2.8.0" } okio = { module = "com.squareup.okio:okio", version = "2.8.0" }
picasso = { group = "com.squareup.picasso", name = "picasso", version = "2.5.1" } picasso = { group = "com.squareup.picasso", name = "picasso", version = "2.5.1" }
retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
google-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
google-firebase-messaging = "com.google.firebase:firebase-messaging"
[plugins] [plugins]
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "1.5.21" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "1.5.21" }

View file

@ -1,5 +1,6 @@
import { extractAllPackageFiles } from '..'; import { extractAllPackageFiles } from '..';
import { fs, loadFixture } from '../../../../test/util'; import { fs, loadFixture } from '../../../../test/util';
import type { ExtractConfig } from '../../types';
jest.mock('../../../util/fs'); jest.mock('../../../util/fs');
@ -24,7 +25,7 @@ describe('manager/gradle/shallow/extract', () => {
'build.gradle': '', 'build.gradle': '',
}); });
const res = await extractAllPackageFiles({} as never, [ const res = await extractAllPackageFiles({} as ExtractConfig, [
'build.gradle', 'build.gradle',
'gradle.properties', 'gradle.properties',
]); ]);
@ -39,7 +40,7 @@ describe('manager/gradle/shallow/extract', () => {
'settings.gradle': null, 'settings.gradle': null,
}); });
const res = await extractAllPackageFiles({} as never, [ const res = await extractAllPackageFiles({} as ExtractConfig, [
'build.gradle', 'build.gradle',
'gradle.properties', 'gradle.properties',
'settings.gradle', 'settings.gradle',
@ -75,7 +76,7 @@ describe('manager/gradle/shallow/extract', () => {
'settings.gradle': null, 'settings.gradle': null,
}); });
const res = await extractAllPackageFiles({} as never, [ const res = await extractAllPackageFiles({} as ExtractConfig, [
'build.gradle', 'build.gradle',
'gradle.properties', 'gradle.properties',
'settings.gradle', 'settings.gradle',
@ -114,7 +115,7 @@ describe('manager/gradle/shallow/extract', () => {
'settings.gradle': null, 'settings.gradle': null,
}); });
const res = await extractAllPackageFiles({} as never, [ const res = await extractAllPackageFiles({} as ExtractConfig, [
'build.gradle', 'build.gradle',
'gradle.properties', 'gradle.properties',
'settings.gradle', 'settings.gradle',
@ -156,7 +157,10 @@ describe('manager/gradle/shallow/extract', () => {
mockFs(fsMock); mockFs(fsMock);
const res = await extractAllPackageFiles({} as never, Object.keys(fsMock)); const res = await extractAllPackageFiles(
{} as ExtractConfig,
Object.keys(fsMock)
);
expect(res).toMatchObject([ expect(res).toMatchObject([
{ packageFile: 'gradle.properties', deps: [] }, { packageFile: 'gradle.properties', deps: [] },
@ -187,7 +191,10 @@ describe('manager/gradle/shallow/extract', () => {
mockFs(fsMock); mockFs(fsMock);
const res = await extractAllPackageFiles({} as never, Object.keys(fsMock)); const res = await extractAllPackageFiles(
{} as ExtractConfig,
Object.keys(fsMock)
);
expect(res).toMatchObject([ expect(res).toMatchObject([
{ {
@ -218,7 +225,10 @@ describe('manager/gradle/shallow/extract', () => {
'gradle/libs.versions.toml': tomlFile, 'gradle/libs.versions.toml': tomlFile,
}; };
mockFs(fsMock); mockFs(fsMock);
const res = await extractAllPackageFiles({} as never, Object.keys(fsMock)); const res = await extractAllPackageFiles(
{} as ExtractConfig,
Object.keys(fsMock)
);
expect(res).toMatchObject([ expect(res).toMatchObject([
{ {
packageFile: 'gradle/libs.versions.toml', packageFile: 'gradle/libs.versions.toml',
@ -302,7 +312,10 @@ describe('manager/gradle/shallow/extract', () => {
'gradle/libs.versions.toml': tomlFile, 'gradle/libs.versions.toml': tomlFile,
}; };
mockFs(fsMock); mockFs(fsMock);
const res = await extractAllPackageFiles({} as never, Object.keys(fsMock)); const res = await extractAllPackageFiles(
{} as ExtractConfig,
Object.keys(fsMock)
);
expect(res).toMatchObject([ expect(res).toMatchObject([
{ {
packageFile: 'gradle/libs.versions.toml', packageFile: 'gradle/libs.versions.toml',
@ -343,6 +356,27 @@ describe('manager/gradle/shallow/extract', () => {
packageFile: 'gradle/libs.versions.toml', packageFile: 'gradle/libs.versions.toml',
}, },
}, },
{
depName: 'google-firebase-analytics',
managerData: {
packageFile: 'gradle/libs.versions.toml',
},
skipReason: 'no-version',
},
{
depName: 'google-firebase-crashlytics',
managerData: {
packageFile: 'gradle/libs.versions.toml',
},
skipReason: 'no-version',
},
{
depName: 'google-firebase-messaging',
managerData: {
packageFile: 'gradle/libs.versions.toml',
},
skipReason: 'no-version',
},
{ {
depName: 'org.jetbrains.kotlin.jvm', depName: 'org.jetbrains.kotlin.jvm',
depType: 'plugin', depType: 'plugin',
@ -351,7 +385,7 @@ describe('manager/gradle/shallow/extract', () => {
lookupName: lookupName:
'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin', 'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin',
managerData: { managerData: {
fileReplacePosition: 415, fileReplacePosition: 661,
packageFile: 'gradle/libs.versions.toml', packageFile: 'gradle/libs.versions.toml',
}, },
registryUrls: [ registryUrls: [
@ -386,7 +420,10 @@ describe('manager/gradle/shallow/extract', () => {
'gradle/libs.versions.toml': tomlFile, 'gradle/libs.versions.toml': tomlFile,
}; };
mockFs(fsMock); mockFs(fsMock);
const res = await extractAllPackageFiles({} as never, Object.keys(fsMock)); const res = await extractAllPackageFiles(
{} as ExtractConfig,
Object.keys(fsMock)
);
expect(res).toBeNull(); expect(res).toBeNull();
}); });
}); });

View file

@ -67,12 +67,8 @@ export async function extractAllPackageFiles(
updateVars(vars); updateVars(vars);
extractedDeps.push(...deps); extractedDeps.push(...deps);
} else if (isTOMLFile(packageFile)) { } else if (isTOMLFile(packageFile)) {
try {
const updatesFromCatalog = parseCatalog(packageFile, content); const updatesFromCatalog = parseCatalog(packageFile, content);
extractedDeps.push(...updatesFromCatalog); extractedDeps.push(...updatesFromCatalog);
} catch (error) {
logger.warn({ error }, 'TOML parsing error');
}
} else if (isGradleFile(packageFile)) { } else if (isGradleFile(packageFile)) {
const vars = getVars(registry, dir); const vars = getVars(registry, dir);
const { const {
@ -89,9 +85,9 @@ export async function extractAllPackageFiles(
updateVars(gradleVars); updateVars(gradleVars);
extractedDeps.push(...deps); extractedDeps.push(...deps);
} }
} catch (e) { } catch (err) {
logger.warn( logger.warn(
{ config, packageFile }, { err, config, packageFile },
`Failed to process Gradle file: ${packageFile}` `Failed to process Gradle file: ${packageFile}`
); );
} }

View file

@ -1,7 +1,15 @@
import { parse } from '@iarna/toml'; import { parse } from '@iarna/toml';
import { PackageDependency } from '../../../types'; import deepmerge from 'deepmerge';
import { GradleManagerData } from '../../types'; import { SkipReason } from '../../../../types';
import type { GradleCatalog, GradleCatalogPluginDescriptor } from '../types'; import { hasKey } from '../../../../util/object';
import type { PackageDependency } from '../../../types';
import type { GradleManagerData } from '../../types';
import type {
GradleCatalog,
GradleCatalogArtifactDescriptor,
GradleCatalogModuleDescriptor,
VersionPointer,
} from '../types';
function findIndexAfter( function findIndexAfter(
content: string, content: string,
@ -12,6 +20,123 @@ function findIndexAfter(
return slicePoint + content.slice(slicePoint).indexOf(find); return slicePoint + content.slice(slicePoint).indexOf(find);
} }
function isArtifactDescriptor(
obj: GradleCatalogArtifactDescriptor | GradleCatalogModuleDescriptor
): obj is GradleCatalogArtifactDescriptor {
return hasKey('group', obj);
}
interface VersionExtract {
currentValue?: string;
fileReplacePosition?: number;
}
function extractVersion({
version,
versions,
depStartIndex,
depSubContent,
depName,
versionStartIndex,
versionSubContent,
}: {
version: string | VersionPointer;
versions: Record<string, string>;
depStartIndex: number;
depSubContent: string;
depName: string;
versionStartIndex: number;
versionSubContent: string;
}): VersionExtract {
if (!version) {
return {};
}
const currentValue =
typeof version === 'string' ? version : versions[version.ref];
const fileReplacePosition =
typeof version === 'string'
? depStartIndex + findIndexAfter(depSubContent, depName, currentValue)
: versionStartIndex +
findIndexAfter(versionSubContent, version.ref, currentValue);
return { currentValue, fileReplacePosition };
}
function extractDependency({
descriptor,
versions,
depStartIndex,
depSubContent,
depName,
versionStartIndex,
versionSubContent,
}: {
descriptor:
| string
| GradleCatalogModuleDescriptor
| GradleCatalogArtifactDescriptor;
versions: Record<string, string>;
depStartIndex: number;
depSubContent: string;
depName: string;
versionStartIndex: number;
versionSubContent: string;
}): PackageDependency<GradleManagerData> {
if (typeof descriptor === 'string') {
const [groupName, name, currentValue] = descriptor.split(':');
if (!currentValue) {
return {
depName,
skipReason: SkipReason.NoVersion,
};
}
return {
depName: `${groupName}:${name}`,
groupName,
currentValue,
managerData: {
fileReplacePosition:
depStartIndex + findIndexAfter(depSubContent, depName, currentValue),
},
};
}
const { currentValue, fileReplacePosition } = extractVersion({
version: descriptor.version,
versions,
depStartIndex,
depSubContent,
depName,
versionStartIndex,
versionSubContent,
});
if (!currentValue) {
return {
depName,
skipReason: SkipReason.NoVersion,
};
}
if (isArtifactDescriptor(descriptor)) {
const { group: groupName, name } = descriptor;
return {
depName: `${groupName}:${name}`,
groupName,
currentValue,
managerData: { fileReplacePosition },
};
}
const [groupName, name] = descriptor.module.split(':');
const dependency = {
depName: `${groupName}:${name}`,
groupName,
currentValue,
managerData: { fileReplacePosition },
};
return dependency;
}
export function parseCatalog( export function parseCatalog(
packageFile: string, packageFile: string,
content: string content: string
@ -26,58 +151,46 @@ export function parseCatalog(
const extractedDeps: PackageDependency<GradleManagerData>[] = []; const extractedDeps: PackageDependency<GradleManagerData>[] = [];
for (const libraryName of Object.keys(libs)) { for (const libraryName of Object.keys(libs)) {
const libDescriptor = libs[libraryName]; const libDescriptor = libs[libraryName];
const group: string = const dependency = extractDependency({
typeof libDescriptor === 'string' descriptor: libDescriptor,
? libDescriptor.split(':')[0] versions,
: libDescriptor.group || libDescriptor.module?.split(':')[0]; depStartIndex: libStartIndex,
const name: string = depSubContent: libSubContent,
typeof libDescriptor === 'string' depName: libraryName,
? libDescriptor.split(':')[1] versionStartIndex,
: libDescriptor.name || libDescriptor.module?.split(':')[1]; versionSubContent,
const version = libDescriptor.version || libDescriptor.split(':')[2]; });
const currentVersion =
typeof version === 'string' ? version : versions[version.ref];
const fileReplacePosition =
typeof version === 'string'
? libStartIndex +
findIndexAfter(libSubContent, libraryName, currentVersion)
: versionStartIndex +
findIndexAfter(versionSubContent, version.ref, currentVersion);
const dependency = {
depName: `${group}:${name}`,
groupName: group,
currentValue: currentVersion,
managerData: { fileReplacePosition, packageFile },
};
extractedDeps.push(dependency); extractedDeps.push(dependency);
} }
const plugins = tomlContent.plugins || {}; const plugins = tomlContent.plugins || {};
const pluginsStartIndex = content.indexOf('[plugins]'); const pluginsStartIndex = content.indexOf('[plugins]');
const pluginsSubContent = content.slice(pluginsStartIndex); const pluginsSubContent = content.slice(pluginsStartIndex);
for (const pluginName of Object.keys(plugins)) { for (const pluginName of Object.keys(plugins)) {
const pluginDescriptor = plugins[ const pluginDescriptor = plugins[pluginName];
pluginName const depName = pluginDescriptor.id;
] as GradleCatalogPluginDescriptor; const { currentValue, fileReplacePosition } = extractVersion({
const pluginId = pluginDescriptor.id; version: pluginDescriptor.version,
const version = pluginDescriptor.version; versions,
const currentVersion: string = depStartIndex: pluginsStartIndex,
typeof version === 'string' ? version : versions[version.ref]; depSubContent: pluginsSubContent,
const fileReplacePosition = depName,
typeof version === 'string' versionStartIndex,
? pluginsStartIndex + versionSubContent,
findIndexAfter(pluginsSubContent, pluginId, currentVersion) });
: versionStartIndex +
findIndexAfter(versionSubContent, version.ref, currentVersion);
const dependency = { const dependency = {
depType: 'plugin', depType: 'plugin',
depName: pluginId, depName,
lookupName: `${pluginId}:${pluginId}.gradle.plugin`, lookupName: `${depName}:${depName}.gradle.plugin`,
registryUrls: ['https://plugins.gradle.org/m2/'], registryUrls: ['https://plugins.gradle.org/m2/'],
currentValue: currentVersion, currentValue,
commitMessageTopic: `plugin ${pluginName}`, commitMessageTopic: `plugin ${pluginName}`,
managerData: { fileReplacePosition, packageFile }, managerData: { fileReplacePosition },
}; };
extractedDeps.push(dependency); extractedDeps.push(dependency);
} }
return extractedDeps; return extractedDeps.map((dep) =>
deepmerge(dep, { managerData: { packageFile } })
);
} }

View file

@ -63,23 +63,23 @@ export interface ParseGradleResult {
} }
export interface GradleCatalog { export interface GradleCatalog {
versions?: Map<string, string>; versions?: Record<string, string>;
libraries?: Map< libraries?: Record<
string, string,
GradleCatalogModuleDescriptor | GradleCatalogArtifactDescriptor | string GradleCatalogModuleDescriptor | GradleCatalogArtifactDescriptor | string
>; >;
plugins?: Map<string, GradleCatalogPluginDescriptor>; plugins?: Record<string, GradleCatalogPluginDescriptor>;
} }
export interface GradleCatalogModuleDescriptor { export interface GradleCatalogModuleDescriptor {
module: string; module: string;
version: string | VersionPointer; version?: string | VersionPointer;
} }
export interface GradleCatalogArtifactDescriptor { export interface GradleCatalogArtifactDescriptor {
name: string; name: string;
group: string; group: string;
version: string | VersionPointer; version?: string | VersionPointer;
} }
export interface GradleCatalogPluginDescriptor { export interface GradleCatalogPluginDescriptor {

View file

@ -1,4 +1,4 @@
export interface GradleManagerData { export interface GradleManagerData {
fileReplacePosition: number; fileReplacePosition?: number;
packageFile?: string; packageFile?: string;
} }