Skip to content

Commit

Permalink
implement #101: Support omitting specific entries from the mergedModu…
Browse files Browse the repository at this point in the history
…le's module-info
  • Loading branch information
siordache committed Dec 18, 2019
1 parent 383a544 commit 2ad651d
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 35 deletions.
38 changes: 33 additions & 5 deletions doc/user_guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,32 @@ The plugin automatically exports all packages found in the merged module, theref
If a `mergedModule` block appears in yout build script, the generated module descriptor will contain the clauses specified in this block.
Otherwise, the module descriptor is created using the algorithm implemented by the `suggestMergedModuleInfo` task.

In many cases the "suggested" descriptor is just the right one for your merged module, so you don't need to provide a `mergedModule` block.
In some other cases the "suggested" descriptor is _almost_ right, in the sense that it only misses one or a few clauses.
[purple]##**additive**##:: In many cases the suggested descriptor is just the right one for your merged module, so you don't need to provide a `mergedModule` block.
In some other cases the suggested descriptor is _almost_ right, in the sense that it only misses one or a few clauses.
In these cases you are allowed to configure only the missing clauses in the `mergedModule` block and instruct the plugin
to add them to the suggested descriptor by setting the attribute `additive` to true.
(The default value of `additive` is false.)
to add them to the suggested descriptor by setting the attribute `additive` to true. +
_defaultValue_: `false`

There are also situations where the suggested descriptor contains some unwanted clauses.
The plugin provides a few methods that allow excluding these clauses:

[maroon]##excludeRequires##(String... [purple]##modules##):: Instructs the plugin to not generate `requires` clauses for the specified _modules_. +
_usage example_: `excludeRequires 'java.rmi', 'java-compiler'`

[maroon]##excludeUses##(String... [purple]##services##):: Instructs the plugin to not generate `uses` clauses for the specified _services_. +
_usage example_: `excludeUses 'java.nio.file.spi.FileSystemProvider'`

[maroon]##excludeProvides##(Map [purple]##constraints##):: Instructs the plugin to not generate `provides` clauses that match the specified _constraints_. +
The following keys are allowed in the _constraints_ map: +
[purple]##**service**##: the qualified name of the service +
[purple]##**implementation**##: the qualified name of the implementation class +
[purple]##**servicePattern**##: the regular expression to be matched by the qualified name of the service +
[purple]##**implementationPattern**##: the regular expression to be matched by the qualified name of the implementation class +
_usage example_: `excludeProvides servicePattern: 'org.codehaus.stax2.*'`


By calling one of the above methods you automatically enable the additive mode.
This means that is no longer necessary to explicitly set the `additive` property to `true`.


