Resource Maven & NMS tutorial

Discussion in 'Spigot Plugin Development' started by Sataniel, Nov 13, 2018.

  1. [1/3]
    Hello there!
    This is a Maven and multi version support tutorial, written because I felt that there may be good tutorials about either the one or the other, but none that sets both things into a context. The Maven documentation includes many things that make it seem more complicated than it actually is in the everyday life of a Bukkit developer.

    This tutorial is based on my personal experiences. I have knowingly made controversal statements here and there as I regard this as personal advice rather than style preaching. I hope to have explained my reasoning sufficiently sothat you can understand why I made these respective statements, even if you may not agree, and I am open to constructive suggestions and discussions.

    By the way: Please let me know if there are any language mistakes, since I am not a native speaker.

    NMS TUTORIAL
    by Sataniel

    Terms
    All terms are explained within the meaning of this guide.
    • Project: The collectivity of all scripts, configs, modules and source code meant to build one product forms a project.
    • Project folder: The root directory of a project.
    • Internals: Internal classes of a server that are not part of the API. Mostly inside the packages org.bukkit.craftbukkit and net.minecraft.server.
    • pom.xml: The name of a Maven project / module config file.
    • Implementation: A class (or possibly but less commonly an enum) that implements an interface or extends an abstract class.
    • Module: Subpart of the project; consists at least of one sub directory of the project and a pom.xml file that defines it. In multi module projects:
      • Parent (module): Defined and configured by the pom.xml of the project's root directory; contains no source code directly.
      • Core (/main) module: The module that contains most of the plugin source code, excluding parts that call the internals.
      • Implementation module: Hypernym for modules that contain calls to the internals. Projects usually contain one per supported package version.
      • Distribution ("dist") module: The module that collects the parts of a project and builds the product out of them; contains no source code.
    • Shading: Including a module or project as a part of the built jar, e.g. like CraftBukkit contains Google's Gson library.
    The benefits of using a dependency management system
    In general
    • Reproducibility of builds
    • Encouragement of tidy dependency versioning
    A system like Maven or Gradle allows you to specify the exact steps how to build your project and to specify online repositories where the dependencies are stored. Thus, it makes it possible for contributors to build the project without the need to search for the dependencies and to look for steps to build. They don't even need to open an IDE, just one command is enough to build the project. On the longterm, it's also easier to handle for the maintainer himself.

    For version independent Bukkit plugins
    • Maintainability and clarity
    • Error-proneness
    • Performance
    • And - as this tutorial is supposed to show - multi version support!
    The only way to support multiple versions without Maven and pom projects or comparable software with a comparable feature is to use reflection or to import several server jars in a single module project. Both is, after all, not very clean but frustrating.

    A mapping doesn't help if the field or method gets removed or altered. For example,
    Code (Text):
    PlayerList#moveToWorld(EntityPlayer, int, boolean)
    is in Minecraft 1.13 a method that can be used to respawn players, and basically what the Spigot API method Player.Spigot#respawn() wraps.

    However in Minecraft 1.13.1, the int parameter was replaced with an enum value of DimensionManager.
    Code (Text):
    PlayerList#moveToWorld(EntityPlayer, DimensionManager, boolean)
    If you used reflection to bypass the package version, then congratulations, your plugin just broke. And what if Mojang decides that this method should act a bit differently in 1.13.2: That it as of then moves the player, but reviving them is handled in a different method?

    Probably no big deal and easily fixed in a plugin that uses just one or two NMS reflections - I can't say I've never done that myself either - but if your plugin relies on it heavily, then you gave up primarily maintainability and readability, but also performance not for ensured functionality with upcoming versions but for a gamble if your plugin will continue to work or not.

    IDEs cannot highlight erroneous calls if they are done through reflection. Reflection calls are hard to read and impact performance negatively.

    Importing all jars with different package versions works better, but only because of mentioned package versions. This is neither applicable to supporting multiple versions of a plugin nor to the packages that are not affected by this feature; as soon as a changed or removed API needs to be called, things get messy.

    Maven: Installation
    Information about how to install Maven can be found here.

    Source code and resources of a Maven project are supposed to be stored at src/main/java/{packages} and src/main/resources/{resource files}.

    Maven: Commands
    • mvn clean - Removes the target directory of the current project folder to clean it up
    • mvn package - Builds the project
    • mvn install - Builds the project and installs it to the local repository (see "dependencies and repositories")
    These commands can and should be combined to mvn clean install. However if you are building your project only for testing purposes, using mvn clean package might make more sense sothat the local repository does not keep a junk jar permanently.

    Maven: jar projects
    Let's start with a simple single module project to explain the basics of Maven :)

    The setup of this example is similar to a project without Maven, where you download dependencies manually and add them to the build path. The only difference is that instead of tediously searching for and downloading these dependencies, we let Maven handle this for us.

    The pom.xml
    The file "pom.xml" which is located in the root directory of the project configures Maven. This for example is a simple pom.xml for a single module project:

    Code (Text):
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>de.erethon</groupId>
       <artifactId>sakura</artifactId>
       <version>1.8</version>
       <packaging>jar</packaging>
       <name>Sakura</name>
       <url>https://dre2n.github.io</url>
       <description>Bring Japanese cherries to Minecraft!</description>
       <properties>
           <buildNo></buildNo>
       </properties>
       <build>
           <finalName>${project.artifactId}-${project.version}${buildNo}</finalName>
           <sourceDirectory>src/main/java</sourceDirectory>
           <resources>
               <resource>
                   <targetPath>.</targetPath>
                   <filtering>true</filtering>
                   <directory>${basedir}/src/main/resources/</directory>
                   <includes>
                       <include>plugin.yml</include>
                   </includes>
               </resource>
           </resources>
           <plugins>
               <plugin>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <version>3.7.0</version>
                   <configuration>
                       <source>1.8</source>
                       <target>1.8</target>
                   </configuration>
               </plugin>
           </plugins>
       </build>
       <dependencies>
           <dependency>
               <groupId>org.bukkit</groupId>
               <artifactId>bukkit</artifactId>
               <version>1.13-R0.1-SNAPSHOT</version>
               <scope>provided</scope>
           </dependency>
           <dependency>
               <groupId>net.sothatsit</groupId>
               <artifactId>blockstore</artifactId>
               <version>1.5.0</version>
               <scope>provided</scope>
           </dependency>
       </dependencies>
       <repositories>
           <repository>
               <id>spigot-repo</id>
               <url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
           </repository>
           <repository>
               <id>dre-repo</id>
               <url>http://erethon.de/repo/</url>
           </repository>
       </repositories>
    </project>
    Basic project information
    The following lines define the basic project information. The groupId and artifactId are supposed to follow the naming conventions of the used main package of the project and should identify the project uniquely. Even though you'll find many plugins breaking this convention (looking at you, VAULT!), both artifactId and groupId should use only basic characters and are supposed to be lowercase. "name" can be used for a neatly formatted project name instead.

    Code (Text):
       <groupId>de.erethon</groupId>
       <artifactId>sakura</artifactId>
       <version>1.8</version>
       <name>Sakura</name>
    The "build" section
    Let's have a look at the build section next. It's almost self-explaining.

    Code (Text):
       <build>
           <finalName>${project.artifactId}-${project.version}${buildNo}</finalName>
           <sourceDirectory>src/main/java</sourceDirectory>
           <resources>
               <resource>
                   <targetPath>.</targetPath>
                   <filtering>true</filtering>
                   <directory>${basedir}/src/main/resources/</directory>
                   <includes>
                       <include>plugin.yml</include>
                   </includes>
               </resource>
           </resources>
           <plugins>
    ...
           </plugins>
       </build>
    The "plugins" section
    Not so much self explaining is probably the plugins section, even though my example project doesn't really use that excessively. Maven supports "plugins" to allow even more control over the build process. However, knowing just two of these plugins is definitely enough for now, and to be honest, I have barely ever needed more than the two I intend to show you. The first one is the compiler plugin. It can make Maven aware that the source is supposed to be for Java 8.

    Code (Text):
           <plugins>
               <plugin>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <version>3.7.0</version>
                   <configuration>
                       <source>1.8</source>
                       <target>1.8</target>
                   </configuration>
               </plugin>
           </plugins>
    The second one is the shade plugin (see post #3).

    You can see what the current latest version is at mvnrepository.com: https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin

    Dependencies and repositories
    Maven allows you to specify the dependencies of your project and online repositories where they are stored. Thus, it makes it possible for contributors to build the project without the need to search for each jar and figure out which version is needed to compile the project. In this case, Bukkit can be fetched from the Spigot repo (https://hub.spigotmc.org/nexus/content/groups/public/) and BlockStore from my own one (http://erethon.de/repo/; and yes, I did ask for permission to upload it there :p ). The order of the dependencies and repositories doesn't matter.

    The dependency scope parameters you should definitely know are "compile" and "provided". To make it not too complicated, "compile" is what you should use if you shade the dependency into the jar, while "provided" means that the dependency will be linked dynamically at runtime, like the Bukkit API or a plugin dependency. If no scope is set, it defaults to "compile". However, setting this explicitly doesn't hurt.

    Code (Text):
       <dependencies>
           <dependency>
               <groupId>org.bukkit</groupId>
               <artifactId>bukkit</artifactId>
               <version>1.13-R0.1-SNAPSHOT</version>
               <scope>provided</scope>
           </dependency>
           <dependency>
               <groupId>net.sothatsit</groupId>
               <artifactId>blockstore</artifactId>
               <version>1.5.0</version>
               <scope>provided</scope>
           </dependency>
       </dependencies>
       <repositories>
           <repository>
               <id>spigot-repo</id>
               <url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
           </repository>
           <repository>
               <id>dre-repo</id>
               <url>http://erethon.de/repo/</url>
           </repository>
       </repositories>
    All Java archives are downloaded to a local repository in the .m2 folder in the user directory of your computer and taken from there when you compile.

    CraftBukkit and Spigot as dependencies
    If you run BuildTools, not only the APIs but also the server jars CraftBukkit and Spigot are automatically installed to this local repository. An online repository doesn't exist for the same reasons like why there is no direct download button anymore, so you'll need to run BuildTools for all versions that you wish to support in your plugins.

    • --rev 1.8 = 1_8_R1
    • --rev 1.8.3 = 1_8_R2
    • --rev 1.8.8 = 1_8_R3
    • --rev 1.9.2 = 1_9_R1
    • --rev 1.9.4 = 1_9_R2
    • --rev 1.10.2 = 1_10_R1
    • --rev 1.11.2 = 1_11_R1
    • --rev 1.12.2 = 1_12_R1
    • --rev 1.13 = 1_13_R1
    • --rev 1.13.2 = 1_13_R2
    • --rev 1.14.4 = 1_14_R1
    • --rev 1.15.2 = 1_15_R1
    • --rev 1.16.1 = 1_16_R1
    • --rev 1.16.3 = 1_16_R2
    • --rev 1.16.4 = 1_16_R3
    The IDs of these dependencies are (groupId:artifactId):

    • Bukkit API: org.bukkit:bukkit
    • Spigot API: org.spigotmc:spigot-api
    • CraftBukkit server: org.bukkit:craftbukkit
    • Spigot server: org.spigotmc:spigot
    Using information from the pom.xml in the plugin.yml file
    You do not need to modify the version two times if you're using Maven, it can replace that automatically for you while compiling:

    Code (Text):
    name: ${project.name}
    main: de.erethon.sakura.Sakura
    version: ${project.version}
    author: Daniel Saukel
    dependencies: [BlockStore]
    description: ${project.description}
    website: ${project.url}
    commands:
      sakura:
       description: Gives a Sakura item
       aliases: [sgive,cherry]
    api-version: 1.13
    Maven: pom projects
    The example I'm going to use for multi module projects is another project of mine called ItemStackLib. ISL is not a standalone plugin but a library that is supposed to be distributed as a part of plugins that depend on it. It is very small, but it heavily relies on NMS and thus can serve as a comprehensible example.

    What the heck are pom projects?
    Multi module or pom projects are Maven projects that are devided into multiple more or less small projects that share one parent. Disincorporating portions of the project makes it possible for this portion to call different dependencies that would normally overlap with other dependencies. A multi module project allows a Bukkit plugin that relies on the internals but is supposed to support multiple versions to cleanly handle multiple server jar dependencies. It can of course also be used to handle different versions of plugins that your plugin hooks into - Factions for example has dozens of forks that all share the same packages and name - or to cleanly handle not only implementation specific, but also changed Bukkit API code. Some projects even support different server APIs, like Sponge and Bukkit, through multi module projects in the same jar. This tutorial however shows an example of a Bukkit based library that will support all versions available through BuildTools.

    The "modules" are visible as sub directories of the project folder. Our version independent project code will go into the "core" module, while the version specific code gets its own named after the respective package version.

    Parent module
    One pom.xml can be found in the main directory like in a single module project. It defines the parent module.

    Code (Text):
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>de.erethon</groupId>
       <artifactId>itemstacklib-parent</artifactId>
       <version>2.0.1</version>
       <packaging>pom</packaging>
       <name>ItemStackLib</name>
       <url>https://dre2n.github.io</url>
       <description>Library to modify some ItemStack traits that Bukkit doesn't support</description>
       <modules>
           <module>core</module>
           <module>craftbukkit_1_13_R2</module>
           <module>craftbukkit_1_13_R1</module>
           <module>craftbukkit_1_12_R1</module>
           <module>craftbukkit_1_11_R1</module>
           <module>craftbukkit_1_10_R1</module>
           <module>craftbukkit_1_9_R2</module>
           <module>craftbukkit_1_9_R1</module>
           <module>craftbukkit_1_8_R3</module>
           <module>craftbukkit_1_8_R2</module>
           <module>craftbukkit_1_8_R1</module>
           <module>dist</module>
       </modules>
       <dependencies>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>compatibility</artifactId>
               <version>1.3.4</version>
               <scope>compile</scope>
           </dependency>
       </dependencies>
       <repositories>
           <repository>
               <id>dre-repo</id>
               <url>https://erethon.de/repo/</url>
           </repository>
       </repositories>
    </project>
    Packaging
    Code (Text):
       <packaging>pom</packaging>
    The packaging setting is what indicates that the project is a multi module project. In the single module project, we used "jar" to make Maven build a Java archive, and a multi module projects needs this to be set to "pom".

    The "modules" section
    Code (Text):
       <modules>
           <module>core</module>
           <module>craftbukkit_1_13_R2</module>
           <module>craftbukkit_1_13_R1</module>
           <module>craftbukkit_1_12_R1</module>
           <module>craftbukkit_1_11_R1</module>
           <module>craftbukkit_1_10_R1</module>
           <module>craftbukkit_1_9_R2</module>
           <module>craftbukkit_1_9_R1</module>
           <module>craftbukkit_1_8_R3</module>
           <module>craftbukkit_1_8_R2</module>
           <module>craftbukkit_1_8_R1</module>
           <module>dist</module>
       </modules>
    The module section, as you probably noticed, contains the names of each of the sub directories of the project folder. Each one of these sub directories is a Maven project on its own with its own pom.xml.

    Dependencies
    The parent project does not contain any source code. However, the modules are children of it and inherit / can call its properties. That being said, you can include dependencies and repositories that are shared between multiple modules here to reduce duplicates.
     
    #1 Sataniel, Nov 13, 2018
    Last edited: Nov 4, 2020
    • Like x 9
    • Useful x 6
    • Winner x 3
    • Informative x 2
    • Agree x 1
  2. [2/3]
    Core module
    Code (Text):
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>de.erethon</groupId>
       <artifactId>itemstacklib-core</artifactId>
       <version>${project.parent.version}</version>
       <packaging>jar</packaging>
       <parent>
           <groupId>de.erethon</groupId>
           <artifactId>itemstacklib-parent</artifactId>
           <version>2.0.1</version>
       </parent>
       <build>
           <plugins>
               <plugin>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <version>3.7.0</version>
                   <configuration>
                       <source>1.8</source>
                       <target>1.8</target>
                   </configuration>
               </plugin>
           </plugins>
       </build>
       <dependencies>
           <dependency>
               <groupId>org.bukkit</groupId>
               <artifactId>bukkit</artifactId>
               <version>1.13.1-R0.1-SNAPSHOT</version>
               <scope>provided</scope>
           </dependency>
       </dependencies>
       <repositories>
           <repository>
               <id>spigot-repo</id>
               <url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
           </repository>
       </repositories>
    </project>
    The "core" or "main" module contains, as the name suggests, the main part of the plugin. It shouldn't depend on implementation specific code, but ideally only on the Bukkit API and other libraries. If you have a plugin.yml (which this project doesn't need), it should be in the src/main/resources folder of the core module.

    The "parent" section
    The parent section is the counter part to the module section in the main pom.xml.

    Code (Text):
       <parent>
           <groupId>de.erethon</groupId>
           <artifactId>itemstacklib-parent</artifactId>
           <version>2.0.1</version>
       </parent>
    Multi version compatibility through abstraction
    In the example, the ItemUtil utility class contains various public static methods to modify ItemStack attributes. Half of them are just overloaded, so we end up with five methods that require NMS. What now?

    The abstract internals provider
    The idea is to have one interface or abstract class which handles the calls to the internals as a template in the main module and to let classes in the implementation modules implement or extend it.

    Code (Text):
    abstract class InternalsProvider {

       abstract Collection<AttributeWrapper> getAttributes(ItemStack itemStack);

       abstract ItemStack removeAttribute(ItemStack itemStack, String name, boolean type);

       abstract ItemStack setAttribute(ItemStack itemStack, String attributeName, String name, double amount, byte operation, String... slots);

       abstract String getTextureValue(ItemStack itemStack);

       abstract ItemStack setSkullOwner(ItemStack itemStack, String id, String textureValue);

    }
    (In this case, the InternalsProvider could have been an interface as well, but I chose to make it an abstract class because multi inheritance isn't an issue here and abstract classes can underline better than interfaces with their always implicitly public methods that all of this is supposed to be strictly package private. This is a very personal decision of mine, choose what matches your project best.)

    An abstract internals provider and its implementations should however not be public - We already have got the well-commented ItemUtil class for the public to call.

    Now, all we need to do to finish the work on our core module is loading the implementations. This can be done more elegantly through reflection than through a giant switch or else if statement.

    In case you are wondering if this is hypocritical as the one reason to do all of this is to avoid reflection, it really is not. This is done to avoid the negative impact of reflection. This reflection call however is used only once to load the implementation, thus, it does not affect performance. Plus, we are using it to access our own class and no methods at all. That means, because this code is not subject to be changed upon updates, there is no issue with maintainability and error-proneness. In fact, it makes the project easier to maintain since you do not need to touch the core module at all in order to create an implementation module for a new version. Using a giant switch statement, I did happen to forget adding it in the past (sobs ashamedly).

    This example assumes that the class is named after the package version.

    To load the implementation:

    Code (Text):
       static InternalsProvider internals;

       static {
           try {
               String packageName = ItemUtil.class.getPackage().getName();
               String internalsName = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
               internals = (InternalsProvider) Class.forName(packageName + "." + internalsName).newInstance();
           } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException exception) {
               Bukkit.getLogger().log(Level.SEVERE, "ItemUtil could not find a valid implementation for this server version.");
           }
       }
    ...and a usage example:

    Code (Text):
       /**
        * Sets an attribute to the ItemStack.
        * <p>
        * Returns a copy of the ItemStack with the attribute applied to it
        *
        * @param item          a Bukkit ItemStack
        * @param attributeName the Attribute name
        * @param name          an identifier that has no impact on how the attribute behaves
        * @param amount        the Attribute amount
        * @param operation     the modifier operation
        * @param slots         the slot where the attribute affects the player
        * @return a copy of the ItemStack with the attribute applied to it
        */
       public static ItemStack setAttribute(ItemStack item, String attributeName, String name, double amount, byte operation, String... slots) {
           return internals.setAttribute(item, attributeName, name, amount, operation, slots);
       }
    Avoiding cyclic dependencies
    In many multi module Bukkit plugin projects, you'll probably find the InternalsProvider or however they call it outsourced to an "abstract" or "API" module. I have also done that in the past, but learned that there are good reasons not to always do so.

    Just to clarify this: I'm not saying adding an API module is "bad" or should be avoided. While the core part of your plugin might change unforseeably in the future if you decide to add or change something, it can be useful to have the supported methods that are supposed to stay compatible upon updates in an API module separated from the core part of the plugin itself. See filoghost's HolographicDisplays as an example for this. Thus, having an abstracted module is a great way to maintain reliable hooks for other plugins. However, our example is a utility library. There is obviously no point in adding an abstract module on top of that.

    Having a module called "API" but using it for stuff that isn't even public and only for internal usage - like our internals provider class - is just confusing. Why would you create yet another module if this code could easily be part of the core module? Most developers who still do so do so to prevent a problem called "cyclic dependencies". Modules of the same project don't depend on each other by default, they need to be added as dependencies like external, different projects:

    Code (Text):
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-core</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
    This for example allows a module to call the classes of the core module. While all modules could easily reference each other at runtime thanks to Java's lazy class loader, the compiler unfortunately needs a straight compile order. Therefore, if the module depends on the core module the core module on the other hand cannot depend on it. If you used a switch / else if statement (with "internals = new v1_13_R2();" etc.) instead of the reflection method above, the core module must depend on all implementations. The implementations in that case wouldn't be able to depend on the InternalsProvider in the main module, and thus, the InternalsProvider can't be part of it. While the core and implementation module cannot depend on each other, both can depend on the same abstract module without issues. Well, that's the theory, but doing so results in more, new issues.

    If the InternalsProvider is part of an independent abstract module, it cannot call the classes of the core module. This is a big problem. In ItemStackLib, we've got this method:

    Code (Text):
       abstract Collection<AttributeWrapper> getAttributes(ItemStack itemStack);
    that is supposed to build a Collection of instances of AttributeWrapper from a Bukkit ItemStack. But AttributeWrapper is part of the core library, and if we don't want to cripple our cleanly devided module setup, we can't just move it to an abstract module. Another possibility is to over-engineer this with an interface in the abstract module that AttributeWrapper can implement, but I believe this is getting too far-fetched for the purposes of our tutorial.

    On the other hand, I see no reason why it could be bad if an implementation called methods from the core.

    tl;dr; it is way easier and in my books not less clean to stick to having the InternalsProvider in the core module and letting the implementations depend on the core.

    Implementation modules
    Implementation modules contain the implementation specific code for each package version. Therefore, they depend on the server jar instead of the API. As it has been mentioned, you need to run BuildTools for all versions that you wish to support due to the lack of a public repository. The pom.xml has no other anomalies compared to the core module.

    A full pom.xml is available here.

    Code (Text):
       <dependencies>
           <dependency>
               <groupId>org.bukkit</groupId>
               <artifactId>craftbukkit</artifactId>
               <version>1.13.1-R0.1-SNAPSHOT</version>
               <scope>provided</scope>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-core</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
       </dependencies>
    Source code
    Because of the package private access modifier, it is important to have the InternalsProvider implementation in the same package as the abstract class. Also, make sure to name the class in the way expected by the reflection inspection in the core module. Override the abstract methods to return something useful - and you're done.
    Code (Text):
       @Override
       ItemStack setSkullOwner(ItemStack item, String id, String textureValue) {
           net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(item);

           NBTTagCompound tag = nmsStack.getOrCreateTag();

           NBTTagCompound skullOwner = new NBTTagCompound();
           skullOwner.setString("Id", id);
           NBTTagCompound properties = new NBTTagCompound();
           NBTTagList textures = new NBTTagList();
           NBTTagCompound value = new NBTTagCompound();
           value.setString("Value", textureValue);
           textures.add(value);
           properties.set("textures", textures);
           skullOwner.set("Properties", properties);

           tag.set("SkullOwner", skullOwner);

           return CraftItemStack.asBukkitCopy(nmsStack);
       }
     
    #2 Sataniel, Nov 13, 2018
    Last edited: May 14, 2020
    • Winner Winner x 6
    • Like Like x 5
    • Useful Useful x 1
  3. [3/3]
    Distribution module
    Code (Text):
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <groupId>de.erethon</groupId>
       <artifactId>itemstacklib-dist</artifactId>
       <version>${project.parent.version}</version>
       <packaging>jar</packaging>
       <parent>
           <groupId>de.erethon</groupId>
           <artifactId>itemstacklib-parent</artifactId>
           <version>2.0.1</version>
       </parent>
       <build>
           <directory>../target</directory>
           <finalName>itemstacklib-${project.version}</finalName>
           <plugins>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-shade-plugin</artifactId>
                   <version>3.1.1</version>
                   <executions>
                       <execution>
                           <phase>package</phase>
                           <goals>
                               <goal>shade</goal>
                           </goals>
                           <configuration>
                               <artifactSet>
                                   <includes>
                                       <include>de.erethon:itemstacklib-*</include>
                                       <include>de.erethon:compatibility</include>
                                   </includes>
                               </artifactSet>
                           </configuration>
                       </execution>
                   </executions>
               </plugin>
           </plugins>
       </build>
       <dependencies>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-core</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_13_R2</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_13_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_12_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_11_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_10_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_9_R2</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_9_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_8_R3</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_8_R2</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
           <dependency>
               <groupId>de.erethon</groupId>
               <artifactId>itemstacklib-craftbukkit_1_8_R1</artifactId>
               <version>${project.parent.version}</version>
           </dependency>
       </dependencies>
    </project>
    The "dist" module depends on all modules, but has no source code. It is supposed to collect the parts of the whole project and produce a full jar that includes all modules: The distribution. Doing this in an own module makes things a bit clearer, and can, depending on your setup, be necessary to prevent cyclic dependencies.

    The "build" section
    Code (Text):
           <directory>../target</directory>
           <finalName>itemstacklib-${project.version}</finalName>
    I'm removing the module suffix ("-dist") from the name of the final jar and moving the target directory to /target in the main directory of the project, to indicate that this is the product of the whole project. It is also possible to give the "dist" module an artifactId without a -suffix. The disadvantage is that the module won't have a name that people can clearly read off its purpose, but it makes it so that mvn install installs the artifact directly to where you probably want it to end up instead of to the "-dist" suffixed location in the local repo.

    The "artifactId-version" name, sometimes followed by a build number is a style standard that is for example used in Maven online repositories where you can almost always find what you're looking for at {https://address}/groupId/artifactId/artifactId-version.jar.

    The assembly plugin
    ...is a Maven plugin that is designed to actually do exactly what we're looking for: Gathering all parts of the project and building a final jar. It is, strictly speaking, a more appropriate choice to do this than the shade plugin. However, I'm more used to using the shade plugin.

    The shade plugin
    ...can be used to make the dependencies integral parts of the compiled Java archive, and to move code to other packages. CraftBukkit for example uses the shade plugin to include some libraries such as jline, but also the Minecraft server, and to move the org.bukkit.craftbukkit and net.minecraft.server packages to *.v1_13_R2 or whatever the current package version is.

    The shade plugin can of course also shade modules of the same project into a jar. Just make sure to add the modules as dependencies and to set the scope to "compile". As it was mentioned, not including the scope setting makes it default to "compile", and in a distribution module where all dependencies are of the "compile" scope, adding this hardly makes it any clearer, so I decided to leave the scope out here.

    Code (Text):
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-shade-plugin</artifactId>
                   <version>3.1.1</version>
                   <executions>
                       <execution>
                           <phase>package</phase>
                           <goals>
                               <goal>shade</goal>
                           </goals>
                           <configuration>
                               <artifactSet>
                                   <includes>
                                       <include>de.erethon:itemstacklib-*</include>
                                       <include>de.erethon:compatibility</include>
                                   </includes>
                               </artifactSet>
                           </configuration>
                       </execution>
                   </executions>
               </plugin>
    The wildcard support shortens a lot here and makes it very easy to maintain.

    de.erethon:compatibility is an example for an external dependency that can be shaded into the final jar the same way.

    Relocations with the Maven shade plugin
    There is no example to relocate packages in ItemStackLib. This is an unrelated example on how to relocate bStats and inventivetalent's Spiget updater to a different package.

    Code (Text):
                           <configuration>
                               <relocations>
                                   <relocation>
                                       <pattern>org.bstats.bukkit</pattern>
                                       <shadedPattern>de.erethon.commons.bstats</shadedPattern>
                                   </relocation>
                                   <relocation>
                                       <pattern>org.inventivetalent.update</pattern>
                                       <shadedPattern>de.erethon.commons.update</shadedPattern>
                                   </relocation>
                               </relocations>
                           </configuration>
    Moving shaded dependencies ensures that they cannot be overriden by the class loader, which might cause problems if another plugin shades the same dependency, but a different version of it.

    Note that in the includes section, input matching "groupId:artifactId" is expected, while the relocation section uses the plain package names.
     
    #3 Sataniel, Nov 13, 2018
    Last edited: Jan 12, 2019
    • Useful Useful x 6
    • Winner Winner x 3
    • Like Like x 2
  4. Im your fan :B
     
    • Friendly Friendly x 1
    • Optimistic Optimistic x 1
  5. drives_a_ford

    Moderator

    I got a StackOverflowError while reading this part :)
     
  6. Thanks, numerus fixed ;)

    BTW, I also drive a Ford
     
  7. FrostedSnowman

    Resource Staff

    Instead of the maven compiler plugin for the java version, just do:

    Code (Java):

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.release>8</maven.compiler.release>
    </properties>
     
    • Agree Agree x 1
    • Informative Informative x 1
  8. This is the best resource I've found to explain Maven modules on Spigot or elsewhere, because it's both comprehensive and uses clear examples. Personally, I struggled with understanding the purpose of a distribution pom before reading this. Bearing in mind that I'm new to modules, I feel that the following could use improving on:
    • "Implementation" modules are often called "child" modules (even by Maven itself), which I think should be mentioned here for those readily familiar with the concept of parent/child relationship.
    • The example code for implementation modules could be more complete. For example, it doesn't currently show use of the necessary <parent> tags.
    • I'm confused as to the purpose of <include>de.erethon:compatibility</include> in your dist module (also referenced in the parent).
    • The Github link under Maven: Commands is broken.
     
    • Friendly Friendly x 1
  9. [​IMG]
    I haven't read this anywhere so far. I know the term only as a hypernym for all modules that specify a common parent. In this case, the itemstacklib-core, -craftbukkit_* and -dist modules are all children of itemstacklib-parent. I'm not saying yours is wrong, as it does make sense to call them children of the core module for depending on it, but I'm afraid I'd rather not create a possible source of confusion by using the same term with different meanings in two situations.
    That's true. I'll add a full version of the pom.
    This is an example for a dependency other than Bukkit. Bukkit should not be specified in the parent pom, because the implementations use their own versions of CraftBukkit and the sole API of a specific version is exclusively needed in the core module. But I'd still like to show that pom project pom.xml files can specify dependencies for all their children.
    Fixed, thanks! It seems they didn't survive being copied from GitHub, where I posted this originally.
     
  10. If I am using a plugin.yml should I use ${project.parent.name} or ${project.name}? It is not clear to me if a child module will inherit its parent name if not specifically set or not. Or should I add <name>${project.parent.name}</name> to the core module pom.xml (and then stick with ${project.name} in the plugin.yml)

    /e I image I have to add the part with resources in one of the pom files too?
     
    #10 Alex_qp, May 31, 2019
    Last edited: May 31, 2019
  11. ${project.name} is not inherited. If you don't specify a name in your module, ${project.name} is replaced by the artifactId instead (which is obligatory). In the end, both of your solutions should work, so I'd decide what to do based on what your goal is: Do you want the plugin name to be the name of the specific module, which is the same as the project name? Or do you want it to be the name of the project? I suppose it is the latter, so I'd rather go with ${project.parent.name} in the plugin.yml.

    However, everything inside <properties> is inherited. You can specify custom values there.
     
    • Like Like x 1
  12. Thanks for the answer, I went with your parent suggestion and it now works perfectly fine.

    Well and I would also like to review your tutorial since I just started using maven. Your tutorial is very detailed but I would not recommend it for starters because it is sometimes a bit overkill in my opinion. You may can get around this with the use of spoilers (for example I would use it on whole explaining paragraphs why you should do it this way and not the other way round because this is not that important for a starter to fully get, he just needs to be sent in the right direction how to do things)

    But overall a very good tutorial. For me personally I have watched a youtube video first and then got back to this.
     
  13. Doing things without understanding why it’s done that way is how you end up using poor practices and utilizing code smells. Explaining why things are done the way they are establishes the author’s credibility in addition to teaching the reader the correct techniques.
     
  14. Thank you for the constructive criticism :)
    I understand that the length of this tutorial is scaring off people who want some quick advice. The problem is that it seems to me that many projects, even highly popular ones made by fantastic developers with elegant Java code styles, use very different Maven setup styles.

    I don't want to pretend that matters of opinion and emphases of different advantages can be discussed in terms of "right" or "wrong". Except the things that are specified by conventions (like lowercase groupIds and artifactIds), they are, at best, "reasonable" or "empirically problematic". I have personally helped out a few developers setting up their projects; once they understood how the things worked and why I did what I did, none of them ended up keeping the things exactly like I set them up for them. And that is fine, because this tutorial wasn't meant as something to blindly follow to begin with.
    If I left out or put pieces of information in the rear, people might cobble together working setups faster, but they wouldn't easily be able to change things to match their needs and to handle issues.

    I have a feeling that you were referring to the part about cyclic dependencies, "API" modules, and initialization through reflection, which is rather long but has not so much of a practical value for a correctly set up project. This topic in particular is maybe the one I spent most time thinking about and searching for well-working solutions, but also the one where my solution differs the most from common solutions. If I didn't explain my reasons thoroughly there, people would probably run into the same frustrating and time-expensive issues as I did, and experienced Maven users would definitely have complained about these parts by now.
     
    • Agree Agree x 1
  15. While I was browsing your plugins a question came up: What spigot api version should my core module have? You used the latest version you support despite the fact that the bukkit api only officially supports forward compatibility (or tries to at least). So is there a reason I cannot see?
     
  16. Spigot is NOT fully forward compatible. It has some functions that to some degree make old plugins work, but this is by no means something worth using as a development paradigm. As the 1.13 update thread says:

    "The flip side of this is that the scope of the changes means forwards/backwards compatibility is not assured. One of the key strengths of the Bukkit API is that there is a strong emphasis on forwards compatibility and that there is a good chance you can pick up a plugin that has not been updated in many years with the expectation that it will still run on your server. This is something that we wish to maintain, so a considerable amount of time has been spent writing a compatibility layer that will allow many plugins to function to some degree. [...] The compatibility layer is not a substitute for updating plugins. [...] The compatibility layer is largely best effort support."

    If possible, you shouldn't rely on forward compatibility mechanics at all, you'll only end up using deprecated methods that are likelier to break than modern APIs. If it has Bukkit as a dependency, the core module must only rely on fully supported APIs shared between all versions. And shared APIs are present in any version, so it doesn't matter that much which one you pick. But using the latest version in the core module comes with the advantage that you see modern deprecation warnings and that you can react to your code becoming outdated better.
     
    • Informative Informative x 1
    • Like Like x 1
  17. Hey, I was looking to add multi-version support in my plugin. I did everything listed here but my plugin doesn't recognize any methods. Such as "Player" variable. Do I need to do something else in IntelliJ to make it use the poms listed?
     
  18. Perhaps the project isn't registered as a Maven project but as one without an organized build system and the dependencies from the pom are ignored. Or maybe if you never built the project the dependencies just haven't been fetched yet. IDEs don't necessarily do this on their own.