rewritten multi-release-jar plugin in Java

This commit is contained in:
2023-07-09 16:54:56 +08:00
parent cf91e7a28b
commit 34810e9263
6 changed files with 246 additions and 217 deletions

View File

@@ -5,12 +5,12 @@ subprojects { subproject ->
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
languageVersion = JavaLanguageVersion.of(17)
}
}
int javaVersion
if(subproject.path == ':osgi-app') {
if(subproject.path == ':osgi-app' || ':multi-release-jar') {
javaVersion = 11
} else {
javaVersion = 8

View File

@@ -1,8 +1,8 @@
woggioniMavenRepositoryUrl=https://mvn.woggioni.net/
lys.catalog.version=2023.06.11
lys.catalog.version=2023.07.08
version.myGradlePlugins=2023.06.13
version.myGradlePlugins=2023.07.09
version.gradle=7.6
version.felix.config.admin=1.9.26
version.felix=7.0.5

View File

@@ -1,6 +1,6 @@
plugins {
id 'maven-publish'
id 'groovy-gradle-plugin'
id 'java-gradle-plugin'
}
gradlePlugin {

View File

@@ -1,198 +0,0 @@
package net.woggioni.gradle.multi.release.jar
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.plugins.DslObject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.process.CommandLineArgumentProvider
import java.lang.module.ModuleDescriptor
import java.util.jar.JarFile
import java.util.stream.Collectors
import java.util.zip.ZipFile
import static org.gradle.api.attributes.LibraryElements.JAR
import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE
class MultiReleaseJarPlugin implements Plugin<Project> {
private static void jpmsModuleName(File file) {
JarFile jarFile = new JarFile(file).with {
if (it.isMultiRelease()) {
new JarFile(
file,
false,
ZipFile.OPEN_READ,
Runtime.version()
)
} else {
it
}
}
String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") }
def moduleInfoEntry = jarFile.getJarEntry("module-info.class")
moduleInfoEntry
?.with(jarFile.&getInputStream)
?.withCloseable(ModuleDescriptor.&read) ?: automaticModuleName
ModuleDescriptor.read()
}
// @Canonical
static class CompilerArgumentProvider implements CommandLineArgumentProvider {
private final Project project
private final ObjectFactory objects
@Input
final Provider<String> jpmsModuleName
private final Map<String, ListProperty<String>> patchModules
@InputFiles
@CompileClasspath
final FileCollection sourceSetOutput
// @InputFiles
// FileCollection getPatchModules() {
// return project.files(patchModules.entrySet().stream().flatMap {
// it.getValue().get().stream()
// }.toArray(String::new))
// }
CompilerArgumentProvider(
Project project,
ObjectFactory objects,
Provider<String> jpmsModuleName,
Map<String, ListProperty<String>> patchModules,
FileCollection sourceSetOutput) {
this.project = project
this.objects = objects
this.jpmsModuleName = jpmsModuleName
this.patchModules = patchModules
this.sourceSetOutput = sourceSetOutput
}
@Override
Iterable<String> asArguments() {
Map<String, ListProperty<String>> patchModules = new HashMap<>(patchModules)
String name = jpmsModuleName.get()
if(name) {
patchModules.computeIfAbsent(name) {
objects.listProperty(String.class).convention(new ArrayList<String>())
}.addAll(sourceSetOutput.collect { it.toString() })
} else {
throw new GradleException("Missing property 'jpms.module.name'")
}
String sep = System.getProperty('path.separator')
List<String> result = new ArrayList<>()
for(Map.Entry<String, ListProperty<String>> entry : patchModules.entrySet()) {
String arg = entry.getValue().get().stream().collect(Collectors.joining(sep))
result += '--patch-module'
result += "${entry.getKey()}=${arg}"
}
result
}
}
@Override
void apply(Project project) {
project.pluginManager.apply(JavaPlugin)
MultiReleaseJarPluginExtension mrjpe = new MultiReleaseJarPluginExtension(project.objects)
project.extensions.add('multiReleaseJar', mrjpe)
JavaPluginExtension javaPluginExtension = project.extensions.findByType(JavaPluginExtension.class)
JavaVersion binaryVersion = javaPluginExtension.targetCompatibility ?: javaPluginExtension.toolchain?.with {
it.languageVersion.get()
} ?: JavaVersion.current()
if(binaryVersion > JavaVersion.VERSION_1_8) {
Configuration compileClasspathConfiguration = project.configurations.compileClasspath
project.configurations.named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) {
attributes {
attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.class, JAR))
}
}
SourceSet mainSourceSet = (project.sourceSets.main as SourceSet)
JavaCompile compileJavaTask = project.tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile).get()
compileJavaTask.configure {
options.release.set(JavaVersion.VERSION_1_8.majorVersion.toInteger())
}
Jar jarTask = project.tasks.named(JavaPlugin.JAR_TASK_NAME, Jar).get()
jarTask.configure {
manifest.attributes('Multi-Release': 'true')
}
ArrayList<FileCollection> compileOutputs = new ArrayList<>()
compileOutputs << compileJavaTask.outputs.files
ArrayList<FileCollection> sourcePaths = new ArrayList<>()
sourcePaths << mainSourceSet.java.sourceDirectories
Arrays.stream(JavaVersion.values()).filter {
it > JavaVersion.VERSION_1_8 && it <= binaryVersion
}.forEach {javaVersion ->
SourceDirectorySet sourceDirectorySet =
project.objects.sourceDirectorySet("java${javaVersion.majorVersion}", javaVersion.toString())
sourceDirectorySet.with {
srcDir(new File(project.projectDir, "src/${mainSourceSet.name}/${sourceDirectorySet.name}"))
destinationDirectory.set(new File(project.buildDir, "classes/${mainSourceSet.name}/${sourceDirectorySet.name}"))
sourcePaths << sourceDirectories
}
new DslObject(mainSourceSet).getConvention().getPlugins().put(sourceDirectorySet.name, sourceDirectorySet)
mainSourceSet.getExtensions().add(SourceDirectorySet.class, sourceDirectorySet.name, sourceDirectorySet)
TaskProvider<JavaCompile> compileTask = project.tasks.register(JavaPlugin.COMPILE_JAVA_TASK_NAME + javaVersion.majorVersion, JavaCompile, { javaCompileTask ->
javaCompileTask.options.release.set(javaVersion.majorVersion.toInteger())
javaCompileTask.classpath = compileClasspathConfiguration + compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
javaCompileTask.options.compilerArgumentProviders.add(
new CompilerArgumentProvider(
project,
project.objects,
project.provider {
project.hasProperty("jpms.module.name") ?
project.property("jpms.module.name") : null
},
mrjpe.patchModules,
mainSourceSet.output
)
)
javaCompileTask.source = sourceDirectorySet
javaCompileTask.destinationDirectory.set(sourceDirectorySet.destinationDirectory)
javaCompileTask.options.annotationProcessorPath = mainSourceSet.annotationProcessorPath
javaCompileTask.modularity.inferModulePath = javaPluginExtension.modularity.inferModulePath
javaCompileTask.options.sourcepath = sourcePaths.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
javaCompileTask.javaCompiler = compileJavaTask.javaCompiler
})
compileOutputs << compileTask.get().outputs.files
sourceDirectorySet.compiledBy(compileTask, { it.getDestinationDirectory()})
jarTask.configure {
from(compileTask.get().destinationDirectory) {
into("META-INF/versions/${javaVersion.majorVersion}")
}
}
}
SourceSet testSourceSet = (project.sourceSets.test as SourceSet)
testSourceSet.compileClasspath += compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
testSourceSet.runtimeClasspath += compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
["apiElements", "runtimeElements"].forEach { String name ->
Configuration conf = project.configurations.getByName(name)
conf.attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, compileJavaTask.options.release.get())
}
}
}
}
}

