Skip to content

Commit c800692

Browse files
authored
Merge pull request #15411 from apache/micronaut-fixes-2
2 parents 118f897 + 1344716 commit c800692

File tree

59 files changed

+4334
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4334
-37
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ gradleCycloneDxPluginVersion=2.4.1
5656
micronautPlatformVersion=4.9.2
5757

5858
# Libraries only specific to test apps, these should not be exposed
59+
ersatzVersion=4.0.1
5960
grailsSpringSecurityVersion=7.0.2-SNAPSHOT
6061
jbossTransactionApiVersion=2.0.0.Final
6162
# Note: we do not import the micronaut bom in our tests to avoid spring version mismatches

grails-doc/src/en/guide/upgrading/upgrading60x.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,24 @@ Here's an example `gradle.properties` file:
371371
micronautPlatformVersion=4.9.2
372372
----
373373

374+
Please note that, due to https://github.com/micronaut-projects/micronaut-spring/issues/769[this issue], Spring Boot DevTools does not work with the Micronaut integration.
375+
376+
The Grails Gradle Plugin automatically configures Groovy-based Micronaut bean registration via AST transforms (`micronaut-inject-groovy` on `compileOnlyApi`). Groovy classes annotated with `@Singleton`, `@Factory`, `@ConfigurationProperties`, etc. are processed automatically.
377+
378+
IMPORTANT: If your project contains **Java source files** with Micronaut annotations (e.g. `@Singleton`, `@Factory`), you must manually add the Micronaut annotation processor to your `build.gradle`. The annotation processor is not configured automatically because it is incompatible with Groovy incremental compilation (see https://github.com/apache/grails-core/issues/15211[#15211]). For projects that mix Java and Groovy Micronaut beans, consider splitting them into separate source sets or modules to avoid incremental compilation issues.
379+
380+
[source,groovy]
381+
.build.gradle - Adding annotation processor for Java Micronaut beans
382+
----
383+
dependencies {
384+
annotationProcessor platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
385+
annotationProcessor 'io.micronaut:micronaut-inject-java'
386+
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
387+
}
388+
----
389+
390+
NOTE: The Grails Gradle Plugin automatically configures the Spring Boot `bootJar` and `bootWar` tasks to use the `CLASSIC` loader implementation when `grails-micronaut` is detected. This is required for `java -jar` execution to work correctly with the Micronaut-Spring integration (see https://github.com/apache/grails-core/issues/15207[#15207]). If you have explicitly set `loaderImplementation` in your `build.gradle`, you can remove it as the plugin now handles this automatically.
391+
374392
===== 12.5 hibernate-ehcache
375393

376394
The `org.hibernate:hibernate-ehcache` library is no longer provided by the `org.apache.grails:grails-hibernate5` plugin. If

grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildGradle.rocker.raw

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ repositories {
9898
compileJava.options.release = @features.getTargetJdk()
9999

100100
@if (features.contains("jrebel")) {
101-
bootRun {
101+
tasks.named('bootRun') {
102102
dependsOn(generateRebel)
103103
if (project.hasProperty("rebelAgent")) {
104104
jvmArgs(rebelAgent)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.forge.feature.micronaut;
20+
21+
import java.util.Set;
22+
23+
import jakarta.inject.Singleton;
24+
25+
import org.grails.forge.application.ApplicationType;
26+
import org.grails.forge.feature.Feature;
27+
import org.grails.forge.feature.reloading.SpringBootDevTools;
28+
import org.grails.forge.feature.validation.FeatureValidator;
29+
import org.grails.forge.options.Options;
30+
31+
@Singleton
32+
public class GrailsMicronautValidator implements FeatureValidator {
33+
34+
@Override
35+
public void validatePreProcessing(Options options, ApplicationType applicationType, Set<Feature> features) {
36+
if (features.stream().anyMatch(f -> f instanceof GrailsMicronaut)) {
37+
if (features.stream().anyMatch(f -> (f instanceof SpringBootDevTools))) {
38+
// See: https://github.com/micronaut-projects/micronaut-spring/issues/769
39+
throw new IllegalArgumentException("Spring Boot Dev Tools are not supported with Grails Micronaut");
40+
}
41+
}
42+
}
43+
44+
@Override
45+
public void validatePostProcessing(Options options, ApplicationType applicationType, Set<Feature> features) {
46+
47+
}
48+
}

grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/reloading/SpringBootDevTools.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919
package org.grails.forge.feature.reloading;
2020

2121
import jakarta.inject.Singleton;
22+
2223
import org.grails.forge.application.ApplicationType;
2324
import org.grails.forge.application.generator.GeneratorContext;
2425
import org.grails.forge.build.dependencies.Dependency;
2526
import org.grails.forge.build.dependencies.Scope;
2627
import org.grails.forge.feature.DefaultFeature;
2728
import org.grails.forge.feature.Feature;
29+
import org.grails.forge.feature.micronaut.GrailsMicronaut;
2830
import org.grails.forge.options.Options;
2931

3032
import java.util.Set;
3133

3234
@Singleton
3335
public class SpringBootDevTools implements ReloadingFeature, DefaultFeature {
36+
3437
@Override
3538
public String getName() {
3639
return "spring-boot-devtools";
@@ -43,7 +46,11 @@ public String getTitle() {
4346

4447
@Override
4548
public String getDescription() {
46-
return "Spring Boot Devtools is a powerful tool that enhances development productivity by providing features like automatic application restarts on code changes, live reloading of static resources, and remote debugging support. It enables developers to rapidly iterate and test changes during the development process, making it a valuable asset for Spring Boot projects.";
49+
return "Spring Boot Devtools is a powerful tool that enhances development productivity " +
50+
"by providing features like automatic application restarts on code changes, live " +
51+
"reloading of static resources, and remote debugging support. It enables developers " +
52+
"to rapidly iterate and test changes during the development process, making it a " +
53+
"valuable asset for Spring Boot projects.";
4754
}
4855

4956
@Override
@@ -61,6 +68,9 @@ public boolean isVisible() {
6168

6269
@Override
6370
public boolean shouldApply(ApplicationType applicationType, Options options, Set<Feature> selectedFeatures) {
71+
if (selectedFeatures.stream().anyMatch(f -> f instanceof GrailsMicronaut)) {
72+
return false;
73+
}
6474
return selectedFeatures.stream().noneMatch(f -> f instanceof ReloadingFeature);
6575
}
6676

grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/reloading/SpringBootDevToolsSpec.groovy

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,45 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
2019
package org.grails.forge.feature.reloading
2120

21+
import spock.lang.Unroll
22+
2223
import org.grails.forge.ApplicationContextSpec
2324
import org.grails.forge.application.ApplicationType
24-
import org.grails.forge.feature.Features
2525
import org.grails.forge.fixture.CommandOutputFixture
2626

2727
class SpringBootDevToolsSpec extends ApplicationContextSpec implements CommandOutputFixture {
2828

2929
void "test spring-boot-devtools feature"() {
30-
31-
when:
32-
final Features features = getFeatures(["spring-boot-devtools"])
33-
34-
then:
35-
features.contains("spring-boot-devtools")
36-
30+
expect:
31+
'spring-boot-devtools' in getFeatures(['spring-boot-devtools'])
3732
}
3833

39-
void "test spring-boot-devtools dependency is present for #applicationType application type"() {
40-
34+
@Unroll
35+
void "test spring-boot-devtools dependency is present for #applicationType application type"(ApplicationType applicationType) {
4136
when:
42-
def output = generate(applicationType, ["spring-boot-devtools"])
43-
def build = output["build.gradle"]
37+
def output = generate(applicationType, ['spring-boot-devtools'])
38+
def build = output['build.gradle']
4439

4540
then:
46-
build.contains("developmentOnly \"org.springframework.boot:spring-boot-devtools\"")
41+
build.contains('developmentOnly "org.springframework.boot:spring-boot-devtools"')
4742

4843
where:
4944
applicationType << [ApplicationType.WEB, ApplicationType.REST_API]
5045
}
5146

5247
void "test there can be only one of Reloading feature"() {
5348
when:
54-
getFeatures(["spring-boot-devtools", "jrebel"])
49+
getFeatures(['spring-boot-devtools', 'jrebel'])
5550

5651
then:
5752
def ex = thrown(IllegalArgumentException)
58-
ex.message.contains("There can only be one of the following features selected")
53+
ex.message.contains('There can only be one of the following features selected')
54+
}
55+
56+
void "test spring-boot-devtools is not applied when grails-micronaut is selected"() {
57+
expect:
58+
!('spring-boot-devtools' in getFeatures(['grails-micronaut']))
5959
}
6060
}

grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import org.springframework.boot.gradle.plugin.ResolveMainClassName
7575
import org.springframework.boot.gradle.plugin.SpringBootPlugin
7676
import org.springframework.boot.gradle.tasks.bundling.BootArchive
7777
import org.springframework.boot.gradle.tasks.run.BootRun
78+
import org.springframework.boot.loader.tools.LoaderImplementation
7879

7980
import javax.inject.Inject
8081

@@ -381,10 +382,11 @@ class GrailsGradlePlugin extends GroovyPlugin {
381382
}
382383
}
383384

384-
project.logger.info('Adding Micronaut annotationProcessor dependencies to project {}', project.name)
385-
project.getDependencies().add('annotationProcessor', project.dependencies.platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion"))
386-
project.getDependencies().add('annotationProcessor', 'io.micronaut:micronaut-inject-java')
387-
project.getDependencies().add('annotationProcessor', 'jakarta.annotation:jakarta.annotation-api')
385+
project.logger.info('Configuring CLASSIC boot loader for Micronaut compatibility in {}', project.name)
386+
project.tasks.withType(BootArchive).configureEach {
387+
it.loaderImplementation.convention(LoaderImplementation.CLASSIC)
388+
}
389+
388390
}
389391
}
390392

grails-micronaut/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ group = 'org.apache.grails'
3333
dependencies {
3434
compileOnlyApi platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
3535
compileOnlyApi 'io.micronaut:micronaut-inject-groovy'
36-
compileOnlyApi 'io.micronaut:micronaut-inject-java'
3736

37+
// Use the micronaut spring starter so that any bean in the spring context will be exposed to micronaut
3838
api platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
3939
api 'io.micronaut.spring:micronaut-spring-boot-starter'
40-
api 'io.micronaut.spring:micronaut-spring-context'
4140

4241
// For the grails plugin interface
4342
compileOnly platform(project(':grails-bom'))

grails-micronaut/src/main/groovy/org/apache/grails/micronaut/GrailsMicronautGrailsPlugin.groovy

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
2019
package org.apache.grails.micronaut
2120

2221
import groovy.transform.CompileStatic
2322
import groovy.util.logging.Slf4j
2423

25-
import io.micronaut.context.ConfigurableApplicationContext
24+
import io.micronaut.context.ApplicationContext as MicronautApplicationContext
2625
import io.micronaut.context.env.AbstractPropertySourceLoader
2726
import io.micronaut.context.env.PropertySource
2827

@@ -50,24 +49,39 @@ class GrailsMicronautGrailsPlugin extends Plugin {
5049
}
5150

5251
if (!applicationContext.containsBean('micronautApplicationContext')) {
53-
throw new IllegalStateException('A Micronaut Application Context should exist prior to the loading of the Grails Micronaut plugin.')
52+
throw new IllegalStateException(
53+
'A Micronaut Application Context should exist prior to the loading ' +
54+
'of the Grails Micronaut plugin.'
55+
)
5456
}
5557

56-
def micronautContext = applicationContext.getBean('micronautApplicationContext', ConfigurableApplicationContext)
58+
def micronautContext = applicationContext.getBean(
59+
'micronautApplicationContext',
60+
MicronautApplicationContext
61+
)
5762
def micronautEnv = micronautContext.environment
5863

5964
log.debug('Loading configurations from the plugins to the parent Micronaut context')
6065

6166
def plugins = pluginManager.allPlugins
62-
def pluginsFromContext = pluginManagerFromContext ? pluginManagerFromContext.allPlugins : new GrailsPlugin[0]
67+
def pluginsFromContext = pluginManagerFromContext ?
68+
pluginManagerFromContext.allPlugins :
69+
new GrailsPlugin[0]
6370
int priority = AbstractPropertySourceLoader.DEFAULT_POSITION
6471
[plugins, pluginsFromContext].each { pluginsToProcess ->
65-
Arrays.stream(pluginsToProcess)
66-
.filter { plugin -> plugin.propertySource != null }
67-
.forEach { plugin ->
68-
log.debug('Loading configurations from {} plugin to the parent Micronaut context', plugin.name)
69-
// If invoking the source as `.source`, the NavigableMapPropertySource will return null, while invoking the getter, it will return the correct value
70-
micronautEnv.addPropertySource(PropertySource.of("grails.plugins.$plugin.name", (Map) plugin.propertySource.getSource(), --priority))
72+
pluginsToProcess
73+
.findAll { it.propertySource != null }
74+
.each {
75+
log.debug('Loading configurations from {} plugin to the parent Micronaut context', it.name)
76+
// If invoking the source as `.source`, the NavigableMapPropertySource will return null,
77+
// while invoking the getter, it will return the correct value
78+
micronautEnv.addPropertySource(
79+
PropertySource.of(
80+
"grails.plugins.$it.name",
81+
(Map) it.propertySource.getSource(),
82+
--priority
83+
)
84+
)
7185
}
7286
}
7387
micronautEnv.refresh()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
plugins {
20+
id 'org.apache.grails.buildsrc.properties'
21+
id 'org.apache.grails.buildsrc.compile'
22+
}
23+
24+
version = '0.1'
25+
group = 'micronautgroovyonly'
26+
27+
apply plugin: 'org.apache.grails.gradle.grails-web'
28+
29+
// This module intentionally has NO annotationProcessor dependencies.
30+
// It validates that a Groovy-only Grails application works with
31+
// grails-micronaut without explicit Java annotation processor configuration.
32+
// The Grails Gradle plugin automatically applies the required annotation
33+
// processors when grails-micronaut is present on the classpath.
34+
35+
dependencies {
36+
implementation platform(project(':grails-bom'))
37+
38+
implementation 'org.apache.grails:grails-dependencies-starter-web'
39+
implementation 'org.apache.grails:grails-micronaut'
40+
41+
runtimeOnly 'com.h2database:h2'
42+
runtimeOnly 'org.apache.tomcat:tomcat-jdbc'
43+
44+
testImplementation 'org.apache.grails:grails-dependencies-test'
45+
46+
integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
47+
}
48+
49+
apply {
50+
from rootProject.layout.projectDirectory.file('gradle/functional-test-config.gradle')
51+
from rootProject.layout.projectDirectory.file('gradle/grails-extension-gradle-config.gradle')
52+
}

0 commit comments

Comments
 (0)