Add support for source set and Spring profile specific application properties - #1807
Add support for source set and Spring profile specific application properties#1807denisfalqueto wants to merge 2 commits into
Conversation
|
Thanks for the PR, and thanks for the very thorough write-up — the motivation and the design rationale made this much easier to review. I like the feature and I agree with the problem statement: targeting a (source set, profile) pair is the right model, and navigating to a section rather than adding parameters to every Resolve the output paths through
|
|
Hi @mhalbritter , thank you so much for your review. I've instructed Claude Code to implement your suggestions (didn't use auto mode, I like to review each step). I've rebased my branch and resent it, so you can review it again, when you're ready. Please, feel free to provide any further comments and I'll do it as soon as I can. |
ApplicationProperties becomes a composite: the root instance keeps
representing the main source set with the default profile, and a new
section(SourceSet, String) method returns the properties of any other
(source set, profile) pair, creating it on demand. Sections expose the
exact same API as the root and calling section(...) on a section
resolves through to the same canonical instance as calling it on the
root, so nesting is transparent rather than rejected. Profile names are
validated to reject path separators and leading dots, since they are
interpolated into a file path and customizers commonly derive them from
incoming requests.
The properties and YAML contributors now share a new base class,
AbstractApplicationPropertiesContributor, which writes one file per
non-empty section following the application[-{profile}] convention. The
output directory is resolved through the project's BuildSystem (main or
test SourceStructure) rather than a hardcoded src/{sourceSet}/resources
template, so a build system that relocates its source trees is honored
instead of silently ignored. The file for the main source set and
default profile is still always written, preserving existing behavior.
SourceSet is introduced as a small, generator-spring-local enum rather
than new public API in initializr-generator, since main/test is a
concern of this contributor rather than of the build system model.
Compliance tests gain the (source set, profile) pair as an additional
parameterized axis for both configuration file formats.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
Describe in the configuration guide how application properties can be contributed to the generated project, including how to target another source set or Spring profile through sections, with a runnable example. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
Add support for source set and Spring profile specific application properties
Disclaimer
These commits were made with aid of Claude Code's models. But they were supervised by me, in each step. I assume responsibility for the reasoning and approval of these changes, as well as providing addition information.
Motivation
The
ApplicationPropertiesabstraction introduced ininitializr-generator-springallows contributors to add properties to the generated project's configuration file. However, it is currently a single flat key-value container, and bothApplicationPropertiesContributorandApplicationYamlPropertiesContributorwrite it to a hardcoded location:src/main/resources/application.properties(or.yaml).This means an
ApplicationPropertiesCustomizerhas no way to express two very common needs of generated projects:Test-only configuration. Properties that should only apply when running tests (for example, an in-memory datasource URL, or disabling a cloud integration during tests) belong in
src/test/resources/application.properties, not in the main configuration packaged with the application.Profile-specific configuration. Spring Boot's convention of
application-{profile}.properties/application-{profile}.yamlfiles is the idiomatic way to separate configuration per environment, yet the generation model cannot produce these files today.As a result, custom Initializr instances that need either behavior must bypass the
ApplicationPropertiesabstraction entirely and write raw files through a dedicatedProjectContributor, duplicating the properties/YAML rendering logic that already exists in this module.This PR generalizes the model so that application properties can be targeted at a (source set, Spring profile) pair, while keeping the existing single-file behavior and public API fully intact.
What this change does
ApplicationPropertiesbecomes a composite of itself. The root instance — the bean that customizers already receive — represents the main source set with the default profile, exactly as before. A newsection(SourceSet, String profile)method returns theApplicationPropertiesfor any other (source set, profile) combination, creating it on demand:The two file contributors now share a new base class,
AbstractApplicationPropertiesContributor, which iterates the root and its sections and resolves the output path following the standard convention:The file for the main source set and default profile is always written, even when empty, preserving today's behavior. Files for other sections are only written when the section actually contains properties.
The reference documentation now covers the
ApplicationPropertiesCustomizerextension point, including how to target sections, with a runnable example inProjectCustomizationExamples.Design decisions
No breaking change to the public API. The signatures of
ApplicationProperties(alladd/get/contains/removeoverloads),ApplicationPropertiesCustomizer, and both contributors' constructors are untouched. Existing customizers — including the lambda-based registrations that are the documented idiom for this extension point — keep working unmodified and keep targeting the main/default file. The feature is purely additive: one new public method (section, plus a convenience overload) and one new public enum (SourceSet).Dimensions are fixed by navigation, not by method parameters. The (source set, profile) pair is fixed once when obtaining the section, and the section then exposes the exact same API as the root, keeping every typed
addoverload untouched. This mirrors the idiom already established in this code base byMavenBuild, wherebuild.profiles().id("profile-id")navigates to aMavenProfilethat exposes the regular build-configuration API scoped to that profile.Default profile is represented by
null, not the string"default". The literal"default"has its own semantics in Spring (spring.profiles.default) and using it as a sentinel could suggest aapplication-default.propertiesfile should be generated. Anullprofile unambiguously means "the profile-lessapplication.{properties|yaml}file".Sections cannot be nested. Calling
section(...)on a section throws anIllegalStateException, andsection(SourceSet.MAIN, null)on the root returns the root itself, so there is exactly one canonical instance per (source set, profile) pair.Testing
Unit tests were added for the section semantics (root identity for main/default, idempotent section retrieval, isolation between sections, rejection of nesting and of blank profile names) and for the contributor output paths (test source set, profile-specific files, combination of both, empty sections producing no file, and the main/default file still always being written).
ApplicationPropertiesComplianceTestswas extended with the source set/profile pair as an additional parameterized axis: for both the properties and YAML formats, a customizer targeting a section is verified end-to-end to produce the expected file at the expected location, while the main/default file keeps being generated.All existing tests pass unchanged, which follows from the API being untouched.
Why
SourceSetlives inio.spring.initializr.generator.buildsystemAlthough this PR only consumes
SourceSetfrom the properties support ininitializr-generator-spring, the enum was deliberately placed in theio.spring.initializr.generator.buildsystempackage ofinitializr-generator, for three reasons.First, the concept genuinely belongs to the build-system domain, not to the properties feature. The main/test split is defined by the build systems this project abstracts over: it is Maven's Standard Directory Layout (
src/main,src/test, mirrored lifecycle phases and output directories) and it is literally Gradle's source set concept, from which the name is borrowed. A type that answers "which tree of the standard directory layout does this artifact belong to" is build-system vocabulary regardless of which feature happens to ask the question.Second, the package already contains the direct sibling of this concept:
DependencyScope. That enum models the build-system-agnostic classification of dependencies (compile, runtime, test-compile, ...) in a neutral form that eachBuildSystemimplementation translates to its own representation.SourceSetdoes exactly the same for the classification of sources and resources. Placing the two classification axes side by side keeps the build abstraction's vocabulary coherent and discoverable.Third, it enables reuse without churn. Other contributors in the code base hardcode the same layout knowledge today — for example
WebFoldersContributorresolvessrc/main/resources/templatesandsrc/main/resources/staticas string literals — and language source-code contributors are inherently organized around the main/test split. Hosting the enum in the sharedbuildsystempackage lets those spots adopt it incrementally as further consumers appear.No module dependencies change:
initializr-generator-springalready depends oninitializr-generator.Closes #1806