View File

@@ -0,0 +1,219 @@
package net.woggioni.gradle.multi.release.jar;
import lombok.Getter;
import org.gradle.api.GradleException;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.internal.plugins.DslObject;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CompileClasspath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.AbstractCompile;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.process.CommandLineArgumentProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static org.gradle.api.attributes.LibraryElements.JAR;
import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE;
public class MultiReleaseJarPlugin implements Plugin<Project> {
// @SneakyThrows
// private static void jpmsModuleName(File file) {
// JarFile jarFile = new JarFile(file);
// if (jarFile.isMultiRelease()) {
// jarFile = new JarFile(
// file,
// false,
// ZipFile.OPEN_READ,
// Runtime.version()
// );
// }
// String automaticModuleName = Optional.ofNullable(jarFile.getManifest())
// .map(Manifest::getMainAttributes)
// .map(mainAttr -> mainAttr.getValue("Automatic-Module-Name"))
// .orElse(null);
// Optional<JarEntry> moduleInfoEntry = Optional.ofNullable(jarFile.getJarEntry("module-info.class"));
// moduleInfoEntry
// .map(jarFile::getInputStream)
// .map(ModuleDescriptor::read)
// .orElse(automaticModuleName);
// }
static class CompilerArgumentProvider implements CommandLineArgumentProvider {
@Getter(onMethod_ = {@Input})
private final Provider<String> jpmsModuleName;
@Getter(onMethod_ = {@Input})
private final MapProperty<String, List<String>> patchModules;
@Getter(onMethod_ = {@InputFiles, @CompileClasspath})
private final FileCollection sourceSetOutput;
public CompilerArgumentProvider(
Provider<String> jpmsModuleName,
MapProperty<String, List<String>> patchModules,
FileCollection sourceSetOutput) {
this.jpmsModuleName = jpmsModuleName;
this.patchModules = patchModules;
this.sourceSetOutput = sourceSetOutput;
}
@Override
public Iterable<String> asArguments() {
Map<String, List<String>> patchModules = new TreeMap<>(this.patchModules.get());
if (jpmsModuleName.isPresent()) {
String name = jpmsModuleName.get();
patchModules.computeIfAbsent(name, k -> new ArrayList<>())
.addAll(
StreamSupport.stream(sourceSetOutput.spliterator(), false)
.map(File::toString)
.collect(Collectors.toList())
);
} else {
throw new GradleException("Missing property 'jpms.module.name'");
}
String sep = System.getProperty("path.separator");
List<String> result = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : patchModules.entrySet()) {
String arg = String.join(sep, entry.getValue());
result.add("--patch-module");
result.add(entry.getKey() + "=" + arg);
}
return result;
}
}
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class);
MultiReleaseJarPluginExtension mrjpe = project.getObjects().newInstance(MultiReleaseJarPluginExtension.class);
project.getExtensions().add("multiReleaseJar", mrjpe);
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
SourceSetContainer ssc = javaPluginExtension.getSourceSets();
JavaVersion binaryVersion = Optional.ofNullable(javaPluginExtension.getTargetCompatibility())
.or(() -> Optional.ofNullable(javaPluginExtension.getToolchain())
.map(JavaToolchainSpec::getLanguageVersion)
.filter(Property::isPresent)
.map(Property::get)
.map(JavaLanguageVersion::asInt)
.map(JavaVersion::toVersion)
).orElseGet(JavaVersion::current);
if (Comparator.<JavaVersion>naturalOrder().compare(binaryVersion, JavaVersion.VERSION_1_8) > 0) {
Configuration compileClasspathConfiguration = project.getConfigurations()
.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
project.getConfigurations().named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, cfg -> {
cfg.attributes(attr -> {
attr.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, JAR));
});
});
SourceSet mainSourceSet = ssc.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
JavaCompile compileJavaTask = project.getTasks()
.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile.class, (JavaCompile t) -> {
t.getOptions().getRelease().set(Integer.parseInt(JavaVersion.VERSION_1_8.getMajorVersion()));
}).get();
Jar jarTask = project.getTasks().named(JavaPlugin.JAR_TASK_NAME, Jar.class, (Jar t) -> {
Map<String, String> attrs = new HashMap<>();
attrs.put("Multi-Release", "true");
t.getManifest().attributes(attrs);
}).get();
SourceDirectorySet java = mainSourceSet.getJava();
ArrayList<FileCollection> compileOutputs = new ArrayList<>();
compileOutputs.add(compileJavaTask.getOutputs().getFiles());
ArrayList<FileCollection> sourcePaths = new ArrayList<>();
sourcePaths.add(java.getSourceDirectories());
Comparator<JavaVersion> cmp = Comparator.naturalOrder();
Arrays.stream(JavaVersion.values())
.filter((JavaVersion jv) -> cmp.compare(jv, JavaVersion.VERSION_1_8) > 0 && cmp.compare(jv, binaryVersion) <= 0)
.forEach(javaVersion -> {
SourceDirectorySet sourceDirectorySet = project.getObjects()
.sourceDirectorySet("java" + javaVersion.getMajorVersion(), javaVersion.toString());
sourceDirectorySet.srcDir(new File(project.getProjectDir(),
"src/" + mainSourceSet.getName() + "/" + sourceDirectorySet.getName()));
sourceDirectorySet.getDestinationDirectory().set(
new File(project.getBuildDir(),
"classes/" + mainSourceSet.getName() + "/" + sourceDirectorySet.getName())
);
sourcePaths.add(sourceDirectorySet.getSourceDirectories());
new DslObject(mainSourceSet).getConvention().getPlugins().put(sourceDirectorySet.getName(), sourceDirectorySet);
mainSourceSet.getExtensions().add(SourceDirectorySet.class, sourceDirectorySet.getName(), sourceDirectorySet);
TaskProvider<JavaCompile> compileTask =
project.getTasks().register(JavaPlugin.COMPILE_JAVA_TASK_NAME + javaVersion.getMajorVersion(), JavaCompile.class,
(JavaCompile javaCompileTask) -> {
javaCompileTask.getOptions().getRelease().set(Integer.parseInt(javaVersion.getMajorVersion()));
javaCompileTask.setClasspath(compileClasspathConfiguration.plus(
compileOutputs.stream().reduce(project.getObjects().fileCollection(), FileCollection::plus)));
javaCompileTask.getOptions().getCompilerArgumentProviders().add(
new CompilerArgumentProvider(
project.provider(() -> Optional.of("jpms.module.name")
.filter(project::hasProperty)
.map(project::property)
.map(Object::toString)
.orElse(null)),
mrjpe.getPatchModules(),
mainSourceSet.getOutput()
)
);
javaCompileTask.source(sourceDirectorySet);
javaCompileTask.getDestinationDirectory().set(sourceDirectorySet.getDestinationDirectory());
javaCompileTask.getOptions().setAnnotationProcessorPath(mainSourceSet.getAnnotationProcessorPath());
javaCompileTask.getModularity().getInferModulePath().set(javaPluginExtension.getModularity().getInferModulePath());
javaCompileTask.getOptions().setSourcepath(sourcePaths.stream().reduce(project.files(), FileCollection::plus));
javaCompileTask.getJavaCompiler().set(compileJavaTask.getJavaCompiler());
});
compileOutputs.add(compileTask.get().getOutputs().getFiles());
sourceDirectorySet.compiledBy(compileTask, AbstractCompile::getDestinationDirectory);
jarTask.from(compileTask.get().getDestinationDirectory(), copySpec -> {
copySpec.into("META-INF/versions/" + javaVersion.getMajorVersion());
});
});
SourceSet testSourceSet = ssc.getByName(SourceSet.TEST_SOURCE_SET_NAME);
testSourceSet.setCompileClasspath(
testSourceSet.getCompileClasspath().plus(compileOutputs.stream().reduce(project.files(), FileCollection::plus))
);
testSourceSet.setRuntimeClasspath(
testSourceSet.getRuntimeClasspath().plus(compileOutputs.stream().reduce(project.files(), FileCollection::plus))
);
Arrays.asList("apiElements", "runtimeElements").forEach((String name) -> {
Configuration conf = project.getConfigurations().getByName(name);
conf.attributes(attrs -> {
attrs.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
compileJavaTask.getOptions().getRelease().get());
});
});
}
}
}

