diff --git a/gradle.properties b/gradle.properties index 0934819..9ce2e79 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ woggioniMavenRepositoryUrl=https://mvn.woggioni.net/ -lys.catalog.version=2023.07.16 +lys.catalog.version=2023.10.01 -version.myGradlePlugins=2023.07.16 +version.myGradlePlugins=2023.10.05 version.gradle=7.6 version.felix.config.admin=1.9.26 version.felix=7.0.5 diff --git a/native-image/build.gradle b/native-image/build.gradle new file mode 100644 index 0000000..53f4771 --- /dev/null +++ b/native-image/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'java-gradle-plugin' +} + + +gradlePlugin { + plugins { + create("NativeImagePlugin") { + id = "net.woggioni.gradle.native-image" + implementationClass = "net.woggioni.gradle.nativeimage.NativeImagePlugin" + } + } +} diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java new file mode 100644 index 0000000..c68572b --- /dev/null +++ b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java @@ -0,0 +1,62 @@ +package net.woggioni.gradle.nativeimage; + +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.bundling.Jar; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME; +import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_TASK_GROUP; + +public abstract class NativeImageConfigurationTask extends JavaExec { + + @OutputDirectory + public abstract DirectoryProperty getConfigurationDir(); + + public NativeImageConfigurationTask() { + setGroup(NATIVE_IMAGE_TASK_GROUP); + setDescription("Run the application with the native-image-agent " + + "to create a configuration for native image creation"); + ProjectLayout layout = getProject().getLayout(); + TaskContainer taskContainer = getProject().getTasks(); + JavaApplication javaApplication = getProject().getExtensions().findByType(JavaApplication.class); + if(!Objects.isNull(javaApplication)) { + getMainClass().convention(javaApplication.getMainClass()); + getMainModule().convention(javaApplication.getMainModule()); + } + getConfigurationDir().convention(layout.getProjectDirectory() + .dir(NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME)); + ConfigurationContainer cc = getProject().getConfigurations(); + Configuration runtimeClasspath = cc.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + setClasspath(runtimeClasspath); + Provider jarTaskProvider = taskContainer.named(JavaPlugin.JAR_TASK_NAME, Jar.class); + setClasspath( + getProject().files( + jarTaskProvider, + runtimeClasspath + ) + ); + List jvmArgs = new ArrayList<>(); + jvmArgs.add("-agentlib:native-image-agent=config-output-dir=" + getConfigurationDir().get()); + for(String jvmArg : Optional.ofNullable(javaApplication.getApplicationDefaultJvmArgs()).orElse(Collections.emptyList())) { + jvmArgs.add(jvmArg); + } + jvmArgs(jvmArgs); + } +} diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java new file mode 100644 index 0000000..15cfee4 --- /dev/null +++ b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java @@ -0,0 +1,54 @@ +package net.woggioni.gradle.nativeimage; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaLibraryPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.jvm.tasks.Jar; + +import java.util.Optional; + +public class NativeImagePlugin implements Plugin { + + public static final String NATIVE_IMAGE_TASK_NAME = "nativeImage"; + public static final String CONFIGURE_NATIVE_IMAGE_TASK_NAME = "configureNativeImage"; + public static final String NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME = "native-image"; + + public static final String NATIVE_IMAGE_TASK_GROUP = "native image"; + + @Override + public void apply(Project project) { + project.getPluginManager().apply(JavaLibraryPlugin.class); + ProjectLayout layout = project.getLayout(); + ExtensionContainer extensionContainer = project.getExtensions(); + JavaApplication javaApplicationExtension = + Optional.ofNullable(extensionContainer.findByType(JavaApplication.class)) + .orElseGet(() -> extensionContainer.create("application", JavaApplication.class)); + + TaskContainer tasks = project.getTasks(); + Provider jarTaskProvider = tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, jar -> { + jar.from(layout.getProjectDirectory().dir(NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME), copySpec -> { + copySpec.into( + String.format("META-INF/native-image/%s/%s/", + project.getName(), + project.getGroup() + ) + ); + }); + }); + tasks.register(CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask.class); + tasks.register(NATIVE_IMAGE_TASK_NAME, NativeImageTask.class, nativeImageTask -> { + ConfigurationContainer configurations = project.getConfigurations(); + FileCollection classpath = project.files(jarTaskProvider, + configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + nativeImageTask.getClasspath().set(classpath); + }); + } +} diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java new file mode 100644 index 0000000..abe3a0c --- /dev/null +++ b/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java @@ -0,0 +1,127 @@ +package net.woggioni.gradle.nativeimage; + +import org.gradle.api.Project; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Exec; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.internal.jvm.JavaModuleDetector; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.process.CommandLineArgumentProvider; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static java.util.Optional.ofNullable; +import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_TASK_GROUP; + +public abstract class NativeImageTask extends Exec { + + @Classpath + public abstract Property getClasspath(); + + @InputDirectory + public abstract DirectoryProperty getGraalVmHome(); + + @Input + public abstract Property getUseJpms(); + @Input + public abstract Property getEnableFallback(); + + @Input + public abstract Property getMainClass(); + + @Input + @Optional + public abstract Property getMainModule(); + + @Inject + protected abstract JavaModuleDetector getJavaModuleDetector(); + + @OutputFile + protected abstract RegularFileProperty getOutputFile(); + private final Logger logger; + public NativeImageTask() { + Project project = getProject(); + logger = project.getLogger(); + setGroup(NATIVE_IMAGE_TASK_GROUP); + setDescription("Create a native image of the application using GraalVM"); + getUseJpms().convention(false); + getEnableFallback().convention(false); + ExtensionContainer ext = project.getExtensions(); + JavaApplication javaApplication = ext.findByType(JavaApplication.class); + if(!Objects.isNull(javaApplication)) { + getMainClass().convention(javaApplication.getMainClass()); + getMainModule().convention(javaApplication.getMainModule()); + } + getClasspath().convention(project.files()); + ProjectLayout layout = project.getLayout(); + JavaToolchainService javaToolchainService = ext.findByType(JavaToolchainService.class); + JavaPluginExtension javaPluginExtension = ext.findByType(JavaPluginExtension.class); + Provider graalHomeDirectoryProvider = ofNullable(javaPluginExtension.getToolchain()).map(javaToolchainSpec -> + javaToolchainService.launcherFor(javaToolchainSpec) + ).map(javaLauncher -> + javaLauncher.map(JavaLauncher::getMetadata).map(JavaInstallationMetadata::getInstallationPath) + ).orElseGet(() -> layout.dir(project.provider(() ->project.file(System.getProperty("java.home"))))); + getGraalVmHome().convention(graalHomeDirectoryProvider); + + BasePluginExtension basePluginExtension = + ext.getByType(BasePluginExtension.class); + getOutputFile().convention(basePluginExtension.getLibsDirectory().file(project.getName())); + Object executableProvider = new Object() { + @Override + public String toString() { + return getGraalVmHome().get() + "/bin/native-image"; + } + }; + executable(executableProvider); + + CommandLineArgumentProvider argumentProvider = new CommandLineArgumentProvider() { + @Override + public Iterable asArguments() { + List result = new ArrayList<>(); + if(!getEnableFallback().get()) { + result.add("--no-fallback"); + } + JavaModuleDetector javaModuleDetector = getJavaModuleDetector(); + boolean useJpms = getUseJpms().get(); + FileCollection classpath = getClasspath().get(); + FileCollection cp = javaModuleDetector.inferClasspath(useJpms, classpath); + FileCollection mp = javaModuleDetector.inferModulePath(useJpms, classpath); + if(!cp.isEmpty()) { + result.add("-cp"); + result.add(cp.getAsPath()); + } + if(!mp.isEmpty()) { + result.add("-p"); + result.add(mp.getAsPath()); + } + result.add("-o"); + result.add(getOutputFile().get().getAsFile().toString()); + result.add(getMainClass().get()); + logger.info("Native image arguments: " + String.join(" ", result)); + return Collections.unmodifiableList(result); + } + }; + getArgumentProviders().add(argumentProvider); + } +} diff --git a/sambal/build.gradle b/sambal/build.gradle new file mode 100644 index 0000000..7272ad7 --- /dev/null +++ b/sambal/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java-gradle-plugin' +} + +dependencies { + implementation catalog.jgit +} + +gradlePlugin { + plugins { + create("SambalPlugin") { + id = "net.woggioni.gradle.sambal" + implementationClass = "net.woggioni.gradle.sambal.SambalPlugin" + } + } +} diff --git a/sambal/src/main/java/net/woggioni/gradle/sambal/SambalPlugin.java b/sambal/src/main/java/net/woggioni/gradle/sambal/SambalPlugin.java new file mode 100644 index 0000000..2b4d2a8 --- /dev/null +++ b/sambal/src/main/java/net/woggioni/gradle/sambal/SambalPlugin.java @@ -0,0 +1,232 @@ +package net.woggioni.gradle.sambal; + +import lombok.SneakyThrows; +import org.codehaus.groovy.runtime.MethodClosure; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.lib.Ref; +import org.gradle.api.Action; +import org.gradle.api.GradleException; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.java.archives.Attributes; +import org.gradle.api.logging.Logger; +import org.gradle.api.plugins.AppliedPlugin; +import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.PluginManager; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.bundling.Jar; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SambalPlugin implements Plugin { + private static Pattern tagPattern = Pattern.compile("^refs/tags/v?(\\d+\\.\\d+.*)"); + final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + private static final String currentTagCachedKey = "CURRENT_TAG_CACHED"; + private static final String currentRevCachedKey = "CURRENT_REV_CACHED"; + + private static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + private static void copyConfigurationAttributes(Configuration source, Configuration destination) { + destination.attributes(new Action() { + @Override + public void execute(@Nonnull AttributeContainer destinationAttributes) { + AttributeContainer sourceAttributes = source.getAttributes(); + for (Attribute attr : sourceAttributes.keySet()) { + destinationAttributes.attribute(attr, Objects.requireNonNull(sourceAttributes.getAttribute(attr))); + } + } + }); + } + + @SneakyThrows + private static String sha256(File f) { + byte[] buffer = new byte[0x10000]; + MessageDigest md = MessageDigest.getInstance("SHA-256"); + try (InputStream inputStream = new DigestInputStream(new FileInputStream(f), md)) { + while (true) { + int read = inputStream.read(buffer); + if (read < 0) break; + md.update(buffer, 0, read); + } + } + return bytesToHex(md.digest()); + } + + private static Optional which(String command) { + return Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator))) + .map(path -> Paths.get(path, command)) + .filter(Files::exists) + .filter(Files::isExecutable) + .findFirst(); + } + + private static int getVersionInt(Project project) { + int result = 0; + int scale = 100 * 100; + String versionString = project.getVersion().toString(); + int dash = versionString.indexOf('-'); + if (dash < 0) { + dash = versionString.length(); + } + versionString = versionString.substring(0, dash); + for (String part : versionString.split("\\.")) { + if (scale >= 1) { + result += scale * Integer.parseInt(part); + scale /= 100; + } + } + return result; + } + + @SneakyThrows + private static String runCommand(String... args) { + ProcessBuilder pb = new ProcessBuilder(args); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + pb.redirectOutput(ProcessBuilder.Redirect.PIPE); + Process p = pb.start(); + p.waitFor(); + if (p.exitValue() != 0) throw new GradleException("Error invoking subprocess"); + StringWriter sw = new StringWriter(); + char[] buffer = new char[0x1024]; + try (Reader reader = new InputStreamReader(p.getInputStream())) { + while (true) { + int read = reader.read(buffer); + if (read <= 0) break; + sw.write(buffer, 0, read); + } + } + return sw.toString(); + } + + @SneakyThrows + private static String getCurrentTag(Git git, Logger logger) { + List tags = git.tagList().call(); + Ref currentRef = git.getRepository().findRef("HEAD"); + List currentTag = tags.stream() + .filter(it -> Objects.equals(it.getObjectId(), currentRef.getObjectId())) + .map(it -> tagPattern.matcher(it.getName())).filter(Matcher::matches) + .map(it -> it.group(1)) + .collect(Collectors.toList()); + if (currentTag.isEmpty()) return null; + else { + if (currentTag.size() > 1) { + logger.warn("Found more than one tag in correct format for HEAD."); + } + return currentTag.get(0); + } + } + + @SneakyThrows + private static String getCurrentTag(Project project) { + ExtraPropertiesExtension ext = project.getRootProject().getExtensions().getExtraProperties(); + if (!ext.has(currentTagCachedKey)) { + Git git = Git.open(project.getRootDir()); + Status status = git.status().call(); + String currentTag; + if (status.isClean() && (currentTag = getCurrentTag(git, project.getLogger())) != null) { + ext.set(currentTagCachedKey, currentTag); + } else { + ext.set(currentTagCachedKey, null); + } + } + return Optional.ofNullable(ext.get(currentTagCachedKey)) + .map(Object::toString) + .orElse(null); + } + + @SneakyThrows + private static String getGitRevision(Project project) { + ExtraPropertiesExtension ext = project.getRootProject().getExtensions().getExtraProperties(); + if (!ext.has(currentRevCachedKey)) { + Git git = Git.open(project.getRootDir()); + ext.set(currentRevCachedKey, git.getRepository().findRef("HEAD").getObjectId().name()); + } + return Optional.ofNullable(ext.get(currentRevCachedKey)) + .map(Object::toString) + .orElse(null); + } + + private static String resolveProperty(Project project, String key, String defaultValue) { + if (project.hasProperty(key)) return project.property(key).toString(); + else { + String envVarKey = key.replace('.', '_').toUpperCase(); + String envVarValue = System.getenv().get(envVarKey); + if (envVarValue != null) return envVarValue; + else return defaultValue; + } + } + + private static String resolveProperty(Project project, String key) { + if (project.hasProperty(key)) return project.property(key).toString(); + else { + String envVarKey = key.replace('.', '_').toUpperCase(); + String envVarValue = System.getenv().get(envVarKey); + if (envVarValue != null) return envVarValue; + else { + String msg = String.format("Impossible to resolve property '%s'," + + " either add it to Gradle's project properties from the command line using:\n" + + "./gradlew -P%s=someValue\n" + + "or set the environmental variable %s", key, key, envVarKey); + throw new GradleException(msg); + } + } + } + + @Override + public void apply(Project project) { + ExtraPropertiesExtension ext = project.getRootProject().getExtensions().getExtraProperties(); + ext.set("getIntegerVersion", new MethodClosure(this, "getVersionInt").curry(project)); + ext.set("currentTag", getCurrentTag(project)); + ext.set("resolveProperty", new MethodClosure(this, "resolveProperty").curry(project)); + ext.set("copyConfigurationAttributes", new MethodClosure(this, "copyConfigurationAttributes")); + final String gitRevision = getGitRevision(project); + ext.set("gitRevision", gitRevision); + + PluginManager pluginManager = project.getPluginManager(); + pluginManager.withPlugin("java-library", (AppliedPlugin plugin) -> { + TaskContainer tasks = project.getTasks(); + project.afterEvaluate(p -> { + tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, jarTask -> { + jarTask.manifest(mf -> { + Attributes attrs = mf.getAttributes(); + attrs.put(java.util.jar.Attributes.Name.SPECIFICATION_TITLE.toString(), project.getName()); + attrs.put(java.util.jar.Attributes.Name.SPECIFICATION_VERSION.toString(), project.getVersion()); + attrs.put(java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION.toString(), gitRevision); + }); + }); + }); + }); + } +} diff --git a/sambal/src/main/java/net/woggioni/gradle/sambal/SignJarTask.java b/sambal/src/main/java/net/woggioni/gradle/sambal/SignJarTask.java new file mode 100644 index 0000000..f4731b3 --- /dev/null +++ b/sambal/src/main/java/net/woggioni/gradle/sambal/SignJarTask.java @@ -0,0 +1,84 @@ +package net.woggioni.gradle.sambal; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.bundling.Jar; + +import javax.inject.Inject; +import java.io.File; +import java.util.Map; +import java.util.TreeMap; + +abstract class SignJarTask extends DefaultTask { + @InputFile + public abstract RegularFileProperty getInputJarFile(); + + @Input + public abstract MapProperty getOptions(); + + @Input + public abstract DirectoryProperty getArchiveDestination(); + + @Input + public abstract Property getArchiveBaseName(); + + @Input + public abstract Property getArchiveVersion(); + + @Input + public abstract Property getArchiveExtension(); + + @Input + @Optional + public abstract Property getArchiveClassifier(); + + @OutputFile + public Provider getArchiveFile() { + StringBuilder sb = new StringBuilder(getArchiveBaseName().get()); + sb.append('-').append(getArchiveVersion().get()); + if(getArchiveClassifier().isPresent() && !getArchiveClassifier().get().isBlank()) { + sb.append('-').append(getArchiveClassifier().get()); + } + sb.append('.').append(getArchiveExtension().get()); + return getProject().getLayout().file(getArchiveDestination().map(ad -> + new File(ad.getAsFile(), sb.toString()) + )); + } + + @Inject + public SignJarTask() { + BasePluginExtension bpe = getProject() + .getExtensions() + .getByType(BasePluginExtension.class); + getArchiveDestination().convention(bpe.getLibsDirectory()); + getArchiveBaseName().convention(getProject().getName()); + getArchiveVersion().convention(getProject().getVersion().toString()); + getArchiveExtension().convention("jar"); + getArchiveClassifier().convention("signed"); + getArchiveClassifier().set("signed"); + getInputJarFile().convention( + getProject().getTasks() + .named(JavaPlugin.JAR_TASK_NAME, Jar.class) + .flatMap(Jar::getArchiveFile)); + } + + @TaskAction + public void run() { + Map signingOptions = new TreeMap<>(getOptions().get()); + signingOptions.put("jar", getInputJarFile().map(RegularFile::getAsFile).get().toString()); + signingOptions.put("signedJar", getArchiveFile().get().getAsFile().toString()); + getAnt().invokeMethod("signjar", signingOptions); + } +} diff --git a/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Sealing.java b/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Sealing.java new file mode 100644 index 0000000..0d79410 --- /dev/null +++ b/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Sealing.java @@ -0,0 +1,54 @@ +package net.woggioni.gradle.sambal.attribute; + +import org.gradle.api.Named; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeCompatibilityRule; +import org.gradle.api.attributes.AttributeDisambiguationRule; +import org.gradle.api.attributes.CompatibilityCheckDetails; +import org.gradle.api.attributes.MultipleCandidatesDetails; +import org.gradle.api.internal.ReusableAction; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +public interface Sealing extends Named { + Attribute SEALING_ATTRIBUTE = Attribute.of("net.woggioni.gradle.lys.artifact.type", Sealing.class); + + String sealed = "sealed"; + String open = "open"; + + class CompatibilityRules implements AttributeCompatibilityRule, ReusableAction { + public void execute(CompatibilityCheckDetails details) { + Sealing consumerValue = details.getConsumerValue(); + Sealing producerValue = details.getProducerValue(); + if (consumerValue == null) { + details.compatible(); + } else if(producerValue == null) { + details.incompatible(); + } else if(Objects.equals(consumerValue.getName(), producerValue.getName())) { + details.compatible(); + } else { + details.incompatible(); + } + } + } + + class DisambiguationRules implements AttributeDisambiguationRule, ReusableAction { + private static final List ORDER = Arrays.asList(open, sealed); + private static final Comparator comparator = + Comparator.comparingInt(sealing -> ORDER.indexOf(sealing.getName())); + + @Override + public void execute(MultipleCandidatesDetails details) { + if(details.getConsumerValue() == null) { + details.closestMatch(null); + } else { + details.getCandidateValues().stream() + .min(comparator) + .ifPresent(details::closestMatch); + } + } + } +} diff --git a/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Signing.java b/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Signing.java new file mode 100644 index 0000000..caa7f49 --- /dev/null +++ b/sambal/src/main/java/net/woggioni/gradle/sambal/attribute/Signing.java @@ -0,0 +1,54 @@ +package net.woggioni.gradle.sambal.attribute; + +import org.gradle.api.Named; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeCompatibilityRule; +import org.gradle.api.attributes.AttributeDisambiguationRule; +import org.gradle.api.attributes.CompatibilityCheckDetails; +import org.gradle.api.attributes.MultipleCandidatesDetails; +import org.gradle.api.internal.ReusableAction; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +public interface Signing extends Named { + Attribute SIGNING_ATTRIBUTE = Attribute.of("net.woggioni.gradle.lys.artifact.signing", Signing.class); + + String signed = "signed"; + String unsigned = "unsigned"; + + class CompatibilityRules implements AttributeCompatibilityRule, ReusableAction { + public void execute(CompatibilityCheckDetails details) { + Signing consumerValue = details.getConsumerValue(); + Signing producerValue = details.getProducerValue(); + if (consumerValue == null) { + details.compatible(); + } else if(producerValue == null) { + details.incompatible(); + } else if(Objects.equals(consumerValue.getName(), producerValue.getName())) { + details.compatible(); + } else { + details.incompatible(); + } + } + } + + class DisambiguationRules implements AttributeDisambiguationRule, ReusableAction { + private static final List ORDER = Arrays.asList(unsigned, signed); + private static final Comparator comparator = + Comparator.comparingInt(signing -> ORDER.indexOf(signing.getName())); + + @Override + public void execute(MultipleCandidatesDetails details) { + if(details.getConsumerValue() == null) { + details.closestMatch(null); + } else { + details.getCandidateValues().stream() + .min(comparator) + .ifPresent(details::closestMatch); + } + } + } +} diff --git a/settings.gradle b/settings.gradle index 94c8038..ef5191d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,3 +25,5 @@ include 'osgi-app:osgi-simple-bootstrapper' include 'osgi-app:osgi-simple-bootstrapper-api' include 'osgi-app:osgi-simple-bootstrapper-application' include 'wildfly' +include 'sambal' +include 'native-image'