Skip to content

[FEATURE] Gradle plugin #234

@oujesky

Description

@oujesky

The setup for native dependencies in a Gradle based projects is still relatively complex even after #221 and might be simplified even more by providing a Gradle plugin that takes care of the setup automatically.

In Gradle-based project that could be used simply as:

plugins {
    id("com.aayushatharva.brotli4j") version "x.y.z"
}

dependencies {
    runtimeOnly("com.aayushatharva.brotli4j:natives:x.y.z")

    // optionally together with Gradle Shadow plugin
    brotli4jShadow("com.aayushatharva.brotli4j:natives:x.y.z")
}

I was able to produce such plugin in my local project based on #221 and @solonovamax's #219. This could be probably published to Maven Central / Gradle Plugin Portal and then it would be available without any additional setup. I have no idea if that could be achieved from the current Maven-based project structure or if that would need an additional separate Gradle-based project just for the plugin & its publishing.

PoC code:

buildSrc/build.gradle.kts

plugins {
    `java-gradle-plugin`
}

gradlePlugin {
    plugins {
        create("brotli4j-plugin") {
            id = "com.aayushatharva.brotli4j"
            implementationClass = "com.aayushatharva.brotli4j.gradle.Brotli4jPlugin"
        }
    }
}

buildSrc/src/main/java/com/aayushatharva/brotli4j/gradle/Brotli4jPlugin.java

package com.aayushatharva.brotli4j.gradle;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.file.FileCollection;
import org.gradle.api.model.ObjectFactory;
import org.gradle.nativeplatform.MachineArchitecture;
import org.gradle.nativeplatform.OperatingSystemFamily;
import org.gradle.nativeplatform.internal.DefaultTargetMachineFactory;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

class Brotli4jPlugin implements Plugin<Project> {

    static final String BROTLI4J_GROUP_ID = "com.aayushatharva.brotli4j";
    static final String BROTLI4J_NATIVES_MODULE = "natives";
    static final String BROTLI4J_NATIVE_MODULE = "native";
    static final String BROTLI4J_SHADOW_CONFIGURATION = "brotli4jShadow";

    private final ObjectFactory objects;

    @Inject
    public Brotli4jPlugin(ObjectFactory objects) {
        this.objects = objects;
    }

    @Override
    public void apply(Project project) {
        var configurations = project.getConfigurations();

        project.getDependencies().getComponents().withModule(
            BROTLI4J_GROUP_ID + ":" + BROTLI4J_NATIVES_MODULE,
            Brotli4jComponentMetadataRule.class
        );

        var host = new DefaultTargetMachineFactory(project.getObjects()).host();
        configurations.configureEach(configuration -> {
            if (!configuration.getName().endsWith("Classpath")) {
                return;
            }

            configuration.attributes(attributes -> {
                if (!attributes.keySet().contains(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE)) {
                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, host.getOperatingSystemFamily());
                }

                if (!attributes.keySet().contains(MachineArchitecture.ARCHITECTURE_ATTRIBUTE)) {
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, host.getArchitecture());
                }
            });
        });

        project.getPluginManager().withPlugin("com.gradleup.shadow", shadowPlugin -> {
            var runtimeClasspath = configurations.getByName("runtimeClasspath");

            var brotliShadow = configurations.register(BROTLI4J_SHADOW_CONFIGURATION, configuration -> {
                configuration.extendsFrom(runtimeClasspath);

                configuration.setCanBeConsumed(true);
                configuration.setCanBeResolved(true);
                configuration.setCanBeDeclared(false);

                configuration.attributes(attributes -> {
                    attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME));
                    attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
                    attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));

                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily.class, "all"));
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture.class, "all"));
                });
            });

            project.getTasks().matching(task -> task.getName().equals("shadowJar"))
                .configureEach(task -> {
                    @SuppressWarnings("unchecked")
                    var shadowJarConfigurations = (List<FileCollection>) task.property("configurations");
                    if (shadowJarConfigurations == null) {
                        shadowJarConfigurations = new ArrayList<>();
                    }
                    shadowJarConfigurations.add(brotliShadow.get());
                    task.setProperty("configurations", shadowJarConfigurations);
                });

        });

    }
}

