diff --git a/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java b/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java index ae7154516..abdeb44bb 100644 --- a/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java +++ b/package/package/src/main/java/net/runelite/pluginhub/packager/Plugin.java @@ -31,6 +31,7 @@ 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.CountingOutputStream; import com.google.common.io.MoreFiles; import com.google.common.io.RecursiveDeleteOption; import java.io.BufferedReader; @@ -46,8 +47,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -60,11 +66,14 @@ import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import javax.annotation.Nullable; import javax.imageio.ImageIO; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; +import lombok.Value; import net.runelite.pluginhub.uploader.ExternalPluginManifest; import net.runelite.pluginhub.uploader.UploadConfiguration; import okhttp3.HttpUrl; @@ -82,6 +91,10 @@ import org.slf4j.helpers.MessageFormatter; public class Plugin implements Closeable { + private static final long MIB = 1024 * 1024; + private static final long MAX_JAR_SIZE = 10 * MIB; + private static final long MAX_SRC_SIZE = 10 * MIB; + 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}$"); @@ -121,6 +134,7 @@ public class Plugin implements Closeable final File repositoryDirectory; private final File jarFile; + private final File srcZipFile; private final File iconFile; @Getter @@ -215,6 +229,7 @@ public class Plugin implements Closeable logFile = new File(buildDirectory, "log"); log = new FileOutputStream(logFile, true); jarFile = new File(buildDirectory, "plugin.jar"); + srcZipFile = new File(buildDirectory, "source.zip"); iconFile = new File(repositoryDirectory, "icon.png"); } @@ -287,6 +302,67 @@ public class Plugin implements Closeable } } + try ( + CountingOutputStream cos = new CountingOutputStream(new FileOutputStream(srcZipFile)); + ZipOutputStream zos = new ZipOutputStream(cos)) + { + @Value + class Entry + { + Path path; + String zipPath; + long length; + } + + List core = new ArrayList<>(); + List extras = new ArrayList<>(); + Files.walkFileTree(repositoryDirectory.toPath(), new SimpleFileVisitor() + { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { + if (dir.toString().contains(".git")) + { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException + { + String zipPath = repositoryDirectory.toPath().relativize(path).toString().replace('\\', '/'); + (zipPath.contains(".gradle") || zipPath.startsWith("src/main/") ? core : extras) + .add(new Entry(path, zipPath, path.toFile().length())); + return FileVisitResult.CONTINUE; + } + }); + + core.sort(Comparator.comparing(Entry::getZipPath)); + for (Entry e : core) + { + ZipEntry ze = new ZipEntry(e.zipPath); + zos.putNextEntry(ze); + Files.copy(e.path, zos); + zos.closeEntry(); + } + + extras.sort(Comparator.comparing(Entry::getLength).thenComparing(Entry::getZipPath)); + for (Entry e : extras) + { + if (cos.getCount() + e.length > MAX_SRC_SIZE) + { + writeLog("File \"{}\" is skipped from the source archive as it would make it too big ({} MiB)\n", e.zipPath, e.length / MIB); + continue; + } + + ZipEntry ze = new ZipEntry(e.zipPath); + zos.putNextEntry(ze); + Files.copy(e.path, zos); + zos.closeEntry(); + } + } + try (ProjectConnection con = GradleConnector.newConnector() .forProjectDirectory(repositoryDirectory) .useInstallation(GRADLE_HOME) @@ -366,13 +442,21 @@ public class Plugin implements Closeable { long size = jarFile.length(); - if (size > 10 * 1024 * 1024) + if (size > MAX_JAR_SIZE) { - throw PluginBuildException.of(this, "the output jar is {}MiB, which is above our limit of 10MiB", size / (1024 * 1024)); + throw PluginBuildException.of(this, "the output jar is {}MiB, which is above our limit of 10MiB", size / MIB); } manifest.setSize((int) size); } + { + long size = srcZipFile.length(); + if (size > MAX_SRC_SIZE + MIB) // allow the header to be a bit bigger + { + throw PluginBuildException.of(this, "the source archive is {}MiB, which is above our limit of 10MiB", size / MIB); + } + } + manifest.setHash(com.google.common.io.Files.asByteSource(jarFile) .hash(Hashing.sha256()) .toString()); @@ -596,7 +680,7 @@ public class Plugin implements Closeable { throw PluginBuildException.of(this, "Missing LICENSE file") .withHelp("All plugins must be licensed under a license that allows us to freely distribute the plugin jar standalone.\n" + - "We recommend the BSD 2 Clause license."); + "We recommend the BSD 2 Clause license."); } } } @@ -611,6 +695,10 @@ public class Plugin implements Closeable pluginRoot.newBuilder().addPathSegment(commit + ".jar").build(), jarFile); + uploadConfig.put( + pluginRoot.newBuilder().addPathSegment(commit + "-sources.zip").build(), + srcZipFile); + if (manifest.isHasIcon()) { uploadConfig.put(