Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(manager/gradle): add support for apply from statements #16030

Merged
merged 24 commits into from
Jul 3, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
42bb838
add synchronous local file read
Churro Jun 12, 2022
eb92922
add tests
Churro Jun 12, 2022
679ba4e
add support for apply from statements
Churro Jun 12, 2022
9f15041
add safeguard to prevent breakout from localDir
Churro Jun 13, 2022
ac5c5f6
add safeguard to allow "apply from" only with official Gradle file types
Churro Jun 13, 2022
5396dee
fix: use async io
viceice Jun 15, 2022
c7a9e93
fix test coverage
Churro Jun 15, 2022
7998581
Merge remote-tracking branch 'upstream/main' into gradle-applyfrom
Churro Jun 25, 2022
b1b99f4
Merge branch 'main' into gradle-applyfrom
rarkins Jun 27, 2022
c000462
Merge branch 'main' into gradle-applyfrom
rarkins Jun 27, 2022
04e42b7
Update lib/modules/manager/gradle/extract.spec.ts
Churro Jun 27, 2022
144e2eb
Update lib/modules/manager/gradle/extract.spec.ts
Churro Jun 27, 2022
5297d49
Update lib/modules/manager/gradle/extract.spec.ts
Churro Jun 27, 2022
60b1c57
re-add try/catch
Churro Jun 28, 2022
4b23afe
add istanbul ignore
Churro Jun 28, 2022
f004497
Update lib/modules/manager/gradle/parser.ts
Churro Jun 29, 2022
0ad4184
fs tests: remove obsolete block
Churro Jun 29, 2022
5ff3bd7
Merge remote-tracking branch 'upstream/main' into gradle-applyfrom
Churro Jun 29, 2022
597a2f1
Merge remote-tracking branch 'upstream/main' into gradle-applyfrom
Churro Jul 2, 2022
38be900
remove istanbul ignore next
Churro Jul 2, 2022
1f9382f
Merge branch 'main' into gradle-applyfrom
rarkins Jul 3, 2022
3a129fb
Update lib/modules/manager/gradle/extract.ts
Churro Jul 3, 2022
7ff7222
tests: replace mockImplementationOnce() with mockRejectedValueOnce()
Churro Jul 3, 2022
c092a5d
scriptFile: explicitly set null for undefined values
Churro Jul 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 196 additions & 3 deletions lib/modules/manager/gradle/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ 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}`);
return Promise.resolve(content ?? '');
});

fs.getSiblingFileName.mockImplementation(
(existingFileNameWithPath: string, otherFileName: string) => {
return existingFileNameWithPath
.slice(0, existingFileNameWithPath.lastIndexOf('/') + 1)
.concat(otherFileName);
}
);
}

describe('modules/manager/gradle/extract', () => {
Expand Down Expand Up @@ -605,4 +611,191 @@ describe('modules/manager/gradle/extract', () => {
},
]);
});

it('loads further scripts using apply from statements', async () => {
const buildFile = `
buildscript {
repositories {
mavenCentral()
}

apply from: "\${someDir}/libs1.gradle"
apply from: file("gradle/libs2.gradle")
apply from: "gradle/libs3.gradle"
apply from: file("gradle/non-existing.gradle")

dependencies {
classpath "com.google.protobuf:protobuf-java:\${protoBufVersion}"
classpath "com.google.guava:guava:\${guavaVersion}"
classpath "io.jsonwebtoken:jjwt-api:0.11.2"

classpath "org.junit.jupiter:junit-jupiter-api:\${junitVersion}"
classpath "org.junit.jupiter:junit-jupiter-engine:\${junitVersion}"
}
}
`;

mockFs({
Churro marked this conversation as resolved.
Show resolved Hide resolved
'gradleX/libs1.gradle': "ext.junitVersion = '5.5.2'",
'gradle/libs2.gradle': "ext.protoBufVersion = '3.18.2'",
'gradle/libs3.gradle': "ext.guavaVersion = '30.1-jre'",
'build.gradle': buildFile,
'gradle.properties': 'someDir=gradleX',
});

const res = await extractAllPackageFiles({} as ExtractConfig, [
'gradleX/libs1.gradle',
'gradle/libs2.gradle',
// 'gradle/libs3.gradle', is intentionally not listed here
'build.gradle',
'gradle.properties',
]);

expect(res).toMatchObject([
{ packageFile: 'gradle.properties' },
{
packageFile: 'build.gradle',
deps: [{ depName: 'io.jsonwebtoken:jjwt-api' }],
},
{
packageFile: 'gradle/libs2.gradle',
deps: [
{
depName: 'com.google.protobuf:protobuf-java',
currentValue: '3.18.2',
managerData: { packageFile: 'gradle/libs2.gradle' },
},
],
},
{
packageFile: 'gradleX/libs1.gradle',
deps: [
{
depName: 'org.junit.jupiter:junit-jupiter-api',
currentValue: '5.5.2',
managerData: { packageFile: 'gradleX/libs1.gradle' },
},
{
depName: 'org.junit.jupiter:junit-jupiter-engine',
currentValue: '5.5.2',
managerData: { packageFile: 'gradleX/libs1.gradle' },
},
],
},
{
packageFile: 'gradle/libs3.gradle',
deps: [
{
depName: 'com.google.guava:guava',
currentValue: '30.1-jre',
managerData: { packageFile: 'gradle/libs3.gradle' },
},
],
},
]);
});

it('apply from works with files in sub-directories', async () => {
const buildFile = `
buildscript {
repositories {
mavenCentral()
}

apply from: "gradle/libs4.gradle"

dependencies {
classpath "com.google.protobuf:protobuf-java:\${protoBufVersion}"
}
}
`;

mockFs({
'somesubdir/gradle/libs4.gradle': "ext.protoBufVersion = '3.18.2'",
'somesubdir/build.gradle': buildFile,
});

const res = await extractAllPackageFiles({} as ExtractConfig, [
'somesubdir/gradle/libs4.gradle',
'somesubdir/build.gradle',
]);

expect(res).toMatchObject([
{ packageFile: 'somesubdir/build.gradle' },
{
packageFile: 'somesubdir/gradle/libs4.gradle',
deps: [{ depName: 'com.google.protobuf:protobuf-java' }],
},
]);
});

it('prevents recursive apply from calls', async () => {
mockFs({
'build.gradle': "apply from: 'test.gradle'",
'test.gradle': "apply from: 'build.gradle'",
});

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

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

it('prevents inclusion of non-Gradle files', async () => {
mockFs({
'build.gradle': "apply from: '../../test.non-gradle'",
});

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

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

it('filters duplicate dependency findings', async () => {
const buildFile = `
apply from: 'test.gradle'

