diff --git a/e2e/gradle/src/gradle.test.ts b/e2e/gradle/src/gradle.test.ts
index b2e48f44e4a9d..856f77b43c592 100644
--- a/e2e/gradle/src/gradle.test.ts
+++ b/e2e/gradle/src/gradle.test.ts
@@ -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', () => {
diff --git a/graph/ui-project-details/src/lib/target-configuration-details-group-list/target-configuration-details-group-list.tsx b/graph/ui-project-details/src/lib/target-configuration-details-group-list/target-configuration-details-group-list.tsx
index bbb8c5031a9d8..218df08b2b444 100644
--- a/graph/ui-project-details/src/lib/target-configuration-details-group-list/target-configuration-details-group-list.tsx
+++ b/graph/ui-project-details/src/lib/target-configuration-details-group-list/target-configuration-details-group-list.tsx
@@ -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 (
);
- }
- )}
+ })}
{
"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",
diff --git a/packages/gradle/src/generators/init/init.ts b/packages/gradle/src/generators/init/init.ts
index 77ba771b76656..2a7f9028ec457 100644
--- a/packages/gradle/src/generators/init/init.ts
+++ b/packages/gradle/src/generators/init/init.ts
@@ -52,6 +52,7 @@ function addPlugin(tree: Tree) {
testTargetName: 'test',
classesTargetName: 'classes',
buildTargetName: 'build',
+ includeSubprojectsTasks: false,
},
});
updateNxJson(tree, nxJson);
diff --git a/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.spec.ts b/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.spec.ts
new file mode 100644
index 0000000000000..ad989cbc00abe
--- /dev/null
+++ b/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.spec.ts
@@ -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",
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.ts b/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.ts
new file mode 100644
index 0000000000000..b81506bbdf62c
--- /dev/null
+++ b/packages/gradle/src/migrations/20-2-0/add-include-subprojects-tasks.ts
@@ -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);
+}
diff --git a/packages/gradle/src/plugin/nodes.spec.ts b/packages/gradle/src/plugin/nodes.spec.ts
index 47cbf615bcf03..2860777af9a02 100644
--- a/packages/gradle/src/plugin/nodes.spec.ts
+++ b/packages/gradle/src/plugin/nodes.spec.ts
@@ -30,8 +30,17 @@ describe('@nx/gradle/plugin', () => {
gradleFileToOutputDirsMap: new Map>([
['proj/build.gradle', new Map([['build', 'build']])],
]),
+ gradleProjectToTasksMap: new Map>([
+ ['proj', new Set(['test'])],
+ ]),
gradleProjectToTasksTypeMap: new Map>([
- ['proj', new Map([['test', 'Verification']])],
+ [
+ 'proj',
+ new Map([
+ ['test', 'Verification'],
+ ['build', 'Build'],
+ ]),
+ ],
]),
gradleProjectToProjectName: new Map([['proj', 'proj']]),
gradleProjectNameToProjectRootMap: new Map([
@@ -90,7 +99,113 @@ describe('@nx/gradle/plugin', () => {
],
},
"name": "proj",
+ "projectType": "application",
+ "targets": {
+ "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 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",
@@ -138,6 +253,9 @@ describe('@nx/gradle/plugin', () => {
gradleFileToOutputDirsMap: new Map>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
+ gradleProjectToTasksMap: new Map>([
+ ['proj', new Set(['test'])],
+ ]),
gradleProjectToTasksTypeMap: new Map>([
['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>([
['nested/nested/proj/build.gradle', new Map([['build', 'build']])],
]),
+ gradleProjectToTasksMap: new Map>([
+ ['proj', new Set(['test'])],
+ ]),
gradleProjectToTasksTypeMap: new Map>([
['proj', new Map([['test', 'Test']])],
]),
@@ -290,6 +412,7 @@ describe('@nx/gradle/plugin', () => {
],
},
"name": "proj",
+ "projectType": "application",
"targets": {
"test": {
"cache": false,
diff --git a/packages/gradle/src/plugin/nodes.ts b/packages/gradle/src/plugin/nodes.ts
index f17aae096cbff..cbedf477d1b37 100644
--- a/packages/gradle/src/plugin/nodes.ts
+++ b/packages/gradle/src/plugin/nodes.ts
@@ -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;
- metadata: ProjectConfiguration['metadata'];
- }
->;
+type GradleTargets = Record>;
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,16 +188,26 @@ async function createGradleProject(
return;
}
- const tasksTypeMap = gradleProjectToTasksTypeMap.get(gradleProject) as Map<
- string,
- string
- >;
+ const tasksTypeMap: Map = gradleProjectToTasksTypeMap.get(
+ gradleProject
+ ) as Map;
+ const tasksSet = gradleProjectToTasksMap.get(gradleProject) as Set;
let tasks: GradleTask[] = [];
- for (let [taskName, taskType] of tasksTypeMap.entries()) {
+ tasksSet.forEach((taskName) => {
tasks.push({
- type: taskType,
+ 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<
@@ -219,8 +224,9 @@ async function createGradleProject(
gradleFilePath,
testFiles
);
- const project = {
+ const project: Partial = {
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 (!targetGroups[task.type]) {
- targetGroups[task.type] = [];
+ if (task.type) {
+ if (!targetGroups[task.type]) {
+ targetGroups[task.type] = [];
+ }
+ targetGroups[task.type].push(targetName as string);
}
- targetGroups[task.type].push(targetName);
}
return { targetGroups, targets };
}
diff --git a/packages/gradle/src/utils/get-gradle-report.ts b/packages/gradle/src/utils/get-gradle-report.ts
index 1d689890e6ce5..254e8b16c36bc 100644
--- a/packages/gradle/src/utils/get-gradle-report.ts
+++ b/packages/gradle/src/utils/get-gradle-report.ts
@@ -21,6 +21,7 @@ export interface GradleReport {
buildFileToDepsMap: Map;
gradleFileToOutputDirsMap: Map>;
gradleProjectToTasksTypeMap: Map>;
+ gradleProjectToTasksMap: Map>;
gradleProjectToProjectName: Map;
gradleProjectNameToProjectRootMap: Map;
gradleProjectToChildProjects: Map;
@@ -105,6 +106,7 @@ export function processProjectReports(
* Map of Gradle Build File to tasks type map
*/
const gradleProjectToTasksTypeMap = new Map>();
+ const gradleProjectToTasksMap = new Map>();
const gradleProjectToProjectName = new Map();
const gradleProjectNameToProjectRootMap = new Map();
/**
@@ -159,6 +161,7 @@ export function processProjectReports(
absBuildFilePath: string,
absBuildDirPath: string;
const outputDirMap = new Map();
+ const tasks = new Set();
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,