buildSrc/src/main/java/com/aayushatharva/brotli4j/gradle/Brotli4jComponentMetadataRule.java

package com.aayushatharva.brotli4j.gradle;

import org.gradle.api.artifacts.CacheableRule;
import org.gradle.api.artifacts.ComponentMetadataContext;
import org.gradle.api.artifacts.ComponentMetadataRule;
import org.gradle.api.model.ObjectFactory;
import org.gradle.nativeplatform.MachineArchitecture;
import org.gradle.nativeplatform.OperatingSystemFamily;
import org.gradle.nativeplatform.platform.internal.Architectures;

import javax.inject.Inject;
import java.util.List;

import static com.aayushatharva.brotli4j.gradle.Brotli4jPlugin.BROTLI4J_GROUP_ID;
import static com.aayushatharva.brotli4j.gradle.Brotli4jPlugin.BROTLI4J_NATIVE_MODULE;

@CacheableRule
abstract class Brotli4jComponentMetadataRule implements ComponentMetadataRule {

    private static final List<NativeVariant> NATIVE_VARIANTS = List.of(
        new NativeVariant(OperatingSystemFamily.WINDOWS, Architectures.AARCH64.getCanonicalName(), "windows-aarch64"),
        new NativeVariant(OperatingSystemFamily.WINDOWS, Architectures.X86_64.getCanonicalName(), "windows-x86_64"),
        new NativeVariant(OperatingSystemFamily.MACOS, Architectures.X86_64.getCanonicalName(), "osx-x86_64"),
        new NativeVariant(OperatingSystemFamily.MACOS, Architectures.AARCH64.getCanonicalName(), "osx-aarch64"),
        new NativeVariant(OperatingSystemFamily.LINUX, Architectures.X86_64.getCanonicalName(), "linux-x86_64"),
        new NativeVariant(OperatingSystemFamily.LINUX, Architectures.AARCH64.getCanonicalName(), "linux-aarch64"),
        new NativeVariant(OperatingSystemFamily.LINUX, Architectures.ARM_V7.getCanonicalName(), "linux-armv7"),
        new NativeVariant(OperatingSystemFamily.LINUX, "s390x", "linux-s390x"),
        new NativeVariant(OperatingSystemFamily.LINUX, "riscv64", "linux-riscv64"),
        new NativeVariant(OperatingSystemFamily.LINUX, "ppc64le", "linux-ppc64le")
    );

    private final ObjectFactory objects;

    @Inject
    public Brotli4jComponentMetadataRule(ObjectFactory objects) {
        this.objects = objects;
    }

    @Override
    public void execute(ComponentMetadataContext context) {
        List.of("compile", "runtime")
            .forEach(base -> addVariant(context, base));
    }

    private void addVariant(ComponentMetadataContext context, String base) {
        var version = context.getDetails().getId().getVersion();

        NATIVE_VARIANTS.forEach(nativeVariant -> {
            context.getDetails().addVariant("%s-%s".formatted(nativeVariant.classifier(), base), base, variant -> {
                variant.attributes(attributes -> {
                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily.class, nativeVariant.os()));
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture.class, nativeVariant.arch()));
                });

                variant.withDependencies(dependencies ->
                    dependencies.add(nativeVariant.dependency(version))
                );
            });
        });

        context.getDetails().addVariant("all-%s".formatted(base), base, variant -> {
            variant.attributes(attributes -> {
                attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily.class, "all"));
                attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture.class, "all"));
            });

            variant.withDependencies(dependencies ->
                NATIVE_VARIANTS.forEach(nativeVariant ->
                    dependencies.add(nativeVariant.dependency(version))
                )
            );
        });
    }

    private record NativeVariant(
        String os,
        String arch,
        String classifier
    ) {
        String dependency(String version) {
            return "%s:%s-%s:%s".formatted(BROTLI4J_GROUP_ID, BROTLI4J_NATIVE_MODULE, classifier(), version);
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions