package: rewrite build scripts (#701)

not using bash gives us more flexibility and significantly reduces build
times via parallelism. Additionally this includes support for GitHub
Actions which also significantly reduces build times.
This commit is contained in:
Abex
2020-10-26 15:58:39 -06:00
committed by GitHub
parent 0144c7aaef
commit df657e20f5
29 changed files with 2670 additions and 438 deletions

58
.github/workflows/build.yml vendored Normal file
View File

@@ -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

View File

@@ -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
script: ./package/travis.sh

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;
}

4
package/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.gradle/
.idea/
build
profile.svg

59
package/build.gradle Normal file
View File

@@ -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)
}
}

View File

@@ -0,0 +1,2 @@
# this should match what Plugin uses to build with
org.gradle.jvmargs=-Xmx768M -XX:+UseParallelGC

View File

@@ -0,0 +1,373 @@
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.0.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
<trusted-artifacts>
<trust file=".*-javadoc[.]jar" regex="true"/>
<trust file=".*-sources[.]jar" regex="true"/>
</trusted-artifacts>
</configuration>
<components>
<component group="com.github.jengelman.gradle.plugins" name="shadow" version="6.1.0">
<artifact name="shadow-6.1.0.jar">
<sha256 value="b66cb33a1d204ffaa1ba67393bdddbe9ff517f24f4438d11c341423868759aa3" origin="Generated by Gradle"/>
</artifact>
<artifact name="shadow-6.1.0.pom">
<sha256 value="d40c29bce31762b6c8539a87d2515324f44db9d7d579a5aa7016a15ce164abb8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.johnrengelman.shadow" name="com.github.johnrengelman.shadow.gradle.plugin" version="6.1.0">
<artifact name="com.github.johnrengelman.shadow.gradle.plugin-6.1.0.pom">
<sha256 value="d79cec882e8f6870d9872bc10d1a4f80630308b64a91ba0630cd5e1fb4dfd05b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.code.findbugs" name="jsr305" version="3.0.2">
<artifact name="jsr305-3.0.2.jar">
<sha256 value="766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7" origin="Generated by Gradle"/>
</artifact>
<artifact name="jsr305-3.0.2.pom">
<sha256 value="19889dbdf1b254b2601a5ee645b8147a974644882297684c798afe5d63d78dfe" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.code.gson" name="gson" version="2.8.5">
<artifact name="gson-2.8.5.jar">
<sha256 value="233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81" origin="Generated by Gradle"/>
</artifact>
<artifact name="gson-2.8.5.pom">
<sha256 value="b8308557a7fccc92d9fe7c8cd0599258b361285d2ecde7689eda98843255a092" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.code.gson" name="gson-parent" version="2.8.5">
<artifact name="gson-parent-2.8.5.pom">
<sha256 value="8f1fec72b91a71ea39ec39f5f778c4d1124b6b097c6d55b3a50b554a52237b27" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.errorprone" name="error_prone_annotations" version="2.0.18">
<artifact name="error_prone_annotations-2.0.18.jar">
<sha256 value="cb4cfad870bf563a07199f3ebea5763f0dec440fcda0b318640b1feaa788656b" origin="Generated by Gradle"/>
</artifact>
<artifact name="error_prone_annotations-2.0.18.pom">
<sha256 value="9144127192d6f612c2366825dceaeb23b0d53130b83e0bf1ffe107d1470a8487" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.errorprone" name="error_prone_parent" version="2.0.18">
<artifact name="error_prone_parent-2.0.18.pom">
<sha256 value="cf149955279b07d4f11e817985c1164a69e930d73db7441b43a6ef53bbd286c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava" version="23.2-jre">
<artifact name="guava-23.2-jre.jar">
<sha256 value="5be9a7d05ba0ccd74708bc8018ae412255f85843c0b92302e9b9befa6ed52564" origin="Generated by Gradle"/>
</artifact>
<artifact name="guava-23.2-jre.pom">
<sha256 value="57abac7e3962e45ad1c8645d91cdad96e1e0bf23b8b38e6f389011f7ae496870" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava-parent" version="23.2-jre">
<artifact name="guava-parent-23.2-jre.pom">
<sha256 value="e92cd52f3b86a0f6fe90fc94a86ea072a2b112fc40063bd33f6679778038cc47" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.j2objc" name="j2objc-annotations" version="1.1">
<artifact name="j2objc-annotations-1.1.jar">
<sha256 value="2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6" origin="Generated by Gradle"/>
</artifact>
<artifact name="j2objc-annotations-1.1.pom">
<sha256 value="f0c98c571e93a7cb4dd18df0fa308f0963e7a0620ac2d4244e61e709d03ad6be" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="mockwebserver" version="3.14.9">
<artifact name="mockwebserver-3.14.9.jar">
<sha256 value="099f70ab2997f628bf93c358baac829ccb625b6e08a9361e09d6923fa9af1f68" origin="Generated by Gradle"/>
</artifact>
<artifact name="mockwebserver-3.14.9.pom">
<sha256 value="24fc2ed2c0d80c7461e24f4dc3f873381a5a77246cf99a57988d1b389b091fd6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="okhttp" version="3.14.9">
<artifact name="okhttp-3.14.9.jar">
<sha256 value="2570fab55515cbf881d7a4ceef49fc515490bc027057e666776a2832465aeca0" origin="Generated by Gradle"/>
</artifact>
<artifact name="okhttp-3.14.9.pom">
<sha256 value="61edf3c4764ea95f1212ca2dfbc6ea5d4133c71c4e8c40bd247ec83e7c6b7ce7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="parent" version="3.14.9">
<artifact name="parent-3.14.9.pom">
<sha256 value="b5f101dcf2cc7f32ec3d7b68bdc078e77eac1979bb8664e8f87f762288c3dce5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio" version="1.17.2">
<artifact name="okio-1.17.2.jar">
<sha256 value="f80ce42d2ffac47ad4c47e1d6f980d604d247ceb1a886705cf4581ab0c9fe2b8" origin="Generated by Gradle"/>
</artifact>
<artifact name="okio-1.17.2.pom">
<sha256 value="cd57f75443ab6a714b203da51994ee64ed0e919fea81f04e4f4b5324e4bb03f0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio-parent" version="1.17.2">
<artifact name="okio-parent-1.17.2.pom">
<sha256 value="e81f40146f2a0eb394b10fa3a175c85ab9fbe757aa418aa44d4b760efe16f024" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="commons-io" name="commons-io" version="2.5">
<artifact name="commons-io-2.5.pom">
<sha256 value="28ebb2998bc7d7acb25078526971640892000f3413586ff42d611f1043bfec30" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="commons-io" name="commons-io" version="2.6">
<artifact name="commons-io-2.6.jar">
<sha256 value="f877d304660ac2a142f3865badfc971dec7ed73c747c7f8d5d2f5139ca736513" origin="Generated by Gradle"/>
</artifact>
<artifact name="commons-io-2.6.pom">
<sha256 value="0c23863893a2291f5a7afdbd8d15923b3948afd87e563fa341cdcf6eae338a60" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="junit" name="junit" version="4.12">
<artifact name="junit-4.12.jar">
<sha256 value="59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a" origin="Generated by Gradle"/>
</artifact>
<artifact name="junit-4.12.pom">
<sha256 value="90f163f78e3ffb6f1c7ad97de9e7eba4eea25807141b85d6d12be67ca25449c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="16">
<artifact name="apache-16.pom">
<sha256 value="9f85ff2fd7d6cb3097aa47fb419ee7f0ebe869109f98aba9f4eca3f49e74a40e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="18">
<artifact name="apache-18.pom">
<sha256 value="7831307285fd475bbc36b20ae38e7882f11c3153b1d5930f852d44eda8f33c17" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="21">
<artifact name="apache-21.pom">
<sha256 value="af10c108da014f17cafac7b52b2b4b5a3a1c18265fa2af97a325d9143537b380" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.ant" name="ant" version="1.9.7">
<artifact name="ant-1.9.7.jar">
<sha256 value="9a5dbe3f5f2cb91854c8682cab80178afa412ab35a5ab718bf39ce01b3435d93" origin="Generated by Gradle"/>
</artifact>
<artifact name="ant-1.9.7.pom">
<sha256 value="1b9fbd4f325a71e99b279080d63084f12d884d42081af298f9e553e1fe0cd74a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.ant" name="ant-launcher" version="1.9.7">
<artifact name="ant-launcher-1.9.7.jar">
<sha256 value="bc376f6d6cb586229f451ac459faf1443b144c26d6647618ec9cba60e54c2b79" origin="Generated by Gradle"/>
</artifact>
<artifact name="ant-launcher-1.9.7.pom">
<sha256 value="d7bcdd3ab0ff55edbe1b96d06f06dac2135ec63b5a7c32cef3a436b49c9eee27" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.ant" name="ant-parent" version="1.9.7">
<artifact name="ant-parent-1.9.7.pom">
<sha256 value="75d2cef64c65ccbdd2faf7261e53b444778d56d338763154e30fada4a41d1215" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-parent" version="39">
<artifact name="commons-parent-39.pom">
<sha256 value="87cd27e1a02a5c3eb6d85059ce98696bb1b44c2b8b650f0567c86df60fa61da7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-parent" version="42">
<artifact name="commons-parent-42.pom">
<sha256 value="cd313494c670b483ec256972af1698b330e598f807002354eb765479f604b09c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.logging.log4j" name="log4j" version="2.13.3">
<artifact name="log4j-2.13.3.pom">
<sha256 value="674f1fa5165b9d48935f4103d9316fe5b161dff6f9be904a6edb9baa33da4480" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.logging.log4j" name="log4j-api" version="2.13.3">
<artifact name="log4j-api-2.13.3.jar">
<sha256 value="2b4b1965c9dce7f3732a0fbf5c8493199c1e6bf8cf65c3e235b57d98da5f36af" origin="Generated by Gradle"/>
</artifact>
<artifact name="log4j-api-2.13.3.pom">
<sha256 value="5953807d4e4fd4d7ae8087b5a76660236e55e718fcad62cf8a7adedc2ddc5a6e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.logging.log4j" name="log4j-core" version="2.13.3">
<artifact name="log4j-core-2.13.3.jar">
<sha256 value="9529c55814264ab96b0eeba2920ac0805170969c994cc479bd3d4d7eb24a35a8" origin="Generated by Gradle"/>
</artifact>
<artifact name="log4j-core-2.13.3.pom">
<sha256 value="ec5592381a9b37e5054a91fcaf79e3c2c4582eee3574d9ad8a022afbd5b5a3fb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus" name="codehaus-parent" version="4">
<artifact name="codehaus-parent-4.pom">
<sha256 value="6b87237de8c2e1740cf80627c7f3ce3e15de1930bb250c55a1eca94fa3e014df" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.mojo" name="animal-sniffer-annotations" version="1.14">
<artifact name="animal-sniffer-annotations-1.14.jar">
<sha256 value="2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d" origin="Generated by Gradle"/>
</artifact>
<artifact name="animal-sniffer-annotations-1.14.pom">
<sha256 value="1879f19a05991e3ed95910b96689333396b0c467a215dc4d1f90018404b72a26" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.mojo" name="animal-sniffer-parent" version="1.14">
<artifact name="animal-sniffer-parent-1.14.pom">
<sha256 value="f51550a06b1410bd4962cb0e71df0b921a60a7ef47bfa9c4825a14be72316eea" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.mojo" name="mojo-parent" version="34">
<artifact name="mojo-parent-34.pom">
<sha256 value="3e395d6fbc43c09a3774cac8694ce527398305ea3fd5492d80e25af27d382a9c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus" version="4.0">
<artifact name="plexus-4.0.pom">
<sha256 value="0a1b692d7fcc90d6a45dae2e50f4660d48f7a44504f174aa60ef34fbe1327f6a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus-utils" version="3.0.24">
<artifact name="plexus-utils-3.0.24.jar">
<sha256 value="83ee748b12d06afb0ad4050a591132b3e8025fbb1990f1ed002e8b73293e69b4" origin="Generated by Gradle"/>
</artifact>
<artifact name="plexus-utils-3.0.24.pom">
<sha256 value="11067f6a75fded12bcdc8daf7a66ddd942ce289c3daf88a3fe0f8b12858a2ee6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.gradle" name="gradle-tooling-api" version="6.6.1">
<artifact name="gradle-tooling-api-6.6.1.jar">
<sha256 value="38b7a9317a06a0f3797f972462d198d8b370faa1aecc5832fba736c4c99aa3b2" origin="Generated by Gradle"/>
</artifact>
<artifact name="gradle-tooling-api-6.6.1.module">
<sha256 value="41817723d2b2ff73f7fde1e7a927593a201b31348bcbfaff8322dbdf0d3f38b6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hamcrest" name="hamcrest-core" version="1.3">
<artifact name="hamcrest-core-1.3.jar">
<sha256 value="66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9" origin="Generated by Gradle"/>
</artifact>
<artifact name="hamcrest-core-1.3.pom">
<sha256 value="fde386a7905173a1b103de6ab820727584b50d0e32282e2797787c20a64ffa93" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.hamcrest" name="hamcrest-parent" version="1.3">
<artifact name="hamcrest-parent-1.3.pom">
<sha256 value="6d535f94efb663bdb682c9f27a50335394688009642ba7a9677504bc1be4129b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jdom" name="jdom2" version="2.0.6">
<artifact name="jdom2-2.0.6.jar">
<sha256 value="1345f11ba606d15603d6740551a8c21947c0215640770ec67271fe78bea97cf5" origin="Generated by Gradle"/>
</artifact>
<artifact name="jdom2-2.0.6.pom">
<sha256 value="47b23a79fe336b741b82434c6e049d68165256e405e75c10921fd72fa8a65d8d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2" name="ow2" version="1.5">
<artifact name="ow2-1.5.pom">
<sha256 value="0f8a1b116e760b8fe6389c51b84e4b07a70fc11082d4f936e453b583dd50b43b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm" version="7.0">
<artifact name="asm-7.0.jar">
<sha256 value="b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf" origin="Generated by Gradle"/>
</artifact>
<artifact name="asm-7.0.pom">
<sha256 value="83f65b1083d5ce4f8ba7f9545cfe9ff17824589c9a7cc82c3a4695801e4f5f68" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm" version="9.0">
<artifact name="asm-9.0.jar">
<sha256 value="0df97574914aee92fd349d0cb4e00f3345d45b2c239e0bb50f0a90ead47888e0" origin="Generated by Gradle"/>
</artifact>
<artifact name="asm-9.0.module">
<sha256 value="8af81096ed3affa39a4729fc900a55b663894911d67c4d4bef0ea424393dd3f9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm-analysis" version="9.0">
<artifact name="asm-analysis-9.0.jar">
<sha256 value="2d46de6df856a4daac9aa534459ab7287eb80584e9109850405e5b302dc9c2a6" origin="Generated by Gradle"/>
</artifact>
<artifact name="asm-analysis-9.0.module">
<sha256 value="6e02aafb4637979c7cdc3daa3047a88b40f3e1071bdbbd0c7f1cd5da7ac38454" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm-commons" version="9.0">
<artifact name="asm-commons-9.0.jar">
<sha256 value="1b9090acb7e67bd4ed2f2cfb002063316d79cecace237bd07cc4f7f1b302092f" origin="Generated by Gradle"/>
</artifact>
<artifact name="asm-commons-9.0.module">
<sha256 value="58880e03e9196f566c998186f58bd0af41c77a04ba841664f80377ba0665f97c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm-tree" version="9.0">
<artifact name="asm-tree-9.0.jar">
<sha256 value="e2c25f332eb95861883a8568e45aac5e77d140d0fe961ae8eb9a474ec876e00d" origin="Generated by Gradle"/>
</artifact>
<artifact name="asm-tree-9.0.module">
<sha256 value="b38cbdd2c47fa4f29ab680c18954c7216d0afd28692221cd288c1cc7b9d9641c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.projectlombok" name="lombok" version="1.18.4">
<artifact name="lombok-1.18.4.jar">
<sha256 value="39f3922deb679b1852af519eb227157ef2dd0a21eec3542c8ce1b45f2df39742" origin="Generated by Gradle"/>
</artifact>
<artifact name="lombok-1.18.4.pom">
<sha256 value="2eaf8fc3d7a9c2ce18ec874bc4674f7ab492b2121143342321e820eea12ffa2f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.slf4j" name="slf4j-api" version="1.7.10">
<artifact name="slf4j-api-1.7.10.jar">
<sha256 value="3863e27005740d4d1289bf87b113efea115e9a22408a7d623be8004991232bfe" origin="Generated by Gradle"/>
</artifact>
<artifact name="slf4j-api-1.7.10.pom">
<sha256 value="af9c5e8d2263422c74792ddd91b3cc1a24bd02b451b54cbb10cd6f2ba46c14b1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.slf4j" name="slf4j-api" version="1.7.28">
<artifact name="slf4j-api-1.7.28.jar">
<sha256 value="fb6e4f67a2a4689e3e713584db17a5d1090c1ebe6eec30e9e0349a6ee118141e" origin="Generated by Gradle"/>
</artifact>
<artifact name="slf4j-api-1.7.28.pom">
<sha256 value="61f10feac576665b68caa6170cd423e8fb00055f1fad7ad9d7de2150e5f15caa" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.slf4j" name="slf4j-parent" version="1.7.10">
<artifact name="slf4j-parent-1.7.10.pom">
<sha256 value="1abee7f5182fb79b4926fb64658af8c3248ed6b374f9ac7da1fd9e8b9197e2ce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.slf4j" name="slf4j-parent" version="1.7.28">
<artifact name="slf4j-parent-1.7.28.pom">
<sha256 value="919b5f42dde33ace036865d1e2b292d98a0627417ff756e0287f4a56ad3e544e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.slf4j" name="slf4j-simple" version="1.7.10">
<artifact name="slf4j-simple-1.7.10.jar">
<sha256 value="3209a859bb47873c0d1873f7d69aa6652dc07fe32ed935492fe0b1cf1d2cf140" origin="Generated by Gradle"/>
</artifact>
<artifact name="slf4j-simple-1.7.10.pom">
<sha256 value="bbd8c6f5cf54d1c1fb11b415e51f6b35483fc264f81ccd98654cc3420e756901" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonatype.forge" name="forge-parent" version="10">
<artifact name="forge-parent-10.pom">
<sha256 value="c14fb9c32b59cc03251f609416db7c0cff01f811edcccb4f6a865d6e7046bd0b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonatype.oss" name="oss-parent" version="7">
<artifact name="oss-parent-7.pom">
<sha256 value="b51f8867c92b6a722499557fc3a1fdea77bdf9ef574722fe90ce436a29559454" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.vafer" name="jdependency" version="2.1.1">
<artifact name="jdependency-2.1.1.jar">
<sha256 value="642d23a86217850721d9fa80671683d8308fd03114f0da7af553d43b82013a09" origin="Generated by Gradle"/>
</artifact>
<artifact name="jdependency-2.1.1.pom">
<sha256 value="4a139306cbe0aa3765bd9fd837a71253a911a9c4e55c50e062a4bd6843ee19a1" origin="Generated by Gradle"/>
</artifact>
</component>
</components>
</verification-metadata>

