@@ -33,7 +33,8 @@ def writeFile = { File base, String rel, String text ->
3333}
3434
3535def srcTree = { File base, String module, String pkgPath,
36- List<String > mainSubs, List<String > testSubs, String description ->
36+ List<String > mainSubs, List<String > testSubs, String description,
37+ String testDescription = null ->
3738 ensureDir(base, " ${ module} /src/main/java" )
3839 mainSubs. each { sub ->
3940 def fullPkg = (pkgPath + ' /' + sub). replace(' /' , ' .' )
@@ -48,8 +49,10 @@ package ${fullPkg};
4849 }
4950 ensureDir(base, " ${ module} /src/main/resources" )
5051 ensureDir(base, " ${ module} /src/test/java" )
51- // Test-package Javadoc reuses the main wording, prefixed to mark it as the tests for that code.
52- def testDesc = ' Tests for ' + lcFirst(description. replaceAll(/ \.\s *$/ , ' ' )) + ' .'
52+ // Test-package Javadoc reuses the main wording, prefixed to mark it as the tests for that code,
53+ // unless the caller supplies an explicit testDescription (e.g. acceptance-tests, whose test
54+ // package holds the functional tests themselves rather than tests *for* the main package).
55+ def testDesc = testDescription ?: (' Tests for ' + lcFirst(description. replaceAll(/ \.\s *$/ , ' ' )) + ' .' )
5356 testSubs. each { sub ->
5457 def fullPkg = (pkgPath + ' /' + sub). replace(' /' , ' .' )
5558 def dir = " ${ module} /src/test/java/${ pkgPath} /${ sub} "
@@ -75,23 +78,37 @@ def failsafeSection = """\
7578 </build>
7679"""
7780
78- /* * Builds a child-module pom.xml string (2-space indent).
79- * A dependency entry is either a String (an internal module artifactId, inheriting this project's
80- * groupId and BOM-managed version) or a Map [groupId:…, artifactId:…, scope:…] for an external
81+ /* * Builds a child-module pom.xml string (2-space indent). `prefix` is the project artifactId;
82+ * every module's artifactId is rendered as "{prefix}-{aId}" and the inherited parent as
83+ * "{prefix}-parent".
84+ * A dependency entry is either a String (an internal module's bare name, rendered as
85+ * "{prefix}-{name}" with this project's groupId and BOM-managed version) or a Map
86+ * [groupId:…, artifactId:…, scope:…, exclusions:[[groupId:…, artifactId:…], …]] for an external
8187 * dependency. Dependencies are grouped into a "<!-- TEST -->" section (scope 'test', emitted first)
8288 * and a "<!-- PROD -->" section (everything else); both headers are always present. */
83- def modulePom = { String gId, String parentAId , String ver,
89+ def modulePom = { String gId, String prefix , String ver,
8490 String aId, String desc, List deps = [], String buildSection = ' ' ->
8591 def depsSection = ' '
8692 if (deps) {
8793 def renderDep = { dep ->
8894 def dGroup = (dep instanceof Map ) ? dep. groupId : gId
89- def dArtifact = (dep instanceof Map ) ? dep. artifactId : dep
95+ def dArtifact = (dep instanceof Map ) ? dep. artifactId : " ${ prefix } - ${ dep} "
9096 def dScope = (dep instanceof Map && dep. scope) ? " \n <scope>${ dep.scope} </scope>" : ' '
97+ def dExclusions = ' '
98+ if (dep instanceof Map && dep. exclusions) {
99+ def exLines = dep. exclusions. collect { ex ->
100+ """ \
101+ <exclusion>
102+ <groupId>${ ex.groupId} </groupId>
103+ <artifactId>${ ex.artifactId} </artifactId>
104+ </exclusion>"""
105+ }. join(' \n ' )
106+ dExclusions = " \n <exclusions>\n ${ exLines} \n </exclusions>"
107+ }
91108 """ \
92109 <dependency>
93110 <groupId>${ dGroup} </groupId>
94- <artifactId>${ dArtifact} </artifactId>${ dScope}
111+ <artifactId>${ dArtifact} </artifactId>${ dScope}${ dExclusions }
95112 </dependency>"""
96113 }
97114 def isTest = { it instanceof Map && it. scope == ' test' }
@@ -115,12 +132,12 @@ def modulePom = { String gId, String parentAId, String ver,
115132
116133 <parent>
117134 <groupId>${ gId} </groupId>
118- <artifactId>${ parentAId } </artifactId>
135+ <artifactId>${ prefix } -parent </artifactId>
119136 <version>${ ver} </version>
120137 <relativePath>../parent/pom.xml</relativePath>
121138 </parent>
122139
123- <artifactId>${ aId} </artifactId>
140+ <artifactId>${ prefix } - ${ aId} </artifactId>
124141 <description>${ desc} </description>
125142${ depsSection}${ buildSection} </project>
126143"""
@@ -272,7 +289,7 @@ def configClass = { String module, String mainSub ->
272289package ${ rootPkgFq} ;
273290
274291/**
275- * Anchor class for based package class scanning.
292+ * Anchor class for basePackageClasses component scanning.
276293 */
277294public interface ${ scanName} {
278295}
@@ -314,11 +331,56 @@ def dmXml = allModules.collect { mod ->
314331 """ \
315332 <dependency>
316333 <groupId>${ groupId} </groupId>
317- <artifactId>${ mod} </artifactId>
334+ <artifactId>${ artifactId } - ${ mod} </artifactId>
318335 <version>\$ {project.version}</version>
319336 </dependency>"""
320337}. join(' \n ' )
321338
339+ // ─── Parent-pom dependency blocks (instructions 4–6) ──────────────────────────
340+ // All gated on includeSpring so a Spring-off project carries no Spring artifacts.
341+ // #4 dependencyManagement PROD: manage spring-core, excluding commons-logging.
342+ // #5 dependencies TEST: spring-boot-starter-test (test scope, all submodules), no commons-logging.
343+ // #6 dependencies PROD: spring-boot-starter-logging + jspecify (compile scope, all submodules).
344+ def springCoreDm = ! includeSpring ? ' ' : ' \n ' + [
345+ ' <dependency>' ,
346+ ' <groupId>org.springframework</groupId>' ,
347+ ' <artifactId>spring-core</artifactId>' ,
348+ ' <exclusions>' ,
349+ ' <exclusion>' ,
350+ ' <groupId>commons-logging</groupId>' ,
351+ ' <artifactId>commons-logging</artifactId>' ,
352+ ' </exclusion>' ,
353+ ' </exclusions>' ,
354+ ' </dependency>' ,
355+ ]. join(' \n ' )
356+
357+ def parentDependencies = ! includeSpring ? ' ' : ' \n ' + [
358+ ' <dependencies>' ,
359+ ' <!-- TEST -->' ,
360+ ' <dependency>' ,
361+ ' <groupId>org.springframework.boot</groupId>' ,
362+ ' <artifactId>spring-boot-starter-test</artifactId>' ,
363+ ' <scope>test</scope>' ,
364+ ' <exclusions>' ,
365+ ' <exclusion>' ,
366+ ' <groupId>commons-logging</groupId>' ,
367+ ' <artifactId>commons-logging</artifactId>' ,
368+ ' </exclusion>' ,
369+ ' </exclusions>' ,
370+ ' </dependency>' ,
371+ ' ' ,
372+ ' <!-- PROD -->' ,
373+ ' <dependency>' ,
374+ ' <groupId>org.springframework.boot</groupId>' ,
375+ ' <artifactId>spring-boot-starter-logging</artifactId>' ,
376+ ' </dependency>' ,
377+ ' <dependency>' ,
378+ ' <groupId>org.jspecify</groupId>' ,
379+ ' <artifactId>jspecify</artifactId>' ,
380+ ' </dependency>' ,
381+ ' </dependencies>' ,
382+ ]. join(' \n ' ) + ' \n '
383+
322384def parentInherit = includeSpring ? """ \
323385 <parent>
324386 <groupId>org.springframework.boot</groupId>
@@ -338,11 +400,11 @@ def parentPom = """\
338400 <modelVersion>4.0.0</modelVersion>
339401
340402${ parentInherit} <groupId>${ groupId} </groupId>
341- <artifactId>${ artifactId} </artifactId>
403+ <artifactId>${ artifactId} -parent </artifactId>
342404 <version>${ version} </version>
343405 <packaging>pom</packaging>
344406
345- <name>${ artifactId} </name>
407+ <name>${ artifactId} -parent </name>
346408
347409 <modules>
348410${ modulesXml}
@@ -365,10 +427,13 @@ ${modulesXml}
365427
366428 <dependencyManagement>
367429 <dependencies>
368- ${ dmXml}
430+ <!-- TEST -->
431+
432+ <!-- PROD -->
433+ ${ dmXml}${ springCoreDm}
369434 </dependencies>
370435 </dependencyManagement>
371-
436+ ${ parentDependencies }
372437 <build>
373438 <pluginManagement>
374439 <plugins>
@@ -537,11 +602,19 @@ configClass('common-domain', 'common/domain')
537602
538603// ─── common-testing ───────────────────────────────────────────────────────────
539604
605+ // common-testing carries spring-boot-starter-test at compile scope (it is test-support code that
606+ // other modules compile their tests against), overriding the test-scoped copy every module inherits
607+ // from the parent (#5). No commons-logging exclusion is needed here — the parent already excludes it.
608+ def commonTestingDeps = [' common-domain' ]
609+ if (includeSpring) {
610+ commonTestingDeps << [groupId : ' org.springframework.boot' , artifactId : ' spring-boot-starter-test' ,
611+ scope : ' compile' ]
612+ }
540613writeFile(projectDir, ' common-testing/pom.xml' ,
541614 modulePom(groupId, artifactId, version,
542615 ' common-testing' ,
543616 ' Testing infrastructure common to all test modules.' ,
544- [ ' common-domain ' ] ))
617+ commonTestingDeps ))
545618srcTree(projectDir, ' common-testing' , pkgPath,
546619 [' common/testing' ], [' common/testing' ],
547620 ' Test infrastructure common to all modules.' )
@@ -725,5 +798,8 @@ writeFile(projectDir, 'acceptance-tests/pom.xml',
725798 ' Functional acceptance tests for the application.' ,
726799 atDeps,
727800 failsafeSection))
801+ // The test package holds the functional tests themselves (no "Tests for " prefix); the rarely-used
802+ // main package holds supporting classes / infrastructure for those tests.
728803srcTree(projectDir, ' acceptance-tests' , pkgPath, [' at' ], [' at' ],
804+ ' Supporting classes and infrastructure for the functional acceptance tests.' ,
729805 ' Functional acceptance tests for the application.' )
0 commit comments