You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/05/28 16:23:55 UTC
(grails-core) 01/01: Add grails-code-analysis convention plugin and violation aggregation
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch feat/grails-code-analysis-plugin
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 29a24649f14f5f662b0fb5627c84dd24ddd130f2
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Thu May 28 12:23:09 2026 -0400
Add grails-code-analysis convention plugin and violation aggregation
Splits static code analysis out of the code-style plugin into a dedicated
grails-code-analysis convention plugin (PMD + SpotBugs), and adds a
root-only grails-violation-aggregation plugin that consolidates the
per-module Checkstyle/CodeNarc/PMD XML reports into Markdown summaries
under build/reports/violations/ via an aggregateStyleViolations task.
Extracted from the Hibernate 7 staging branch (PR #15654) so it can be
reviewed as a focused, single-topic change against 8.0.x, per review
feedback that the analysis tooling should land in its own PR.
Includes:
- GrailsCodeAnalysisPlugin + GrailsCodeAnalysisExtension
- GrailsViolationAggregationPlugin (aggregateStyleViolations)
- GrailsCodeStylePlugin improvements: enable/disable properties,
codenarcFix task, test-styling toggle; resource path renamed to
grails-code-style, PMD config under grails-code-analysis
- codeanalysis.yml workflow; codestyle.yml switched to aggregated reports
- pmd/spotbugs versions; violation-fixer skill
- Applies grails-code-analysis to 96 subprojects and wires forge
Assisted-by: claude-code:claude-4.7-opus
---
.agents/skills/violation-fixer/SKILL.md | 231 ++++++++++
.github/workflows/codeanalysis.yml | 95 +++++
.github/workflows/codestyle.yml | 75 ++--
build-logic/plugins/build.gradle | 19 +-
.../org/apache/grails/buildsrc/GradleUtils.groovy | 7 +
...n.groovy => GrailsCodeAnalysisExtension.groovy} | 29 +-
.../buildsrc/GrailsCodeAnalysisPlugin.groovy | 183 ++++++++
.../buildsrc/GrailsCodeStyleExtension.groovy | 9 +-
.../grails/buildsrc/GrailsCodeStylePlugin.groovy | 141 +++++--
.../GrailsViolationAggregationPlugin.groovy | 467 +++++++++++++++++++++
.../pmd/pmd.xml} | 15 +-
.../checkstyle/checkstyle-suppressions.xml | 0
.../checkstyle/checkstyle.xml | 0
.../codenarc/codenarc.groovy | 0
.../buildsrc/GrailsCodeStylePluginSpec.groovy | 135 ++++++
.../GrailsViolationAggregationPluginSpec.groovy | 177 ++++++++
build.gradle | 4 +
gradle.properties | 2 +
grails-async/core/build.gradle | 1 +
grails-async/gpars/build.gradle | 1 +
grails-async/plugin/build.gradle | 1 +
grails-async/rxjava/build.gradle | 1 +
grails-async/rxjava2/build.gradle | 1 +
grails-async/rxjava3/build.gradle | 1 +
grails-bootstrap/build.gradle | 1 +
grails-cache/build.gradle | 1 +
grails-codecs-core/build.gradle | 1 +
grails-codecs/build.gradle | 1 +
grails-common/build.gradle | 1 +
grails-console/build.gradle | 1 +
grails-controllers/build.gradle | 1 +
grails-converters/build.gradle | 1 +
grails-core/build.gradle | 1 +
grails-data-hibernate5/boot-plugin/build.gradle | 1 +
grails-data-hibernate5/core/build.gradle | 1 +
grails-data-hibernate5/dbmigration/build.gradle | 1 +
grails-data-hibernate5/grails-plugin/build.gradle | 1 +
grails-data-hibernate5/spring-orm/build.gradle | 1 +
grails-data-mongodb/boot-plugin/build.gradle | 1 +
grails-data-mongodb/bson/build.gradle | 1 +
grails-data-mongodb/core/build.gradle | 1 +
grails-data-mongodb/ext/build.gradle | 1 +
grails-data-mongodb/grails-plugin/build.gradle | 1 +
grails-data-mongodb/gson-templates/build.gradle | 1 +
grails-data-simple/build.gradle | 1 +
grails-databinding-core/build.gradle | 1 +
grails-databinding/build.gradle | 1 +
grails-datamapping-async/build.gradle | 1 +
grails-datamapping-core/build.gradle | 1 +
grails-datamapping-rx/build.gradle | 1 +
grails-datamapping-support/build.gradle | 1 +
grails-datamapping-tck/build.gradle | 1 +
grails-datamapping-validation/build.gradle | 1 +
grails-datasource/build.gradle | 1 +
grails-datastore-async/build.gradle | 1 +
grails-datastore-core/build.gradle | 1 +
grails-datastore-web/build.gradle | 1 +
grails-domain-class/build.gradle | 1 +
grails-encoder/build.gradle | 1 +
grails-events/core/build.gradle | 1 +
grails-events/gpars/build.gradle | 1 +
grails-events/plugin/build.gradle | 1 +
grails-events/rxjava/build.gradle | 1 +
grails-events/rxjava2/build.gradle | 1 +
grails-events/rxjava3/build.gradle | 1 +
grails-events/spring/build.gradle | 1 +
grails-events/transforms/build.gradle | 1 +
grails-fields/build.gradle | 1 +
grails-forge/build.gradle | 1 +
grails-forge/gradle/code-style-config.gradle | 1 +
grails-geb/build.gradle | 1 +
grails-gradle/common/build.gradle | 1 +
grails-gradle/model/build.gradle | 1 +
grails-gradle/plugins/build.gradle | 1 +
grails-gradle/tasks/build.gradle | 1 +
grails-gsp/core/build.gradle | 1 +
grails-gsp/grails-layout/build.gradle | 1 +
grails-gsp/grails-sitemesh3/build.gradle | 1 +
grails-gsp/grails-taglib/build.gradle | 1 +
grails-gsp/grails-web-gsp-taglib/build.gradle | 1 +
grails-gsp/grails-web-gsp/build.gradle | 1 +
grails-gsp/grails-web-jsp/build.gradle | 1 +
grails-gsp/grails-web-taglib/build.gradle | 1 +
grails-gsp/plugin/build.gradle | 1 +
grails-i18n/build.gradle | 1 +
grails-interceptors/build.gradle | 1 +
grails-logging/build.gradle | 1 +
grails-mimetypes/build.gradle | 1 +
grails-rest-transforms/build.gradle | 1 +
grails-scaffolding/build.gradle | 1 +
grails-services/build.gradle | 1 +
grails-shell-cli/build.gradle | 1 +
grails-spring/build.gradle | 1 +
grails-test-core/build.gradle | 1 +
grails-test-suite-base/build.gradle | 1 +
grails-testing-support-core/build.gradle | 1 +
grails-testing-support-datamapping/build.gradle | 1 +
grails-testing-support-dbcleanup-core/build.gradle | 1 +
grails-testing-support-dbcleanup-h2/build.gradle | 1 +
.../build.gradle | 1 +
grails-testing-support-http-client/build.gradle | 1 +
grails-testing-support-mongodb/build.gradle | 1 +
grails-testing-support-views-gson/build.gradle | 1 +
grails-testing-support-web/build.gradle | 1 +
grails-url-mappings/build.gradle | 1 +
grails-validation/build.gradle | 1 +
grails-views-core/build.gradle | 1 +
grails-views-gson/build.gradle | 1 +
grails-views-markup/build.gradle | 1 +
grails-web-boot/build.gradle | 1 +
grails-web-common/build.gradle | 1 +
grails-web-core/build.gradle | 1 +
grails-web-databinding/build.gradle | 1 +
grails-web-mvc/build.gradle | 1 +
grails-web-url-mappings/build.gradle | 1 +
grails-wrapper/build.gradle | 1 +
116 files changed, 1579 insertions(+), 108 deletions(-)
diff --git a/.agents/skills/violation-fixer/SKILL.md b/.agents/skills/violation-fixer/SKILL.md
new file mode 100644
index 0000000000..07ed784d35
--- /dev/null
+++ b/.agents/skills/violation-fixer/SKILL.md
@@ -0,0 +1,231 @@
+---
+name: violation-fixer
+description: Guide for running, interpreting, and fixing code style and analysis violations in grails-core using GrailsCodeStylePlugin, GrailsCodeAnalysisPlugin, and GrailsViolationAggregationPlugin — covering CodeNarc, Checkstyle, PMD, SpotBugs, and JaCoCo
+license: Apache-2.0
+---
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+## What I Do
+
+- Explain how `GrailsCodeStylePlugin`, `GrailsCodeAnalysisPlugin`, and `GrailsViolationAggregationPlugin` enforce code quality across all 60+ modules.
+- Guide you through running style and analysis checks, interpreting the per-tool Markdown violation reports, and fixing each class of violation.
+- Describe which tools are always-on vs. opt-in, how to configure them via Gradle properties, and which violations can be auto-fixed.
+
+## When to Use Me
+
+Activate this skill when:
+
+- Running `./gradlew aggregateViolations` and interpreting the resulting `*_VIOLATIONS.md` files.
+- Fixing CodeNarc, Checkstyle, PMD, SpotBugs, or Spotless violations reported in those files.
+- Configuring code style or analysis tools across the repo (enabling/disabling tools or adjusting rule files).
+- Preparing a commit — the plugin output must be clean before merging.
+
+---
+
+## Plugin Overview
+
+| Plugin | Applied to | Responsibility |
+|--------|-----------|----------------|
+| `org.apache.grails.gradle.grails-code-style` | Every subproject | Applies Checkstyle and CodeNarc; registers per-project `codeStyle` task; redirects XML reports to root `build/reports/codestyle/` |
+| `org.apache.grails.gradle.grails-code-analysis` | Every subproject | Applies PMD and SpotBugs (both opt-in); registers per-project `codeAnalysis` task; redirects XML reports to root `build/reports/codestyle/` |
+| `org.apache.grails.gradle.grails-jacoco` | Every subproject | Applies JaCoCo; wires `jacocoTestReport` to run after each `test` task |
+| `org.apache.grails.gradle.grails-violation-aggregation` | **Root project only** | Registers `aggregateViolations` and `aggregateJacocoCoverage` tasks; writes Markdown summaries to `build/reports/violations/` |
+
+---
+
+## Key Tasks
+
+| Task | Scope | Description |
+|------|-------|-------------|
+| `./gradlew codeStyle` | per-project | Runs Checkstyle and CodeNarc for that project |
+| `./gradlew codeAnalysis` | per-project | Runs PMD and/or SpotBugs for that project (when enabled) |
+| `./gradlew aggregateViolations` | root | Runs all checks across every module, then writes `*_VIOLATIONS.md` to `build/reports/violations/` |
+| `./gradlew aggregateJacocoCoverage` | root | Runs JaCoCo reports across every module, then writes `JACOCO_COVERAGE.md` to `build/reports/violations/` |
+| `./gradlew codenarcFix` | per-project | Auto-fixes a subset of CodeNarc violations |
+
+### Quick commands
+
+```bash
+# Check a single module (style only)
+./gradlew :grails-core:codeStyle
+
+# Check a single module (analysis — must be enabled via properties)
+./gradlew :grails-core:codeAnalysis -Pgrails.codeanalysis.enabled.pmd=true
+
+# Full multi-module check + report
+./gradlew aggregateViolations
+
+# Include test sources in style checks
+./gradlew aggregateViolations -Pgrails.codestyle.enabled.tests=true
+
+# Include test sources in analysis
+./gradlew aggregateViolations -Pgrails.codeanalysis.enabled.tests=true
+
+# Ignore failures (collect reports without failing the build)
+./gradlew aggregateViolations -Pgrails.codestyle.ignoreFailures=true -Pgrails.codeanalysis.ignoreFailures=true
+
+# Auto-fix some CodeNarc violations before running checks
+./gradlew codenarcFix codeStyle
+
+# JaCoCo coverage report
+./gradlew aggregateJacocoCoverage
+```
+
+---
+
+## Output Files
+
+After running `aggregateViolations`, these files appear under `build/reports/violations/` in the **root project build directory**:
+
+| File | Tool | Always generated |
+|------|------|-----------------|
+| `build/reports/violations/CODENARC_VIOLATIONS.md` | CodeNarc | Yes |
+| `build/reports/violations/CHECKSTYLE_VIOLATIONS.md` | Checkstyle | Yes |
+| `build/reports/violations/PMD_VIOLATIONS.md` | PMD | Yes — contains `No violations found!` when PMD is disabled |
+| `build/reports/violations/SPOTBUGS_VIOLATIONS.md` | SpotBugs | Yes — contains `No violations found!` when SpotBugs is disabled |
+
+After running `aggregateJacocoCoverage`:
+
+| File | Tool | Generated |
+|------|------|-----------|
+| `build/reports/violations/JACOCO_COVERAGE.md` | JaCoCo | Only when at least one subproject has a JaCoCo CSV report |
+
+All reports are inside `build/` and are excluded from version control via `.gitignore`. A clean run produces `No violations found! 🎉` in each style file. **The build must be clean before committing.**
+
+Each file is a Markdown table grouped by module, with columns: **Class**, **Tool**, **Violation**, **Line**, **Message**.
+
+---
+
+## Tool Details
+
+### CodeNarc (Groovy — always enabled)
+
+Rule file: `build/codestyle/codenarc/codenarc.groovy` (generated by the plugin during setup; not intended to be edited directly).
+
+Most common violations and how to fix them:
+
+| Rule | Fix |
+|------|-----|
+| `UnnecessaryGString` | Replace `"plain string"` with `'plain string'` |
+| `UnnecessarySemicolon` | Remove trailing `;` |
+| `SpaceBeforeOpeningBrace` | Add space before `{` → `method() {` |
+| `SpaceAroundMapEntryColon` | `[key: value]` not `[key:value]` |
+| `ConsecutiveBlankLines` | Collapse 3+ blank lines to 2 |
+| `ClassStartsWithBlankLine` | Remove blank line right after `class Foo {` |
+| `NoWildcardImports` | Expand `import org.foo.*` to explicit imports |
+| `UnusedImport` | Remove imports not referenced in the file |
+| `MethodName` | Method names must be camelCase (not `snake_case`) |
+| `VariableName` | Variable names must be camelCase |
+| `LineLength` | Keep lines ≤ 200 chars (default) |
+
+Auto-fixable via `codenarcFix`: `ClassStartsWithBlankLine`, `SpaceAroundMapEntryColon`, `UnnecessaryGString`, `UnnecessarySemicolon`, `SpaceBeforeOpeningBrace`, `ConsecutiveBlankLines`.
+
+### Checkstyle (Java — always enabled)
+
+Rule file: `build/codestyle/checkstyle/checkstyle.xml`.
+
+Common violations:
+
+| Rule | Fix |
+|------|-----|
+| `ImportOrder` | Re-order imports: `java|javax`, then `groovy`, then `jakarta`, then blank, then `io.spring|org.springframework`, then `grails|org.apache.grails|org.grails`, then static imports |
+| `AvoidStarImport` | Use explicit class imports |
+| `UnusedImports` | Remove unused imports |
+| `WhitespaceAround` | Add spaces around operators and keywords |
+| `NeedBraces` | Add `{}` to single-statement `if`/`for`/`while` |
+| `FileTabCharacter` | Replace tabs with 4 spaces |
+| `NewlineAtEndOfFile` | Ensure file ends with `\n` |
+
+### PMD (Java/Groovy — opt-in)
+
+Enable: `-Pgrails.codeanalysis.enabled.pmd=true`
+
+Rule file: `build/codeanalysis/pmd/pmd.xml`.
+
+### SpotBugs (Java bytecode — opt-in)
+
+Enable: `-Pgrails.codeanalysis.enabled.spotbugs=true`
+
+Runs at `Effort.MAX` / `Confidence.HIGH`. Only high-confidence bugs are reported.
+
+### Spotless (Java auto-formatting — opt-in)
+
+Enable: `-Pgrails.codestyle.enabled.spotless=true`
+
+Uses Palantir Java Format. Can auto-fix by running:
+```bash
+./gradlew spotlessApply
+```
+
+---
+
+## Configuration Properties
+
+All properties can be set in `gradle.properties` or passed as `-P` flags:
+
+### `grails-code-style` plugin (Checkstyle + CodeNarc)
+
+| Property | Default | Description |
+|----------|---------|-------------|
+| `grails.codestyle.enabled.checkstyle` | `true` | Enable Checkstyle |
+| `grails.codestyle.enabled.codenarc` | `true` | Enable CodeNarc |
+| `grails.codestyle.enabled.spotless` | `false` | Enable Spotless |
+| `grails.codestyle.enabled.tests` | `false` | Also check test source sets |
+| `grails.codestyle.ignoreFailures` | `false` | Collect reports without failing build |
+| `grails.codestyle.codenarc.fix` | `false` | Run `codenarcFix` before CodeNarc tasks |
+| `grails.codestyle.dir.checkstyle` | (auto) | Custom path to Checkstyle config dir |
+| `grails.codestyle.dir.codenarc` | (auto) | Custom path to CodeNarc config dir |
+| `skipCodeStyle` | unset | If present, all style tasks are skipped |
+
+### `grails-code-analysis` plugin (PMD + SpotBugs)
+
+| Property | Default | Description |
+|----------|---------|-------------|
+| `grails.codeanalysis.enabled.pmd` | `false` | Enable PMD |
+| `grails.codeanalysis.enabled.spotbugs` | `false` | Enable SpotBugs |
+| `grails.codeanalysis.enabled.tests` | `false` | Also analyse test source sets |
+| `grails.codeanalysis.ignoreFailures` | `false` | Collect reports without failing build |
+| `grails.codeanalysis.dir.pmd` | (auto) | Custom path to PMD config dir |
+| `skipCodeStyle` | unset | If present, all analysis tasks are also skipped |
+
+---
+
+## Fixing Violations Workflow
+
+1. Run `./gradlew aggregateViolations -Pgrails.codestyle.ignoreFailures=true -Pgrails.codeanalysis.ignoreFailures=true`
+2. Open `build/reports/violations/CODENARC_VIOLATIONS.md` and `build/reports/violations/CHECKSTYLE_VIOLATIONS.md` to see all issues by module
+3. For CodeNarc, run `./gradlew codenarcFix` to auto-fix what it can
+4. Fix remaining violations manually using the table above
+5. Re-run `./gradlew aggregateViolations` and confirm files contain `No violations found! 🎉`
+6. The reports are inside `build/` and do not need to be deleted before committing
+
+---
+
+## Reports Directory Structure
+
+All XML reports are consolidated at:
+```
+build/reports/codestyle/ ← XML inputs for style aggregation
+├── checkstyle/
+│ ├── grails-core-checkstyleMain.xml
+│ ├── grails-web-mvc-checkstyleMain.xml
+│ └── ...
+├── codenarc/
+│ ├── grails-core-codenarcMain.xml
+│ └── ...
+├── pmd/ (if enabled)
+└── spotbugs/ (if enabled)
+
+build/reports/violations/ ← Markdown summaries written by aggregateViolations
+├── CODENARC_VIOLATIONS.md
+├── CHECKSTYLE_VIOLATIONS.md
+├── PMD_VIOLATIONS.md
+├── SPOTBUGS_VIOLATIONS.md
+└── JACOCO_COVERAGE.md ← written by aggregateJacocoCoverage
+```
+
+The module name is derived from the filename: everything before the last `-` (e.g. `grails-core-checkstyleMain.xml` → module `grails-core`).
diff --git a/.github/workflows/codeanalysis.yml b/.github/workflows/codeanalysis.yml
new file mode 100644
index 0000000000..f9d08fe5f2
--- /dev/null
+++ b/.github/workflows/codeanalysis.yml
@@ -0,0 +1,95 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "Code Analysis"
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
+ pull_request:
+ workflow_dispatch:
+# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+ check_core_projects:
+ name: "Core Projects"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "🌐 Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "📥 Checkout repository"
+ uses: actions/checkout@v6
+ - name: "☕️ Setup JDK"
+ uses: actions/setup-java@v4
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "🐘 Setup Gradle"
+ uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
+ with:
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "🔎 Check Core Projects"
+ run: ./gradlew aggregateAnalysisViolations --continue
+ - name: "📤 Upload Reports"
+ if: always()
+ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: core-reports
+ path: build/reports/violations/
+ - name: "📋 Publish Code Analysis Report in Job Summary"
+ if: always()
+ run: |
+ echo "## 🔎 Code Analysis Report - Core Projects" >> $GITHUB_STEP_SUMMARY
+ for report in PMD_VIOLATIONS.md SPOTBUGS_VIOLATIONS.md; do
+ file="build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
+ done
+ check_gradle_plugin_projects:
+ name: "Gradle Plugin Projects"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "🌐 Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "📥 Checkout repository"
+ uses: actions/checkout@v6
+ - name: "☕️ Setup JDK"
+ uses: actions/setup-java@v4
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "🐘 Setup Gradle"
+ uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
+ with:
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "🔎 Check Gradle Plugin Projects"
+ working-directory: grails-gradle
+ run: ./gradlew aggregateAnalysisViolations --continue
+ - name: "📤 Upload Reports"
+ if: always()
+ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: gradle-plugin-reports
+ path: grails-gradle/build/reports/violations/
+ - name: "📋 Publish Code Analysis Report in Job Summary"
+ if: always()
+ run: |
+ echo "## 🔎 Code Analysis Report - Gradle Plugin Projects" >> $GITHUB_STEP_SUMMARY
+ for report in PMD_VIOLATIONS.md SPOTBUGS_VIOLATIONS.md; do
+ file="grails-gradle/build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
+ done
diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml
index 4a3663fea1..cb4abe4ec8 100644
--- a/.github/workflows/codestyle.yml
+++ b/.github/workflows/codestyle.yml
@@ -18,6 +18,7 @@ on:
push:
branches:
- '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
pull_request:
workflow_dispatch:
# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits
@@ -43,27 +44,23 @@ jobs:
with:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- name: "🔎 Check Core Projects"
- run: ./gradlew codeStyle
- - name: "📤 Upload Failure Reports"
+ run: ./gradlew aggregateStyleViolations --continue
+ - name: "📤 Upload Reports"
if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: core-reports
- path: build/reports/codestyle/
+ path: build/reports/violations/
- name: "📋 Publish Code Style Report in Job Summary"
if: always()
run: |
echo "## 🔎 Code Style Report - Core Projects" >> $GITHUB_STEP_SUMMARY
- for file in build/reports/codestyle/checkstyle/*.xml build/reports/codestyle/codenarc/*.xml; do
- [ -f "$file" ] || continue
- if grep -q "<error " "$file" || grep -q "<Violation " "$file"; then
- echo "### ❌ $(basename $file .xml)" >> $GITHUB_STEP_SUMMARY
- grep "<error " "$file" | awk -F'"' '{print "- **Line " $2 "**: " $8}' >> $GITHUB_STEP_SUMMARY
- grep "<Violation " "$file" | awk -F"'" '{print "- **Line " $6 "**: " $2}' >> $GITHUB_STEP_SUMMARY
- fi
+ for report in CODENARC_VIOLATIONS.md CHECKSTYLE_VIOLATIONS.md; do
+ file="build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
done
- check_gradle_plugin_projects:
- name: "Gradle Plugin Projects"
+ check_forge_projects:
+ name: "Forge Projects"
runs-on: ubuntu-24.04
steps:
- name: "🌐 Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
@@ -79,29 +76,25 @@ jobs:
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
with:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- - name: "🔎 Check Gradle Plugin Projects"
- working-directory: grails-gradle
- run: ./gradlew codeStyle
- - name: "📤 Upload Failure Reports"
+ - name: "🔎 Check Forge Projects"
+ working-directory: grails-forge
+ run: ./gradlew aggregateStyleViolations --continue
+ - name: "📤 Upload Reports"
if: always()
uses: actions/upload-artifact@v7.0.1
with:
- name: gradle-plugin-reports
- path: grails-gradle/build/reports/codestyle/
+ name: forge-reports
+ path: grails-forge/build/reports/violations/
- name: "📋 Publish Code Style Report in Job Summary"
if: always()
run: |
- echo "## 🔎 Code Style Report - Gradle Plugin Projects" >> $GITHUB_STEP_SUMMARY
- for file in grails-gradle/build/reports/codestyle/checkstyle/*.xml grails-gradle/build/reports/codestyle/codenarc/*.xml; do
- [ -f "$file" ] || continue
- if grep -q "<error " "$file" || grep -q "<Violation " "$file"; then
- echo "### ❌ $(basename $file .xml)" >> $GITHUB_STEP_SUMMARY
- grep "<error " "$file" | awk -F'"' '{print "- **Line " $2 "**: " $8}' >> $GITHUB_STEP_SUMMARY
- grep "<Violation " "$file" | awk -F"'" '{print "- **Line " $6 "**: " $2}' >> $GITHUB_STEP_SUMMARY
- fi
+ echo "## 🔎 Code Style Report - Forge Projects" >> $GITHUB_STEP_SUMMARY
+ for report in CODENARC_VIOLATIONS.md CHECKSTYLE_VIOLATIONS.md; do
+ file="grails-forge/build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
done
- check_grails_forge_projects:
- name: "Forge Projects"
+ check_gradle_plugin_projects:
+ name: "Gradle Plugin Projects"
runs-on: ubuntu-24.04
steps:
- name: "🌐 Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
@@ -117,24 +110,20 @@ jobs:
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
with:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- - name: "🔎 Check Forge Projects"
- working-directory: grails-forge
- run: ./gradlew codeStyle
- - name: "📤 Upload Failure Reports"
+ - name: "🔎 Check Gradle Plugin Projects"
+ working-directory: grails-gradle
+ run: ./gradlew aggregateStyleViolations --continue
+ - name: "📤 Upload Reports"
if: always()
uses: actions/upload-artifact@v7.0.1
with:
- name: forge-reports
- path: grails-forge/build/reports/codestyle/
+ name: gradle-plugin-reports
+ path: grails-gradle/build/reports/violations/
- name: "📋 Publish Code Style Report in Job Summary"
if: always()
run: |
- echo "## 🔎 Code Style Report - Forge Projects" >> $GITHUB_STEP_SUMMARY
- for file in grails-forge/build/reports/codestyle/checkstyle/*.xml grails-forge/build/reports/codestyle/codenarc/*.xml; do
- [ -f "$file" ] || continue
- if grep -q "<error " "$file" || grep -q "<Violation " "$file"; then
- echo "### ❌ $(basename $file .xml)" >> $GITHUB_STEP_SUMMARY
- grep "<error " "$file" | awk -F'"' '{print "- **Line " $2 "**: " $8}' >> $GITHUB_STEP_SUMMARY
- grep "<Violation " "$file" | awk -F"'" '{print "- **Line " $6 "**: " $2}' >> $GITHUB_STEP_SUMMARY
- fi
- done
\ No newline at end of file
+ echo "## 🔎 Code Style Report - Gradle Plugin Projects" >> $GITHUB_STEP_SUMMARY
+ for report in CODENARC_VIOLATIONS.md CHECKSTYLE_VIOLATIONS.md; do
+ file="grails-gradle/build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
+ done
diff --git a/build-logic/plugins/build.gradle b/build-logic/plugins/build.gradle
index ef5b010fbd..b196ee2431 100644
--- a/build-logic/plugins/build.gradle
+++ b/build-logic/plugins/build.gradle
@@ -38,6 +38,15 @@ dependencies {
implementation "${gradleBomDependencies['grails-publish-plugin']}"
implementation "org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:${gradleProperties.gradleChecksumPluginVersion}"
implementation "org.cyclonedx.bom:org.cyclonedx.bom.gradle.plugin:${gradleProperties.gradleCycloneDxPluginVersion}"
+ implementation "com.github.spotbugs.snom:spotbugs-gradle-plugin:${gradleProperties.spotbugsPluginVersion}"
+
+ testImplementation "org.spockframework:spock-core:${gradleBomDependencyVersions['gradle-spock.version']}"
+ testImplementation gradleTestKit()
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
}
gradlePlugin {
@@ -62,6 +71,14 @@ gradlePlugin {
id = 'org.apache.grails.gradle.grails-code-style'
implementationClass = 'org.apache.grails.buildsrc.GrailsCodeStylePlugin'
}
+ register('grailsCodeAnalysis') {
+ id = 'org.apache.grails.gradle.grails-code-analysis'
+ implementationClass = 'org.apache.grails.buildsrc.GrailsCodeAnalysisPlugin'
+ }
+ register('grailsViolationAggregation') {
+ id = 'org.apache.grails.gradle.grails-violation-aggregation'
+ implementationClass = 'org.apache.grails.buildsrc.GrailsViolationAggregationPlugin'
+ }
register('groovydocEnhancer') {
id = 'org.apache.grails.buildsrc.groovydoc-enhancer'
implementationClass = 'org.apache.grails.buildsrc.GroovydocEnhancerPlugin'
@@ -79,4 +96,4 @@ gradlePlugin {
implementationClass = 'org.apache.grails.buildsrc.GrailsDependencyValidatorPlugin'
}
}
-}
\ No newline at end of file
+}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy
index 55ac4e38ab..f556d634e2 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GradleUtils.groovy
@@ -22,6 +22,7 @@ package org.apache.grails.buildsrc
import groovy.transform.CompileStatic
import org.gradle.api.Project
import org.gradle.api.file.Directory
+import org.gradle.api.provider.Provider
@CompileStatic
class GradleUtils {
@@ -37,6 +38,12 @@ class GradleUtils {
asfFile.exists() ? currentDirectory : findAsfRootDir(currentDirectory.dir('../'))
}
+ static Provider<Boolean> booleanProvider(Project project, String name, boolean defaultValue = false) {
+ project.providers.gradleProperty(name)
+ .map { Boolean.parseBoolean(it as String) }
+ .orElse(defaultValue)
+ }
+
static <T> T lookupProperty(Project project, String name, T defaultValue = null) {
T v = lookupPropertyByType(project, name, defaultValue?.class) as T
return v == null ? defaultValue : v
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisExtension.groovy
similarity index 61%
copy from build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy
copy to build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisExtension.groovy
index 8d338d3482..f7ba0448fe 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisExtension.groovy
@@ -27,36 +27,27 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
@CompileStatic
-class GrailsCodeStyleExtension {
+class GrailsCodeAnalysisExtension {
/**
- * Defaults to project.buildDir/checkstyle.
- * Default checkstyle files will be written here and used from this location.
+ * Defaults to project.rootProject.buildDir/codestyle/pmd.
+ * Directory for PMD configuration files (e.g. pmd.xml).
*/
- final DirectoryProperty checkstyleDirectory
+ final DirectoryProperty pmdDirectory
/**
- * Defaults to project.buildDir/codenarc.
- * Default codenarc files will be written here and used from this location.
- */
- final DirectoryProperty codenarcDirectory
-
- /**
- * Defaults to rootProject.buildDir/reports/codestyle.
- * All Checkstyle and Codenarc reports will be written here.
+ * Defaults to rootProject.buildDir/reports/codeanalysis.
+ * PMD and SpotBugs XML reports will be written here.
*/
final DirectoryProperty reportsDirectory
@Inject
- GrailsCodeStyleExtension(ObjectFactory objects, Project project) {
- checkstyleDirectory = objects.directoryProperty().convention(
- project.rootProject.layout.buildDirectory.dir('checkstyle')
- )
- codenarcDirectory = objects.directoryProperty().convention(
- project.rootProject.layout.buildDirectory.dir('codenarc')
+ GrailsCodeAnalysisExtension(ObjectFactory objects, Project project) {
+ pmdDirectory = objects.directoryProperty().convention(
+ project.rootProject.layout.buildDirectory.dir('codeanalysis/pmd')
)
reportsDirectory = objects.directoryProperty().convention(
- project.rootProject.layout.buildDirectory.dir('reports/codestyle')
+ project.rootProject.layout.buildDirectory.dir('reports/codeanalysis')
)
}
}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisPlugin.groovy
new file mode 100644
index 0000000000..a4d1ee17d1
--- /dev/null
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeAnalysisPlugin.groovy
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.buildsrc
+
+import java.nio.file.Files
+import java.nio.file.Path
+
+import groovy.transform.CompileStatic
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.quality.Pmd
+import org.gradle.api.plugins.quality.PmdExtension
+import org.gradle.api.plugins.quality.PmdPlugin
+import org.gradle.api.provider.Provider
+
+import com.github.spotbugs.snom.Confidence
+import com.github.spotbugs.snom.Effort
+import com.github.spotbugs.snom.SpotBugsExtension
+import com.github.spotbugs.snom.SpotBugsPlugin
+import com.github.spotbugs.snom.SpotBugsReport
+import com.github.spotbugs.snom.SpotBugsTask
+
+/**
+ * Convention plugin for Grails byte code analysis (PMD and SpotBugs).
+ * Both tools are opt-in; enable via Gradle properties.
+ */
+@CompileStatic
+class GrailsCodeAnalysisPlugin implements Plugin<Project> {
+
+ static String PMD_DIR_PROPERTY = 'grails.codeanalysis.dir.pmd'
+ static String PMD_ENABLED_PROPERTY = 'grails.codeanalysis.enabled.pmd'
+ static String PMD_CONFIG_FILE_NAME = 'pmd.xml'
+
+ static String SPOTBUGS_ENABLED_PROPERTY = 'grails.codeanalysis.enabled.spotbugs'
+
+ static String IGNORE_FAILURES_PROPERTY = 'grails.codeanalysis.ignoreFailures'
+ static String TEST_ANALYSIS_PROPERTY = 'grails.codeanalysis.enabled.tests'
+
+ static String BASE_RESOURCE_PATH = '/META-INF/org.apache.grails.buildsrc.grails-code-analysis'
+
+ @Override
+ void apply(Project project) {
+ initExtension(project)
+ configurePmd(project)
+ configureSpotbugs(project)
+
+ // withType returns a live empty collection when the tool is not enabled,
+ // so these dependsOn calls are safe regardless of whether PMD/SpotBugs are active
+ project.tasks.register('codeAnalysis') { task ->
+ task.group = 'verification'
+ task.description = 'Runs code analysis checks (PMD, SpotBugs)'
+ task.dependsOn(project.tasks.withType(Pmd))
+ task.dependsOn(project.tasks.withType(SpotBugsTask))
+ }
+ }
+
+ private static void initExtension(Project project) {
+ def gca = project.extensions.create('grailsCodeAnalysis', GrailsCodeAnalysisExtension)
+
+ gca.pmdDirectory.set(project.provider {
+ def directory = project.hasProperty(PMD_DIR_PROPERTY) ?
+ project.rootProject.layout.projectDirectory.dir(project.property(PMD_DIR_PROPERTY) as String) :
+ project.rootProject.layout.buildDirectory.get().dir('codeanalysis').dir('pmd')
+
+ def toCreate = directory.asFile.toPath()
+ Files.createDirectories(toCreate)
+
+ createOrLoad(
+ toCreate.resolve(PMD_CONFIG_FILE_NAME),
+ "${BASE_RESOURCE_PATH}/pmd/${PMD_CONFIG_FILE_NAME}",
+ project
+ )
+
+ directory
+ })
+ }
+
+ private static void createOrLoad(Path expectedPath, String defaultResource, Project project) {
+ boolean defaultPath = expectedPath.startsWith(project.rootProject.buildDir.toPath())
+ if (!Files.exists(expectedPath) || expectedPath.size() == 0 || defaultPath) {
+ def defaultValue = GrailsCodeAnalysisPlugin.getResourceAsStream(defaultResource)
+ if (!defaultValue) {
+ throw new IllegalStateException("Could not locate default configuration file: ${defaultResource}")
+ }
+ project.logger.info('Replacing code analysis configuration')
+ expectedPath.text = defaultValue.text
+ }
+ }
+
+ static void configurePmd(Project project) {
+ Provider<Boolean> pmdEnabled = GradleUtils.booleanProvider(project, PMD_ENABLED_PROPERTY)
+ if (!pmdEnabled.get()) {
+ return
+ }
+
+ project.pluginManager.apply(PmdPlugin)
+
+ Provider<Boolean> ignoreFailures = GradleUtils.booleanProvider(project, IGNORE_FAILURES_PROPERTY)
+ Provider<Boolean> testStylingEnabled = GradleUtils.booleanProvider(project, TEST_ANALYSIS_PROPERTY)
+
+ project.extensions.configure(PmdExtension) {
+ it.ruleSetFiles = project.files(project.extensions.getByType(GrailsCodeAnalysisExtension).pmdDirectory.file(PMD_CONFIG_FILE_NAME))
+ it.ruleSets = []
+ it.ignoreFailures = ignoreFailures.get()
+ it.consoleOutput = true
+ it.toolVersion = project.findProperty('pmdVersion')
+ }
+
+ project.tasks.withType(Pmd).configureEach { Pmd task ->
+ task.group = 'verification'
+ task.onlyIf { !project.hasProperty('skipCodeStyle') }
+ task.ignoreFailures = ignoreFailures.get()
+
+ if (task.name.contains('Test') || task.name.contains('test')) {
+ task.enabled = testStylingEnabled.get()
+ }
+
+ task.reports.xml.required.set(true)
+ task.reports.xml.outputLocation.set(
+ project.extensions.getByType(GrailsCodeAnalysisExtension)
+ .reportsDirectory.get()
+ .dir('pmd')
+ .file("${project.name}-${task.name}.xml")
+ )
+ }
+ }
+
+ static void configureSpotbugs(Project project) {
+ Provider<Boolean> spotbugsEnabled = GradleUtils.booleanProvider(project, SPOTBUGS_ENABLED_PROPERTY)
+ if (!spotbugsEnabled.get()) {
+ return
+ }
+
+ project.pluginManager.apply(SpotBugsPlugin)
+
+ Provider<Boolean> ignoreFailures = GradleUtils.booleanProvider(project, IGNORE_FAILURES_PROPERTY)
+ Provider<Boolean> testStylingEnabled = GradleUtils.booleanProvider(project, TEST_ANALYSIS_PROPERTY)
+
+ project.extensions.configure(SpotBugsExtension) {
+ it.effort.set(Effort.valueOf('MAX'))
+ it.reportLevel.set(Confidence.valueOf('HIGH'))
+ it.ignoreFailures.set(ignoreFailures)
+ }
+
+ project.tasks.withType(SpotBugsTask).configureEach { SpotBugsTask task ->
+ task.group = 'verification'
+ NamedDomainObjectContainer<SpotBugsReport> spotBugsReports = task.reports
+ SpotBugsReport htmlReport = spotBugsReports.maybeCreate('html')
+ htmlReport.required.set(true)
+ SpotBugsReport xmlReport = spotBugsReports.maybeCreate('xml')
+ xmlReport.required.set(true)
+ xmlReport.outputLocation.set(
+ project.extensions.getByType(GrailsCodeAnalysisExtension)
+ .reportsDirectory.get()
+ .dir('spotbugs')
+ .file("${project.name}-${task.name}.xml")
+ )
+ task.onlyIf { !project.hasProperty('skipCodeStyle') }
+
+ if (task.name.contains('Test') || task.name.contains('test')) {
+ task.enabled = testStylingEnabled.get()
+ }
+ }
+ }
+}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy
index 8d338d3482..6fb04b0ae4 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStyleExtension.groovy
@@ -25,18 +25,19 @@ import groovy.transform.CompileStatic
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Property
@CompileStatic
class GrailsCodeStyleExtension {
/**
- * Defaults to project.buildDir/checkstyle.
+ * Defaults to project.rootProject.buildDir/codestyle/checkstyle.
* Default checkstyle files will be written here and used from this location.
*/
final DirectoryProperty checkstyleDirectory
/**
- * Defaults to project.buildDir/codenarc.
+ * Defaults to project.rootProject.buildDir/codestyle/codenarc.
* Default codenarc files will be written here and used from this location.
*/
final DirectoryProperty codenarcDirectory
@@ -50,10 +51,10 @@ class GrailsCodeStyleExtension {
@Inject
GrailsCodeStyleExtension(ObjectFactory objects, Project project) {
checkstyleDirectory = objects.directoryProperty().convention(
- project.rootProject.layout.buildDirectory.dir('checkstyle')
+ project.rootProject.layout.buildDirectory.dir('codestyle/checkstyle')
)
codenarcDirectory = objects.directoryProperty().convention(
- project.rootProject.layout.buildDirectory.dir('codenarc')
+ project.rootProject.layout.buildDirectory.dir('codestyle/codenarc')
)
reportsDirectory = objects.directoryProperty().convention(
project.rootProject.layout.buildDirectory.dir('reports/codestyle')
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
index 67974e3b88..d8ba2c881e 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy
@@ -21,44 +21,52 @@ package org.apache.grails.buildsrc
import java.nio.file.Files
import java.nio.file.Path
+import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.api.file.Directory
import org.gradle.api.plugins.quality.Checkstyle
import org.gradle.api.plugins.quality.CheckstyleExtension
import org.gradle.api.plugins.quality.CheckstylePlugin
import org.gradle.api.plugins.quality.CodeNarc
import org.gradle.api.plugins.quality.CodeNarcExtension
import org.gradle.api.plugins.quality.CodeNarcPlugin
+import org.gradle.api.provider.Provider
+/**
+ * Convention plugin for Grails code style enforcement (Checkstyle and CodeNarc).
+ */
@CompileStatic
class GrailsCodeStylePlugin implements Plugin<Project> {
static String CHECKSTYLE_DIR_PROPERTY = 'grails.codestyle.dir.checkstyle'
+ static String CHECKSTYLE_ENABLED_PROPERTY = 'grails.codestyle.enabled.checkstyle'
static String CHECKSTYLE_CONFIG_FILE_NAME = 'checkstyle.xml'
static String CHECKSTYLE_SUPPRESSION_CONFIG_FILE_NAME = 'checkstyle-suppressions.xml'
static String CODENARC_DIR_PROPERTY = 'grails.codestyle.dir.codenarc'
+ static String CODENARC_ENABLED_PROPERTY = 'grails.codestyle.enabled.codenarc'
static String CODENARC_CONFIG_FILE_NAME = 'codenarc.groovy'
- static String BASE_RESOURCE_PATH = '/META-INF/org.apache.grails.buildsrc.codestyle'
+ static String CODENARC_FIX_PROPERTY = 'grails.codestyle.codenarc.fix'
+
+ static String IGNORE_FAILURES_PROPERTY = 'grails.codestyle.ignoreFailures'
+
+ static String TEST_STYLING_PROPERTY = 'grails.codestyle.enabled.tests'
+
+ static String BASE_RESOURCE_PATH = '/META-INF/org.apache.grails.buildsrc.grails-code-style'
@Override
void apply(Project project) {
initExtension(project)
configureCodeStyle(project)
- doNotApplyStylingToTests(project)
}
private static void initExtension(Project project) {
def gce = project.extensions.create('grailsCodeStyle', GrailsCodeStyleExtension)
- // Unfortunately, the codenarc plugin is still using a non-lazy property.
- // Rather than rewrite the plugin to use afterEvaluate,
- // this plugin uses properties to override the configuration location by default
-
+ // We are trying to avoid afterEvaluate usage here, so use properties for enabling / disabling
gce.checkstyleDirectory.set(project.provider {
def directory = project.hasProperty(CHECKSTYLE_DIR_PROPERTY) ?
@@ -70,11 +78,13 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
createOrLoad(
toCreate.resolve(CHECKSTYLE_CONFIG_FILE_NAME),
- "${BASE_RESOURCE_PATH}/checkstyle/${CHECKSTYLE_CONFIG_FILE_NAME}"
+ "${BASE_RESOURCE_PATH}/checkstyle/${CHECKSTYLE_CONFIG_FILE_NAME}",
+ project
)
createOrLoad(
toCreate.resolve(CHECKSTYLE_SUPPRESSION_CONFIG_FILE_NAME),
- "${BASE_RESOURCE_PATH}/checkstyle/${CHECKSTYLE_SUPPRESSION_CONFIG_FILE_NAME}"
+ "${BASE_RESOURCE_PATH}/checkstyle/${CHECKSTYLE_SUPPRESSION_CONFIG_FILE_NAME}",
+ project
)
directory
@@ -90,40 +100,27 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
createOrLoad(
toCreate.resolve(CODENARC_CONFIG_FILE_NAME),
- "${BASE_RESOURCE_PATH}/codenarc/${CODENARC_CONFIG_FILE_NAME}"
+ "${BASE_RESOURCE_PATH}/codenarc/${CODENARC_CONFIG_FILE_NAME}",
+ project
)
directory
})
}
- private static void createOrLoad(Path expectedPath, String defaultResource) {
- if (!Files.exists(expectedPath) || expectedPath.size() == 0) {
+ private static void createOrLoad(Path expectedPath, String defaultResource, Project project) {
+ boolean defaultPath = expectedPath.startsWith(project.rootProject.buildDir.toPath())
+ if (!Files.exists(expectedPath) || expectedPath.size() == 0 || defaultPath) {
def defaultValue = GrailsCodeStylePlugin.getResourceAsStream(defaultResource)
if (!defaultValue) {
throw new IllegalStateException("Could not locate default configuration file: ${defaultResource}")
}
+ // TODO: This really need to use gradle caching instead
+ project.logger.info("Replacing code style configuration")
expectedPath.text = defaultValue.text
}
}
- private static void doNotApplyStylingToTests(Project project) {
- project.tasks.named('checkstyleTest') {
- it.enabled = false // Do not check test sources at this time
- }
-
- project.afterEvaluate {
- // Do not check test sources at this time
- ['codenarcIntegrationTest', 'codenarcTest'].each { testTaskName ->
- if (project.tasks.names.contains(testTaskName)) {
- project.tasks.named(testTaskName) {
- it.enabled = false
- }
- }
- }
- }
- }
-
private static void configureCodeStyle(Project project) {
configureCheckstyle(project)
configureCodenarc(project)
@@ -131,31 +128,37 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
project.tasks.register('codeStyle') {
it.group = 'verification'
it.description = 'Runs code style checks'
- it.dependsOn(project.tasks.withType(Checkstyle))
it.dependsOn(project.tasks.withType(CodeNarc))
+ it.dependsOn(project.tasks.withType(Checkstyle))
}
}
static void configureCheckstyle(Project project) {
project.pluginManager.apply(CheckstylePlugin)
+ Provider<Boolean> ignoreFailures = GradleUtils.booleanProvider(project, IGNORE_FAILURES_PROPERTY)
+ Provider<Boolean> testStylingEnabled = GradleUtils.booleanProvider(project, TEST_STYLING_PROPERTY)
+
project.extensions.configure(CheckstyleExtension) {
// Explicit `it` is required in extension configuration
it.getConfigDirectory().set(project.extensions.getByType(GrailsCodeStyleExtension).checkstyleDirectory)
it.maxWarnings = 0
it.showViolations = true
- it.ignoreFailures = false
+ it.ignoreFailures = ignoreFailures.get()
it.toolVersion = project.findProperty('checkstyleVersion')
}
project.tasks.withType(Checkstyle).configureEach { Checkstyle task ->
task.group = 'verification'
task.onlyIf { !project.hasProperty('skipCodeStyle') }
+ task.ignoreFailures = ignoreFailures.get()
+
+ if (task.name.contains('Test') || task.name.contains('test')) {
+ task.enabled = testStylingEnabled.get()
+ }
// Redirect XML report output to a single directory to consolidate
- // reports across all subprojects into one known location.
- // Include the task name to avoid overlapping outputs when a project has
- // multiple source sets (e.g. grails-cache has ast + main).
+ // reports across all subprojects into one known location
task.reports.xml.outputLocation.set(
project.extensions.getByType(GrailsCodeStyleExtension)
.reportsDirectory.get()
@@ -168,20 +171,34 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
static void configureCodenarc(Project project) {
project.pluginManager.apply(CodeNarcPlugin)
+ registerCodenarcFixTask(project)
+
+ Provider<Boolean> ignoreFailures = GradleUtils.booleanProvider(project, IGNORE_FAILURES_PROPERTY)
+ Provider<Boolean> testStylingEnabled = GradleUtils.booleanProvider(project, TEST_STYLING_PROPERTY)
+ Provider<Boolean> codenarcFix = GradleUtils.booleanProvider(project, CODENARC_FIX_PROPERTY)
+
project.extensions.configure(CodeNarcExtension) {
it.configFile = project.extensions.getByType(GrailsCodeStyleExtension)
- .codenarcDirectory.get().file(CODENARC_CONFIG_FILE_NAME).asFile
+ .codenarcDirectory.file(CODENARC_CONFIG_FILE_NAME).get().asFile
+ it.ignoreFailures = ignoreFailures.get()
it.toolVersion = project.findProperty('codenarcVersion')
}
project.tasks.withType(CodeNarc).configureEach { CodeNarc task ->
task.group = 'verification'
task.onlyIf { !project.hasProperty('skipCodeStyle') }
+ task.ignoreFailures = ignoreFailures.get()
+
+ if (codenarcFix.get()) {
+ task.dependsOn('codenarcFix')
+ }
+
+ if (task.name.contains('Test') || task.name.contains('test')) {
+ task.enabled = testStylingEnabled.get()
+ }
// Redirect XML report output to a single directory to consolidate
- // reports across all subprojects into one known location.
- // Include the task name to avoid overlapping outputs when a project has
- // multiple source sets.
+ // reports across all subprojects into one known location
task.reports.xml.required.set(true)
task.reports.xml.outputLocation.set(
project.extensions.getByType(GrailsCodeStyleExtension)
@@ -191,4 +208,52 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
)
}
}
+
+ @CompileDynamic
+ private static void registerCodenarcFixTask(Project project) {
+ project.tasks.register('codenarcFix') {
+ it.group = 'verification'
+ it.description = 'Automatically fixes some CodeNarc violations'
+ it.doLast {
+ project.fileTree(project.projectDir) {
+ it.include 'src/**/*.groovy'
+ it.include 'grails-app/**/*.groovy'
+ it.include 'scripts/**/*.groovy'
+ it.exclude '**/build/**'
+ }.each { file ->
+ String content = file.text
+ String original = content
+
+ // 1. ClassStartsWithBlankLine
+ content = content.replaceAll(/(class\s+[^{]+\{\n)([ \t]*[^ \s\n\/])/, '$1\n$2')
+
+ // 2. SpaceAroundMapEntryColon
+ content = content.replaceAll(/([\[,]\s*(?:[\w\-.]+|'[^']+'|"[^"]+")):([^\s\/])/, '$1: $2')
+ content = content.replaceAll(/(\(\s*(?:[\w\-.]+|'[^']+'|"[^"]+")):([^\s\/])/, '$1: $2')
+
+ // 3. UnnecessaryGString
+ content = content.replaceAll(/(?<!\\)(?<!")"([^"$\n\\]*)"(?!")/) { all, inner ->
+ if (!inner.contains("'")) {
+ return "'$inner'"
+ }
+ return all
+ }
+
+ // 4. UnnecessarySemicolon
+ content = content.replaceAll(/(?m);[ \t]*$/, '')
+
+ // 5. SpaceBeforeOpeningBrace
+ content = content.replaceAll(/(?<!\\)([\)\]\}\w])\{/, '$1 {')
+
+ // 6. ConsecutiveBlankLines
+ content = content.replaceAll(/\n{3,}/, '\n\n')
+
+ if (content != original) {
+ file.text = content
+ project.logger.lifecycle("Fixed CodeNarc violations in ${file.path}")
+ }
+ }
+ }
+ }
+ }
}
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPlugin.groovy
new file mode 100644
index 0000000000..d8e69897a8
--- /dev/null
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPlugin.groovy
@@ -0,0 +1,467 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.buildsrc
+
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.Directory
+import org.gradle.api.file.FileCollection
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.plugins.AppliedPlugin
+import org.gradle.api.plugins.quality.Checkstyle
+import org.gradle.api.plugins.quality.CodeNarc
+import org.gradle.api.plugins.quality.Pmd
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.testing.jacoco.tasks.JacocoReport
+
+import com.github.spotbugs.snom.SpotBugsTask
+import groovy.xml.XmlSlurper
+
+/**
+ * Root-only convention plugin that aggregates code-style violation XML reports and JaCoCo coverage
+ * CSV reports into human-readable Markdown files under build/reports/violations/.
+ *
+ * Apply this plugin to the root project only. Subprojects should apply
+ * grails-code-style and grails-jacoco individually.
+ *
+ * Tasks registered:
+ * aggregateStyleViolations — CodeNarc + Checkstyle only
+ * aggregateAnalysisViolations — PMD + SpotBugs only (requires opt-in properties)
+ * aggregateViolations — depends on both of the above
+ * aggregateJacocoCoverage — JaCoCo CSV → Markdown
+ */
+@CompileStatic
+class GrailsViolationAggregationPlugin implements Plugin<Project> {
+
+ private static final Logger LOGGER = Logging.getLogger(GrailsViolationAggregationPlugin)
+
+ @Override
+ void apply(Project project) {
+ if (project != project.rootProject) {
+ throw new GradleException(
+ 'GrailsViolationAggregationPlugin must be applied to the root project only. ' +
+ 'Apply grails-code-style and grails-jacoco to subprojects instead.'
+ )
+ }
+
+ Provider<Directory> violationsDir = project.layout.buildDirectory.dir('reports/violations')
+ Provider<Directory> styleXmlDir = project.layout.buildDirectory.dir('reports/codestyle')
+ Provider<Directory> analysisXmlDir = project.layout.buildDirectory.dir('reports/codeanalysis')
+
+ TaskProvider<Task> styleTask = registerStyleAggregation(project, styleXmlDir, violationsDir)
+ TaskProvider<Task> analysisTask = registerAnalysisAggregation(project, analysisXmlDir, violationsDir)
+ registerJacocoAggregation(project, violationsDir)
+
+ project.tasks.register('aggregateViolations') { Task task ->
+ task.group = 'verification'
+ task.description = 'Aggregates all violation reports (style + analysis) into build/reports/violations/'
+ task.dependsOn(styleTask, analysisTask)
+ }
+ }
+
+ private static TaskProvider<Task> registerStyleAggregation(Project root, Provider<Directory> styleXmlDir, Provider<Directory> violationsDir) {
+ // Wire property flags as Providers — values are resolved at task execution time, not at apply() time,
+ // and Providers are configuration-cache safe to capture in task actions
+ Provider<Boolean> checkStyleTests = GradleUtils.booleanProvider(root, GrailsCodeStylePlugin.TEST_STYLING_PROPERTY)
+ Provider<Boolean> codenarcEnabled = GradleUtils.booleanProvider(root, GrailsCodeStylePlugin.CODENARC_ENABLED_PROPERTY, true)
+ Provider<Boolean> checkstyleEnabled = GradleUtils.booleanProvider(root, GrailsCodeStylePlugin.CHECKSTYLE_ENABLED_PROPERTY, true)
+
+ TaskProvider<Task> aggregateTask = root.tasks.register('aggregateStyleViolations') { Task task ->
+ task.group = 'verification'
+ task.description = 'Aggregates CodeNarc and Checkstyle violation reports into build/reports/violations/'
+ task.outputs.file(root.file('build/reports/violations/CODENARC_VIOLATIONS.md'))
+ task.outputs.file(root.file('build/reports/violations/CHECKSTYLE_VIOLATIONS.md'))
+ task.doLast {
+ parseStyleViolations(styleXmlDir.get(), violationsDir.get(),
+ checkStyleTests.get(), codenarcEnabled.get(), checkstyleEnabled.get())
+ }
+ }
+ root.subprojects { Project sub ->
+ sub.pluginManager.withPlugin('codenarc') { AppliedPlugin p ->
+ aggregateTask.configure { Task task ->
+ task.dependsOn(sub.tasks.withType(CodeNarc))
+ }
+ }
+ sub.pluginManager.withPlugin('checkstyle') { AppliedPlugin p ->
+ aggregateTask.configure { Task task ->
+ task.dependsOn(sub.tasks.withType(Checkstyle))
+ }
+ }
+ }
+ aggregateTask
+ }
+
+ private static TaskProvider<Task> registerAnalysisAggregation(Project root, Provider<Directory> analysisXmlDir, Provider<Directory> violationsDir) {
+ Provider<Boolean> checkAnalysisTests = GradleUtils.booleanProvider(root, GrailsCodeAnalysisPlugin.TEST_ANALYSIS_PROPERTY)
+ Provider<Boolean> pmdEnabled = GradleUtils.booleanProvider(root, GrailsCodeAnalysisPlugin.PMD_ENABLED_PROPERTY)
+ Provider<Boolean> spotbugsEnabled = GradleUtils.booleanProvider(root, GrailsCodeAnalysisPlugin.SPOTBUGS_ENABLED_PROPERTY)
+
+ TaskProvider<Task> aggregateTask = root.tasks.register('aggregateAnalysisViolations') { Task task ->
+ task.group = 'verification'
+ task.description = 'Aggregates PMD and SpotBugs violation reports into build/reports/violations/'
+ task.outputs.file(root.file('build/reports/violations/PMD_VIOLATIONS.md'))
+ task.outputs.file(root.file('build/reports/violations/SPOTBUGS_VIOLATIONS.md'))
+ task.doLast {
+ parseAnalysisViolations(analysisXmlDir.get(), violationsDir.get(),
+ checkAnalysisTests.get(), pmdEnabled.get(), spotbugsEnabled.get())
+ }
+ }
+ root.subprojects { Project sub ->
+ sub.pluginManager.withPlugin('pmd') { AppliedPlugin p ->
+ aggregateTask.configure { Task task ->
+ task.dependsOn(sub.tasks.withType(Pmd))
+ }
+ }
+ sub.pluginManager.withPlugin('com.github.spotbugs') { AppliedPlugin p ->
+ aggregateTask.configure { Task task ->
+ task.dependsOn(sub.tasks.withType(SpotBugsTask))
+ }
+ }
+ }
+ aggregateTask
+ }
+
+ private static void registerJacocoAggregation(Project root, Provider<Directory> violationsDir) {
+ // Collect all potential CSV paths at configuration time — Project must not be referenced from task actions
+ FileCollection jacocoCsvFiles = root.files(
+ root.allprojects.collect { Project p -> p.file('build/reports/jacoco/test/jacocoTestReport.csv') }
+ )
+
+ TaskProvider<Task> aggregateTask = root.tasks.register('aggregateJacocoCoverage') { Task task ->
+ task.group = 'verification'
+ task.description = 'Aggregates JaCoCo coverage reports from all subprojects into build/reports/violations/'
+ task.inputs.files(jacocoCsvFiles).optional(true)
+ task.outputs.file(root.file('build/reports/violations/JACOCO_COVERAGE.md'))
+ task.doLast {
+ parseJacocoCoverage(jacocoCsvFiles, violationsDir.get())
+ }
+ }
+ root.subprojects { Project sub ->
+ sub.pluginManager.withPlugin('jacoco') { AppliedPlugin p ->
+ aggregateTask.configure { Task task ->
+ task.dependsOn(sub.tasks.withType(JacocoReport))
+ }
+ }
+ }
+ }
+
+ @CompileDynamic
+ private static void parseStyleViolations(Directory styleXmlDir, Directory violationsDir,
+ boolean checkStyleTests, boolean codenarcEnabled, boolean checkstyleEnabled) {
+ def slurper = new XmlSlurper()
+ slurper.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd', false)
+ slurper.setFeature('http://xml.org/sax/features/namespaces', false)
+
+ def getModule = { String fileName ->
+ def lastDash = fileName.lastIndexOf('-')
+ lastDash != -1 ? fileName.substring(0, lastDash) : fileName
+ }
+
+ def isTestFile = { String fileName ->
+ fileName.toLowerCase().contains('test') || fileName.toLowerCase().contains('integrationtest')
+ }
+
+ def shouldSkipClass = { boolean includeTests, String className, String filePath = null ->
+ if (includeTests) {
+ return false
+ }
+ if (filePath && (filePath.contains('src/test/') || filePath.contains('src/integrationTest/'))) {
+ return true
+ }
+ !filePath && (className.contains('Spec') || className.contains('Test') || className.contains('Tests'))
+ }
+
+ def writeReport = { String fileName, List violations, String title ->
+ def outDir = violationsDir.asFile
+ outDir.mkdirs()
+ def reportFile = new File(outDir, fileName)
+ def out = new StringBuilder()
+ out.append("# ${title}\n")
+ out.append("Generated on: ${LocalDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss'))}\n\n")
+
+ if (violations.isEmpty()) {
+ out.append('No violations found! 🎉\n')
+ } else {
+ def uniqueViolations = violations.unique().sort { v -> "${v.module}:${v.className}:${v.line}" }
+ def groupedByModule = uniqueViolations.groupBy { it.module }.sort()
+ groupedByModule.each { module, modViolations ->
+ out.append("## Module: ${module}\n")
+ out.append('| Class | Tool | Violation | Line | Message |\n')
+ out.append('| :--- | :--- | :--- | :--- | :--- |\n')
+ modViolations.each { v ->
+ out.append("| ${v.className} | ${v.tool} | ${v.type} | ${v.line} | ${v.message.replaceAll(/\|/, '\\|')} |\n")
+ }
+ out.append('\n')
+ }
+ }
+ reportFile.text = out.toString()
+ LOGGER.lifecycle("Aggregated report generated: ${reportFile.absolutePath}")
+ }
+
+ // CodeNarc
+ def codenarcViolations = []
+ def codenarcDir = styleXmlDir.dir('codenarc').asFile
+ if (codenarcDir.exists() && codenarcEnabled) {
+ codenarcDir.eachFileMatch(~/.*\.xml/) { file ->
+ if (file.size() == 0 || (!checkStyleTests && isTestFile(file.name))) {
+ return
+ }
+ def module = getModule(file.name)
+ def xml = slurper.parse(file)
+ xml.Package.each { pkg ->
+ pkg.File.each { f ->
+ def pkgName = pkg.@name.text()
+ def fileName = f.@name.text()
+ def className = pkgName ? "${pkgName}.${fileName}" : fileName
+ className = className.replace('.groovy', '').replace('.java', '')
+ if (shouldSkipClass(checkStyleTests, className, f.@name.text())) {
+ return
+ }
+ f.Violation.each { v ->
+ codenarcViolations << [
+ module : module,
+ className: className,
+ tool : 'CodeNarc',
+ type : v.@ruleName.text(),
+ line : v.@lineNumber.text(),
+ message : v.Message.text().trim()
+ ]
+ }
+ }
+ }
+ }
+ }
+ writeReport('CODENARC_VIOLATIONS.md', codenarcViolations, 'CodeNarc Violations Summary')
+
+ // Checkstyle
+ def checkstyleViolations = []
+ def checkstyleDir = styleXmlDir.dir('checkstyle').asFile
+ if (checkstyleDir.exists() && checkstyleEnabled) {
+ checkstyleDir.eachFileMatch(~/.*\.xml/) { file ->
+ if (file.size() == 0 || (!checkStyleTests && isTestFile(file.name))) {
+ return
+ }
+ def module = getModule(file.name)
+ def xml = slurper.parse(file)
+ xml.file.each { f ->
+ def filePath = f.@name.text()
+ def className = filePath.contains('src/main/groovy/') ? filePath.split('src/main/groovy/')[1] :
+ filePath.contains('src/main/java/') ? filePath.split('src/main/java/')[1] :
+ filePath.contains('src/test/groovy/') ? filePath.split('src/test/groovy/')[1] :
+ filePath.contains('src/test/java/') ? filePath.split('src/test/java/')[1] :
+ filePath.split('/').last()
+ className = className.replace('.groovy', '').replace('.java', '').replace('/', '.')
+ if (shouldSkipClass(checkStyleTests, className)) {
+ return
+ }
+ f.error.each { e ->
+ checkstyleViolations << [
+ module : module,
+ className: className,
+ tool : 'Checkstyle',
+ type : e.@source.text().split(/\./).last(),
+ line : e.@line.text(),
+ message : e.@message.text().trim()
+ ]
+ }
+ }
+ }
+ }
+ writeReport('CHECKSTYLE_VIOLATIONS.md', checkstyleViolations, 'Checkstyle Violations Summary')
+ }
+
+ @CompileDynamic
+ private static void parseAnalysisViolations(Directory analysisXmlDir, Directory violationsDir,
+ boolean checkAnalysisTests, boolean pmdEnabled, boolean spotbugsEnabled) {
+ def slurper = new XmlSlurper()
+ slurper.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd', false)
+ slurper.setFeature('http://xml.org/sax/features/namespaces', false)
+
+ def getModule = { String fileName ->
+ def lastDash = fileName.lastIndexOf('-')
+ lastDash != -1 ? fileName.substring(0, lastDash) : fileName
+ }
+
+ def isTestFile = { String fileName ->
+ fileName.toLowerCase().contains('test') || fileName.toLowerCase().contains('integrationtest')
+ }
+
+ def shouldSkipClass = { boolean includeTests, String className ->
+ if (includeTests) {
+ return false
+ }
+ className.contains('Spec') || className.contains('Test') || className.contains('Tests')
+ }
+
+ def writeReport = { String fileName, List violations, String title ->
+ def outDir = violationsDir.asFile
+ outDir.mkdirs()
+ def reportFile = new File(outDir, fileName)
+ def out = new StringBuilder()
+ out.append("# ${title}\n")
+ out.append("Generated on: ${LocalDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss'))}\n\n")
+
+ if (violations.isEmpty()) {
+ out.append('No violations found! 🎉\n')
+ } else {
+ def uniqueViolations = violations.unique().sort { v -> "${v.module}:${v.className}:${v.line}" }
+ def groupedByModule = uniqueViolations.groupBy { it.module }.sort()
+ groupedByModule.each { module, modViolations ->
+ out.append("## Module: ${module}\n")
+ out.append('| Class | Tool | Violation | Line | Message |\n')
+ out.append('| :--- | :--- | :--- | :--- | :--- |\n')
+ modViolations.each { v ->
+ out.append("| ${v.className} | ${v.tool} | ${v.type} | ${v.line} | ${v.message.replaceAll(/\|/, '\\|')} |\n")
+ }
+ out.append('\n')
+ }
+ }
+ reportFile.text = out.toString()
+ LOGGER.lifecycle("Aggregated report generated: ${reportFile.absolutePath}")
+ }
+
+ // PMD
+ def pmdViolations = []
+ def pmdDir = analysisXmlDir.dir('pmd').asFile
+ if (pmdDir.exists() && pmdEnabled) {
+ pmdDir.eachFileMatch(~/.*\.xml/) { file ->
+ if (file.size() == 0 || (!checkAnalysisTests && isTestFile(file.name))) {
+ return
+ }
+ def module = getModule(file.name)
+ def xml = slurper.parse(file)
+ xml.file.each { f ->
+ f.violation.each { v ->
+ def className = "${v.@package}.${v.@class}"
+ if (shouldSkipClass(checkAnalysisTests, className)) {
+ return
+ }
+ pmdViolations << [
+ module : module,
+ className: className,
+ tool : 'PMD',
+ type : v.@rule.text(),
+ line : v.@beginline.text(),
+ message : v.text().trim()
+ ]
+ }
+ }
+ }
+ }
+ writeReport('PMD_VIOLATIONS.md', pmdViolations, 'PMD Violations Summary')
+
+ // SpotBugs
+ def spotbugsViolations = []
+ def spotbugsDir = analysisXmlDir.dir('spotbugs').asFile
+ if (spotbugsDir.exists() && spotbugsEnabled) {
+ spotbugsDir.eachFileMatch(~/.*\.xml/) { file ->
+ if (file.size() == 0 || (!checkAnalysisTests && isTestFile(file.name))) {
+ return
+ }
+ def module = getModule(file.name)
+ def xml = slurper.parse(file)
+ xml.BugInstance.each { b ->
+ def className = b.Class.@classname.text()
+ if (shouldSkipClass(checkAnalysisTests, className)) {
+ return
+ }
+ spotbugsViolations << [
+ module : module,
+ className: className,
+ tool : 'SpotBugs',
+ type : b.@type.text(),
+ line : b.SourceLine.@start.text(),
+ message : b.LongMessage.text().trim()
+ ]
+ }
+ }
+ }
+ writeReport('SPOTBUGS_VIOLATIONS.md', spotbugsViolations, 'SpotBugs Violations Summary')
+ }
+
+ @CompileDynamic
+ private static void parseJacocoCoverage(FileCollection csvFiles, Directory violationsDir) {
+ def jacocoCoverage = []
+ csvFiles.each { File csvReport ->
+ if (csvReport.exists()) {
+ LOGGER.debug("Processing JaCoCo report: ${csvReport.absolutePath}")
+ csvReport.splitEachLine(',') { fields ->
+ if (fields.size() < 5 || fields[0] == 'GROUP') {
+ return
+ }
+ def module = fields[0]
+ def pkg = fields[1]
+ def clazz = fields[2]
+ def missedStr = fields[3]
+ def coveredStr = fields[4]
+
+ if (missedStr.isNumber() && coveredStr.isNumber()) {
+ def m = missedStr.toInteger()
+ def c = coveredStr.toInteger()
+ def total = m + c
+ def percent = total > 0 ? (c * 100 / total).round(2) : 100.0
+
+ jacocoCoverage << [
+ module : module,
+ className: "${pkg}.${clazz}",
+ percent : percent
+ ]
+ }
+ }
+ }
+ }
+
+ if (jacocoCoverage.isEmpty()) {
+ LOGGER.info('No JaCoCo coverage reports found to aggregate')
+ return
+ }
+
+ jacocoCoverage.removeIf { it.className.startsWith('org.grails.orm.hibernate.support.hibernate7.') }
+
+ def outDir = violationsDir.asFile
+ outDir.mkdirs()
+ def reportFile = new File(outDir, 'JACOCO_COVERAGE.md')
+ def out = new StringBuilder()
+ out.append('# JaCoCo Coverage Report\n')
+ out.append("Generated on: ${LocalDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss'))}\n\n")
+
+ def groupedByModule = jacocoCoverage.groupBy { it.module }.sort()
+ groupedByModule.each { module, coverageList ->
+ out.append("## Module: ${module}\n")
+ out.append('| Class | % Instructions Covered |\n')
+ out.append('| :--- | :--- |\n')
+ coverageList.sort { it.percent }.each { c ->
+ out.append("| ${c.className} | ${c.percent}% |\n")
+ }
+ out.append('\n')
+ }
+ reportFile.text = out.toString()
+ LOGGER.lifecycle("Aggregated JaCoCo report generated: ${reportFile.absolutePath}")
+ }
+}
diff --git a/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle-suppressions.xml b/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-analysis/pmd/pmd.xml
similarity index 63%
copy from build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle-suppressions.xml
copy to build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-analysis/pmd/pmd.xml
index 58ee65cc40..cf494a7125 100644
--- a/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle-suppressions.xml
+++ b/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-analysis/pmd/pmd.xml
@@ -17,6 +17,15 @@
specific language governing permissions and limitations
under the License.
-->
-<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd">
-<suppressions>
-</suppressions>
+<ruleset name="Grails PMD Rules"
+ xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
+
+ <description>PMD ruleset for the Grails codebase</description>
+
+ <rule ref="category/java/bestpractices.xml"/>
+ <rule ref="category/java/errorprone.xml"/>
+ <rule ref="category/java/security.xml"/>
+
+</ruleset>
diff --git a/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle-suppressions.xml b/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/checkstyle/checkstyle-suppressions.xml
similarity index 100%
rename from build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle-suppressions.xml
rename to build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/checkstyle/checkstyle-suppressions.xml
diff --git a/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle.xml b/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/checkstyle/checkstyle.xml
similarity index 100%
rename from build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/checkstyle/checkstyle.xml
rename to build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/checkstyle/checkstyle.xml
diff --git a/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/codenarc/codenarc.groovy b/build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/codenarc/codenarc.groovy
similarity index 100%
rename from build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.codestyle/codenarc/codenarc.groovy
rename to build-logic/plugins/src/main/resources/META-INF/org.apache.grails.buildsrc.grails-code-style/codenarc/codenarc.groovy
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy
new file mode 100644
index 0000000000..1d26a0eced
--- /dev/null
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsCodeStylePluginSpec.groovy
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.buildsrc
+
+import org.gradle.testkit.runner.GradleRunner
+import spock.lang.Specification
+import spock.lang.TempDir
+import java.nio.file.Path
+
+class GrailsCodeStylePluginSpec extends Specification {
+ @TempDir
+ Path testProjectDir
+
+ File buildFile
+ File groovyFile
+
+ def setup() {
+ buildFile = testProjectDir.resolve('build.gradle').toFile()
+ buildFile << """
+ plugins {
+ id 'groovy'
+ id 'org.apache.grails.gradle.grails-code-style'
+ }
+
+ // Minimal configuration for the plugin
+ repositories {
+ mavenCentral()
+ }
+ """
+
+ testProjectDir.resolve('src/main/groovy').toFile().mkdirs()
+ groovyFile = testProjectDir.resolve('src/main/groovy/Test.groovy').toFile()
+ }
+
+ def "test codenarcFix task fixes violations"() {
+ given: "a file with violations"
+ groovyFile.text = """package org.test
+
+class Test{
+ def map = [key:"value"]
+ def str = "unnecessary gstring"
+ def semi = "semicolon";
+ def lines = 1
+
+
+ def other = 2
+}
+"""
+ when: "running codenarcFix"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('codenarcFix', '--stacktrace')
+ .withPluginClasspath()
+ .build()
+
+ then: "task finished successfully"
+ result.task(':codenarcFix').outcome == org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+ and: "violations are fixed"
+ def fixedContent = groovyFile.text
+ fixedContent.contains('class Test {') // SpaceBeforeOpeningBrace
+ fixedContent.contains('class Test {\n\n def map')
+ fixedContent.contains("[key: 'value']") // SpaceAroundMapEntryColon and UnnecessaryGString
+ fixedContent.contains("'unnecessary gstring'") // UnnecessaryGString
+ fixedContent.contains("def semi = 'semicolon'") // UnnecessarySemicolon
+ !fixedContent.contains(";")
+ fixedContent.count('\n\n') == 3 // ConsecutiveBlankLines
+ }
+
+ def "test codenarcFix task does not break strings with single quotes"() {
+ given: "a file with double quoted strings containing single quotes"
+ groovyFile.text = """package org.test
+
+class Test {
+ def s1 = "it's a test"
+ def s2 = "format 'yyyy-MM-dd'"
+ def s3 = "contains \\"double\\" and 'single' quotes"
+}
+"""
+ when: "running codenarcFix"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('codenarcFix', '--stacktrace')
+ .withPluginClasspath()
+ .build()
+
+ then: "task finished successfully"
+ result.task(':codenarcFix').outcome == org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+ and: "strings with single quotes are NOT changed to single quotes (which would break them)"
+ def content = groovyFile.text
+ content.contains('def s1 = "it\'s a test"')
+ content.contains('def s2 = "format \'yyyy-MM-dd\'"')
+ // s3 has escaped double quotes, so it should also remain double quoted
+ content.contains('def s3 = "contains \\"double\\" and \'single\' quotes"')
+ }
+
+ def "test codenarcFix task does not break escaped double quotes in double quotes"() {
+ given: "a file with escaped double quotes"
+ groovyFile.text = """package org.test
+
+class Test {
+ def s = "\\"\\\$it\\""
+}
+"""
+ when: "running codenarcFix"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('codenarcFix', '--stacktrace')
+ .withPluginClasspath()
+ .build()
+
+ then: "task finished successfully"
+ result.task(':codenarcFix').outcome == org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+ and: "escaped quotes are NOT broken"
+ groovyFile.text.contains('"\\"\\$it\\""')
+ }
+}
diff --git a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPluginSpec.groovy b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPluginSpec.groovy
new file mode 100644
index 0000000000..be51ff7508
--- /dev/null
+++ b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/GrailsViolationAggregationPluginSpec.groovy
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.buildsrc
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import spock.lang.Specification
+import spock.lang.TempDir
+
+import java.nio.file.Path
+
+class GrailsViolationAggregationPluginSpec extends Specification {
+
+ @TempDir
+ Path testProjectDir
+
+ def "plugin must be applied to root project only"() {
+ given: "a subproject-only build with the aggregation plugin"
+ testProjectDir.resolve('settings.gradle').toFile().text = "include 'sub'"
+ testProjectDir.resolve('build.gradle').toFile().text = ''
+ def sub = testProjectDir.resolve('sub')
+ sub.toFile().mkdirs()
+ sub.resolve('build.gradle').toFile().text = """
+ plugins {
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
+ }
+ """
+
+ when: "configuring the project"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('tasks')
+ .withPluginClasspath()
+ .buildAndFail()
+
+ then: "an error is thrown"
+ result.output.contains('must be applied to the root project only')
+ }
+
+ def "all aggregation tasks are registered on root"() {
+ given: "root project with aggregation plugin"
+ testProjectDir.resolve('settings.gradle').toFile().text = ''
+ testProjectDir.resolve('build.gradle').toFile().text = """
+ plugins {
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
+ }
+ """
+
+ when: "listing verification tasks"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('tasks', '--group=verification')
+ .withPluginClasspath()
+ .build()
+
+ then:
+ result.output.contains('aggregateStyleViolations')
+ result.output.contains('aggregateAnalysisViolations')
+ result.output.contains('aggregateViolations')
+ result.output.contains('aggregateJacocoCoverage')
+ }
+
+ def "aggregateStyleViolations writes CodeNarc and Checkstyle reports to build/reports/violations/"() {
+ given: "a root project with a subproject that has codestyle XML reports"
+ testProjectDir.resolve('settings.gradle').toFile().text = "include 'app-module'"
+ testProjectDir.resolve('build.gradle').toFile().text = """
+ plugins {
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
+ }
+ """
+ def moduleDir = testProjectDir.resolve('app-module')
+ moduleDir.toFile().mkdirs()
+ moduleDir.resolve('build.gradle').toFile().text = """
+ plugins {
+ id 'groovy'
+ id 'org.apache.grails.gradle.grails-code-style'
+ }
+ repositories { mavenCentral() }
+ dependencies {
+ implementation localGroovy()
+ }
+ """
+ def srcFile = moduleDir.resolve('src/main/groovy/com/example/AppClass.groovy').toFile()
+ srcFile.parentFile.mkdirs()
+ srcFile.text = 'package com.example\nclass AppClass {}'
+
+ // Pre-populate XML reports in the standard consolidated location (build/reports/codestyle/)
+ def checkstyleDir = testProjectDir.resolve('build/reports/codestyle/checkstyle').toFile()
+ checkstyleDir.mkdirs()
+ new File(checkstyleDir, 'app-module-checkstyleMain.xml').text = """<?xml version="1.0" encoding="UTF-8"?>
+<checkstyle version="10.0">
+<file name="${srcFile.absolutePath}">
+<error line="2" column="1" severity="error" message="Missing a Javadoc comment." source="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck"/>
+</file>
+</checkstyle>
+"""
+ def codenarcDir = testProjectDir.resolve('build/reports/codestyle/codenarc').toFile()
+ codenarcDir.mkdirs()
+ new File(codenarcDir, 'app-module-codenarcMain.xml').text = """<?xml version="1.0" encoding="UTF-8"?>
+<CodeNarc version="3.1.0">
+<Package name="com.example">
+<File name="AppClass.groovy">
+<Violation ruleName="EmptyClass" priority="2" lineNumber="1">
+<Message>The class is empty</Message>
+</Violation>
+</File>
+</Package>
+</CodeNarc>
+"""
+
+ when: "running aggregateStyleViolations skipping the actual style tasks"
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('aggregateStyleViolations', '-x', 'checkstyleMain', '-x', 'codenarcMain', '--stacktrace')
+ .withPluginClasspath()
+ .build()
+
+ then: "task succeeds"
+ result.task(':aggregateStyleViolations').outcome == TaskOutcome.SUCCESS
+
+ and: "reports land in build/reports/violations/ — NOT in the repo root"
+ def violationsDir = testProjectDir.resolve('build/reports/violations').toFile()
+ new File(violationsDir, 'CHECKSTYLE_VIOLATIONS.md').exists()
+ new File(violationsDir, 'CODENARC_VIOLATIONS.md').exists()
+ !testProjectDir.resolve('CHECKSTYLE_VIOLATIONS.md').toFile().exists()
+ !testProjectDir.resolve('CODENARC_VIOLATIONS.md').toFile().exists()
+
+ and: "checkstyle report contains the violation"
+ def checkstyleMd = new File(violationsDir, 'CHECKSTYLE_VIOLATIONS.md').text
+ checkstyleMd.contains('## Module: app-module')
+ checkstyleMd.contains('JavadocPackageCheck')
+
+ and: "codenarc report contains the violation"
+ def codenarcMd = new File(violationsDir, 'CODENARC_VIOLATIONS.md').text
+ codenarcMd.contains('## Module: app-module')
+ codenarcMd.contains('EmptyClass')
+ }
+
+ def "aggregateJacocoCoverage handles no csv reports gracefully"() {
+ given: "root project with no subproject csv reports"
+ testProjectDir.resolve('settings.gradle').toFile().text = ''
+ testProjectDir.resolve('build.gradle').toFile().text = """
+ plugins {
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
+ }
+ """
+
+ when:
+ def result = GradleRunner.create()
+ .withProjectDir(testProjectDir.toFile())
+ .withArguments('aggregateJacocoCoverage', '--stacktrace')
+ .withPluginClasspath()
+ .build()
+
+ then: "task succeeds without error"
+ result.task(':aggregateJacocoCoverage').outcome == TaskOutcome.SUCCESS
+
+ and: "no report file is created"
+ !testProjectDir.resolve('build/reports/violations/JACOCO_COVERAGE.md').toFile().exists()
+ }
+}
diff --git a/build.gradle b/build.gradle
index bb330e2487..87b54c25f2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,6 +15,10 @@
* limitations under the License.
*/
+plugins {
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
+}
+
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
diff --git a/gradle.properties b/gradle.properties
index 547692b327..75cd9d7d1c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -70,6 +70,8 @@ jbossTransactionApiVersion=2.0.0.Final
# build dependencies for code quality checks
checkstyleVersion=11.0.0
codenarcVersion=3.6.0-groovy-4.0
+pmdVersion=6.55.0
+spotbugsPluginVersion=6.4.8
# This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs
# https://github.com/apache/grails-gradle-plugin/issues/222
diff --git a/grails-async/core/build.gradle b/grails-async/core/build.gradle
index cee4816f2e..b2f5f45bc1 100644
--- a/grails-async/core/build.gradle
+++ b/grails-async/core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-async/gpars/build.gradle b/grails-async/gpars/build.gradle
index 8c8d001b26..e30f7d2e28 100644
--- a/grails-async/gpars/build.gradle
+++ b/grails-async/gpars/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-async/plugin/build.gradle b/grails-async/plugin/build.gradle
index c6d1f34085..73984e20e0 100644
--- a/grails-async/plugin/build.gradle
+++ b/grails-async/plugin/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-async/rxjava/build.gradle b/grails-async/rxjava/build.gradle
index 0bc5e0a8c7..9494becc59 100644
--- a/grails-async/rxjava/build.gradle
+++ b/grails-async/rxjava/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-async/rxjava2/build.gradle b/grails-async/rxjava2/build.gradle
index 1e3d564320..aa269ef1b8 100644
--- a/grails-async/rxjava2/build.gradle
+++ b/grails-async/rxjava2/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-async/rxjava3/build.gradle b/grails-async/rxjava3/build.gradle
index d5c36cb6f4..25e643124b 100644
--- a/grails-async/rxjava3/build.gradle
+++ b/grails-async/rxjava3/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-bootstrap/build.gradle b/grails-bootstrap/build.gradle
index 8326c9da51..22b423f149 100644
--- a/grails-bootstrap/build.gradle
+++ b/grails-bootstrap/build.gradle
@@ -29,6 +29,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-cache/build.gradle b/grails-cache/build.gradle
index 9d607d16ca..45c83921c4 100644
--- a/grails-cache/build.gradle
+++ b/grails-cache/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-codecs-core/build.gradle b/grails-codecs-core/build.gradle
index a6e2562f86..8b952e765f 100644
--- a/grails-codecs-core/build.gradle
+++ b/grails-codecs-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-codecs/build.gradle b/grails-codecs/build.gradle
index 0e612cbb19..47fbaab85c 100644
--- a/grails-codecs/build.gradle
+++ b/grails-codecs/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-common/build.gradle b/grails-common/build.gradle
index 8957177a9a..f6ca3c40f1 100644
--- a/grails-common/build.gradle
+++ b/grails-common/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-console/build.gradle b/grails-console/build.gradle
index d3ec60a3f1..21b341638d 100644
--- a/grails-console/build.gradle
+++ b/grails-console/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-controllers/build.gradle b/grails-controllers/build.gradle
index dc68dacf8f..962430281b 100644
--- a/grails-controllers/build.gradle
+++ b/grails-controllers/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-converters/build.gradle b/grails-converters/build.gradle
index 0bb79ca376..22e3e605e1 100644
--- a/grails-converters/build.gradle
+++ b/grails-converters/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-core/build.gradle b/grails-core/build.gradle
index 7e486bfaae..bbea195d0d 100644
--- a/grails-core/build.gradle
+++ b/grails-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-hibernate5/boot-plugin/build.gradle b/grails-data-hibernate5/boot-plugin/build.gradle
index 2c6c0aa627..6dfcb4389d 100644
--- a/grails-data-hibernate5/boot-plugin/build.gradle
+++ b/grails-data-hibernate5/boot-plugin/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-hibernate5/core/build.gradle b/grails-data-hibernate5/core/build.gradle
index 1f77e3657d..4838afb4af 100644
--- a/grails-data-hibernate5/core/build.gradle
+++ b/grails-data-hibernate5/core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-hibernate5/dbmigration/build.gradle b/grails-data-hibernate5/dbmigration/build.gradle
index 3a74dd0429..342dbcfb17 100644
--- a/grails-data-hibernate5/dbmigration/build.gradle
+++ b/grails-data-hibernate5/dbmigration/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-hibernate5/grails-plugin/build.gradle b/grails-data-hibernate5/grails-plugin/build.gradle
index 22fd52fe58..dd0a1a13de 100644
--- a/grails-data-hibernate5/grails-plugin/build.gradle
+++ b/grails-data-hibernate5/grails-plugin/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-hibernate5/spring-orm/build.gradle b/grails-data-hibernate5/spring-orm/build.gradle
index 3713dfb9cc..6e65fbdc63 100644
--- a/grails-data-hibernate5/spring-orm/build.gradle
+++ b/grails-data-hibernate5/spring-orm/build.gradle
@@ -28,6 +28,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/boot-plugin/build.gradle b/grails-data-mongodb/boot-plugin/build.gradle
index 572e90fa09..31a76648f2 100644
--- a/grails-data-mongodb/boot-plugin/build.gradle
+++ b/grails-data-mongodb/boot-plugin/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/bson/build.gradle b/grails-data-mongodb/bson/build.gradle
index 1ac2663ee7..6c1e0243bb 100644
--- a/grails-data-mongodb/bson/build.gradle
+++ b/grails-data-mongodb/bson/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/core/build.gradle b/grails-data-mongodb/core/build.gradle
index f080478090..94c1e6d97e 100644
--- a/grails-data-mongodb/core/build.gradle
+++ b/grails-data-mongodb/core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/ext/build.gradle b/grails-data-mongodb/ext/build.gradle
index f8271315be..982b44960d 100644
--- a/grails-data-mongodb/ext/build.gradle
+++ b/grails-data-mongodb/ext/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/grails-plugin/build.gradle b/grails-data-mongodb/grails-plugin/build.gradle
index 1ace1bc70d..c22e6dca21 100644
--- a/grails-data-mongodb/grails-plugin/build.gradle
+++ b/grails-data-mongodb/grails-plugin/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-mongodb/gson-templates/build.gradle b/grails-data-mongodb/gson-templates/build.gradle
index 445f6f3676..da0d0758fa 100644
--- a/grails-data-mongodb/gson-templates/build.gradle
+++ b/grails-data-mongodb/gson-templates/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-data-simple/build.gradle b/grails-data-simple/build.gradle
index 22567f865c..1290a63ad5 100644
--- a/grails-data-simple/build.gradle
+++ b/grails-data-simple/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-databinding-core/build.gradle b/grails-databinding-core/build.gradle
index cf2bf90b51..cee4c8051b 100644
--- a/grails-databinding-core/build.gradle
+++ b/grails-databinding-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-databinding/build.gradle b/grails-databinding/build.gradle
index 86b4fe0f04..363c001285 100644
--- a/grails-databinding/build.gradle
+++ b/grails-databinding/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-async/build.gradle b/grails-datamapping-async/build.gradle
index a273194893..00c2afdff4 100644
--- a/grails-datamapping-async/build.gradle
+++ b/grails-datamapping-async/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-core/build.gradle b/grails-datamapping-core/build.gradle
index 9eb9a7412c..700db6df88 100644
--- a/grails-datamapping-core/build.gradle
+++ b/grails-datamapping-core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-rx/build.gradle b/grails-datamapping-rx/build.gradle
index 66157ce459..e0d5c9d6ea 100644
--- a/grails-datamapping-rx/build.gradle
+++ b/grails-datamapping-rx/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-support/build.gradle b/grails-datamapping-support/build.gradle
index 95656e2b87..78a342a5c8 100644
--- a/grails-datamapping-support/build.gradle
+++ b/grails-datamapping-support/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-tck/build.gradle b/grails-datamapping-tck/build.gradle
index 15e7de9509..bc42a8baa4 100644
--- a/grails-datamapping-tck/build.gradle
+++ b/grails-datamapping-tck/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datamapping-validation/build.gradle b/grails-datamapping-validation/build.gradle
index 0748bcffd9..accbf04ac2 100644
--- a/grails-datamapping-validation/build.gradle
+++ b/grails-datamapping-validation/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datasource/build.gradle b/grails-datasource/build.gradle
index a6ed818743..c6cf03293e 100644
--- a/grails-datasource/build.gradle
+++ b/grails-datasource/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datastore-async/build.gradle b/grails-datastore-async/build.gradle
index 68f9ed6685..edb0d968a2 100644
--- a/grails-datastore-async/build.gradle
+++ b/grails-datastore-async/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datastore-core/build.gradle b/grails-datastore-core/build.gradle
index 8750516d37..85da8ab7a1 100644
--- a/grails-datastore-core/build.gradle
+++ b/grails-datastore-core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-datastore-web/build.gradle b/grails-datastore-web/build.gradle
index 138497f193..7589e7531e 100644
--- a/grails-datastore-web/build.gradle
+++ b/grails-datastore-web/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-domain-class/build.gradle b/grails-domain-class/build.gradle
index a34f2607f9..9510314243 100644
--- a/grails-domain-class/build.gradle
+++ b/grails-domain-class/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-encoder/build.gradle b/grails-encoder/build.gradle
index a1cac6930e..692cf98566 100644
--- a/grails-encoder/build.gradle
+++ b/grails-encoder/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/core/build.gradle b/grails-events/core/build.gradle
index 0c68efd560..fed99b5dd2 100644
--- a/grails-events/core/build.gradle
+++ b/grails-events/core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/gpars/build.gradle b/grails-events/gpars/build.gradle
index ad365eceee..3e80b36094 100644
--- a/grails-events/gpars/build.gradle
+++ b/grails-events/gpars/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/plugin/build.gradle b/grails-events/plugin/build.gradle
index d2cc1e2445..5069cad90f 100644
--- a/grails-events/plugin/build.gradle
+++ b/grails-events/plugin/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/rxjava/build.gradle b/grails-events/rxjava/build.gradle
index 312477fefa..d53e072abc 100644
--- a/grails-events/rxjava/build.gradle
+++ b/grails-events/rxjava/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/rxjava2/build.gradle b/grails-events/rxjava2/build.gradle
index 5a107dd924..43791ba91f 100644
--- a/grails-events/rxjava2/build.gradle
+++ b/grails-events/rxjava2/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/rxjava3/build.gradle b/grails-events/rxjava3/build.gradle
index efca93c18d..2daa711d67 100644
--- a/grails-events/rxjava3/build.gradle
+++ b/grails-events/rxjava3/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/spring/build.gradle b/grails-events/spring/build.gradle
index c568f92623..caefaf3ef6 100644
--- a/grails-events/spring/build.gradle
+++ b/grails-events/spring/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-events/transforms/build.gradle b/grails-events/transforms/build.gradle
index 778980c095..b8797a4699 100644
--- a/grails-events/transforms/build.gradle
+++ b/grails-events/transforms/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-fields/build.gradle b/grails-fields/build.gradle
index 1ed72931b9..1b30c8c623 100644
--- a/grails-fields/build.gradle
+++ b/grails-fields/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-forge/build.gradle b/grails-forge/build.gradle
index d262f97fab..a7ac18ca66 100644
--- a/grails-forge/build.gradle
+++ b/grails-forge/build.gradle
@@ -25,6 +25,7 @@ import java.time.format.DateTimeFormatter
plugins {
id 'org.apache.grails.buildsrc.properties'
id 'org.apache.grails.buildsrc.dependency-validator'
+ id 'org.apache.grails.gradle.grails-violation-aggregation'
}
ext {
diff --git a/grails-forge/gradle/code-style-config.gradle b/grails-forge/gradle/code-style-config.gradle
index c8d7bbaf83..b3a1e36a60 100644
--- a/grails-forge/gradle/code-style-config.gradle
+++ b/grails-forge/gradle/code-style-config.gradle
@@ -25,6 +25,7 @@ apply plugin: 'codenarc'
apply plugin: 'com.diffplug.spotless'
apply plugin: 'io.spring.nohttp' // enforce https everywhere
apply plugin: 'org.apache.grails.gradle.grails-code-style'
+apply plugin: 'org.apache.grails.gradle.grails-code-analysis'
extensions.configure(SpotlessExtension) {
// Explicit `it` is required in extension configuration block
diff --git a/grails-geb/build.gradle b/grails-geb/build.gradle
index 160bdf8e95..db3b5b47ea 100644
--- a/grails-geb/build.gradle
+++ b/grails-geb/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gradle/common/build.gradle b/grails-gradle/common/build.gradle
index 7d05cb8211..9ec66022ff 100644
--- a/grails-gradle/common/build.gradle
+++ b/grails-gradle/common/build.gradle
@@ -24,6 +24,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gradle/model/build.gradle b/grails-gradle/model/build.gradle
index 23f4e81e02..2cd0762d4d 100644
--- a/grails-gradle/model/build.gradle
+++ b/grails-gradle/model/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gradle/plugins/build.gradle b/grails-gradle/plugins/build.gradle
index 405b5d4462..78bca0abee 100644
--- a/grails-gradle/plugins/build.gradle
+++ b/grails-gradle/plugins/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gradle/tasks/build.gradle b/grails-gradle/tasks/build.gradle
index a994c5f868..5970381fa2 100644
--- a/grails-gradle/tasks/build.gradle
+++ b/grails-gradle/tasks/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/core/build.gradle b/grails-gsp/core/build.gradle
index e55a044be7..a032e6f974 100644
--- a/grails-gsp/core/build.gradle
+++ b/grails-gsp/core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-layout/build.gradle b/grails-gsp/grails-layout/build.gradle
index 32eb8c13c3..59e02446ec 100644
--- a/grails-gsp/grails-layout/build.gradle
+++ b/grails-gsp/grails-layout/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-sitemesh3/build.gradle b/grails-gsp/grails-sitemesh3/build.gradle
index 676039b035..494e1dd0c9 100644
--- a/grails-gsp/grails-sitemesh3/build.gradle
+++ b/grails-gsp/grails-sitemesh3/build.gradle
@@ -26,6 +26,7 @@ plugins {
// TODO: id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-taglib/build.gradle b/grails-gsp/grails-taglib/build.gradle
index 55c91cfe45..6ddf4cdd5c 100644
--- a/grails-gsp/grails-taglib/build.gradle
+++ b/grails-gsp/grails-taglib/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-web-gsp-taglib/build.gradle b/grails-gsp/grails-web-gsp-taglib/build.gradle
index 869592160c..e29c66a4ae 100644
--- a/grails-gsp/grails-web-gsp-taglib/build.gradle
+++ b/grails-gsp/grails-web-gsp-taglib/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-web-gsp/build.gradle b/grails-gsp/grails-web-gsp/build.gradle
index f9df4c2edd..b5c19fef86 100644
--- a/grails-gsp/grails-web-gsp/build.gradle
+++ b/grails-gsp/grails-web-gsp/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-web-jsp/build.gradle b/grails-gsp/grails-web-jsp/build.gradle
index 7776095819..92a20f8bcd 100644
--- a/grails-gsp/grails-web-jsp/build.gradle
+++ b/grails-gsp/grails-web-jsp/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/grails-web-taglib/build.gradle b/grails-gsp/grails-web-taglib/build.gradle
index a69196b114..f6c5fed7d1 100644
--- a/grails-gsp/grails-web-taglib/build.gradle
+++ b/grails-gsp/grails-web-taglib/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-gsp/plugin/build.gradle b/grails-gsp/plugin/build.gradle
index 767a130825..82e924f5a1 100644
--- a/grails-gsp/plugin/build.gradle
+++ b/grails-gsp/plugin/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-i18n/build.gradle b/grails-i18n/build.gradle
index ad7785dbfc..c08aa68d99 100644
--- a/grails-i18n/build.gradle
+++ b/grails-i18n/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-interceptors/build.gradle b/grails-interceptors/build.gradle
index 7d18d14b91..a54d1af354 100644
--- a/grails-interceptors/build.gradle
+++ b/grails-interceptors/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-logging/build.gradle b/grails-logging/build.gradle
index 9ba07de826..40ca7b5ae2 100644
--- a/grails-logging/build.gradle
+++ b/grails-logging/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-mimetypes/build.gradle b/grails-mimetypes/build.gradle
index 0391a33364..20cd89b716 100644
--- a/grails-mimetypes/build.gradle
+++ b/grails-mimetypes/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-rest-transforms/build.gradle b/grails-rest-transforms/build.gradle
index 3cbfdba682..ad80cb4057 100644
--- a/grails-rest-transforms/build.gradle
+++ b/grails-rest-transforms/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-scaffolding/build.gradle b/grails-scaffolding/build.gradle
index 49020837a1..93dfa5f2a4 100644
--- a/grails-scaffolding/build.gradle
+++ b/grails-scaffolding/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-services/build.gradle b/grails-services/build.gradle
index eda69a8eaf..c13a91ef24 100644
--- a/grails-services/build.gradle
+++ b/grails-services/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-shell-cli/build.gradle b/grails-shell-cli/build.gradle
index 1843aa8825..b63e2a865c 100644
--- a/grails-shell-cli/build.gradle
+++ b/grails-shell-cli/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-spring/build.gradle b/grails-spring/build.gradle
index 574091c26e..45048ac9c3 100644
--- a/grails-spring/build.gradle
+++ b/grails-spring/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-test-core/build.gradle b/grails-test-core/build.gradle
index 82af7708ce..f23de34dc1 100644
--- a/grails-test-core/build.gradle
+++ b/grails-test-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-test-suite-base/build.gradle b/grails-test-suite-base/build.gradle
index 453cb33570..6e5b9a320d 100644
--- a/grails-test-suite-base/build.gradle
+++ b/grails-test-suite-base/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.dependency-validator'
id 'org.apache.grails.buildsrc.compile'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-core/build.gradle b/grails-testing-support-core/build.gradle
index 11d60439dd..223b2a9ba9 100644
--- a/grails-testing-support-core/build.gradle
+++ b/grails-testing-support-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-datamapping/build.gradle b/grails-testing-support-datamapping/build.gradle
index 09f4203521..715fbe3f98 100755
--- a/grails-testing-support-datamapping/build.gradle
+++ b/grails-testing-support-datamapping/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-dbcleanup-core/build.gradle b/grails-testing-support-dbcleanup-core/build.gradle
index 7368f6dee1..70f9682077 100644
--- a/grails-testing-support-dbcleanup-core/build.gradle
+++ b/grails-testing-support-dbcleanup-core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
diff --git a/grails-testing-support-dbcleanup-h2/build.gradle b/grails-testing-support-dbcleanup-h2/build.gradle
index c12e761a30..2bc1cc0adf 100644
--- a/grails-testing-support-dbcleanup-h2/build.gradle
+++ b/grails-testing-support-dbcleanup-h2/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
diff --git a/grails-testing-support-dbcleanup-postgresql/build.gradle b/grails-testing-support-dbcleanup-postgresql/build.gradle
index 3644ad1461..6ff298cbc1 100644
--- a/grails-testing-support-dbcleanup-postgresql/build.gradle
+++ b/grails-testing-support-dbcleanup-postgresql/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-http-client/build.gradle b/grails-testing-support-http-client/build.gradle
index ddb22c9656..7d8119d07d 100644
--- a/grails-testing-support-http-client/build.gradle
+++ b/grails-testing-support-http-client/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-mongodb/build.gradle b/grails-testing-support-mongodb/build.gradle
index be2333a450..cad012a5cb 100644
--- a/grails-testing-support-mongodb/build.gradle
+++ b/grails-testing-support-mongodb/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-views-gson/build.gradle b/grails-testing-support-views-gson/build.gradle
index 7bfcc80cfe..c847e79679 100644
--- a/grails-testing-support-views-gson/build.gradle
+++ b/grails-testing-support-views-gson/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-testing-support-web/build.gradle b/grails-testing-support-web/build.gradle
index 385b7bca5d..6b362c056d 100755
--- a/grails-testing-support-web/build.gradle
+++ b/grails-testing-support-web/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-url-mappings/build.gradle b/grails-url-mappings/build.gradle
index 720b4ba50d..9e548d1212 100644
--- a/grails-url-mappings/build.gradle
+++ b/grails-url-mappings/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-validation/build.gradle b/grails-validation/build.gradle
index 22cf4c820a..38b907bf6d 100644
--- a/grails-validation/build.gradle
+++ b/grails-validation/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-views-core/build.gradle b/grails-views-core/build.gradle
index 3da0370d06..d30a0d560d 100644
--- a/grails-views-core/build.gradle
+++ b/grails-views-core/build.gradle
@@ -26,6 +26,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-views-gson/build.gradle b/grails-views-gson/build.gradle
index 3848ebd373..de8b38ed5f 100644
--- a/grails-views-gson/build.gradle
+++ b/grails-views-gson/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-views-markup/build.gradle b/grails-views-markup/build.gradle
index 6d6bc3da1b..48de6f7f0c 100644
--- a/grails-views-markup/build.gradle
+++ b/grails-views-markup/build.gradle
@@ -25,6 +25,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-boot/build.gradle b/grails-web-boot/build.gradle
index 755fe01739..3dbbd9fde6 100644
--- a/grails-web-boot/build.gradle
+++ b/grails-web-boot/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-common/build.gradle b/grails-web-common/build.gradle
index 1053fba613..ff03053790 100644
--- a/grails-web-common/build.gradle
+++ b/grails-web-common/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-core/build.gradle b/grails-web-core/build.gradle
index 2b60e56ea9..cd9b52c658 100644
--- a/grails-web-core/build.gradle
+++ b/grails-web-core/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-databinding/build.gradle b/grails-web-databinding/build.gradle
index 3fc3cd3578..92b7aecc41 100644
--- a/grails-web-databinding/build.gradle
+++ b/grails-web-databinding/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-mvc/build.gradle b/grails-web-mvc/build.gradle
index ac72a8621b..d16e9f41f3 100644
--- a/grails-web-mvc/build.gradle
+++ b/grails-web-mvc/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-web-url-mappings/build.gradle b/grails-web-url-mappings/build.gradle
index adcd1f6579..06c6b87870 100644
--- a/grails-web-url-mappings/build.gradle
+++ b/grails-web-url-mappings/build.gradle
@@ -27,6 +27,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion
diff --git a/grails-wrapper/build.gradle b/grails-wrapper/build.gradle
index d51388ab06..71a80b3ee4 100644
--- a/grails-wrapper/build.gradle
+++ b/grails-wrapper/build.gradle
@@ -24,6 +24,7 @@ plugins {
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
+ id 'org.apache.grails.gradle.grails-code-analysis'
}
version = projectVersion