fix(gradle): fix dependencies.txt not found (#29787)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
- currently, when cache project report, it caches buildFileToDepsMap,
which is a map of build file to path of dependencies.txt
- however, when we try to read dependencies.txt in create dependencies,
it might not exist

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
- read the dependencies.txt file when process project report to cache
- cache dependencies as processed project report result
- no need to read dependencies.txt in create dependencies

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Emily Xiong 2025-01-29 07:50:28 -08:00 committed by GitHub
parent 21aba7a179
commit 8754e3871d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 186 deletions

View File

@ -23,7 +23,7 @@ describe('@nx/gradle/plugin', () => {
gradleFileToGradleProjectMap: new Map<string, string>([
['proj/build.gradle', 'proj'],
]),
buildFileToDepsMap: new Map<string, string>(),
buildFileToDepsMap: new Map<string, Set<string>>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['proj/build.gradle', new Map([['build', 'build']])],
]),

View File

@ -1,102 +0,0 @@
import { join } from 'path';
import { processGradleDependencies } from './dependencies';
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
validateDependency: jest.fn().mockReturnValue(true),
}));
describe('processGradleDependencies', () => {
it('should process gradle dependencies with composite build', () => {
const depFilePath = join(
__dirname,
'..',
'utils/__mocks__/gradle-composite-dependencies.txt'
);
const dependencies = new Set([]);
processGradleDependencies(
depFilePath,
new Map([
[':my-utils:number-utils', 'utilities/number-utils'],
[':my-utils:string-utils', 'utilities/string-utils'],
]),
'app',
'app',
{
projects: {
'number-utils': {
root: 'utilities/number-utils',
name: 'number-utils',
},
'string-utils': {
root: 'utilities/string-utils',
name: 'string-utils',
},
utilities: {
root: 'utilities',
name: 'utilities',
},
},
} as any,
dependencies
);
expect(Array.from(dependencies)).toEqual([
{
source: 'app',
sourceFile: 'app',
target: 'number-utils',
type: 'static',
},
{
source: 'app',
sourceFile: 'app',
target: 'string-utils',
type: 'static',
},
]);
});
it('should process gradle dependencies with regular build', () => {
const depFilePath = join(
__dirname,
'..',
'utils/__mocks__/gradle-dependencies.txt'
);
const dependencies = new Set([]);
processGradleDependencies(
depFilePath,
new Map([
[':my-utils:number-utils', 'utilities/number-utils'],
[':my-utils:string-utils', 'utilities/string-utils'],
[':utilities', 'utilities'],
]),
'app',
'app',
{
projects: {
'number-utils': {
root: 'utilities/number-utils',
name: 'number-utils',
},
'string-utils': {
root: 'utilities/string-utils',
name: 'string-utils',
},
utilities: {
root: 'utilities',
name: 'utilities',
},
},
} as any,
dependencies
);
expect(Array.from(dependencies)).toEqual([
{
source: 'app',
sourceFile: 'app',
target: 'utilities',
type: 'static',
},
]);
});
});

View File