_Usage example_
Expand All @@ -198,11 +219,14 @@ _Usage example_
jlink {
...
mergedModule {
additive = true
additive = true // redundant, because excludeXXX() methods are also present
requires 'java.desktop'
requires transitive 'java.sql'
uses 'java.sql.Driver'
provides 'java.sql.Driver' with 'org.hsqldb.jdbc.JDBCDriver'
excludeRequires 'java.compiler', 'java.rmi'
excludeUses 'org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory'
excludeProvides servicePattern: 'org.apache.logging.*'
}
...
}
Expand All @@ -214,10 +238,14 @@ jlink {
jlink {
...
mergedModule {
additive = true // redundant, because excludeXXX() methods are also present
requires("java.desktop")
requiresTransitive("java.sql")
uses("java.sql.Driver")
provides("java.sql.Driver").with("org.hsqldb.jdbc.JDBCDriver")
excludeRequires("java.compiler", "java.rmi")
excludeUses("org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory")
excludeProvides(mapOf("servicePattern" to "org.apache.logging.*"))
}
...
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
10 changes: 10 additions & 0 deletions src/main/groovy/org/beryx/jlink/SuggestMergedModuleInfoTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class SuggestMergedModuleInfoTask extends BaseTask {
@Input
final Property<ModuleInfo.Language> language

private boolean useConstraints = false

SuggestMergedModuleInfoTask() {
dependsOn(JlinkPlugin.TASK_NAME_PREPARE_MERGED_JARS_DIR)
description = 'Suggests a module declaration for the merged module'
Expand All @@ -88,6 +90,9 @@ class SuggestMergedModuleInfoTask extends BaseTask {
taskData.configuration = project.configurations.getByName(configuration)
taskData.useJdeps = useJdeps.get()
taskData.language = language.get()
if(useConstraints) {
taskData.additiveConstraints = extension.mergedModuleInfo.get().additiveConstraints
}
taskData.mergedJarsDir = mergedJarsDir.asFile
taskData.jlinkJarsDirPath = PathUtil.getJlinkJarsDirPath(taskData.jlinkBasePath)
taskData.tmpJarsDirPath = PathUtil.getTmpJarsDirPath(taskData.jlinkBasePath)
Expand Down Expand Up @@ -115,4 +120,9 @@ class SuggestMergedModuleInfoTask extends BaseTask {
throw new GradleException("Unknown value for option 'language': $language. Accepted values: ${ModuleInfo.Language.values()*.name().join(' / ').toLowerCase()}.")
}
}

@Option(option = 'useConstraints', description = "Specifies that the 'excludeXXX' constraints configured in mergedModule should be taken into account.")
void setUseConstraints(boolean useConstraints) {
this.useConstraints = useConstraints
}
}
51 changes: 48 additions & 3 deletions src/main/groovy/org/beryx/jlink/data/ModuleInfo.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package org.beryx.jlink.data

import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.MapConstructor
import groovy.transform.ToString

import java.util.stream.Collectors
Expand All @@ -26,14 +27,58 @@ import java.util.stream.Collectors
class ModuleInfo implements Serializable {
boolean enabled
boolean additive
List<RequiresBuilder> requiresBuilders = []
List<UsesBuilder> usesBuilders = []
List<ProvidesBuilder> providesBuilders = []
final AdditiveConstraints additiveConstraints = new AdditiveConstraints()
final List<RequiresBuilder> requiresBuilders = []
final List<UsesBuilder> usesBuilders = []
final List<ProvidesBuilder> providesBuilders = []

private boolean afterRequiresTransitive = false

static enum Language {JAVA, GROOVY, KOTLIN}

static class AdditiveConstraints implements Serializable {
boolean enabled = false
final Set<String> excludedRequires = []
final Set<String> excludedUses = []
final List<ProvidesConstraint> excludedProvidesConstraints = []
}

void excludeRequires(String... modules) {
additiveConstraints.enabled = true
additiveConstraints.excludedRequires.addAll(modules)
}

void excludeUses(String... services) {
additiveConstraints.enabled = true
additiveConstraints.excludedUses.addAll(services)
}

void excludeProvides(Map constraints) {
additiveConstraints.enabled = true
additiveConstraints.excludedProvidesConstraints.add(new ProvidesConstraint(constraints))
}

@MapConstructor
@ToString
static class ProvidesConstraint implements Serializable{
String service
String implementation
String servicePattern
String implementationPattern

boolean matches(String svc, String impl) {
if(service && svc != service) return false
if(implementation && impl != implementation) return false
if(servicePattern && !(svc ==~ servicePattern)) return false
if(implementationPattern && !(impl ==~ implementationPattern)) return false
true
}
}

boolean shouldUseSuggestions() {
!enabled || additive || additiveConstraints.enabled
}

@EqualsAndHashCode
static class RequiresBuilder implements Serializable {
final String module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SuggestMergedModuleInfoTaskData extends BaseTaskData {
String javaHome
Configuration configuration
ModuleInfo.Language language
ModuleInfo.AdditiveConstraints additiveConstraints
JdepsUsage useJdeps
String jlinkJarsDirPath
String tmpJarsDirPath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ class CreateMergedModuleTaskImpl extends BaseTaskImpl<CreateMergedModuleTaskData
packages.each {
modInfoJava << " exports $it;\n"
}
if(td.mergedModuleInfo.additive || !td.mergedModuleInfo.enabled) {
if(td.mergedModuleInfo.shouldUseSuggestions()) {
def builder = new SuggestedMergedModuleInfoBuilder(
project: project,
mergedJarsDir: td.mergedJarsDir,
javaHome: td.javaHome,
forceMergedJarPrefixes: td.forceMergedJarPrefixes,
extraDependenciesPrefixes: td.extraDependenciesPrefixes,
configuration: td.configuration
configuration: td.configuration,
constraints: td.mergedModuleInfo.additiveConstraints
)
modInfoJava << builder.moduleInfo.toString(4)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class SuggestMergedModuleInfoTaskImpl extends BaseTaskImpl<SuggestMergedModuleIn
javaHome: td.javaHome,
forceMergedJarPrefixes: td.forceMergedJarPrefixes,
extraDependenciesPrefixes: td.extraDependenciesPrefixes,
configuration: td.configuration
configuration: td.configuration,
constraints: td.additiveConstraints
)
println "mergedModule {\n${builder.moduleInfo.toString(4, td.language)}\n}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.beryx.jlink.util

import groovy.transform.Canonical
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
Expand All @@ -37,6 +38,7 @@ class SuggestedMergedModuleInfoBuilder {
List<String> forceMergedJarPrefixes
List<String> extraDependenciesPrefixes
Configuration configuration
AdditiveConstraints constraints

ModuleInfo getModuleInfo() {
def info = new ModuleInfo()
Expand All @@ -49,13 +51,53 @@ class SuggestedMergedModuleInfoBuilder {
Set<UsesBuilder> getUsesBuilders() {
def scanner = new ServiceLoaderUseScanner()
scanner.scan(mergedJarsDir)
scanner.builders
def builders = scanner.builders
if(constraints?.excludedUses) {
builders.removeAll { it.service in constraints.excludedUses }
}
builders
}

Set<ProvidesBuilder> getProvidesBuilders() {
def scanner = new ServiceProviderScanner()
scanner.scan(mergedJarsDir)
scanner.builders
def builders = scanner.builders

if(constraints?.excludedProvidesConstraints) {
def providesSet = toSingleProvidesSet(builders, constraints.excludedProvidesConstraints)
builders = toProvidesBuilders(providesSet)
}
builders
}

@Canonical
private static class SingleProvides {
final String service
final String implementation
}

private Set<SingleProvides> toSingleProvidesSet(
Set<ProvidesBuilder> builders,
List<ProvidesConstraint> providesConstraints) {
Set<SingleProvides> providesSet = []
builders.each { builder ->
builder.implementations.each { implementation ->
if(providesConstraints.every { !it.matches(builder.service, implementation)}) {
providesSet << new SingleProvides(builder.service, implementation)
}
}
}
providesSet
}

private Set<ProvidesBuilder> toProvidesBuilders(Set<SingleProvides> singleProvidesSet) {
Map<String, ProvidesBuilder> builders = [:]
singleProvidesSet.each { provides ->
builders.compute(provides.service, { svc, builder ->
(builder ?: new ProvidesBuilder(svc)).with(provides.implementation)
})
};
new HashSet(builders.values())
}

@CompileDynamic
Expand All @@ -79,6 +121,9 @@ class SuggestedMergedModuleInfoBuilder {
builders << new RequiresBuilder(moduleName)
}
}
if(constraints?.excludedRequires) {
builders.removeAll { it.module in constraints.excludedRequires }
}
builders
}
}
Loading

0 comments on commit 2ad651d

Please sign in to comment.