From af8517b83d1b7b96663ff639569c19df4cdaa3a6 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 26 Sep 2023 17:32:09 +0200 Subject: [PATCH] Config: native build verification - verify the current ImageMode when a config object is injected - fail if injected during the static initialization phase of a native image build --- .../arc/deployment/ConfigBuildStep.java | 12 +- .../ConfigInjectionStaticInitBuildItem.java | 6 + .../deployment/NativeBuildConfigSteps.java | 73 +++++++++++ .../arc/test/config/nativebuild/Fail.java | 19 +++ .../NativeBuildConfigInjectionFailTest.java | 24 ++++ .../NativeBuildConfigInjectionOkTest.java | 19 +++ .../arc/test/config/nativebuild/Ok.java | 22 ++++ .../quarkus/arc/config/NativeBuildTime.java | 20 ++++ .../arc/runtime/ConfigMappingCreator.java | 5 + .../arc/runtime/NativeBuildConfigCheck.java | 20 ++++ .../NativeBuildConfigCheckInterceptor.java | 113 ++++++++++++++++++ .../arc/runtime/NativeBuildConfigContext.java | 18 +++ .../NativeBuildConfigContextCreator.java | 22 ++++ 13 files changed, 370 insertions(+), 3 deletions(-) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/NativeBuildConfigSteps.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Fail.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionFailTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionOkTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Ok.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/config/NativeBuildTime.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheck.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheckInterceptor.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContext.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContextCreator.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index af7ad71af9a81..254c2d04fbbf6 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -70,6 +70,7 @@ import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.annotations.ConfigPhase; @@ -83,7 +84,7 @@ public class ConfigBuildStep { private static final Logger LOGGER = Logger.getLogger(ConfigBuildStep.class.getName()); private static final DotName MP_CONFIG = DotName.createSimple(Config.class.getName()); - private static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); + static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); @@ -290,6 +291,7 @@ void registerConfigMappingsBean( BeanRegistrationPhaseBuildItem beanRegistration, List configClasses, CombinedIndexBuildItem combinedIndex, + PackageConfig packageConfig, BuildProducer beanConfigurator) { if (configClasses.isEmpty()) { @@ -323,7 +325,8 @@ void registerConfigMappingsBean( .creator(ConfigMappingCreator.class) .addInjectionPoint(ClassType.create(DotNames.INJECTION_POINT)) .param("type", configClass.getConfigClass()) - .param("prefix", configClass.getPrefix()); + .param("prefix", configClass.getPrefix()) + .param("nativeBuild", packageConfig.type.equalsIgnoreCase(PackageConfig.BuiltInType.NATIVE.getValue())); if (configClass.getConfigClass().isAnnotationPresent(Unremovable.class)) { bean.unremovable(); @@ -338,6 +341,7 @@ void registerConfigPropertiesBean( BeanRegistrationPhaseBuildItem beanRegistration, List configClasses, CombinedIndexBuildItem combinedIndex, + PackageConfig packageConfig, BuildProducer beanConfigurator) { if (configClasses.isEmpty()) { @@ -371,7 +375,9 @@ void registerConfigPropertiesBean( .creator(ConfigMappingCreator.class) .addInjectionPoint(ClassType.create(DotNames.INJECTION_POINT)) .param("type", configClass.getConfigClass()) - .param("prefix", configClass.getPrefix()))); + .param("prefix", configClass.getPrefix()) + .param("nativeBuild", + packageConfig.type.equalsIgnoreCase(PackageConfig.BuiltInType.NATIVE.getValue())))); } } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java index efb1919e33b02..d3674722fa64b 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java @@ -4,7 +4,13 @@ import io.quarkus.builder.item.MultiBuildItem; +/** + * + * @deprecated TODO + */ +@Deprecated(forRemoval = true) public final class ConfigInjectionStaticInitBuildItem extends MultiBuildItem { + private final DotName declaringCandidate; public ConfigInjectionStaticInitBuildItem(final DotName declaringCandidate) { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/NativeBuildConfigSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/NativeBuildConfigSteps.java new file mode 100644 index 0000000000000..51a14bfbc372b --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/NativeBuildConfigSteps.java @@ -0,0 +1,73 @@ +package io.quarkus.arc.deployment; + +import java.util.HashSet; +import java.util.Set; + +import jakarta.inject.Singleton; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.InjectionPointInfo; +import io.quarkus.arc.runtime.NativeBuildConfigCheck; +import io.quarkus.arc.runtime.NativeBuildConfigCheckInterceptor; +import io.quarkus.arc.runtime.NativeBuildConfigContext; +import io.quarkus.arc.runtime.NativeBuildConfigContextCreator; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.pkg.steps.NativeBuild; + +public class NativeBuildConfigSteps { + + @BuildStep(onlyIf = NativeBuild.class) + SyntheticBeanBuildItem registerNativeBuildConfigContext(ConfigurationBuildItem config, + BeanDiscoveryFinishedBuildItem beanDiscoveryFinished) { + + // Collect all @ConfigProperty injection points + Set injectedProperties = new HashSet<>(); + for (InjectionPointInfo injectionPoint : beanDiscoveryFinished.getInjectionPoints()) { + if (injectionPoint.hasDefaultedQualifier()) { + continue; + } + AnnotationInstance configProperty = injectionPoint.getRequiredQualifier(ConfigBuildStep.MP_CONFIG_PROPERTY_NAME); + if (configProperty != null) { + injectedProperties.add(configProperty.value("name").asString()); + } + } + + // Retain only BUILD_AND_RUN_TIME_FIXED properties + injectedProperties.retainAll(config.getReadResult().getBuildTimeRunTimeValues().keySet()); + + return SyntheticBeanBuildItem.configure(NativeBuildConfigContext.class) + .param( + "buildAndRunTimeFixed", + injectedProperties.toArray(String[]::new)) + .creator(NativeBuildConfigContextCreator.class) + .scope(Singleton.class) + .done(); + + } + + @BuildStep(onlyIf = NativeBuild.class) + AdditionalBeanBuildItem registerBeans() { + return AdditionalBeanBuildItem.builder().addBeanClasses(NativeBuildConfigCheckInterceptor.class, + NativeBuildConfigCheck.class).build(); + } + + @BuildStep(onlyIf = NativeBuild.class) + AnnotationsTransformerBuildItem transformConfigProducer() { + DotName configProducerName = DotName.createSimple("io.smallrye.config.inject.ConfigProducer"); + + return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToMethod().whenMethod(m -> { + // Apply to all producer methods declared on io.smallrye.config.inject.ConfigProducer + return m.declaringClass().name().equals(configProducerName) + && m.hasAnnotation(DotNames.PRODUCES) + && m.hasAnnotation(ConfigBuildStep.MP_CONFIG_PROPERTY_NAME); + }).thenTransform(t -> { + t.add(NativeBuildConfigCheck.class); + })); + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Fail.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Fail.java new file mode 100644 index 0000000000000..a3096528e7efb --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Fail.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.test.config.nativebuild; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Singleton +public class Fail { + + @ConfigProperty(name = "foo", defaultValue = "bar") + String value; + + // triggers init during static init of native build + void init(@Observes @Initialized(ApplicationScoped.class) Object event) { + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionFailTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionFailTest.java new file mode 100644 index 0000000000000..b3887176251a6 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionFailTest.java @@ -0,0 +1,24 @@ +package io.quarkus.arc.test.config.nativebuild; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class NativeBuildConfigInjectionFailTest { + + @RegisterExtension + static final QuarkusProdModeTest TEST = new QuarkusProdModeTest() + .setBuildNative(true) + .withApplicationRoot( + root -> root.addClass(Fail.class)) + // ImageGenerationFailureException is private + .setExpectedException(RuntimeException.class); + + @Test + void test() { + fail(); + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionOkTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionOkTest.java new file mode 100644 index 0000000000000..ce36e86a4ebae --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/NativeBuildConfigInjectionOkTest.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.test.config.nativebuild; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class NativeBuildConfigInjectionOkTest { + + @RegisterExtension + static final QuarkusProdModeTest TEST = new QuarkusProdModeTest() + .setBuildNative(true) + .withApplicationRoot( + root -> root.addClass(Ok.class)); + + @Test + void test() { + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Ok.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Ok.java new file mode 100644 index 0000000000000..c92db100e5b32 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/nativebuild/Ok.java @@ -0,0 +1,22 @@ +package io.quarkus.arc.test.config.nativebuild; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.arc.config.NativeBuildTime; + +@Singleton +public class Ok { + + @NativeBuildTime + @ConfigProperty(name = "foo", defaultValue = "bar") + String value; + + // triggers init during static init of native build + void init(@Observes @Initialized(ApplicationScoped.class) Object event) { + } +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/NativeBuildTime.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/NativeBuildTime.java new file mode 100644 index 0000000000000..132ab2bbcc00c --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/NativeBuildTime.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.config; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * A config property injected during the static initialization phase of a native image build may result in unexpected errors + * because the injected value was obtained at build time and cannot be updated at runtime. + *

+ * If it's intentional and expected then use this annotation to eliminate the false positive. + */ +@Retention(RUNTIME) +@Target({ FIELD, PARAMETER }) +public @interface NativeBuildTime { + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java index 98d572aadfc9f..c3fea0a37f816 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.util.Optional; +import java.util.Set; import jakarta.enterprise.inject.spi.Annotated; import jakarta.enterprise.inject.spi.InjectionPoint; @@ -23,6 +24,10 @@ public Object create(SyntheticCreationalContext context) { throw new IllegalStateException("No current injection point found"); } + if ((boolean) context.getParams().get("nativeBuild")) { + NativeBuildConfigCheckInterceptor.verifyCurrentImageMode(Set.of()); + } + Class interfaceType = (Class) context.getParams().get("type"); String prefix = (String) context.getParams().get("prefix"); diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheck.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheck.java new file mode 100644 index 0000000000000..6528b0de43f59 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheck.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.runtime; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.interceptor.InterceptorBinding; + +/** + * Interceptor binding for {@link NativeBuildConfigCheckInterceptor}. + */ +@InterceptorBinding +@Retention(RUNTIME) +@Target({ TYPE, METHOD }) +public @interface NativeBuildConfigCheck { + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheckInterceptor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheckInterceptor.java new file mode 100644 index 0000000000000..6c6564292d0b0 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigCheckInterceptor.java @@ -0,0 +1,113 @@ +package io.quarkus.arc.runtime; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.AnnotatedConstructor; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import io.quarkus.arc.config.NativeBuildTime; +import io.quarkus.arc.impl.InjectionPointProvider; +import io.quarkus.runtime.ImageMode; + +/** + * The goal of this interceptor is to verify the current ImageMode when a dependent config property is being injected. + */ +@Priority(jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE) +@Interceptor +@NativeBuildConfigCheck +public class NativeBuildConfigCheckInterceptor { + + private static final Logger LOG = Logger.getLogger(NativeBuildConfigCheckInterceptor.class); + + @Inject + NativeBuildConfigContext nativeBuildConfigContext; + + @AroundInvoke + Object aroundInvoke(InvocationContext context) throws Exception { + verifyCurrentImageMode(nativeBuildConfigContext.getBuildAndRunTimeFixed()); + return context.proceed(); + } + + static void verifyCurrentImageMode(Set buildAndRunTimeFixed) { + InjectionPoint injectionPoint = InjectionPointProvider.get(); + if (injectionPoint != null) { + // Skip injection points annotated with NativeBuildTime + Annotated annotated = injectionPoint.getAnnotated(); + if (annotated != null && annotated.isAnnotationPresent(NativeBuildTime.class)) { + return; + } + // Skip BUILD_AND_RUN_TIME_FIXED properties + if (!buildAndRunTimeFixed.isEmpty()) { + String propertyName = null; + for (Annotation qualifier : injectionPoint.getQualifiers()) { + if (qualifier instanceof ConfigProperty) { + propertyName = ((ConfigProperty) qualifier).name(); + } + } + if (propertyName != null && buildAndRunTimeFixed.contains(propertyName)) { + return; + } + } + } + if (ImageMode.current() == ImageMode.NATIVE_BUILD) { + StringBuilder b = new StringBuilder(); + b.append("\n\n"); + b.append("=".repeat(120)); + b.append("\nPOSSIBLE CONFIG INJECTION PROBLEM DETECTED\n"); + b.append("-".repeat(120)); + b.append("\nA config object was injected during the static initialization phase of a native image build.\n"); + b.append( + "This may result in unexpected errors.\n"); + b.append("The injected value was obtained at native image build time and cannot be updated at runtime.\n\n"); + if (injectionPoint != null) { + b.append("Injection point: "); + b.append(injectionPointToString(injectionPoint)); + b.append("\n"); + } + b.append("Solutions:\n"); + b.append("\t- If that's intentional then annotate the injected field/parameter with @"); + b.append(NativeBuildTime.class.getName()); + b.append(" to eliminate the false positive\n"); + b.append( + "\t- You can leverage the programmatic lookup to delay the injection of a config property; for example '@ConfigProperty(name = \"foo\") Instance foo'\n"); + b.append( + "\t- You can try to use a normal CDI scope to initialize the bean lazily; this may help if the is only injected but not directly used during the static initialization phase"); + b.append("\n"); + b.append("=".repeat(120)); + b.append("\n\n"); + + LOG.error(b.toString()); + throw new IllegalStateException( + "POSSIBLE CONFIG INJECTION PROBLEM DETECTED: a config object was injected during the static initialization phase of a native image build. See the error message above for more details."); + } + } + + private static String injectionPointToString(InjectionPoint injectionPoint) { + Annotated annotated = injectionPoint.getAnnotated(); + if (annotated instanceof AnnotatedField) { + AnnotatedField field = (AnnotatedField) annotated; + return field.getDeclaringType().getJavaClass().getName() + "#" + field.getJavaMember().getName(); + } else if (annotated instanceof AnnotatedParameter) { + AnnotatedParameter param = (AnnotatedParameter) annotated; + if (param.getDeclaringCallable() instanceof AnnotatedConstructor) { + return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "()"; + } else { + return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "#" + + param.getDeclaringCallable().getJavaMember().getName() + "()"; + } + } + return injectionPoint.toString(); + } +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContext.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContext.java new file mode 100644 index 0000000000000..b5377cff2b718 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContext.java @@ -0,0 +1,18 @@ +package io.quarkus.arc.runtime; + +import java.util.Set; + +import io.quarkus.arc.config.NativeBuildTime; + +/** + * @see NativeBuildTime + * @see NativeBuildConfigCheckInterceptor + */ +public interface NativeBuildConfigContext { + + /** + * + * @return the injected BUILD_AND_RUN_TIME_FIXED properties + */ + Set getBuildAndRunTimeFixed(); +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContextCreator.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContextCreator.java new file mode 100644 index 0000000000000..297ad94ec42c4 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/NativeBuildConfigContextCreator.java @@ -0,0 +1,22 @@ +package io.quarkus.arc.runtime; + +import java.util.Set; + +import io.quarkus.arc.BeanCreator; +import io.quarkus.arc.SyntheticCreationalContext; + +public class NativeBuildConfigContextCreator implements BeanCreator { + + @Override + public NativeBuildConfigContext create(SyntheticCreationalContext context) { + Set buildAndRunTimeFixed = Set.of((String[]) context.getParams().get("buildAndRunTimeFixed")); + return new NativeBuildConfigContext() { + + @Override + public Set getBuildAndRunTimeFixed() { + return buildAndRunTimeFixed; + } + }; + } + +}