Add symbol processor for configuration metadata that shares the implementation of the annotation processor - #51930
Open
salatmaster wants to merge 4 commits into
Open
salatmaster wants to merge 4 commits into
salatmaster wants to merge 4 commits into
Conversation
Configuration metadata could previously only be generated from the javax.lang.model API, which prevented the code that generates it from being used by anything other than a Java annotation processor. This commit introduces a small model of the declarations that the processor inspects, made of types, methods, variables, type references and annotations. The code that resolves the properties of a type and that collects, merges and writes the metadata now works with that model only and has moved from ConfigurationMetadataAnnotationProcessor to ConfigurationMetadataGenerator. The annotation processor adapts the javax.lang.model API to the model and delegates to the generator. The metadata that the annotation processor generates is unchanged. See spring-projectsgh-28046 Signed-off-by: Areg Iazychian <abstractcoderx@gmail.com>
Add spring-boot-configuration-symbol-processor, a Kotlin Symbol Processing (KSP) processor that writes the configuration metadata of Kotlin types without having to rely on kapt. The processor is a thin layer that adapts the symbols of KSP to the model of the configuration processor and delegates to the ConfigurationMetadataGenerator that the annotation processor uses. A Kotlin class is exposed the way that Java sees it, with each property being a field with a getter and, when it is mutable, a setter, so that the same rules apply to both languages and the generated metadata is the same as when the annotation processor runs through kapt. KSP does not expose initializers, nor does it give access to the resources of the module and of its classpath. As a result, default values can only be described with @DefaultValue, the location of additional metadata has to be provided with a processor option, and the metadata of a @ConfigurationPropertiesSource type of another module cannot be reused. See spring-projectsgh-28046 Signed-off-by: Areg Iazychian <abstractcoderx@gmail.com>
Add the symbol processor to spring-boot-dependencies so that a project can declare it without having to specify its version. See spring-projectsgh-28046 Signed-off-by: Areg Iazychian <abstractcoderx@gmail.com>
Describe how to apply the symbol processor, how Kotlin types are described, how to contribute additional metadata, and its limitations. Note that a module has to apply either the annotation processor or the symbol processor, as both write the same metadata file. See spring-projectsgh-28046 Signed-off-by: Areg Iazychian <abstractcoderx@gmail.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a second take on KSP support for the configuration processor. It replaces #51375, which was declined because it duplicated the logic of the annotation processor in Kotlin, and it follows the direction suggested there: a thin layer that deals with the Kotlin specifics and delegates to an implementation written in Java that is shared with the annotation processor.
See gh-28046
Approach
The first commit introduces a small language-neutral model in
spring-boot-configuration-processor(org.springframework.boot.configurationprocessor.model):TypeDeclaration,MethodDeclaration,VariableDeclaration,TypeReferenceandAnnotationReference, plusProcessingContextandProcessingRoundfor what the processing tool provides (type lookup, messages, options, resources, annotated declarations).Everything that decides what ends up in the metadata now works with that model only:
PropertyDescriptorResolver, thePropertyDescriptorhierarchy,TypeElementMembers,MetadataGenerationEnvironment,MetadataCollectorsandMetadataStore. The orchestration that was inConfigurationMetadataAnnotationProcessorhas moved toConfigurationMetadataGenerator. The annotation processor is left with adaptingjavax.lang.modelto the model (package-privateJava*classes, withTypeUtilsand the field values parser behind them) and delegating to the generator. Most of the diff in the existing classes is the substitution ofjavax.lang.modeltypes with their model counterparts.The second commit adds
spring-boot-configuration-symbol-processor. It only adapts KSP symbols to the model and callsConfigurationMetadataGenerator; it has no knowledge of what configuration metadata is. A Kotlin class is exposed the way Java sees it: a property is a field with a getter and, when it is mutable, a setter. That keeps the binding rules in one place and makes the output the same as what the annotation processor produces through kapt. What is left in Kotlin is about 400 lines of code: the adapters, the mapping of Kotlin type names to their JVM names, KDoc lookup and nullability.The annotation processor is unchanged in behavior
*MetadataGenerationTestsare untouched. The unit tests that build descriptors fromjavax.lang.modelelements now build them from the model, with the same expectations.spring-boot-sessionwhereSessionsEndpointandReactiveSessionsEndpointshare thesessionsid and the type that the group is attributed to depends on the order in which the two are processed.maingives either result from one build to the next, and both versions give the same result on a clean output directory.The symbol processor
@DefaultValue,@Name, nested groups, generics inherited from a superclass, deprecations, an endpoint and a@Beanmethod. Leaving aside the additional metadata, whose location I had only configured for the KSP build, the two files differ in two places only, both in favor of KSP: the properties of a data class whose constructor parameters all have defaults are described (kapt sees several constructors and describes none), and descriptions are picked up from the@propertytags of the class KDoc.Limitations
These come from KSP rather than from the design, and are documented in the new appendix section:
@DefaultValueor additional metadata. A parameter that has a Kotlin default gets no default value rather than a wrong one.additional-spring-configuration-metadata.jsonhas to be passed with the existingadditionalMetadataLocationsoption, and the metadata of a@ConfigurationPropertiesSourcetype of another module cannot be reused.Things I would like your opinion on
ProcessingContext,ProcessingRoundandConfigurationMetadataGeneratorare public because the symbol processor is a separate artifact. If you would rather not have that API, the KSP adapter could live inspring-boot-configuration-processoritself, at the cost of that jar containing Kotlin classes and compiling against the KSP API.javax.lang.modelon top of KSP so that the annotation processor runs unmodified. It leaves the existing code alone, but a partial implementation of such a large API fails at runtime when the processor starts using a new method, whereas adding a method to the model breaks the compilation of both adapters.model,Declaration, theJava*prefix of the adapters) is easy to change.I'm aware that this touches most of the annotation processor, so I'm happy to split it differently or to rework it.