fix(gradle): read tasks from properties report (#29124)

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

Some gradle tasks are not real tasks. They exist in `tasks.txt` because it is derived from subprojects should not be a real target for that project.

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

Gradle projects only have real gradle tasks which are not derived from their subproject tasks. 

## 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-12-05 10:53:29 -08:00 committed by GitHub
parent 7328a8dae4
commit 37adb48db8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 280 additions and 44 deletions

View File

@ -29,8 +29,7 @@ describe('Gradle', () => {
expect(projects).toContain('utilities');
expect(projects).toContain(gradleProjectName);
let buildOutput = runCLI('build app', { verbose: true });
// app depends on list and utilities
const buildOutput = runCLI('build app', { verbose: true });
expect(buildOutput).toContain('nx run list:build');
expect(buildOutput).toContain(':list:classes');
expect(buildOutput).toContain('nx run utilities:build');
@ -41,15 +40,6 @@ describe('Gradle', () => {
`list/build/libs/list.jar`,
`utilities/build/libs/utilities.jar`
);
buildOutput = runCLI(`build ${gradleProjectName}`, { verbose: true });
// root project depends on app, list and utilities
expect(buildOutput).toContain('nx run app:build');
expect(buildOutput).toContain(':app:classes');
expect(buildOutput).toContain('nx run list:build');
expect(buildOutput).toContain(':list:classes');
expect(buildOutput).toContain('nx run utilities:build');
expect(buildOutput).toContain(':utilities:classes');
});
it('should track dependencies for new app', () => {

View File

@ -44,8 +44,11 @@ export function TargetConfigurationGroupList({
if (hasGroups) {
return (
<>
{Object.entries(targetsGroup.groups).map(
([targetGroupName, targets]) => {
{Object.entries(targetsGroup.groups)
.sort(([targetGroupName1], [targetGroupName2]) =>
targetGroupName1.localeCompare(targetGroupName2)
)
.map(([targetGroupName, targets]) => {
return (
<TargetConfigurationGroupContainer
targetGroupName={targetGroupName}
@ -71,8 +74,7 @@ export function TargetConfigurationGroupList({
</ul>
</TargetConfigurationGroupContainer>
);
}
)}
})}
<TargetConfigurationGroupContainer
targetGroupName="Others"
targetsNumber={targetsGroup.targets.length}

View File

@ -11,6 +11,12 @@
"cli": "nx",
"description": "This function changes !{projectRoot}/test/**/* in nx.json for production to !{projectRoot}/src/test/**/*",
"factory": "./src/migrations/19-4-1/change-regex-test-production"
},
"add-include-subprojects-tasks": {
"version": "20.2.0-beta.4",
"cli": "nx",
"description": "Add includeSubprojectsTasks to build.gradle file",
"factory": "./src/migrations/20-2-0/add-include-subprojects-tasks"
}
},
"packageJsonUpdates": {}

View File

@ -27,7 +27,7 @@ jobs:
main-branch-name: 'main'
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
- run: ./nx affected --base=$NX_BASE --head=$NX_HEAD -t test build
- run: ./nx affected --base=$NX_BASE --head=$NX_HEAD -t build
workflows:
version: 2
@ -78,7 +78,7 @@ jobs:
- uses: nrwl/nx-set-shas@v4
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
- run: ./nx affected -t test build
- run: ./nx affected -t build
"
`;
@ -109,7 +109,7 @@ jobs:
main-branch-name: 'main'
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
- run: ./nx affected --base=$NX_BASE --head=$NX_HEAD -t test build
- run: ./nx affected --base=$NX_BASE --head=$NX_HEAD -t build
workflows:
version: 2
@ -160,6 +160,6 @@ jobs:
- uses: nrwl/nx-set-shas@v4
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
- run: ./nx affected -t test build
- run: ./nx affected -t build
"
`;

View File

@ -19,7 +19,7 @@ function getCiCommands(ci: Schema['ci']): Command[] {
comment: `# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected`,
},
{
command: `./nx affected --base=$NX_BASE --head=$NX_HEAD -t test build`,
command: `./nx affected --base=$NX_BASE --head=$NX_HEAD -t build`,
},
];
}
@ -28,7 +28,7 @@ function getCiCommands(ci: Schema['ci']): Command[] {
{
comment: `# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected`,
},
{ command: `./nx affected -t test build` },
{ command: `./nx affected -t build` },
];
}
}

View File

@ -24,6 +24,7 @@ describe('@nx/gradle:init', () => {
"options": {
"buildTargetName": "build",
"classesTargetName": "classes",
"includeSubprojectsTasks": false,
"testTargetName": "test",
},
"plugin": "@nx/gradle",
@ -48,6 +49,7 @@ describe('@nx/gradle:init', () => {
"options": {
"buildTargetName": "build",
"classesTargetName": "classes",
"includeSubprojectsTasks": false,
"testTargetName": "test",
},
"plugin": "@nx/gradle",

View File

@ -52,6 +52,7 @@ function addPlugin(tree: Tree) {
testTargetName: 'test',
classesTargetName: 'classes',
buildTargetName: 'build',
includeSubprojectsTasks: false,
},
});
updateNxJson(tree, nxJson);

View File

@ -0,0 +1,63 @@
import { Tree, readNxJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-include-subprojects-tasks';
describe('AddIncludeSubprojectsTasks', () => {
let tree: Tree;
beforeAll(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should not change nx.json if @nx/gradle is not added', async () => {
tree.write('nx.json', JSON.stringify({ namedInputs: {} }));
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"namedInputs": {},
}
`);
});
it('should add includeSubprojectsTasks to @nx/gradle plugin', async () => {
tree.write('nx.json', JSON.stringify({ plugins: ['@nx/gradle'] }));
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"plugins": [
{
"options": {
"includeSubprojectsTasks": true,
},
"plugin": "@nx/gradle",
},
],
}
`);
});
it('should add includeSubprojectsTasks to @nx/gradle plugin with options', async () => {
tree.write(
'nx.json',
JSON.stringify({
plugins: [
{ plugin: '@nx/gradle', options: { testTargetName: 'test' } },
],
})
);
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"plugins": [
{
"options": {
"includeSubprojectsTasks": true,
"testTargetName": "test",
},
"plugin": "@nx/gradle",
},
],
}
`);
});
});

View File

@ -0,0 +1,32 @@
import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
import { GradlePluginOptions } from '../../plugin/nodes';
// This function add options includeSubprojectsTasks as true in nx.json for gradle plugin
export default function update(tree: Tree) {
const nxJson = readNxJson(tree);
if (!nxJson) {
return;
}
if (!hasGradlePlugin(tree)) {
return;
}
let gradlePluginIndex = nxJson.plugins.findIndex((p) =>
typeof p === 'string' ? p === '@nx/gradle' : p.plugin === '@nx/gradle'
);
let gradlePlugin = nxJson.plugins[gradlePluginIndex];
if (typeof gradlePlugin === 'string') {
gradlePlugin = {
plugin: '@nx/gradle',
options: {
includeSubprojectsTasks: true,
},
};
nxJson.plugins[gradlePluginIndex] = gradlePlugin;
} else {
gradlePlugin.options ??= {};
(gradlePlugin.options as GradlePluginOptions).includeSubprojectsTasks =
true;
}
updateNxJson(tree, nxJson);
}

View File

@ -30,8 +30,17 @@ describe('@nx/gradle/plugin', () => {
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['proj/build.gradle', new Map([['build', 'build']])],
]),
gradleProjectToTasksMap: new Map<string, Set<string>>([
['proj', new Set(['test'])],
]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Verification']])],
[
'proj',
new Map([
['test', 'Verification'],
['build', 'Build'],
]),
],
]),
gradleProjectToProjectName: new Map<string, string>([['proj', 'proj']]),
gradleProjectNameToProjectRootMap: new Map<string, string>([
@ -90,6 +99,7 @@ describe('@nx/gradle/plugin', () => {
],
},
"name": "proj",
"projectType": "application",
"targets": {
"test": {
"cache": true,
@ -129,6 +139,111 @@ describe('@nx/gradle/plugin', () => {
`);
});
it('should create nodes include subprojects tasks', async () => {
const results = await createNodesFunction(
['proj/build.gradle'],
{
buildTargetName: 'build',
includeSubprojectsTasks: true,
},
context
);
expect(results).toMatchInlineSnapshot(`
[
[
"proj/build.gradle",
{
"projects": {
"proj": {
"metadata": {
"targetGroups": {
"Build": [
"build",
],
"Verification": [
"test",
],
},
"technologies": [
"gradle",
],
},
"name": "proj",
"projectType": "application",
"targets": {
"build": {
"cache": true,
"command": "./gradlew proj:build",
"dependsOn": [
"^build",
"classes",
"test",
],
"inputs": [
"production",
"^production",
],
"metadata": {
"help": {
"command": "./gradlew help --task proj:build",
"example": {
"options": {
"args": [
"--rerun",
],
},
},
},
"technologies": [
"gradle",
],
},
"options": {
"cwd": ".",
},
"outputs": [
"build",
],
},
"test": {
"cache": true,
"command": "./gradlew proj:test",
"dependsOn": [
"testClasses",
],
"inputs": [
"default",
"^production",
],
"metadata": {
"help": {
"command": "./gradlew help --task proj:test",
"example": {
"options": {
"args": [
"--rerun",
],
},
},
},
"technologies": [
"gradle",
],
},
"options": {
"cwd": ".",
},
},
},
},
},
},
],
]
`);
});
it('should create nodes based on gradle for nested project root', async () => {
gradleReport = {
gradleFileToGradleProjectMap: new Map<string, string>([
@ -138,6 +253,9 @@ describe('@nx/gradle/plugin', () => {
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
gradleProjectToTasksMap: new Map<string, Set<string>>([
['proj', new Set(['test'])],
]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Verification']])],
]),
@ -177,6 +295,7 @@ describe('@nx/gradle/plugin', () => {
],
},
"name": "proj",
"projectType": "application",
"targets": {
"test": {
"cache": true,
@ -226,6 +345,9 @@ describe('@nx/gradle/plugin', () => {
gradleFileToOutputDirsMap: new Map<string, Map<string, string>>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
gradleProjectToTasksMap: new Map<string, Set<string>>([
['proj', new Set(['test'])],
]),
gradleProjectToTasksTypeMap: new Map<string, Map<string, string>>([
['proj', new Map([['test', 'Test']])],
]),
@ -290,6 +412,7 @@ describe('@nx/gradle/plugin', () => {
],
},
"name": "proj",
"projectType": "application",
"targets": {
"test": {
"cache": false,

View File

@ -31,7 +31,7 @@ import { getGradleExecFile, findGraldewFile } from '../utils/exec-gradle';
const cacheableTaskType = new Set(['Build', 'Verification']);
const dependsOnMap = {
build: ['^build', 'classes'],
build: ['^build', 'classes', 'test'],
testClasses: ['classes'],
test: ['testClasses'],
classes: ['^classes'],
@ -43,11 +43,12 @@ interface GradleTask {
}
export interface GradlePluginOptions {
includeSubprojectsTasks?: boolean; // default is false, show all gradle tasks in the project
ciTargetName?: string;
testTargetName?: string;
classesTargetName?: string;
buildTargetName?: string;
[taskTargetName: string]: string | undefined;
[taskTargetName: string]: string | undefined | boolean;
}
function normalizeOptions(options: GradlePluginOptions): GradlePluginOptions {
@ -58,14 +59,7 @@ function normalizeOptions(options: GradlePluginOptions): GradlePluginOptions {
return options;
}
type GradleTargets = Record<
string,
{
name: string;
targets: Record<string, TargetConfiguration>;
metadata: ProjectConfiguration['metadata'];
}
>;
type GradleTargets = Record<string, Partial<ProjectConfiguration>>;
function readTargetsCache(cachePath: string): GradleTargets {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
@ -180,6 +174,7 @@ async function createGradleProject(
try {
const {
gradleProjectToTasksTypeMap,
gradleProjectToTasksMap,
gradleFileToOutputDirsMap,
gradleFileToGradleProjectMap,
gradleProjectToProjectName,
@ -193,17 +188,27 @@ async function createGradleProject(
return;
}
const tasksTypeMap = gradleProjectToTasksTypeMap.get(gradleProject) as Map<
string,
string
>;
const tasksTypeMap: Map<string, string> = gradleProjectToTasksTypeMap.get(
gradleProject
) as Map<string, string>;
const tasksSet = gradleProjectToTasksMap.get(gradleProject) as Set<string>;
let tasks: GradleTask[] = [];
for (let [taskName, taskType] of tasksTypeMap.entries()) {
tasksSet.forEach((taskName) => {
tasks.push({
type: tasksTypeMap.get(taskName) as string,
name: taskName,
});
});
if (options.includeSubprojectsTasks) {
tasksTypeMap.forEach((taskType, taskName) => {
if (!tasksSet.has(taskName)) {
tasks.push({
type: taskType,
name: taskName,
});
}
});
}
const outputDirs = gradleFileToOutputDirsMap.get(gradleFilePath) as Map<
string,
@ -219,8 +224,9 @@ async function createGradleProject(
gradleFilePath,
testFiles
);
const project = {
const project: Partial<ProjectConfiguration> = {
name: projectName,
projectType: 'application',
targets,
metadata: {
targetGroups,
@ -266,7 +272,7 @@ async function createGradleTargets(
getTestCiTargets(
testFiles,
gradleProject,
targetName,
targetName as string,
options.ciTargetName,
inputsMap['test'],
outputs,
@ -281,7 +287,7 @@ async function createGradleTargets(
task.name
}`;
targets[targetName] = {
targets[targetName as string] = {
command: `${getGradleExecFile()} ${taskCommandToRun}`,
options: {
cwd: gradlewFileDirectory,
@ -303,10 +309,12 @@ async function createGradleTargets(
...(outputs && outputs.length ? { outputs } : {}),
};
if (task.type) {
if (!targetGroups[task.type]) {
targetGroups[task.type] = [];
}
targetGroups[task.type].push(targetName);
targetGroups[task.type].push(targetName as string);
}
}
return { targetGroups, targets };
}

View File

@ -21,6 +21,7 @@ export interface GradleReport {
buildFileToDepsMap: Map<string, string>;
gradleFileToOutputDirsMap: Map<string, Map<string, string>>;
gradleProjectToTasksTypeMap: Map<string, Map<string, string>>;
gradleProjectToTasksMap: Map<string, Set<String>>;
gradleProjectToProjectName: Map<string, string>;
gradleProjectNameToProjectRootMap: Map<string, string>;
gradleProjectToChildProjects: Map<string, string[]>;
@ -105,6 +106,7 @@ 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 gradleProjectToProjectName = new Map<string, string>();
const gradleProjectNameToProjectRootMap = new Map<string, string>();
/**
@ -159,6 +161,7 @@ export function processProjectReports(
absBuildFilePath: string,
absBuildDirPath: string;
const outputDirMap = new Map<string, string>();
const tasks = new Set<string>();
for (const line of propertyReportLines) {
if (line.startsWith('name: ')) {
projectName = line.substring('name: '.length);
@ -190,6 +193,10 @@ export function processProjectReports(
`{workspaceRoot}/${relative(workspaceRoot, dirPath)}`
);
}
if (line.includes(': task ')) {
const [task] = line.split(': task ');
tasks.add(task);
}
}
if (!projectName || !absBuildFilePath || !absBuildDirPath) {
@ -217,6 +224,7 @@ export function processProjectReports(
gradleProject,
dirname(buildFile)
);
gradleProjectToTasksMap.set(gradleProject, tasks);
}
if (line.endsWith('taskReport')) {
const gradleProject = line.substring(
@ -267,6 +275,7 @@ export function processProjectReports(
buildFileToDepsMap,
gradleFileToOutputDirsMap,
gradleProjectToTasksTypeMap,
gradleProjectToTasksMap,
gradleProjectToProjectName,
gradleProjectNameToProjectRootMap,
gradleProjectToChildProjects,