diff --git a/.github/workflows/java-module-test.yml b/.github/workflows/java-module-test.yml new file mode 100644 index 00000000..df7bef38 --- /dev/null +++ b/.github/workflows/java-module-test.yml @@ -0,0 +1,262 @@ +name: Java 9+ Module Support + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + # Test Java 9+ module support (JPMS) + # Verifies that module-info.java is properly compiled and the resulting + # JAR can be used with jlink to create custom Java runtimes. + # See GitHub issue #85 for background. + module-test: + strategy: + matrix: + os: [ 'ubuntu-latest' ] + jdk_version: [ '11', '17', '21' ] + runs-on: ${{ matrix.os }} + name: Module Test (JDK ${{ matrix.jdk_version }}) + + steps: + - uses: actions/checkout@v4 + + - name: Cache JUnit dependencies + uses: actions/cache@v4 + id: cache-junit + with: + path: junit + key: junit-jars-v1 + + - name: Download junit-4.13.2.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar + + - name: Download hamcrest-all-1.3.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar + + - name: Build native wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + configure: --enable-jni + check: false + install: true + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: ${{ matrix.jdk_version }} + + - name: Set JUNIT_HOME + run: | + echo "JUNIT_HOME=$GITHUB_WORKSPACE/junit" >> "$GITHUB_ENV" + + - name: Set LD_LIBRARY_PATH + run: | + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib" >> "$GITHUB_ENV" + + - name: Build JNI library + run: ./java.sh $GITHUB_WORKSPACE/build-dir + + - name: Build JAR with module support (ant) + run: ant + + - name: Verify module-info.class exists in JAR + run: | + echo "Checking for module-info.class in wolfssl-jsse.jar..." + if jar tf lib/wolfssl-jsse.jar | grep -q "module-info.class"; then + echo "SUCCESS: module-info.class found in JAR" + else + echo "FAILURE: module-info.class not found in JAR" + echo "JAR contents:" + jar tf lib/wolfssl-jsse.jar | head -20 + exit 1 + fi + + - name: Verify module descriptor with jar --describe-module + run: | + echo "Describing module in wolfssl-jsse.jar..." + jar --describe-module --file=lib/wolfssl-jsse.jar + echo "" + echo "Verifying module name is 'com.wolfssl'..." + MODULE_NAME=$(jar --describe-module --file=lib/wolfssl-jsse.jar 2>&1 | head -1 | cut -d' ' -f1) + if [ "$MODULE_NAME" = "com.wolfssl" ]; then + echo "SUCCESS: Module name is correct: $MODULE_NAME" + else + echo "FAILURE: Expected module name 'com.wolfssl', got '$MODULE_NAME'" + exit 1 + fi + + - name: Verify module exports correct packages + run: | + echo "Verifying module exports..." + EXPORTS=$(jar --describe-module --file=lib/wolfssl-jsse.jar 2>&1) + echo "$EXPORTS" + echo "" + if echo "$EXPORTS" | grep -q "exports com.wolfssl"; then + echo "SUCCESS: exports com.wolfssl" + else + echo "FAILURE: missing 'exports com.wolfssl'" + exit 1 + fi + if echo "$EXPORTS" | grep -q "exports com.wolfssl.provider.jsse"; then + echo "SUCCESS: exports com.wolfssl.provider.jsse" + else + echo "FAILURE: missing 'exports com.wolfssl.provider.jsse'" + exit 1 + fi + + - name: Test jlink can create runtime with module + run: | + echo "Testing jlink integration..." + jlink \ + --module-path lib/wolfssl-jsse.jar \ + --add-modules com.wolfssl \ + --output test-jlink-runtime \ + --no-header-files \ + --no-man-pages + echo "" + echo "SUCCESS: jlink created custom runtime" + echo "Runtime modules:" + ./test-jlink-runtime/bin/java --list-modules + echo "" + echo "Verifying com.wolfssl module is present..." + if ./test-jlink-runtime/bin/java --list-modules | grep -q "com.wolfssl"; then + echo "SUCCESS: com.wolfssl module found in custom runtime" + else + echo "FAILURE: com.wolfssl module not found in custom runtime" + exit 1 + fi + + - name: Clean up jlink test runtime + run: rm -rf test-jlink-runtime + + - name: Run standard tests to verify module doesn't break functionality + run: ant test + + - name: Clean ant build for Maven test + run: ant clean + + - name: Maven build and verify module-info in JAR + run: | + echo "Building with Maven..." + mvn package -DskipTests -q + echo "" + MAVEN_JAR=$(ls target/wolfssl-jsse-*.jar) + echo "Maven JAR: $MAVEN_JAR" + echo "" + echo "Checking for module-info.class in Maven-built JAR..." + if jar tf "$MAVEN_JAR" | grep -q "module-info.class"; then + echo "SUCCESS: module-info.class found in Maven JAR" + else + echo "FAILURE: module-info.class not found in Maven JAR" + jar tf "$MAVEN_JAR" | head -20 + exit 1 + fi + echo "" + echo "Verifying Maven JAR module descriptor..." + jar --describe-module --file="$MAVEN_JAR" + + - name: Clean Maven build + run: mvn clean -q + + - name: Show logs on failure + if: failure() || cancelled() + run: | + cat build/reports/*.txt 2>/dev/null || echo "No test reports found" + + # Verify Java 8 builds do NOT include module-info.class + # This ensures the conditional compilation works correctly for Java 8 users + java8-no-module-test: + runs-on: ubuntu-latest + name: Java 8 No Module Test + + steps: + - uses: actions/checkout@v4 + + - name: Cache JUnit dependencies + uses: actions/cache@v4 + id: cache-junit + with: + path: junit + key: junit-jars-v1 + + - name: Download junit-4.13.2.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar + + - name: Download hamcrest-all-1.3.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar + + - name: Build native wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + configure: --enable-jni + check: false + install: true + + - name: Setup Java 8 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '8' + + - name: Set JUNIT_HOME + run: | + echo "JUNIT_HOME=$GITHUB_WORKSPACE/junit" >> "$GITHUB_ENV" + + - name: Set LD_LIBRARY_PATH + run: | + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib" >> "$GITHUB_ENV" + + - name: Build JNI library + run: ./java.sh $GITHUB_WORKSPACE/build-dir + + - name: Build JAR without module support (ant on Java 8) + run: ant + + - name: Verify module-info.class is NOT in JAR (Java 8 build) + run: | + echo "Checking that module-info.class is NOT in wolfssl-jsse.jar (Java 8 build)..." + if jar tf lib/wolfssl-jsse.jar | grep -q "module-info.class"; then + echo "FAILURE: module-info.class should NOT be in JAR when built with Java 8" + exit 1 + else + echo "SUCCESS: module-info.class correctly excluded from Java 8 build" + fi + + - name: Run tests to verify Java 8 build works correctly + run: ant test + + - name: Maven build and verify NO module-info in JAR (Java 8) + run: | + echo "Building with Maven on Java 8..." + mvn package -DskipTests -q + echo "" + MAVEN_JAR=$(ls target/wolfssl-jsse-*.jar) + echo "Maven JAR: $MAVEN_JAR" + echo "" + echo "Checking that module-info.class is NOT in Maven-built JAR..." + if jar tf "$MAVEN_JAR" | grep -q "module-info.class"; then + echo "FAILURE: module-info.class should NOT be in Maven JAR on Java 8" + exit 1 + else + echo "SUCCESS: module-info.class correctly excluded from Maven Java 8 build" + fi + mvn clean -q + + - name: Show logs on failure + if: failure() || cancelled() + run: | + cat build/reports/*.txt 2>/dev/null || echo "No test reports found" + diff --git a/README.md b/README.md index 933a004c..db915648 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,85 @@ an application can include this as a dependency in the application's ``` +## Java 9+ Module Support (JPMS) + +wolfSSL JNI/JSSE supports the Java Platform Module System (JPMS) introduced in +Java 9. This allows the library to be used with `jlink` to create custom Java +runtimes. + +### How It Works + +The build system uses conditional compilation to include module support only +when building with Java 9 or later: + +| JDK Used to Build | Resulting JAR | +| --- | --- | +| Java 8 | Standard JAR (classpath only) | +| Java 9+ | Modular JAR (works with both classpath and module path) | + +When building with Java 9+, a `module-info.class` is included in the JAR that: +- Declares the module as `com.wolfssl` +- Exports `com.wolfssl` and `com.wolfssl.provider.jsse` packages +- Registers `WolfSSLProvider` as a `java.security.Provider` service + +### Building a Modular JAR + +To build a modular JAR, simply use Java 9 or later when building. Both Ant and +Maven builds support automatic module-info compilation. + +**Using Ant:** + +``` +$ export JAVA_HOME=/path/to/jdk11 # or any JDK 9+ +$ ./java.sh +$ ant +``` + +**Using Maven:** + +``` +$ export JAVA_HOME=/path/to/jdk11 # or any JDK 9+ +$ ./java.sh +$ mvn package +``` + +Maven uses a profile (`java9-module`) that automatically activates on Java 9+ +to compile and include `module-info.class` in the JAR. + +You can verify module support with: + +``` +$ jar --describe-module --file=lib/wolfssl-jsse.jar +com.wolfssl jar:file:///path/to/lib/wolfssl-jsse.jar/!module-info.class +exports com.wolfssl +exports com.wolfssl.provider.jsse +requires java.logging +provides java.security.Provider with com.wolfssl.provider.jsse.WolfSSLProvider +``` + +### Using with jlink + +Once built with Java 9+, the JAR can be used with `jlink` to create custom +Java runtimes: + +``` +$ jlink \ + --module-path lib/wolfssl-jsse.jar \ + --add-modules com.wolfssl \ + --output custom-runtime \ + --no-header-files \ + --no-man-pages +``` + +This creates a minimal Java runtime with wolfJSSE included, which can be +deployed independently. + +### Java 8 Compatibility + +Java 8 users can still build and use wolfSSL JNI/JSSE normally. When building +with Java 8, the `module-info.java` is automatically excluded from compilation, +and the resulting JAR works as a standard classpath JAR. + ## Examples Examples of using wolfssljni can be found in the `./examples` subdirectory. diff --git a/build.xml b/build.xml index eca043c1..ab0ddf89 100644 --- a/build.xml +++ b/build.xml @@ -18,6 +18,7 @@ + @@ -38,6 +39,18 @@ + + + + + + + + + + + + @@ -98,7 +111,7 @@ - + @@ -148,6 +161,22 @@ + + + + + + + + diff --git a/pom.xml b/pom.xml index 79e73342..d0d6597d 100644 --- a/pom.xml +++ b/pom.xml @@ -69,4 +69,42 @@ + + + + + java9-module + + [9,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + + compile-module-info + compile + + compile + + + 9 + + ${project.basedir}/src/java9 + + false + + + + + + + + diff --git a/src/java9/module-info.java b/src/java9/module-info.java new file mode 100644 index 00000000..826ea60a --- /dev/null +++ b/src/java9/module-info.java @@ -0,0 +1,47 @@ +/* module-info.java + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/** + * wolfSSL JNI/JSSE Module + * + * This module provides: + * - JNI bindings to the native wolfSSL SSL/TLS library (com.wolfssl) + * - A JSSE provider implementation (com.wolfssl.provider.jsse) + * + * Note: This module-info.java is only compiled when building with Java 9+. + * When building with Java 8, this file is excluded and the resulting JAR + * will be a standard (non-modular) JAR that works on the classpath. + */ +module com.wolfssl { + /* Required modules */ + requires java.logging; + + /* Export public API packages */ + exports com.wolfssl; + exports com.wolfssl.provider.jsse; + + /* Declare service usage for ServiceLoader.load(Provider.class) */ + uses java.security.Provider; + + /* Register wolfJSSE as a security provider */ + provides java.security.Provider + with com.wolfssl.provider.jsse.WolfSSLProvider; +}