Repository: nrwl/nx
Pull request #31304: fix(gradle): add migration script to check gradle plugin version
Author: login: xiongemi
Created: 2025-05-22T02:46:20Z | Merged: 2025-05-26T17:59:29Z

<!-- 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 migrate @nx/gradle to latest, we don't check the dev.nx.gradle.project-graph version in build file

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR -->
we should check the project-praph in the build file and make sure it is correct version

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is merged. -->

Fixes #



Diff:
diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json
index d9013d9d83da3..3e55d0410fa95 100644
--- a/docs/generated/manifests/nx-api.json
+++ b/docs/generated/manifests/nx-api.json
@@ -2145,6 +2145,16 @@
       }
     },
     "migrations": {
+      "/nx-api/gradle/migrations/change-plugin-version-0-1-0": {
+        "description": "Change dev.nx.gradle.project-graph to version 0.1.0 in build file",
+        "file": "generated/packages/gradle/migrations/change-plugin-version-0-1-0.json",
+        "hidden": false,
+        "name": "change-plugin-version-0-1-0",
+        "version": "21.1.2-beta.1",
+        "originalFilePath": "/packages/gradle",
+        "path": "/nx-api/gradle/migrations/change-plugin-version-0-1-0",
+        "type": "migration"
+      },
       "/nx-api/gradle/migrations/change-ciTargetName-to-ciTestTargetName": {
         "description": "Change @nx/gradle option from ciTargetName to ciTestTargetName",
         "file": "generated/packages/gradle/migrations/change-ciTargetName-to-ciTestTargetName.json",
diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json
index abe34799bdcfc..87e1dec7210f6 100644
--- a/docs/generated/packages-metadata.json
+++ b/docs/generated/packages-metadata.json
@@ -2129,6 +2129,16 @@
       }
     ],
     "migrations": [
+      {
+        "description": "Change dev.nx.gradle.project-graph to version 0.1.0 in build file",
+        "file": "generated/packages/gradle/migrations/change-plugin-version-0-1-0.json",
+        "hidden": false,
+        "name": "change-plugin-version-0-1-0",
+        "version": "21.1.2-beta.1",
+        "originalFilePath": "/packages/gradle",
+        "path": "gradle/migrations/change-plugin-version-0-1-0",
+        "type": "migration"
+      },
       {
         "description": "Change @nx/gradle option from ciTargetName to ciTestTargetName",
         "file": "generated/packages/gradle/migrations/change-ciTargetName-to-ciTestTargetName.json",
diff --git a/docs/generated/packages/gradle/migrations/change-plugin-version-0-1-0.json b/docs/generated/packages/gradle/migrations/change-plugin-version-0-1-0.json
new file mode 100644
index 0000000000000..0f93b9a0eb5fa
--- /dev/null
+++ b/docs/generated/packages/gradle/migrations/change-plugin-version-0-1-0.json
@@ -0,0 +1,14 @@
+{
+  "name": "change-plugin-version-0-1-0",
+  "version": "21.1.2-beta.1",
+  "cli": "nx",
+  "description": "Change dev.nx.gradle.project-graph to version 0.1.0 in build file",
+  "factory": "./src/migrations/21-1-2/change-plugin-version-0-1-0",
+  "implementation": "/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.ts",
+  "aliases": [],
+  "hidden": false,
+  "path": "/packages/gradle",
+  "schema": null,
+  "type": "migration",
+  "examplesFile": "#### Change dev.nx.gradle.project-graph to version 0.1.0\n\nChange dev.nx.gradle.project-graph to version 0.1.0 in build file\n\n#### Sample Code Changes\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```{% fileName=\"build.gradle\" %}\nplugins {\n\tid \"dev.nx.gradle.project-graph\" version \"0.0.5\"\n}\n```\n\n{% /tab %}\n{% tab label=\"After\" %}\n\n```{% fileName=\"build.gradle\" %}\nplugins {\n    id \"dev.nx.gradle.project-graph\" version \"0.1.0\"\n}\n```\n\n{% /tab %}\n{% /tabs %}\n"
+}
diff --git a/packages/gradle/migrations.json b/packages/gradle/migrations.json
index a6f4a99f7ff68..0ad4c2bfae560 100644
--- a/packages/gradle/migrations.json
+++ b/packages/gradle/migrations.json
@@ -29,6 +29,12 @@
       "cli": "nx",
       "description": "Change @nx/gradle option from ciTargetName to ciTestTargetName",
       "factory": "./src/migrations/21-0-0/change-ciTargetName-to-ciTestTargetName"
+    },
+    "change-plugin-version-0-1-0": {
+      "version": "21.1.2-beta.1",
+      "cli": "nx",
+      "description": "Change dev.nx.gradle.project-graph to version 0.1.0 in build file",
+      "factory": "./src/migrations/21-1-2/change-plugin-version-0-1-0"
     }
   },
   "packageJsonUpdates": {}
