Skip to content

Commit 3fb6a63

Browse files
authored
Allow ignoring specific service provider implementations (#172)
Resolves #171
1 parent b73a24c commit 3fb6a63

File tree

4 files changed

+220
-8
lines changed

4 files changed

+220
-8
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ extraJavaModuleInfo {
170170
}
171171
```
172172

173+
You can also be more granular and ignore specific implementations while leaving the remaining active.
174+
175+
```
176+
extraJavaModuleInfo {
177+
module("org.liquibase:liquibase-core", "liquibase.core") {
178+
...
179+
ignoreServiceProvider(
180+
"liquibase.change.Change", // the provider
181+
"liquibase.change.core.LoadDataChange", "liquibase.change.core.LoadUpdateDataChange" // Ignored implementations
182+
)
183+
}
184+
}
185+
```
186+
173187
## Should I use real modules or automatic modules?
174188

175189
Only if you use _real_ modules (Jars with `module-info.class`) everywhere you can use all features of the Java Module System

src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.util.Collections;
5555
import java.util.GregorianCalendar;
5656
import java.util.LinkedHashMap;
57+
import java.util.LinkedHashSet;
5758
import java.util.List;
5859
import java.util.Map;
5960
import java.util.Optional;
@@ -475,10 +476,20 @@ private void addModuleInfoEntires(ModuleInfo moduleInfo, Map<String, List<String
475476
}
476477
for (Map.Entry<String, List<String>> entry : providers.entrySet()) {
477478
String name = entry.getKey();
478-
List<String> implementations = entry.getValue();
479-
if (!moduleInfo.ignoreServiceProviders.contains(name)) {
480-
moduleVisitor.visitProvide(name.replace('.', '/'),
481-
implementations.stream().map(impl -> impl.replace('.', '/')).toArray(String[]::new));
479+
Set<String> skipSet = moduleInfo.ignoreServiceProviders.get(name);
480+
Set<String> implementations = new LinkedHashSet<>(entry.getValue());
481+
if (skipSet != null) {
482+
if (skipSet.isEmpty()) {
483+
implementations.clear(); // Skip altogether
484+
} else {
485+
implementations.removeAll(skipSet); // Skip some
486+
}
487+
}
488+
if (!implementations.isEmpty()) {
489+
moduleVisitor.visitProvide(
490+
name.replace('.', '/'),
491+
implementations.stream().map(impl -> impl.replace('.', '/')).toArray(String[]::new)
492+
);
482493
}
483494
}
484495
}

src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class ModuleInfo extends ModuleSpec {
3939
final Set<String> requiresTransitive = new LinkedHashSet<>();
4040
final Set<String> requiresStatic = new LinkedHashSet<>();
4141
final Set<String> requiresStaticTransitive = new LinkedHashSet<>();
42-
final Set<String> ignoreServiceProviders = new LinkedHashSet<>();
42+
final Map<String, Set<String>> ignoreServiceProviders = new LinkedHashMap<>();
4343
final Set<String> uses = new LinkedHashSet<>();
4444

4545
boolean exportAllPackages;
@@ -114,10 +114,11 @@ public void uses(String uses) {
114114
}
115115

116116
/**
117-
* @param ignoreServiceProvider do not transfer service provider to the 'module-info.class'
117+
* @param provider do not transfer service provider to the 'module-info.class'
118+
* @param implementations the array of specific implementations to skip
118119
*/
119-
public void ignoreServiceProvider(String ignoreServiceProvider) {
120-
addOrThrow(this.ignoreServiceProviders, ignoreServiceProvider);
120+
public void ignoreServiceProvider(String provider, String... implementations) {
121+
addOrThrow(this.ignoreServiceProviders, provider, implementations);
121122
}
122123

123124
/**
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.gradlex.javamodule.moduleinfo.test
2+
3+
import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
4+
import spock.lang.Specification
5+
6+
class IgnoreServiceProviderFunctionalTest extends Specification {
7+
8+
@Delegate
9+
GradleBuild build = new GradleBuild()
10+
11+
def "specific implementations can be ignored"() {
12+
settingsFile << 'rootProject.name = "test-project"'
13+
buildFile << '''
14+
plugins {
15+
id("application")
16+
id("org.gradlex.extra-java-module-info")
17+
}
18+
19+
application {
20+
mainModule.set("org.gradle.sample.app")
21+
mainClass.set("org.gradle.sample.app.Main")
22+
}
23+
24+
repositories {
25+
mavenCentral()
26+
}
27+
28+
dependencies {
29+
implementation("org.hsqldb:hsqldb:2.7.4")
30+
implementation("org.liquibase:liquibase-core:4.31.1")
31+
implementation("com.mattbertolini:liquibase-slf4j:5.1.0")
32+
implementation("org.slf4j:slf4j-simple:2.0.16")
33+
components {
34+
withModule("org.liquibase:liquibase-core") {
35+
allVariants {
36+
withDependencies {
37+
removeIf { it.name in setOf("opencsv", "jaxb-api", "commons-collections4", "commons-text") }
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
extraJavaModuleInfo {
45+
failOnAutomaticModules.set(true)
46+
module("org.liquibase:liquibase-core", "liquibase.core") {
47+
closeModule()
48+
requiresTransitive("java.sql")
49+
requires("java.desktop")
50+
requires("java.logging")
51+
requires("java.naming")
52+
requires("java.xml")
53+
requires("org.apache.commons.io")
54+
requires("org.apache.commons.lang3")
55+
requires("org.yaml.snakeyaml")
56+
exports("liquibase")
57+
exports("liquibase.analytics")
58+
exports("liquibase.analytics.configuration")
59+
exports("liquibase.configuration")
60+
exports("liquibase.database.jvm")
61+
exports("liquibase.exception")
62+
exports("liquibase.logging")
63+
exports("liquibase.logging.core")
64+
exports("liquibase.resource")
65+
exports("liquibase.ui")
66+
67+
opens("www.liquibase.org.xml.ns.dbchangelog")
68+
69+
uses("liquibase.change.Change")
70+
uses("liquibase.changelog.ChangeLogHistoryService")
71+
uses("liquibase.changelog.visitor.ValidatingVisitorGenerator")
72+
uses("liquibase.changeset.ChangeSetService")
73+
uses("liquibase.command.CommandStep")
74+
uses("liquibase.command.LiquibaseCommand")
75+
uses("liquibase.configuration.AutoloadedConfigurations")
76+
uses("liquibase.configuration.ConfigurationValueProvider")
77+
uses("liquibase.configuration.ConfiguredValueModifier")
78+
uses("liquibase.database.Database")
79+
uses("liquibase.database.DatabaseConnection")
80+
uses("liquibase.database.LiquibaseTableNames")
81+
uses("liquibase.database.jvm.ConnectionPatterns")
82+
uses("liquibase.datatype.LiquibaseDataType")
83+
uses("liquibase.diff.DiffGenerator")
84+
uses("liquibase.diff.compare.DatabaseObjectComparator")
85+
uses("liquibase.diff.output.changelog.ChangeGenerator")
86+
uses("liquibase.executor.Executor")
87+
uses("liquibase.io.OutputFileHandler")
88+
uses("liquibase.lockservice.LockService")
89+
uses("liquibase.logging.LogService")
90+
uses("liquibase.logging.mdc.CustomMdcObject")
91+
uses("liquibase.logging.mdc.MdcManager")
92+
uses("liquibase.parser.ChangeLogParser")
93+
uses("liquibase.parser.LiquibaseSqlParser")
94+
uses("liquibase.parser.NamespaceDetails")
95+
uses("liquibase.parser.SnapshotParser")
96+
uses("liquibase.precondition.Precondition")
97+
uses("liquibase.report.ShowSummaryGenerator")
98+
uses("liquibase.resource.PathHandler")
99+
uses("liquibase.serializer.ChangeLogSerializer")
100+
uses("liquibase.serializer.SnapshotSerializer")
101+
uses("liquibase.servicelocator.ServiceLocator")
102+
uses("liquibase.snapshot.SnapshotGenerator")
103+
uses("liquibase.sqlgenerator.SqlGenerator")
104+
uses("liquibase.structure.DatabaseObject")
105+
ignoreServiceProvider("liquibase.change.Change", "liquibase.change.core.LoadDataChange", "liquibase.change.core.LoadUpdateDataChange")
106+
}
107+
}
108+
'''
109+
file("src/main/java/module-info.java") << '''
110+
@SuppressWarnings("opens") // the db package contains a resource file
111+
module org.gradle.sample.app {
112+
requires liquibase.core;
113+
requires org.hsqldb;
114+
//opens org.example.db to liquibase.core; -- this is too strict, the package needs to be "opened" globally so that liquibase's resource scan mechanism can detect resource files there
115+
opens org.gradle.sample.db;
116+
}
117+
'''
118+
file("src/main/resources/simplelogger.properties") << '''
119+
org.slf4j.simpleLogger.logFile=System.out
120+
org.slf4j.simpleLogger.cacheOutputStream=false
121+
'''.stripIndent()
122+
file("src/main/resources/org/gradle/sample/db/db.changelog-master.xml") << '''
123+
<databaseChangeLog
124+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
125+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
126+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.31.xsd">
127+
128+
<changeSet id="1743298524674-1" author="Ihor Herasymenko">
129+
<createTable tableName="person">
130+
<column name="person_id" type="int"/>
131+
<column name="person_name" type="varchar(32)"/>
132+
</createTable>
133+
</changeSet>
134+
135+
</databaseChangeLog>
136+
'''.stripIndent()
137+
file("src/main/java/org/gradle/sample/app/Main.java") << '''
138+
package org.gradle.sample.app;
139+
140+
import liquibase.Liquibase;
141+
import liquibase.Scope;
142+
import liquibase.UpdateSummaryEnum;
143+
import liquibase.analytics.configuration.AnalyticsArgs;
144+
import liquibase.database.jvm.JdbcConnection;
145+
import liquibase.resource.ClassLoaderResourceAccessor;
146+
import liquibase.ui.UIServiceEnum;
147+
import org.hsqldb.jdbc.JDBCDataSource;
148+
149+
import java.sql.Connection;
150+
import java.util.Map;
151+
152+
public class Main {
153+
public static void main(String[] args) throws Exception {
154+
JDBCDataSource ds = new JDBCDataSource();
155+
ds.setURL("jdbc:hsqldb:mem:test");
156+
try (Connection connection = ds.getConnection()) {
157+
Map<String, Object> attrs = Map.of(
158+
// use logging instead of printing directly to stdout
159+
Scope.Attr.ui.name(), UIServiceEnum.LOGGER.getUiServiceClass().getConstructor().newInstance(),
160+
// do not send analytics
161+
AnalyticsArgs.ENABLED.getKey(), false
162+
);
163+
Scope.child(attrs, () -> {
164+
Liquibase liquibase = new Liquibase(
165+
"org/gradle/sample/db/db.changelog-master.xml",
166+
new ClassLoaderResourceAccessor(),
167+
new JdbcConnection(connection)
168+
);
169+
170+
liquibase.setShowSummary(UpdateSummaryEnum.OFF);
171+
liquibase.update();
172+
});
173+
}
174+
175+
}
176+
}
177+
'''
178+
expect:
179+
def out = run()
180+
out.output.contains('[main] INFO liquibase.lockservice.StandardLockService - Successfully acquired change log lock')
181+
out.output.contains('[main] INFO liquibase.ui.LoggerUIService - Liquibase: Update has been successful. Rows affected: 1')
182+
out.output.contains('[main] INFO liquibase.lockservice.StandardLockService - Successfully released change log lock')
183+
!out.output.contains("Caused by: java.lang.NoClassDefFoundError: com/opencsv/exceptions/CsvMalformedLineException")
184+
}
185+
186+
}

0 commit comments

Comments
 (0)