View File

Binary file not shown.

View File

@@ -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

172
package/gradlew vendored Executable file
View File

@@ -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" "$@"

84
package/gradlew.bat vendored Normal file
View File

@@ -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

View File

@@ -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"
}

View File

@@ -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, "../")
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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<File> 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<File> buildList) throws IOException
{
this.buildList = buildList;
this.numTotal = buildList.size();
this.runeliteVersion = readRLVersion();
}
Set<ExternalPluginManifest> newManifests = Sets.newConcurrentHashSet();
Set<String> remove = Sets.newConcurrentHashSet();
public void buildPlugins()
throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException
{
Queue<File> buildQueue = Queues.synchronizedQueue(new ArrayDeque<>(buildList));
List<Thread> 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<ExternalPluginManifest> 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<List<ExternalPluginManifest>>()
{
}.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<File> 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<File> listAllPlugins()
{
return Arrays.asList(PLUGIN_ROOT.listFiles());
}
}

View File

@@ -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<Object, Object> extra : cd.entrySet())
{
throw PluginBuildException.of(internalName, "unexpected key in commit descriptor")
.withFileLine(pluginCommitDescriptor, extra.getKey() + "=" + extra.getValue());
}
buildDirectory = new File(TMP_ROOT, internalName);
if (!buildDirectory.mkdirs())
{
throw new RuntimeException("Unable to create temp directory");
}
repositoryDirectory = new File(buildDirectory, "repo");
logFile = new File(buildDirectory, "log");
log = new FileOutputStream(logFile, true);
jarFile = new File(buildDirectory, "plugin.jar");
iconFile = new File(repositoryDirectory, "icon.png");
}
public void download() throws IOException, PluginBuildException
{
Process gitclone = new ProcessBuilder("git", "clone", "--config", "advice.detachedHead=false", this.repositoryURL, repositoryDirectory.getAbsolutePath())
.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
.redirectError(ProcessBuilder.Redirect.appendTo(logFile))
.start();
Util.waitAndCheck(this, gitclone, "git clone", 2, TimeUnit.MINUTES);
Process gitcheckout = new ProcessBuilder("git", "checkout", commit + "^{commit}")
.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
.redirectError(ProcessBuilder.Redirect.appendTo(logFile))
.directory(repositoryDirectory)
.start();
Util.waitAndCheck(this, gitcheckout, "git checkout", 2, TimeUnit.MINUTES);
}
public void build(String runeliteVersion) throws IOException, PluginBuildException
{
try (ProjectConnection con = GradleConnector.newConnector()
.forProjectDirectory(repositoryDirectory)
.useInstallation(GRADLE_HOME)
.connect())
{
CancellationTokenSource cancel = GradleConnector.newCancellationTokenSource();
BlockingQueue<Object> queue = new ArrayBlockingQueue<>(1);
String buildSuccess = "success";
con.newBuild()
.withArguments(
"--no-build-cache",
"--console=plain",
"--init-script", new File("./package/target_init.gradle").getAbsolutePath())
.setEnvironmentVariables(ImmutableMap.of(
"runelite.pluginhub.package.lib", new File(Packager.PACKAGE_ROOT, "initLib/build/libs/initLib.jar").toString(),
"runelite.pluginhub.package.buildDir", buildDirectory.getAbsolutePath(),
"runelite.pluginhub.package.runeliteVersion", runeliteVersion))
.setJvmArguments("-Xmx768M", "-XX:+UseParallelGC")
.setStandardOutput(log)
.setStandardError(log)
.forTasks("runelitePluginHubPackage", "runelitePluginHubManifest")
.withCancellationToken(cancel.token())
.run(new ResultHandler<Void>()
{
@Override
public void onComplete(Void result)
{
queue.add(buildSuccess);
}
@Override
public void onFailure(GradleConnectionException failure)
{
queue.add(failure);
}
});
log.flush();
Object output = queue.poll(5, TimeUnit.MINUTES);
if (output == null)
{
cancel.cancel();
throw PluginBuildException.of(this, "build did not complete within 5 minutes");
}
if (output == buildSuccess)
{
return;
}
else if (output instanceof GradleConnectionException)
{
throw PluginBuildException.of(this, "build failed", output);
}
throw new IllegalStateException(output.toString());
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
public void assembleManifest() throws IOException, PluginBuildException
{
manifest.setInternalName(internalName);
manifest.setCommit(commit);
manifest.setWarning(warning);
{
Properties chunk = loadProperties(new File(buildDirectory, "chunk.properties"));
manifest.setVersion(chunk.getProperty("version"));
if (Strings.isNullOrEmpty(manifest.getVersion()))
{
throw new IllegalStateException("version in empty");
}
}
{
long size = jarFile.length();
if (size > 10 * 1024 * 1024)
{
throw PluginBuildException.of(this, "the output jar is {}MiB, which is above our limit of 10MiB", size / (1024 * 1024));
}
manifest.setSize((int) size);
}
manifest.setHash(com.google.common.io.Files.asByteSource(jarFile)
.hash(Hashing.sha256())
.toString());
if (iconFile.exists())
{
long size = iconFile.length();
if (size > 256 * 1024)
{
throw PluginBuildException.of(this, "icon.png is {}KiB, which is above our limit of 256KiB", size / 1024)
.withFile(iconFile);
}
synchronized (ImageIO.class)
{
try
{
Objects.requireNonNull(ImageIO.read(iconFile));
}
catch (Exception e)
{
throw PluginBuildException.of(this, "icon is invalid", e)
.withFile(iconFile);
}
}
manifest.setHasIcon(true);
}
Set<String> pluginClasses = new HashSet<>();
Set<String> jarClasses = new HashSet<>();
{
try (JarInputStream jis = new JarInputStream(new FileInputStream(jarFile)))
{
for (JarEntry je; (je = jis.getNextJarEntry()) != null; )
{
String fileName = je.getName();
if (!fileName.endsWith(".class"))
{
continue;
}
byte[] classData = ByteStreams.toByteArray(jis);
new ClassReader(classData).accept(new ClassVisitor(Opcodes.ASM7)
{
boolean extendsPlugin;
String name;
@SneakyThrows
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if (version > Opcodes.V1_8 && !fileName.startsWith("META-INF/versions"))
{
throw PluginBuildException.of(Plugin.this, "plugins must be Java 1.8 compatible")
.withFile(fileName);
}
jarClasses.add(name.replace('/', '.'));
extendsPlugin = "net/runelite/client/plugins/Plugin".equals(superName);
this.name = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
{
if ("Lnet/runelite/client/plugins/PluginDescriptor;".equals(descriptor) && extendsPlugin)
{
pluginClasses.add(name.replace('/', '.'));
}
return null;
}
}, ClassReader.SKIP_FRAMES);
}
}
}
{
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");
if (Strings.isNullOrEmpty(displayName))
{
throw PluginBuildException.of(this, "\"displayName\" must be set")
.withFile(propFile);
}
manifest.setDisplayName(displayName);
}
{
String author = (String) props.remove("author");
if (Strings.isNullOrEmpty(author))
{
throw PluginBuildException.of(this, "\"author\" must be set")
.withFile(propFile);
}
manifest.setAuthor(author);
}
{
String supportStr = (String) props.remove("support");
if (!Strings.isNullOrEmpty(supportStr))
{
try
{
manifest.setSupport(new URL(supportStr));
}
catch (MalformedURLException e)
{
throw PluginBuildException.of(this, "support url is malformed", e)
.withFileLine(propFile, "support=" + supportStr);
}
}
}
manifest.setDescription((String) props.remove("description"));
{
String tagsStr = (String) props.remove("tags");
if (!Strings.isNullOrEmpty(tagsStr))
{
manifest.setTags(Splitter.on(",")
.omitEmptyStrings()
.trimResults()
.splitToList(tagsStr)
.toArray(new String[0]));
}
}
{
String pluginsStr = (String) props.remove("plugins");
if (pluginsStr == null)
{
throw PluginBuildException.of(this, "\"plugins\" must be set")
.withFile(propFile);
}
List<String> plugins = Splitter.on(CharMatcher.anyOf(",:;"))
.omitEmptyStrings()
.trimResults()
.splitToList(pluginsStr);
manifest.setPlugins(plugins.toArray(new String[0]));
for (String className : plugins)
{
if (pluginClasses.contains(className))
{
continue;
}
if (jarClasses.contains(className))
{
throw PluginBuildException.of(this, "Plugin class \"{}\" is not a valid Plugin", className)
.withHelp("All plugins must extend Plugin an have an @PluginDescriptor")
.withFileLine(propFile, "plugins=" + pluginsStr);
}
Set<String> unusedPlugins = new HashSet<>(pluginClasses);
unusedPlugins.removeAll(plugins);
throw PluginBuildException.of(this,
"Plugin class \"{}\" is missing from the output jar", className)
.withHelp(unusedPlugins.isEmpty()
? "All plugins must extend Plugin an have an @PluginDescriptor"
: ("Perhaps you wanted " + String.join(", ", unusedPlugins)))
.withFileLine(propFile, "plugins=" + pluginsStr);
}
}
if (props.size() != 0)
{
writeLog("warning: unused props in runelite-plugin.properties: {}\n", props.keySet());
}
}
}
public void upload(UploadConfiguration uploadConfig) throws IOException
{
HttpUrl pluginRoot = uploadConfig.getUploadRepoRoot().newBuilder()
.addPathSegment(internalName)
.build();
uploadConfig.put(
pluginRoot.newBuilder().addPathSegment(commit + ".jar").build(),
jarFile);
if (manifest.isHasIcon())
{
uploadConfig.put(
pluginRoot.newBuilder().addPathSegment(commit + ".png").build(),
iconFile);
}
}
public void uploadLog(UploadConfiguration uploadConfig) throws IOException
{
try
{
log.close();
log = null;
}
catch (IOException ignored)
{
}
uploadConfig.put(uploadConfig.getUploadRepoRoot()
.newBuilder()
.addPathSegment(internalName)
.addPathSegment(commit + ".log")
.build(),
logFile);
}
public void writeLog(String format, Object... args) throws IOException
{
FormattingTuple fmt = MessageFormatter.arrayFormat(format, args);
log.write(fmt.getMessage().getBytes(StandardCharsets.UTF_8));
Throwable t = fmt.getThrowable();
if (t != null)
{
PrintWriter pw = new PrintWriter(new OutputStreamWriter(log, StandardCharsets.UTF_8));
pw.println(t.getMessage());
t.printStackTrace(pw);
pw.flush();
}
log.flush();
}
static Properties loadProperties(File path) throws IOException
{
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(path))
{
props.load(fis);
}
return props;
}
@Override
public void close() throws IOException
{
if (log != null)
{
log.close();
}
MoreFiles.deleteRecursively(buildDirectory.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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 java.io.File;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.function.Supplier;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;
public class PluginBuildException extends Exception
{
private String file;
private String line;
private String help;
private PluginBuildException(String message, Throwable throwable)
{
super(message, throwable);
}
public static PluginBuildException of(String internalName, String message, Object... args)
{
FormattingTuple fmt = MessageFormatter.arrayFormat(internalName + ": " + message, args);
return new PluginBuildException(fmt.getMessage(), fmt.getThrowable());
}
public static PluginBuildException of(Plugin plugin, String message, Object... args)
{
FormattingTuple fmt = MessageFormatter.arrayFormat(plugin.getInternalName() + ": " + message, args);
return new PluginBuildException(fmt.getMessage(), fmt.getThrowable());
}
public PluginBuildException withFileLine(File file, String line)
{
return withFileLine(file.toString(), line);
}
public PluginBuildException withFile(File file)
{
return withFileLine(file, null);
}
public PluginBuildException withFileLine(String file, String line)
{
this.file = file;
this.line = line;
return this;
}
public PluginBuildException withFile(String file)
{
return withFileLine(file, null);
}
public PluginBuildException withHelp(String help)
{
this.help = help;
return this;
}
public PluginBuildException withHelp(Supplier<String> help)
{
this.help = help.get();
return this;
}
public String getHelpText()
{
StringBuilder sb = new StringBuilder();
if (this.file != null)
{
sb.append("in file ").append(this.file);
if (this.line != null)
{
sb.append(":\n").append(line);
}
sb.append("\n");
}
if (this.help != null)
{
sb.append(help);
}
return sb.toString();
}
@Override
public void printStackTrace(PrintStream s)
{
super.printStackTrace(s);
s.println("\n");
s.println(getMessage());
s.println(getHelpText());
}
@Override
public void printStackTrace(PrintWriter s)
{
super.printStackTrace(s);
s.println("\n");
s.println(getMessage());
s.println(getHelpText());
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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.Strings;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@Getter
@Accessors(chain = true)
public class UploadConfiguration
{
private RSAPrivateCrtKey key;
private PublicKey cert;
private OkHttpClient client;
@Setter
private HttpUrl uploadRepoRoot;
public UploadConfiguration fromEnvironment(String runeliteVersion)
{
String prNo = System.getenv("PACKAGE_IS_PR");
if (prNo != null && !prNo.isEmpty() && !"false".equalsIgnoreCase(prNo))
{
return this;
}
setKey(System.getenv("SIGNING_KEY"));
setClient(System.getenv("REPO_CREDS"));
String uploadRepoRootStr = System.getenv("REPO_ROOT");
if (!Strings.isNullOrEmpty(uploadRepoRootStr))
{
uploadRepoRoot = HttpUrl.parse(uploadRepoRootStr)
.newBuilder()
.addPathSegment(runeliteVersion)
.build();
}
return this;
}
public boolean isComplete()
{
return key != null && cert != null && client != null && uploadRepoRoot != null;
}
public UploadConfiguration setKey(String keyStr)
{
if (keyStr == null)
{
key = null;
cert = null;
return this;
}
try
{
KeyFactory kf = KeyFactory.getInstance("RSA");
byte[] pkcs8 = Base64.getMimeDecoder().decode(keyStr
.replace("\\n", "\n")
.replaceAll(" |-----(BEGIN|END) PRIVATE KEY-----(\n?)", ""));
key = (RSAPrivateCrtKey) kf.generatePrivate(new PKCS8EncodedKeySpec(pkcs8));
cert = kf.generatePublic(new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()));
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e)
{
throw new RuntimeException(e);
}
return this;
}
public UploadConfiguration setClient(String credentials)
{
String repoAuth = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
client = new OkHttpClient.Builder()
.addInterceptor(chain ->
{
Request userAgentRequest = chain.request()
.newBuilder()
.header("User-Agent", "RuneLite-PluginHub-Package/2")
.header("Authorization", repoAuth)
.build();
Response res = null;
for (int attempts = 0; attempts < 2; attempts++)
{
res = chain.proceed(userAgentRequest);
if (res.code() == 520)
{
res.close();
continue;
}
break;
}
return res;
})
.build();
return this;
}
public void put(HttpUrl path, File data) throws IOException
{
try (Response res = client.newCall(new Request.Builder()
.url(path)
.put(RequestBody.create(null, data))
.build())
.execute())
{
Util.check(res);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Response;
public class Util
{
private Util()
{
}
public static void waitAndCheck(Plugin plugin, Process process, String name, long timeout, TimeUnit timeoutUnit) throws PluginBuildException
{
try
{
if (!process.waitFor(timeout, timeoutUnit))
{
process.destroy();
throw PluginBuildException.of(plugin, name + " failed to complete in a reasonable time");
}
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
if (process.exitValue() != 0)
{
throw PluginBuildException.of(plugin, name + " exited with " + process.exitValue());
}
}
public static void check(Response res) throws IOException
{
if ((res.code() / 100) != 2)
{
throw new IOException(res.request().url() + ": " + res.code() + " " + res.message());
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.io.Files;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
@Slf4j
public class PluginTest
{
@Test
public void testInternalNameChecks() throws IOException, DisabledPluginException
{
try
{
new Plugin(new File("plugins/I Like Spaces_and_UNDERSCORES"));
Assert.fail();
}
catch (PluginBuildException e)
{
log.info("ok: ", e);
assertContains(e.getHelpText(), "try: \"i-like-spaces-and-underscores\"");
}
}
@Test
public void testCommitMustBeComplete() throws DisabledPluginException, IOException
{
try
{
newPlugin("test", "" +
"repository=https://github.com/runelite/example-plugin.git\n" +
"commit=2357276b");
Assert.fail();
}
catch (PluginBuildException e)
{
log.info("ok: ", e);
assertContains(e.getHelpText(), "commit");
}
}
@Test
public void testExamplePluginCompiles() throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
{
try (Plugin p = createExamplePlugin("example"))
{
p.build(Packager.readRLVersion());
p.assembleManifest();
}
}
@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);
p.build(Packager.readRLVersion());
p.assembleManifest();
Assert.fail();
}
catch (PluginBuildException e)
{
log.info("ok: ", e);
assertContains(e.getHelpText(), "com.example.ExamplePlugin");
}
}
private static void writeProperties(Properties props, File fi) throws IOException
{
try (FileOutputStream fos = new FileOutputStream(fi))
{
props.store(fos, "");
}
}
private static Plugin newPlugin(String name, String desc) throws DisabledPluginException, PluginBuildException, IOException
{
File tmp = Files.createTempDir();
File f = new File(tmp, name);
try
{
Files.asCharSink(f, StandardCharsets.UTF_8).write(desc);
return new Plugin(f);
}
finally
{
f.delete();
tmp.delete();
}
}
private static Plugin createExamplePlugin(String name) throws DisabledPluginException, PluginBuildException, IOException, InterruptedException
{
Plugin p = newPlugin(name, "" +
"repository=https://github.com/runelite/example-plugin.git\n" +
"commit=0000000000000000000000000000000000000000");
Assert.assertEquals(new ProcessBuilder(
new File("./create_new_plugin.py").getAbsolutePath(),
"--noninteractive",
"--output_directory", p.repositoryDirectory.getAbsolutePath(),
"--name", "Example",
"--package", "com.example",
"--author", "Nobody",
"--description", "An example greeter plugin")
.inheritIO()
.start()
.waitFor(), 0);
return p;
}
private void assertContains(String haystack, String needle)
{
Assert.assertTrue(haystack, haystack.contains(needle));
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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 java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Assert;
import org.junit.Test;
public class UploadConfigurationTest
{
public static final String TEST_SIGNING_KEY = "-----BEGIN PRIVATE KEY-----\n" +
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDw78Jgex/z/Wqp\n" +
"CHLYYOQvD2yfOog2UJO7dZ8USRjDOUY6TVjqMg1aHNeI5USKG3jNKchw513PAfqO\n" +
"j36S3I3CEv30kUBB/bgirG0YV/vJtuTcfiAa4Hl3JIKCDRi5gLkgMON8TYZs/afA\n" +
"LVkR8ZFLlTlxLGE0VAReKXZH69poRdQLcAhRybvWWPKywWFXU4yfZzTIkQs82PXN\n" +
"UyClxsOtEGYLlZ50oov6hx+YMPJaXdVOA1Ly01iBkB7gVQnCb/dCJqilNZab+5ja\n" +
"xep4MPLBa39+dpGDECB8al2siYMEFWRT8eY8RAknQa6tL6ubUccHZEmuFT+n+II0\n" +
"Zl6UPg/bAgMBAAECggEAaTD0l3UKJVd96uDSa2AaH+XHEdnXQId7iHu5AX1Mf2eR\n" +
"HsFIUa+anr465/zZKMcHveNBLPIGxetiPj2uEGaUyafLEq0b9fPVIeZQFzHKr23X\n" +
"i+DRGYrp3TemdytKoSrvKHvPxiR+zTUNuVzTJ39lZS94jc3HfrYz1fyaNJpnl+AT\n" +
"6neUBfjXLUJYGzlLVouFIhsVywsF5Hk7N2UnSmQRdrFvgGE0IRUMqicXFuv6agxf\n" +
"NI3Bdqqnzgfel26v7OKocbuolS2Zcr4hyPJdTtrNs5pf9tt2fIkV4P6sTJiVUSyw\n" +
"TEHiCyZZyYDi4qr6F2yNkl/Ew6jNHcUA+XuTDE4OCQKBgQD79AnLckE9xIm1W+mN\n" +
"4qjsZQ5Lxt7wQdXZ9Lovo+VUDJ7/X0qnND0n62OIO1yLW8PgOEzu3xb6f3sBGTp2\n" +
"Yt+4QQmVRga5qHZ58pu3/P3YXM+C95X+x6GcIwisFH+8KEOFoFfGq3jbOUbF0uXt\n" +
"LpuaDiEMR95+96gyTTc9qmTy1wKBgQD0zmxhRgWmhJqFTb2LLvY7E6G7BuVDys4A\n" +
"lDrwNMklcw5LKabR539LbSEXx06fwQMlDyeY0NaRlx2DEFdgTH4CW7Nq/baHjuWl\n" +
"Hq+4PJZvvBC9Ti9yWmDw0lcCJm4y0Kv78yIIwmtG+4LIru6l1/02+iChtYGEA540\n" +
"801mgRGunQKBgQCYRU0GH+8+HWH8safdkHb3J7wUIATsv103dKhx0mPvABG31SeR\n" +
"Fgk/7wsgcn/j2XnwMRaN51ZD3nfAmjazBd6fxO69wKyf2CiCWxWxhL0F3lGrnWaR\n" +
"rKUHcET1ew4X8V2djOJ/t3I7S8pyFJvRVLHF0XQ3r9fQdGy6ueAA7NJF0QKBgBwQ\n" +
"YfpQxasOPoyTmewPySiCmqLPKo83+5+zXoJU+s4xP208bCRaDoy+CPIp5giIXuzr\n" +
"rNVm84IjOb3hrLKcckGg85OLXFZz+j2QpAJR58kNXTnmcagBVmWlJ1ZWw4FNzLmI\n" +
"aNlqOFQd1yNccn1Oonef+weuwBc7NvLJBZF/sGA9AoGACDOQErTZC2HmiqgLGwqs\n" +
"cg1MKU6a5gIpo7/zhR8beU4zKZfRMqmASI5KCA1JEGEnlJHyvGUwQcAx5Eu/JVXo\n" +
"ckOxZ50guxkBMUHvEi6EIOKRsCqVbgVM6/HEYMj5z8VVn32vN1VYFk7Ng461RSgL\n" +
"PTrpFFdp8oDxvgezLBFzqd8=\n" +
"-----END PRIVATE KEY-----";
@Test
public void createClientWithCredentials() throws IOException, InterruptedException
{
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(520).setBody("some cloudflare html"));
server.enqueue(new MockResponse().setResponseCode(200).setBody("ok"));
OkHttpClient client = new UploadConfiguration()
.setClient("Aladdin:open sesame")
.getClient();
try (Response res = client.newCall(new Request.Builder()
.put(RequestBody.create(null, "foo"))
.url(server.url("/"))
.build())
.execute())
{
Assert.assertEquals(res.code(), 200);
Assert.assertEquals(res.body().string(), "ok");
}
RecordedRequest r2 = server.takeRequest();
Assert.assertEquals(r2.getHeader("Authorization"), "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
@Test
public void testSigning() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException
{
UploadConfiguration cfg = new UploadConfiguration()
.setKey(TEST_SIGNING_KEY);
byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8);
byte[] sig;
{
Signature s = Signature.getInstance("SHA256withRSA");
s.initSign(cfg.getKey());
s.update(data);
sig = s.sign();
}
{
Signature s = Signature.getInstance("SHA256withRSA");
s.initVerify(cfg.getCert());
s.update(data);
Assert.assertTrue(s.verify(sig));
}
{
Signature s = Signature.getInstance("SHA256withRSA");
s.initVerify(cfg.getCert());
s.update("moo".getBytes(StandardCharsets.UTF_8));
Assert.assertFalse(s.verify(sig));
}
}
}

4
package/settings.gradle Normal file
View File

@@ -0,0 +1,4 @@
rootProject.name = "package-root"
include "initLib"
include "package"

View File

@@ -0,0 +1,81 @@
/*
* 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.
*/
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
initscript {
dependencies {
classpath files(System.getenv("runelite.pluginhub.package.lib"))
}
}
allprojects {
apply plugin: "java"
apply plugin: ShadowPlugin
compileJava {
options.release.set(8)
}
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
def buildDir = new File(System.getenv("runelite.pluginhub.package.buildDir"));
task runelitePluginHubPackage(type: ShadowJar) {
destinationDir = buildDir
archiveName = "plugin.jar"
configurations = [project.configurations.runtimeClasspath]
from sourceSets.main.output
}
task runelitePluginHubManifest {
doLast {
def props = new Properties()
props["version"] = project.version
new File(buildDir, "chunk.properties").withOutputStream {
props.store(it, "")
}
}
}
task configured {
def runeLiteDeps = [
"client",
"runelite-api",
"http-api",
]
def version = System.getenv("runelite.pluginhub.package.runeliteVersion")
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == "net.runelite" && details.requested.name in runeLiteDeps) {
details.useVersion version
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# Copyright (c) 2019 Abex
# Copyright (c) 2020 Abex
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -23,23 +23,16 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[[ "${TRAVIS_PULL_REQUEST:-false}" == "false" ]] || exit 0
set -e -x
SECONDS=0
: '
env:
-FORCE_BUILD: example-external-plugin
'
pushd "$(dirname "$0")"
./gradlew --console=plain --build-cache prep
popd
for PLUGIN in plugins/* ; do
if [ $SECONDS -gt 60 ]; then
echo "travis_fold:start:intermediate_manifest"
./build_manifest.sh
SECONDS=0
echo "travis_fold:end:intermediate_manifest"
fi
PLUGIN_ID=$(basename "$PLUGIN")
echo "travis_fold:start:$PLUGIN_ID"
./build_plugin.sh "$PLUGIN" || echo "Build failed for $PLUGIN_ID"
echo "travis_fold:end:$PLUGIN_ID"
done
echo "travis_fold:start:final_manifest"
./build_manifest.sh
echo "travis_fold:end:final_manifest"
PACKAGE_IS_PR="$TRAVIS_PULL_REQUEST" \
PACKAGE_COMMIT_RANGE="$TRAVIS_COMMIT_RANGE" \
java -XX:+UseParallelGC -jar package/package/build/libs/package.jar

View File

@@ -1,9 +0,0 @@
#!/bin/sh
# export REPO_CREDS="user:password"
# export REPO_ROOT="https://your/webdav/server"
# export SIGNING_KEY="
# -----BEGIN PRIVATE KEY-----
# ...
# -----END PRIVATE KEY-----
# "

View File

@@ -1,56 +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.
set -e -x
: '
env:
-FORCE_BUILD: example-external-plugin
'
if [[ "$FORCE_BUILD" == "ALL" ]]; then
./rebuild_all.sh
exit
elif [[ -n "${FORCE_BUILD+x}" ]]; then
for FI in $(echo "$FORCE_BUILD" | tr ',' '\n'); do
./build_plugin.sh "plugins/$FI"
done
./build_manifest.sh
exit
fi
PLUGIN_CHANGE=
while read -r FI ; do
if [[ $FI =~ ^plugins/.*$ ]]; then
[ -e "$FI" ] && ./build_plugin.sh "$FI" < /dev/null
PLUGIN_CHANGE=true
elif [[ "$FI" == "runelite.version" ]]; then
./rebuild_all.sh < /dev/null
fi
done < <(git diff --name-only "$TRAVIS_COMMIT_RANGE")
if [[ "$PLUGIN_CHANGE" == true ]]; then
./build_manifest.sh
fi