diff --git a/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.spec.ts b/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.spec.ts
new file mode 100644
index 0000000000000..cc96cc88ae515
--- /dev/null
+++ b/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.spec.ts
@@ -0,0 +1,272 @@
+import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
+import { Tree } from '@nx/devkit';
+import { FsTree } from 'nx/src/generators/tree';
+import {
+  addNxProjectGraphPlugin,
+  extractNxPluginVersion,
+  updateNxPluginVersion,
+} from './gradle-project-graph-plugin-utils';
+import { gradleProjectGraphPluginName } from '../../utils/versions';
+import * as execGradle from '../../utils/exec-gradle';
+
+jest.mock('../../utils/exec-gradle', () => ({
+  findGradlewFile: jest.fn(),
+  execGradleAsync: jest.fn(),
+}));
+
+const mockFindGradlewFile = execGradle.findGradlewFile as jest.Mock;
+const mockExecGradleAsync = execGradle.execGradleAsync as jest.Mock;
+
+describe('Gradle Project Graph Plugin Utils', () => {
+  describe('extractNxPluginVersion', () => {
+    beforeEach(() => {
+      jest.clearAllMocks();
+    });
+
+    it.each([
+      ['id "dev.nx.gradle.project-graph" version "+"', '+'],
+      ['id("dev.nx.gradle.project-graph") version("0.1.0")', '0.1.0'],
+      ['id(\'dev.nx.gradle.project-graph\') version("0.1.0")', '0.1.0'],
+      ["id('dev.nx.gradle.project-graph') version('0.1.0')", '0.1.0'],
+    ])('should extract version from %s', async (input, expected) => {
+      const version = await extractNxPluginVersion('build.gradle.kts', input);
+      expect(version).toBe(expected);
+    });
+
+    it('should return version from buildEnvironment fallback if inline version is missing', async () => {
+      const gradleEnvOutput = `
+      classpath
+      \\--- dev.nx.gradle.project-graph:dev.nx.gradle.project-graph.gradle.plugin:0.2.3
+           \\--- dev.nx.gradle:project-graph:0.2.3
+    `;
+
+      mockFindGradlewFile.mockReturnValue('./gradlew');
+      mockExecGradleAsync.mockResolvedValue(gradleEnvOutput);
+
+      const version = await extractNxPluginVersion(
+        'build.gradle.kts',
+        'some unrelated content'
+      );
+      expect(version).toBe('0.2.3');
+      expect(mockFindGradlewFile).toHaveBeenCalled();
+      expect(mockExecGradleAsync).toHaveBeenCalled();
+    });
+
+    it('should return null when no version is found anywhere', async () => {
+      mockFindGradlewFile.mockImplementation(() => {
+        throw new Error('not found');
+      });
+
+      const version = await extractNxPluginVersion(
+        'build.gradle.kts',
+        'no version here'
+      );
+      expect(version).toBeNull();
+    });
+  });
+
+  describe('updateNxPluginVersion', () => {
+    it('should update version in Groovy DSL format', () => {
+      const input = 'id "dev.nx.gradle.project-graph" version "1.0.0"';
+      const expected = 'id "dev.nx.gradle.project-graph" version "2.0.0"';
+      expect(updateNxPluginVersion(input, '2.0.0')).toBe(expected);
+    });
+
+    it('should update version in Kotlin DSL format', () => {
+      const input = 'id("dev.nx.gradle.project-graph") version("1.0.0")';
+      const expected = 'id("dev.nx.gradle.project-graph") version("2.0.0")';
+      expect(updateNxPluginVersion(input, '2.0.0')).toBe(expected);
+    });
+  });
+
+  describe('addNxProjectGraphPlugin', () => {
+    let tempFs: TempFs;
+    let cwd: string;
+    let tree: Tree;
+
+    beforeEach(async () => {
+      tempFs = new TempFs('test');
+      cwd = process.cwd();
+      process.chdir(tempFs.tempDir);
+      tree = new FsTree(tempFs.tempDir, false);
+    });
+
+    afterEach(() => {
+      jest.resetModules();
+      process.chdir(cwd);
+    });
+
+    describe('Groovy DSL (build.gradle)', () => {
+      it('should add plugin to existing plugins block', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': `plugins {
+    id 'java'
+}`,
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).toMatch(
+          /plugins\s*{\s*id\s*['"]dev\.nx\.gradle\.project-graph['"]\s*version\s*['"][^'"]+['"]\s*id\s*['"]java['"]/
+        );
+        expect(content).toMatch(
+          /allprojects\s*{\s*apply\s*{\s*plugin\(['"]dev\.nx\.gradle\.project-graph['"]\)\s*}\s*}/
+        );
+      });
+
+      it('should use passed in expected version', async () => {
+        const expectedVersion = '2.0.0';
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': `plugins {
+    id 'java'
+}`,
+        });
+
+        await addNxProjectGraphPlugin(tree, expectedVersion);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).toContain(
+          `id "${gradleProjectGraphPluginName}" version "${expectedVersion}"`
+        );
+        expect(content).toMatch(
+          /allprojects\s*{\s*apply\s*{\s*plugin\(['"]dev\.nx\.gradle\.project-graph['"]\)\s*}\s*}/
+        );
+      });
+
+      it('should create plugins block if missing', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': 'apply plugin: "java"',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).toMatch(
+          /^plugins\s*{\s*id\s*['"]dev\.nx\.gradle\.project-graph['"]\s*version\s*['"][^'"]+['"]\s*}\s*apply plugin:/
+        );
+      });
+
+      it('should update existing plugin version', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': `plugins {
+    id "dev.nx.gradle.project-graph" version "1.0.0"
+}`,
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).not.toContain('version "1.0.0"');
+        expect(content).toContain('allprojects {');
+      });
+    });
+
+    describe('Kotlin DSL (build.gradle.kts)', () => {
+      it('should add plugin to existing plugins block', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle.kts': '',
+          'proj/build.gradle.kts': `plugins {
+    id("java")
+}`,
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle.kts', 'utf-8');
+        expect(content).toMatch(
+          /plugins\s*{\s*id\(['"]dev\.nx\.gradle\.project-graph['"]\)\s*version\(['"][^'"]+['"]\)\s*id\(['"]java['"]\)/
+        );
+        expect(content).toMatch(
+          /allprojects\s*{\s*apply\s*{\s*plugin\(['"]dev\.nx\.gradle\.project-graph['"]\)\s*}\s*}/
+        );
+      });
+
+      it('should create plugins block if missing', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle.kts': '',
+          'proj/build.gradle.kts': 'apply(plugin = "java")',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle.kts', 'utf-8');
+        expect(content).toMatch(
+          /^plugins\s*{\s*id\(['"]dev\.nx\.gradle\.project-graph['"]\)\s*version\(['"][^'"]+['"]\)\s*}\s*apply\(plugin =/
+        );
+      });
+    });
+
+    describe('Multiple projects', () => {
+      it('should handle multiple build.gradle files', async () => {
+        await tempFs.createFiles({
+          'proj1/settings.gradle': '',
+          'proj1/build.gradle': 'apply plugin: "java"',
+          'proj2/settings.gradle': '',
+          'proj2/build.gradle': 'apply plugin: "java"',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const proj1Content = tree.read('proj1/build.gradle', 'utf-8');
+        const proj2Content = tree.read('proj2/build.gradle', 'utf-8');
+
+        expect(proj1Content).toContain(`id "${gradleProjectGraphPluginName}"`);
+        expect(proj2Content).toContain(`id "${gradleProjectGraphPluginName}"`);
+      });
+
+      it('should handle mixed Groovy and Kotlin DSL projects', async () => {
+        await tempFs.createFiles({
+          'groovy/settings.gradle': '',
+          'groovy/build.gradle': 'apply plugin: "java"',
+          'kotlin/settings.gradle.kts': '',
+          'kotlin/build.gradle.kts': 'apply(plugin = "java")',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const groovyContent = tree.read('groovy/build.gradle', 'utf-8');
+        const kotlinContent = tree.read('kotlin/build.gradle.kts', 'utf-8');
+
+        expect(groovyContent).toContain(`id "${gradleProjectGraphPluginName}"`);
+        expect(kotlinContent).toContain(
+          `id("${gradleProjectGraphPluginName}")`
+        );
+      });
+    });
+
+    describe('Edge cases', () => {
+      it('should handle empty build.gradle file', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': '',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).toMatch(
+          /^plugins\s*{\s*id\s*['"]dev\.nx\.gradle\.project-graph['"]\s*version\s*['"][^'"]+['"]\s*}\s*allprojects/
+        );
+      });
+
+      it('should handle build.gradle with only comments', async () => {
+        await tempFs.createFiles({
+          'proj/settings.gradle': '',
+          'proj/build.gradle': '// Some comments\n// More comments',
+        });
+
+        await addNxProjectGraphPlugin(tree);
+
+        const content = tree.read('proj/build.gradle', 'utf-8');
+        expect(content).toMatch(
+          /^plugins\s*{\s*id\s*['"]dev\.nx\.gradle\.project-graph['"]\s*version\s*['"][^'"]+['"]\s*}\s*\/\/ Some comments/
+        );
+      });
+    });
+  });
+});
diff --git a/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.ts b/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.ts
new file mode 100644
index 0000000000000..4440759f88121
--- /dev/null
+++ b/packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.ts
@@ -0,0 +1,207 @@
+import { globAsync, logger, Tree, workspaceRoot } from '@nx/devkit';
+import {
+  gradleProjectGraphPluginName,
+  gradleProjectGraphVersion,
+} from '../../utils/versions';
+import { dirname, join } from 'path';
+import { execGradleAsync, findGradlewFile } from '../../utils/exec-gradle';
+
+/**
+ * Adds a `build.gradle(.kts)` file next to each `settings.gradle(.kts)` file found in the workspace.
+ * If the build.gradle file already exists, it reads its contents.
+ */
+export async function addBuildGradleFileNextToSettingsGradle(
+  tree: Tree
+): Promise<{ filePath: string; content: string }[]> {
+  const settingsGradleFiles = await globAsync(tree, [
+    '**/settings.gradle',
+    '**/settings.gradle.kts',
+  ]);
+
+  return settingsGradleFiles.map((settingsGradlePath) => {
+    return ensureBuildGradleFile(tree, settingsGradlePath);
+  });
+}
+
+/**
+ * Determines the appropriate build.gradle file path based on the settings file
+ * and ensures it exists in the tree. Returns the path and contents.
+ */
+function ensureBuildGradleFile(
+  tree: Tree,
+  settingsGradlePath: string
+): { filePath: string; content: string } {
+  const isKotlinDsl = settingsGradlePath.endsWith('.kts');
+  const buildGradleFile = join(
+    dirname(settingsGradlePath),
+    isKotlinDsl ? 'build.gradle.kts' : 'build.gradle'
+  );
+
+  let content = '';
+  if (tree.exists(buildGradleFile)) {
+    content = tree.read(buildGradleFile, 'utf-8');
+  } else {
+    tree.write(buildGradleFile, content);
+  }
+
+  return { filePath: buildGradleFile, content };
+}
+
+// a regex to get the version in build file in format `id "dev.nx.gradle.project-graph" version "x"`
+const regex =
+  /(id\s*\(?["']dev\.nx\.gradle\.project-graph["']\)?\s*version\s*\(?["'])([^"']+)(["']\)?)/;
+
+/**
+ * Extract gradle plugin version from build.gradle file
+ */
+export async function extractNxPluginVersion(
+  gradleFilePath: string,
+  gradleContent: string
+): Promise<string | null> {
+  const match = gradleContent.match(regex);
+  let version = match ? match[2] : null;
+  if (!version) {
+    try {
+      const gradlewFile = findGradlewFile(gradleFilePath, workspaceRoot);
+      const buildEnvironment = (
+        await execGradleAsync(join(workspaceRoot, gradlewFile), [
+          'buildEnvironment',
+          '--quiet',
+        ])
+      ).toString();
+      version = getPluginVersion(buildEnvironment);
+    } catch (e) {} // Silently ignore error, fallback remains null
+  }
+  return version;
+}
+
+function getPluginVersion(dependencyTree: string): string | null {
+  const lines = dependencyTree.split('\n');
+
+  for (const line of lines) {
+    // line is dev.nx.gradle.project-graph:dev.nx.gradle.project-graph.gradle.plugin:version
+    const match = line.match(
+      /dev\.nx\.gradle\.project-graph:dev\.nx\.gradle\.project-graph\.gradle\.plugin:([^\s\\]+)/
+    );
+    if (match) {
+      return match[1]; // returns the version part
+    }
+  }
+
+  return null; // not found
+}
+
+/**
+ * Updates the plugin version in the given Gradle file content.
+ */
+export function updateNxPluginVersion(
+  content: string,
+  newVersion: string
+): string {
+  if (regex.test(content)) {
+    return content.replace(regex, `$1${newVersion}$3`);
+  } else {
+    logger.warn(
+      `Please update plugin dev.nx.gradle.project-graph to ${newVersion}`
+    );
+  }
+  return content;
+}
+
+/**
+ * Ensures all build.gradle(.kts) files use the expected version of dev.nx.gradle.project-graph.
+ */
+export async function addNxProjectGraphPlugin(
+  tree: Tree,
+  expectedVersion: string = gradleProjectGraphVersion
+) {
+  const files = await addBuildGradleFileNextToSettingsGradle(tree);
+
+  files.forEach(({ filePath, content }) => {
+    addNxProjectGraphPluginToBuildGradle(
+      filePath,
+      content,
+      expectedVersion,
+      tree
+    );
+  });
+}
+
+/**
+ * Adds or updates the Nx Project Graph plugin in the build.gradle(.kts) file.
+ * Ensures the correct version and applies the plugin to all projects.
+ * Returns the updated build.gradle content.
+ */
+async function addNxProjectGraphPluginToBuildGradle(
+  gradleFilePath: string,
+  buildGradleContent: string,
+  expectedVersion: string = gradleProjectGraphVersion,
+  tree: Tree
+): Promise<string> {
+  const isKotlinDsl = gradleFilePath.endsWith('.kts');
+  const nxProjectGraphReportPlugin = isKotlinDsl
+    ? `id(\"${gradleProjectGraphPluginName}\") version(\"${expectedVersion}\")`
+    : `id \"${gradleProjectGraphPluginName}\" version \"${expectedVersion}\"`;
+
+  // Helper to add plugin to plugins block
+  function addPluginToPluginsBlock(content: string): string {
+    return content.replace(
+      /plugins\s*\{/,
+      `plugins {\n    ${nxProjectGraphReportPlugin}`
+    );
+  }
+
+  // Helper to add plugins block if missing
+  function addPluginsBlock(content: string): string {
+    return `plugins {\n    ${nxProjectGraphReportPlugin}\n}\n${content}`;
+  }
+
+  // Helper to add plugin application to allprojects
+  function addPluginToAllProjects(content: string): string {
+    const applyPlugin = isKotlinDsl
+      ? `plugin(\"${gradleProjectGraphPluginName}\")`
+      : `plugin(\"${gradleProjectGraphPluginName}\")`;
+    return `${content}\nallprojects {\n    apply {\n        ${applyPlugin}\n    }\n}`;
+  }
+
+  // 1. Ensure plugins block and correct plugin version
+  if (buildGradleContent.includes('plugins {')) {
+    if (buildGradleContent.includes(gradleProjectGraphPluginName)) {
+      // Update version if needed
+      const currentVersion = await extractNxPluginVersion(
+        gradleFilePath,
+        buildGradleContent
+      );
+      if (currentVersion && currentVersion !== expectedVersion) {
+        buildGradleContent = updateNxPluginVersion(
+          buildGradleContent,
+          expectedVersion
+        );
+      }
+    } else {
+      // Add plugin to plugins block
+      buildGradleContent = addPluginToPluginsBlock(buildGradleContent);
+    }
+  } else {
+    // Add plugins block if missing
+    buildGradleContent = addPluginsBlock(buildGradleContent);
+  }
+
+  // 2. Ensure plugin is applied to all projects
+  const applyPluginPattern = new RegExp(
+    `\\s*plugin\\(["']${gradleProjectGraphPluginName}["']\\)`
+  );
+  if (buildGradleContent.includes('allprojects {')) {
+    if (!applyPluginPattern.test(buildGradleContent)) {
+      logger.warn(
+        `Please add the ${gradleProjectGraphPluginName} plugin to your ${gradleFilePath}:\nallprojects {\n  apply {\n      plugin(\"${gradleProjectGraphPluginName}\")\n  }\n}`
+      );
+    }
+  } else {
+    buildGradleContent = addPluginToAllProjects(buildGradleContent);
+  }
+
+  // 3. Write and return updated content
+  tree.write(gradleFilePath, buildGradleContent);
+  return buildGradleContent;
+}
diff --git a/packages/gradle/src/generators/init/init.ts b/packages/gradle/src/generators/init/init.ts
index 55950788aaf1f..ebceda9ac1d74 100644
--- a/packages/gradle/src/generators/init/init.ts
+++ b/packages/gradle/src/generators/init/init.ts
@@ -2,21 +2,15 @@ import {
   addDependenciesToPackageJson,
   formatFiles,
   GeneratorCallback,
-  globAsync,
-  logger,
   readNxJson,
   runTasksInSerial,
   Tree,
   updateNxJson,
 } from '@nx/devkit';
-import {
-  gradleProjectGraphPluginName,
-  gradleProjectGraphVersion,
-  nxVersion,
-} from '../../utils/versions';
+import { nxVersion } from '../../utils/versions';
 import { InitGeneratorSchema } from './schema';
 import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
-import { dirname, join, basename } from 'path';
+import { addNxProjectGraphPlugin } from './gradle-project-graph-plugin-utils';
 
 export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
   const tasks: GeneratorCallback[] = [];
@@ -34,7 +28,7 @@ export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
       )
     );
   }
-  await addBuildGradleFileNextToSettingsGradle(tree);
+  await addNxProjectGraphPlugin(tree);
   addPlugin(tree);
   updateNxJsonConfiguration(tree);
 
@@ -62,84 +56,6 @@ function addPlugin(tree: Tree) {
   }
 }
 
-/**
- * This function creates and populate build.gradle file next to the settings.gradle file.
- */
-export async function addBuildGradleFileNextToSettingsGradle(tree: Tree) {
-  const settingsGradleFiles = await globAsync(tree, [
-    '**/settings.gradle?(.kts)',
-  ]);
-  settingsGradleFiles.forEach((settingsGradleFile) => {
-    addNxProjectGraphPluginToBuildGradle(settingsGradleFile, tree);
-  });
-}
-
-/**
- * - creates a build.gradle file next to the settings.gradle file if it does not exist.
- * - adds the NxProjectGraphPlugin plugin to the build.gradle file if it does not exist.
- */
-function addNxProjectGraphPluginToBuildGradle(
-  settingsGradleFile: string,
-  tree: Tree
-) {
-  const filename = basename(settingsGradleFile);
-  let gradleFilePath = 'build.gradle';
-  if (filename.endsWith('.kts')) {
-    gradleFilePath = 'build.gradle.kts';
-  }
-  gradleFilePath = join(dirname(settingsGradleFile), gradleFilePath);
-  let buildGradleContent = '';
-  if (!tree.exists(gradleFilePath)) {
-    tree.write(gradleFilePath, buildGradleContent); // create a build.gradle file near settings.gradle file if it does not exist
-  } else {
-    buildGradleContent = tree.read(gradleFilePath).toString();
-  }
-
-  const nxProjectGraphReportPlugin = filename.endsWith('.kts')
-    ? `id("${gradleProjectGraphPluginName}") version("${gradleProjectGraphVersion}")`
-    : `id "${gradleProjectGraphPluginName}" version "${gradleProjectGraphVersion}"`;
-  if (buildGradleContent.includes('plugins {')) {
-    if (!buildGradleContent.includes(gradleProjectGraphPluginName)) {
-      buildGradleContent = buildGradleContent.replace(
-        'plugins {',
-        `plugins {
-    ${nxProjectGraphReportPlugin}`
-      );
-    }
-  } else {
-    buildGradleContent = `plugins {
-    ${nxProjectGraphReportPlugin}
-}\n\r${buildGradleContent}`;
-  }
-
-  const applyNxProjectGraphReportPlugin = `plugin("${gradleProjectGraphPluginName}")`;
-  if (buildGradleContent.includes('allprojects {')) {
-    if (
-      !buildGradleContent.includes(
-        `plugin("${gradleProjectGraphPluginName}")`
-      ) &&
-      !buildGradleContent.includes(`plugin('${gradleProjectGraphPluginName}')`)
-    ) {
-      logger.warn(
-        `Please add the ${gradleProjectGraphPluginName} plugin to your ${gradleFilePath}:
-allprojects {
-  apply {
-      ${applyNxProjectGraphReportPlugin}
-  }
-}`
-      );
-    }
-  } else {
-    buildGradleContent = `${buildGradleContent}\n\rallprojects {
-    apply {
-        ${applyNxProjectGraphReportPlugin}
-    }
-  }`;
-  }
-
-  tree.write(gradleFilePath, buildGradleContent);
-}
-
 export function updateNxJsonConfiguration(tree: Tree) {
   const nxJson = readNxJson(tree);
 
diff --git a/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.md b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.md
new file mode 100644
index 0000000000000..2fa84fc9c3bfa
--- /dev/null
+++ b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.md
@@ -0,0 +1,26 @@
+#### Change dev.nx.gradle.project-graph to version 0.1.0
+
+Change dev.nx.gradle.project-graph to version 0.1.0 in build file
+
+#### Sample Code Changes
+
+{% tabs %}
+{% tab label="Before" %}
+
+```{% fileName="build.gradle" %}
+plugins {
+	id "dev.nx.gradle.project-graph" version "0.0.5"
+}
+```
+
+{% /tab %}
+{% tab label="After" %}
+
+```{% fileName="build.gradle" %}
+plugins {
+    id "dev.nx.gradle.project-graph" version "0.1.0"
+}
+```
+
+{% /tab %}
+{% /tabs %}
diff --git a/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.spec.ts b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.spec.ts
new file mode 100644
index 0000000000000..ea8ad1def87a5
--- /dev/null
+++ b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.spec.ts
@@ -0,0 +1,128 @@
+import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
+import { Tree } from '@nx/devkit';
+import { FsTree } from 'nx/src/generators/tree';
+import update from './change-plugin-version-0-1-0';
+import { gradleProjectGraphPluginName } from '../../utils/versions';
+
+describe('change-plugin-version-0-1-0 migration', () => {
+  let tempFs: TempFs;
+  let cwd: string;
+  let tree: Tree;
+
+  beforeEach(async () => {
+    tempFs = new TempFs('test');
+    cwd = process.cwd();
+    process.chdir(tempFs.tempDir);
+    tree = new FsTree(tempFs.tempDir, false);
+  });
+
+  afterEach(() => {
+    jest.resetModules();
+    process.chdir(cwd);
+  });
+
+  it('should update plugin version to 0.1.0 in Groovy DSL', async () => {
+    await tempFs.createFiles({
+      'nx.json': JSON.stringify({
+        plugins: ['@nx/gradle'],
+      }),
+      'proj/settings.gradle': '',
+      'proj/build.gradle': `plugins {
+    id 'java'
+    id "${gradleProjectGraphPluginName}" version "0.0.1"
+}`,
+    });
+
+    await update(tree);
+
+    const content = tree.read('proj/build.gradle', 'utf-8');
+    expect(content).toContain(
+      `id "${gradleProjectGraphPluginName}" version "0.1.0"`
+    );
+    expect(content).not.toContain('version "0.0.1"');
+  });
+
+  it('should update plugin version to 0.1.0 in Kotlin DSL', async () => {
+    await tempFs.createFiles({
+      'nx.json': JSON.stringify({
+        plugins: ['@nx/gradle'],
+      }),
+      'proj/settings.gradle.kts': '',
+      'proj/build.gradle.kts': `plugins {
+    id("java")
+    id("${gradleProjectGraphPluginName}") version("0.0.1")
+}`,
+    });
+
+    await update(tree);
+
+    const content = tree.read('proj/build.gradle.kts', 'utf-8');
+    expect(content).toContain(
+      `id("${gradleProjectGraphPluginName}") version("0.1.0")`
+    );
+    expect(content).not.toContain('version("0.0.1")');
+  });
+
+  it('should not update if nx.json is missing', async () => {
+    await tempFs.createFiles({
+      'proj/settings.gradle': '',
+      'proj/build.gradle': `plugins {
+    id 'java'
+    id "${gradleProjectGraphPluginName}" version "0.0.1"
+}`,
+    });
+
+    await update(tree);
+
+    const content = tree.read('proj/build.gradle', 'utf-8');
+    expect(content).toContain('version "0.0.1"');
+    expect(content).not.toContain('version "0.1.0"');
+  });
+
+  it('should not update if Gradle plugin is not present', async () => {
+    await tempFs.createFiles({
+      'nx.json': JSON.stringify({}),
+      'proj/settings.gradle': '',
+      'proj/build.gradle': `plugins {
+    id 'java'
+}`,
+    });
+
+    await update(tree);
+
+    const content = tree.read('proj/build.gradle', 'utf-8');
+    expect(content).not.toContain(gradleProjectGraphPluginName);
+  });
+
+  it('should handle multiple build.gradle files', async () => {
+    await tempFs.createFiles({
+      'nx.json': JSON.stringify({
+        plugins: ['@nx/gradle'],
+      }),
+      'proj1/settings.gradle': '',
+      'proj1/build.gradle': `plugins {
+    id 'java'
+    id "${gradleProjectGraphPluginName}" version "0.0.1"
+}`,
+      'proj2/settings.gradle': '',
+      'proj2/build.gradle': `plugins {
+    id 'java'
+    id "${gradleProjectGraphPluginName}" version "0.0.1"
+}`,
+    });
+
+    await update(tree);
+
+    const proj1Content = tree.read('proj1/build.gradle', 'utf-8');
+    const proj2Content = tree.read('proj2/build.gradle', 'utf-8');
+
+    expect(proj1Content).toContain(
+      `id "${gradleProjectGraphPluginName}" version "0.1.0"`
+    );
+    expect(proj2Content).toContain(
+      `id "${gradleProjectGraphPluginName}" version "0.1.0"`
+    );
+    expect(proj1Content).not.toContain('version "0.0.1"');
+    expect(proj2Content).not.toContain('version "0.0.1"');
+  });
+});
diff --git a/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.ts b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.ts
new file mode 100644
index 0000000000000..0094bfbba0ea5
--- /dev/null
+++ b/packages/gradle/src/migrations/21-1-2/change-plugin-version-0-1-0.ts
@@ -0,0 +1,16 @@
+import { Tree, readNxJson } from '@nx/devkit';
+import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
+import { addNxProjectGraphPlugin } from '../../generators/init/gradle-project-graph-plugin-utils';
+
+/* Change the plugin version to 0.1.0
+ */
+export default async function update(tree: Tree) {
+  const nxJson = readNxJson(tree);
+  if (!nxJson) {
+    return;
+  }
+  if (!hasGradlePlugin(tree)) {
+    return;
+  }
+  await addNxProjectGraphPlugin(tree, '0.1.0');
+}


Reviews:
user: login: FrozenPandaz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T21:06:48Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

Adds a migration to bump the Gradle Project Graph plugin version and refactors the init generator to use a shared utility for adding or updating that plugin.

- Introduces `change-plugin-version-0-1-0` migration to update plugin version to 0.1.0 in build files.
- Refactors `initGenerator` to remove inline build file logic and delegate to `addNxProjectGraphPlugin`.
- Adds `gradle-project-graph-plugin-utils.ts` with helpers to detect, insert, or update the plugin block consistently.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                                                                                      | Description                                                 |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.ts                     | New migration script for updating plugin version to 0.1.0   |
| packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.spec.ts                | Tests covering migration behavior                           |
| packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.md                     | Migration documentation and sample before/after             |
| packages/gradle/src/generators/init/init.ts                                              | Init generator refactor to use shared plugin utils          |
| packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.ts                 | New utility for adding or updating the Gradle plugin        |
| packages/gradle/src/generators/init/gradle-project-graph-plugin-utils.spec.ts            | Tests for the new plugin utility functions                  |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T21:08:03Z
user: login: xiongemi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T04:12:58Z
user: login: xiongemi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T04:13:49Z
user: login: xiongemi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T05:35:37Z
user: login: jaysoo
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-26T17:59:23Z

Review comments:
diff_hunk: @@ -0,0 +1,16 @@
+import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
+import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
+import { addNxProjectGraphPlugin } from '../../generators/init/gradle-project-graph-plugin-utils';
+
+/* Change the plugin version to 0.1.0
+ */
+export default async function update(tree: Tree) {
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.ts
user: login: FrozenPandaz
body: Need to add it to `migrations.json`. It can be for `21.1.2`
created_at: 2025-05-22T21:06:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,16 @@
+import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.ts
user: login: Copilot
body: The import `updateNxJson` is not used in this file. Consider removing it to clean up dependencies.
```suggestion
import { Tree, readNxJson } from '@nx/devkit';
```
created_at: 2025-05-22T21:08:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+#### Change dev.nx.gradle.project-graph to version 0.1.0
+
+Change dev.nx.gradle.project-graph to version 0.1.0 in build file
+
+#### Sample Code Changes
+
+{% tabs %}
+{% tab label="Before" %}
+
+```{% fileName="build.gradle" %}
+plugins {
+	id "dev.nx.gradle.project-graph" version "0.0.5"
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.md
user: login: Copilot
body: The sample 'before' version is shown as `0.0.5`, but the migration and tests refer to `0.0.1`. Please update the example to match the actual version being migrated.
```suggestion
	id "dev.nx.gradle.project-graph" version "0.0.1"
```
created_at: 2025-05-22T21:08:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+#### Change dev.nx.gradle.project-graph to version 0.1.0
+
+Change dev.nx.gradle.project-graph to version 0.1.0 in build file
+
+#### Sample Code Changes
+
+{% tabs %}
+{% tab label="Before" %}
+
+```{% fileName="build.gradle" %}
+plugins {
+	id "dev.nx.gradle.project-graph" version "0.0.5"
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.md
user: login: xiongemi
body: i don't think the version matters here, this is before code
created_at: 2025-05-26T04:12:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,16 @@
+import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.ts
user: login: xiongemi
body: removed
created_at: 2025-05-26T04:13:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,16 @@
+import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
+import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
+import { addNxProjectGraphPlugin } from '../../generators/init/gradle-project-graph-plugin-utils';
+
+/* Change the plugin version to 0.1.0
+ */
+export default async function update(tree: Tree) {
path: packages/gradle/src/migrations/21-0-0/change-plugin-version-0-1-0.ts
user: login: xiongemi
body: added
created_at: 2025-05-26T05:35:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-22T02:46:25Z
body: [vc]: #s4YBaR1Qf4gmzRXuv4BFFnbGXuCYwymR5sz8Pyhoi74=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJueC1kZXYiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbnJ3bC9ueC1kZXYvMnB6clFQanhZNUR2MVZxa1UzcFg2WHVwd1NENyIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGwsInByZXZpZXdVcmwiOiJueC1kZXYtZ2l0LWZpeC1ncmFkbGUtcGx1Z2luLXZlcnNpb24tbnJ3bC52ZXJjZWwuYXBwIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **nx-dev** | ✅ Ready ([Inspect](https://vercel.com/nrwl/nx-dev/2pzrQPjxY5Dv1VqkU3pX6XupwSD7)) | [Visit Preview](https://nx-dev-git-fix-gradle-plugin-version-nrwl.vercel.app) | May 26, 2025 6:02am |


author_association: NONE
user: login: nx-cloud[bot]
created_at: 2025-05-22T02:46:58Z
body: 
View your [CI Pipeline Execution ↗](https://staging.nx.app/cipes/6834013fa67dee5bc29cfffb?utm_source=pull-request&utm_medium=comment) for commit 1868ec9bbcbca16d471b66528a43732ee7fa59ad.

| Command | Status | Duration | Result |
|---------|--------|----------:|--------|
| `nx affected --targets=lint,test,build,e2e,e2e-c...` | ✅ Succeeded | 17m 33s | [View ↗](https://staging.nx.app/runs/Fp9MS0rhFk?utm_source=pull-request&utm_medium=comment) |
| `nx run-many -t check-imports check-commit check...` | ✅ Succeeded | 15s | [View ↗](https://staging.nx.app/runs/F2XPQYCDEB?utm_source=pull-request&utm_medium=comment) |
| `nx-cloud record -- nx-cloud conformance:check` | ✅ Succeeded | 2s | [View ↗](https://staging.nx.app/runs/irVSxmhLoX?utm_source=pull-request&utm_medium=comment) |
| `nx-cloud record -- nx format:check` | ✅ Succeeded | 4s | [View ↗](https://staging.nx.app/runs/EYDOifJ3YW?utm_source=pull-request&utm_medium=comment) |
| `nx-cloud record -- nx sync:check` | ✅ Succeeded | 2s | [View ↗](https://staging.nx.app/runs/gMUzSgutgB?utm_source=pull-request&utm_medium=comment) |
| `nx documentation` | ✅ Succeeded | 2m 42s | [View ↗](https://staging.nx.app/runs/L3lTOU1ad0?utm_source=pull-request&utm_medium=comment) |

---

☁️ [Nx Cloud](https://staging.nx.app?utm_source=pull-request&utm_medium=comment) last updated this comment at `2025-05-26 06:15:33` UTC

<!-- NX_CLOUD_APP_COMMENT_END -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-06-01T00:13:33Z
body: This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request.
author_association: CONTRIBUTOR