mirror of
https://github.com/runelite/plugin-hub.git
synced 2026-05-18 05:26:11 -04:00
package: add standard builds
This commit is contained in:
@@ -43,8 +43,10 @@ You may contribute to existing plugins by selecting the plugin from https://rune
|
||||
description=Alerts you when you have nothing equipped in your head slot
|
||||
tags=hint,gear,head
|
||||
plugins=com.helmetcheck.HelmetCheckPlugin
|
||||
version=
|
||||
build=standard
|
||||
```
|
||||
`tags` will make it easier to find your plugin when searching for related words. If you want to add multiple plugin files, the `plugins` field allows for comma separated values, but this is not usually needed.
|
||||
`tags` will make it easier to find your plugin when searching for related words. `version` is optional, if missing the commit will be used. If you want to add multiple plugin files, the `plugins` field allows for comma separated values, but this is not usually needed.
|
||||
|
||||
10. Optionally, you can add an icon to be displayed alongside with your plugin. Place a file with the name `icon.png` no larger than 48x72 px at the root of the repository.
|
||||
|
||||
@@ -120,6 +122,11 @@ To add a new dependency, add it to the `thirdParty` configuration in [`package/v
|
||||
then run `../gradlew --write-verification-metadata sha256` to update the metadata file. A maintainer must then verify
|
||||
the dependencies manually before your pull request will be merged. This process generally adds significantly to the amount of time it takes for a plugin submission or update to be reviewed, so we recommend avoiding adding any new dependencies unless absolutely necessary.
|
||||
|
||||
## Build type
|
||||
`runelite-plugin.properties` contains a `build` property, which may be set to either `standard` or `gradle`. In `standard`
|
||||
mode, your`build.gradle` and `settings.gradle` get replaced when built during plugin submission. This allows for expedited review
|
||||
if you don't need to specify any dependencies or other custom build steps.
|
||||
|
||||
## My client version is outdated
|
||||
If your client version is outdated or your plugin suddenly stopped working after RuneLite has been updated, make sure that your `runeLiteVersion` is set to `'latest.release'` in `build.gradle`. If this is set correctly, refresh the Gradle dependencies by doing the following:
|
||||
1. Open the Gradle tool window.
|
||||
|
||||
@@ -86,7 +86,7 @@ subs = OrderedDict([
|
||||
}),
|
||||
("version", {
|
||||
"desc": "The initial version number of the plugin",
|
||||
"value": "1.0-SNAPSHOT",
|
||||
"value": "",
|
||||
}),
|
||||
("plugin_prefix", {
|
||||
"desc": "The name of the your plugin's main class, without the 'Plugin' suffix",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Abex
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.pluginhub.packager;
|
||||
|
||||
public enum BuildType
|
||||
{
|
||||
STANDARD,
|
||||
GRADLE,
|
||||
}
|
||||
@@ -189,6 +189,14 @@ public class Plugin implements Closeable
|
||||
@Setter
|
||||
private long buildTimeMS;
|
||||
|
||||
@VisibleForTesting
|
||||
final File propFile;
|
||||
|
||||
private Properties rlPluginProperties;
|
||||
|
||||
private BuildType buildType;
|
||||
private boolean buildPropMissing;
|
||||
|
||||
private int jarSizeLimitMiB = 10;
|
||||
|
||||
public Plugin(File pluginCommitDescriptor) throws IOException, DisabledPluginException, PluginBuildException
|
||||
@@ -281,6 +289,7 @@ public class Plugin implements Closeable
|
||||
apiFile = new File(buildDirectory, "api");
|
||||
srcZipFile = new File(buildDirectory, "source.zip");
|
||||
iconFile = new File(repositoryDirectory, "icon.png");
|
||||
propFile = new File(repositoryDirectory, "runelite-plugin.properties");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -396,31 +405,32 @@ public class Plugin implements Closeable
|
||||
|
||||
public void build(String runeliteVersion, boolean disallowedIsFatal) throws IOException, PluginBuildException
|
||||
{
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(repositoryDirectory.toPath(), "**.{gradle,gradle.kts}"))
|
||||
if (!propFile.exists())
|
||||
{
|
||||
for (Path path : ds)
|
||||
throw PluginBuildException.of(this, "runelite-plugin.properties must exist in the root of your repo");
|
||||
}
|
||||
rlPluginProperties = loadProperties(propFile);
|
||||
|
||||
{
|
||||
String buildTypeStr = (String) rlPluginProperties.remove("build");
|
||||
if (Strings.isNullOrEmpty(buildTypeStr))
|
||||
{
|
||||
String badLine = MoreFiles.asCharSource(path, StandardCharsets.UTF_8)
|
||||
.lines()
|
||||
.filter(l -> l.codePoints().map(cp ->
|
||||
{
|
||||
if (cp == '\t')
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
else if (cp > 127)
|
||||
{
|
||||
// any special char is counted as 4 because there are some very wide special characters
|
||||
return 4;
|
||||
}
|
||||
return 1;
|
||||
}).sum() > 120)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (badLine != null)
|
||||
buildPropMissing = true;
|
||||
buildType = BuildType.GRADLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (buildTypeStr)
|
||||
{
|
||||
throw PluginBuildException.of(this, "All gradle files must wrap at 120 characters or less")
|
||||
.withFileLine(path.toFile(), badLine);
|
||||
case "gradle":
|
||||
buildType = BuildType.GRADLE;
|
||||
break;
|
||||
case "standard":
|
||||
buildType = BuildType.STANDARD;
|
||||
break;
|
||||
default:
|
||||
throw PluginBuildException.of(this, "build must be one of [gradle, standard], not \"{}\"", buildTypeStr)
|
||||
.withFileLine(propFile, "build=" + buildTypeStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,6 +496,55 @@ public class Plugin implements Closeable
|
||||
}
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(repositoryDirectory.toPath(), "**.{gradle,gradle.kts}"))
|
||||
{
|
||||
for (Path path : ds)
|
||||
{
|
||||
if (buildType == BuildType.GRADLE)
|
||||
{
|
||||
String badLine = MoreFiles.asCharSource(path, StandardCharsets.UTF_8)
|
||||
.lines()
|
||||
.filter(l -> l.codePoints().map(cp ->
|
||||
{
|
||||
if (cp == '\t')
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
else if (cp > 127)
|
||||
{
|
||||
// any special char is counted as 4 because there are some very wide special characters
|
||||
return 4;
|
||||
}
|
||||
return 1;
|
||||
}).sum() > 120)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (badLine != null)
|
||||
{
|
||||
throw PluginBuildException.of(this, "All gradle files must wrap at 120 characters or less")
|
||||
.withFileLine(path.toFile(), badLine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Files.delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buildType != BuildType.GRADLE)
|
||||
{
|
||||
try (InputStream is = Plugin.class.getResourceAsStream("standard-build.gradle"))
|
||||
{
|
||||
Files.copy(is, new File(repositoryDirectory, "build.gradle").toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
try (InputStream is = Plugin.class.getResourceAsStream("standard-settings.gradle"))
|
||||
{
|
||||
Files.copy(is, new File(repositoryDirectory, "settings.gradle").toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream is = Plugin.class.getResourceAsStream("verification-metadata.xml"))
|
||||
{
|
||||
File metadataFile = new File(repositoryDirectory, "gradle/verification-metadata.xml");
|
||||
@@ -611,15 +670,16 @@ public class Plugin implements Closeable
|
||||
displayData.setWarning(warning);
|
||||
|
||||
{
|
||||
Properties chunk = loadProperties(new File(buildDirectory, "chunk.properties"));
|
||||
String version = (String) rlPluginProperties.remove("version");
|
||||
|
||||
String version = chunk.getProperty("version");
|
||||
if (Strings.isNullOrEmpty(version))
|
||||
if (Strings.isNullOrEmpty(version) && buildType == BuildType.GRADLE)
|
||||
{
|
||||
throw new IllegalStateException("version in empty");
|
||||
Properties chunk = loadProperties(new File(buildDirectory, "chunk.properties"));
|
||||
|
||||
version = chunk.getProperty("version");
|
||||
}
|
||||
|
||||
if (version.endsWith("SNAPSHOT"))
|
||||
if (Strings.isNullOrEmpty(version) || version.endsWith("SNAPSHOT"))
|
||||
{
|
||||
version = commit.substring(0, 8);
|
||||
}
|
||||
@@ -781,15 +841,8 @@ public class Plugin implements Closeable
|
||||
}
|
||||
|
||||
{
|
||||
File propFile = new File(repositoryDirectory, "runelite-plugin.properties");
|
||||
if (!propFile.exists())
|
||||
{
|
||||
throw PluginBuildException.of(this, "runelite-plugin.properties must exist in the root of your repo");
|
||||
}
|
||||
Properties props = loadProperties(propFile);
|
||||
|
||||
{
|
||||
String displayName = (String) props.remove("displayName");
|
||||
String displayName = (String) rlPluginProperties.remove("displayName");
|
||||
if (Strings.isNullOrEmpty(displayName) || disallowedIsFatal && "Example".equals(displayName))
|
||||
{
|
||||
throw PluginBuildException.of(this, "\"displayName\" must be set")
|
||||
@@ -799,7 +852,7 @@ public class Plugin implements Closeable
|
||||
}
|
||||
|
||||
{
|
||||
String author = (String) props.remove("author");
|
||||
String author = (String) rlPluginProperties.remove("author");
|
||||
if (Strings.isNullOrEmpty(author) || disallowedIsFatal && "Nobody".equals(author))
|
||||
{
|
||||
throw PluginBuildException.of(this, "\"author\" must be set")
|
||||
@@ -809,7 +862,7 @@ public class Plugin implements Closeable
|
||||
}
|
||||
|
||||
{
|
||||
String description = (String) props.remove("description");
|
||||
String description = (String) rlPluginProperties.remove("description");
|
||||
if (disallowedIsFatal && "An example greeter plugin".equals(description))
|
||||
{
|
||||
throw PluginBuildException.of(this, "\"description\" must be set")
|
||||
@@ -819,7 +872,7 @@ public class Plugin implements Closeable
|
||||
}
|
||||
|
||||
{
|
||||
String tagsStr = (String) props.remove("tags");
|
||||
String tagsStr = (String) rlPluginProperties.remove("tags");
|
||||
if (!Strings.isNullOrEmpty(tagsStr))
|
||||
{
|
||||
displayData.setTags(Splitter.on(",")
|
||||
@@ -831,7 +884,7 @@ public class Plugin implements Closeable
|
||||
}
|
||||
|
||||
{
|
||||
String pluginsStr = (String) props.remove("plugins");
|
||||
String pluginsStr = (String) rlPluginProperties.remove("plugins");
|
||||
if (pluginsStr == null)
|
||||
{
|
||||
throw PluginBuildException.of(this, "\"plugins\" must be set")
|
||||
@@ -886,12 +939,21 @@ public class Plugin implements Closeable
|
||||
}
|
||||
}
|
||||
|
||||
if (props.size() != 0)
|
||||
if (rlPluginProperties.size() != 0)
|
||||
{
|
||||
writeLog("warning: unused props in runelite-plugin.properties: {}\n", props.keySet());
|
||||
writeLog("warning: unused props in runelite-plugin.properties: {}\n", rlPluginProperties.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
if (disallowedIsFatal && buildPropMissing)
|
||||
{
|
||||
throw PluginBuildException.of(this, "\"build\" must be set")
|
||||
.withHelp("You must add a build=standard or build=gradle entry.\n" +
|
||||
"build=standard is recommended unless you have dependencies or other changes to your build.gradle.\n" +
|
||||
"See https://github.com/runelite/plugin-hub#build-type for more info.")
|
||||
.withFile(propFile);
|
||||
}
|
||||
|
||||
realPluginChecks();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
url = 'https://repo.runelite.net'
|
||||
content {
|
||||
includeGroupByRegex("net\\.runelite.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly "net.runelite:client"
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release.set(11)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = "standard-plugin-build"
|
||||
@@ -71,7 +71,7 @@ public class PluginTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExamplePluginCompiles() throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
|
||||
public void testExamplePluginCompilesStandard() throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
|
||||
{
|
||||
try (Plugin p = createExamplePlugin("example"))
|
||||
{
|
||||
@@ -79,15 +79,22 @@ public class PluginTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExamplePluginCompilesGradle() throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
|
||||
{
|
||||
try (Plugin p = createExamplePlugin("example"))
|
||||
{
|
||||
setProp(p, "build", "gradle");
|
||||
p.build(Util.readRLVersion(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingPlugin() throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
|
||||
{
|
||||
try (Plugin p = createExamplePlugin("missing-plugin"))
|
||||
{
|
||||
File propFile = new File(p.repositoryDirectory, "runelite-plugin.properties");
|
||||
Properties props = Plugin.loadProperties(propFile);
|
||||
props.setProperty("plugins", "com.nonexistent");
|
||||
writeProperties(props, propFile);
|
||||
setProp(p, "plugins", "com.nonexistent");
|
||||
p.build(Util.readRLVersion(), true);
|
||||
Assert.fail();
|
||||
}
|
||||
@@ -103,10 +110,7 @@ public class PluginTest
|
||||
{
|
||||
try (Plugin p = createExamplePlugin("empty-plugins"))
|
||||
{
|
||||
File propFile = new File(p.repositoryDirectory, "runelite-plugin.properties");
|
||||
Properties props = Plugin.loadProperties(propFile);
|
||||
props.setProperty("plugins", "");
|
||||
writeProperties(props, propFile);
|
||||
setProp(p, "plugins", "");
|
||||
p.build(Util.readRLVersion(), true);
|
||||
Assert.fail();
|
||||
}
|
||||
@@ -122,6 +126,7 @@ public class PluginTest
|
||||
{
|
||||
try (Plugin p = createExamplePlugin("unverified-dependency"))
|
||||
{
|
||||
setProp(p, "build", "gradle");
|
||||
File buildFile = new File(p.repositoryDirectory, "build.gradle");
|
||||
String buildSrc = Files.asCharSource(buildFile, StandardCharsets.UTF_8).read();
|
||||
buildSrc = buildSrc.replace("dependencies {", "dependencies {\n" +
|
||||
@@ -220,4 +225,11 @@ public class PluginTest
|
||||
{
|
||||
Assert.assertTrue(haystack, haystack.contains(needle));
|
||||
}
|
||||
|
||||
private void setProp(Plugin p, String key, String value) throws IOException
|
||||
{
|
||||
var props = Plugin.loadProperties(p.propFile);
|
||||
props.setProperty(key, value);
|
||||
writeProperties(props, p.propFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ dependencies {
|
||||
}
|
||||
|
||||
group = '${group_id}'
|
||||
version = '${version}'
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
|
||||
@@ -2,4 +2,6 @@ displayName=${name}
|
||||
author=${author}
|
||||
description=${description}
|
||||
tags=
|
||||
plugins=${package}.${plugin_prefix}Plugin
|
||||
version=${version}
|
||||
plugins=${package}.${plugin_prefix}Plugin
|
||||
build=standard
|
||||
Reference in New Issue
Block a user