feat(gradle): gradle atomizer (#26663)

<!-- 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 -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## 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 2024-07-05 12:22:37 -07:00 committed by GitHub
parent e15479b691
commit 62baf4f307
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 411 additions and 71 deletions

View File

@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'graph-ui-project-details',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/graph/graph-ui-project-details',
};

View File

@ -13,6 +13,9 @@
{ {
"path": "./tsconfig.lib.json" "path": "./tsconfig.lib.json"
}, },
{
"path": "./tsconfig.spec.json"
},
{ {
"path": "./tsconfig.storybook.json" "path": "./tsconfig.storybook.json"
} }

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -6,5 +6,5 @@ export default {
'^.+\\.[tj]sx?$': 'babel-jest', '^.+\\.[tj]sx?$': 'babel-jest',
}, },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/graph/ui-graph', coverageDirectory: '../../coverage/graph/graph-ui-tooltips',
}; };

View File

@ -19,17 +19,17 @@ describe('@nx/gradle:init', () => {
}); });
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
expect(nxJson.plugins).toMatchInlineSnapshot(` expect(nxJson.plugins).toMatchInlineSnapshot(`
[ [
{ {
"options": { "options": {
"buildTargetName": "build", "buildTargetName": "build",
"classesTargetName": "classes", "classesTargetName": "classes",
"testTargetName": "test", "testTargetName": "test",
}, },
"plugin": "@nx/gradle", "plugin": "@nx/gradle",
}, },
] ]
`); `);
}); });
it('should not overwrite existing plugins', async () => { it('should not overwrite existing plugins', async () => {
@ -42,18 +42,18 @@ describe('@nx/gradle:init', () => {
}); });
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
expect(nxJson.plugins).toMatchInlineSnapshot(` expect(nxJson.plugins).toMatchInlineSnapshot(`
[ [
"foo", "foo",
{ {
"options": { "options": {
"buildTargetName": "build", "buildTargetName": "build",
"classesTargetName": "classes", "classesTargetName": "classes",
"testTargetName": "test", "testTargetName": "test",
}, },
"plugin": "@nx/gradle", "plugin": "@nx/gradle",
}, },
] ]
`); `);
}); });
it('should not add plugin if already in array', async () => { it('should not add plugin if already in array', async () => {

View File

@ -10,6 +10,7 @@ import { readFileSync } from 'node:fs';
import { basename } from 'node:path'; import { basename } from 'node:path';
import { import {
GRADLE_BUILD_FILES,
getCurrentGradleReport, getCurrentGradleReport,
newLineSeparator, newLineSeparator,
} from '../utils/get-gradle-report'; } from '../utils/get-gradle-report';
@ -58,14 +59,12 @@ export const createDependencies: CreateDependencies = async (
return Array.from(dependencies); return Array.from(dependencies);
}; };
const gradleConfigFileNames = new Set(['build.gradle', 'build.gradle.kts']);
function findGradleFiles(fileMap: FileMap): string[] { function findGradleFiles(fileMap: FileMap): string[] {
const gradleFiles: string[] = []; const gradleFiles: string[] = [];
for (const [_, files] of Object.entries(fileMap.projectFileMap)) { for (const [_, files] of Object.entries(fileMap.projectFileMap)) {
for (const file of files) { for (const file of files) {
if (gradleConfigFileNames.has(basename(file.file))) { if (GRADLE_BUILD_FILES.has(basename(file.file))) {
gradleFiles.push(file.file); gradleFiles.push(file.file);
} }
} }

View File

@ -4,8 +4,9 @@ import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { type GradleReport } from '../utils/get-gradle-report'; import { type GradleReport } from '../utils/get-gradle-report';
let gradleReport: GradleReport; let gradleReport: GradleReport;
jest.mock('../utils/get-gradle-report.ts', () => { jest.mock('../utils/get-gradle-report', () => {
return { return {
GRADLE_BUILD_FILES: new Set(['build.gradle', 'build.gradle.kts']),
populateGradleReport: jest.fn().mockImplementation(() => void 0), populateGradleReport: jest.fn().mockImplementation(() => void 0),
getCurrentGradleReport: jest.fn().mockImplementation(() => gradleReport), getCurrentGradleReport: jest.fn().mockImplementation(() => gradleReport),
}; };
@ -23,14 +24,14 @@ describe('@nx/gradle/plugin', () => {
tempFs = new TempFs('test'); tempFs = new TempFs('test');
gradleReport = { gradleReport = {
gradleFileToGradleProjectMap: new Map<string, string>([ gradleFileToGradleProjectMap: new Map<string, string>([
['proj/gradle.build', 'proj'], ['proj/build.gradle', 'proj'],
]), ]),
buildFileToDepsMap: new Map<string, string>(), buildFileToDepsMap: new Map<string, string>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([ gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['proj/gradle.build', new Map([['build', 'build']])], ['proj/build.gradle', new Map([['build', 'build']])],
]), ]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([ gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Test']])], ['proj', new Map([['test', 'Verification']])],
]), ]),
gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]), gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]),
}; };
@ -48,7 +49,7 @@ describe('@nx/gradle/plugin', () => {
}; };
await tempFs.createFiles({ await tempFs.createFiles({
'proj/gradle.build': ``, 'proj/build.gradle': ``,
gradlew: '', gradlew: '',
}); });
}); });
@ -60,7 +61,7 @@ describe('@nx/gradle/plugin', () => {
it('should create nodes based on gradle', async () => { it('should create nodes based on gradle', async () => {
const results = await createNodesFunction( const results = await createNodesFunction(
['proj/gradle.build'], ['proj/build.gradle'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
}, },
@ -70,13 +71,13 @@ describe('@nx/gradle/plugin', () => {
expect(results).toMatchInlineSnapshot(` expect(results).toMatchInlineSnapshot(`
[ [
[ [
"proj/gradle.build", "proj/build.gradle",
{ {
"projects": { "projects": {
"proj": { "proj": {
"metadata": { "metadata": {
"targetGroups": { "targetGroups": {
"Test": [ "Verification": [
"test", "test",
], ],
}, },
@ -87,7 +88,7 @@ describe('@nx/gradle/plugin', () => {
"name": "proj", "name": "proj",
"targets": { "targets": {
"test": { "test": {
"cache": false, "cache": true,
"command": "./gradlew proj:test", "command": "./gradlew proj:test",
"dependsOn": [ "dependsOn": [
"classes", "classes",
@ -114,23 +115,23 @@ describe('@nx/gradle/plugin', () => {
it('should create nodes based on gradle for nested project root', async () => { it('should create nodes based on gradle for nested project root', async () => {
gradleReport = { gradleReport = {
gradleFileToGradleProjectMap: new Map<string, string>([ gradleFileToGradleProjectMap: new Map<string, string>([
['nested/nested/proj/gradle.build', 'proj'], ['nested/nested/proj/build.gradle', 'proj'],
]), ]),
buildFileToDepsMap: new Map<string, string>(), buildFileToDepsMap: new Map<string, string>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([ gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/gradle.build', new Map([['build', 'build']])], ['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]), ]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([ gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Test']])], ['proj', new Map([['test', 'Verification']])],
]), ]),
gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]), gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]),
}; };
await tempFs.createFiles({ await tempFs.createFiles({
'nested/nested/proj/gradle.build': ``, 'nested/nested/proj/build.gradle': ``,
}); });
const results = await createNodesFunction( const results = await createNodesFunction(
['nested/nested/proj/gradle.build'], ['nested/nested/proj/build.gradle'],
{ {
buildTargetName: 'build', buildTargetName: 'build',
}, },
@ -140,13 +141,101 @@ describe('@nx/gradle/plugin', () => {
expect(results).toMatchInlineSnapshot(` expect(results).toMatchInlineSnapshot(`
[ [
[ [
"nested/nested/proj/gradle.build", "nested/nested/proj/build.gradle",
{
"projects": {
"nested/nested/proj": {
"metadata": {
"targetGroups": {
"Verification": [
"test",
],
},
"technologies": [
"gradle",
],
},
"name": "proj",
"targets": {
"test": {
"cache": true,
"command": "./gradlew proj:test",
"dependsOn": [
"classes",
],
"inputs": [
"default",
"^production",
],
"metadata": {
"technologies": [
"gradle",
],
},
},
},
},
},
},
],
]
`);
});
it('should create nodes with atomized tests targets based on gradle for nested project root', async () => {
gradleReport = {
gradleFileToGradleProjectMap: new Map<string, string>([
['nested/nested/proj/build.gradle', 'proj'],
]),
buildFileToDepsMap: new Map<string, string>(),
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Test']])],
]),
gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]),
};
await tempFs.createFiles({
'nested/nested/proj/build.gradle': ``,
});
await tempFs.createFiles({
'proj/src/test/java/test/rootTest.java': ``,
});
await tempFs.createFiles({
'nested/nested/proj/src/test/java/test/test.java': ``,
});
await tempFs.createFiles({
'nested/nested/proj/src/test/java/test/test1.java': ``,
});
const results = await createNodesFunction(
[
'nested/nested/proj/build.gradle',
'proj/src/test/java/test/rootTest.java',
'nested/nested/proj/src/test/java/test/test.java',
'nested/nested/proj/src/test/java/test/test1.java',
],
{
buildTargetName: 'build',
ciTargetName: 'test-ci',
},
context
);
expect(results).toMatchInlineSnapshot(`
[
[
"nested/nested/proj/build.gradle",
{ {
"projects": { "projects": {
"nested/nested/proj": { "nested/nested/proj": {
"metadata": { "metadata": {
"targetGroups": { "targetGroups": {
"Test": [ "Test": [
"test-ci--test",
"test-ci--test1",
"test-ci",
"test", "test",
], ],
}, },
@ -172,6 +261,67 @@ describe('@nx/gradle/plugin', () => {
], ],
}, },
}, },
"test-ci": {
"cache": true,
"dependsOn": [
{
"params": "forward",
"projects": "self",
"target": "test-ci--test",
},
{
"params": "forward",
"projects": "self",
"target": "test-ci--test1",
},
],
"executor": "nx:noop",
"inputs": [
"default",
"^production",
],
"metadata": {
"description": "Runs Gradle Tests in CI",
"nonAtomizedTarget": "test",
"technologies": [
"gradle",
],
},
},
"test-ci--test": {
"cache": true,
"command": "./gradlew proj:test --tests test",
"dependsOn": [
"classes",
],
"inputs": [
"default",
"^production",
],
"metadata": {
"description": "Runs Gradle test nested/nested/proj/src/test/java/test/test.java in CI",
"technologies": [
"gradle",
],
},
},
"test-ci--test1": {
"cache": true,
"command": "./gradlew proj:test --tests test1",
"dependsOn": [
"classes",
],
"inputs": [
"default",
"^production",
],
"metadata": {
"description": "Runs Gradle test nested/nested/proj/src/test/java/test/test1.java in CI",
"technologies": [
"gradle",
],
},
},
}, },
}, },
}, },

View File

@ -2,7 +2,6 @@ import {
CreateNodes, CreateNodes,
CreateNodesV2, CreateNodesV2,
CreateNodesContext, CreateNodesContext,
CreateNodesContextV2,
ProjectConfiguration, ProjectConfiguration,
TargetConfiguration, TargetConfiguration,
createNodesFromFiles, createNodesFromFiles,
@ -13,8 +12,9 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path'; import { basename, dirname, join } from 'node:path';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { findProjectForPath } from 'nx/src/devkit-internals';
import { getGradleExecFile } from '../utils/exec-gradle'; import { getGradleExecFile } from '../utils/exec-gradle';
import { import {
@ -22,6 +22,8 @@ import {
getCurrentGradleReport, getCurrentGradleReport,
GradleReport, GradleReport,
gradleConfigGlob, gradleConfigGlob,
GRADLE_BUILD_FILES,
gradleConfigAndTestGlob,
} from '../utils/get-gradle-report'; } from '../utils/get-gradle-report';
import { hashObject } from 'nx/src/hasher/file-hasher'; import { hashObject } from 'nx/src/hasher/file-hasher';
@ -38,12 +40,21 @@ interface GradleTask {
} }
export interface GradlePluginOptions { export interface GradlePluginOptions {
ciTargetName?: string;
testTargetName?: string; testTargetName?: string;
classesTargetName?: string; classesTargetName?: string;
buildTargetName?: string; buildTargetName?: string;
[taskTargetName: string]: string | undefined; [taskTargetName: string]: string | undefined;
} }
function normalizeOptions(options: GradlePluginOptions): GradlePluginOptions {
options ??= {};
options.testTargetName ??= 'test';
options.classesTargetName ??= 'classes';
options.buildTargetName ??= 'build';
return options;
}
type GradleTargets = Record< type GradleTargets = Record<
string, string,
{ {
@ -62,8 +73,9 @@ export function writeTargetsToCache(cachePath: string, results: GradleTargets) {
} }
export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
gradleConfigGlob, gradleConfigAndTestGlob,
async (configFiles, options, context) => { async (files, options, context) => {
const { configFiles, projectRoots, testFiles } = splitConfigFiles(files);
const optionsHash = hashObject(options); const optionsHash = hashObject(options);
const cachePath = join( const cachePath = join(
workspaceDataDirectory, workspaceDataDirectory,
@ -73,10 +85,18 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
await populateGradleReport(context.workspaceRoot); await populateGradleReport(context.workspaceRoot);
const gradleReport = getCurrentGradleReport(); const gradleReport = getCurrentGradleReport();
const gradleProjectRootToTestFilesMap = getGradleProjectRootToTestFilesMap(
testFiles,
projectRoots
);
try { try {
return await createNodesFromFiles( return createNodesFromFiles(
makeCreateNodes(gradleReport, targetsCache), makeCreateNodesForGradleConfigFile(
gradleReport,
targetsCache,
gradleProjectRootToTestFilesMap
),
configFiles, configFiles,
options, options,
context context
@ -87,10 +107,11 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
}, },
]; ];
export const makeCreateNodes = export const makeCreateNodesForGradleConfigFile =
( (
gradleReport: GradleReport, gradleReport: GradleReport,
targetsCache: GradleTargets targetsCache: GradleTargets = {},
gradleProjectRootToTestFilesMap: Record<string, string[]> = {}
): CreateNodesFunction => ): CreateNodesFunction =>
async ( async (
gradleFilePath, gradleFilePath,
@ -98,17 +119,19 @@ export const makeCreateNodes =
context: CreateNodesContext context: CreateNodesContext
) => { ) => {
const projectRoot = dirname(gradleFilePath); const projectRoot = dirname(gradleFilePath);
options = normalizeOptions(options);
const hash = await calculateHashForCreateNodes( const hash = await calculateHashForCreateNodes(
projectRoot, projectRoot,
options ?? {}, options ?? {},
context context
); );
targetsCache[hash] ??= createGradleProject( targetsCache[hash] ??= await createGradleProject(
gradleReport, gradleReport,
gradleFilePath, gradleFilePath,
options, options,
context context,
gradleProjectRootToTestFilesMap[projectRoot]
); );
const project = targetsCache[hash]; const project = targetsCache[hash];
if (!project) { if (!project) {
@ -133,16 +156,18 @@ export const createNodes: CreateNodes<GradlePluginOptions> = [
); );
await populateGradleReport(context.workspaceRoot); await populateGradleReport(context.workspaceRoot);
const gradleReport = getCurrentGradleReport(); const gradleReport = getCurrentGradleReport();
const internalCreateNodes = makeCreateNodes(gradleReport, {}); const internalCreateNodes =
makeCreateNodesForGradleConfigFile(gradleReport);
return await internalCreateNodes(configFile, options, context); return await internalCreateNodes(configFile, options, context);
}, },
]; ];
function createGradleProject( async function createGradleProject(
gradleReport: GradleReport, gradleReport: GradleReport,
gradleFilePath: string, gradleFilePath: string,
options: GradlePluginOptions | undefined, options: GradlePluginOptions | undefined,
context: CreateNodesContext context: CreateNodesContext,
testFiles = []
) { ) {
try { try {
const { const {
@ -177,12 +202,14 @@ function createGradleProject(
string string
>; >;
const { targets, targetGroups } = createGradleTargets( const { targets, targetGroups } = await createGradleTargets(
tasks, tasks,
options, options,
context, context,
outputDirs, outputDirs,
gradleProject gradleProject,
gradleFilePath,
testFiles
); );
const project = { const project = {
name: projectName, name: projectName,
@ -200,16 +227,18 @@ function createGradleProject(
} }
} }
function createGradleTargets( async function createGradleTargets(
tasks: GradleTask[], tasks: GradleTask[],
options: GradlePluginOptions | undefined, options: GradlePluginOptions | undefined,
context: CreateNodesContext, context: CreateNodesContext,
outputDirs: Map<string, string>, outputDirs: Map<string, string>,
gradleProject: string gradleProject: string,
): { gradleFilePath: string,
testFiles: string[] = []
): Promise<{
targetGroups: Record<string, string[]>; targetGroups: Record<string, string[]>;
targets: Record<string, TargetConfiguration>; targets: Record<string, TargetConfiguration>;
} { }> {
const inputsMap = createInputsMap(context); const inputsMap = createInputsMap(context);
const targets: Record<string, TargetConfiguration> = {}; const targets: Record<string, TargetConfiguration> = {};
@ -217,28 +246,43 @@ function createGradleTargets(
for (const task of tasks) { for (const task of tasks) {
const targetName = options?.[`${task.name}TargetName`] ?? task.name; const targetName = options?.[`${task.name}TargetName`] ?? task.name;
const outputs = outputDirs.get(task.name); let outputs = [outputDirs.get(task.name)].filter(Boolean);
if (task.name === 'test') {
outputs = [
outputDirs.get('testReport'),
outputDirs.get('testResults'),
].filter(Boolean);
getTestCiTargets(
testFiles,
gradleProject,
targetName,
options.ciTargetName,
inputsMap['test'],
outputs,
task.type,
targets,
targetGroups
);
}
const taskCommandToRun = `${gradleProject ? gradleProject + ':' : ''}${
task.name
}`;
targets[targetName] = { targets[targetName] = {
command: `${getGradleExecFile()} ${ command: `${getGradleExecFile()} ${taskCommandToRun}`,
gradleProject ? gradleProject + ':' : ''
}${task.name}`,
cache: cacheableTaskType.has(task.type), cache: cacheableTaskType.has(task.type),
inputs: inputsMap[task.name], inputs: inputsMap[task.name],
dependsOn: dependsOnMap[task.name], dependsOn: dependsOnMap[task.name],
metadata: { metadata: {
technologies: ['gradle'], technologies: ['gradle'],
}, },
...(outputs && outputs.length ? { outputs } : {}),
}; };
if (outputs) {
targets[targetName].outputs = [outputs];
}
if (!targetGroups[task.type]) { if (!targetGroups[task.type]) {
targetGroups[task.type] = []; targetGroups[task.type] = [];
} }
targetGroups[task.type].push(task.name); targetGroups[task.type].push(targetName);
} }
return { targetGroups, targets }; return { targetGroups, targets };
} }
@ -257,3 +301,103 @@ function createInputsMap(
: ['default', '^default'], : ['default', '^default'],
}; };
} }
function getTestCiTargets(
testFiles: string[],
gradleProject: string,
testTargetName: string,
ciTargetName: string,
inputs: TargetConfiguration['inputs'],
outputs: string[],
targetGroupName: string,
targets: Record<string, TargetConfiguration>,
targetGroups: Record<string, string[]>
): void {
if (!testFiles || testFiles.length === 0 || !ciTargetName) {
return;
}
const taskCommandToRun = `${gradleProject ? gradleProject + ':' : ''}test`;
if (!targetGroups[targetGroupName]) {
targetGroups[targetGroupName] = [];
}
const dependsOn: TargetConfiguration['dependsOn'] = [];
testFiles.forEach((testFile) => {
const testName = basename(testFile).split('.')[0];
const targetName = ciTargetName + '--' + testName;
targets[targetName] = {
command: `${getGradleExecFile()} ${taskCommandToRun} --tests ${testName}`,
cache: true,
inputs,
dependsOn: dependsOnMap['test'],
metadata: {
technologies: ['gradle'],
description: `Runs Gradle test ${testFile} in CI`,
},
...(outputs && outputs.length > 0 ? { outputs } : {}),
};
targetGroups[targetGroupName].push(targetName);
dependsOn.push({
target: targetName,
projects: 'self',
params: 'forward',
});
});
targets[ciTargetName] = {
executor: 'nx:noop',
cache: true,
inputs,
dependsOn: dependsOn,
...(outputs && outputs.length > 0 ? { outputs } : {}),
metadata: {
technologies: ['gradle'],
description: 'Runs Gradle Tests in CI',
nonAtomizedTarget: testTargetName,
},
};
targetGroups[targetGroupName].push(ciTargetName);
}
function splitConfigFiles(files: readonly string[]): {
configFiles: string[];
testFiles: string[];
projectRoots: string[];
} {
const configFiles = [];
const testFiles = [];
const projectRoots = new Set<string>();
files.forEach((file) => {
if (GRADLE_BUILD_FILES.has(basename(file))) {
configFiles.push(file);
projectRoots.add(dirname(file));
} else {
testFiles.push(file);
}
});
return { configFiles, testFiles, projectRoots: Array.from(projectRoots) };
}
function getGradleProjectRootToTestFilesMap(
testFiles: string[],
projectRoots: string[]
): Record<string, string[]> | undefined {
if (testFiles.length === 0 || projectRoots.length === 0) {
return;
}
const roots = new Map(projectRoots.map((root) => [root, root]));
const testFilesToGradleProjectMap: Record<string, string[]> = {};
testFiles.forEach((testFile) => {
const projectRoot = findProjectForPath(testFile, roots);
if (projectRoot) {
if (!testFilesToGradleProjectMap[projectRoot]) {
testFilesToGradleProjectMap[projectRoot] = [];
}
testFilesToGradleProjectMap[projectRoot].push(testFile);
}
});
return testFilesToGradleProjectMap;
}

View File

@ -7,6 +7,7 @@ import {
normalizePath, normalizePath,
workspaceRoot, workspaceRoot,
} from '@nx/devkit'; } from '@nx/devkit';
import { combineGlobPatterns } from 'nx/src/utils/globs';
import { execGradleAsync } from './exec-gradle'; import { execGradleAsync } from './exec-gradle';
import { hashWithWorkspaceContext } from 'nx/src/utils/workspace-context'; import { hashWithWorkspaceContext } from 'nx/src/utils/workspace-context';
@ -30,7 +31,20 @@ export interface GradleReport {
let gradleReportCache: GradleReport; let gradleReportCache: GradleReport;
let gradleCurrentConfigHash: string; let gradleCurrentConfigHash: string;
export const gradleConfigGlob = '**/build.{gradle.kts,gradle}'; export const GRADLE_BUILD_FILES = new Set(['build.gradle', 'build.gradle.kts']);
export const GRADLE_TEST_FILES = [
'**/src/test/java/**/*.java',
'**/src/test/kotlin/**/*.kt',
];
export const gradleConfigGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`)
);
export const gradleConfigAndTestGlob = combineGlobPatterns(
...Array.from(GRADLE_BUILD_FILES).map((file) => `**/${file}`),
...GRADLE_TEST_FILES
);
export function getCurrentGradleReport() { export function getCurrentGradleReport() {
if (!gradleReportCache) { if (!gradleReportCache) {