View File

@@ -1,33 +1,41 @@
package net.woggioni.gradle.multi.release.jar;
import org.gradle.api.Project;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Provider;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
public class MultiReleaseJarPluginExtension {
private final ObjectFactory objects;
private final Map<String, ListProperty<String>> patchModules;
private final Provider<List<String>> listCtor;
private final MapProperty<String, List<String>> patchModules;
@Inject
public MultiReleaseJarPluginExtension(ObjectFactory objects) {
this.objects = objects;
patchModules = new HashMap<>();
public MultiReleaseJarPluginExtension(Project project, ObjectFactory objects) {
patchModules = objects.mapProperty(String.class,
(Class<List<String>>) ((List<String>) new ArrayList<String>()).getClass());
listCtor = project.provider(ArrayList::new);
}
private static <T> List<T> listAdd(List<T> l, T el) {
l.add(el);
return l;
}
public void patchModule(String moduleName, Provider<String> path) {
this.patchModules
.computeIfAbsent(moduleName, key -> objects.listProperty(String.class)
.convention(new ArrayList<>()))
.add(path);
Provider<List<String>> listProvider = this.patchModules.getting(moduleName);
if(listProvider.isPresent()) {
patchModules.put(moduleName, listProvider.zip(path, MultiReleaseJarPluginExtension::listAdd));
} else {
patchModules.put(moduleName, listCtor.zip(path, MultiReleaseJarPluginExtension::listAdd));
}
}
public Map<String, ListProperty<String>> getPatchModules() {
public MapProperty<String, List<String>> getPatchModules() {
return patchModules;
}
}