@ -6,12 +6,10 @@ import {
RawProjectGraphDependency,
validateDependency,
} from '@nx/devkit';
import { readFileSync } from 'node:fs';
import { basename, dirname } from 'node:path';
import { getCurrentGradleReport } from '../utils/get-gradle-report';
import { GRADLE_BUILD_FILES } from '../utils/split-config-files';
import { newLineSeparator } from '../utils/get-project-report-lines';
export const createDependencies: CreateDependencies = async (
_,
@ -36,17 +34,25 @@ export const createDependencies: CreateDependencies = async (
const projectName = Object.values(context.projects).find(
(project) => project.root === dirname(gradleFile)
)?.name;
const depsFile = buildFileToDepsMap.get(gradleFile);
const dependedProjects: Set<string> = buildFileToDepsMap.get(gradleFile);
if (projectName && depsFile) {
processGradleDependencies(
depsFile,
gradleProjectNameToProjectRootMap,
projectName,
gradleFile,
context,
dependencies
);
if (projectName && dependedProjects?.size) {
dependedProjects?.forEach((dependedProject) => {
const targetProjectRoot = gradleProjectNameToProjectRootMap.get(
dependedProject
) as string;
const targetProjectName = Object.values(context.projects).find(
(project) => project.root === targetProjectRoot
)?.name;
const dependency: RawProjectGraphDependency = {
source: projectName as string,
target: targetProjectName as string,
type: DependencyType.static,
sourceFile: gradleFile,
};
validateDependency(dependency, context);
dependencies.add(dependency);
});
}
gradleProjectToChildProjects.get(gradleProject)?.forEach((childProject) => {
if (childProject) {
@ -85,60 +91,3 @@ function findGradleFiles(fileMap: FileMap): string[] {
return gradleFiles;
}
export function processGradleDependencies(
depsFile: string,
gradleProjectNameToProjectRoot: Map<string, string>,
sourceProjectName: string,
gradleFile: string,
context: CreateDependenciesContext,
dependencies: Set<RawProjectGraphDependency>
): void {
const lines = readFileSync(depsFile).toString().split(newLineSeparator);
let inDeps = false;
for (const line of lines) {
if (
line.startsWith('implementationDependenciesMetadata') ||
line.startsWith('compileClasspath')
) {
inDeps = true;
continue;
}
if (inDeps) {
if (line === '') {
inDeps = false;
continue;
}
const [indents, dep] = line.split('--- ');
if (indents === '\\' || indents === '+') {
let gradleProjectName: string | undefined;
if (dep.startsWith('project ')) {
gradleProjectName = dep
.substring('project '.length)
.replace(/ \(n\)$/, '')
.trim();
} else if (dep.includes('-> project')) {
const [_, projectName] = dep.split('-> project');
gradleProjectName = projectName.trim();
}
const targetProjectRoot = gradleProjectNameToProjectRoot.get(
gradleProjectName
) as string;
const targetProjectName = Object.values(context.projects).find(
(project) => project.root === targetProjectRoot
)?.name;
if (targetProjectName) {
const dependency: RawProjectGraphDependency = {
source: sourceProjectName,
target: targetProjectName,
type: DependencyType.static,
sourceFile: gradleFile,
};
validateDependency(dependency, context);
dependencies.add(dependency);
}
}
}
}
}

View File

@ -26,7 +26,7 @@ describe('@nx/gradle/plugin', () => {
gradleFileToGradleProjectMap: new Map<string, string>([
['proj/build.gradle', 'proj'],
]),
buildFileToDepsMap: new Map<string, string>(),
buildFileToDepsMap: new Map<string, Set<string>>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['proj/build.gradle', new Map([['build', 'build']])],
]),
@ -249,7 +249,7 @@ describe('@nx/gradle/plugin', () => {
gradleFileToGradleProjectMap: new Map<string, string>([
['nested/nested/proj/build.gradle', 'proj'],
]),
buildFileToDepsMap: new Map<string, string>(),
buildFileToDepsMap: new Map<string, Set<string>>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
@ -341,7 +341,7 @@ describe('@nx/gradle/plugin', () => {
gradleFileToGradleProjectMap: new Map<string, string>([
['nested/nested/proj/build.gradle', 'proj'],
]),
buildFileToDepsMap: new Map<string, string>(),
buildFileToDepsMap: new Map<string, Set<string>>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),

View File

@ -1,6 +1,9 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { processProjectReports } from './get-gradle-report';
import {
processGradleDependencies,
processProjectReports,
} from './get-gradle-report';
describe('processProjectReports', () => {
it('should process project reports', () => {
@ -46,3 +49,28 @@ describe('processProjectReports', () => {
expect(report.gradleProjectToChildProjects.get('')).toEqual([]);
});
});
describe('processGradleDependencies', () => {
it('should process gradle dependencies with composite build', () => {
const depFilePath = join(
__dirname,
'..',
'utils/__mocks__/gradle-composite-dependencies.txt'
);
const dependencies = processGradleDependencies(depFilePath);
expect(Array.from(dependencies)).toEqual([
':my-utils:number-utils',
':my-utils:string-utils',
]);
});
it('should process gradle dependencies with regular build', () => {
const depFilePath = join(
__dirname,
'..',
'utils/__mocks__/gradle-dependencies.txt'
);
const dependencies = processGradleDependencies(depFilePath);
expect(Array.from(dependencies)).toEqual([':utilities']);
});
});

View File

@ -21,10 +21,10 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
export interface GradleReport {
gradleFileToGradleProjectMap: Map<string, string>;
buildFileToDepsMap: Map<string, string>;
buildFileToDepsMap: Map<string, Set<string>>;
gradleFileToOutputDirsMap: Map<string, Map<string, string>>;
gradleProjectToTasksTypeMap: Map<string, Map<string, string>>;
gradleProjectToTasksMap: Map<string, Set<String>>;
gradleProjectToTasksMap: Map<string, Set<string>>;
gradleProjectToProjectName: Map<string, string>;
gradleProjectNameToProjectRootMap: Map<string, string>;
gradleProjectToChildProjects: Map<string, string[]>;
@ -33,10 +33,10 @@ export interface GradleReport {
export interface GradleReportJSON {
hash: string;
gradleFileToGradleProjectMap: Record<string, string>;
buildFileToDepsMap: Record<string, string>;
buildFileToDepsMap: Record<string, Set<string>>;
gradleFileToOutputDirsMap: Record<string, Record<string, string>>;
gradleProjectToTasksTypeMap: Record<string, Record<string, string>>;
gradleProjectToTasksMap: Record<string, Array<String>>;
gradleProjectToTasksMap: Record<string, Array<string>>;
gradleProjectToProjectName: Record<string, string>;
gradleProjectNameToProjectRootMap: Record<string, string>;
gradleProjectToChildProjects: Record<string, string[]>;
@ -220,13 +220,13 @@ export function processProjectReports(
* Map of Gradle Build File to tasks type map
*/
const gradleProjectToTasksTypeMap = new Map<string, Map<string, string>>();
const gradleProjectToTasksMap = new Map<string, Set<String>>();
const gradleProjectToTasksMap = new Map<string, Set<string>>();
const gradleProjectToProjectName = new Map<string, string>();
const gradleProjectNameToProjectRootMap = new Map<string, string>();
/**
* Map of buildFile to dependencies report path
*/
const buildFileToDepsMap = new Map<string, string>();
const buildFileToDepsMap = new Map<string, Set<string>>();
/**
* Map fo possible output files of each gradle file
* e.g. {build.gradle.kts: { projectReportDir: '' testReportDir: '' }}
@ -320,10 +320,13 @@ export function processProjectReports(
relative(workspaceRoot, absBuildFilePath)
);
const buildDir = relative(workspaceRoot, absBuildDirPath);
buildFileToDepsMap.set(
buildFile,
dependenciesMap.get(gradleProject) as string
);
const depsFile = dependenciesMap.get(gradleProject);
if (depsFile) {
buildFileToDepsMap.set(
buildFile,
processGradleDependencies(depsFile)
);
}
outputDirMap.set('build', `{workspaceRoot}/${buildDir}`);
outputDirMap.set(
@ -395,3 +398,42 @@ export function processProjectReports(
gradleProjectToChildProjects,
};
}
export function processGradleDependencies(depsFile: string): Set<string> {
const dependedProjects = new Set<string>();
const lines = readFileSync(depsFile).toString().split(newLineSeparator);
let inDeps = false;
for (const line of lines) {
if (
line.startsWith('implementationDependenciesMetadata') ||
line.startsWith('compileClasspath')
) {
inDeps = true;
continue;
}
if (inDeps) {
if (line === '') {
inDeps = false;
continue;
}
const [indents, dep] = line.split('--- ');
if (indents === '\\' || indents === '+') {
let targetProjectName: string | undefined;
if (dep.startsWith('project ')) {
targetProjectName = dep
.substring('project '.length)
.replace(/ \(n\)$/, '')
.trim();
} else if (dep.includes('-> project')) {
const [_, projectName] = dep.split('-> project');
targetProjectName = projectName.trim();
}
if (targetProjectName) {
dependedProjects.add(targetProjectName);
}
}
}
}
return dependedProjects;
}