diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64761a4..a8e987f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,37 +1,300 @@ +name: Build & Publish + on: - - push + push: + release: + types: [published] + workflow_dispatch: permissions: contents: read +env: + JAVA_VERSION: '20' + jobs: - build: - strategy: - matrix: - os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}] - #arch: [arm32, arm64, x86, x64] - fail-fast: false + # ============================================ + # Build native libraries for each platform + # ============================================ + + build-linux-x64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + - name: Build native library + run: | + mkdir -p build/natives/linux-x64 + gcc -Wall -O2 -DNDEBUG -fPIC -shared \ + -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \ + -o build/natives/linux-x64/libjthreadmagic.so \ + jthreadmagic.c + - name: Run tests + run: | + mkdir -p build/classes + javac -d build/classes \ + src/main/java/rip/mem/jthreadmagic/JThreadMagic.java \ + src/test/java/rip/mem/jthreadmagic/JThreadMagicTest.java + java -Djava.library.path=build/natives/linux-x64 \ + -cp build/classes rip.mem.jthreadmagic.JThreadMagicTest + - uses: actions/upload-artifact@v4 + with: + name: native-linux-x64 + path: build/natives/ + + build-linux-x86: + # Note: Tests skipped - would require 32-bit JVM which is not readily available runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Setup JDK 8 - uses: actions/setup-java@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + - name: Install 32-bit toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-multilib + - name: Build native library + run: | + mkdir -p build/natives/linux-x86 + gcc -m32 -Wall -O2 -DNDEBUG -fPIC -shared \ + -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \ + -o build/natives/linux-x86/libjthreadmagic.so \ + jthreadmagic.c + - uses: actions/upload-artifact@v4 + with: + name: native-linux-x86 + path: build/natives/ + + build-linux-arm64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + - name: Build native library + run: | + mkdir -p build/natives/linux-arm64 + gcc -Wall -O2 -DNDEBUG -fPIC -shared \ + -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \ + -o build/natives/linux-arm64/libjthreadmagic.so \ + jthreadmagic.c + - name: Run tests + run: | + mkdir -p build/classes + javac -d build/classes \ + src/main/java/rip/mem/jthreadmagic/JThreadMagic.java \ + src/test/java/rip/mem/jthreadmagic/JThreadMagicTest.java + java -Djava.library.path=build/natives/linux-arm64 \ + -cp build/classes rip.mem.jthreadmagic.JThreadMagicTest + - uses: actions/upload-artifact@v4 + with: + name: native-linux-arm64 + path: build/natives/ + + build-windows-x64: + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Build native library + shell: cmd + run: | + mkdir build\natives\windows-x64 + cl /nologo /O2 /W3 /GL /LD /DNDEBUG ^ + /I "%JAVA_HOME%\include" /I "%JAVA_HOME%\include\win32" ^ + /Fe"build\natives\windows-x64\jthreadmagic.dll" ^ + jthreadmagic.c + del build\natives\windows-x64\*.exp build\natives\windows-x64\*.lib + - name: Run tests + shell: cmd + run: | + mkdir build\classes + javac -d build\classes ^ + src\main\java\rip\mem\jthreadmagic\JThreadMagic.java ^ + src\test\java\rip\mem\jthreadmagic\JThreadMagicTest.java + java -Djava.library.path=build\natives\windows-x64 ^ + -cp build\classes rip.mem.jthreadmagic.JThreadMagicTest + - uses: actions/upload-artifact@v4 + with: + name: native-windows-x64 + path: build/natives/ + + build-windows-x86: + # Note: Tests skipped - would require 32-bit JVM which is not readily available + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 with: - java-version: '8' + java-version: ${{ env.JAVA_VERSION }} distribution: 'temurin' - java-package: 'jdk' - # TODO: use old distributions - # TODO: build ubuntu arm, build windows - - name: Build (temp ubuntu) - if: ${{ matrix.os.name == 'ubuntu' }} + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x86 + - name: Build native library + shell: cmd + run: | + mkdir build\natives\windows-x86 + cl /nologo /O2 /W3 /GL /LD /DNDEBUG ^ + /I "%JAVA_HOME%\include" /I "%JAVA_HOME%\include\win32" ^ + /Fe"build\natives\windows-x86\jthreadmagic.dll" ^ + jthreadmagic.c + del build\natives\windows-x86\*.exp build\natives\windows-x86\*.lib + - uses: actions/upload-artifact@v4 + with: + name: native-windows-x86 + path: build/natives/ + + build-windows-arm64: + runs-on: windows-11-arm + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'zulu' + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: arm64 + - name: Build native library + shell: cmd run: | - make MYCFLAGS="-m32" TARGET="libjthreadmagic_x86.so" - make MYCFLAGS="-m64" TARGET="libjthreadmagic_x64.so" - #- name: Build (temp windows) - # if: ${{ matrix.os.name == 'windows' }} - - name: Build (temp macos) - if: ${{ matrix.os.name == 'macos' }} - run: make MYCFLAGS="-arch arm64 -arch x86_64" TARGET="libjthreadmagic.dylib" + mkdir build\natives\windows-arm64 + cl /nologo /O2 /W3 /GL /LD /DNDEBUG ^ + /I "%JAVA_HOME%\include" /I "%JAVA_HOME%\include\win32" ^ + /Fe"build\natives\windows-arm64\jthreadmagic.dll" ^ + jthreadmagic.c + del build\natives\windows-arm64\*.exp build\natives\windows-arm64\*.lib + - name: Run tests + shell: cmd + run: | + mkdir build\classes + javac -d build\classes ^ + src\main\java\rip\mem\jthreadmagic\JThreadMagic.java ^ + src\test\java\rip\mem\jthreadmagic\JThreadMagicTest.java + java -Djava.library.path=build\natives\windows-arm64 ^ + -cp build\classes rip.mem.jthreadmagic.JThreadMagicTest + - uses: actions/upload-artifact@v4 + with: + name: native-windows-arm64 + path: build/natives/ - # build java, build native, test, release \ No newline at end of file + build-macos: + runs-on: macos-14 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + - name: Build universal native library + run: | + mkdir -p build/natives/macos + clang -arch x86_64 -arch arm64 -Wall -O2 -DNDEBUG -fPIC -dynamiclib \ + -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" \ + -o build/natives/macos/libjthreadmagic.dylib \ + jthreadmagic.c + - name: Run tests + run: | + mkdir -p build/classes + javac -d build/classes \ + src/main/java/rip/mem/jthreadmagic/JThreadMagic.java \ + src/test/java/rip/mem/jthreadmagic/JThreadMagicTest.java + java -Djava.library.path=build/natives/macos \ + -cp build/classes rip.mem.jthreadmagic.JThreadMagicTest + - uses: actions/upload-artifact@v4 + with: + name: native-macos + path: build/natives/ + + # ============================================ + # Package everything into JAR and publish + # ============================================ + + package: + needs: + - build-linux-x64 + - build-linux-x86 + - build-linux-arm64 + - build-windows-x64 + - build-windows-x86 + - build-windows-arm64 + - build-macos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + + - name: Download all native artifacts + uses: actions/download-artifact@v4 + with: + path: build/natives + pattern: native-* + merge-multiple: true + + - name: Show downloaded natives + run: find build/natives -type f + + - name: Build with Maven + run: mvn -B package -DskipTests + + - name: Upload JAR artifact + uses: actions/upload-artifact@v4 + with: + name: jthreadmagic-jar + path: target/jthreadmagic-*.jar + + publish: + if: github.event_name == 'release' + needs: package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Java for publishing + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Download all native artifacts + uses: actions/download-artifact@v4 + with: + path: build/natives + pattern: native-* + merge-multiple: true + + - name: Publish to Maven Central + run: | + # Set version from tag (assuming tag is v1.2.3) + VERSION=${GITHUB_REF#refs/tags/} + # Remove 'v' prefix if present + VERSION=${VERSION#v} + mvn -B versions:set -DnewVersion=$VERSION + mvn -B deploy -P release -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.gitignore b/.gitignore index 1e3d786..2e1be5e 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,23 @@ dkms.conf # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -replay_pid* \ No newline at end of file +replay_pid* + +### +### Build +### + +build/ +target/ +out/ + +### +### IDE +### + +.idea/ +*.iml +.vscode/ +.settings/ +.project +.classpath \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..027a53b --- /dev/null +++ b/pom.xml @@ -0,0 +1,149 @@ + + + 4.0.0 + + rip.mem + jthreadmagic + 1.0.0-SNAPSHOT + jar + + JThreadMagic + Reimplement Thread#stop for Java 20+ + https://github.com/mem-rip/JThreadMagic + + + + MIT License + https://opensource.org/licenses/MIT + + + + + + mem + memcorrupt + mem@mem.rip + + + + + scm:git:git://github.com/mem-rip/JThreadMagic.git + scm:git:ssh://github.com:mem-rip/JThreadMagic.git + https://github.com/mem-rip/JThreadMagic + + + + + github + GitHub Packages + https://maven.pkg.github.com/mem-rip/JThreadMagic + + + + + UTF-8 + 25 + 25 + + + + + + build/natives + natives + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 25 + 25 + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + + + + + + + + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + sign-artifacts + verify + + sign + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.4.0 + true + + central + true + true + + + + + + + + diff --git a/src/main/java/rip/mem/jthreadmagic/JThreadMagic.java b/src/main/java/rip/mem/jthreadmagic/JThreadMagic.java new file mode 100644 index 0000000..fb532b9 --- /dev/null +++ b/src/main/java/rip/mem/jthreadmagic/JThreadMagic.java @@ -0,0 +1,106 @@ +package rip.mem.jthreadmagic; + +import java.io.*; +import java.nio.file.*; + +public class JThreadMagic { + + public static final boolean LOADED; + private static native void stopThread0(Thread thread, Object exception); + + static { + boolean isLoaded = false; + + try { + loadNativeLibrary(); + isLoaded = true; + } catch (Exception ex) { + System.err.println("Cannot load JThreadMagic:"); + ex.printStackTrace(); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Cannot load JThreadMagic native library:"); + ex.printStackTrace(); + } + + LOADED = isLoaded; + } + + private static void loadNativeLibrary() throws IOException { + String os = getOS(); + String arch = getArch(); + String libName = getLibraryName(); + + // macOS uses a universal binary (fat binary), others are arch-specific + String resourcePath = os.equals("macos") + ? "/natives/macos/" + libName + : "/natives/" + os + "-" + arch + "/" + libName; + + InputStream in = JThreadMagic.class.getResourceAsStream(resourcePath); + if (in == null) { + // Fallback to system library path + System.loadLibrary("jthreadmagic"); + return; + } + + try { + // Extract to temp file + Path tempDir = Files.createTempDirectory("jthreadmagic"); + Path tempLib = tempDir.resolve(libName); + Files.copy(in, tempLib, StandardCopyOption.REPLACE_EXISTING); + + // Make executable on Unix + tempLib.toFile().setExecutable(true); + + // Mark for deletion on exit + tempLib.toFile().deleteOnExit(); + tempDir.toFile().deleteOnExit(); + + System.load(tempLib.toAbsolutePath().toString()); + } finally { + in.close(); + } + } + + private static String getOS() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return "windows"; + } else if (os.contains("mac") || os.contains("darwin")) { + return "macos"; + } else if (os.contains("linux") || os.contains("nix") || os.contains("nux")) { + return "linux"; + } + throw new UnsupportedOperationException("Unsupported operating system: " + os); + } + + private static String getArch() { + String arch = System.getProperty("os.arch").toLowerCase(); + if (arch.equals("amd64") || arch.equals("x86_64")) { + return "x64"; + } else if (arch.equals("x86") || arch.equals("i386") || arch.equals("i686")) { + return "x86"; + } else if (arch.equals("aarch64") || arch.equals("arm64")) { + return "arm64"; + } + throw new UnsupportedOperationException("Unsupported architecture: " + arch); + } + + private static String getLibraryName() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return "jthreadmagic.dll"; + } else if (os.contains("mac") || os.contains("darwin")) { + return "libjthreadmagic.dylib"; + } else { + return "libjthreadmagic.so"; + } + } + + public static void stopThread(Thread thread) { + stopThread0(thread, new ThreadDeath()); + } + + public static void stopThread(Thread thread, Throwable exception) { + stopThread0(thread, exception); + } +} diff --git a/src/rip/mem/jthreadmagic/JThreadMagic.java b/src/rip/mem/jthreadmagic/JThreadMagic.java deleted file mode 100644 index 34a45f1..0000000 --- a/src/rip/mem/jthreadmagic/JThreadMagic.java +++ /dev/null @@ -1,26 +0,0 @@ -package rip.mem.jthreadmagic; - -public class JThreadMagic { - - public static final boolean LOADED; - private static native void stopThread0(Thread thread, Object exception); - - static { - boolean isLoaded = false; - - try{ - System.loadLibrary("jthreadmagic"); - isLoaded = true; - }catch(Exception ex){ - System.err.println("Cannot load JThreadMagic:"); - ex.printStackTrace(); - } - - LOADED = isLoaded; - } - - public static final void stopThread(Thread thread){ - stopThread0(thread, new ThreadDeath()); - } - -} \ No newline at end of file diff --git a/src/rip/mem/jthreadmagic/JThreadMagicTest.java b/src/test/java/rip/mem/jthreadmagic/JThreadMagicTest.java similarity index 100% rename from src/rip/mem/jthreadmagic/JThreadMagicTest.java rename to src/test/java/rip/mem/jthreadmagic/JThreadMagicTest.java