Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions SimpleAPI/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@
</includes>
</configuration>
</execution>
<execution>
<id>thin-library</id>
<phase>package</phase>
<goals><goal>jar</goal></goals>
<configuration>
<classifier>thin</classifier>
</configuration>
</execution>
<execution>
<id>shared-library-sources</id>
<phase>package</phase>
Expand Down Expand Up @@ -149,14 +157,8 @@
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
<!-- The full artifact is not a multi-release JAR. Keep every
base BC class (including reflective provider mappings), but
do not ship versioned implementations it cannot select. -->
<filter>
<artifact>org.bouncycastle:*</artifact>
<excludes>
<!-- The final artifact deliberately is not multi-release,
so these implementations are unreachable. -->
<exclude>META-INF/versions/**</exclude>
</excludes>
</filter>
Expand Down Expand Up @@ -206,6 +208,7 @@
<reportsDirectory>${project.build.directory}/full-artifact-reports</reportsDirectory>
<systemPropertyVariables>
<simpleapi.fullJar>${project.build.directory}/${project.build.finalName}.jar</simpleapi.fullJar>
<simpleapi.thinJar>${project.build.directory}/${project.build.finalName}-thin.jar</simpleapi.thinJar>
</systemPropertyVariables>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.io.TempDir;

import com.bencodez.simpleapi.file.BungeeJsonFile;
import com.google.gson.JsonObject;
Expand All @@ -24,9 +26,9 @@ public class BungeeJsonFileTest {
private BungeeJsonFile bungeeJsonFile;

@BeforeAll
public void setup() {
public void setup(@TempDir Path temporary) {
// Create a temporary file for testing
testFile = new File("test.json");
testFile = temporary.resolve("test.json").toFile();
bungeeJsonFile = new BungeeJsonFile(testFile);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class FullArtifactTest {
@TempDir Path temporary;

@Test void retainsBaseCryptoClassesWithoutUnusedVersionedPayload() throws Exception {
@Test void retainsBaseCryptoClassesWithoutUnusableVersionedPayload() throws Exception {
Path full = fullJar();
long removedEntries = 0;
long removedCompressedBytes = 0;
Expand All @@ -35,8 +35,8 @@ public class FullArtifactTest {
"Revisit the BC filter before making the full artifact multi-release");
String classPath = output.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
assertTrue(classPath == null || classPath.isBlank(), "Smoke test must not load external manifest dependencies");
assertFalse(output.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/")
&& entry.getName().contains("/org/bouncycastle/")), "Unused versioned BC payload is still bundled");
assertFalse(output.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/")),
"A non-multi-release artifact must not bundle unreachable versioned implementations");

// Resolve all three original libraries from Maven, without a pinned version or ~/.m2 path.
for (Class<?> anchor : List.of(BouncyCastleProvider.class, X509CertificateHolder.class, ContentInfo.class)) {
Expand All @@ -62,6 +62,21 @@ public class FullArtifactTest {
Files.size(full), retainedEntries, removedEntries, removedCompressedBytes);
}

@Test void thinArtifactContainsProjectClassesWithoutEmbeddedDependencies() throws Exception {
Path thin = thinJar();
try (JarFile artifact = new JarFile(thin.toFile())) {
assertNotNull(artifact.getEntry("com/bencodez/simpleapi/servercomm/http/HttpTlsIdentity.class"),
"Thin artifact must preserve the complete SimpleAPI API");
assertNotNull(artifact.getEntry("com/bencodez/simpleapi/scheduler/BukkitScheduler.class"));
assertNull(artifact.getEntry("org/bouncycastle/jce/provider/BouncyCastleProvider.class"));
assertNull(artifact.getEntry("com/zaxxer/hikari/HikariDataSource.class"));
assertNull(artifact.getEntry("redis/clients/jedis/Jedis.class"));
assertNull(artifact.getEntry("org/spongepowered/configurate/ConfigurationNode.class"));
assertFalse(artifact.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/")));
}
System.out.printf("Thin artifact: %,d bytes (project classes only)%n", Files.size(thin));
}

@Test void packagedTlsWorksWithoutMavenDependencies() throws Exception {
Path full = fullJar();
String fixtureName = PackagedTlsSmoke.class.getName();
Expand Down Expand Up @@ -104,4 +119,12 @@ private static Path fullJar() {
assertTrue(Files.isRegularFile(full), "Missing packaged full artifact: " + full);
return full;
}

private static Path thinJar() {
String value = System.getProperty("simpleapi.thinJar");
assertNotNull(value, "Run this test through the Maven package lifecycle");
Path thin = Path.of(value).toAbsolutePath().normalize();
assertTrue(Files.isRegularFile(thin), "Missing packaged thin artifact: " + thin);
return thin;
}
}
22 changes: 15 additions & 7 deletions docs/jar-packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
`shared-sources` classifiers, dependency scopes, and public APIs are unchanged.
There are no additional modules or runtime downloads.

The `thin` classifier contains the complete, unshaded SimpleAPI classes and
resources but no embedded third-party classes. It exists for trusted downstream
projects that immediately shade SimpleAPI while explicitly controlling the
ordinary POM's transitive dependencies. It is not a standalone replacement for
the full artifact. In particular, a consumer of HTTP/TLS APIs must still supply
the declared Bouncy Castle libraries. Normal external consumers should continue
to use the self-contained main artifact.

The HTTP transport uses Bouncy Castle for its private CA and certificates.
Do not remove those dependencies, switch them to `provided`, strip provider
mappings, or enable broad `minimizeJar` without packaged-runtime validation.
Providers load some implementation classes reflectively.

The full shaded artifact is not a multi-release JAR. Its BC-specific shade
filter omits `META-INF/versions/**`, which that artifact cannot select, while
retaining all base BC classes and resources. Other dependencies are not filtered
by this rule. This is a conservative reduction of redundant payload, not removal
of the crypto provider or the entire HTTP dependency cost.
The full shaded artifact is not a multi-release JAR. Its shade filter omits
`META-INF/versions/**`, which that artifact cannot select, while retaining base
classes and resources. This is a conservative reduction of unreachable payload,
not removal of the crypto provider or the entire HTTP dependency cost.

Java only selects versioned classes when the final manifest declares
`Multi-Release: true`. If that contract changes, revisit this filter and the
Expand All @@ -31,8 +38,9 @@ git diff --check

The package phase runs `FullArtifactTest` after shading, followed by the existing
shared-classpath tests. It verifies the non-multi-release manifest, absence of
versioned BC payload, and preservation of every base `org/bouncycastle/` entry
from the three resolved BC libraries. It prints the final JAR size and the
all unreachable versioned payload, preservation of every base
`org/bouncycastle/` entry from the three resolved BC libraries, and the thin
artifact's no-embedded-dependencies contract. It prints the final JAR size and the
compressed upstream payload omitted. That payload counter is not an exact
before/after distribution size: shading/recompression and ZIP overhead differ.
To measure the exact reduction, compare clean baseline and candidate builds with
Expand Down
Loading