Skip to content

Commit 129f315

Browse files
authored
feat: implement ABI inclusions filter (#1591)
1 parent 3bc13f4 commit 129f315

File tree

7 files changed

+502
-1
lines changed

7 files changed

+502
-1
lines changed

src/functionalTest/groovy/com/autonomousapps/jvm/AbiExclusionsSpec.groovy

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.autonomousapps.jvm
44

5+
import com.autonomousapps.jvm.projects.AbiClassAndAnnotationInclusionsProject
56
import com.autonomousapps.jvm.projects.AbiExcludedSourceSetProject
67
import com.autonomousapps.jvm.projects.AbiExclusionsProject
8+
import com.autonomousapps.jvm.projects.AbiPackageInclusionsCombinedProject
79

810
import static com.autonomousapps.utils.Runner.build
911
import static com.google.common.truth.Truth.assertThat
@@ -39,4 +41,34 @@ final class AbiExclusionsSpec extends AbstractJvmSpec {
3941
where:
4042
gradleVersion << gradleVersions()
4143
}
44+
45+
def "can include classes and annotations from different configuration points (#gradleVersion)"() {
46+
given:
47+
def project = new AbiClassAndAnnotationInclusionsProject()
48+
gradleProject = project.gradleProject
49+
50+
when:
51+
build(gradleVersion, gradleProject.rootDir, 'buildHealth')
52+
53+
then:
54+
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth)
55+
56+
where:
57+
gradleVersion << gradleVersions()
58+
}
59+
60+
def "can include a package with excluding specific classes or annotations (#gradleVersion)"() {
61+
given:
62+
def project = new AbiPackageInclusionsCombinedProject()
63+
gradleProject = project.gradleProject
64+
65+
when:
66+
build(gradleVersion, gradleProject.rootDir, 'buildHealth')
67+
68+
then:
69+
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth)
70+
71+
where:
72+
gradleVersion << gradleVersions()
73+
}
4274
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.jvm.projects
4+
5+
import com.autonomousapps.AbstractProject
6+
import com.autonomousapps.kit.GradleProject
7+
import com.autonomousapps.kit.Source
8+
import com.autonomousapps.kit.SourceType
9+
import com.autonomousapps.model.Advice
10+
import com.autonomousapps.model.ProjectAdvice
11+
12+
import static com.autonomousapps.AdviceHelper.actualProjectAdvice
13+
import static com.autonomousapps.AdviceHelper.emptyProjectAdviceFor
14+
import static com.autonomousapps.AdviceHelper.projectAdviceForDependencies
15+
import static com.autonomousapps.AdviceHelper.projectCoordinates
16+
import static com.autonomousapps.kit.gradle.Dependency.project
17+
18+
final class AbiClassAndAnnotationInclusionsProject extends AbstractProject {
19+
20+
final GradleProject gradleProject = build()
21+
22+
Set<ProjectAdvice> actualBuildHealth() {
23+
return actualProjectAdvice(gradleProject)
24+
}
25+
26+
final Set<ProjectAdvice> expectedBuildHealth = [
27+
projectAdviceForDependencies(':consumer', [
28+
Advice.ofChange(projectCoordinates(':producer'), 'implementation', 'api'),
29+
Advice.ofChange(projectCoordinates(':other'), 'implementation', 'api'),
30+
] as Set<Advice>),
31+
emptyProjectAdviceFor(':producer'),
32+
emptyProjectAdviceFor(':other'),
33+
emptyProjectAdviceFor(':annotations'),
34+
] as Set<ProjectAdvice>
35+
36+
private GradleProject build() {
37+
return newGradleProjectBuilder()
38+
.withRootProject { r ->
39+
r.withBuildScript { bs ->
40+
bs.withGroovy("""\
41+
dependencyAnalysis {
42+
abi {
43+
exclusions {
44+
includeClasses("com\\\\.example\\\\.consumer\\\\.root\\\\..*")
45+
}
46+
}
47+
}""")
48+
}
49+
}
50+
.withSubproject('consumer') { s ->
51+
s.sources = consumerSources()
52+
s.withBuildScript { bs ->
53+
bs.plugins(javaLibrary)
54+
bs.dependencies = [
55+
project('implementation', ':producer'),
56+
project('implementation', ':other'),
57+
project('compileOnly', ':annotations'),
58+
]
59+
bs.withGroovy("""\
60+
dependencyAnalysis {
61+
abi {
62+
exclusions {
63+
includeAnnotations("com\\\\.example\\\\.annotations\\\\.PublicApi")
64+
}
65+
}
66+
}""")
67+
}
68+
}
69+
.withSubproject('producer') { s ->
70+
s.sources = producerSources()
71+
s.withBuildScript { bs ->
72+
bs.plugins(javaLibrary)
73+
}
74+
}
75+
.withSubproject('other') { s ->
76+
s.sources = otherSources()
77+
s.withBuildScript { bs ->
78+
bs.plugins(javaLibrary)
79+
}
80+
}
81+
.withSubproject('annotations') { s ->
82+
s.sources = annotationSources()
83+
s.withBuildScript { bs ->
84+
bs.plugins(javaLibrary)
85+
}
86+
}
87+
.write()
88+
}
89+
90+
private List<Source> consumerSources() {
91+
return [
92+
new Source(
93+
SourceType.JAVA, 'RootApi', 'com/example/consumer/root',
94+
"""\
95+
package com.example.consumer.root;
96+
97+
import com.example.other.OtherType;
98+
99+
public class RootApi {
100+
public OtherType other() {
101+
return new OtherType();
102+
}
103+
}""".stripIndent()
104+
),
105+
new Source(
106+
SourceType.JAVA, 'AnnotationApi', 'com/example/consumer/internal',
107+
"""\
108+
package com.example.consumer.internal;
109+
110+
import com.example.annotations.PublicApi;
111+
import com.example.producer.PublicType;
112+
113+
@PublicApi
114+
public class AnnotationApi {
115+
public PublicType type() {
116+
return new PublicType();
117+
}
118+
}""".stripIndent()
119+
),
120+
new Source(
121+
SourceType.JAVA, 'ExcludedApi', 'com/example/consumer/impl',
122+
"""\
123+
package com.example.consumer.impl;
124+
125+
import com.example.producer.PublicType;
126+
127+
public class ExcludedApi {
128+
public PublicType type() {
129+
return new PublicType();
130+
}
131+
}""".stripIndent()
132+
),
133+
]
134+
}
135+
136+
private List<Source> producerSources() {
137+
return [
138+
new Source(
139+
SourceType.JAVA, 'PublicType', 'com/example/producer',
140+
"""\
141+
package com.example.producer;
142+
143+
public class PublicType {}""".stripIndent()
144+
),
145+
]
146+
}
147+
148+
private List<Source> otherSources() {
149+
return [
150+
new Source(
151+
SourceType.JAVA, 'OtherType', 'com/example/other',
152+
"""\
153+
package com.example.other;
154+
155+
public class OtherType {}""".stripIndent()
156+
),
157+
]
158+
}
159+
160+
private List<Source> annotationSources() {
161+
return [
162+
new Source(
163+
SourceType.JAVA, 'PublicApi', 'com/example/annotations',
164+
"""\
165+
package com.example.annotations;
166+
167+
import static java.lang.annotation.ElementType.TYPE;
168+
import static java.lang.annotation.RetentionPolicy.CLASS;
169+
170+
import java.lang.annotation.Retention;
171+
import java.lang.annotation.Target;
172+
173+
@Retention(CLASS)
174+
@Target(TYPE)
175+
public @interface PublicApi {}""".stripIndent()
176+
),
177+
]
178+
}
179+
}

0 commit comments

Comments
 (0)