repositories {
mavenCentral()
}

dependencies {
implementation "io.jsonwebtoken:jjwt-api:$\{jjwtVersion}"
runtimeOnly "io.jsonwebtoken:jjwt-impl:$\{jjwtVersion}"
}
`;

const testFile = `
ext.jjwtVersion = '0.11.2'

ext {
jjwtApi = "io.jsonwebtoken:jjwt-api:$jjwtVersion"
}
`;

mockFs({
'build.gradle': buildFile,
'test.gradle': testFile,
});

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

expect(res).toMatchObject([
{
packageFile: 'test.gradle',
deps: [
{ depName: 'io.jsonwebtoken:jjwt-api' },
{ depName: 'io.jsonwebtoken:jjwt-impl' },
],
},
{ packageFile: 'build.gradle' },
]);
});
});
43 changes: 29 additions & 14 deletions lib/modules/manager/gradle/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export async function extractAllPackageFiles(
deps,
urls,
vars: gradleVars,
} = parseGradle(content, vars, packageFile);
} = await parseGradle(content, vars, packageFile);
urls.forEach((url) => {
if (!registryUrls.includes(url)) {
registryUrls.push(url);
Expand All @@ -84,7 +84,7 @@ export async function extractAllPackageFiles(
updateVars(gradleVars);
extractedDeps.push(...deps);
}
} catch (err) {
Churro marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) /* istanbul ignore next */ {
Churro marked this conversation as resolved.
Show resolved Hide resolved
logger.warn(
{ err, config, packageFile },
`Failed to process Gradle file: ${packageFile}`
Expand All @@ -100,18 +100,33 @@ export async function extractAllPackageFiles(
const key = dep.managerData?.packageFile;
// istanbul ignore else
if (key) {
const pkgFile: PackageFile = packageFilesByName[key];
const { deps } = pkgFile;
deps.push({
...dep,
registryUrls: [
...new Set([
...defaultRegistryUrls,
...(dep.registryUrls ?? []),
...registryUrls,
]),
],
});
let pkgFile: PackageFile = packageFilesByName[key];
Churro marked this conversation as resolved.
Show resolved Hide resolved
if (!pkgFile) {
pkgFile = {
packageFile: key,
datasource,
deps: [],
} as PackageFile;
}

dep.registryUrls = [
...new Set([
...defaultRegistryUrls,
...(dep.registryUrls ?? []),
...registryUrls,
]),
];

const depAlreadyInPkgFile = pkgFile.deps.some(
(item) =>
item.depName === dep.depName &&
item.managerData?.fileReplacePosition ===
dep.managerData?.fileReplacePosition
);
if (!depAlreadyInPkgFile) {
pkgFile.deps.push(dep);
}

packageFilesByName[key] = pkgFile;
} else {
logger.warn({ dep }, `Failed to process Gradle dependency`);
Expand Down
Loading