Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ extraJavaModuleInfo {
}
```

You can also be more granular and ignore specific implementations while leaving the remaining active.

```
extraJavaModuleInfo {
module("org.liquibase:liquibase-core", "liquibase.core") {
...
ignoreServiceProvider(
"liquibase.change.Change", // the provider
"liquibase.change.core.LoadDataChange", "liquibase.change.core.LoadUpdateDataChange" // Ignored implementations
)
}
}
```

## Should I use real modules or automatic modules?

Only if you use _real_ modules (Jars with `module-info.class`) everywhere you can use all features of the Java Module System
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -475,10 +476,20 @@ private void addModuleInfoEntires(ModuleInfo moduleInfo, Map<String, List<String
}
for (Map.Entry<String, List<String>> entry : providers.entrySet()) {
String name = entry.getKey();
List<String> implementations = entry.getValue();
if (!moduleInfo.ignoreServiceProviders.contains(name)) {
moduleVisitor.visitProvide(name.replace('.', '/'),
implementations.stream().map(impl -> impl.replace('.', '/')).toArray(String[]::new));
Set<String> skipSet = moduleInfo.ignoreServiceProviders.get(name);
Set<String> implementations = new LinkedHashSet<>(entry.getValue());
if (skipSet != null) {
if (skipSet.isEmpty()) {
implementations.clear(); // Skip altogether
} else {
implementations.removeAll(skipSet); // Skip some
}
}
if (!implementations.isEmpty()) {
moduleVisitor.visitProvide(
name.replace('.', '/'),
implementations.stream().map(impl -> impl.replace('.', '/')).toArray(String[]::new)
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class ModuleInfo extends ModuleSpec {
final Set<String> requiresTransitive = new LinkedHashSet<>();
final Set<String> requiresStatic = new LinkedHashSet<>();
final Set<String> requiresStaticTransitive = new LinkedHashSet<>();
final Set<String> ignoreServiceProviders = new LinkedHashSet<>();
final Map<String, Set<String>> ignoreServiceProviders = new LinkedHashMap<>();
final Set<String> uses = new LinkedHashSet<>();

boolean exportAllPackages;
Expand Down Expand Up @@ -114,10 +114,11 @@ public void uses(String uses) {
}

/**
* @param ignoreServiceProvider do not transfer service provider to the 'module-info.class'
* @param provider do not transfer service provider to the 'module-info.class'
* @param implementations the array of specific implementations to skip
*/
public void ignoreServiceProvider(String ignoreServiceProvider) {
addOrThrow(this.ignoreServiceProviders, ignoreServiceProvider);
public void ignoreServiceProvider(String provider, String... implementations) {
addOrThrow(this.ignoreServiceProviders, provider, implementations);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package org.gradlex.javamodule.moduleinfo.test

import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild
import spock.lang.Specification

class IgnoreServiceProviderFunctionalTest extends Specification {

@Delegate
GradleBuild build = new GradleBuild()

def "specific implementations can be ignored"() {
settingsFile << 'rootProject.name = "test-project"'
buildFile << '''
plugins {
id("application")
id("org.gradlex.extra-java-module-info")
}

application {
mainModule.set("org.gradle.sample.app")
mainClass.set("org.gradle.sample.app.Main")
}

repositories {
mavenCentral()
}

dependencies {
implementation("org.hsqldb:hsqldb:2.7.4")
implementation("org.liquibase:liquibase-core:4.31.1")
implementation("com.mattbertolini:liquibase-slf4j:5.1.0")
implementation("org.slf4j:slf4j-simple:2.0.16")
components {
withModule("org.liquibase:liquibase-core") {
allVariants {
withDependencies {
removeIf { it.name in setOf("opencsv", "jaxb-api", "commons-collections4", "commons-text") }
}
}
}
}
}

extraJavaModuleInfo {
failOnAutomaticModules.set(true)
module("org.liquibase:liquibase-core", "liquibase.core") {
closeModule()
requiresTransitive("java.sql")
requires("java.desktop")
requires("java.logging")
requires("java.naming")
requires("java.xml")
requires("org.apache.commons.io")
requires("org.apache.commons.lang3")
requires("org.yaml.snakeyaml")
exports("liquibase")
exports("liquibase.analytics")
exports("liquibase.analytics.configuration")
exports("liquibase.configuration")
exports("liquibase.database.jvm")
exports("liquibase.exception")
exports("liquibase.logging")
exports("liquibase.logging.core")
exports("liquibase.resource")
exports("liquibase.ui")

opens("www.liquibase.org.xml.ns.dbchangelog")

uses("liquibase.change.Change")
uses("liquibase.changelog.ChangeLogHistoryService")
uses("liquibase.changelog.visitor.ValidatingVisitorGenerator")
uses("liquibase.changeset.ChangeSetService")
uses("liquibase.command.CommandStep")
uses("liquibase.command.LiquibaseCommand")
uses("liquibase.configuration.AutoloadedConfigurations")
uses("liquibase.configuration.ConfigurationValueProvider")
uses("liquibase.configuration.ConfiguredValueModifier")
uses("liquibase.database.Database")
uses("liquibase.database.DatabaseConnection")
uses("liquibase.database.LiquibaseTableNames")
uses("liquibase.database.jvm.ConnectionPatterns")
uses("liquibase.datatype.LiquibaseDataType")
uses("liquibase.diff.DiffGenerator")
uses("liquibase.diff.compare.DatabaseObjectComparator")
uses("liquibase.diff.output.changelog.ChangeGenerator")
uses("liquibase.executor.Executor")
uses("liquibase.io.OutputFileHandler")
uses("liquibase.lockservice.LockService")
uses("liquibase.logging.LogService")
uses("liquibase.logging.mdc.CustomMdcObject")
uses("liquibase.logging.mdc.MdcManager")
uses("liquibase.parser.ChangeLogParser")
uses("liquibase.parser.LiquibaseSqlParser")
uses("liquibase.parser.NamespaceDetails")
uses("liquibase.parser.SnapshotParser")
uses("liquibase.precondition.Precondition")
uses("liquibase.report.ShowSummaryGenerator")
uses("liquibase.resource.PathHandler")
uses("liquibase.serializer.ChangeLogSerializer")
uses("liquibase.serializer.SnapshotSerializer")
uses("liquibase.servicelocator.ServiceLocator")
uses("liquibase.snapshot.SnapshotGenerator")
uses("liquibase.sqlgenerator.SqlGenerator")
uses("liquibase.structure.DatabaseObject")
ignoreServiceProvider("liquibase.change.Change", "liquibase.change.core.LoadDataChange", "liquibase.change.core.LoadUpdateDataChange")
}
}
'''
file("src/main/java/module-info.java") << '''
@SuppressWarnings("opens") // the db package contains a resource file
module org.gradle.sample.app {
requires liquibase.core;
requires org.hsqldb;
//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
opens org.gradle.sample.db;
}
'''
file("src/main/resources/simplelogger.properties") << '''
org.slf4j.simpleLogger.logFile=System.out
org.slf4j.simpleLogger.cacheOutputStream=false
'''.stripIndent()
file("src/main/resources/org/gradle/sample/db/db.changelog-master.xml") << '''
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.31.xsd">

<changeSet id="1743298524674-1" author="Ihor Herasymenko">
<createTable tableName="person">
<column name="person_id" type="int"/>
<column name="person_name" type="varchar(32)"/>
</createTable>
</changeSet>

</databaseChangeLog>
'''.stripIndent()
file("src/main/java/org/gradle/sample/app/Main.java") << '''
package org.gradle.sample.app;

import liquibase.Liquibase;
import liquibase.Scope;
import liquibase.UpdateSummaryEnum;
import liquibase.analytics.configuration.AnalyticsArgs;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.ui.UIServiceEnum;
import org.hsqldb.jdbc.JDBCDataSource;

import java.sql.Connection;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
JDBCDataSource ds = new JDBCDataSource();
ds.setURL("jdbc:hsqldb:mem:test");
try (Connection connection = ds.getConnection()) {
Map<String, Object> attrs = Map.of(
// use logging instead of printing directly to stdout
Scope.Attr.ui.name(), UIServiceEnum.LOGGER.getUiServiceClass().getConstructor().newInstance(),
// do not send analytics
AnalyticsArgs.ENABLED.getKey(), false
);
Scope.child(attrs, () -> {
Liquibase liquibase = new Liquibase(
"org/gradle/sample/db/db.changelog-master.xml",
new ClassLoaderResourceAccessor(),
new JdbcConnection(connection)
);

liquibase.setShowSummary(UpdateSummaryEnum.OFF);
liquibase.update();
});
}

}
}
'''
expect:
def out = run()
out.output.contains('[main] INFO liquibase.lockservice.StandardLockService - Successfully acquired change log lock')
out.output.contains('[main] INFO liquibase.ui.LoggerUIService - Liquibase: Update has been successful. Rows affected: 1')
out.output.contains('[main] INFO liquibase.lockservice.StandardLockService - Successfully released change log lock')
!out.output.contains("Caused by: java.lang.NoClassDefFoundError: com/opencsv/exceptions/CsvMalformedLineException")
}

}