diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..013cc3305
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,58 @@
+on:
+ workflow_dispatch:
+ inputs:
+ FORCE_BUILD:
+ description: "List of plugins to build, or 'ALL'"
+ required: false
+ COMMIT_RANGE:
+ description: "Commit range to build 1234abc..5689def"
+ required: false
+ push:
+ pull_request:
+jobs:
+ execute:
+ # any forks that predate this repo having an action will have actions
+ # enabled by default, which will fail in a lot of cases because the branch
+ # is new, which makes the differential build fail
+ if: github.event_name != 'push' || github.repository_owner == 'runelite'
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-java@v1
+ with:
+ java-version: 11
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches/
+ ~/.gradle/wrapper/
+ key: package-2.0.0
+ - name: prepare
+ run: |
+ pushd package
+ ./gradlew --build-cache prep
+ popd
+ - name: build
+ env:
+ REPO_CREDS: ${{ secrets.REPO_CREDS }}
+ REPO_ROOT: ${{ secrets.REPO_ROOT }}
+ SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
+ # workflow_dispatch
+ FORCE_BUILD: ${{ github.event.inputs.FORCE_BUILD }}
+ COMMIT_RANGE: ${{ github.event.inputs.COMMIT_RANGE }}
+ # push
+ COMMIT_BEFORE: ${{ github.event.before }}
+ COMMIT_AFTER: ${{ github.event.after }}
+ # pull_request
+ PR_BEFORE: ${{ github.event.pull_request.base.sha }}
+ PR_AFTER: ${{ github.event.pull_request.head.sha }}
+ PACKAGE_IS_PR: ${{ github.event_name == 'pull_request' }}
+ run: |
+ if $PACKAGE_IS_PR; then
+ export PACKAGE_COMMIT_RANGE="$PR_BEFORE..$PR_AFTER"
+ else
+ export PACKAGE_COMMIT_RANGE="${COMMIT_RANGE:-${COMMIT_BEFORE:+$COMMIT_BEFORE..$COMMIT_AFTER}}"
+ fi
+ java -XX:+UseParallelGC -jar package/package/build/libs/package.jar
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 6fa6790a4..f2a313c27 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,12 @@
-language: java
os: linux
-dist: xenial
+virt: lxd
+dist: focal
+language: generic
cache:
directories:
- - $HOME/.m2
+ - $HOME/.gradle/caches/
+ - $HOME/.gradle/wrapper/
jdk:
- - openjdk8
+ - openjdk11
install: true
-script: ./travis.sh
\ No newline at end of file
+script: ./package/travis.sh
\ No newline at end of file
diff --git a/build_manifest.sh b/build_manifest.sh
deleted file mode 100755
index a9b487533..000000000
--- a/build_manifest.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/bash
-
-# Copyright (c) 2019 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.
-
-source repo_config.sh
-
-set -e -x
-
-[[ "${TRAVIS_PULL_REQUEST:-false}" == "false" ]] || exit 0
-
-RUNELITE_VERSION="$(cat "runelite.version")"
-
-MANIFEST="$(mktemp /tmp/manifest.XXXXXXXX)"
-trap "rm -rf ""$MANIFEST*""" EXIT
-MANIFEST_DIR="$MANIFEST.sub/"
-mkdir "$MANIFEST_DIR"
-
-MANIFEST_CHUNK_DOWNLOAD=()
-
-for PLUGINFILE in plugins/*; do
- # read in the plugin descriptor
- disabled=
- # shellcheck disable=SC2162
- while read LINE || [[ -n "$LINE" ]]; do
- [[ $LINE =~ ^(repository|commit|disabled|warning)=(.*)$ ]]
- eval "${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\""
- done < "$PLUGINFILE"
- [ -z "$disabled" ] || continue
-
- PLUGIN_ID=$(basename "$PLUGINFILE")
- LOCATION="$REPO_ROOT/$RUNELITE_VERSION/$PLUGIN_ID/$commit"
- MANIFEST_CHUNK_DOWNLOAD+=('--output' "$MANIFEST_DIR/$PLUGIN_ID" "$LOCATION.manifest")
-done
-
-curl --fail --retry 5 \
- "${MANIFEST_CHUNK_DOWNLOAD[@]}" || true
-
-IS_FIRST=true
-echo "[" > "$MANIFEST"
-for MANIFEST_CHUNK in "$MANIFEST_DIR"/*; do
- if [[ "$IS_FIRST" != true ]]; then
- echo "," >> "$MANIFEST"
- fi
- IS_FIRST=
- cat "$MANIFEST_CHUNK" >> "$MANIFEST"
-done
-echo "]" >> "$MANIFEST"
-
-# shellcheck disable=SC2059
-openssl dgst -sha256 -sign <(set +x; printf -- "$SIGNING_KEY") -out "$MANIFEST.sig" "$MANIFEST"
-
-perl -e "print pack('N', -s \"$MANIFEST.sig\")" > "$MANIFEST.out"
-cat "$MANIFEST.sig" >> "$MANIFEST.out"
-cat "$MANIFEST" >> "$MANIFEST.out"
-
-curl --fail --retry 5 \
- --user "$REPO_CREDS" \
- --upload-file "$MANIFEST.out" "$REPO_ROOT/$RUNELITE_VERSION/manifest.js"
-
-echo "Build Success"
\ No newline at end of file
diff --git a/build_plugin.sh b/build_plugin.sh
deleted file mode 100755
index 73c7729f2..000000000
--- a/build_plugin.sh
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-# Copyright (c) 2019 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.
-
-source repo_config.sh
-
-set -e -x
-
-PLUGINFILE="$1"
-[ -s "$PLUGINFILE" ]
-PLUGIN_ID=$(basename "$PLUGINFILE")
-
-# check valid plugin id
-[[ $PLUGIN_ID =~ ^[a-z0-9-]+$ ]]
-
-SCRIPT_HOME="$(cd "$(dirname "$0")" ; pwd -P)"
-
-RUNELITE_VERSION="$(cat "$SCRIPT_HOME/runelite.version")"
-
-# read in the plugin descriptor
-disabled=
-# shellcheck disable=SC2162
-while read LINE || [[ -n "$LINE" ]]; do
- [[ $LINE =~ ^(repository|commit|disabled|warning)=(.*)$ ]]
- eval "${BASH_REMATCH[1]}=\"${BASH_REMATCH[2]}\""
-done < "$PLUGINFILE"
-[ -z "$disabled" ] || exit 0
-
-# must be a https github repo
-[[ $repository =~ ^https://github.com/.*\.git$ ]]
-
-# we must have a full 40 char sha1sum
-[[ $commit =~ ^[a-fA-F0-9]{40}+$ ]]
-
-# we need gradle 6.2+ for dependency verification
-GRADLE_VER=gradle-6.6.1
-if [[ ! -e "/tmp/$GRADLE_VER/bin/gradle" ]]; then
- wget -q -O/tmp/gradle.zip "https://services.gradle.org/distributions/$GRADLE_VER-bin.zip"
- echo '7873ed5287f47ca03549ab8dcb6dc877ac7f0e3d7b1eb12685161d10080910ac */tmp/gradle.zip' | shasum -a256 -c
- unzip -q /tmp/gradle.zip -d /tmp/
- [[ -e "/tmp/$GRADLE_VER/bin/gradle" ]]
-fi
-export GRADLE_HOME="/tmp/$GRADLE_VER/"
-export PATH="$GRADLE_HOME/bin:$PATH"
-
-BUILDDIR="$(mktemp -d /tmp/external-plugin.XXXXXXXX)"
-trap "rm -rf ""$BUILDDIR""" EXIT
-pushd "$BUILDDIR"
-
-git clone -c 'advice.detachedHead=false' "$repository" "repo"
-pushd "repo"
-git checkout "$commit^{commit}"
-
-SIGNING_KEY="" REPO_CREDS="" gradle \
- --no-build-cache \
- --parallel \
- --console=plain \
- --init-script="$SCRIPT_HOME/package.gradle" \
- -DrlpluginRuneLiteVersion="$RUNELITE_VERSION" \
- -DrlpluginOutputDirectory="$BUILDDIR" \
- -DrlpluginPluginID="$PLUGIN_ID" \
- -DrlpluginCommit="$commit" \
- -DrlpluginWarning="$warning" \
- rlpluginPackageJar rlpluginEmitManifest
-
-[ -s "$BUILDDIR/plugin.jar" ]
-[ -s "$BUILDDIR/plugin.manifest" ]
-
-cat "$BUILDDIR/plugin.manifest"
-
-[[ "${TRAVIS_PULL_REQUEST:-false}" == "false" ]] || exit 0
-
-LOCATION="$REPO_ROOT/$RUNELITE_VERSION/$PLUGIN_ID/$commit"
-
-ICON_UPLOAD=()
-if [ -e "icon.png" ]; then
- ICON_UPLOAD=("--upload-file" "icon.png" "$LOCATION.png")
-fi
-
-curl --fail --retry 5 \
- --user "$REPO_CREDS" \
- --upload-file "$BUILDDIR/plugin.manifest" "$LOCATION.manifest" \
- --upload-file "$BUILDDIR/plugin.jar" "$LOCATION.jar" \
- "${ICON_UPLOAD[@]}"
-
-echo "Build Success"
diff --git a/package.gradle b/package.gradle
deleted file mode 100644
index 6b5bfc350..000000000
--- a/package.gradle
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (c) 2019 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.
- */
-import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
-import com.google.common.hash.Hashing
-import com.google.common.io.Files
-import com.google.gson.Gson
-import java.util.jar.JarFile
-
-initscript {
- repositories {
- jcenter()
- mavenCentral()
- }
- dependencies {
- classpath "com.github.jengelman.gradle.plugins:shadow:6.0.0"
- classpath "com.google.code.gson:gson:2.8.5"
- classpath "com.google.guava:guava:23.2-jre"
- }
- configurations.classpath.resolutionStrategy {
- // We don't have a way to add our direct deps to the metadata file,
- // so we disable it for the initscript's configuration
- disableDependencyVerification()
- }
-}
-
-allprojects {
- apply plugin: "java";
- apply plugin: com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
-
- tasks.withType(AbstractArchiveTask) {
- preserveFileTimestamps = false
- reproducibleFileOrder = true
- }
-
- task rlpluginPackageJar(type: ShadowJar) {
- destinationDir = new File(System.properties["rlpluginOutputDirectory"])
- archiveName = "plugin.jar"
- configurations = [project.configurations.runtimeClasspath]
- from sourceSets.main.output
- }
-
- task rlpluginEmitManifest {
- // this doesn't have up-to-date stuff because we always do --no-build-cache
-
- doLast {
- def manifest = new ExternalPluginManifest()
- manifest.internalName = System.properties["rlpluginPluginID"]
- manifest.commit = System.properties["rlpluginCommit"]
- if (System.properties["rlpluginWarning"]) {
- manifest.warning = System.properties["rlpluginWarning"];
- }
-
- def pluginJar = new File(System.properties["rlpluginOutputDirectory"], "plugin.jar");
- manifest.hash = Files.asByteSource(pluginJar)
- .hash(Hashing.sha256())
- .toString()
- manifest.size = pluginJar.length()
-
- if (manifest.size > 10 * 1024 * 1024) {
- throw new RuntimeException("The output jar is ${manifest.size.intdiv(1024 * 1024)}MiB, which is above our limit of 10MiB")
- }
-
- def props = new Properties()
- new FileInputStream(file("runelite-plugin.properties")).withCloseable { is ->
- props.load(is)
- }
-
- manifest.plugins = props["plugins"].split(/[,:;]/)*.trim()
- new JarFile(pluginJar).withCloseable{ jf ->
- manifest.plugins.each { plugin ->
- if (jf.getEntry(plugin.replaceAll(~/\./, "/") + ".class") == null) {
- throw new RuntimeException("Plugin class \"" + plugin + "\" is not in the output jar")
- }
- }
- }
-
- manifest.displayName = props["displayName"]
- if (!manifest.displayName) {
- throw new RuntimeException("Plugin must have a display name")
- }
- manifest.author = props["author"]
- if (!manifest.author) {
- throw new RuntimeException("Plugin must have an author")
- }
- if (props["support"]) {
- manifest.support = new URL(props["support"])
- }
- manifest.description = props["description"] ?: null
- if (props["tags"]) {
- manifest.tags = props["tags"].split(",")*.trim()
- }
-
- manifest.version = project.version
- if (!(manifest.version ==~ /^[a-zA-Z0-9.-]+$/)) {
- throw new RuntimeException("Plugin version \"${manifest.version}\" is invalid");
- }
-
- manifest.hasIcon = file("icon.png").exists();
-
- new File(System.properties["rlpluginOutputDirectory"], "plugin.manifest")
- .text = new Gson().toJson(manifest);
- }
- }
-
- rlpluginEmitManifest.dependsOn rlpluginPackageJar
-
- task configured {
- def runeLiteDeps = [
- "client",
- "runelite-api",
- "http-api",
- ]
- def version = System.properties["rlpluginRuneLiteVersion"]
- configurations.all {
- resolutionStrategy.eachDependency { DependencyResolveDetails details ->
- if (details.requested.group == "net.runelite" && details.requested.name in runeLiteDeps) {
- details.useVersion version
- }
- }
- }
- }
-}
-
-class ExternalPluginManifest {
- String internalName;
- String commit;
- String hash;
- int size;
- String[] plugins;
-
- String displayName;
- String version;
- String author;
- String description;
- String warning;
- String[] tags;
- URL support;
- boolean hasIcon;
-}
\ No newline at end of file
diff --git a/package/.gitignore b/package/.gitignore
new file mode 100644
index 000000000..33ebf499f
--- /dev/null
+++ b/package/.gitignore
@@ -0,0 +1,4 @@
+.gradle/
+.idea/
+build
+profile.svg
\ No newline at end of file
diff --git a/package/build.gradle b/package/build.gradle
new file mode 100644
index 000000000..d53ee0b11
--- /dev/null
+++ b/package/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 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.
+ */
+plugins {
+ id "com.github.johnrengelman.shadow" version "6.1.0"
+}
+
+allprojects {
+ version "2.0-SNAPSHOT"
+ group "net.runelite.pluginhub"
+
+ tasks.withType(AbstractArchiveTask) {
+ preserveFileTimestamps = false
+ reproducibleFileOrder = true
+ }
+}
+
+subprojects {
+ apply plugin: "java"
+ apply plugin: "com.github.johnrengelman.shadow"
+
+ sourceCompatibility = 1.8
+
+ shadowJar {
+ archiveFileName.set archiveBaseName.get() + "." + archiveExtension.get()
+ }
+}
+
+// we have this task then java -jar the output rather than using gradle run so
+// the daemon used to build this can be reused by a real plugin build
+task prep {
+ dependsOn ":package:shadowJar"
+ dependsOn ":initLib:shadowJar"
+ doLast {
+ file("build").mkdirs()
+ file("build/gradleHome").write(gradle.gradleHomeDir.absolutePath)
+ }
+}
\ No newline at end of file
diff --git a/package/gradle.properties b/package/gradle.properties
new file mode 100644
index 000000000..364404348
--- /dev/null
+++ b/package/gradle.properties
@@ -0,0 +1,2 @@
+# this should match what Plugin uses to build with
+org.gradle.jvmargs=-Xmx768M -XX:+UseParallelGC
\ No newline at end of file
diff --git a/package/gradle/verification-metadata.xml b/package/gradle/verification-metadata.xml
new file mode 100644
index 000000000..a47840a93
--- /dev/null
+++ b/package/gradle/verification-metadata.xml
@@ -0,0 +1,373 @@
+
+
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package/gradle/wrapper/gradle-wrapper.jar b/package/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..87b738cbd
Binary files /dev/null and b/package/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/package/gradle/wrapper/gradle-wrapper.properties b/package/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..2035183c5
--- /dev/null
+++ b/package/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionSha256Sum=11657af6356b7587bfb37287b5992e94a9686d5c8a0a1b60b87b9928a2decde5
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/package/gradlew b/package/gradlew
new file mode 100755
index 000000000..af6708ff2
--- /dev/null
+++ b/package/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/package/gradlew.bat b/package/gradlew.bat
new file mode 100644
index 000000000..0f8d5937c
--- /dev/null
+++ b/package/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/package/initLib/build.gradle b/package/initLib/build.gradle
new file mode 100644
index 000000000..8dc58782b
--- /dev/null
+++ b/package/initLib/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 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.
+ */
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+dependencies {
+ implementation "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:6.1.0"
+}
\ No newline at end of file
diff --git a/package/package/build.gradle b/package/package/build.gradle
new file mode 100644
index 000000000..5a37ad911
--- /dev/null
+++ b/package/package/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 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.
+ */
+repositories {
+ maven {
+ url "https://repo.gradle.org/gradle/libs-releases-local/"
+ }
+ mavenCentral()
+}
+
+dependencies {
+ implementation "org.gradle:gradle-tooling-api:6.6.1"
+ implementation "org.slf4j:slf4j-simple:1.7.10"
+ implementation "com.google.code.findbugs:jsr305:3.0.2"
+ implementation "com.google.guava:guava:23.2-jre"
+ implementation "org.ow2.asm:asm:7.0"
+ implementation "com.squareup.okhttp3:okhttp:3.14.9"
+ implementation "com.google.code.gson:gson:2.8.5"
+
+ def lombok = "org.projectlombok:lombok:1.18.4";
+ compileOnly lombok
+ annotationProcessor lombok
+ testCompileOnly lombok
+ testAnnotationProcessor lombok
+
+ testImplementation "junit:junit:4.12"
+ testImplementation "com.squareup.okhttp3:mockwebserver:3.14.9"
+}
+
+jar {
+ manifest {
+ attributes "Main-Class": "net.runelite.pluginhub.packager.Packager"
+ }
+}
+
+test {
+ workingDir new File(project.rootDir, "../")
+}
\ No newline at end of file
diff --git a/package/package/src/main/java/net/runelite/pluginhub/packager/DisabledPluginException.java b/package/package/src/main/java/net/runelite/pluginhub/packager/DisabledPluginException.java
new file mode 100644
index 000000000..f2692d4a9
--- /dev/null
+++ b/package/package/src/main/java/net/runelite/pluginhub/packager/DisabledPluginException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 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;
+
+import lombok.Getter;
+
+public class DisabledPluginException extends Exception
+{
+ @Getter
+ private final String internalName;
+
+ public DisabledPluginException(String internalName, String cause)
+ {
+ super("Plugin \"" + internalName + "\" is disabled: " + cause);
+ this.internalName = internalName;
+ }
+}
diff --git a/package/package/src/main/java/net/runelite/pluginhub/packager/ExternalPluginManifest.java b/package/package/src/main/java/net/runelite/pluginhub/packager/ExternalPluginManifest.java
new file mode 100644
index 000000000..959721d19
--- /dev/null
+++ b/package/package/src/main/java/net/runelite/pluginhub/packager/ExternalPluginManifest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 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;
+
+import java.net.URL;
+import javax.annotation.Nullable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+public class ExternalPluginManifest
+{
+ private String internalName;
+ private String commit;
+ private String hash;
+ private int size;
+ private String[] plugins;
+
+ private String displayName;
+ private String version;
+ private String author;
+ @Nullable
+ private String description;
+ @Nullable
+ private String warning;
+ @Nullable
+ private String[] tags;
+ @EqualsAndHashCode.Exclude
+ private URL support;
+ private boolean hasIcon;
+}
\ No newline at end of file
diff --git a/package/package/src/main/java/net/runelite/pluginhub/packager/Packager.java b/package/package/src/main/java/net/runelite/pluginhub/packager/Packager.java
new file mode 100644
index 000000000..bc341ef63
--- /dev/null
+++ b/package/package/src/main/java/net/runelite/pluginhub/packager/Packager.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2020 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;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Strings;
+import com.google.common.collect.Queues;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.HttpUrl;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okio.BufferedSource;
+
+@Slf4j
+public class Packager
+{
+ private static final File PLUGIN_ROOT = new File("./plugins");
+ public static final File PACKAGE_ROOT = new File("./package/").getAbsoluteFile();
+
+ private Semaphore downloadSemaphore = new Semaphore(2);
+ private Semaphore buildSemaphore = new Semaphore(Runtime.getRuntime().availableProcessors());
+ private Semaphore uploadSemaphore = new Semaphore(2);
+
+ private final List buildList;
+
+ @Getter
+ private final String runeliteVersion;
+
+ @Getter
+ private final UploadConfiguration uploadConfig = new UploadConfiguration();
+
+ private final AtomicInteger numDone = new AtomicInteger(0);
+ private final int numTotal;
+
+ @Setter
+ private boolean ignoreOldManifest;
+
+ @Setter
+ private boolean alwaysPrintLog;
+
+ public Packager(List buildList) throws IOException
+ {
+ this.buildList = buildList;
+ this.numTotal = buildList.size();
+ this.runeliteVersion = readRLVersion();
+ }
+
+ Set newManifests = Sets.newConcurrentHashSet();
+ Set remove = Sets.newConcurrentHashSet();
+
+ public void buildPlugins()
+ throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException
+ {
+ Queue buildQueue = Queues.synchronizedQueue(new ArrayDeque<>(buildList));
+ List buildThreads = IntStream.range(0, 8)
+ .mapToObj(v ->
+ {
+ Thread t = new Thread(() ->
+ {
+ for (File plugin; (plugin = buildQueue.poll()) != null; )
+ {
+ buildPlugin(plugin);
+ }
+ });
+ t.start();
+ return t;
+ }).collect(Collectors.toList());
+
+ for (Thread buildThread : buildThreads)
+ {
+ try
+ {
+ buildThread.join();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (uploadConfig.isComplete())
+ {
+ Gson gson = new Gson();
+ HttpUrl manifestURL = uploadConfig.getUploadRepoRoot().newBuilder()
+ .addPathSegment("manifest.js")
+ .build();
+
+ List manifests = new ArrayList<>();
+ if (!ignoreOldManifest)
+ {
+ try (Response res = uploadConfig.getClient().newCall(new Request.Builder()
+ .url(manifestURL)
+ .get()
+ .build())
+ .execute())
+ {
+ if (res.code() != 404)
+ {
+ Util.check(res);
+
+ BufferedSource src = res.body().source();
+
+ byte[] signature = new byte[src.readInt()];
+ src.readFully(signature);
+
+ byte[] data = src.readByteArray();
+ Signature s = Signature.getInstance("SHA256withRSA");
+ s.initVerify(uploadConfig.getCert());
+ s.update(data);
+
+ if (!s.verify(signature))
+ {
+ throw new RuntimeException("Unable to verify external plugin manifest");
+ }
+
+ manifests = gson.fromJson(new String(data, StandardCharsets.UTF_8),
+ new TypeToken>()
+ {
+ }.getType());
+ }
+ }
+ }
+
+ manifests.removeIf(m -> remove.contains(m.getInternalName()));
+ manifests.addAll(newManifests);
+ manifests.sort(Comparator.comparing(ExternalPluginManifest::getInternalName));
+
+ {
+ byte[] data = gson.toJson(manifests).getBytes(StandardCharsets.UTF_8);
+ Signature s = Signature.getInstance("SHA256withRSA");
+ s.initSign(uploadConfig.getKey());
+ s.update(data);
+ byte[] sig = s.sign();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new DataOutputStream(out).writeInt(sig.length);
+ out.write(sig);
+ out.write(data);
+ byte[] manifest = out.toByteArray();
+
+ try (Response res = uploadConfig.getClient().newCall(new Request.Builder()
+ .url(manifestURL)
+ .put(RequestBody.create(null, manifest))
+ .build())
+ .execute())
+ {
+ Util.check(res);
+ }
+ }
+
+ uploadConfig.getClient().connectionPool().evictAll();
+ }
+ }
+
+
+ private void buildPlugin(File plugin)
+ {
+ remove.add(plugin.getName());
+
+ if (!plugin.exists())
+ {
+ return;
+ }
+
+ try (Plugin p = new Plugin(plugin))
+ {
+ try
+ {
+ try (Closeable ignored = acquireDownload(p))
+ {
+ p.download();
+ }
+ try (Closeable ignored = acquireBuild(p))
+ {
+ p.build(runeliteVersion);
+ p.assembleManifest();
+ }
+ if (uploadConfig.isComplete())
+ {
+ try (Closeable ignored = acquireUpload(p))
+ {
+ p.upload(uploadConfig);
+ }
+
+ // outside the semaphore so the timing gets uploaded too
+ p.uploadLog(uploadConfig);
+ }
+
+ newManifests.add(p.getManifest());
+ log.info("{}: done in {}ms [{}/{}]", p.getInternalName(), p.getBuildTimeMS(), numDone.get() + 1, numTotal);
+ }
+ catch (PluginBuildException e)
+ {
+ p.writeLog("package failed\n", e);
+ if (!alwaysPrintLog)
+ {
+ Files.asCharSource(p.getLogFile(), StandardCharsets.UTF_8).copyTo(System.out);
+ }
+
+ if (uploadConfig.isComplete())
+ {
+ p.uploadLog(uploadConfig);
+ }
+ }
+ finally
+ {
+ if (alwaysPrintLog)
+ {
+ Files.asCharSource(p.getLogFile(), StandardCharsets.UTF_8).copyTo(System.out);
+ }
+ }
+ }
+ catch (DisabledPluginException e)
+ {
+ log.info("{}", e.getMessage());
+ }
+ catch (PluginBuildException e)
+ {
+ log.info("", e);
+ }
+ catch (Exception e)
+ {
+ log.warn("{}: crashed the build script: ", plugin.getName(), e);
+ }
+ finally
+ {
+ numDone.addAndGet(1);
+ }
+ }
+
+ private Closeable acquireDownload(Plugin plugin)
+ {
+ return section(plugin, "download", downloadSemaphore);
+ }
+
+ private Closeable acquireBuild(Plugin plugin)
+ {
+ return section(plugin, "build", buildSemaphore);
+ }
+
+ private Closeable acquireUpload(Plugin plugin)
+ {
+ return section(plugin, "upload", uploadSemaphore);
+ }
+
+ private Closeable section(Plugin p, String name, Semaphore s)
+ {
+ try
+ {
+ s.acquire();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ Stopwatch time = Stopwatch.createStarted();
+ return () ->
+ {
+ long ms = time.stop()
+ .elapsed(TimeUnit.MILLISECONDS);
+ p.setBuildTimeMS(p.getBuildTimeMS() + ms);
+ p.writeLog("{}: {}ms\n", name, ms);
+ s.release();
+ };
+ }
+
+ public static String readRLVersion() throws IOException
+ {
+ return Files.asCharSource(new File("./runelite.version"), StandardCharsets.UTF_8).read().trim();
+ }
+
+
+ public static void main(String... args) throws Exception
+ {
+ boolean isBuildingAll = false;
+ List buildList;
+ if (args.length != 0)
+ {
+ buildList = Stream.of(args)
+ .map(File::new)
+ .collect(Collectors.toList());
+ }
+ else if ("ALL".equals(System.getenv("FORCE_BUILD")))
+ {
+ buildList = listAllPlugins();
+ }
+ else if (!Strings.isNullOrEmpty(System.getenv("FORCE_BUILD")))
+ {
+ buildList = StreamSupport.stream(
+ Splitter.on(',')
+ .trimResults()
+ .omitEmptyStrings()
+ .split(System.getenv("FORCE_BUILD"))
+ .spliterator(), false)
+ .map(name -> new File(PLUGIN_ROOT, name))
+ .collect(Collectors.toList());
+ }
+ else if (!Strings.isNullOrEmpty(System.getenv("PACKAGE_COMMIT_RANGE")))
+ {
+ Process gitdiff = new ProcessBuilder("git", "diff", "--name-only", System.getenv("PACKAGE_COMMIT_RANGE"))
+ .redirectError(ProcessBuilder.Redirect.INHERIT)
+ .start();
+
+ boolean doAll = false;
+ boolean doPackageTests = false;
+ buildList = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(gitdiff.getInputStream())))
+ {
+ for (String line; (line = br.readLine()) != null; )
+ {
+ if ("runelite.version".equals(line))
+ {
+ doAll = true;
+ }
+ else if (line.startsWith("plugins/"))
+ {
+ buildList.add(new File(line));
+ }
+ else if (line.startsWith("package/") || line.startsWith("templateplugin/") || line.startsWith("create_new_plugin.py"))
+ {
+ doPackageTests = true;
+ }
+ }
+ }
+
+ if (doPackageTests)
+ {
+ new ProcessBuilder(new File(PACKAGE_ROOT, "gradlew").getAbsolutePath(), "--console=plain", "test")
+ .directory(PACKAGE_ROOT)
+ .inheritIO()
+ .start()
+ .waitFor();
+ }
+
+ if (doAll)
+ {
+ isBuildingAll = true;
+ buildList = listAllPlugins();
+ }
+
+ gitdiff.waitFor(1, TimeUnit.SECONDS);
+ if (gitdiff.exitValue() != 0)
+ {
+ throw new RuntimeException("git diff exited with " + gitdiff.exitValue());
+ }
+ }
+ else
+ {
+ throw new RuntimeException("missing env vars");
+ }
+
+ Packager pkg = new Packager(buildList);
+ pkg.getUploadConfig().fromEnvironment(pkg.getRuneliteVersion());
+ pkg.setAlwaysPrintLog(!pkg.getUploadConfig().isComplete());
+ pkg.setIgnoreOldManifest(isBuildingAll);
+ pkg.buildPlugins();
+ }
+
+ static List listAllPlugins()
+ {
+ return Arrays.asList(PLUGIN_ROOT.listFiles());
+ }
+}
diff --git a/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java b/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java
new file mode 100644
index 000000000..be102dbd8
--- /dev/null
+++ b/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2020 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;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.MoreFiles;
+import com.google.common.io.RecursiveDeleteOption;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+import javax.imageio.ImageIO;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import okhttp3.HttpUrl;
+import org.gradle.tooling.CancellationTokenSource;
+import org.gradle.tooling.GradleConnectionException;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.ResultHandler;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
+
+public class Plugin implements Closeable
+{
+ private static final Pattern PLUGIN_INTERNAL_NAME_TEST = Pattern.compile("^[a-z0-9-]+$");
+ private static final Pattern REPOSITORY_TEST = Pattern.compile("^https://github\\.com/.*\\.git$");
+ private static final Pattern COMMIT_TEST = Pattern.compile("^[a-fA-F0-9]{40}$");
+
+ private static final File TMP_ROOT;
+ private static final File GRADLE_HOME;
+
+ static
+ {
+ ImageIO.setUseCache(false);
+
+ try
+ {
+ TMP_ROOT = Files.createTempDirectory("pluginhub-package").toFile();
+ TMP_ROOT.deleteOnExit();
+
+ GRADLE_HOME = new File(com.google.common.io.Files.asCharSource(new File(Packager.PACKAGE_ROOT, "build/gradleHome"), StandardCharsets.UTF_8).read().trim());
+ if (!GRADLE_HOME.exists())
+ {
+ throw new RuntimeException("gradle home has moved");
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Getter
+ private final String internalName;
+
+ private final File buildDirectory;
+
+ @VisibleForTesting
+ final File repositoryDirectory;
+
+ private final File jarFile;
+ private final File iconFile;
+
+ @Getter
+ private final File logFile;
+
+ @Getter
+ private FileOutputStream log;
+
+ @Nullable
+ private final String warning;
+
+ private final String repositoryURL;
+ private final String commit;
+
+ @Getter
+ private final ExternalPluginManifest manifest = new ExternalPluginManifest();
+
+ @Getter
+ @Setter
+ private long buildTimeMS;
+
+ public Plugin(File pluginCommitDescriptor) throws IOException, DisabledPluginException, PluginBuildException
+ {
+ internalName = pluginCommitDescriptor.getName();
+ if (!PLUGIN_INTERNAL_NAME_TEST.matcher(internalName).matches())
+ {
+ throw PluginBuildException.of(internalName, "invalid plugin file name \"{}\"", internalName)
+ .withHelp("plugin file names must be lowercase alphanumeric + dashes. try: \""
+ + internalName.toLowerCase().replaceAll("[^a-z0-9]+", "-") + "\"")
+ .withFile(pluginCommitDescriptor);
+ }
+
+ Properties cd = loadProperties(pluginCommitDescriptor);
+
+ String disabled = cd.getProperty("disabled");
+ if (!Strings.isNullOrEmpty(disabled))
+ {
+ throw new DisabledPluginException(internalName, disabled);
+ }
+
+ repositoryURL = (String) cd.remove("repository");
+ if (repositoryURL == null)
+ {
+ throw PluginBuildException.of(internalName, "repository is missing from {}", pluginCommitDescriptor)
+ .withFile(pluginCommitDescriptor);
+ }
+
+ if (!REPOSITORY_TEST.matcher(repositoryURL).matches())
+ {
+ throw PluginBuildException.of(internalName, "repository is not an accepted url")
+ .withFileLine(pluginCommitDescriptor, "repository=" + repositoryURL)
+ .withHelp(() ->
+ {
+ if (!repositoryURL.startsWith("https"))
+ {
+ return "repositories must be https clone urls, not git:";
+ }
+ if (!repositoryURL.contains("github"))
+ {
+ return "repositories must be hosted on GitHub.com";
+ }
+ if (!repositoryURL.endsWith(".git"))
+ {
+ return "repository must be a clone url ~ it should end with .git";
+ }
+ return null;
+ });
+ }
+
+ commit = (String) cd.remove("commit");
+ if (!COMMIT_TEST.matcher(commit).matches())
+ {
+ throw PluginBuildException.of(internalName, "commit must be a full 40 character sha1sum")
+ .withFileLine(pluginCommitDescriptor, "commit=" + commit);
+ }
+
+ warning = (String) cd.remove("warning");
+
+ for (Map.Entry