Skip to content

Commit

Permalink
feat(gradle-lite): JS-based Gradle manager (#7521)
Browse files Browse the repository at this point in the history
  • Loading branch information
zharinov committed Dec 8, 2020
1 parent 858af63 commit c43a4dc
Show file tree
Hide file tree
Showing 17 changed files with 1,875 additions and 2 deletions.
4 changes: 2 additions & 2 deletions lib/manager/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ export interface UpdateArtifact {
config: UpdateArtifactsConfig;
}

export interface UpdateDependencyConfig {
export interface UpdateDependencyConfig<T = Record<string, any>> {
fileContent: string;
upgrade: Upgrade;
upgrade: Upgrade<T>;
}

export interface ManagerApi {
Expand Down
95 changes: 95 additions & 0 deletions lib/manager/gradle-lite/__snapshots__/parser.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`manager/gradle-lite/parser parses fixture from "gradle" manager 1`] = `
Array [
Object {
"currentValue": "1.5.2.RELEASE",
"depName": "org.springframework.boot:spring-boot-gradle-plugin",
"groupName": "springBootVersion",
"managerData": Object {
"fileReplacePosition": 53,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "1.2.3",
"depName": "com.github.jengelman.gradle.plugins:shadow",
"managerData": Object {
"fileReplacePosition": 388,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "0.1",
"depName": "com.fkorotkov:gradle-libraries-plugin",
"managerData": Object {
"fileReplacePosition": 452,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "0.2.3",
"depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin",
"managerData": Object {
"fileReplacePosition": 539,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "3.1.1",
"depName": "org.apache.openjpa:openjpa",
"managerData": Object {
"fileReplacePosition": 592,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "6.0.9.RELEASE",
"depName": "org.grails:gorm-hibernate5-spring-boot",
"managerData": Object {
"fileReplacePosition": 1785,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "6.0.5",
"depName": "mysql:mysql-connector-java",
"managerData": Object {
"fileReplacePosition": 1841,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "1.0-groovy-2.4",
"depName": "org.spockframework:spock-spring",
"managerData": Object {
"fileReplacePosition": 1899,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "1.3",
"depName": "org.hamcrest:hamcrest-core",
"managerData": Object {
"fileReplacePosition": 2004,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "3.1",
"depName": "cglib:cglib-nodep",
"managerData": Object {
"fileReplacePosition": 2092,
"packageFile": "build.gradle",
},
},
Object {
"currentValue": "3.1.1",
"depName": "org.apache.openjpa:openjpa",
"managerData": Object {
"fileReplacePosition": 2198,
"packageFile": "build.gradle",
},
},
]
`;
100 changes: 100 additions & 0 deletions lib/manager/gradle-lite/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { PackageDependency } from '../common';

export interface ManagerData {
fileReplacePosition: number;
packageFile?: string;
}

export interface VariableData extends ManagerData {
key: string;
value: string;
}

export type PackageVariables = Record<string, VariableData>;
export type VariableRegistry = Record<string, PackageVariables>;

export enum TokenType {
Space = 'space',
LineComment = 'lineComment',
MultiComment = 'multiComment',
Newline = 'newline',

Semicolon = 'semicolon',
Colon = 'colon',
Dot = 'dot',
Comma = 'comma',
Operator = 'operator',

Assignment = 'assignment',

Word = 'word',

LeftParen = 'leftParen',
RightParen = 'rightParen',

LeftBracket = 'leftBracket',
RightBracket = 'rightBracket',

LeftBrace = 'leftBrace',
RightBrace = 'rightBrace',

SingleQuotedStart = 'singleQuotedStart',
SingleQuotedFinish = 'singleQuotedFinish',

DoubleQuotedStart = 'doubleQuotedStart',
StringInterpolation = 'interpolation',
IgnoredInterpolationStart = 'ignoredInterpolation',
Variable = 'variable',
DoubleQuotedFinish = 'doubleQuotedFinish',

TripleSingleQuotedStart = 'tripleQuotedStart',
TripleDoubleQuotedStart = 'tripleDoubleQuotedStart',
TripleQuotedFinish = 'tripleQuotedFinish',

Char = 'char',
EscapedChar = 'escapedChar',
String = 'string',

UnknownLexeme = 'unknownChar',
UnknownFragment = 'unknownFragment',
}

export interface Token {
type: TokenType;
value: string;
offset: number;
}

export interface StringInterpolation extends Token {
type: TokenType.StringInterpolation;
children: Token[]; // Tokens inside double-quoted string that are subject of interpolation
isComplete: boolean; // True if token has parsed completely
isValid: boolean; // False if string contains something unprocessable
}

// Matcher on single token
export interface SyntaxMatcher {
matchType: TokenType | TokenType[];
matchValue?: string | string[];
lookahead?: boolean;
tokenMapKey?: string;
}

export type TokenMap = Record<string, Token>;

export interface SyntaxHandlerInput {
packageFile: string;
variables: PackageVariables;
tokenMap: TokenMap;
}

export type SyntaxHandlerOutput = {
deps?: PackageDependency<ManagerData>[];
vars?: PackageVariables;
urls?: string[];
} | null;

export interface SyntaxMatchConfig {
matchers: SyntaxMatcher[];
handler: (MatcherHandlerInput) => SyntaxHandlerOutput;
}
69 changes: 69 additions & 0 deletions lib/manager/gradle-lite/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { fs } from '../../../test/util';
import { extractAllPackageFiles } from '.';

jest.mock('../../util/fs');

function mockFs(files: Record<string, string>): void {
fs.readLocalFile.mockImplementation(
(fileName: string): Promise<string> => {
const content = files?.[fileName];
return typeof content === 'string'
? Promise.resolve(content)
: Promise.reject(`File not found: ${fileName}`);
}
);
}

describe('manager/gradle-lite/extract', () => {
beforeAll(() => {});
afterAll(() => {
jest.resetAllMocks();
});

it('returns null', async () => {
mockFs({
'gradle.properties': '',
'build.gradle': '',
});

const res = await extractAllPackageFiles({} as never, [
'build.gradle',
'gradle.properties',
]);

expect(res).toBeNull();
});

it('works', async () => {
mockFs({
'gradle.properties': 'baz=1.2.3',
'build.gradle': 'url "https://example.com"; "foo:bar:$baz"',
'settings.gradle': null,
});

const res = await extractAllPackageFiles({} as never, [
'build.gradle',
'gradle.properties',
'settings.gradle',
]);

expect(res).toMatchObject([
{
packageFile: 'gradle.properties',
deps: [
{
depName: 'foo:bar',
currentValue: '1.2.3',
registryUrls: ['https://example.com'],
},
],
},
{ packageFile: 'build.gradle', deps: [] },
{
datasource: 'maven',
deps: [],
packageFile: 'settings.gradle',
},
]);
});
});
72 changes: 72 additions & 0 deletions lib/manager/gradle-lite/extract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as upath from 'upath';
import * as datasourceMaven from '../../datasource/maven';
import { logger } from '../../logger';
import { readLocalFile } from '../../util/fs';
import { ExtractConfig, PackageDependency, PackageFile } from '../common';
import { ManagerData, VariableRegistry } from './common';
import { parseGradle, parseProps } from './parser';
import {
getVars,
isGradleFile,
isPropsFile,
reorderFiles,
toAbsolutePath,
} from './utils';

export async function extractAllPackageFiles(
config: ExtractConfig,
packageFiles: string[]
): Promise<PackageFile[] | null> {
const extractedDeps: PackageDependency<ManagerData>[] = [];
const registry: VariableRegistry = {};
const packageFilesByName: Record<string, PackageFile> = {};
const registryUrls = [];
for (const packageFile of reorderFiles(packageFiles)) {
packageFilesByName[packageFile] = {
packageFile,
datasource: datasourceMaven.id,
deps: [],
};

try {
const content = await readLocalFile(packageFile, 'utf8');
const dir = upath.dirname(toAbsolutePath(packageFile));
if (isPropsFile(packageFile)) {
const { vars, deps } = parseProps(content, packageFile);
registry[dir] = vars;
extractedDeps.push(...deps);
} else if (isGradleFile(packageFile)) {
const vars = getVars(registry, dir);
const { deps, urls } = parseGradle(content, vars, packageFile);
urls.forEach((url) => {
if (!registryUrls.includes(url)) {
registryUrls.push(url);
}
});
extractedDeps.push(...deps);
}
} catch (e) {
logger.warn(
{ config, packageFile },
`Failed to process Gradle file: ${packageFile}`
);
}
}

if (!extractedDeps.length) {
return null;
}

extractedDeps.forEach((dep) => {
const key = dep.managerData.packageFile;
const pkgFile: PackageFile = packageFilesByName[key];
const { deps } = pkgFile;
deps.push({
...dep,
registryUrls: [...(dep.registryUrls || []), ...registryUrls],
});
packageFilesByName[key] = pkgFile;
});

return Object.values(packageFilesByName);
}
10 changes: 10 additions & 0 deletions lib/manager/gradle-lite/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as gradleVersioning from '../../versioning/gradle';

export { extractAllPackageFiles } from './extract';
export { updateDependency } from './update';

export const defaultConfig = {
fileMatch: ['(^|/)gradle.properties$', '\\.gradle(\\.kts)?$'],
versioning: gradleVersioning.id,
enabled: false,
};

0 comments on commit c43a4dc

Please sign in to comment.