diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml index bdb5c5c..8c8ecbf 100644 --- a/.github/workflows/build_pr.yml +++ b/.github/workflows/build_pr.yml @@ -1,7 +1,149 @@ { "jobs": { + "natives-test": { + "needs": [ + "build" + ], + "runs-on": "${{ matrix.runner }}", + "strategy": { + "fail-fast": false, + "matrix": { + "os": [ + "linux", + "windows", + "macos" + ], + "arch": [ + "x86_64", + "aarch64", + "arm" + ], + "exclude": [ + { + "os": "macos", + "arch": "i386" + }, + { + "os": "macos", + "arch": "arm" + }, + { + "os": "windows", + "arch": "arm" + }, + { + "os": "windows", + "arch": "aarch64" + }, + { + "os": "macos", + "arch": "x86_64" + }, + { + "os": "linux", + "arch": "arm" + } + ], + "include": [ + { + "os": "macos", + "runner": "macos-latest", + "java_home": "$JAVA_HOME_17_arm64" + }, + { + "os": "windows", + "runner": "windows-latest", + "java_home": "$JAVA_HOME_17_X64" + }, + { + "os": "linux", + "runner": "ubuntu-latest", + "java_home": "$JAVA_HOME_17_X64" + }, + { + "os": "linux", + "arch": "aarch64", + "qemu": "arm64", + "docker_arch": "arm64", + "docker": "arm64v8/eclipse-temurin:17" + }, + { + "os": "linux", + "arch": "arm", + "qemu": "arm/v7", + "docker_arch": "arm/v7", + "docker": "arm32v7/eclipse-temurin:17" + } + ] + } + }, + "steps": [ + { + "with": { + "name": "natives-test-environment" + }, + "name": "Download Artifact", + "uses": "actions/download-artifact@v4", + "id": "download" + }, + { + "name": "Setup QEMU", + "run": "sudo apt-get -qq install -y qemu qemu-user-static\ndocker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes", + "if": "${{ matrix.qemu }}" + }, + { + "name": "Run with Docker", + "run": "docker run --workdir \"${GITHUB_WORKSPACE}\" -v \"${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}:z\" --rm --platform linux/${{ matrix.docker_arch }} -t ${{ matrix.docker }} /bin/bash -c 'mkdir ../working; cp -r ./ ../working/; cd ../working; java @args-${{ matrix.os }}.txt; return=$?; cp -r ./ ${GITHUB_WORKSPACE}/; cd ${GITHUB_WORKSPACE}; exit $return'", + "if": "${{ matrix.docker }}" + }, + { + "shell": "bash", + "name": "Run", + "run": "${{ matrix.java_home }}/bin/java @args-${{ matrix.os }}.txt", + "if": "${{ !matrix.qemu }}" + }, + { + "with": { + "name": "junit-test-results-native-${{ matrix.os }}-${{ matrix.arch }}", + "path": "results/TEST-*.xml" + }, + "name": "Upload Results", + "uses": "actions/upload-artifact@v4", + "if": "(success() || failure()) && steps.download.outcome == 'success'" + } + ] + }, + "aggregate-test-results": { + "needs": [ + "natives-test", + "build" + ], + "runs-on": "ubuntu-latest", + "steps": [ + { + "with": { + "pattern": "junit-test-results-*" + }, + "name": "Download Artifact", + "uses": "actions/download-artifact@v4", + "id": "download" + }, + { + "with": { + "name": "junit-test-results", + "path": "**/TEST-*.xml" + }, + "name": "Upload Results", + "uses": "actions/upload-artifact@v4" + } + ], + "if": "always()" + }, "build": { "runs-on": "ubuntu-22.04", + "permissions": { + "packages": "read" + }, "steps": [ { "name": "Setup Java", @@ -38,17 +180,28 @@ }, { "name": "Assemble", - "run": "./gradlew assemble", + "run": "./gradlew assemble :opensesame-natives:setupTestEnvironment", "id": "assemble", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "PR_NUMBER": "${{ github.event.pull_request.number }}" } }, + { + "with": { + "name": "natives-test-environment", + "path": "natives/build/testEnvironment/*", + "retention-days": "1" + }, + "name": "Upload natives-test-environment", + "uses": "actions/upload-artifact@v4" + }, { "name": "Test", "run": "./gradlew check --continue", "id": "test", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "PR_NUMBER": "${{ github.event.pull_request.number }}" } }, @@ -57,6 +210,7 @@ "run": "./gradlew check --continue", "id": "test_plugin", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "PR_NUMBER": "${{ github.event.pull_request.number }}" }, "working-directory": "./testplugin", @@ -64,20 +218,11 @@ }, { "with": { - "name": "junit-test-results", + "name": "junit-test-results-gradle", "path": "**/build/test-results/*/TEST-*.xml", "retention-days": "1" }, - "name": "Upload junit-test-results", - "uses": "actions/upload-artifact@v4", - "if": "(success() || failure()) && steps.assemble.outcome == 'success'" - }, - { - "with": { - "name": "jacoco-coverage", - "path": "build/reports/jacoco/testCodeCoverageReport" - }, - "name": "Upload jacoco-coverage", + "name": "Upload junit-test-results-gradle", "uses": "actions/upload-artifact@v4", "if": "(success() || failure()) && steps.assemble.outcome == 'success'" }, @@ -86,6 +231,7 @@ "run": "./gradlew publish", "id": "publish", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "PR_NUMBER": "${{ github.event.pull_request.number }}" } }, diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 815f236..7ef6dca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,8 @@ }, "runs-on": "ubuntu-22.04", "permissions": { - "contents": "write" + "contents": "write", + "packages": "read" }, "steps": [ { @@ -46,35 +47,41 @@ { "name": "Tag Release", "run": "./gradlew tagRelease", - "id": "tag_release" + "id": "tag_release", + "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + } }, { "name": "Build", "run": "./gradlew build", - "id": "build" + "id": "build", + "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + } }, { "run": "git push && git push --tags" }, - { - "with": { - "gradle-build-module": ":\n:opensesame-compile\n:opensesame-core\n:opensesame-fabric\n:opensesame-groovy\n:opensesame-javac\n:opensesame-mixin\n:opensesame-plugin-core\n:opensesame-plugin-loom", - "gradle-build-configuration": "compileClasspath", - "sub-module-mode": "INDIVIDUAL_DEEP", - "include-build-environment": true - }, - "name": "Submit Dependencies", - "uses": "mikepenz/gradle-dependency-submission@v0.9.0" - }, { "name": "Record Version", "run": "./gradlew recordVersion", - "id": "record_version" + "id": "record_version", + "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + } }, { "name": "Capture Recorded Version", "run": "echo version=$(cat build/recordVersion.txt) >> \"$GITHUB_OUTPUT\"", "id": "record_version_capture_version" + }, + { + "name": "Submit Dependencies", + "uses": "gradle/actions/dependency-submission@v3", + "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + } } ] }, @@ -83,6 +90,9 @@ "build" ], "runs-on": "ubuntu-22.04", + "permissions": { + "packages": "read" + }, "steps": [ { "name": "Setup Java", @@ -123,6 +133,7 @@ "run": "./gradlew publishCentral closeAndReleaseSonatypeStagingRepository", "id": "publish", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", @@ -139,6 +150,9 @@ "build" ], "runs-on": "ubuntu-22.04", + "permissions": { + "packages": "read" + }, "steps": [ { "name": "Setup Java", @@ -179,6 +193,7 @@ "run": "./gradlew publishPlugins", "id": "publish_plugins", "env": { + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml index ba404cb..0fe0114 100644 --- a/.github/workflows/report.yml +++ b/.github/workflows/report.yml @@ -31,7 +31,8 @@ "path": "**/test-results/testOn17/**/TEST-*.xml", "reporter": "java-junit", "fail-on-empty": "true", - "list-tests": "failed" + "list-tests": "failed", + "list-suites": "failed" }, "name": "JUnit Test Report - Java 17", "uses": "dorny/test-reporter@v1" @@ -42,15 +43,28 @@ "path": "**/test-results/testOn21/**/TEST-*.xml", "reporter": "java-junit", "fail-on-empty": "true", - "list-tests": "failed" + "list-tests": "failed", + "list-suites": "failed" }, "name": "JUnit Test Report - Java 21", "uses": "dorny/test-reporter@v1" }, + { + "with": { + "name": "Test Results - Java 22", + "path": "**/test-results/testOn22/**/TEST-*.xml", + "reporter": "java-junit", + "fail-on-empty": "true", + "list-tests": "failed", + "list-suites": "failed" + }, + "name": "JUnit Test Report - Java 22", + "uses": "dorny/test-reporter@v1" + }, { "with": { "name": "Test Results - Misc", - "path": "**/test-results/test/**/TEST-*.xml", + "path": "**/test-results/test/**/TEST-*.xml\njunit-test-results-native-*/**/TEST-*.xml", "reporter": "java-junit", "fail-on-empty": "true", "list-tests": "failed" diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 098a2e9..32d35e1 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -1,7 +1,149 @@ { "jobs": { + "natives-test": { + "needs": [ + "build" + ], + "runs-on": "${{ matrix.runner }}", + "strategy": { + "fail-fast": false, + "matrix": { + "os": [ + "linux", + "windows", + "macos" + ], + "arch": [ + "x86_64", + "aarch64", + "arm" + ], + "exclude": [ + { + "os": "macos", + "arch": "i386" + }, + { + "os": "macos", + "arch": "arm" + }, + { + "os": "windows", + "arch": "arm" + }, + { + "os": "windows", + "arch": "aarch64" + }, + { + "os": "macos", + "arch": "x86_64" + }, + { + "os": "linux", + "arch": "arm" + } + ], + "include": [ + { + "os": "macos", + "runner": "macos-latest", + "java_home": "$JAVA_HOME_17_arm64" + }, + { + "os": "windows", + "runner": "windows-latest", + "java_home": "$JAVA_HOME_17_X64" + }, + { + "os": "linux", + "runner": "ubuntu-latest", + "java_home": "$JAVA_HOME_17_X64" + }, + { + "os": "linux", + "arch": "aarch64", + "qemu": "arm64", + "docker_arch": "arm64", + "docker": "arm64v8/eclipse-temurin:17" + }, + { + "os": "linux", + "arch": "arm", + "qemu": "arm/v7", + "docker_arch": "arm/v7", + "docker": "arm32v7/eclipse-temurin:17" + } + ] + } + }, + "steps": [ + { + "with": { + "name": "natives-test-environment" + }, + "name": "Download Artifact", + "uses": "actions/download-artifact@v4", + "id": "download" + }, + { + "name": "Setup QEMU", + "run": "sudo apt-get -qq install -y qemu qemu-user-static\ndocker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes", + "if": "${{ matrix.qemu }}" + }, + { + "name": "Run with Docker", + "run": "docker run --workdir \"${GITHUB_WORKSPACE}\" -v \"${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}:z\" --rm --platform linux/${{ matrix.docker_arch }} -t ${{ matrix.docker }} /bin/bash -c 'mkdir ../working; cp -r ./ ../working/; cd ../working; java @args-${{ matrix.os }}.txt; return=$?; cp -r ./ ${GITHUB_WORKSPACE}/; cd ${GITHUB_WORKSPACE}; exit $return'", + "if": "${{ matrix.docker }}" + }, + { + "shell": "bash", + "name": "Run", + "run": "${{ matrix.java_home }}/bin/java @args-${{ matrix.os }}.txt", + "if": "${{ !matrix.qemu }}" + }, + { + "with": { + "name": "junit-test-results-native-${{ matrix.os }}-${{ matrix.arch }}", + "path": "results/TEST-*.xml" + }, + "name": "Upload Results", + "uses": "actions/upload-artifact@v4", + "if": "(success() || failure()) && steps.download.outcome == 'success'" + } + ] + }, + "aggregate-test-results": { + "needs": [ + "natives-test", + "build" + ], + "runs-on": "ubuntu-latest", + "steps": [ + { + "with": { + "pattern": "junit-test-results-*" + }, + "name": "Download Artifact", + "uses": "actions/download-artifact@v4", + "id": "download" + }, + { + "with": { + "name": "junit-test-results", + "path": "**/TEST-*.xml" + }, + "name": "Upload Results", + "uses": "actions/upload-artifact@v4" + } + ], + "if": "always()" + }, "build": { "runs-on": "ubuntu-22.04", + "permissions": { + "packages": "read" + }, "steps": [ { "name": "Setup Java", @@ -38,17 +180,27 @@ }, { "name": "Assemble", - "run": "./gradlew assemble", + "run": "./gradlew assemble :opensesame-natives:setupTestEnvironment", "id": "assemble", "env": { "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "SNAPSHOT_MAVEN_PASSWORD": "${{ secrets.SNAPSHOT_MAVEN_PASSWORD }}", "SNAPSHOT_MAVEN_USER": "github", "SNAPSHOT_MAVEN_URL": "https://maven.lukebemish.dev/snapshots/" } }, + { + "with": { + "name": "natives-test-environment", + "path": "natives/build/testEnvironment/*", + "retention-days": "1" + }, + "name": "Upload natives-test-environment", + "uses": "actions/upload-artifact@v4" + }, { "name": "Test", "run": "./gradlew check --continue", @@ -57,6 +209,7 @@ "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "SNAPSHOT_MAVEN_PASSWORD": "${{ secrets.SNAPSHOT_MAVEN_PASSWORD }}", "SNAPSHOT_MAVEN_USER": "github", "SNAPSHOT_MAVEN_URL": "https://maven.lukebemish.dev/snapshots/" @@ -70,6 +223,7 @@ "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "SNAPSHOT_MAVEN_PASSWORD": "${{ secrets.SNAPSHOT_MAVEN_PASSWORD }}", "SNAPSHOT_MAVEN_USER": "github", "SNAPSHOT_MAVEN_URL": "https://maven.lukebemish.dev/snapshots/" @@ -79,20 +233,11 @@ }, { "with": { - "name": "junit-test-results", + "name": "junit-test-results-gradle", "path": "**/build/test-results/*/TEST-*.xml", "retention-days": "1" }, - "name": "Upload junit-test-results", - "uses": "actions/upload-artifact@v4", - "if": "(success() || failure()) && steps.assemble.outcome == 'success'" - }, - { - "with": { - "name": "jacoco-coverage", - "path": "build/reports/jacoco/testCodeCoverageReport" - }, - "name": "Upload jacoco-coverage", + "name": "Upload junit-test-results-gradle", "uses": "actions/upload-artifact@v4", "if": "(success() || failure()) && steps.assemble.outcome == 'success'" }, @@ -104,6 +249,7 @@ "BUILD_CACHE_PASSWORD": "${{ secrets.BUILD_CACHE_PASSWORD }}", "BUILD_CACHE_USER": "${{ secrets.BUILD_CACHE_USER }}", "BUILD_CACHE_URL": "${{ secrets.BUILD_CACHE_URL }}", + "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", "SNAPSHOT_MAVEN_PASSWORD": "${{ secrets.SNAPSHOT_MAVEN_PASSWORD }}", "SNAPSHOT_MAVEN_USER": "github", "SNAPSHOT_MAVEN_URL": "https://maven.lukebemish.dev/snapshots/" diff --git a/build.gradle b/build.gradle index 5fa210b..dcbf7ee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,5 @@ plugins { - alias cLibs.plugins.managedversioning - id 'jacoco-report-aggregation' + alias libs.plugins.managedversioning id 'test-report-aggregation' id 'java-library' id 'opensesame.publishing' @@ -20,18 +19,20 @@ managedVersioning { workflowDispatch = true gradleJob { name = 'build' + permissions.put 'packages', 'read' + secret('GITHUB_TOKEN') setupGitUser() readOnly = false gradlew 'Tag Release', 'tagRelease' gradlew 'Build', 'build' push() - dependencySubmission { - projects(allprojects.findAll { it.name != 'testtargets' }) - } recordVersion 'Record Version', 'version' + dependencySubmission() } gradleJob { name.set 'publishCentral' + permissions.put 'packages', 'read' + secret('GITHUB_TOKEN') buildCache() needs.add('build') gradlew 'Publish', 'publishCentral', 'closeAndReleaseSonatypeStagingRepository' @@ -41,6 +42,8 @@ managedVersioning { } gradleJob { name.set 'publishPlugins' + permissions.put 'packages', 'read' + secret('GITHUB_TOKEN') buildCache() needs.add('build') gradlew 'Publish Plugins', 'publishPlugins' @@ -50,57 +53,140 @@ managedVersioning { } } + def testOnBuild = { dev.lukebemish.managedversioning.actions.GradleJob job -> + job.gradlew 'Assemble', 'assemble', ':opensesame-natives:setupTestEnvironment' + job.upload('natives-test-environment', ['natives/build/testEnvironment/*']) { + requiredSteps.add 'assemble' + with.put('retention-days', '1') + } + job.gradlew 'Test', 'check', '--continue' + job.gradlew('Test Plugin', ['check', '--continue']) { + workingDirectory.set './testplugin' + runsOnError.set true + requiredSteps.add 'assemble' + } + job.upload('junit-test-results-gradle', ['**/build/test-results/*/TEST-*.xml']) { + runsOnError.set true + requiredSteps.add 'assemble' + with.put('retention-days', '1') + } + } + + def nativeTest = { dev.lukebemish.managedversioning.actions.GitHubAction action -> + action.job { + def strategy = [:] + strategy['fail-fast'] = false + def matrix = [:] + strategy.matrix = matrix + matrix.os = ['linux', 'windows', 'macos'] + // We're giving up on testing on x32 systems (windows or linux) because it's too much of a hassle -- we'd probably need custom JDK builds at some point... + matrix.arch = ['x86_64', 'aarch64', 'arm'] + matrix.exclude = [ + // No natives + [os: 'macos', arch: 'i386'], + [os: 'macos', arch: 'arm'], + [os: 'windows', arch: 'arm'], + + // No way to test at present + [os: 'windows', arch: 'aarch64'], + [os: 'macos', arch: 'x86_64'], + [os: 'linux', arch: 'arm'] + ] + matrix.include = [ + [os: 'macos', runner: 'macos-latest', java_home: '$JAVA_HOME_17_arm64'], + [os: 'windows', runner: 'windows-latest', java_home: '$JAVA_HOME_17_X64'], + [os: 'linux', runner: 'ubuntu-latest', java_home: '$JAVA_HOME_17_X64'], + + [os: 'linux', arch: 'aarch64', qemu: 'arm64', docker_arch: 'arm64', docker: 'arm64v8/eclipse-temurin:17'], + [os: 'linux', arch: 'arm', qemu: 'arm/v7', docker_arch: 'arm/v7', docker: 'arm32v7/eclipse-temurin:17'], + ] + parameters.put('strategy', strategy) + name.set 'natives-test' + needs.add('build') + runsOn.set('${{ matrix.runner }}') + step { + name.set 'Download Artifact' + id.set 'download' + uses.set 'actions/download-artifact@v4' + with.put 'name', 'natives-test-environment' + } + step { + name.set 'Setup QEMU' + parameters.put('if', '${{ matrix.qemu }}') + run.set('sudo apt-get -qq install -y qemu qemu-user-static\n' + + 'docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes') + } + step { + name.set 'Run with Docker' + parameters.put('if', '${{ matrix.docker }}') + run.set 'docker run --workdir "${GITHUB_WORKSPACE}" -v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}:z" --rm --platform linux/${{ matrix.docker_arch }} -t ${{ matrix.docker }} /bin/bash -c \'' + + 'mkdir ../working; cp -r ./ ../working/; cd ../working; ' + + 'java @args-${{ matrix.os }}.txt; return=$?; ' + + 'cp -r ./ ${GITHUB_WORKSPACE}/; cd ${GITHUB_WORKSPACE}; exit $return\'' + } + step { + name.set 'Run' + parameters.put('if', '${{ !matrix.qemu }}') + parameters.put('shell', 'bash') + run.set '${{ matrix.java_home }}/bin/java @args-${{ matrix.os }}.txt' + } + step { + name.set 'Upload Results' + runsOnError.set true + requiredSteps.add 'download' + uses.set 'actions/upload-artifact@v4' + with.put 'name', 'junit-test-results-native-${{ matrix.os }}-${{ matrix.arch }}' + with.put 'path', 'results/TEST-*.xml' + } + } + action.job { + name.set 'aggregate-test-results' + runsOn.set 'ubuntu-latest' + needs.add('natives-test') + needs.add('build') + getIf().set('always()') + step { + name.set 'Download Artifact' + id.set 'download' + uses.set 'actions/download-artifact@v4' + with.put 'pattern', 'junit-test-results-*' + } + step { + name.set 'Upload Results' + uses.set 'actions/upload-artifact@v4' + with.put 'name', 'junit-test-results' + with.put 'path', '**/TEST-*.xml' + } + } + } + snapshot { prettyName.set 'Snapshot' workflowDispatch.set(true) onBranches.add 'main' gradleJob { buildCache() + permissions.put 'packages', 'read' + secret('GITHUB_TOKEN') name.set 'build' - gradlew 'Assemble', 'assemble' - gradlew 'Test', 'check', '--continue' - gradlew('Test Plugin', ['check', '--continue']) { - workingDirectory.set './testplugin' - runsOnError.set true - requiredSteps.add 'assemble' - } - upload('junit-test-results', ['**/build/test-results/*/TEST-*.xml']) { - runsOnError.set true - requiredSteps.add 'assemble' - with.put('retention-days', '1') - } - upload('jacoco-coverage', ['build/reports/jacoco/testCodeCoverageReport']) { - runsOnError.set true - requiredSteps.add 'assemble' - } + testOnBuild.call(it) gradlew 'Publish', 'publish' mavenSnapshot('github') } + nativeTest.call(it) } build_pr { prettyName.set 'Build PR' pullRequest.set(true) gradleJob { name.set 'build' - gradlew 'Assemble', 'assemble' - gradlew 'Test', 'check', '--continue' - gradlew('Test Plugin', ['check', '--continue']) { - workingDirectory.set './testplugin' - runsOnError.set true - requiredSteps.add 'assemble' - } - upload('junit-test-results', ['**/build/test-results/*/TEST-*.xml']) { - runsOnError.set true - requiredSteps.add 'assemble' - with.put('retention-days', '1') - } - upload('jacoco-coverage', ['build/reports/jacoco/testCodeCoverageReport']) { - runsOnError.set true - requiredSteps.add 'assemble' - } + permissions.put 'packages', 'read' + secret('GITHUB_TOKEN') + testOnBuild.call(it) gradlew 'Publish', 'publish' pullRequestArtifact() } + nativeTest.call(it) } publish_pr { prettyName.set 'Publish PR' @@ -131,29 +217,23 @@ managedVersioning { with.put 'github-token', '${{ github.token }}' with.put 'run-id', '${{ github.event.workflow_run.id }}' } - step { - name.set 'JUnit Test Report - Java 17' - uses.set 'dorny/test-reporter@v1' - with.put 'name', 'Test Results - Java 17' - with.put 'path', '**/test-results/testOn17/**/TEST-*.xml' - with.put 'reporter', 'java-junit' - with.put 'fail-on-empty', 'true' - with.put 'list-tests', 'failed' - } - step { - name.set 'JUnit Test Report - Java 21' - uses.set 'dorny/test-reporter@v1' - with.put 'name', 'Test Results - Java 21' - with.put 'path', '**/test-results/testOn21/**/TEST-*.xml' - with.put 'reporter', 'java-junit' - with.put 'fail-on-empty', 'true' - with.put 'list-tests', 'failed' + [17, 21, 22].each { int javaVersion -> + step { + name.set 'JUnit Test Report - Java '+javaVersion + uses.set 'dorny/test-reporter@v1' + with.put 'name', 'Test Results - Java '+javaVersion + with.put 'path', '**/test-results/testOn'+javaVersion+'/**/TEST-*.xml' + with.put 'reporter', 'java-junit' + with.put 'fail-on-empty', 'true' + with.put 'list-tests', 'failed' + with.put 'list-suites', 'failed' + } } step { name.set 'JUnit Test Report - Misc' uses.set 'dorny/test-reporter@v1' with.put 'name', 'Test Results - Misc' - with.put 'path', '**/test-results/test/**/TEST-*.xml' + with.put 'path', '**/test-results/test/**/TEST-*.xml\njunit-test-results-native-*/**/TEST-*.xml' with.put 'reporter', 'java-junit' with.put 'fail-on-empty', 'true' with.put 'list-tests', 'failed' @@ -195,7 +275,6 @@ configurations { dependencies { subprojects.each { p -> if (p.name.startsWith('opensesame-')) { - jacocoAggregation p testReportAggregation p } } @@ -219,12 +298,6 @@ managedVersioning.publishing.mavenCentral() reporting { reports { - testCodeCoverageReport(JacocoCoverageReport) { - testType = TestSuiteType.UNIT_TEST - reportTask.configure { - reports.xml.required = true - } - } testAggregateTestReport(AggregateTestReport) { testType = TestSuiteType.UNIT_TEST } @@ -301,6 +374,5 @@ publishing { } tasks.named('check').configure { - dependsOn tasks.testCodeCoverageReport dependsOn tasks.testAggregateTestReport } diff --git a/buildSrc/src/main/groovy/opensesame.conventions.gradle b/buildSrc/src/main/groovy/opensesame.conventions.gradle index 3f26500..997c067 100644 --- a/buildSrc/src/main/groovy/opensesame.conventions.gradle +++ b/buildSrc/src/main/groovy/opensesame.conventions.gradle @@ -2,8 +2,8 @@ plugins { id 'java-library' id 'signing' id 'maven-publish' - id 'jacoco' id 'opensesame.publishing' + id 'opensesame.testing' } java.toolchain.languageVersion.set JavaLanguageVersion.of(17) @@ -15,96 +15,10 @@ repositories { mavenCentral() } -configurations { - testModuleClasspath { - canBeResolved = true - } -} - dependencies { compileOnly libs.jetbrains.annotations annotationProcessor(libs.autoservice) compileOnly(libs.autoservice) - - testRuntimeOnly libs.junit.engine - testImplementation libs.junit.api - - testImplementation project(':testtargets') - testModuleClasspath project(':testtargets') -} - -def openSesameExtension = project.extensions.getByName('openSesameConvention') - -testing { - suites.create('testOn21', JvmTestSuite) { - targets.configureEach { - testTask.configure { - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(21) - } - } - } - } - - suites.create('testOn17', JvmTestSuite) { - targets.configureEach { - testTask.configure { - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(17) - } - } - } - } - - suites.configureEach { - if (it.name == 'test') { - return - } - - dependencies { - implementation sourceSets.test.output - } - - targets.configureEach { - testTask.configure { - useJUnitPlatform() - - testLogging { - showStandardStreams = true - exceptionFormat = 'full' - showCauses = true - showStackTraces = true - events = ['passed', 'failed', 'skipped'] - } - - if (!openSesameExtension.getTestAsModule().get()) { - modularity.inferModulePath.set(false) - } - - testClassesDirs += files( - sourceSets.test.output, - configurations.testModuleClasspath - ) - - jacoco { - excludes += 'dev/lukebemish/opensesame/test/target/*' - } - } - } - } -} - -configurations { - testOn17RuntimeClasspath.extendsFrom testRuntimeClasspath - testOn21RuntimeClasspath.extendsFrom testRuntimeClasspath - - testResultsElementsForTest.extendsFrom testResultsElementsForTestOn17 - testResultsElementsForTest.extendsFrom testResultsElementsForTestOn21 -} - -tasks.named('check') { - dependsOn(testing.suites.testOn21) - dependsOn(testing.suites.testOn17) } processResources { diff --git a/buildSrc/src/main/groovy/opensesame.testing.gradle b/buildSrc/src/main/groovy/opensesame.testing.gradle new file mode 100644 index 0000000..6cb50a6 --- /dev/null +++ b/buildSrc/src/main/groovy/opensesame.testing.gradle @@ -0,0 +1,72 @@ +plugins { + id 'java-library' + id 'opensesame.publishing' +} + +configurations { + testModuleClasspath { + canBeResolved = true + transitive = false + } +} + +dependencies { + testRuntimeOnly libs.junit.engine + testImplementation libs.junit.api + + testImplementation project(':testtargets') + testModuleClasspath project(':testtargets') + testRuntimeOnly sourceSets.test.output + testRuntimeOnly libs.junit.engine + testRuntimeOnly libs.junit.api +} + +def openSesameExtension = project.extensions.getByName('openSesameConvention') + +def javaVersions = [17, 21, 22] + +testing { + javaVersions.each { javaVersion -> + suites.create("testOn$javaVersion", JvmTestSuite) { + targets.configureEach { + testTask.configure { + useJUnitPlatform() + + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } + + if (!openSesameExtension.getTestAsModule().get()) { + modularity.inferModulePath.set(false) + } else { + systemProperty 'modulartests', 'true' + testClassesDirs.from configurations.testModuleClasspath + } + + testClassesDirs.from sourceSets.test.output.classesDirs + + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(javaVersion) + } + } + } + } + + configurations.maybeCreate("testOn${javaVersion}RuntimeClasspath").extendsFrom configurations.testRuntimeClasspath + } +} + +javaVersions.each { + def resultElements = configurations.maybeCreate("testResultsElementsForTestOn$it") + configurations.testResultsElementsForTest.extendsFrom resultElements +} + +tasks.named('check') { + javaVersions.each { + dependsOn(testing.suites."testOn$it") + } +} \ No newline at end of file diff --git a/compile/build.gradle b/compile/build.gradle index 5f356b2..4d6d48e 100644 --- a/compile/build.gradle +++ b/compile/build.gradle @@ -1,5 +1,6 @@ plugins { id 'opensesame.conventions' + id 'opensesame.testing' } java.withSourcesJar() @@ -19,6 +20,9 @@ configurations { testSource { canBeResolved = true } + testBundle { + canBeResolved = true + } } dependencies { @@ -41,7 +45,7 @@ tasks.named('compileTestJava', JavaCompile).configure { destinationDirectory.set(tempClassesDir) } -var asmCompileTest = tasks.register('processTestClasses', JavaExec) { +var processTestClasses = tasks.register('processTestClasses', JavaExec) { dependsOn compileTestJava dependsOn configurations.asmRuntimeClasspath inputs.dir(tempClassesDir) @@ -50,25 +54,6 @@ var asmCompileTest = tasks.register('processTestClasses', JavaExec) { classpath = sourceSets.asm.runtimeClasspath mainClass.set 'dev.lukebemish.opensesame.compile.asm.VisitingProcessor' args = [tempClassesDir.get().asFile.canonicalPath, sourceSets.test.output.classesDirs.singleFile.canonicalPath] - - jacoco.applyTo(it) - extensions.configure(JacocoTaskExtension) { - it.excludes += 'dev/lukebemish/opensesame/test/target/*' - } - - finalizedBy tasks.named('testAsmCompileCodeCoverateReport') -} - -var report = tasks.register('testAsmCompileCodeCoverateReport', JacocoReport) { - dependsOn asmCompileTest.get() - executionData asmCompileTest.get() - sourceSets sourceSets.main -} - -artifacts { - add('coverageDataElementsForTest', report.get().executionData.singleFile) { - builtBy report.get() - } } tasks.testClasses.dependsOn processTestClasses @@ -96,7 +81,7 @@ publishing { mavenJava(MavenPublication) { pom { openSesameConvention.pomShared(it, 'Compile') - description = 'Compile-time abstractions for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Compile-time abstractions for OpenSesame, a tool for typesafe access to normally inaccessible members' } } } diff --git a/compile/src/asm/java/dev/lukebemish/opensesame/compile/asm/VisitingProcessor.java b/compile/src/asm/java/dev/lukebemish/opensesame/compile/asm/VisitingProcessor.java index fbeb546..46334b6 100644 --- a/compile/src/asm/java/dev/lukebemish/opensesame/compile/asm/VisitingProcessor.java +++ b/compile/src/asm/java/dev/lukebemish/opensesame/compile/asm/VisitingProcessor.java @@ -70,14 +70,11 @@ public static void main(String[] args) { } } - public static void process(Path input, Path output) throws IOException { - if (Files.isDirectory(input)) { - try (var paths = Files.walk(input)) { + public static void cleanup(Path output) throws IOException { + if (Files.isDirectory(output)) { + try (var paths = Files.walk(output)) { paths.filter(Files::isRegularFile) - .filter(file -> - file.getFileName().toString().endsWith(VisitingProcessor.UNFINAL_SERVICE + ".class") - || file.getFileName().toString().endsWith(MIXIN_PROVIDER.getClassName()) - ) + .filter(VisitingProcessor::isOpenSesameGenerated) .forEach(file -> { try { Files.delete(file); @@ -86,20 +83,39 @@ public static void process(Path input, Path output) throws IOException { } }); } + } + } + + private static boolean isOpenSesameGenerated(Path file) { + return file.getFileName().toString().endsWith(VisitingProcessor.UNFINAL_SERVICE + ".class") + || file.getFileName().toString().endsWith(MIXIN_PROVIDER.getClassName()); + } + + public static Set process(Path input, Path output) throws IOException { + if (Files.isDirectory(input)) { + cleanup(output); + Set modified = new HashSet<>(); try (var paths = Files.walk(input)) { - paths.filter(Files::isRegularFile).forEach(file -> { + paths.filter(Files::isRegularFile).filter(f -> !isOpenSesameGenerated(f)).forEach(file -> { try { var relative = input.relativize(file); var out = output.resolve(relative); Files.createDirectories(out.getParent()); - processFile(file, out, output); + if (processFile(file, out, output)) { + modified.add(file); + } } catch (IOException e) { throw new RuntimeException(e); } }); } + return modified; } else { - processFile(input, output, null); + if (processFile(input, output, null)) { + return Set.of(input); + } else { + return Set.of(); + } } } @@ -148,11 +164,12 @@ protected enum MixinProviderType { } } - private static void processFile(Path file, Path out, @Nullable Path rootPath) throws IOException { + public static boolean processFile(Path file, Path out, @Nullable Path rootPath) throws IOException { if (!file.getFileName().toString().endsWith(".class")) { Files.copy(file, out); - return; + return false; } + boolean[] modifiedExternal = {false}; try (var inputStream = Files.newInputStream(file)) { ClassReader reader = new ClassReader(inputStream); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); @@ -208,6 +225,7 @@ protected void writeMixinProviderLines(Map> line makeMixins(false, true, selfType, target, index, rootPath); makeMixins(false, false, selfType, target, index, rootPath); } + modifiedExternal[0] = true; } super.writeMixinProviderLines(lines, selfType); } @@ -237,6 +255,7 @@ private static void generateImpl(String implMethodName, Type selfType, ClassWrit } Files.write(out, writer.toByteArray()); } + return modifiedExternal[0]; } List unFinalLines = new ArrayList<>(); diff --git a/core/build.gradle b/core/build.gradle index c5fb7ba..cacfd31 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -15,7 +15,7 @@ publishing { mavenJava(MavenPublication) { pom { openSesameConvention.pomShared(it, 'Core') - description = 'Core annotations and runtime metafactory used for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Core annotations and runtime metafactory used for OpenSesame, a tool for typesafe access to normally inaccessible members' } } } diff --git a/core/src/main/java/dev/lukebemish/opensesame/annotations/Open.java b/core/src/main/java/dev/lukebemish/opensesame/annotations/Open.java index 8bf81ad..138b284 100644 --- a/core/src/main/java/dev/lukebemish/opensesame/annotations/Open.java +++ b/core/src/main/java/dev/lukebemish/opensesame/annotations/Open.java @@ -48,7 +48,7 @@ Type type(); /** - * {@return whether this invocation should be done unsafely, breaking module boundaries with {@link sun.misc.Unsafe}} + * {@return whether this invocation should be done unsafely, breaking module boundaries with native access or {@link sun.misc.Unsafe}} */ boolean unsafe() default false; diff --git a/core/src/main/java/dev/lukebemish/opensesame/annotations/extend/Extend.java b/core/src/main/java/dev/lukebemish/opensesame/annotations/extend/Extend.java index fcac16b..49613a3 100644 --- a/core/src/main/java/dev/lukebemish/opensesame/annotations/extend/Extend.java +++ b/core/src/main/java/dev/lukebemish/opensesame/annotations/extend/Extend.java @@ -38,7 +38,7 @@ Class targetProvider() default ErrorProvider.class; /** - * Determines whether the extension can occur over module boundaries with {@link sun.misc.Unsafe}. Note that this + * Determines whether the extension can occur over module boundaries with native access or {@link sun.misc.Unsafe}. Note that this * is required to be {@code true} in order to extend non-public classes in another module. * @return whether the extension should be done unsafely, breaking module boundaries */ diff --git a/core/src/main/java/dev/lukebemish/opensesame/runtime/LookupProviderNative.java b/core/src/main/java/dev/lukebemish/opensesame/runtime/LookupProviderNative.java new file mode 100644 index 0000000..2b975ab --- /dev/null +++ b/core/src/main/java/dev/lukebemish/opensesame/runtime/LookupProviderNative.java @@ -0,0 +1,32 @@ +package dev.lukebemish.opensesame.runtime; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +@ApiStatus.Internal +class LookupProviderNative implements LookupProvider { + + + private final MethodHandles.Lookup lookup; + + LookupProviderNative() { + try { + Class clazz = Class.forName("dev.lukebemish.opensesame.natives.NativeImplementations"); + var nativesLookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + MethodHandle setup = nativesLookup.findStatic(clazz, "setup", MethodType.methodType(void.class)); + MethodHandle nativeImplLookup = nativesLookup.findStatic(clazz, "nativeImplLookup", MethodType.methodType(MethodHandles.Lookup.class)); + setup.invoke(); + this.lookup = (MethodHandles.Lookup) nativeImplLookup.invoke(); + } catch (Throwable t) { + throw new RuntimeException("Error calling native library", t); + } + } + + @Override + public MethodHandles.Lookup openingLookup(MethodHandles.Lookup original, Class target) { + return this.lookup; + } +} diff --git a/core/src/main/java/dev/lukebemish/opensesame/runtime/OpeningMetafactory.java b/core/src/main/java/dev/lukebemish/opensesame/runtime/OpeningMetafactory.java index 10c0ba4..7d5963c 100644 --- a/core/src/main/java/dev/lukebemish/opensesame/runtime/OpeningMetafactory.java +++ b/core/src/main/java/dev/lukebemish/opensesame/runtime/OpeningMetafactory.java @@ -154,11 +154,17 @@ public int hashCode() { Exception LOOKUP_PROVIDER_EXCEPTION1; LookupProvider LOOKUP_PROVIDER1; try { - LOOKUP_PROVIDER1 = new LookupProviderUnsafe(); + LOOKUP_PROVIDER1 = new LookupProviderNative(); LOOKUP_PROVIDER_EXCEPTION1 = null; - } catch (Exception e) { - LOOKUP_PROVIDER_EXCEPTION1 = e; - LOOKUP_PROVIDER1 = new LookupProviderFallback(); + } catch (Exception e1) { + LOOKUP_PROVIDER_EXCEPTION1 = e1; + try { + LOOKUP_PROVIDER1 = new LookupProviderUnsafe(); + } catch(Exception e2){ + e2.addSuppressed(e1); + LOOKUP_PROVIDER_EXCEPTION1 = e2; + LOOKUP_PROVIDER1 = new LookupProviderFallback(); + } } LOOKUP_PROVIDER_EXCEPTION = LOOKUP_PROVIDER_EXCEPTION1; LOOKUP_PROVIDER_UNSAFE = LOOKUP_PROVIDER1; diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 1c1ade6..5a45782 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -6,6 +6,7 @@ exports dev.lukebemish.opensesame.runtime; exports dev.lukebemish.opensesame.annotations; + exports dev.lukebemish.opensesame.annotations.extend; uses dev.lukebemish.opensesame.runtime.RuntimeRemapper; } \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle index fcde157..1ea4391 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -61,7 +61,7 @@ publishing { mavenJava(MavenPublication) { pom { openSesameConvention.pomShared(it, 'Fabric') - description = 'Fabric compatibility for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Fabric compatibility for OpenSesame, a tool for typesafe access to normally inaccessible members' } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2891f69..675882b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,8 @@ [versions] -junit = "5.8.1" +junitjupiter = "5.8.1" +junitplatform = "1.10.3" +junit = "4.13.2" jetbrains_annotations = "24.0.1" autoservice = "1.0.1" @@ -14,10 +16,14 @@ tinyremapper = "0.10.0" mixin = "0.8.5" +managedversioning = "1.2.21" + [libraries] -junit_api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } -junit_engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } +junit_api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitjupiter" } +junit_engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitjupiter" } +junit_console = { module = "org.junit.platform:junit-platform-console", version.ref = "junitplatform" } +junit_junit = { module = "junit:junit", version.ref = "junit" } jetbrains_annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains_annotations" } autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } @@ -34,3 +40,7 @@ fabric_loader_junit = { module = "net.fabricmc:fabric-loader-junit", version.ref fabric_tinyremapper = { module = "net.fabricmc:tiny-remapper", version.ref = "tinyremapper" } mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" } + +[plugins] + +managedversioning = { id = "dev.lukebemish.managedversioning", version.ref = "managedversioning" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..b740cf1 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/groovy/build.gradle b/groovy/build.gradle index 4f0bf72..35d2f4a 100644 --- a/groovy/build.gradle +++ b/groovy/build.gradle @@ -23,63 +23,6 @@ tasks.named('compileGroovy', GroovyCompile).configure { it.groovyOptions.optimizationOptions.indy = true } -def testCompileOn(int v) { - var singleTest = tasks.register('testCompileOn'+v, JavaExec) { - javaLauncher.set javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(v) - } - - var task = it - dependsOn configurations.testRuntimeClasspath - dependsOn sourceSets.test.groovy.sourceDirectories - - var dirProvider = layout.buildDirectory.dir('testClasses') - - outputs.dir(dirProvider) - inputs.files(sourceSets.test.groovy.sourceDirectories) - - group = "Verification" - description = "Compile test sources with groovy" - classpath = sourceSets.main.runtimeClasspath - mainClass.set 'org.codehaus.groovy.tools.FileSystemCompiler' - argumentProviders.add( - new CommandLineArgumentProvider() { - @Override - List asArguments() { - return [ - '-cp', configurations.testCompileClasspath.asPath, - '--temp', task.temporaryDir.path, - '-d', dirProvider.get().asFile.path - ] + - sourceSets.test.groovy.srcDirs.collectMany { fileTree(it.path).files.collect { it.path } } - } - } - ) - - jacoco.applyTo(it) - extensions.configure(JacocoTaskExtension) { - it.excludes += 'dev/lukebemish/opensesame/test/target/*' - } - - finalizedBy tasks.named('testCompileOn'+v+'CodeCoverateReport') - } - - var report = tasks.register('testCompileOn'+v+'CodeCoverateReport', JacocoReport) { - dependsOn singleTest.get() - executionData singleTest.get() - sourceSets sourceSets.main - } - - artifacts { - add('coverageDataElementsForTest', report.get().executionData.singleFile) { - builtBy report.get() - } - } -} - -testCompileOn(17) -testCompileOn(21) - publishing { publications { mavenJava(MavenPublication) { @@ -87,7 +30,7 @@ publishing { pom { openSesameConvention.pomShared(it, 'Groovy Transform') - description = 'Compile-time groovy ASTT used for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Compile-time groovy ASTT used for OpenSesame, a tool for typesafe access to normally inaccessible members' } } } diff --git a/javac/build.gradle b/javac/build.gradle index 6432d56..ba737f3 100644 --- a/javac/build.gradle +++ b/javac/build.gradle @@ -60,75 +60,12 @@ tasks.named('compileTestJava', JavaCompile).configure { inputs.files(sourceSets.main.runtimeClasspath) } -def testCompileOn(int v) { - var singleTest = tasks.register('testCompileOn'+v, JavaExec) { - javaLauncher.set javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(v) - } - - dependsOn configurations.testRuntimeClasspath - dependsOn sourceSets.test.java.sourceDirectories - dependsOn configurations.testSource - - var dirProvider = layout.buildDirectory.dir('testClasses/'+v) - - outputs.dir(dirProvider) - inputs.files(sourceSets.test.java.sourceDirectories) - inputs.files(configurations.testSource) - - group = "Verification" - description = "Compile test sources with java" - classpath = sourceSets.main.runtimeClasspath - mainClass.set 'com.sun.tools.javac.Main' - argumentProviders.add( - new CommandLineArgumentProvider() { - @Override - List asArguments() { - return [ - '-cp', configurations.testCompileClasspath.asPath, - '-d', dirProvider.get().asFile.path, - '-Xplugin:OpenSesame' - ] + - (sourceSets.test.java.srcDirs.collectMany { fileTree(it.path).files.collect { it.path } }) + - (configurations.testSource.files.collectMany { fileTree(it.path).files.collect { it.path } }) - } - } - ) - - jacoco.applyTo(it) - extensions.configure(JacocoTaskExtension) { - it.excludes += 'dev/lukebemish/opensesame/test/target/*' - } - - finalizedBy tasks.named('testCompileOn'+v+'CodeCoverateReport') - } - - var report = tasks.register('testCompileOn'+v+'CodeCoverateReport', JacocoReport) { - dependsOn singleTest.get() - executionData singleTest.get() - sourceSets sourceSets.main - } - - artifacts { - add('coverageDataElementsForTest', report.get().executionData.singleFile) { - builtBy report.get() - } - } - - tasks.named('check').configure { - dependsOn singleTest - } -} - -testCompileOn(17) -testCompileOn(21) - publishing { publications { mavenJava(MavenPublication) { pom { openSesameConvention.pomShared(it, 'Javac Compiler Plugin') - description = 'javac compiler plugin for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'javac compiler plugin for OpenSesame, a tool for typesafe access to normally inaccessible members' } } } diff --git a/mixin/build.gradle b/mixin/build.gradle index 7e2853e..2b79abf 100644 --- a/mixin/build.gradle +++ b/mixin/build.gradle @@ -29,30 +29,6 @@ dependencies { asmRuntimeClasspath project(path: ':opensesame-compile', configuration: 'asmRuntimeElements') } -// process test classes with ASM processor -def tempClassesDir = layout.buildDirectory.dir("tempClasses/compileTestJava") -var asmCompileTest = tasks.register('processTestClasses', JavaExec) { - dependsOn compileTestJava - dependsOn configurations.asmRuntimeClasspath - inputs.dir(tempClassesDir) - inputs.files(configurations.asmRuntimeClasspath) - outputs.dir(sourceSets.test.output.classesDirs) - doFirst { - sourceSets.test.output.classesDirs.singleFile.deleteDir() - } - classpath = configurations.asmRuntimeClasspath - mainClass.set 'dev.lukebemish.opensesame.compile.asm.VisitingProcessor' - args = [tempClassesDir.get().asFile.canonicalPath, sourceSets.test.output.classesDirs.singleFile.canonicalPath] -} - -tasks.named('compileTestJava', JavaCompile).configure { - destinationDirectory.set(tempClassesDir) -} - -tasks.named('testClasses').configure { - dependsOn asmCompileTest -} - jar { manifest { attributes.put('FMLModType', 'MOD') diff --git a/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/OpenSesameMixinPlugin.java b/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/OpenSesameMixinPlugin.java index ffe5f5c..3274e3d 100644 --- a/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/OpenSesameMixinPlugin.java +++ b/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/OpenSesameMixinPlugin.java @@ -54,7 +54,6 @@ public List getMixins() { var classLoader = OpenSesameMixinPlugin.class.getClassLoader(); List providers = new ArrayList<>(); ServiceLoader.load(OpenSesameMixinProvider.class, classLoader).forEach(providers::add); - collectLegacyProviders(classLoader, providers); final Set targetClasses = new HashSet<>(); final Set deFinalClasses = new HashSet<>(); final Map> deFinalMethods = new HashMap<>(); @@ -83,17 +82,32 @@ public List getMixins() { return mixins; } - @SuppressWarnings("removal") - private static void collectLegacyProviders(ClassLoader classLoader, List providers) { - ServiceLoader.load(UnFinalLineProvider.class, classLoader).forEach(provider -> providers.add(provider.makeToOpen())); + private static String remapDescriptor(String descriptor) { + var type = Type.getType(descriptor); + if (type.getSort() == Type.METHOD) { + var builder = new StringBuilder(); + builder.append("("); + for (var argument : type.getArgumentTypes()) { + builder.append(remapDescriptor(argument.getDescriptor())); + } + builder.append(")"); + builder.append(remapDescriptor(type.getReturnType().getDescriptor())); + return builder.toString(); + } else if (type.getSort() == Type.ARRAY) { + return "[" + remapDescriptor(type.getElementType().getDescriptor()); + } else if (type.getSort() == Type.OBJECT) { + return Type.getObjectType(OpeningMetafactory.remapClass(type.getInternalName().replace('/', '.'), OpenSesameMixinPlugin.class.getClassLoader()).replace('.', '/')).getDescriptor(); + } else { + return descriptor; + } } private static void extractActions(String line, ClassLoader classLoader, List mixins, Set targetClasses, Set classes, Map> methods, Map> fields, boolean allowFields, String searchingName) { if (line.isBlank()) return; var parts = line.split("\\."); - var packageName = parts[0].replace("/", "."); - var className = parts[1].replace("/", "."); + var packageName = parts[0].replace('/', '.'); + var className = parts[1].replace('/', '.'); var remappedClassName = OpeningMetafactory.remapClass(className, classLoader); if (parts.length == 2) { classes.add(remappedClassName); @@ -102,7 +116,7 @@ private static void extractActions(String line, ClassLoader classLoader, List new ArrayList<>()).add(OpeningMetafactory.remapMethod(name, desc, className, classLoader)); + methods.computeIfAbsent(remappedClassName, k -> new ArrayList<>()).add(OpeningMetafactory.remapMethod(name, desc, className, classLoader)+"."+remapDescriptor(desc)); } else if (type.getSort() == Type.OBJECT) { if (!allowFields) { throw new RuntimeException("Invalid " + searchingName + " line: " + line); diff --git a/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/UnFinalLineProvider.java b/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/UnFinalLineProvider.java deleted file mode 100644 index 094d01e..0000000 --- a/mixin/src/main/java/dev/lukebemish/opensesame/mixin/plugin/UnFinalLineProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.lukebemish.opensesame.mixin.plugin; - -import java.util.Arrays; - -@SuppressWarnings("DeprecatedIsStillUsed") -@Deprecated(forRemoval = true) -public interface UnFinalLineProvider { - String[] lines(); - - default OpenSesameMixinProvider makeToOpen() { - return new OpenSesameMixinProvider() { - @Override - public String[] unFinal() { - return Arrays.stream(lines()).map(line -> { - String[] parts = line.split(" "); - parts[0] = parts[0].replace(".", "/"); - parts[1] = parts[1].replace(".", "/"); - return String.join(".", parts); - }).toArray(String[]::new); - } - }; - } -} diff --git a/mixin/src/test/java/dev/lukebemish/opensesame/test/unfinal/TestUnFinal.java b/mixin/src/test/java/dev/lukebemish/opensesame/test/unfinal/TestUnFinal.java deleted file mode 100644 index b5c4935..0000000 --- a/mixin/src/test/java/dev/lukebemish/opensesame/test/unfinal/TestUnFinal.java +++ /dev/null @@ -1,53 +0,0 @@ -package dev.lukebemish.opensesame.test.unfinal; - -import dev.lukebemish.opensesame.annotations.Open; -import dev.lukebemish.opensesame.annotations.extend.Constructor; -import dev.lukebemish.opensesame.annotations.extend.Extend; -import dev.lukebemish.opensesame.annotations.extend.Overrides; -import dev.lukebemish.opensesame.annotations.mixin.UnFinal; -import dev.lukebemish.opensesame.test.target.Final; -import dev.lukebemish.opensesame.test.target.Public; -import org.junit.jupiter.api.Disabled; - -// We don't have a way to test this besides looking at the bytecode, at present -@Disabled -public class TestUnFinal { - @Open( - name = "privateFinalInstanceField", - targetClass = Public.class, - type = Open.Type.SET_STATIC - ) - @UnFinal - private static void privateFinalInstanceField(String value) { - throw new AssertionError("Method not replaced"); - } - - @Extend( - targetClass = Final.class, - unsafe = true - ) - @UnFinal - public interface FinalExtension { - @Constructor - static FinalExtension constructor() { - throw new AssertionError("Constructor not replaced"); - } - } - - @Extend( - targetClass = Public.class, - unsafe = true - ) - public interface PublicExtension { - @Constructor - static PublicExtension constructor() { - throw new AssertionError("Constructor not replaced"); - } - - @Overrides(value = "finalMethod") - @UnFinal - default String finalMethodOverride() { - return "not so final now!"; - } - } -} diff --git a/natives/build.gradle b/natives/build.gradle new file mode 100644 index 0000000..3ea64a1 --- /dev/null +++ b/natives/build.gradle @@ -0,0 +1,162 @@ +import javax.inject.Inject + +plugins { + id 'opensesame.conventions' +} + +java.withSourcesJar() +java.withJavadocJar() + +repositories { + maven { + url = uri("https://maven.pkg.github.com/lukebemishprojects/OpenSesame") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + content { + includeModule 'dev.lukebemish', 'opensesamenative' + } + } +} + +configurations { + natives +} + +def targets = [ + 'linux': ['x86_64', 'aarch64', 'i386', 'arm'], + 'windows': ['x86_64', 'aarch64', 'i386'], + 'macos': ['x86_64', 'aarch64'] +] + +def targetfinder = [ + 'linux': 'libopensesamenative.so', + 'windows': 'opensesamenative.dll', + 'macos': 'libopensesamenative.dylib' +] + +def nativesCommit = '2a6fe093edd06bfbe66e6d243d9a128233decc76' + +targets.each { os, archs -> + archs.each { arch -> + def finalName = targetfinder[os] + def extension = finalName.split(/\./)[1] + dependencies.add('natives', "dev.lukebemish:opensesamenative:$nativesCommit:$os-$arch@$extension") + } +} + +processResources { + from(configurations.natives) { + into 'dev/lukebemish/opensesame/natives' + rename { String fileName -> + String[] parts = fileName.split(/[-.]/) + String os = parts[2] + String arch = parts[3] + return "$os/$arch/${targetfinder[os]}" + } + } +} + +configurations { + runtimeElements { + outgoing.variants.clear() + } + apiElements { + outgoing.variants.clear() + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + openSesameConvention.pomShared(it, 'Core') + description = 'Native code for OpenSesame, a tool for typesafe access to normally inaccessible members' + } + } + } +} + +sourceSets { + testnatives {} +} + +configurations { + testBundle { + canBeResolved = true + } + testnativesImplementation.extendsFrom testBundle +} + +dependencies { + testBundle libs.junit.api + testBundle libs.junit.engine + testBundle libs.junit.console + testBundle libs.junit.junit + testBundle project(':opensesame-natives') +} + +abstract class SetupJunitEnvironment extends DefaultTask { + @OutputDirectory + abstract DirectoryProperty getOutputDirectory() + + @InputFiles + @PathSensitive(PathSensitivity.NAME_ONLY) + abstract ConfigurableFileCollection getClasspath() + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + abstract ConfigurableFileCollection getTestClasses() + + @Inject + SetupJunitEnvironment() {} + + @Inject + abstract FileSystemOperations getFileSystemOperations() + + @TaskAction + void run() { + getOutputDirectory().get().asFile.deleteDir() + getOutputDirectory().get().asFile.mkdirs() + fileSystemOperations.copy { + from classpath + into getOutputDirectory().get() + } + fileSystemOperations.copy { + from testClasses + into getOutputDirectory().get().dir('testclasses') + } + def unixargfile = getOutputDirectory().get().file('args-linux.txt') + def macosargfile= getOutputDirectory().get().file('args-macos.txt') + def winargfile = getOutputDirectory().get().file('args-windows.txt') + def args = [] + def filenames = classpath.files.collect { it.name } + args.add('-cp') + args.add(filenames.join(':')) + args.add('org.junit.platform.console.ConsoleLauncher') + args.add('execute') + args.add('--fail-if-no-tests') + args.add('--class-path=testclasses') + testClasses.files.each { outer -> + outer.eachFileRecurse(groovy.io.FileType.FILES) { classFile -> + def className = outer.relativePath(classFile).replace('/', '.').replace('\\', '.') + if (className.endsWith('.class')) { + args.add('--select-class=' + className[0..-7]) + } + } + } + args.add('--reports-dir=results') + unixargfile.getAsFile().text = args.join(' ') + macosargfile.getAsFile().text = args.join(' ') + winargfile.getAsFile().text = args.join(' ').replace(':', ';') + } +} + +tasks.register('setupTestEnvironment', SetupJunitEnvironment) { + dependsOn tasks.testnativesClasses + outputDirectory.set(layout.buildDirectory.dir('testEnvironment')) + classpath.from configurations.testBundle + testClasses.from sourceSets.testnatives.output.classesDirs +} + diff --git a/natives/src/main/java/dev/lukebemish/opensesame/natives/NativeImplementations.java b/natives/src/main/java/dev/lukebemish/opensesame/natives/NativeImplementations.java new file mode 100644 index 0000000..eb29d6e --- /dev/null +++ b/natives/src/main/java/dev/lukebemish/opensesame/natives/NativeImplementations.java @@ -0,0 +1,95 @@ +package dev.lukebemish.opensesame.natives; + +import org.jetbrains.annotations.ApiStatus; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Set; + +@ApiStatus.Internal +public class NativeImplementations { + static volatile boolean SETUP = false; + + private enum Architecture { + I386("i386"), + X86_64("x86_64"), + ARM("arm"), + AAARCH64("aarch64"); + + private final String name; + + Architecture(String name) { + this.name = name; + } + } + + private enum OperatingSystem { + WINDOWS(Set.of(Architecture.I386, Architecture.X86_64, Architecture.AAARCH64)), + LINUX(Set.of(Architecture.I386, Architecture.X86_64, Architecture.AAARCH64, Architecture.ARM)), + MACOS(Set.of(Architecture.X86_64, Architecture.AAARCH64)); + + private final Set architectures; + + OperatingSystem(Set architectures) { + this.architectures = architectures; + } + } + + static void setup() throws IOException { + if (SETUP) { + return; + } + String osName = System.getProperty("os.name").toLowerCase(); + OperatingSystem os; + if (osName.startsWith("windows")) { + os = OperatingSystem.WINDOWS; + } else if (osName.startsWith("linux")) { + os = OperatingSystem.LINUX; + } else if (osName.startsWith("mac")) { + os = OperatingSystem.MACOS; + } else { + throw new IllegalStateException("Unsupported operating system for opensesame native lookup provider: " + osName); + } + + Architecture arch; + String osArch = System.getProperty("os.arch"); + boolean is64Bit = osArch.contains("64") || osArch.startsWith("armv8"); + if (osArch.startsWith("aarch") || osArch.startsWith("arm")) { + arch = is64Bit ? Architecture.AAARCH64 : Architecture.ARM; + } else if (osArch.startsWith("ppc") || osArch.startsWith("riscv")) { + throw new IllegalStateException("Unsupported architecture for opensesame native lookup provider: " + osArch); + } else { + arch = is64Bit ? Architecture.X86_64 : Architecture.I386; + } + + if (!os.architectures.contains(arch)) { + throw new IllegalStateException("Unsupported architecture and operating system combination for opensesame native lookup provider: " + osArch + ", " + osName); + } + + String fileName = switch (os) { + case LINUX -> "libopensesamenative.so"; + case MACOS -> "libopensesamenative.dylib"; + case WINDOWS -> "opensesamenative.dll"; + }; + Path path = Files.createTempDirectory(null).resolve(fileName); + String nativePath = "/dev/lukebemish/opensesame/natives/"+os.name().toLowerCase(Locale.ROOT)+"/"+arch.name+"/"+fileName; + var resource = NativeImplementations.class.getResource( + nativePath + ); + if (resource == null) { + throw new IOException("Could not find native library in opensesame jar, at path "+nativePath); + } + try (var lib = resource.openStream()) { + Files.copy(lib, path.toAbsolutePath()); + } + assert Files.exists(path); + System.load(path.toAbsolutePath().toString()); + SETUP = true; + } + + native static MethodHandles.Lookup nativeImplLookup(); +} + diff --git a/natives/src/main/java/module-info.java b/natives/src/main/java/module-info.java new file mode 100644 index 0000000..a25a350 --- /dev/null +++ b/natives/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module dev.lukebemish.opensesame.natives { + requires static org.jetbrains.annotations; + + opens dev.lukebemish.opensesame.natives to dev.lukebemish.opensesame.core; + exports dev.lukebemish.opensesame.natives to dev.lukebemish.opensesame.core; +} diff --git a/natives/src/testnatives/java/dev/lukebemish/opensesame/test/natives/TestNatives.java b/natives/src/testnatives/java/dev/lukebemish/opensesame/test/natives/TestNatives.java new file mode 100644 index 0000000..6fcae7d --- /dev/null +++ b/natives/src/testnatives/java/dev/lukebemish/opensesame/test/natives/TestNatives.java @@ -0,0 +1,22 @@ +package dev.lukebemish.opensesame.test.natives; + +import dev.lukebemish.opensesame.natives.NativeImplementations; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +class TestNatives { + @Test + void testNativeImplLookup() throws Throwable { + Class clazz = NativeImplementations.class; + var lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + MethodHandle setup = lookup.findStatic(clazz, "setup", MethodType.methodType(void.class)); + MethodHandle nativeImplLookup = lookup.findStatic(clazz, "nativeImplLookup", MethodType.methodType(MethodHandles.Lookup.class)); + setup.invoke(); + MethodHandles.Lookup implLookup = (MethodHandles.Lookup) nativeImplLookup.invoke(); + Assertions.assertNotEquals(0, implLookup.lookupModes() & MethodHandles.Lookup.ORIGINAL); + } +} diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index ea71604..2b85b30 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -38,7 +38,7 @@ gradlePlugin { id = 'dev.lukebemish.opensesame' implementationClass = 'dev.lukebemish.opensesame.plugin.OpenSesamePlugin' displayName = 'OpenSesame - Gradle Plugin' - description = 'Gradle plugin for OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Gradle plugin for OpenSesame, a tool for typesafe access to normally inaccessible members' tags.addAll(['java', 'mapping']) } } diff --git a/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameExtension.java b/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameExtension.java index dcdf4b7..03e40b7 100644 --- a/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameExtension.java +++ b/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameExtension.java @@ -1,7 +1,7 @@ package dev.lukebemish.opensesame.plugin; -import dev.lukebemish.opensesame.compile.asm.VisitingProcessor; import org.gradle.api.Project; +import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; @@ -9,8 +9,7 @@ import org.gradle.api.tasks.compile.JavaCompile; import javax.inject.Inject; -import java.io.IOException; -import java.nio.file.Path; +import java.util.Locale; public abstract class OpenSesameExtension implements ExtensionAware { private final Project project; @@ -20,20 +19,21 @@ public OpenSesameExtension(Project project) { this.project = project; } - public void apply(TaskProvider compileTaskProvider) { + public void apply(SourceSet sourceSet, SourceDirectorySet sourceDirectorySet, TaskProvider compileTaskProvider) { + var unprocessed = project.getLayout().getBuildDirectory().dir("openSesame/unprocessed/"+compileTaskProvider.getName()); compileTaskProvider.configure(compileTask -> - compileTask.doLast("processOpenSesame", task -> { - try { - Path classesPath = compileTask.getDestinationDirectory().get().getAsFile().toPath(); - VisitingProcessor.process(classesPath, classesPath); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) + compileTask.getDestinationDirectory().set(unprocessed) ); + var capitalized = compileTaskProvider.getName().substring(0, 1).toUpperCase(Locale.ROOT) + compileTaskProvider.getName().substring(1); + var openSesameTask = project.getTasks().register("openSesame" + capitalized, OpenSesameTask.class, task -> { + task.getInputClasses().set(compileTaskProvider.get().getDestinationDirectory()); + task.getOutputClasses().set(sourceDirectorySet.getClassesDirectory()); + }); + sourceDirectorySet.compiledBy(openSesameTask, OpenSesameTask::getOutputClasses); + project.getTasks().named(sourceSet.getClassesTaskName()).configure(task -> task.dependsOn(openSesameTask)); } public void apply(SourceSet sourceSet) { - apply(project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class)); + apply(sourceSet, sourceSet.getJava(), project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class)); } } diff --git a/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameTask.java b/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameTask.java new file mode 100644 index 0000000..4c24874 --- /dev/null +++ b/plugin/core/src/main/java/dev/lukebemish/opensesame/plugin/OpenSesameTask.java @@ -0,0 +1,87 @@ +package dev.lukebemish.opensesame.plugin; + +import dev.lukebemish.opensesame.compile.asm.VisitingProcessor; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.IgnoreEmptyDirectories; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.LocalState; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; +import org.gradle.work.ChangeType; +import org.gradle.work.InputChanges; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +@CacheableTask +public abstract class OpenSesameTask extends DefaultTask { + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + @IgnoreEmptyDirectories + @SkipWhenEmpty + public abstract DirectoryProperty getInputClasses(); + + @OutputDirectory + public abstract DirectoryProperty getOutputClasses(); + + @LocalState + public abstract RegularFileProperty getIncrementalClasses(); + + @Inject + public OpenSesameTask() { + getIncrementalClasses().convention(getProject().getLayout().getBuildDirectory().file("openSesame/incrementalClasses/"+getName())); + } + + @TaskAction + protected void process(InputChanges changes) throws IOException { + var outputDir = getOutputClasses().get().getAsFile().toPath(); + var inputDir = getInputClasses().get().getAsFile().toPath(); + if (!Files.exists(outputDir)) { + Files.createDirectories(outputDir); + } + Set toProcess = new LinkedHashSet<>(); + var incrementalClassesFile = getIncrementalClasses().get().getAsFile(); + if (incrementalClassesFile.exists()) { + toProcess.addAll(Files.readAllLines(incrementalClassesFile.toPath())); + } + changes.getFileChanges(getInputClasses()).forEach(change -> { + var path = change.getFile().toPath(); + var relativePath = inputDir.relativize(path).toString(); + var outputPath = outputDir.resolve(relativePath); + if (change.getChangeType() == ChangeType.REMOVED) { + toProcess.remove(relativePath); + if (Files.exists(outputPath)) { + try { + Files.delete(outputPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } else { + toProcess.add(relativePath); + } + }); + VisitingProcessor.cleanup(outputDir); + List processed = new ArrayList<>(); + for (var relativePath : toProcess) { + Files.createDirectories(outputDir.resolve(relativePath).getParent()); + var inputPath = inputDir.resolve(relativePath); + var outputPath = outputDir.resolve(relativePath); + if (VisitingProcessor.processFile(inputPath, outputPath, outputDir)) { + processed.add(relativePath); + } + } + Files.write(incrementalClassesFile.toPath(), processed); + } +} diff --git a/plugin/loom/build.gradle b/plugin/loom/build.gradle index 2ebf660..b10dd14 100644 --- a/plugin/loom/build.gradle +++ b/plugin/loom/build.gradle @@ -47,7 +47,7 @@ gradlePlugin { id = 'dev.lukebemish.opensesame.loom' implementationClass = 'dev.lukebemish.opensesame.plugin.loom.OpenSesamePluginLoom' displayName = 'OpenSesame Loom - Gradle Plugin' - description = 'Gradle plugin adding fabric-loom support to OpenSesame, a tool for typesafe access to normally inacessible members' + description = 'Gradle plugin adding fabric-loom support to OpenSesame, a tool for typesafe access to normally inaccessible members' tags.addAll(['java', 'mapping']) } } diff --git a/settings.gradle b/settings.gradle index 826f913..6b3302a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,7 @@ rootProject.name = 'opensesame' def subprojects = [ 'core', + 'natives', 'compile', 'groovy', 'javac', @@ -28,8 +29,8 @@ include subprojects subprojects.each { def p = project(":$it") - p.projectDir = file(it.replace('-', '/')) - p.name = "opensesame-$it" + p.projectDir = file(it.replace('-', '/').replace(':', '/')) + p.name = "opensesame-$it".split(':')[-1] } include 'testtargets' diff --git a/testplugin/build.gradle b/testplugin/build.gradle index 5524009..c3f090b 100644 --- a/testplugin/build.gradle +++ b/testplugin/build.gradle @@ -13,7 +13,7 @@ configurations { testSource { canBeResolved = true } - testModuleClasspath { + testModulePath { canBeResolved = true } } @@ -28,12 +28,7 @@ dependencies { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'java-source')) } } - testImplementation 'dev.lukebemish.opensesame:testtargets' - testModuleClasspath 'dev.lukebemish.opensesame:testtargets' - testImplementation 'dev.lukebemish.opensesame:opensesame-core' - testImplementation libs.asm.core - testImplementation libs.junit.api - testRuntimeOnly libs.junit.engine + testModulePath 'dev.lukebemish.opensesame:testtargets' } opensesame.apply(sourceSets.test) @@ -43,19 +38,35 @@ tasks.named('compileTestJava', JavaCompile).configure { source(configurations.testSource) } -test { - useJUnitPlatform() +testing { + suites { + test { + useJUnit() - testLogging { - showStandardStreams = true - exceptionFormat = 'full' - showCauses = true - showStackTraces = true - events = ['passed', 'failed', 'skipped'] - } + targets.configureEach { + testTask.configure { + useJUnitPlatform() + + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } + + testClassesDirs.from configurations.testModulePath + systemProperty 'modulartests', 'true' + } + } - testClassesDirs += files( - sourceSets.test.output, - configurations.testModuleClasspath - ) + dependencies { + runtimeOnly libs.junit.engine + implementation 'dev.lukebemish.opensesame:opensesame-core' + implementation 'dev.lukebemish.opensesame:testtargets' + implementation libs.asm.core + implementation libs.junit.api + } + } + } } diff --git a/testplugin/gradle/wrapper/gradle-wrapper.jar b/testplugin/gradle/wrapper/gradle-wrapper.jar index 7f93135..e644113 100644 Binary files a/testplugin/gradle/wrapper/gradle-wrapper.jar and b/testplugin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/testplugin/gradle/wrapper/gradle-wrapper.properties b/testplugin/gradle/wrapper/gradle-wrapper.properties index 1af9e09..a441313 100644 --- a/testplugin/gradle/wrapper/gradle-wrapper.properties +++ b/testplugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/testplugin/gradlew b/testplugin/gradlew index 1aa94a4..b740cf1 100755 --- a/testplugin/gradlew +++ b/testplugin/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/testplugin/gradlew.bat b/testplugin/gradlew.bat index 93e3f59..25da30d 100644 --- a/testplugin/gradlew.bat +++ b/testplugin/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/testplugin/loom/build.gradle b/testplugin/loom/build.gradle index 5e7ff25..eb86317 100644 --- a/testplugin/loom/build.gradle +++ b/testplugin/loom/build.gradle @@ -12,7 +12,7 @@ loom { runs.removeAll() } -opensesame.apply(sourceSets.main) +opensesame.apply(sourceSets.test) configurations { testSource { @@ -37,38 +37,48 @@ dependencies { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'java-source')) } } - implementation 'dev.lukebemish.opensesame:testtargets' - implementation 'dev.lukebemish.opensesame:opensesame-core' - implementation libs.asm.core - implementation libs.junit.api modImplementation libs.fabric.loader - - testRuntimeOnly libs.junit.engine } -tasks.named('compileJava', JavaCompile).configure { +tasks.named('compileTestJava', JavaCompile) { dependsOn(configurations.testSource) source(configurations.testSource) } +tasks.named('jar', Jar) { + from sourceSets.test.output +} + artifacts { testClasses tasks.remapJar } -test { - useJUnitPlatform() +testing { + suites { + test { + useJUnit() - testLogging { - showStandardStreams = true - exceptionFormat = 'full' - events = ['passed', 'failed', 'skipped'] - } + targets.configureEach { + testTask.configure { + useJUnitPlatform() - testClassesDirs += files( - sourceSets.main.output - ) + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } + } + } - classpath += files( - sourceSets.main.output - ) + dependencies { + runtimeOnly libs.junit.engine + implementation 'dev.lukebemish.opensesame:testtargets' + implementation 'dev.lukebemish.opensesame:opensesame-core' + implementation libs.asm.core + implementation libs.junit.api + } + } + } } \ No newline at end of file diff --git a/testplugin/loom/exec-intermediary/build.gradle b/testplugin/loom/exec-intermediary/build.gradle index a01296b..7d3c862 100644 --- a/testplugin/loom/exec-intermediary/build.gradle +++ b/testplugin/loom/exec-intermediary/build.gradle @@ -30,12 +30,6 @@ dependencies { testClasses project(path: ':loom', configuration: 'testClasses') - implementation 'dev.lukebemish.opensesame:testtargets' - implementation 'dev.lukebemish.opensesame:opensesame-core' - implementation libs.asm.core - implementation libs.junit.api - - testRuntimeOnly libs.junit.engine modImplementation libs.fabric.loader } @@ -53,22 +47,35 @@ tasks.register('unpackRemappedJar', Copy).configure { into layout.buildDirectory.dir('unpacked') } -test { - useJUnitPlatform() - - testLogging { - showStandardStreams = true - exceptionFormat = 'full' - showCauses = true - showStackTraces = true - events = ['passed', 'failed', 'skipped'] +testing { + suites.test { + targets.configureEach { + testTask.configure { + test { + useJUnitPlatform() + + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } + + testClassesDirs.from tasks.unpackRemappedJar.outputs + classpath.from tasks.unpackRemappedJar.outputs + } + } + } + + dependencies { + implementation 'dev.lukebemish.opensesame:testtargets' + implementation 'dev.lukebemish.opensesame:opensesame-core' + implementation libs.asm.core + implementation libs.junit.api + + runtimeOnly libs.junit.engine + runtimeOnly 'dev.lukebemish.opensesame:opensesame-fabric' + } } - - testClassesDirs += files( - tasks.unpackRemappedJar.outputs - ) - - classpath += files( - tasks.unpackRemappedJar.outputs - ) } diff --git a/testplugin/loom/exec-named/build.gradle b/testplugin/loom/exec-named/build.gradle index 13737a6..51af64f 100644 --- a/testplugin/loom/exec-named/build.gradle +++ b/testplugin/loom/exec-named/build.gradle @@ -34,14 +34,7 @@ dependencies { testClasses project(path: ':loom', configuration: 'testClasses') - implementation 'dev.lukebemish.opensesame:testtargets' - implementation 'dev.lukebemish.opensesame:opensesame-core' - implementation libs.asm.core - implementation libs.junit.api - - testRuntimeOnly libs.junit.engine modImplementation libs.fabric.loader - testRuntimeOnly 'dev.lukebemish.opensesame:opensesame-fabric' } tasks.register('remapTestClasses', RemapJarTask).configure { @@ -58,22 +51,35 @@ tasks.register('unpackRemappedJar', Copy).configure { into layout.buildDirectory.dir('unpacked') } -test { - useJUnitPlatform() - - testLogging { - showStandardStreams = true - exceptionFormat = 'full' - showCauses = true - showStackTraces = true - events = ['passed', 'failed', 'skipped'] +testing { + suites.test { + targets.configureEach { + testTask.configure { + test { + useJUnitPlatform() + + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } + + testClassesDirs.from tasks.unpackRemappedJar.outputs + classpath.from tasks.unpackRemappedJar.outputs + } + } + } + + dependencies { + implementation 'dev.lukebemish.opensesame:testtargets' + implementation 'dev.lukebemish.opensesame:opensesame-core' + implementation libs.asm.core + implementation libs.junit.api + + runtimeOnly libs.junit.engine + runtimeOnly 'dev.lukebemish.opensesame:opensesame-fabric' + } } - - testClassesDirs += files( - tasks.unpackRemappedJar.outputs - ) - - classpath += files( - tasks.unpackRemappedJar.outputs - ) } diff --git a/testplugin/loom/src/main/java/dev/lukebemish/opensesame/test/java/loom/FakeKnot.java b/testplugin/loom/src/test/java/dev/lukebemish/opensesame/test/java/loom/FakeKnot.java similarity index 100% rename from testplugin/loom/src/main/java/dev/lukebemish/opensesame/test/java/loom/FakeKnot.java rename to testplugin/loom/src/test/java/dev/lukebemish/opensesame/test/java/loom/FakeKnot.java diff --git a/testplugin/loom/src/main/java/dev/lukebemish/opensesame/test/java/loom/TestOpenLoom.java b/testplugin/loom/src/test/java/dev/lukebemish/opensesame/test/java/loom/TestOpenLoom.java similarity index 100% rename from testplugin/loom/src/main/java/dev/lukebemish/opensesame/test/java/loom/TestOpenLoom.java rename to testplugin/loom/src/test/java/dev/lukebemish/opensesame/test/java/loom/TestOpenLoom.java diff --git a/testplugin/loom/testmod/build.gradle b/testplugin/loom/testmod/build.gradle index 9895e21..ebb24b1 100644 --- a/testplugin/loom/testmod/build.gradle +++ b/testplugin/loom/testmod/build.gradle @@ -29,10 +29,12 @@ dependencies { modImplementation libs.fabric.loader implementation 'dev.lukebemish.opensesame:opensesame-fabric' + implementation 'dev.lukebemish.opensesame:testtargets' testImplementation libs.junit.api testImplementation libs.fabric.loader.junit testRuntimeOnly libs.junit.engine + testImplementation 'dev.lukebemish.opensesame:testtargets' } opensesame.apply(sourceSets.main) @@ -45,4 +47,12 @@ test { doFirst { runDir.mkdirs() } + + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + showCauses = true + showStackTraces = true + events = ['passed', 'failed', 'skipped'] + } } \ No newline at end of file diff --git a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestModOpenedClasses.java b/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestModOpenedClasses.java index c6f5525..8f4785b 100644 --- a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestModOpenedClasses.java +++ b/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestModOpenedClasses.java @@ -8,6 +8,10 @@ import dev.lukebemish.opensesame.annotations.extend.Overrides; import dev.lukebemish.opensesame.annotations.mixin.Expose; import dev.lukebemish.opensesame.annotations.mixin.UnFinal; +import dev.lukebemish.opensesame.test.target.Final; +import dev.lukebemish.opensesame.test.target.Public; +import dev.lukebemish.opensesame.test.target.RecordClass; +import dev.lukebemish.opensesame.test.target.SealedClass; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.biome.Climate; @@ -84,7 +88,7 @@ public static void setNamespace(ResourceLocation resourceLocation, String namesp } @Extend( - targetClass = TestRecord.class, + targetClass = RecordClass.class, unsafe = false ) @UnFinal @@ -99,7 +103,7 @@ static RecordExtension constructor(@Field(value = "field") @Field.Final String f } @Extend( - targetClass = TestSealed.class, + targetClass = SealedClass.class, unsafe = false ) @UnFinal @@ -109,4 +113,71 @@ static SealedClassExtension constructor() { throw new UnsupportedOperationException("Constructor not replaced"); } } + + @Extend( + targetClass = Final.class, + unsafe = false + ) + @UnFinal + public interface FinalExtension { + @Constructor + static FinalExtension constructor() { + throw new AssertionError("Constructor not replaced"); + } + } + + @Extend( + targetClass = Public.class, + unsafe = false + ) + public interface PublicExtension { + @Constructor + static PublicExtension constructor() { + throw new AssertionError("Constructor not replaced"); + } + + @Overrides(value = "finalMethod") + @UnFinal + default String finalMethodOverride() { + return "not so final now!"; + } + } + + @Open( + name = "privateFinalInstanceField", + targetClass = Public.class, + type = Open.Type.SET_INSTANCE + ) + @UnFinal + public static void privateFinalInstanceField(Public it, String value) { + throw new AssertionError("Method not replaced"); + } + + @Open( + name = "privateFinalInstanceField", + targetClass = Public.class, + type = Open.Type.GET_INSTANCE + ) + public static String privateFinalInstanceField(Public it) { + throw new AssertionError("Method not replaced"); + } + + @Open( + name = "privateFinalStaticField", + targetClass = Public.class, + type = Open.Type.SET_STATIC + ) + @UnFinal + public static void privateFinalStaticField(String value) { + throw new AssertionError("Method not replaced"); + } + + @Open( + name = "privateFinalStaticField", + targetClass = Public.class, + type = Open.Type.GET_STATIC + ) + public static String privateFinalStaticField() { + throw new AssertionError("Method not replaced"); + } } diff --git a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestRecord.java b/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestRecord.java deleted file mode 100644 index 4e2cfd1..0000000 --- a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestRecord.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.lukebemish.opensesame.testmod; - -public record TestRecord(int a, int b) { -} diff --git a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestSealed.java b/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestSealed.java deleted file mode 100644 index 0031976..0000000 --- a/testplugin/loom/testmod/src/main/java/dev/lukebemish/opensesame/testmod/TestSealed.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.lukebemish.opensesame.testmod; - -public sealed interface TestSealed { - record Allowed() implements TestSealed {} -} diff --git a/testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/testmod/tests/TestUnFinal.java b/testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/test/testmod/TestUnFinal.java similarity index 61% rename from testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/testmod/tests/TestUnFinal.java rename to testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/test/testmod/TestUnFinal.java index 4b14cbb..13c7c7a 100644 --- a/testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/testmod/tests/TestUnFinal.java +++ b/testplugin/loom/testmod/src/test/java/dev/lukebemish/opensesame/test/testmod/TestUnFinal.java @@ -1,8 +1,10 @@ -package dev.lukebemish.opensesame.testmod.tests; +package dev.lukebemish.opensesame.test.testmod; +import dev.lukebemish.opensesame.test.target.Final; +import dev.lukebemish.opensesame.test.target.Public; +import dev.lukebemish.opensesame.test.target.RecordClass; +import dev.lukebemish.opensesame.test.target.SealedClass; import dev.lukebemish.opensesame.testmod.TestModOpenedClasses; -import dev.lukebemish.opensesame.testmod.TestRecord; -import dev.lukebemish.opensesame.testmod.TestSealed; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.biome.Climate; import org.junit.jupiter.api.Assertions; @@ -48,8 +50,7 @@ void testUnFinalMutable() { @Test void testExtendRecord() { var instance = TestModOpenedClasses.RecordExtension.constructor("fieldValue", 1, 2); - Assertions.assertInstanceOf(TestRecord.class, instance); - Assertions.assertEquals("TestRecord[a=1, b=2]", instance.toString()); + Assertions.assertInstanceOf(RecordClass.class, instance); Assertions.assertEquals("fieldValue", instance.field()); } @@ -57,6 +58,32 @@ void testExtendRecord() { @Test void testExtendSealed() { var instance = TestModOpenedClasses.SealedClassExtension.constructor(); - Assertions.assertInstanceOf(TestSealed.class, instance); + Assertions.assertInstanceOf(SealedClass.class, instance); + } + + @Test + void testPrivateFinalInstanceField() { + Public p = new Public(); + TestModOpenedClasses.privateFinalInstanceField(p, "test"); + Assertions.assertEquals("test", TestModOpenedClasses.privateFinalInstanceField(p)); + } + + @Test + void testPrivateFinalStaticField() { + TestModOpenedClasses.privateFinalStaticField("test"); + Assertions.assertEquals("test", TestModOpenedClasses.privateFinalStaticField()); + } + + @Test + void testPublicExtension() { + TestModOpenedClasses.PublicExtension instance = TestModOpenedClasses.PublicExtension.constructor(); + Assertions.assertInstanceOf(Public.class, instance); + Assertions.assertEquals("not so final now!", ((Public) instance).finalMethod()); + } + + @Test + void testFinalExtension() { + TestModOpenedClasses.FinalExtension instance = TestModOpenedClasses.FinalExtension.constructor(); + Assertions.assertInstanceOf(Final.class, instance); } } diff --git a/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/RecordClass.java b/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/RecordClass.java new file mode 100644 index 0000000..1a2bed1 --- /dev/null +++ b/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/RecordClass.java @@ -0,0 +1,4 @@ +package dev.lukebemish.opensesame.test.target; + +public record RecordClass(int a, int b) { +} diff --git a/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/SealedClass.java b/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/SealedClass.java new file mode 100644 index 0000000..7165674 --- /dev/null +++ b/testtargets/src/main/java/dev/lukebemish/opensesame/test/target/SealedClass.java @@ -0,0 +1,5 @@ +package dev.lukebemish.opensesame.test.target; + +public sealed interface SealedClass { + record Allowed() implements SealedClass {} +} diff --git a/testtargets/src/main/java/module-info.java b/testtargets/src/main/java/module-info.java index d5ac042..d6cda22 100644 --- a/testtargets/src/main/java/module-info.java +++ b/testtargets/src/main/java/module-info.java @@ -1,3 +1,4 @@ module dev.lukebemish.opensesame.test.target { opens dev.lukebemish.opensesame.test.target; + exports dev.lukebemish.opensesame.test.target; } \ No newline at end of file diff --git a/testtargets/src/test/java/dev/lukebemish/opensesame/test/java/Open/TestModules.java b/testtargets/src/test/java/dev/lukebemish/opensesame/test/java/Open/TestModules.java index b30e32e..f9ba1cf 100644 --- a/testtargets/src/test/java/dev/lukebemish/opensesame/test/java/Open/TestModules.java +++ b/testtargets/src/test/java/dev/lukebemish/opensesame/test/java/Open/TestModules.java @@ -41,6 +41,11 @@ void testModuleBreaking() { } private static boolean isNotModular() { - return !Public.class.getModule().isNamed(); + var intendedModular = Boolean.getBoolean("modulartests"); + var actuallyModular = Public.class.getModule().isNamed(); + if (intendedModular && !actuallyModular) { + throw new RuntimeException("Module tests are enabled, but running in a non-modular environment."); + } + return !intendedModular; } } diff --git a/version.properties b/version.properties index 554f6b0..af0eb1c 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -version=0.5.13 +version=0.6.0