diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 5cdc7d1..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-plugins {
- id "io.micronaut.build.internal.docs"
- id "io.micronaut.build.internal.quality-reporting"
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..2fbc3d3
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,5 @@
+plugins {
+ id("io.micronaut.build.internal.docs")
+ id("io.micronaut.build.internal.quality-reporting")
+}
+
diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.guice-module.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.guice-module.gradle
index b49f716..21d4ac8 100644
--- a/buildSrc/src/main/groovy/io.micronaut.build.internal.guice-module.gradle
+++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.guice-module.gradle
@@ -2,3 +2,9 @@ plugins {
id 'io.micronaut.build.internal.guice-base'
id "io.micronaut.build.internal.module"
}
+
+micronautBuild {
+ binaryCompatibility {
+ enabled.set(false)
+ }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6f401fc..e0bb762 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,30 +1,13 @@
-#
-# This file is used to declare the list of libraries
-# which are used as dependencies in the project.
-# See https://docs.gradle.org/7.4.2/userguide/platforms.html#sub:central-declaration-of-dependencies
-#
-# For Micronaut, we have 3 kinds of dependencies:
-# - managed dependencies, which are exposed to consumers via a BOM (or version catalog)
-# - managed BOMs, which are imported into the BOM that we generate
-# - all other dependencies, which are implementation details
-#
-# If a library needs to appear in the BOM of the project, then it must be
-# declared with the "managed-" prefix.
-# If a BOM needs to be imported in the BOM of the project, then it must be
-# declared with the "boms-" prefix.
-# Both managed dependencies and BOMs need to have their version declared via
-# a managed version (a version which alias starts with "managed-"
-
[versions]
-micronaut = "4.4.8"
+micronaut = "4.4.3"
micronaut-docs = "2.0.0"
micronaut-test = "4.2.1"
groovy = "4.0.17"
spock = "2.3-groovy-4.0"
# Managed versions appear in the BOM
-# managed-somelib = "1.0"
-# managed-somebom = "1.1"
+managed-guice = "7.0.0"
+managed-guava = "33.2.0-jre"
[libraries]
# Core
@@ -32,19 +15,8 @@ micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'mi
#
# Managed dependencies appear in the BOM
-#
-# managed-somelib = { module = "group:artifact", version.ref = "managed-somelib" }
-
-#
-# Imported BOMs, also appearing in the generated BOM
-#
-# boms-somebom = { module = "com.foo:somebom", version.ref = "managed-somebom" }
-
-# Other libraries used by the project but non managed
-
-# micronaut-bom = { module = "io.micronaut:micronaut-bom", version.ref = "micronaut" }
-# jdoctor = { module = "me.champeau.jdoctor:jdoctor-core", version.ref="jdoctor" }
-
-[bundles]
-
-[plugins]
+managed-guice = { module = "com.google.inject:guice", version.ref = "managed-guice" }
+managed-guava = { module = "com.google.guava:guava", version.ref = "managed-guava" }
+junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api" }
+junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" }
+junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params" }
diff --git a/micronaut-guice-annotation/build.gradle.kts b/micronaut-guice-annotation/build.gradle.kts
new file mode 100644
index 0000000..67791f5
--- /dev/null
+++ b/micronaut-guice-annotation/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("io.micronaut.build.internal.guice-module")
+}
+
+dependencies {
+ implementation(libs.managed.guice) {
+ exclude(group="com.google.guava", module = "guava")
+ }
+}
diff --git a/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/Guice.java b/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/Guice.java
new file mode 100644
index 0000000..1c0373b
--- /dev/null
+++ b/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/Guice.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.annotation;
+
+import com.google.inject.Module;
+import io.micronaut.context.annotation.AliasFor;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that can be applied to the application entry point
+ * that allows the import of Guice modules.
+ *
+ *
Micronaut will import the modules and run them at startup when the application starts
+ * registering the provided beans using the Guice DSL.
+ *
+ * Note all features of Guice are supported, there exist the following limitations:
+ *
+ *
+ * Guice Scopes are not supported
+ * Guice AOP/Interceptors are not supported
+ * Guice private modules are not supported
+ * Static Injection is not supported
+ * Guice TypeConverters are not supported (use {@link io.micronaut.core.convert.TypeConverter} instead.
+ * Guice Listeners are not supported (use {@link io.micronaut.context.event.BeanCreatedEventListener} instead.
+ * None of the {@code com.google.inject.spi} API is supported
+ *
+ *
+ * Note that if you create a runtime binding to a class with {@link com.google.inject.binder.LinkedBindingBuilder#to(Class)} that has no injection annotations you may need to import the bean first
+ * to allow the bean to be instantiated without reflection. This can be done with {@link io.micronaut.context.annotation.Import}
+ *
+ * Otherwise it is recommended to as a minimum use the {@link jakarta.inject.Inject} annotation on the constructor to avoid this need.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Guice {
+ /**
+ * Import the given Guice modules.
+ *
+ * The modules are imported in the order defined by the array.
+ *
+ * @return An array of module types
+ */
+ Class extends Module>[] modules();
+
+ /**
+ * Import the given Guice classes.
+ *
+ * @return An array of classes to import
+ */
+ Class>[] classes() default {};
+
+ /**
+ * Import the given named Guice classes.
+ *
+ * @return An array of class names to import
+ */
+ @AliasFor(member = "classes")
+ String[] classNames() default {};
+
+ /**
+ * The environment where the modules should be active (Defaults to all environments).
+ *
+ * @return The environments.
+ */
+ String[] environments() default {};
+}
diff --git a/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/internal/GuiceAnnotation.java b/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/internal/GuiceAnnotation.java
new file mode 100644
index 0000000..a5b8283
--- /dev/null
+++ b/micronaut-guice-annotation/src/main/java/io/micronaut/guice/annotation/internal/GuiceAnnotation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.annotation.internal;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.NonNull;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Internal meta-annotation for identifying Guice annotated beans.
+ *
+ * A Guice annotated bean is a bean that is meta annotated with the annotation {@link com.google.inject.ScopeAnnotation}.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Internal
+public @interface GuiceAnnotation {
+ @NonNull AnnotationValue ANNOTATION_VALUE = AnnotationValue.builder(GuiceAnnotation.class).build();
+}
diff --git a/micronaut-guice-bom/build.gradle b/micronaut-guice-bom/build.gradle
deleted file mode 100644
index 92fe279..0000000
--- a/micronaut-guice-bom/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-plugins {
- id 'io.micronaut.build.internal.guice-base'
- id "io.micronaut.build.internal.bom"
-}
diff --git a/micronaut-guice-bom/build.gradle.kts b/micronaut-guice-bom/build.gradle.kts
new file mode 100644
index 0000000..a7d7761
--- /dev/null
+++ b/micronaut-guice-bom/build.gradle.kts
@@ -0,0 +1,10 @@
+plugins {
+ id("io.micronaut.build.internal.guice-base")
+ id("io.micronaut.build.internal.bom")
+}
+
+micronautBuild {
+ binaryCompatibility {
+ enabled.set(false)
+ }
+}
diff --git a/micronaut-guice-processor/build.gradle.kts b/micronaut-guice-processor/build.gradle.kts
new file mode 100644
index 0000000..e2a21eb
--- /dev/null
+++ b/micronaut-guice-processor/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("io.micronaut.build.internal.guice-module")
+}
+
+dependencies {
+ implementation(projects.micronautGuiceAnnotation)
+ implementation(mn.micronaut.core.processor)
+ implementation(libs.managed.guice) {
+ exclude(group="com.google.guava", module = "guava")
+ }
+ testImplementation(mn.micronaut.inject.java.test)
+ testImplementation(projects.micronautGuice)
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/BindingAnnotationTransformer.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/BindingAnnotationTransformer.java
new file mode 100644
index 0000000..01ddcf7
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/BindingAnnotationTransformer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.BindingAnnotation;
+import io.micronaut.core.annotation.AnnotationUtil;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationTransformer;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+
+/**
+ * Transforms {@link com.google.inject.BindingAnnotation} to {@link jakarta.inject.Qualifier}.
+ */
+public class BindingAnnotationTransformer
+ implements TypedAnnotationTransformer {
+ @Override
+ public Class annotationType() {
+ return BindingAnnotation.class;
+ }
+
+ @Override
+ public List> transform(AnnotationValue annotation, VisitorContext visitorContext) {
+ return List.of(
+ AnnotationValue.builder(AnnotationUtil.QUALIFIER)
+ .build(),
+ GuiceAnnotation.ANNOTATION_VALUE
+ );
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/GuiceBeanVisitor.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/GuiceBeanVisitor.java
new file mode 100644
index 0000000..f459cbb
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/GuiceBeanVisitor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.RestrictedBindingSource;
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.core.annotation.AnnotationClassValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.ast.ClassElement;
+import io.micronaut.inject.ast.ConstructorElement;
+import io.micronaut.inject.processing.ProcessingException;
+import io.micronaut.inject.visitor.TypeElementVisitor;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.Set;
+
+/**
+ * Guice beans have a only 1 binding type. This visitor resolves that.
+ */
+public class GuiceBeanVisitor
+ implements TypeElementVisitor {
+ @Override
+ public VisitorKind getVisitorKind() {
+ return VisitorKind.ISOLATING;
+ }
+
+ @Override
+ public Set getSupportedAnnotationNames() {
+ return Set.of(GuiceAnnotation.class.getName(), "com.google.inject.*");
+ }
+
+ @Override
+ public void visitConstructor(ConstructorElement element, VisitorContext context) {
+ }
+
+ @Override
+ public void visitClass(ClassElement element, VisitorContext context) {
+ if (element.hasDeclaredAnnotation(RestrictedBindingSource.class)) {
+ throw new ProcessingException(element, "The @RestrictedBindingSource annotation is not supported");
+ }
+ if (element.hasStereotype(GuiceAnnotation.class)) {
+ exposeOnlyType(element);
+ }
+ }
+
+ private static void exposeOnlyType(ClassElement element) {
+ if (!element.isPresent(Bean.class, "typed")) {
+ element.annotate(Bean.class, builder ->
+ builder.member("typed", new AnnotationClassValue<>(element.getName()))
+ );
+ }
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImplementedByTransformer.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImplementedByTransformer.java
new file mode 100644
index 0000000..f0e322c
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImplementedByTransformer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.ImplementedBy;
+import io.micronaut.context.annotation.DefaultImplementation;
+import io.micronaut.core.annotation.AnnotationClassValue;
+import io.micronaut.core.annotation.AnnotationMetadata;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationTransformer;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+
+
+/**
+ * Transforms {@link com.google.inject.ImplementedBy} to {@link DefaultImplementation}.
+ */
+public class ImplementedByTransformer
+ implements TypedAnnotationTransformer {
+ @Override
+ public Class annotationType() {
+ return ImplementedBy.class;
+ }
+
+ @Override
+ public List> transform(AnnotationValue annotation, VisitorContext visitorContext) {
+ AnnotationClassValue t = annotation.stringValue().map(AnnotationClassValue::new).orElse(null);
+ if (t != null) {
+ return List.of(
+ AnnotationValue.builder(DefaultImplementation.class)
+ .member(AnnotationMetadata.VALUE_MEMBER, t)
+ .build(),
+ GuiceAnnotation.ANNOTATION_VALUE
+ );
+ }
+ return List.of();
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImportModuleVisitor.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImportModuleVisitor.java
new file mode 100644
index 0000000..3dd8962
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ImportModuleVisitor.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import io.micronaut.context.annotation.Primary;
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.core.annotation.AnnotationMetadata;
+import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Order;
+import io.micronaut.core.util.ArrayUtils;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.inject.ast.ClassElement;
+import io.micronaut.inject.ast.ElementQuery;
+import io.micronaut.inject.ast.MethodElement;
+import io.micronaut.inject.ast.beans.BeanElementBuilder;
+import io.micronaut.inject.processing.ProcessingException;
+import io.micronaut.inject.visitor.TypeElementVisitor;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+import java.util.Set;
+
+public class ImportModuleVisitor
+ implements TypeElementVisitor {
+
+ public static final String MEMBER_ENVS = "environments";
+ public static final String MEMBER_MODULES = "modules";
+ public static final String MEMBER_CLASSES = "classes";
+
+ @Override
+ public void visitClass(ClassElement element, VisitorContext context) {
+ @NonNull String[] moduleNames = element.stringValues(Guice.class, MEMBER_MODULES);
+ @NonNull String[] classNames = element.stringValues(Guice.class, MEMBER_CLASSES);
+ @NonNull String[] envs = element.stringValues(Guice.class, MEMBER_ENVS);
+ for (String className : classNames) {
+ ClassElement classElement = context.getClassElement(className).orElse(null);
+ if (classElement == null) {
+ throw new ProcessingException(element, "Guice class import [" + className + "] must be on the compilation classpath");
+ } else {
+ BeanElementBuilder builder = element.addAssociatedBean(classElement);
+ builder.inject();
+ builder.typed(classElement);
+ }
+ }
+ for (int i = 0; i < moduleNames.length; i++) {
+ String className = moduleNames[i];
+ ClassElement moduleElement = context.getClassElement(className).orElse(null);
+ if (moduleElement == null) {
+ throw new ProcessingException(element, "Guice module [" + className + "] must be on the compilation classpath");
+ }
+ int order = i;
+ MethodElement primaryConstructor =
+ moduleElement.getPrimaryConstructor().orElse(null);
+ if (primaryConstructor == null) {
+ throw new ProcessingException(element, """
+ Cannot import Guice module [" + moduleElement.getName() + "], since it has multiple constructors or no accessible constructor.
+ Consider defining a single public accessible constructor or if there are multiple adding @Inject to one of them.
+ """);
+ } else {
+
+ BeanElementBuilder beanElementBuilder = element.addAssociatedBean(
+ moduleElement
+ ).annotate(Order.class, builder ->
+ builder.value(order) // retain load order
+ );
+ if (ArrayUtils.isNotEmpty(envs)) {
+ beanElementBuilder.annotate(Requires.class, env -> env.member("env", envs));
+ }
+ beanElementBuilder.createWith(primaryConstructor);
+ ElementQuery producesMethodQuery = ElementQuery.ALL_METHODS
+ .annotated(am -> am.hasAnnotation(Provides.class))
+ .onlyDeclared()
+ .onlyConcrete();
+ List methodElements = moduleElement.getEnclosedElements(producesMethodQuery);
+ for (MethodElement methodElement : methodElements) {
+ if (!methodElement.isPublic()) {
+ throw new ProcessingException(methodElement, "Method's annotated with @Produces must be public");
+ }
+ if (methodElement.getReturnType().isVoid()) {
+ throw new ProcessingException(methodElement, "Method's annotated with @Produces cannot return 'void'");
+ }
+ if (!methodElement.getReturnType().isPublic()) {
+ throw new ProcessingException(methodElement, "Method's annotated with @Produces must return a publicly accessible type");
+ }
+ }
+ beanElementBuilder.produceBeans(producesMethodQuery, childBuilder -> {
+ MethodElement methodElement = (MethodElement) childBuilder.getProducingElement();
+ childBuilder.typed(methodElement.getGenericReturnType());
+ childBuilder.annotate(Primary.class);
+ AnnotationMetadata annotationMetadata = methodElement.getAnnotationMetadata();
+ Set annotationNames = annotationMetadata.getAnnotationNames();
+ for (String annotationName : annotationNames) {
+ if (!annotationName.equals(Provides.class.getName())) {
+ annotationMetadata.findAnnotation(annotationName)
+ .ifPresent(childBuilder::annotate);
+ }
+ }
+ if (ArrayUtils.isNotEmpty(envs)) {
+ beanElementBuilder.annotate(Requires.class, env -> env.member("env", envs));
+ }
+ });
+
+ beanElementBuilder.typed(ClassElement.of(Module.class), moduleElement);
+ }
+ }
+ }
+
+ @Override
+ public VisitorKind getVisitorKind() {
+ return VisitorKind.ISOLATING;
+ }
+
+ @Override
+ public Set getSupportedAnnotationNames() {
+ return Set.of(Guice.class.getName());
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/InjectTransformer.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/InjectTransformer.java
new file mode 100644
index 0000000..d0cf4d9
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/InjectTransformer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.Inject;
+import io.micronaut.core.annotation.AnnotationUtil;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationTransformer;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+
+/**
+ * Transforms {@link com.google.inject.Inject} to {@link jakarta.inject.Inject}.
+ */
+public class InjectTransformer
+ implements TypedAnnotationTransformer {
+ @Override
+ public Class annotationType() {
+ return Inject.class;
+ }
+
+ @Override
+ public List> transform(AnnotationValue annotation, VisitorContext visitorContext) {
+ return List.of(
+ AnnotationValue.builder(AnnotationUtil.INJECT)
+ .member("required", annotation.booleanValue("optional").map(optional -> !optional).orElse(true))
+ .build(),
+ GuiceAnnotation.ANNOTATION_VALUE
+ );
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/NamedAnnotationTransformer.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/NamedAnnotationTransformer.java
new file mode 100644
index 0000000..bdf3eeb
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/NamedAnnotationTransformer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.name.Named;
+import io.micronaut.core.annotation.AnnotationUtil;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.core.annotation.AnnotationValueBuilder;
+import io.micronaut.core.util.StringUtils;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationTransformer;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class NamedAnnotationTransformer
+ implements TypedAnnotationTransformer {
+ @Override
+ public Class annotationType() {
+ return Named.class;
+ }
+
+ @Override
+ public List> transform(AnnotationValue annotation, VisitorContext visitorContext) {
+ String name = annotation.stringValue().orElse(null);
+ AnnotationValueBuilder builder = AnnotationValue.builder(AnnotationUtil.NAMED);
+ if (StringUtils.isNotEmpty(name)) {
+ builder.value(name);
+ }
+ return List.of(
+ builder.build(),
+ GuiceAnnotation.ANNOTATION_VALUE
+ );
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ScopeAnnotationMapper.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ScopeAnnotationMapper.java
new file mode 100644
index 0000000..696110e
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/ScopeAnnotationMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.ScopeAnnotation;
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.core.annotation.AnnotationUtil;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationMapper;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+
+/**
+ * Transforms {@link com.google.inject.ScopeAnnotation} to {@link jakarta.inject.Scope}.
+ */
+public class ScopeAnnotationMapper
+ implements TypedAnnotationMapper {
+
+ @Override
+ public Class annotationType() {
+ return ScopeAnnotation.class;
+ }
+
+ @Override
+ public List> map(AnnotationValue annotation, VisitorContext visitorContext) {
+ return List.of(
+ AnnotationValue.builder(AnnotationUtil.SCOPE).build(),
+ GuiceAnnotation.ANNOTATION_VALUE,
+ AnnotationValue.builder(Bean.class).build()
+ );
+ }
+}
diff --git a/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/SingletonTransformer.java b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/SingletonTransformer.java
new file mode 100644
index 0000000..ceacbb0
--- /dev/null
+++ b/micronaut-guice-processor/src/main/java/io/micronaut/guice/processor/SingletonTransformer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice.processor;
+
+import com.google.inject.Singleton;
+import io.micronaut.core.annotation.AnnotationUtil;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.guice.annotation.internal.GuiceAnnotation;
+import io.micronaut.inject.annotation.TypedAnnotationTransformer;
+import io.micronaut.inject.visitor.VisitorContext;
+import java.util.List;
+
+/**
+ * Transforms {@link com.google.inject.Singleton} to {@link jakarta.inject.Singleton}.
+ */
+public class SingletonTransformer
+ implements TypedAnnotationTransformer {
+ @Override
+ public Class annotationType() {
+ return Singleton.class;
+ }
+
+ @Override
+ public List> transform(AnnotationValue annotation, VisitorContext visitorContext) {
+ return List.of(
+ AnnotationValue.builder(AnnotationUtil.SINGLETON)
+ .stereotypes(annotation.getStereotypes())
+ .build(),
+ GuiceAnnotation.ANNOTATION_VALUE
+ );
+ }
+}
diff --git a/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationMapper b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationMapper
new file mode 100644
index 0000000..34e03ad
--- /dev/null
+++ b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationMapper
@@ -0,0 +1 @@
+io.micronaut.guice.processor.ScopeAnnotationMapper
diff --git a/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationTransformer b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationTransformer
new file mode 100644
index 0000000..0ae2763
--- /dev/null
+++ b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationTransformer
@@ -0,0 +1,5 @@
+io.micronaut.guice.processor.InjectTransformer
+io.micronaut.guice.processor.ImplementedByTransformer
+io.micronaut.guice.processor.SingletonTransformer
+io.micronaut.guice.processor.NamedAnnotationTransformer
+io.micronaut.guice.processor.BindingAnnotationTransformer
diff --git a/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor
new file mode 100644
index 0000000..b84b0a3
--- /dev/null
+++ b/micronaut-guice-processor/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor
@@ -0,0 +1,2 @@
+io.micronaut.guice.processor.ImportModuleVisitor
+io.micronaut.guice.processor.GuiceBeanVisitor
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/BindingAnnotationSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/BindingAnnotationSpec.groovy
new file mode 100644
index 0000000..b5f1b03
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/BindingAnnotationSpec.groovy
@@ -0,0 +1,51 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class BindingAnnotationSpec
+ extends AbstractTypeElementSpec {
+ void "test binding annotation"() {
+ given:
+ def ctx = buildContext( '''
+package test;
+
+import com.google.inject.BindingAnnotation;import com.google.inject.ImplementedBy;
+import com.google.inject.Inject;import com.google.inject.Singleton;import io.micronaut.context.annotation.Bean;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;
+
+class Test {
+ @Inject @One public TestInterface one;
+ @Inject @Two public TestInterface two;
+}
+interface TestInterface {
+}
+
+@Singleton
+@One
+@Bean(typed = TestInterface.class)
+class TestImpl implements TestInterface {
+
+}
+
+@Singleton
+@Two
+@Bean(typed = TestInterface.class)
+class TestImpl2 implements TestInterface {
+
+}
+
+@BindingAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@interface One {}
+
+@BindingAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@interface Two {}
+''')
+ def cls = ctx.classLoader.loadClass('test.Test')
+ def bean = ctx.getBean(cls)
+
+ expect:
+ bean.one.getClass().simpleName == 'TestImpl'
+ bean.two.getClass().simpleName == 'TestImpl2'
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImplementedBySpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImplementedBySpec.groovy
new file mode 100644
index 0000000..ed4c121
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImplementedBySpec.groovy
@@ -0,0 +1,37 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+import io.micronaut.context.annotation.DefaultImplementation
+import spock.lang.PendingFeature
+
+class ImplementedBySpec
+ extends AbstractTypeElementSpec {
+
+ @PendingFeature(reason = "requires Micronaut 4.5 - see https://github.com/micronaut-projects/micronaut-core/pull/10820")
+ void "test implemented by"() {
+ given:
+ def ctx = buildContext( '''
+package test;
+
+import com.google.inject.ImplementedBy;
+import com.google.inject.Singleton;
+
+@ImplementedBy(TestImpl.class)
+interface Test {
+}
+
+@Singleton
+class TestImpl implements Test {
+
+}
+
+@Singleton
+class TestImpl2 implements Test {
+
+}
+''')
+ def cls = ctx.classLoader.loadClass('test.Test')
+ expect:
+ ctx.getBean(cls).class.simpleName == 'TestImpl'
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImportModulesSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImportModulesSpec.groovy
new file mode 100644
index 0000000..c1400f7
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ImportModulesSpec.groovy
@@ -0,0 +1,299 @@
+package io.micronaut.guice.processor
+
+import com.google.inject.Module
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class ImportModulesSpec
+ extends AbstractTypeElementSpec {
+
+ void "test bind instance"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(String.class).toInstance("test");
+ }
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject public String foo;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.foo == 'test'
+ }
+
+ void "test bind provider"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(String.class).toProvider(() -> "test");
+ }
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject public String foo;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.foo == 'test'
+ }
+
+ void "test bind instance with annotation binding"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.BindingAnnotation;import com.google.inject.Inject;
+import io.micronaut.guice.annotation.Guice;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(String.class).annotatedWith(One.class).toInstance("test1");
+ bind(String.class).annotatedWith(Two.class).toInstance("test2");
+ }
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject @One public String foo;
+ @Inject @Two public String bar;
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@BindingAnnotation
+@interface One {}
+
+@Retention(RetentionPolicy.RUNTIME)
+@BindingAnnotation
+@interface Two {}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.foo == 'test1'
+ bean.bar == 'test2'
+ }
+
+
+ void "test bind interface to impl"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(ITest.class).to(TestImpl.class);
+ }
+}
+
+interface ITest {}
+
+@Singleton
+class TestImpl implements ITest {
+
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject public ITest test;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.test != null
+ bean.test.getClass().simpleName == 'TestImpl'
+ }
+
+ void "test bind interface to impl - jakarta"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import io.micronaut.guice.annotation.Guice;
+import jakarta.inject.Singleton;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(ITest.class).to(TestImpl.class);
+ }
+}
+
+interface ITest {}
+
+@Singleton
+class TestImpl implements ITest {
+
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject public ITest test;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.test != null
+ bean.test.getClass().simpleName == 'TestImpl'
+ }
+
+ void "test bind interface to impl - import"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import io.micronaut.context.annotation.Import;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(ITest.class).to(TestImpl.class);
+ }
+}
+
+interface ITest {}
+
+class TestImpl implements ITest {
+
+}
+
+@Guice(modules= SimpleModule.class)
+@Import(classes = TestImpl.class)
+class Test {
+ @Inject public ITest test1;
+ @Inject public ITest test2;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.test1 != null
+ bean.test2.getClass().simpleName == 'TestImpl'
+ bean.test1 != bean.test2
+ }
+
+ void "test bind interface to impl - import as singleton"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import io.micronaut.context.annotation.Import;
+import io.micronaut.guice.annotation.Guice;
+import jakarta.inject.Singleton;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(ITest.class).to(TestImpl.class).in(Singleton.class);
+ }
+}
+
+interface ITest {}
+
+class TestImpl implements ITest {
+
+}
+
+@Guice(modules= SimpleModule.class)
+@Import(classes = TestImpl.class)
+class Test {
+ @Inject public ITest test1;
+ @Inject public ITest test2;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.test1 != null
+ bean.test2.getClass().simpleName == 'TestImpl'
+ bean.test1 == bean.test2
+ }
+
+ void "test bind interface to impl - import as singleton 2"() {
+ given:
+ def context = buildContext("test.Test", '''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.micronaut.context.annotation.Import;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Override protected void configure() {
+ bind(ITest.class).to(TestImpl.class).in(Singleton.class);
+ }
+}
+
+interface ITest {}
+
+class TestImpl implements ITest {
+
+}
+
+@Guice(modules= SimpleModule.class)
+@Import(classes = TestImpl.class)
+class Test {
+ @Inject public ITest test1;
+ @Inject public ITest test2;
+}
+''', true)
+
+
+ expect:
+ context.getBean(Module.class)
+ def bean = getBean(context, 'test.Test')
+ bean.test1 != null
+ bean.test2.getClass().simpleName == 'TestImpl'
+ bean.test1 == bean.test2
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/InjectSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/InjectSpec.groovy
new file mode 100644
index 0000000..785b0c1
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/InjectSpec.groovy
@@ -0,0 +1,21 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class InjectSpec extends AbstractTypeElementSpec {
+
+ void "test inject annotation"() {
+ given:
+ def definition = buildBeanDefinition('test.Test', '''
+package test;
+import com.google.inject.Inject;
+
+class Test {
+ @Inject String whatever;
+}
+''')
+ expect:
+ definition != null
+ definition.injectedFields.size() == 1
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/NamedSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/NamedSpec.groovy
new file mode 100644
index 0000000..2d2a431
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/NamedSpec.groovy
@@ -0,0 +1,32 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+import io.micronaut.inject.qualifiers.Qualifiers
+
+class NamedSpec
+ extends AbstractTypeElementSpec {
+
+ void "test named"() {
+ given:
+ def definition = buildBeanDefinition('test.Test', '''
+package test;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+
+class Test {
+ @Named("foo")
+ @Inject
+ String foo;
+}
+
+''')
+ expect:
+ definition != null
+ !definition.isSingleton()
+ definition.injectedFields.size() == 1
+ def qualifier = Qualifiers.forArgument(definition.injectedFields[0].asArgument())
+ qualifier == Qualifiers.byName("foo")
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ProvidesSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ProvidesSpec.groovy
new file mode 100644
index 0000000..217c1a4
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ProvidesSpec.groovy
@@ -0,0 +1,32 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class ProvidesSpec extends AbstractTypeElementSpec {
+
+ void "test produces annotation"() {
+ given:
+ def ctx = buildContext('''
+package test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import io.micronaut.guice.annotation.Guice;
+
+class SimpleModule extends AbstractModule {
+ @Provides
+ public String test() {
+ return "good";
+ }
+}
+
+@Guice(modules= SimpleModule.class)
+class Test {
+ @Inject public String foo;
+}
+''')
+ expect:
+ getBean(ctx, 'test.Test').foo == 'good'
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ScopeSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ScopeSpec.groovy
new file mode 100644
index 0000000..ca11e66
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/ScopeSpec.groovy
@@ -0,0 +1,32 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class ScopeSpec
+ extends AbstractTypeElementSpec {
+
+ void "test scope annotation"() {
+ given:
+ def definition = buildBeanDefinition('test.Test', '''
+package test;
+
+import com.google.inject.ScopeAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@MyScope
+class Test {
+}
+
+@ScopeAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@interface MyScope {
+
+}
+''')
+ expect:
+ definition != null
+ !definition.isSingleton()
+ definition.getScopeName().get() == 'test.MyScope'
+ }
+}
diff --git a/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/SingletonSpec.groovy b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/SingletonSpec.groovy
new file mode 100644
index 0000000..383c338
--- /dev/null
+++ b/micronaut-guice-processor/src/test/groovy/io/micronaut/guice/processor/SingletonSpec.groovy
@@ -0,0 +1,44 @@
+package io.micronaut.guice.processor
+
+import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
+
+class SingletonSpec
+ extends AbstractTypeElementSpec {
+
+ void "test singleton annotation"() {
+ given:
+ def definition = buildBeanDefinition('test.Test', '''
+package test;
+
+import com.google.inject.Singleton;
+
+@Singleton
+class Test {
+}
+''')
+ expect:
+ definition != null
+ definition.isSingleton()
+ }
+
+ void "test singleton annotation has only one binding"() {
+ given:
+ def definition = buildBeanDefinition('test.Test', '''
+package test;
+
+import com.google.inject.Singleton;
+
+@Singleton
+class Test implements ITest {
+}
+
+interface ITest {
+
+}
+''')
+ expect:
+ definition != null
+ definition.isSingleton()
+ definition.exposedTypes == [definition.beanType] as Set
+ }
+}
diff --git a/micronaut-guice/build.gradle b/micronaut-guice/build.gradle
deleted file mode 100644
index f982748..0000000
--- a/micronaut-guice/build.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-plugins {
- id 'io.micronaut.build.internal.guice-module'
-}
diff --git a/micronaut-guice/build.gradle.kts b/micronaut-guice/build.gradle.kts
new file mode 100644
index 0000000..c7f0af5
--- /dev/null
+++ b/micronaut-guice/build.gradle.kts
@@ -0,0 +1,24 @@
+plugins {
+ id("io.micronaut.build.internal.guice-module")
+}
+
+dependencies {
+ implementation(mn.micronaut.context)
+ implementation(projects.micronautGuiceAnnotation)
+ implementation(libs.managed.guice) {
+ exclude(group="com.google.guava", module = "guava")
+ }
+ runtimeOnly(libs.managed.guava)
+ testAnnotationProcessor(projects.micronautGuiceProcessor)
+ testAnnotationProcessor(mn.micronaut.inject.java)
+ testImplementation(mnTest.micronaut.test.junit5)
+ testImplementation(mnTest.mockito.junit.jupiter)
+ testRuntimeOnly(libs.junit.jupiter.engine)
+}
+
+//tasks {
+// compileTestJava {
+// options.isFork = true
+// options.forkOptions.jvmArgs = listOf("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005")
+// }
+//}
diff --git a/micronaut-guice/src/main/java/io/micronaut/guice/GuiceModuleBinder.java b/micronaut-guice/src/main/java/io/micronaut/guice/GuiceModuleBinder.java
new file mode 100644
index 0000000..3da34cd
--- /dev/null
+++ b/micronaut-guice/src/main/java/io/micronaut/guice/GuiceModuleBinder.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed 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 io.micronaut.guice;
+
+import com.google.inject.Binder;
+import com.google.inject.Binding;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.CreationException;
+import com.google.inject.ImplementedBy;
+import com.google.inject.Key;
+import com.google.inject.MembersInjector;
+import com.google.inject.Module;
+import com.google.inject.PrivateBinder;
+import com.google.inject.ProvidedBy;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.ConstantBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.name.Named;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.Message;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
+import com.google.inject.spi.ProvisionListener;
+import com.google.inject.spi.TypeConverter;
+import com.google.inject.spi.TypeListener;
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.context.BeanProvider;
+import io.micronaut.context.RuntimeBeanDefinition;
+import io.micronaut.context.annotation.Context;
+import io.micronaut.context.env.Environment;
+import io.micronaut.context.event.StartupEvent;
+import io.micronaut.context.exceptions.BeanInstantiationException;
+import io.micronaut.context.exceptions.ConfigurationException;
+import io.micronaut.context.exceptions.NoSuchBeanException;
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.Order;
+import io.micronaut.core.order.Ordered;
+import io.micronaut.core.reflect.InstantiationUtils;
+import io.micronaut.core.type.Argument;
+import io.micronaut.core.util.StringUtils;
+import io.micronaut.inject.BeanDefinition;
+import io.micronaut.inject.annotation.MutableAnnotationMetadata;
+import io.micronaut.inject.qualifiers.PrimaryQualifier;
+import io.micronaut.inject.qualifiers.Qualifiers;
+import io.micronaut.runtime.event.annotation.EventListener;
+import jakarta.inject.Qualifier;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Context
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@Internal
+class GuiceModuleBinder implements Binder {
+ private static final Logger LOG = LoggerFactory.getLogger(GuiceModuleBinder.class);
+ private final ApplicationContext applicationContext;
+ private final List> linkedBindingBuilders = new ArrayList<>();
+ private final List constantBuilders = new ArrayList<>();
+ private final List errors = new ArrayList<>();
+ private final List toInject = new ArrayList<>();
+ private Object currentSource;
+
+ GuiceModuleBinder(
+ ApplicationContext applicationContext,
+ List modules) {
+ this.applicationContext = applicationContext;
+ for (Module module : modules) {
+ withSource(module);
+ module.configure(this);
+ }
+ try {
+ if (!this.errors.isEmpty()) {
+ for (Message error : errors) {
+ Throwable cause = error.getCause();
+ if (cause != null) {
+ LOG.error("Guice Module Error: " + error.getMessage(), cause);
+ } else {
+ LOG.error("Guice Module Error: {}", error.getMessage());
+ }
+ }
+ throw new ConfigurationException("Failed to import modules due to prior errors");
+ }
+ for (LinkedBindingBuilderImpl> builder : linkedBindingBuilders) {
+ RuntimeBeanDefinition> beanDefinition = builder.build();
+ if (beanDefinition != null) {
+ applicationContext.registerBeanDefinition(beanDefinition);
+ }
+ }
+
+ for (AnnotatedConstantBindingBuilderImpl constantBuilder : constantBuilders) {
+ RuntimeBeanDefinition> beanDefinition = constantBuilder.build();
+ applicationContext.registerBeanDefinition(beanDefinition);
+ }
+ } finally {
+ linkedBindingBuilders.clear();
+ constantBuilders.clear();
+ }
+ }
+
+ @EventListener
+ void onStartup(StartupEvent startupEvent) {
+ // run more injections
+ try {
+ for (Object o : toInject) {
+ applicationContext.inject(o);
+ }
+ } finally {
+ toInject.clear();
+ }
+ }
+
+ @Override
+ public void bindInterceptor(Matcher super Class>> classMatcher, Matcher super Method> methodMatcher, MethodInterceptor... interceptors) {
+ throw new UnsupportedOperationException("Guice interceptors are not supported");
+ }
+
+ @Override
+ public void bindScope(Class extends Annotation> annotationType, Scope scope) {
+ if (scope != Scopes.NO_SCOPE && scope != Scopes.SINGLETON) {
+ throw new UnsupportedOperationException("Guice custom scopes are not supported");
+ }
+ }
+
+ @Override
+ public LinkedBindingBuilder bind(Key key) {
+ Argument argument = (Argument) Argument.of(key.getTypeLiteral().getType());
+ LinkedBindingBuilderImpl builder = new LinkedBindingBuilderImpl<>(argument);
+ linkedBindingBuilders.add(builder);
+ return builder;
+ }
+
+ @Override
+ public AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) {
+ Argument argument = (Argument) Argument.of(typeLiteral.getType());
+ LinkedBindingBuilderImpl builder = new LinkedBindingBuilderImpl<>(argument);
+ linkedBindingBuilders.add(builder);
+ return builder;
+ }
+
+ @Override
+ public AnnotatedBindingBuilder bind(Class type) {
+ LinkedBindingBuilderImpl builder = new LinkedBindingBuilderImpl<>(Argument.of(type));
+ linkedBindingBuilders.add(builder);
+ return builder;
+ }
+
+ @Override
+ public AnnotatedConstantBindingBuilder bindConstant() {
+ AnnotatedConstantBindingBuilderImpl builder = new AnnotatedConstantBindingBuilderImpl();
+ constantBuilders.add(builder);
+ return builder;
+ }
+
+ @Override
+ public void requestInjection(TypeLiteral type, T instance) {
+ requestInjection(instance);
+ }
+
+ @Override
+ public void requestInjection(Object instance) {
+ if (!toInject.contains(instance)) {
+ toInject.add(instance);
+ }
+
+ }
+
+ @Override
+ public void requestStaticInjection(Class>... types) {
+ throw new UnsupportedOperationException("Static injection is not supported");
+ }
+
+ @Override
+ public void install(Module module) {
+ module.configure(this);
+ }
+
+ @Override
+ public Stage currentStage() {
+ Set activeNames = applicationContext.getEnvironment().getActiveNames();
+ if (activeNames.contains(Environment.DEVELOPMENT) || activeNames.contains(Environment.TEST)) {
+ return Stage.DEVELOPMENT;
+ }
+ return Stage.PRODUCTION;
+ }
+
+ @Override
+ public void addError(String message, Object... arguments) {
+ Objects.requireNonNull(message, "Message cannot be null");
+ String msg = String.format(message, arguments);
+ addError(new Message(msg));
+ }
+
+ @Override
+ public void addError(Throwable t) {
+ Objects.requireNonNull(t, "Throwable cannot be null");
+ addError(new Message(t.getMessage(), t));
+ }
+
+ @Override
+ public void addError(Message message) {
+ Objects.requireNonNull(message, "Message cannot be null");
+ errors.add(message);
+ }
+
+ @Override
+ public Provider getProvider(Key key) {
+ Objects.requireNonNull(key, "Key cannot be null");
+ @SuppressWarnings("unchecked")
+ Argument argument = (Argument) Argument.of(key.getTypeLiteral().getType());
+ @SuppressWarnings("unchecked")
+ BeanProvider provider = applicationContext.getBean(Argument.of(BeanProvider.class, argument));
+ return provider::get;
+ }
+
+ @Override
+ public Provider getProvider(Dependency dependency) {
+ Objects.requireNonNull(dependency, "Dependency cannot be null");
+ return getProvider(dependency.getKey());
+ }
+
+ @Override
+ public Provider getProvider(Class type) {
+ Objects.requireNonNull(type, "Type cannot be null");
+ @SuppressWarnings("unchecked")
+ BeanProvider provider = applicationContext.getBean(Argument.of(BeanProvider.class, type));
+ return provider::get;
+ }
+
+ @Override
+ public MembersInjector getMembersInjector(TypeLiteral typeLiteral) {
+ return instance -> {
+ if (!applicationContext.isRunning()) {
+ throw new IllegalStateException("Injector not started");
+ }
+ applicationContext.inject(instance);
+ };
+ }
+
+ @Override
+ public MembersInjector getMembersInjector(Class type) {
+ return instance -> {
+ if (!applicationContext.isRunning()) {
+ throw new IllegalStateException("Injector not started");
+ }
+ applicationContext.inject(instance);
+ };
+ }
+
+ @Override
+ public void convertToTypes(Matcher super TypeLiteral>> typeMatcher, TypeConverter converter) {
+ throw new UnsupportedOperationException("Method convertToTypes is not supported");
+ }
+
+ @Override
+ public void bindListener(Matcher super TypeLiteral>> typeMatcher, TypeListener listener) {
+ throw new UnsupportedOperationException("Method bindListener is not supported");
+ }
+
+ @Override
+ public void bindListener(Matcher super Binding>> bindingMatcher, ProvisionListener... listeners) {
+ throw new UnsupportedOperationException("Method bindListener is not supported");
+ }
+
+ @Override
+ public Binder withSource(Object source) {
+ this.currentSource = source;
+ return this;
+ }
+
+ @Override
+ public Binder skipSources(Class>... classesToSkip) {
+ throw new UnsupportedOperationException("Method skipSources is not supported");
+ }
+
+ @Override
+ public PrivateBinder newPrivateBinder() {
+ throw new UnsupportedOperationException("Private bindings are not supported");
+ }
+
+ @Override
+ public void requireExplicitBindings() {
+ // no-op
+ }
+
+ @Override
+ public void disableCircularProxies() {
+ // no-op
+ }
+
+ @Override
+ public void requireAtInjectOnConstructors() {
+ // no-op
+ }
+
+ @Override
+ public void requireExactBindingAnnotations() {
+ // no-op
+ }
+
+ @Override
+ public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) {
+ // no-op
+ }
+
+ private static void bindQualifier(RuntimeBeanDefinition.Builder builder, String beanName, Class extends Annotation> beanQualifier, boolean primary) {
+ if (StringUtils.isNotEmpty(beanName)) {
+ builder.named(beanName);
+ } else {
+ if (beanQualifier != null) {
+ MutableAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata();
+ annotationMetadata.addAnnotation(beanQualifier.getName(), Map.of());
+ builder.annotationMetadata(annotationMetadata);
+ builder.qualifier(Qualifiers.byAnnotation(annotationMetadata, beanQualifier));
+ } else if (primary) {
+ builder.qualifier(PrimaryQualifier.INSTANCE);
+ }
+ }
+ }
+
+ private static void validateBindingAnnotation(Class extends Annotation> annotationType) {
+ Objects.requireNonNull(annotationType, "Annotation type cannot be null");
+ if (annotationType.getAnnotation(BindingAnnotation.class) == null && annotationType.getAnnotation(Qualifier.class) == null) {
+ throw new IllegalArgumentException("Annotation type must be annotated itself with either @BindingAnnotation or jakarta.inject.Qualifier");
+ }
+ }
+
+ private static class AnnotatedConstantBindingBuilderImpl implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
+ private Object value;
+ private Class extends Annotation> annotationType;
+ private String name;
+
+ @Override
+ public ConstantBindingBuilder annotatedWith(Class extends Annotation> annotationType) {
+ Objects.requireNonNull(annotationType, "Annotation type cannot be null");
+ this.annotationType = annotationType;
+ return this;
+ }
+
+ @Override
+ public ConstantBindingBuilder annotatedWith(Annotation annotation) {
+ Objects.requireNonNull(annotation, "Annotation cannot be null");
+ if (annotation instanceof Named named) {
+ this.name = named.value();
+ } else {
+ this.annotationType = annotation.annotationType();
+ }
+ return this;
+ }
+
+ @Override
+ public void to(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(float value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(char value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public void to(Class> value) {
+ this.value = value;
+ }
+
+ @Override
+ public > void to(E value) {
+ this.value = value;
+ }
+
+ public RuntimeBeanDefinition> build() {
+ Objects.requireNonNull(value, "Binding constant cannot be null, call one of the to(..) methods on the Guice binding");
+ RuntimeBeanDefinition.Builder builder = RuntimeBeanDefinition.builder(value);
+ bindQualifier(builder, name, annotationType, false);
+ return builder.build();
+ }
+ }
+
+ private class LinkedBindingBuilderImpl implements LinkedBindingBuilder, AnnotatedBindingBuilder {
+ private final Argument beanType;
+ private boolean isSingleton;
+ private Class extends Annotation> scope;
+
+ private Supplier supplier;
+ private Class extends Annotation> annotationType;
+ private String name;
+ private boolean primary;
+
+ public LinkedBindingBuilderImpl(Argument argument) {
+ this.beanType = argument;
+ }
+
+ @Override
+ public ScopedBindingBuilder to(Class extends T> implementation) {
+ BeanProvider provider = applicationContext.getBean(Argument.of(BeanProvider.class, implementation));
+ this.supplier = provider::get;
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder to(TypeLiteral extends T> implementation) {
+ @SuppressWarnings("unchecked")
+ Argument argument = (Argument) Argument.of(implementation.getType());
+ BeanProvider provider = applicationContext.getBean(Argument.of(BeanProvider.class, argument));
+ this.supplier = provider::get;
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder to(Key extends T> targetKey) {
+ @SuppressWarnings("unchecked")
+ Argument argument = (Argument) Argument.of(targetKey.getTypeLiteral().getType());
+ BeanProvider provider = applicationContext.getBean(Argument.of(BeanProvider.class, argument));
+ this.supplier = provider::get;
+ return this;
+ }
+
+ @Override
+ public void toInstance(T instance) {
+ Objects.requireNonNull(instance, "Instance cannot be null");
+ this.supplier = () -> instance;
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Provider extends T> provider) {
+ Objects.requireNonNull(provider, "Provider cannot be null");
+ this.supplier = provider::get;
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(jakarta.inject.Provider extends T> provider) {
+ Objects.requireNonNull(provider, "Provider cannot be null");
+ this.supplier = provider::get;
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Class extends jakarta.inject.Provider extends T>> providerType) {
+ Objects.requireNonNull(providerType, "Provider type cannot be null");
+ BeanProvider> provider = applicationContext.getBean(Argument.of(BeanProvider.class, providerType));
+ this.supplier = () -> provider.get().get();
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(TypeLiteral extends jakarta.inject.Provider extends T>> providerType) {
+ Objects.requireNonNull(providerType, "Provider type cannot be null");
+ @SuppressWarnings("unchecked") Argument extends jakarta.inject.Provider extends T>> argument =
+ (Argument extends jakarta.inject.Provider extends T>>) Argument.of(providerType.getType());
+ BeanProvider> provider = applicationContext.getBean(Argument.of(BeanProvider.class, argument));
+ this.supplier = () -> provider.get().get();
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Key extends jakarta.inject.Provider extends T>> providerKey) {
+ Objects.requireNonNull(providerKey, "Provider type cannot be null");
+ return toProvider(providerKey.getTypeLiteral());
+ }
+
+ @Override
+ public ScopedBindingBuilder toConstructor(Constructor constructor) {
+ supplier = () -> InstantiationUtils.tryInstantiate(constructor)
+ .orElseThrow(() -> new BeanInstantiationException("Unable to instance bean via constructor: " + constructor));
+ return this;
+ }
+
+ @Override
+ public ScopedBindingBuilder toConstructor(Constructor constructor, TypeLiteral extends S> type) {
+ supplier = () -> InstantiationUtils.tryInstantiate(constructor)
+ .orElseThrow(() -> new BeanInstantiationException("Unable to instance bean via constructor: " + constructor));
+ return this;
+ }
+
+ @Override
+ public void in(Class extends Annotation> scopeAnnotation) {
+ if (scopeAnnotation == Singleton.class || scopeAnnotation == jakarta.inject.Singleton.class) {
+ this.isSingleton = true;
+ }
+ this.scope = scopeAnnotation;
+ }
+
+ @Override
+ public void in(Scope scope) {
+ if (scope == Scopes.SINGLETON) {
+ this.isSingleton = true;
+ } else if (scope != Scopes.NO_SCOPE) {
+ throw new IllegalArgumentException("Custom Guice scopes are not supported");
+ }
+ }
+
+ @Override
+ public void asEagerSingleton() {
+ this.isSingleton = true;
+ this.scope = Context.class;
+ }
+
+ public RuntimeBeanDefinition build() {
+ Objects.requireNonNull(beanType, "Bean type cannot be null");
+ if (supplier == null) {
+ // untargetted binding
+ Class javaType = beanType.getType();
+ ImplementedBy implementedBy = javaType.getAnnotation(ImplementedBy.class);
+ ProvidedBy providedBy = javaType.getAnnotation(ProvidedBy.class);
+ if (implementedBy != null) {
+ if (!javaType.isAssignableFrom(implementedBy.value())) {
+ Message message = new Message(javaType, "@ImplementedBy annotation specifies a type that does not implement the declaring type");
+ throw new com.google.inject.ConfigurationException(
+ List.of(message)
+ );
+ }
+ to((Class extends T>) implementedBy.value());
+ } else if (providedBy != null) {
+ toProvider((Class extends jakarta.inject.Provider extends T>>) providedBy.value());
+ } else {
+ if (!applicationContext.containsBean(javaType)) {
+ Message message = new Message(javaType, "Cannot create untargetted binding to type that is not itself declared a bean. " +
+ "Considering adding @Guice(classes=" + javaType.getSimpleName() + ".class) below your @Guice declaration.");
+ throw new com.google.inject.ConfigurationException(
+ List.of(message)
+ );
+ } else {
+ BeanDefinition beanDefinition = applicationContext.getBeanDefinition(javaType);
+ toProvider(() -> applicationContext.getBean(beanDefinition));
+ this.primary = true;
+ }
+ }
+ }
+ Objects.requireNonNull(supplier, "Bean Provider cannot be null, call one of the binding methods like to(instance)");
+
+ RuntimeBeanDefinition.Builder builder = RuntimeBeanDefinition
+ .builder(beanType, () -> {
+ try {
+ return supplier.get();
+ } catch (NoSuchBeanException e) {
+ throw new CreationException(List.of(
+ new Message("Guice binding to bean [" + beanType.getTypeName() + "] cannot be resolved since no bean exists. " +
+ "Considering adding @Guice(classes=" + beanType.getSimpleName() + ".class) to the @Guice annotation definition."),
+ new Message(e.getMessage(), e)
+ ));
+ }
+ });
+
+ if (scope != null) {
+ builder.scope(scope);
+ }
+ if (isSingleton) {
+ builder.singleton(true);
+ }
+ builder.exposedTypes(beanType.getType());
+ String beanName = name;
+ Class extends Annotation> beanQualifier = annotationType;
+ bindQualifier(builder, beanName, beanQualifier, primary);
+ return builder
+ .build();
+ }
+
+ @Override
+ public LinkedBindingBuilder annotatedWith(Class extends Annotation> annotationType) {
+ validateBindingAnnotation(annotationType);
+ this.annotationType = annotationType;
+ return this;
+ }
+
+ @Override
+ public LinkedBindingBuilder annotatedWith(Annotation annotation) {
+ Objects.requireNonNull(annotation, "Annotation cannot be null");
+ if (annotation instanceof Named named) {
+ this.name = named.value();
+ return this;
+ } else {
+ return annotatedWith(annotation.annotationType());
+ }
+ }
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/BindingAnnotationTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/BindingAnnotationTest.java
new file mode 100644
index 0000000..90be795
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/BindingAnnotationTest.java
@@ -0,0 +1,23 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import io.micronaut.context.annotation.Import;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest(startApplication = false)
+@Guice(modules = CreditCardProcessorModule.class)
+@Import(classes = {PayPalCreditCardProcessor.class, CheckoutCreditCardProcessor.class})
+class BindingAnnotationTest {
+ @Inject @PayPal CreditCardProcessor paypalProcessor;
+ @Inject @GoogleCheckout CreditCardProcessor checkoutProcessor;
+
+ @Test
+ void testInjectWithQualifiers() {
+ assertInstanceOf(CheckoutCreditCardProcessor.class, checkoutProcessor);
+ assertInstanceOf(PayPalCreditCardProcessor.class, paypalProcessor);
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CheckoutCreditCardProcessor.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CheckoutCreditCardProcessor.java
new file mode 100644
index 0000000..7142388
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CheckoutCreditCardProcessor.java
@@ -0,0 +1,4 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+public class CheckoutCreditCardProcessor implements CreditCardProcessor {
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessor.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessor.java
new file mode 100644
index 0000000..952c978
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessor.java
@@ -0,0 +1,4 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+public interface CreditCardProcessor {
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessorModule.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessorModule.java
new file mode 100644
index 0000000..19a4d42
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/CreditCardProcessorModule.java
@@ -0,0 +1,22 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+final class CreditCardProcessorModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ // This uses the optional `annotatedWith` clause in the `bind()` statement
+ bind(CreditCardProcessor.class)
+ .annotatedWith(PayPal.class)
+ .to(PayPalCreditCardProcessor.class);
+ }
+
+ // This uses binding annotation with a @Provides method
+ @Provides
+ @GoogleCheckout
+ public CreditCardProcessor provideCheckoutProcessor(
+ CheckoutCreditCardProcessor processor) {
+ return processor;
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/GoogleCheckout.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/GoogleCheckout.java
new file mode 100644
index 0000000..7b60b3b
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/GoogleCheckout.java
@@ -0,0 +1,16 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+
+@BindingAnnotation
+@Target({ FIELD, PARAMETER, METHOD })
+@Retention(RUNTIME)
+public @interface GoogleCheckout {}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPal.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPal.java
new file mode 100644
index 0000000..6e4caec
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPal.java
@@ -0,0 +1,15 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import jakarta.inject.Qualifier;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Qualifier
+@Target({ FIELD, PARAMETER, METHOD })
+@Retention(RUNTIME)
+public @interface PayPal {}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPalCreditCardProcessor.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPalCreditCardProcessor.java
new file mode 100644
index 0000000..22225c8
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/annotations/PayPalCreditCardProcessor.java
@@ -0,0 +1,4 @@
+package io.micronaut.guice.doc.examples.bindings.annotations;
+
+public class PayPalCreditCardProcessor implements CreditCardProcessor {
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/instance/InstanceBindingTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/instance/InstanceBindingTest.java
new file mode 100644
index 0000000..557b38f
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/instance/InstanceBindingTest.java
@@ -0,0 +1,53 @@
+package io.micronaut.guice.doc.examples.bindings.instance;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import jakarta.inject.Qualifier;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest(startApplication = false)
+@Guice(modules = InstanceBindingModule.class)
+public class InstanceBindingTest {
+ @Inject @Named("JDBC URL") String jdbcUrl;
+ @Inject @Named("login timeout seconds") Integer timeout;
+ @Inject @HttpPort Integer port;
+
+ @Test
+ void testInstanceBinding() {
+ assertEquals("jdbc:mysql://localhost/pizza", jdbcUrl);
+ assertEquals(10, timeout);
+ assertEquals(8080, port);
+ }
+}
+
+class InstanceBindingModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(String.class)
+ .annotatedWith(Names.named("JDBC URL"))
+ .toInstance("jdbc:mysql://localhost/pizza");
+ bind(Integer.class)
+ .annotatedWith(Names.named("login timeout seconds"))
+ .toInstance(10);
+ bindConstant()
+ .annotatedWith(HttpPort.class)
+ .to(8080);
+ }
+}
+
+@Qualifier
+@Target({ FIELD, PARAMETER, METHOD })
+@Retention(RUNTIME)
+@interface HttpPort {}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/DatabaseTransactionLog.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/DatabaseTransactionLog.java
new file mode 100644
index 0000000..94d5ac3
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/DatabaseTransactionLog.java
@@ -0,0 +1,9 @@
+package io.micronaut.guice.doc.examples.bindings.linked;
+
+import com.google.inject.Inject;
+
+public class DatabaseTransactionLog implements TransactionLog {
+ @Inject
+ public DatabaseTransactionLog() {
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingNotActiveTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingNotActiveTest.java
new file mode 100644
index 0000000..63a7707
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingNotActiveTest.java
@@ -0,0 +1,19 @@
+package io.micronaut.guice.doc.examples.bindings.linked;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest
+public class LinkedBindingNotActiveTest {
+ @Inject
+ ApplicationContext applicationContext;
+
+ @Test
+ void testNotActive() {
+ assertFalse(applicationContext.containsBean(BillingModule.class));
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingTest.java
new file mode 100644
index 0000000..c8ff7a3
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/LinkedBindingTest.java
@@ -0,0 +1,36 @@
+package io.micronaut.guice.doc.examples.bindings.linked;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest(startApplication = false, environments = "linked")
+@Guice(
+ modules = BillingModule.class,
+ environments = "linked"
+)
+class LinkedBindingTest {
+ @Test
+ void testLinkedBinding(TransactionLog transactionLog, DatabaseTransactionLog databaseTransactionLog) {
+ Assertions.assertNotNull(transactionLog);
+ Assertions.assertNotNull(databaseTransactionLog);
+ Assertions.assertInstanceOf(MySqlDatabaseTransactionLog.class, transactionLog);
+ Assertions.assertInstanceOf(MySqlDatabaseTransactionLog.class, databaseTransactionLog);
+ }
+}
+
+class BillingModule extends AbstractModule {
+ @Provides
+ public TransactionLog provideTransactionLog(DatabaseTransactionLog databaseTransactionLog) {
+ return databaseTransactionLog;
+ }
+
+ @Provides
+ public DatabaseTransactionLog provideDatabaseTransactionLog(MySqlDatabaseTransactionLog impl) {
+ return impl;
+ }
+}
+
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/MySqlDatabaseTransactionLog.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/MySqlDatabaseTransactionLog.java
new file mode 100644
index 0000000..c5cae9e
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/MySqlDatabaseTransactionLog.java
@@ -0,0 +1,9 @@
+package io.micronaut.guice.doc.examples.bindings.linked;
+
+import com.google.inject.Inject;
+
+class MySqlDatabaseTransactionLog extends DatabaseTransactionLog {
+ @Inject
+ public MySqlDatabaseTransactionLog() {
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/TransactionLog.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/TransactionLog.java
new file mode 100644
index 0000000..535923d
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/linked/TransactionLog.java
@@ -0,0 +1,4 @@
+package io.micronaut.guice.doc.examples.bindings.linked;
+
+public interface TransactionLog {
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/multi/MultiBinderTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/multi/MultiBinderTest.java
new file mode 100644
index 0000000..06a9b2e
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/multi/MultiBinderTest.java
@@ -0,0 +1,57 @@
+package io.micronaut.guice.doc.examples.bindings.multi;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.multibindings.Multibinder;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import java.net.URI;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest(startApplication = false)
+@Guice(
+ modules = FlickrPluginModule.class,
+ classes = FlickrPhotoSummarizer.class
+)
+public class MultiBinderTest {
+ @Inject
+ Set uriSummarizers;
+
+ @Test
+ void testMultiBindings() {
+ Assertions.assertNotNull(uriSummarizers);
+ Assertions.assertEquals(2, uriSummarizers.size());
+ Assertions.assertTrue(uriSummarizers.stream().anyMatch(s -> s instanceof FlickrPhotoSummarizer));
+ Assertions.assertTrue(uriSummarizers.stream().anyMatch(s -> s instanceof GooglePhotoSummarizer));
+ }
+}
+
+class FlickrPluginModule extends AbstractModule {
+ public void configure() {
+ Multibinder uriBinder = Multibinder.newSetBinder(binder(), UriSummarizer.class);
+ uriBinder.addBinding().to(FlickrPhotoSummarizer.class);
+ uriBinder.addBinding().toInstance(new GooglePhotoSummarizer());
+ }
+}
+interface UriSummarizer {
+ /**
+ * Returns a short summary of the URI, or null if this summarizer doesn't
+ * know how to summarize the URI.
+ */
+ String summarize(URI uri);
+}
+
+class FlickrPhotoSummarizer implements UriSummarizer {
+ @Override
+ public String summarize(URI uri) {
+ return "flickr";
+ }
+}
+class GooglePhotoSummarizer implements UriSummarizer {
+ @Override
+ public String summarize(URI uri) {
+ return "google";
+ }
+}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/provider/ProviderBindingTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/provider/ProviderBindingTest.java
new file mode 100644
index 0000000..640fc84
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/provider/ProviderBindingTest.java
@@ -0,0 +1,51 @@
+package io.micronaut.guice.doc.examples.bindings.provider;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.annotation.MockBean;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import java.sql.Connection;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@MicronautTest(startApplication = false)
+@Guice(modules = BillingModule.class)
+public class ProviderBindingTest {
+ @MockBean Connection connection = Mockito.mock(Connection.class);
+ @Inject TransactionLog transactionLog;
+
+ @Test
+ void testProviderInjection() {
+ assertInstanceOf(DatabaseTransactionLog.class, transactionLog);
+ DatabaseTransactionLog dtl = (DatabaseTransactionLog) transactionLog;
+ Assertions.assertNotNull(dtl.connection());
+ }
+}
+
+class BillingModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(TransactionLog.class)
+ .toProvider(DatabaseTransactionLogProvider.class);
+ }
+}
+interface TransactionLog {}
+class DatabaseTransactionLogProvider implements Provider {
+ private final Connection connection;
+
+ @Inject
+ public DatabaseTransactionLogProvider(Connection connection) {
+ this.connection = connection;
+ }
+
+ public TransactionLog get() {
+ return new DatabaseTransactionLog(connection);
+ }
+}
+
+record DatabaseTransactionLog(Connection connection) implements TransactionLog {}
diff --git a/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/untargetted/UntargettedBindingTest.java b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/untargetted/UntargettedBindingTest.java
new file mode 100644
index 0000000..be54d9b
--- /dev/null
+++ b/micronaut-guice/src/test/java/io/micronaut/guice/doc/examples/bindings/untargetted/UntargettedBindingTest.java
@@ -0,0 +1,55 @@
+package io.micronaut.guice.doc.examples.bindings.untargetted;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.ImplementedBy;
+import com.google.inject.Singleton;
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Import;
+import io.micronaut.guice.annotation.Guice;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+@MicronautTest(startApplication = false)
+@Guice(modules = MyModule.class)
+@Import(classes = MyConcreteClass.class)
+public class UntargettedBindingTest {
+ @Inject MyInterface myInterface1;
+ @Inject MyInterface myInterface2;
+ @Inject MyConcreteClass myConcreteClass;
+ @Inject AnotherConcreteClass first;
+ @Inject AnotherConcreteClass second;
+ @Test
+ void testUntargetted() {
+ assertInstanceOf(MyImplementation.class, myInterface1);
+ assertSame(myInterface1, myInterface2);
+ assertSame(first, second);
+ assertNotNull(myConcreteClass);
+ }
+}
+
+class MyModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(MyConcreteClass.class);
+ bind(MyInterface.class);
+ bind(AnotherConcreteClass.class).in(Singleton.class);
+ }
+}
+
+class MyConcreteClass {
+}
+
+@ImplementedBy(MyImplementation.class)
+interface MyInterface {
+}
+
+@Singleton
+class MyImplementation implements MyInterface {}
+
+@Bean
+class AnotherConcreteClass {}
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 05b6d64..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-pluginManagement {
- repositories {
- gradlePluginPortal()
- mavenCentral()
- }
-}
-
-plugins {
- id 'io.micronaut.build.shared.settings' version '6.7.1'
-}
-
-rootProject.name = 'project-template-parent'
-
-include 'project-template'
-include 'project-template-bom'
-
-enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
-
-micronautBuild {
- importMicronautCatalog()
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..d4f9c3e
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id("io.micronaut.build.shared.settings") version "6.6.1"
+}
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+rootProject.name = "guice-parent"
+
+include("micronaut-guice-bom")
+include("micronaut-guice")
+include("micronaut-guice-annotation")
+include("micronaut-guice-processor")
+
+val micronautVersion = providers.gradleProperty("micronautVersion")
+
+configure {
+ useStandardizedProjectNames.set(true)
+ importMicronautCatalog()
+}