diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 6c4938f8a..a21db2e5b 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,24 +1,117 @@ name-template: $RESOLVED_VERSION tag-template: v$RESOLVED_VERSION +pull-request: + title-templates: + fix: '🐛 $TITLE (#$NUMBER)' + feat: '🚀 $TITLE (#$NUMBER)' + default: '$TITLE (#$NUMBER)' +autolabeler: + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'improvement' + branch: + - '/improv\/.+/' + title: + - '/improv/i' + - label: 'feature' + branch: + - '/feature\/.+/' + title: + - '/feat/i' + - label: 'documentation' + branch: + - '/docs\/.+/' + title: + - '/docs/i' + - label: 'maintenance' + branch: + - '/(chore|refactor|style|test|ci|perf|build)\/.+/' + title: + - '/(chore|refactor|style|test|ci|perf|build)/i' + - label: 'chore' + branch: + - '/chore\/.+/' + title: + - '/chore/i' + - label: 'refactor' + branch: + - '/refactor\/.+/' + title: + - '/refactor/i' + - label: 'style' + branch: + - '/style\/.+/' + title: + - '/style/i' + - label: 'test' + branch: + - '/test\/.+/' + title: + - '/test/i' + - label: 'ci' + branch: + - '/ci\/.+/' + title: + - '/ci/i' + - label: 'perf' + branch: + - '/perf\/.+/' + title: + - '/perf/i' + - label: 'build' + branch: + - '/build\/.+/' + title: + - '/build/i' + - label: 'deps' + branch: + - '/deps\/.+/' + title: + - '/deps/i' + - label: 'revert' + branch: + - '/revert\/.+/' + title: + - '/revert/i' categories: - - title: ✨ Features + - title: '🚀 Features' labels: + - 'feature' - "type: enhancement" - "type: new feature" - "type: major" - - title: 🐛 Bug Fixes/Improvements + - "type: minor" + - title: '💡 Improvements' labels: + - 'improvement' - "type: improvement" + + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bug' - "type: bug" - - "type: minor" - - title: 🛠 Dependency upgrades + - title: '📚 Documentation' labels: - - "type: dependency upgrade" - - "dependencies" - - title: ⚙️ Build/CI + - 'docs' + - title: '🔧 Maintenance' labels: + - 'maintenance' + - 'chore' + - 'refactor' + - 'style' + - 'test' + - 'ci' + - 'perf' + - 'build' - "type: ci" - "type: build" + - title: '⏪ Reverts' + labels: + - 'revert' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' version-resolver: major: diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7edf67f01..20a58c243 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -2,26 +2,22 @@ name: Java CI on: push: branches: - - master - '[2-9]+.[0-9]+.x' pull_request: branches: - - master - '[2-9]+.[0-9]+.x' +env: + GIT_USER_NAME: puneetbehl + GIT_USER_EMAIL: behlp@unityfoundation.io + jobs: - build: + test_project: runs-on: ubuntu-latest - + if: github.event_name == 'pull_request' strategy: - fail-fast: false - matrix: - java: [11, 17] - - env: - WORKSPACE: ${{ github.workspace }} - GRADLE_OPTS: -Xmx1500m -Dfile.encoding=UTF-8 - + fail-fast: false + matrix: { java: [11, 17] } steps: - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v2 @@ -29,7 +25,6 @@ jobs: with: distribution: temurin java-version: ${{ matrix.java }} - - name: Run Tests if: github.event_name == 'pull_request' id: tests @@ -39,56 +34,55 @@ jobs: GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} with: - arguments: | - check - -Dgeb.env=chromeHeadless + arguments: check -Dgeb.env=chromeHeadless + build_project: + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 + - uses: actions/setup-java@v4 + with: { java-version: 11, distribution: temurin } - name: Run Build - if: github.event_name == 'push' - id: build uses: gradle/actions/setup-gradle@v3 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} with: - arguments: | - build - -Dgeb.env=chromeHeadless + arguments: build -Dgeb.env=chromeHeadless - - name: Publish Snapshot to repo.grails.org - id: publish + - name: Publish Snapshot artifacts to Artifactory (repo.grails.org) + if: success() uses: gradle/actions/setup-gradle@v3 - if: steps.build.outcome == 'success' && github.event_name == 'push' && matrix.java == '11' env: - ORG_GRADLE_PROJECT_artifactoryUsername: ${{ secrets.ARTIFACTORY_USERNAME }} - ORG_GRADLE_PROJECT_artifactoryPassword: ${{ secrets.ARTIFACTORY_PASSWORD }} GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} - GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + ORG_GRADLE_PROJECT_artifactoryUsername: ${{ secrets.ARTIFACTORY_USERNAME }} + ORG_GRADLE_PROJECT_artifactoryPassword: ${{ secrets.ARTIFACTORY_PASSWORD }} with: arguments: | -Dorg.gradle.internal.publish.checksums.insecure=true publish - - name: Build Docs - id: docs - if: steps.build.outcome == 'success' && github.event_name == 'push' && matrix.java == '11' + - name: Generate Snapshot Documentation + if: success() uses: gradle/actions/setup-gradle@v3 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} - GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} with: arguments: views-docs:docs - - name: Publish Snapshot docs to Github Pages - if: steps.docs.outcome == 'success' && github.event_name == 'push' && matrix.java == '11' + - name: Publish Snapshot Documentation to Github Pages + if: success() uses: micronaut-projects/github-pages-deploy-action@grails env: - TARGET_REPOSITORY: ${{ github.repository }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} BRANCH: gh-pages + COMMIT_EMAIL: ${{ env.GIT_USER_EMAIL }} + COMMIT_NAME: ${{ env.GIT_USER_NAME }} FOLDER: docs/build/docs - COMMIT_EMAIL: behlp@unityfoundation.io - COMMIT_NAME: Puneet Behl + GH_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/groovy-joint-workflow.yml b/.github/workflows/groovy-joint-workflow.yml index 19f271233..620ec928e 100644 --- a/.github/workflows/groovy-joint-workflow.yml +++ b/.github/workflows/groovy-joint-workflow.yml @@ -41,8 +41,8 @@ jobs: - name: Get version of Gradle Enterprise plugin id: gradle_enterprise_version run: | - GE_PLUGIN_VERSION=$(grep -m 1 'id\s*\(\"com.gradle.enterprise\"\|'"'com.gradle.enterprise'"'\)\s*version' settings.gradle | sed -E "s/.*version[[:space:]]*['\"]?([0-9]+\.[0-9]+\.[0-9]+)['\"]?.*/\1/" | tr -d [:space:]) - GE_USER_DATA_PLUGIN_VERSION=$(grep -m 1 'id\s*\(\"com.gradle.common-custom-user-data-gradle-plugin\"\|'"'com.gradle.common-custom-user-data-gradle-plugin'"'\)\s*version' settings.gradle | sed -E "s/.*version[[:space:]]*['\"]?([0-9]+\.[0-9]+\.[0-9]+)['\"]?.*/\1/" | tr -d [:space:]) + GE_PLUGIN_VERSION=$(grep -m 1 'id\s*\(\"com.gradle.enterprise\"\|'"'com.gradle.enterprise'"'\)\s*version' settings.gradle | sed -E "s/.*version[[:space:]]*['\"]?([0-9]+\.[0-9]+(\.[0-9]+)?)['\"]?.*/\1/" | tr -d [:space:]) + GE_USER_DATA_PLUGIN_VERSION=$(grep -m 1 'id\s*\(\"com.gradle.common-custom-user-data-gradle-plugin\"\|'"'com.gradle.common-custom-user-data-gradle-plugin'"'\)\s*version' settings.gradle | sed -E "s/.*version[[:space:]]*['\"]?([0-9]+\.[0-9]+(\.[0-9]+)?)['\"]?.*/\1/" | tr -d [:space:]) echo "Project uses Gradle Enterprise Plugin version: $GE_PLUGIN_VERSION" echo "Project uses Gradle Common Custom User Data Plugin version: $GE_USER_DATA_PLUGIN_VERSION" echo "ge_plugin_version=$GE_PLUGIN_VERSION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml index dd1a081c6..8453cc17b 100644 --- a/.github/workflows/release-notes.yml +++ b/.github/workflows/release-notes.yml @@ -6,6 +6,10 @@ on: branches: - master - '[2-9]+.[0-9]+.x' + pull_request: + types: [opened, reopened, synchronize] + pull_request_target: + types: [opened, reopened, synchronize] workflow_dispatch: jobs: release_notes: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 628d57dad..ffb2e1f5a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,36 +2,47 @@ name: Release on: release: types: [published] - jobs: - - release: + publish: + outputs: + release_version: ${{ steps.release_version.outputs.value }} runs-on: ubuntu-latest - env: GIT_USER_NAME: puneetbehl GIT_USER_EMAIL: behlp@unityfoundation.io - steps: - - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v2 - uses: actions/setup-java@v4 - with: { java-version: 11, distribution: temurin } - - - name: Set the current release version + with: + java-version: 11 + distribution: temurin + - name: Get the current release version id: release_version - run: echo "release_version=${GITHUB_REF:11}" >> $GITHUB_OUTPUT - - - name: Run pre-release + run: echo "value=${GITHUB_REF:11}" >> $GITHUB_OUTPUT + - name: Set projectVersion to the release version uses: micronaut-projects/github-actions/pre-release@master - - - name: Generate secring file + - name: Run Assemble + if: success() + id: assemble + uses: gradle/gradle-build-action@v3 + with: + arguments: assemble + env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + - name: Upload Distribution + if: success() + uses: actions/upload-artifact@v4 + with: + name: grails-views-${{ steps.release_version.outputs.value }} + path: ./**/build/libs/* + - name: Generate key file for artifact signing env: SECRING_FILE: ${{ secrets.SECRING_FILE }} run: echo $SECRING_FILE | base64 -d > ${{ github.workspace }}/secring.gpg - - - name: Publish to Sonatype + - name: Publish release artifacts to Sonatype id: publish_to_sonatype uses: gradle/actions/setup-gradle@v3 env: @@ -41,40 +52,79 @@ jobs: ORG_GRADLE_PROJECT_sonatypeOssUsername: ${{ secrets.SONATYPE_USERNAME }} ORG_GRADLE_PROJECT_sonatypeOssPassword: ${{ secrets.SONATYPE_PASSWORD }} ORG_GRADLE_PROJECT_sonatypeOssStagingProfileId: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} - ORG_GRADLE_PROJECT_sonatypeOssRepo: 'https://s01.oss.sonatype.org/service/local/' + ORG_GRADLE_PROJECT_sonatypeOssRepo: ${{ secrets.SONATYPE_NEXUS_URL }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} with: arguments: | - -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg - publishToSonatype - closeAndReleaseSonatypeStagingRepository - + -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + publishToSonatype + closeSonatypeStagingRepository + release: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_TOKEN }} + ref: v${{ needs.publish.outputs.release_version }} + - name: Nexus Staging Close And Release + uses: gradle/gradle-build-action@v3 + env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + ORG_GRADLE_PROJECT_sonatypeOssUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_sonatypeOssPassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_sonatypeOssStagingProfileId: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + ORG_GRADLE_PROJECT_sonatypeOssRepo: ${{ secrets.SONATYPE_NEXUS_URL }} + with: + arguments: | + findSonatypeStagingRepository + releaseSonatypeStagingRepository + - name: Run post-release + if: success() + uses: micronaut-projects/github-actions/post-release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + env: + SNAPSHOT_SUFFIX: -SNAPSHOT + docs: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_TOKEN }} + ref: v${{ needs.publish.outputs.release_version }} + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: '11' - name: Generate Documentation if: success() uses: gradle/actions/setup-gradle@v3 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} - GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} + GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} with: arguments: views-docs:docs - - - name: Export Gradle Properties - uses: micronaut-projects/github-actions/export-gradle-properties@master - - - name: Publish to Github Pages + - name: Publish Documentation to Github Pages if: success() uses: micronaut-projects/github-pages-deploy-action@grails env: - BETA: ${{ contains(steps.release_version.outputs.release_version, 'M') || contains(steps.release_version.outputs.release_version, 'RC') }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} + BETA: ${{ contains(needs.publish.outputs.release_version, 'M') }} BRANCH: gh-pages - FOLDER: docs/build/docs - VERSION: ${{ steps.release_version.outputs.release_version }} COMMIT_EMAIL: ${{ env.GIT_USER_EMAIL }} COMMIT_NAME: ${{ env.GIT_USER_NAME }} - - - name: Run post-release - if: success() - uses: micronaut-projects/github-actions/post-release@master \ No newline at end of file + FOLDER: docs/build/docs + GH_TOKEN: ${{ secrets.GH_TOKEN }} + VERSION: ${{ needs.publish.outputs.release_version }} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 224ef345d..d8a4745ac 100644 --- a/build.gradle +++ b/build.gradle @@ -6,38 +6,44 @@ plugins { id 'org.asciidoctor.jvm.convert' } +group = 'org.grails' version = projectVersion ext.set('grailsVersion', libs.versions.grails.asProvider().get()) ext.set('isSnapshot', version.endsWith('-SNAPSHOT')) ext.set('isReleaseVersion', !isSnapshot) -ext.set('pomInfo', { - delegate.url 'https://views.grails.org/latest/' - delegate.licenses { - delegate.license { - delegate.name 'The Apache Software License, Version 2.0' - delegate.url 'https://www.apache.org/licenses/LICENSE-2.0.txt' + +allprojects { + + ext.set('signing.keyId', findProperty('signing.keyId') ?: System.getenv('SIGNING_KEY')) + ext.set('signing.password', findProperty('signing.password') ?: System.getenv('SIGNING_PASSPHRASE')) + ext.set('signing.secretKeyRingFile', findProperty('signing.secretKeyRingFile') ?: "${System.properties['user.home']}${File.separator}.gnupg${File.separator}secring.gpg") + ext.set('pomInfo', { + delegate.url 'https://views.grails.org/latest/' + delegate.licenses { + delegate.license { + delegate.name 'The Apache Software License, Version 2.0' + delegate.url 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } } - } - delegate.scm { - delegate.url 'https://github.com/grails/grails-views/' - delegate.connection 'scm:git:git://github.com/grails/grails-views' - delegate.developerConnection 'scm:git:ssh://github.com:grails/grails-views' - } - delegate.developers { - delegate.developer { - delegate.id 'graemerocher' - delegate.name 'Graeme Rocher' - delegate.email 'graeme.rocher@gmail.com' + delegate.scm { + delegate.url 'https://github.com/grails/grails-views/' + delegate.connection 'scm:git:git://github.com/grails/grails-views' + delegate.developerConnection 'scm:git:ssh://github.com:grails/grails-views' } - delegate.developer { - delegate.id 'puneetbehl' - delegate.name 'Puneet Behl' - delegate.email 'behlp@unityfoundation.io' + delegate.developers { + delegate.developer { + delegate.id 'graemerocher' + delegate.name 'Graeme Rocher' + delegate.email 'graeme.rocher@gmail.com' + } + delegate.developer { + delegate.id 'puneetbehl' + delegate.name 'Puneet Behl' + delegate.email 'behlp@unityfoundation.io' + } } - } -}) + }) -allprojects { version = rootProject.version repositories { mavenLocal() // Used by Groovy Joint workflow github action after building Groovy @@ -63,14 +69,17 @@ allprojects { 'Implementation-Vendor': 'grails.org' ) } + tasks.withType(Sign).configureEach { + onlyIf { isReleaseVersion } + } } if (isReleaseVersion) { nexusPublishing { - String ossUser = project.findProperty('sonatypeOssUsername') ?: '' - String ossPass = project.findProperty('sonatypeOssPassword') ?: '' - String ossStagingProfileId = project.findProperty('sonatypeOssStagingProfileId') ?: '' - String ossRepo = project.findProperty('sonatypeOssRepo') ?: '' + String ossUser = findProperty('sonatypeOssUsername') + String ossPass = findProperty('sonatypeOssPassword') + String ossStagingProfileId = findProperty('sonatypeOssStagingProfileId') + String ossRepo = findProperty('sonatypeOssRepo') ?: 'https://s01.oss.sonatype.org/service/local/' repositories { sonatype { nexusUrl = uri(ossRepo) diff --git a/core/build.gradle b/core/build.gradle index 6177e8f6a..9540a3a89 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,43 +8,24 @@ plugins { group = 'org.grails' ext.set('projectDesc', 'Grails Views Core') -// Groovydoc task requires the Groovy version of Gradle -configurations.register('groovydocImplementation') - dependencies { - implementation libs.groovy.core + api libs.caffeine // Used in public API + api libs.grails.datastore.core // MappingContext is used in public API + api libs.grails.mimetypes // MimeUtility is used in public API + api libs.grails.rest // Used in public API + api libs.grails.web.urlmappings // LinkGenerator is used in public API + api libs.spring.context // MessageSource is used in public API - implementation libs.grails.core - implementation libs.grails.web.urlmappings - implementation libs.grails.mimetypes + implementation libs.grails.bootstrap implementation libs.grails.datastore.gorm.support - implementation libs.grails.rest - implementation libs.slf4j.api - - api libs.caffeine // Used in public api + implementation libs.spring.beans testImplementation libs.spock.core - - groovydocImplementation localGroovy(), { - because 'The Groovydoc task requires the Groovy version of Gradle' - } -} - -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withJavadocJar() - withSourcesJar() -} - -tasks.named('javadocJar', Jar) { - from tasks.named('groovydoc') -} - -tasks.withType(Groovydoc).configureEach { - classpath = configurations.groovydocImplementation - groovyClasspath = configurations.groovydocImplementation + testRuntimeOnly libs.slf4j.nop // Get rid of warnings about missing slf4j implementation during test task } -apply from: rootProject.layout.projectDirectory.file('gradle/grailsCentralPublishing.gradle') \ No newline at end of file +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/api-docs-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') \ No newline at end of file diff --git a/core/src/main/groovy/grails/views/mvc/renderer/DefaultViewRenderer.groovy b/core/src/main/groovy/grails/views/mvc/renderer/DefaultViewRenderer.groovy index a8ceabe5a..806e4b231 100644 --- a/core/src/main/groovy/grails/views/mvc/renderer/DefaultViewRenderer.groovy +++ b/core/src/main/groovy/grails/views/mvc/renderer/DefaultViewRenderer.groovy @@ -13,7 +13,6 @@ import org.grails.plugins.web.rest.render.ServletRenderContext import org.grails.plugins.web.rest.render.html.DefaultHtmlRenderer import org.grails.web.util.GrailsApplicationAttributes import org.springframework.web.servlet.ModelAndView -import org.springframework.web.servlet.View import org.springframework.web.servlet.view.AbstractUrlBasedView /** diff --git a/docs/build.gradle b/docs/build.gradle index ef9181383..cc1881d8f 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -20,6 +20,8 @@ tasks.withType(AsciidoctorTask).configureEach { 'icons' : 'font', 'version' : version //'sourcedir' : coreSourceDir + + outputDir layout.buildDirectory.dir('docs') } // Groovydoc task requires the Groovy version of Gradle @@ -54,4 +56,4 @@ tasks.withType(Groovydoc).configureEach { tasks.register('docs') { group = 'documentation' dependsOn 'apidocs', 'asciidoctor' -} +} \ No newline at end of file diff --git a/docs/src/docs/asciidoc/json/history.adoc b/docs/src/docs/asciidoc/json/history.adoc index 55cae0d7b..754c34745 100644 --- a/docs/src/docs/asciidoc/json/history.adoc +++ b/docs/src/docs/asciidoc/json/history.adoc @@ -2,6 +2,8 @@ The current release is {version}. Below is a version history. +For later releases check the release notes at: https://github.com/grails/grails-views/releases + *2.0.2* * Fix bug where incorrect JSON was generated when all values are null in the nested object. diff --git a/docs/src/docs/asciidoc/json/modelNaming.adoc b/docs/src/docs/asciidoc/json/modelNaming.adoc index 28e7f3fb9..166725f3a 100644 --- a/docs/src/docs/asciidoc/json/modelNaming.adoc +++ b/docs/src/docs/asciidoc/json/modelNaming.adoc @@ -3,7 +3,7 @@ Grails Framework supports a convention for the model names in your JSON views. If the convention does not meet your needs, model variables can be explicitly defined. NOTE: Some model names are _reserved_ since there are properties of the same name injected into the view: -`locale`, `response`, `request`, `page`, `controllerNamespace`, `controllerName`, `actionName`, `config`, `generator` +`locale`, `response`, `request`, `page`, `controllerNamespace`, `controllerName`, `actionName`, `config`, `generator`, `json` == Explicit Model Naming diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 374a75714..6995192bf 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -1,71 +1,57 @@ plugins { - id 'java-gradle-plugin' - id 'groovy' - id 'maven-publish' - id 'signing' + id 'java-gradle-plugin' + id 'groovy' + id 'maven-publish' + id 'signing' } group = 'org.grails.plugins' ext.set('projectDesc', 'Grails Views Gradle Plugin') dependencies { - implementation libs.grails.bootstrap - implementation libs.grails.gradle.plugin - implementation libs.groovy.core - implementation libs.spring.boot.gradle.plugin - // This is a workaround for grails-bootstrap exposing a - // different version of groovy-xml than the one used by Gradle. - // This causes issues with the Groovy compiler. - compileOnly "org.codehaus.groovy:groovy-xml:$GroovySystem.version" -} + // the gradle api is provided by java-gradle-plugin -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withJavadocJar() - withSourcesJar() -} + implementation libs.grails.bootstrap, { + // grails-bootstrap leaks groovy-xml which is a problem for Gradle (version conflict) + exclude group: 'org.codehaus.groovy', module: 'groovy-xml' + } + implementation libs.grails.gradle.plugin + implementation libs.spring.boot.gradle.plugin -tasks.named('javadocJar', Jar) { - from tasks.named('groovydoc') -} - -tasks.withType(Sign).configureEach { - onlyIf { isReleaseVersion } + compileOnly libs.groovy.core // @CompileStatic } gradlePlugin { - plugins { - register('viewsJson') { - id = 'org.grails.plugins.views-json' - implementationClass = 'grails.views.gradle.json.GrailsJsonViewsPlugin' - displayName = 'Grails Json Views Gradle Plugin' - description = 'The Gradle plugin for Json Views' - } - register('viewsMarkup') { - id = 'org.grails.plugins.views-markup' - implementationClass = 'grails.views.gradle.markup.GrailsMarkupViewsPlugin' - displayName = 'Grails Markup Views Gradle Plugin' - description = 'The Gradle plugin for Markup Views' - } - } + plugins { + create('viewsJson') { + id = 'org.grails.plugins.views-json' + implementationClass = 'grails.views.gradle.json.GrailsJsonViewsPlugin' + displayName = 'Grails Json Views Gradle Plugin' + description = 'The Gradle plugin for Json Views' + } + create('viewsMarkup') { + id = 'org.grails.plugins.views-markup' + implementationClass = 'grails.views.gradle.markup.GrailsMarkupViewsPlugin' + displayName = 'Grails Markup Views Gradle Plugin' + description = 'The Gradle plugin for Markup Views' + } + } } +// Publishing for this project is handled +// separately as it is using the java-gradle-plugin afterEvaluate { - signing { - required { isReleaseVersion && gradle.taskGraph.hasTask('publish') } - Publication[] publications = new Publication[publishing.publications.size() - 1] - publishing.publications.findAll { it.name != 'pluginMaven' }.toArray(publications) - sign(publications) - } + publishing.publications.each { MavenPublication publication -> + if (publication.name != "pluginMaven") { + publication.pom.withXml { + def xml = asNode() + xml.children().last() + pomInfo + } + } + } +} - publishing.publications.each { publication -> - MavenPublication pub = publication as MavenPublication - if (pub.name != 'pluginMaven') { - pub.pom.withXml { - def xml = asNode() - xml.children().last() + pomInfo - } - } - } -} \ No newline at end of file +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/api-docs-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') \ No newline at end of file diff --git a/gradle-plugin/src/main/groovy/grails/views/gradle/markup/GrailsMarkupViewsPlugin.groovy b/gradle-plugin/src/main/groovy/grails/views/gradle/markup/GrailsMarkupViewsPlugin.groovy index cf4f1ece8..baf6eede3 100644 --- a/gradle-plugin/src/main/groovy/grails/views/gradle/markup/GrailsMarkupViewsPlugin.groovy +++ b/gradle-plugin/src/main/groovy/grails/views/gradle/markup/GrailsMarkupViewsPlugin.groovy @@ -1,7 +1,6 @@ package grails.views.gradle.markup import grails.views.gradle.AbstractGroovyTemplatePlugin -import grails.views.gradle.markup.MarkupViewCompilerTask import groovy.transform.CompileStatic /** diff --git a/gradle/api-docs-config.gradle b/gradle/api-docs-config.gradle new file mode 100644 index 000000000..137159f23 --- /dev/null +++ b/gradle/api-docs-config.gradle @@ -0,0 +1,14 @@ +// Groovydoc task requires the Groovy version of Gradle +configurations.register('groovydocImplementation') + +dependencies { + groovydocImplementation localGroovy(), { + because 'The Groovydoc task requires the Groovy version of Gradle' + } +} +tasks.withType(Groovydoc).configureEach { + classpath = configurations.groovydocImplementation +} +tasks.named('javadocJar', Jar) { + from tasks.named('groovydoc') +} diff --git a/gradle/buildsrc.libs.versions.toml b/gradle/buildsrc.libs.versions.toml index f64815544..0eb675358 100644 --- a/gradle/buildsrc.libs.versions.toml +++ b/gradle/buildsrc.libs.versions.toml @@ -1,8 +1,8 @@ [versions] -asciidoctor-gradle-jvm = '4.0.1' -assetpipeline = '4.3.0' +asciidoctor-gradle-jvm = '4.0.2' +assetpipeline = '4.4.0' grails-gradle-plugin = '6.1.2' -grails-views = '3.1.2' +grails-views = '3.2.3' groovy-doc = '1.0.1' nexus-publish-gradle-plugin = '1.3.0' diff --git a/gradle/grails-plugin-config.gradle b/gradle/grails-plugin-config.gradle new file mode 100644 index 000000000..a2916122f --- /dev/null +++ b/gradle/grails-plugin-config.gradle @@ -0,0 +1,9 @@ +group = 'org.grails.plugins' + +tasks.named('bootJar') { + enabled = false // Grails plugins shouldn't produce a boot jar +} +tasks.named('jar', Jar) { + enabled = true + archiveClassifier = '' // Skip the '-plain' suffix on the jar file name +} diff --git a/gradle/grailsCentralPublishing.gradle b/gradle/grailsCentralPublishing.gradle deleted file mode 100644 index c7ac1470f..000000000 --- a/gradle/grailsCentralPublishing.gradle +++ /dev/null @@ -1,56 +0,0 @@ -ext.set('signing.keyId', project.findProperty('signing.keyId') ?: System.getenv('SIGNING_KEY')) -ext.set('signing.password', project.findProperty('signing.password') ?: System.getenv('SIGNING_PASSPHRASE')) - -def javaComponent = components.named('java') -project.extensions.configure(PublishingExtension) { PublishingExtension pe -> - - pe.publications.register('pluginMaven', MavenPublication) { - artifactId = project.name - from javaComponent.get() - versionMapping { - usage('java-api') { fromResolutionOf('runtimeClasspath') } - usage('java-runtime') { fromResolutionResult() } - } - pom { - name = 'Grails Views' - description = 'Provides additional view technologies to the Grails framework, including JSON and Markup views.' - } - pom.withXml { - def pomNode = asNode() - pomNode.children().last() + pomInfo - - // dependency management shouldn't be included - try { pomNode.dependencyManagement.replaceNode({}) } catch (Throwable ignore) {} - } - } - - if (isSnapshot) { - repositories { - maven { - credentials { - username = project.findProperty('artifactoryUsername') ?: '' - password = project.findProperty('artifactoryPassword') ?: '' - } - url = group == 'org.grails.plugins' ? - uri('https://repo.grails.org/grails/plugins3-snapshots-local') : - uri('https://repo.grails.org/grails/libs-snapshots-local') - } - } - } -} - -afterEvaluate { - def mavenPublication = project.extensions.findByType(PublishingExtension).publications.named('pluginMaven') - project.extensions.configure(SigningExtension) { SigningExtension se -> - se.required = { isReleaseVersion && gradle.taskGraph.hasTask('publish') } - se.sign mavenPublication.get() - } -} - -tasks.withType(Sign).configureEach { - onlyIf { isReleaseVersion } -} - -tasks.register('install') { - dependsOn 'publishToMavenLocal' -} \ No newline at end of file diff --git a/gradle/java-config.gradle b/gradle/java-config.gradle new file mode 100644 index 000000000..255391d97 --- /dev/null +++ b/gradle/java-config.gradle @@ -0,0 +1,5 @@ +java { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) + withJavadocJar() + withSourcesJar() +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7863c79e7..fc5b27ee8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,20 @@ [versions] assetpipeline = '4.3.0' caffeine = '2.9.3' -gorm = '8.0.4' -gorm-hibernate5 = '8.0.3' -gorm-mongodb = '8.1.2' -grails = '6.1.2' +gorm = '8.1.2' +gorm-hibernate5 = '8.1.0' +gorm-mongodb = '8.2.0' +grails = '6.2.0' grails-gradle-plugin = '6.1.2' -grails-testing-support = '3.1.2' -groovy = '3.0.11' +grails-testing-support = '3.2.2' +groovy = '3.0.21' java-baseline = '11' -micronaut = '3.8.7' +javax-annotation-api = '1.3.2' +micronaut = '3.10.4' +mongodb = '4.11.2' slf4j = '1.7.36' spock = '2.3-groovy-3.0' +spring = '5.3.33' spring-boot = '2.7.18' [libraries] @@ -23,6 +26,7 @@ grails-datastore-core = { module = 'org.grails:grails-datastore-core', version.r grails-datastore-gorm-hibernate5 = { module = 'org.grails:grails-datastore-gorm-hibernate5', version.ref = 'gorm-hibernate5' } grails-datastore-gorm-mongodb = { module = 'org.grails:grails-datastore-gorm-mongodb', version.ref = 'gorm-mongodb' } grails-datastore-gorm-support = { module = 'org.grails:grails-datastore-gorm-support', version.ref = 'gorm' } +grails-encoder = { module = 'org.grails:grails-encoder', version.ref = 'grails' } grails-gradle-plugin = { module = 'org.grails:grails-gradle-plugin', version.ref = 'grails-gradle-plugin' } grails-mimetypes = { module = 'org.grails:grails-plugin-mimetypes', version.ref = 'grails' } grails-rest = { module = 'org.grails:grails-plugin-rest', version.ref = 'grails' } @@ -31,8 +35,16 @@ grails-testing-support-gorm = { module = 'org.grails:grails-gorm-testing-support grails-web-urlmappings = { module = 'org.grails:grails-web-url-mappings', version.ref = 'grails' } groovy-core = { module = 'org.codehaus.groovy:groovy', version.ref = 'groovy' } groovy-json = { module = 'org.codehaus.groovy:groovy-json', version.ref = 'groovy' } +groovy-templates = { module = 'org.codehaus.groovy:groovy-templates', version.ref = 'groovy' } +javax-annotation-api = { module = 'javax.annotation:javax.annotation-api', version.ref = 'javax-annotation-api' } micronaut-http-client = { module = 'io.micronaut:micronaut-http-client', version.ref = 'micronaut' } +mongodb-bson = { module = 'org.mongodb:bson', version.ref = 'mongodb' } slf4j-api = { module = 'org.slf4j:slf4j-api', version.ref = 'slf4j' } slf4j-nop = { module = 'org.slf4j:slf4j-nop', version.ref = 'slf4j' } spock-core = { module = 'org.spockframework:spock-core', version.ref = 'spock' } +spring-beans = { module = 'org.springframework:spring-beans', version.ref = 'spring' } +spring-context = { module = 'org.springframework:spring-context', version.ref = 'spring' } +spring-web = { module = 'org.springframework:spring-web', version.ref = 'spring' } +spring-webmvc = { module = 'org.springframework:spring-webmvc', version.ref = 'spring' } +spring-boot = { module = 'org.springframework.boot:spring-boot', version.ref = 'spring-boot' } spring-boot-gradle-plugin = { module = 'org.springframework.boot:spring-boot-gradle-plugin', version.ref = 'spring-boot' } \ No newline at end of file diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle new file mode 100644 index 000000000..081789fa2 --- /dev/null +++ b/gradle/publishing.gradle @@ -0,0 +1,65 @@ +def javaComponent = components.named('java') + +publishing { + publications { + pluginMaven(MavenPublication) { + artifactId = project.name + if (project.name != 'views-gradle') { + from javaComponent.get() + } + versionMapping { + usage('java-api') { fromResolutionOf('runtimeClasspath') } + usage('java-runtime') { fromResolutionResult() } + } + if (ext.has('isGrailsPlugin')) { + artifact source: "${project.sourceSets.main.groovy.classesDirectory.getAsFile().get()}/META-INF/grails-plugin.xml", + classifier: "plugin", + extension: 'xml' + } + pom { + name = 'Grails Views' + description = 'Provides additional view technologies to the Grails framework, including JSON and Markup views.' + } + pom.withXml { + def pomNode = asNode() + pomNode.children().last() + pomInfo + // dependency management shouldn't be included + try { + pomNode.dependencyManagement.replaceNode({}) + } catch (Throwable ignore) { + } + } + } + } + + if (isSnapshot) { + repositories { + maven { + credentials { + username = project.findProperty('artifactoryUsername') + password = project.findProperty('artifactoryPassword') + } + url = group == 'org.grails.plugins' ? + uri('https://repo.grails.org/grails/plugins3-snapshots-local') : + uri('https://repo.grails.org/grails/libs-snapshots-local') + } + } + } +} + +afterEvaluate { + signing { + required = { isReleaseVersion && gradle.taskGraph.hasTask("publish") } + Publication[] publications = new Publication[project.publishing.publications.size()] + project.publishing.publications.findAll().toArray(publications) + sign(publications) + } + + tasks.withType(Sign) { + onlyIf { isReleaseVersion } + } +} + +tasks.register('install') { + dependsOn 'publishToMavenLocal' +} \ No newline at end of file diff --git a/json-templates/build.gradle b/json-templates/build.gradle index 615cc70d2..20ddb046e 100644 --- a/json-templates/build.gradle +++ b/json-templates/build.gradle @@ -12,47 +12,48 @@ dependencies { api project(':views-json') - // Should this be api, implementation, compileOnly or provided? - // The templates expect classes from this library as models - // but I'm not sure in what context these templates are used - // They were previously compileOnly - implementation libs.grails.datastore.gorm.mongodb + // The templates in this project use classes from these dependencies so they should be in the implementation configuration. + // + // But, setting them as implementation will make grails apps using this project try to autoconfigure MongoDB beans, + // even without the real MongoDB dependencies on the classpath, which fails with ClassNotFoundExeption. + // + // Therefore, because this project is only supposed to work with MongoDB, setting them as compileOnly/provided is + // probably best, as it will not break apps that erroneously include this project as a dependency but don't use MongoDB. + compileOnly libs.grails.datastore.gorm.mongodb // provided + compileOnly libs.mongodb.bson // provided compileOnly libs.slf4j.nop // Get rid of warning about missing slf4j implementation during compileGsonViews task } -def sourceDir = layout.projectDirectory.dir('src/templates') -def outputDir = layout.buildDirectory.dir('classes/groovy/main') +def templateSourceDir = layout.projectDirectory.dir('src/templates') +def compilationOutputDir = layout.buildDirectory.dir('classes/groovy/main') sourceSets { main { groovy { - srcDirs = [sourceDir] + // Add templates as source dir + srcDirs = [templateSourceDir] } } } tasks.register('compileViews', JavaExec) { - inputs.dir sourceDir - outputs.dir outputDir + inputs.dir templateSourceDir + outputs.dir compilationOutputDir mainClass = 'grails.plugin.json.view.JsonViewCompiler' - classpath configurations.compileClasspath + configurations.runtimeClasspath - args(sourceDir.asFile, outputDir.get().asFile, libs.versions.java.baseline.get(), ' ', ' ', 'none', 'UTF-8') + classpath configurations.compileClasspath + args(templateSourceDir.asFile, compilationOutputDir.get().asFile, libs.versions.java.baseline.get(), ' ', ' ', 'none', 'UTF-8') } +// This is needed to trigger compilation of the views tasks.named('classes') { dependsOn 'compileViews' } -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withSourcesJar() - withJavadocJar() -} +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') // There are no javadocs for this project. // This is a workaround as a javadoc jar is required for publishing. tasks.named('javadocJar', Jar) { from 'src/templates' -} - -apply from: rootProject.layout.projectDirectory.file('gradle/grailsCentralPublishing.gradle') \ No newline at end of file +} \ No newline at end of file diff --git a/json-testing-support/build.gradle b/json-testing-support/build.gradle index 5e6ccb7cd..7feeea438 100644 --- a/json-testing-support/build.gradle +++ b/json-testing-support/build.gradle @@ -8,37 +8,22 @@ plugins { group = 'org.grails' ext.set('projectDesc', 'JSON Views Testing Support') - -configurations.register('groovydocImplementation') - dependencies { - api project(':views-json') - api libs.grails.datastore.core // Used in public api + api project(':views-json') // Used in public API + api libs.spring.web // Used in public API + api libs.grails.datastore.core // Used in public API + api libs.spock.core // Used in public API implementation libs.grails.core implementation libs.grails.testing.support.core + implementation libs.grails.web.urlmappings implementation libs.groovy.core implementation libs.groovy.json - implementation libs.spock.core - - groovydocImplementation localGroovy(), { - because 'The Groovydoc task requires the same Groovy version as Gradle' - } -} - -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withJavadocJar() - withSourcesJar() -} - -tasks.withType(Groovydoc).configureEach { - classpath = configurations.groovydocImplementation -} - -tasks.named('javadocJar', Jar) { - from tasks.named('groovydoc') + implementation libs.groovy.templates + implementation libs.spring.webmvc } -apply from: rootProject.layout.projectDirectory.file('gradle/grailsCentralPublishing.gradle') \ No newline at end of file +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/api-docs-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') \ No newline at end of file diff --git a/json/build.gradle b/json/build.gradle index 2e9a09b52..9e7b6e880 100644 --- a/json/build.gradle +++ b/json/build.gradle @@ -8,13 +8,15 @@ plugins { group = 'org.grails.plugins' ext.set('projectDesc', 'Grails JSON Views') +ext.set('isGrailsPlugin', true) dependencies { - api project(':views-core') + api project(':views-core') // Used in public API + api libs.grails.rest // Used in public API - implementation libs.grails.core - implementation libs.grails.rest + implementation libs.grails.encoder + implementation libs.groovy.core implementation libs.groovy.json testImplementation libs.grails.testing.support.core @@ -25,26 +27,9 @@ dependencies { testRuntimeOnly libs.slf4j.nop // Get rid of warning about missing slf4j implementation during test task } -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withJavadocJar() - withSourcesJar() -} - -springBoot { - // Project has 2 classes with main methods, so we need to specify which one to select - mainClass = 'grails.plugin.json.view.Application' -} - -tasks.named('bootJar') { - enabled = false // Don't need a bootJar for plugin -} - def testArtifacts = ['*.gson', 'circular/**'] tasks.named('jar', Jar) { - enabled = true // Need a jar for plugin - archiveClassifier = '' // Don't want the '-plain' classifier exclude testArtifacts } @@ -52,12 +37,11 @@ tasks.named('sourcesJar', Jar) { exclude testArtifacts } -tasks.named('javadocJar', Jar) { - from tasks.named('groovydoc') -} - tasks.named('build') { finalizedBy 'javadocJar', 'sourcesJar' } -apply from: rootProject.layout.projectDirectory.file('gradle/grailsCentralPublishing.gradle') \ No newline at end of file +apply from: rootProject.layout.projectDirectory.file('gradle/grails-plugin-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/api-docs-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') \ No newline at end of file diff --git a/json/grails-app/views/_child2MultipleParents.gson b/json/grails-app/views/_child2MultipleParents.gson new file mode 100644 index 000000000..90b4610ef --- /dev/null +++ b/json/grails-app/views/_child2MultipleParents.gson @@ -0,0 +1,9 @@ +import grails.plugin.json.view.Player +import groovy.transform.Field + +inherits(template: 'parent2') +inherits(template: 'parent4') + +@Field Player player + +json g.render(player) diff --git a/json/grails-app/views/_child3MultipleParents.gson b/json/grails-app/views/_child3MultipleParents.gson new file mode 100644 index 000000000..a6a377cb1 --- /dev/null +++ b/json/grails-app/views/_child3MultipleParents.gson @@ -0,0 +1,9 @@ +import grails.plugin.json.view.Player +import groovy.transform.Field + +inherits(template: 'parent3') +inherits(template: 'parent4') + +@Field Player player + +json g.render(player, [includes:'name']) diff --git a/json/grails-app/views/_child4MultipleParents.gson b/json/grails-app/views/_child4MultipleParents.gson new file mode 100644 index 000000000..f188161e4 --- /dev/null +++ b/json/grails-app/views/_child4MultipleParents.gson @@ -0,0 +1,11 @@ +import grails.plugin.json.view.Player +import groovy.transform.Field + +inherits(template: 'parent2') +inherits(template: 'parent4') + +@Field Player player + +json { + name player.name +} diff --git a/json/grails-app/views/_parent4.gson b/json/grails-app/views/_parent4.gson new file mode 100644 index 000000000..5af6f3039 --- /dev/null +++ b/json/grails-app/views/_parent4.gson @@ -0,0 +1,7 @@ +import groovy.transform.Field + +@Field Object object + +json { + bar "foo" +} diff --git a/json/src/main/groovy/grails/plugin/json/view/JsonViewWritableScript.groovy b/json/src/main/groovy/grails/plugin/json/view/JsonViewWritableScript.groovy index a3f43d626..bbed80730 100644 --- a/json/src/main/groovy/grails/plugin/json/view/JsonViewWritableScript.groovy +++ b/json/src/main/groovy/grails/plugin/json/view/JsonViewWritableScript.groovy @@ -4,8 +4,10 @@ import grails.plugin.json.builder.JsonOutput import grails.plugin.json.builder.StreamingJsonBuilder import grails.plugin.json.view.api.JsonView import grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper +import grails.plugin.json.view.api.internal.ParentInfo import grails.util.GrailsNameUtils import grails.views.AbstractWritableScript +import grails.views.GrailsViewTemplate import grails.views.api.GrailsView import groovy.transform.CompileStatic import org.grails.buffer.FastStringWriter @@ -46,13 +48,20 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements * @return */ StreamingJsonBuilder json(@DelegatesTo(value = StreamingJsonBuilder.StreamingJsonDelegate, strategy = Closure.DELEGATE_FIRST) Closure callable) { - if(parentTemplate != null) { + if(parentData.size() > 0) { if (!inline) { out.write(JsonOutput.OPEN_BRACE) } - def parentWritable = prepareParentWritable() - parentWritable.writeTo(out) - resetProcessedObjects() + Iterator parentInfoIt = parentData.iterator() + while ( parentInfoIt.hasNext() ) { + ParentInfo parentInfo = parentInfoIt.next() + def parentWritable = prepareParentWritable(parentInfo.parentTemplate, parentInfo.parentModel) + parentWritable.writeTo(out) + resetProcessedObjects() + if ( parentInfoIt.hasNext() ) { + out.write(JsonOutput.COMMA) + } + } def jsonDelegate = new StreamingJsonBuilder.StreamingJsonDelegate(out, false, generator) callable.setDelegate(jsonDelegate) callable.call() @@ -107,13 +116,20 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements * @return The json builder */ StreamingJsonBuilder json(JsonOutput.JsonWritable writable) { - if(parentTemplate != null) { + if(parentData.size() > 0) { if (!inline) { out.write(JsonOutput.OPEN_BRACE) } - def parentWritable = prepareParentWritable() - parentWritable.writeTo(out) - resetProcessedObjects() + Iterator parentInfoIt = parentData.iterator() + while ( parentInfoIt.hasNext() ) { + ParentInfo parentInfo = parentInfoIt.next() + def parentWritable = prepareParentWritable(parentInfo.parentTemplate, parentInfo.parentModel) + parentWritable.writeTo(out) + resetProcessedObjects() + if ( parentInfoIt.hasNext() ) { + out.write(JsonOutput.COMMA) + } + } writable.setInline(true) writable.setFirst(false) writable.writeTo(out) @@ -158,7 +174,7 @@ abstract class JsonViewWritableScript extends AbstractWritableScript implements return json } - private GrailsView prepareParentWritable() { + private GrailsView prepareParentWritable(GrailsViewTemplate parentTemplate, Map parentModel) { parentModel.putAll(binding.variables) for(o in binding.variables.values()) { if (o != null) { diff --git a/json/src/main/groovy/grails/plugin/json/view/api/JsonView.groovy b/json/src/main/groovy/grails/plugin/json/view/api/JsonView.groovy index 1f9210152..0cef22a42 100644 --- a/json/src/main/groovy/grails/plugin/json/view/api/JsonView.groovy +++ b/json/src/main/groovy/grails/plugin/json/view/api/JsonView.groovy @@ -5,6 +5,7 @@ import grails.plugin.json.builder.StreamingJsonBuilder import grails.plugin.json.view.api.internal.DefaultGrailsJsonViewHelper import grails.plugin.json.view.api.internal.DefaultHalViewHelper import grails.plugin.json.view.api.internal.DefaultJsonApiViewHelper +import grails.plugin.json.view.api.internal.ParentInfo import grails.plugin.json.view.api.internal.TemplateRenderer import grails.plugin.json.view.api.jsonapi.JsonApiIdRenderStrategy import grails.views.GrailsViewTemplate @@ -21,7 +22,6 @@ import groovy.transform.CompileStatic */ @CompileStatic trait JsonView extends GrailsView { - /** * The default generator */ @@ -37,15 +37,7 @@ trait JsonView extends GrailsView { */ StreamingJsonBuilder json - /** - * The parent template if any - */ - GrailsViewTemplate parentTemplate - - /** - * The parent model, if any - */ - Map parentModel + Collection parentData = [] /** * Overrides the default helper with new methods specific to JSON building */ @@ -91,8 +83,11 @@ trait JsonView extends GrailsView { .resolveTemplateUri(getControllerNamespace(), getControllerName(), template.toString()) GrailsViewTemplate parentTemplate = (GrailsViewTemplate)templateEngine.resolveTemplate(templateUri, locale) if(parentTemplate != null) { - this.parentTemplate = parentTemplate - this.parentModel = model + ParentInfo parentInfo = new ParentInfo( + parentTemplate: parentTemplate, + parentModel: model + ) + parentData.add(parentInfo) } else { throw new ViewException("Template not found for name $template") diff --git a/json/src/main/groovy/grails/plugin/json/view/api/internal/ParentInfo.groovy b/json/src/main/groovy/grails/plugin/json/view/api/internal/ParentInfo.groovy new file mode 100644 index 000000000..e0d84fdaa --- /dev/null +++ b/json/src/main/groovy/grails/plugin/json/view/api/internal/ParentInfo.groovy @@ -0,0 +1,17 @@ +package grails.plugin.json.view.api.internal + +import grails.views.GrailsViewTemplate +import groovy.transform.CompileStatic + +@CompileStatic +class ParentInfo { + /** + * The parent template if any + */ + GrailsViewTemplate parentTemplate + + /** + * The parent model, if any + */ + Map parentModel +} diff --git a/json/src/test/groovy/grails/plugin/json/view/TemplateInheritanceSpec.groovy b/json/src/test/groovy/grails/plugin/json/view/TemplateInheritanceSpec.groovy index 8555ea840..64732c1ba 100644 --- a/json/src/test/groovy/grails/plugin/json/view/TemplateInheritanceSpec.groovy +++ b/json/src/test/groovy/grails/plugin/json/view/TemplateInheritanceSpec.groovy @@ -44,4 +44,31 @@ class TemplateInheritanceSpec extends Specification implements JsonViewTest { result.jsonText == '{"name":"Cantona"}' } + void "test extending multiple templates"() { + when: + + def result = render(template:'child2MultipleParents', model:[player: new Player(name: "Cantona")]) + then: + result.jsonText == '{"_links":{"self":{"href":"http://localhost:8080/player","hreflang":"en","type":"application/hal+json"}},"foo":"bar","bar":"foo","name":"Cantona"}' + } + + void "test extending multiple templates that uses g.render(..)"() { + + when: + def player = new Player(name: "Cantona") + player.id = 1L + def result = render(template:'child3MultipleParents', model:[player: player]) + then: + result.jsonText == '{"id":1,"bar":"foo","name":"Cantona"}' + } + + void "test extending multiple templates and rendering a JSON block"() { + when: + + def result = render(template:'child4MultipleParents', model:[player: new Player(name: "Cantona")]) + then: + result.jsonText == '{"_links":{"self":{"href":"http://localhost:8080/player","hreflang":"en","type":"application/hal+json"}},"foo":"bar","bar":"foo","name":"Cantona"}' + } + + } diff --git a/markup/build.gradle b/markup/build.gradle index f17a3ebb0..a4f4d40ff 100644 --- a/markup/build.gradle +++ b/markup/build.gradle @@ -6,41 +6,32 @@ plugins { id 'signing' } -group = 'org.grails.plugins' ext.set('projectDesc', 'Grails Markup Views') +ext.set('isGrailsPlugin', true) dependencies { - api project(':views-core') + api project(':views-core') // Used in public API + api libs.grails.core // Used in public API + api libs.grails.mimetypes // Used in public API + api libs.grails.rest // Used in public API - implementation libs.grails.core - implementation libs.grails.rest + implementation libs.groovy.core + implementation libs.spring.beans + implementation libs.spring.boot // For @ConfigurationProperties + compileOnly libs.javax.annotation.api // Provided + + testImplementation libs.grails.web.urlmappings testImplementation libs.spock.core testRuntimeOnly libs.slf4j.nop // Get rid of warning about missing slf4j implementation during test task } -java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.baseline.get()) - withJavadocJar() - withSourcesJar() -} - -tasks.named('bootJar') { - enabled = false // Don't need a bootJar for plugin -} - -tasks.named('jar', Jar) { - enabled = true // Need a jar for plugin - archiveClassifier = '' // Don't want the '-plain' classifier -} - -tasks.named('javadocJar', Jar) { - from tasks.named('groovydoc') -} - tasks.named('build') { finalizedBy 'javadocJar', 'sourcesJar' } -apply from: rootProject.layout.projectDirectory.file('gradle/grailsCentralPublishing.gradle') \ No newline at end of file +apply from: rootProject.layout.projectDirectory.file('gradle/grails-plugin-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/java-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/api-docs-config.gradle') +apply from: rootProject.layout.projectDirectory.file('gradle/publishing.gradle') \ No newline at end of file diff --git a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewGrailsPlugin.groovy b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewGrailsPlugin.groovy index 149f32b13..03b290e90 100644 --- a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewGrailsPlugin.groovy +++ b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewGrailsPlugin.groovy @@ -2,13 +2,8 @@ package grails.plugin.markup.view import grails.plugin.markup.view.mvc.MarkupViewResolver import grails.plugins.Plugin -import grails.util.BuildSettings -import grails.util.Environment -import grails.util.Metadata -import grails.views.ViewsEnvironment import grails.views.mvc.GenericGroovyTemplateViewResolver import grails.views.resolve.PluginAwareTemplateResolver -import org.grails.io.support.GrailsResourceUtils /** * Plugin class for markup views diff --git a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplate.groovy b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplate.groovy index 14a78cc45..ecbd109ff 100644 --- a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplate.groovy +++ b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplate.groovy @@ -1,7 +1,6 @@ package grails.plugin.markup.view import grails.plugin.markup.view.api.MarkupView -import grails.views.Views import grails.views.WritableScript import groovy.text.markup.BaseTemplate import groovy.text.markup.MarkupTemplateEngine diff --git a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplateEngine.groovy b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplateEngine.groovy index 6e939604d..ad8e2d44f 100644 --- a/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplateEngine.groovy +++ b/markup/src/main/groovy/grails/plugin/markup/view/MarkupViewTemplateEngine.groovy @@ -1,11 +1,8 @@ package grails.plugin.markup.view -import grails.compiler.traits.TraitInjector import grails.plugin.markup.view.internal.MarkupViewsTransform -import grails.views.GrailsViewTemplate import grails.views.ResolvableGroovyTemplateEngine import grails.views.ViewCompilationException -import grails.views.WritableScript import grails.views.WritableScriptTemplate import grails.views.api.GrailsView import grails.views.compiler.ViewsTransform @@ -19,7 +16,6 @@ import org.codehaus.groovy.control.CompilationFailedException import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import org.codehaus.groovy.control.customizers.CompilationCustomizer -import org.grails.core.io.support.GrailsFactoriesLoader /** * A {@link ResolvableGroovyTemplateEngine} that uses Groovy's {@link MarkupTemplateEngine} internally diff --git a/markup/src/main/groovy/grails/plugin/markup/view/mvc/MarkupViewResolver.groovy b/markup/src/main/groovy/grails/plugin/markup/view/mvc/MarkupViewResolver.groovy index c1355197e..04ee5c696 100644 --- a/markup/src/main/groovy/grails/plugin/markup/view/mvc/MarkupViewResolver.groovy +++ b/markup/src/main/groovy/grails/plugin/markup/view/mvc/MarkupViewResolver.groovy @@ -4,7 +4,6 @@ import grails.core.support.proxy.ProxyHandler import grails.plugin.markup.view.MarkupViewConfiguration import grails.plugin.markup.view.MarkupViewTemplate import grails.plugin.markup.view.MarkupViewTemplateEngine -import grails.plugin.markup.view.MarkupViewWritableScriptTemplate import grails.plugin.markup.view.renderer.MarkupViewXmlRenderer import grails.rest.render.RendererRegistry import grails.views.mvc.SmartViewResolver diff --git a/settings.gradle b/settings.gradle index 937467559..571810f0c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'com.gradle.enterprise' version '3.16.1' - id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.12.1' + id 'com.gradle.enterprise' version '3.17' + id 'com.gradle.common-custom-user-data-gradle-plugin' version '2.0' } gradleEnterprise { @@ -8,7 +8,7 @@ gradleEnterprise { buildScan { publishAlwaysIf(System.getenv('CI') == 'true') publishIfAuthenticated() - uploadInBackground = System.getenv('CI') == null + uploadInBackground = System.getenv("CI") == null capture { taskInputFiles = true } @@ -17,14 +17,10 @@ gradleEnterprise { buildCache { local { enabled = System.getenv('CI') != 'true' } - remote(HttpBuildCache) { - push = System.getenv('CI') == 'true' + remote(gradleEnterprise.buildCache) { + def isAuthenticated = System.getenv('GRADLE_ENTERPRISE_ACCESS_KEY') + push = System.getenv('CI') == 'true' && isAuthenticated enabled = true - url = 'https://ge.grails.org/cache/' - credentials { - username = System.getenv('GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER') - password = System.getenv('GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY') - } } }