diff --git a/.github/.well-known/funding-manifest-urls b/.github/.well-known/funding-manifest-urls new file mode 100644 index 0000000000..856e91df9f --- /dev/null +++ b/.github/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://jmonkeyengine.org/funding.json \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..5e829187ab --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: jmonkeyengine diff --git a/.github/actions/tools/uploadToCentral.sh b/.github/actions/tools/uploadToCentral.sh new file mode 100755 index 0000000000..12a36ac5e2 --- /dev/null +++ b/.github/actions/tools/uploadToCentral.sh @@ -0,0 +1,68 @@ +#! /bin/bash +set -euo pipefail + +## Upload a deployment +## from the "org.jmonkeyengine" namespace in Sonatype's OSSRH staging area +## to Sonatype's Central Publisher Portal +## so the deployment can be tested and then published or dropped. + +## IMPORTANT: The upload request must originate +## from the IP address used to stage the deployment to the staging area! + +# The required -p and -u flags on the command line +# specify the password and username components of a "user token" +# generated using the web interface at https://central.sonatype.com/account + +while getopts p:u: flag +do + case "${flag}" in + p) centralPassword=${OPTARG};; + u) centralUsername=${OPTARG};; + esac +done + +# Combine both components into a base64 "user token" +# suitable for the Authorization header of a POST request: + +token=$(printf %s:%s "${centralUsername}" "${centralPassword}" | base64) + +# Send a POST request to upload the deployment: + +server='ossrh-staging-api.central.sonatype.com' +endpoint='/manual/upload/defaultRepository/org.jmonkeyengine' +url="https://${server}${endpoint}" + +statusCode=$(curl "${url}" \ + --no-progress-meter \ + --output postData1.txt \ + --write-out '%{response_code}' \ + --request POST \ + --header 'accept: */*' \ + --header "Authorization: Bearer ${token}" \ + --data '') + +echo "Status code = ${statusCode}" +echo 'Received data:' +cat postData1.txt +echo '[EOF]' + +# Retry if the default repo isn't found (status=400). + +if [ "${statusCode}" == "400" ]; then + echo "Will retry after 30 seconds." + sleep 30 + + statusCode2=$(curl "${url}" \ + --no-progress-meter \ + --output postData2.txt \ + --write-out '%{response_code}' \ + --request POST \ + --header 'accept: */*' \ + --header "Authorization: Bearer ${token}" \ + --data '') + + echo "Status code = ${statusCode2}" + echo 'Received data:' + cat postData2.txt + echo '[EOF]' +fi diff --git a/.github/actions/tools/uploadToMaven.sh b/.github/actions/tools/uploadToMaven.sh new file mode 100644 index 0000000000..51dc2da533 --- /dev/null +++ b/.github/actions/tools/uploadToMaven.sh @@ -0,0 +1,56 @@ +#!/bin/bash +############################################# +# +# Usage +# uploadAllToMaven path/of/dist/maven $GITHUB_PACKAGE_REPOSITORY user password +# +############################################# +root="`dirname ${BASH_SOURCE[0]}`" + +set -e +function uploadToMaven { + file="$1" + destfile="$2" + repourl="$3" + user="$4" + password="$5" + srcrepo="$6" + license="$7" + + auth="" + + if [ "$user" != "token" ]; + then + echo "Upload with username $user and password" + auth="-u$user:$password" + else + echo "Upload with token" + auth="-H \"Authorization: token $password\"" + fi + + cmd="curl -T \"$file\" $auth \ + \"$repourl/$destfile\" \ + -vvv" + + echo "Run $cmd" + eval "$cmd" +} +export -f uploadToMaven + +function uploadAllToMaven { + path="$1" + cdir="$PWD" + cd "$path" + files="`find . \( -name "*.jar" -o -name "*.pom" \) -type f -print`" + IFS=" +" + set -f + for art in $files; do + art="${art:2}" + uploadToMaven "$art" "$art" ${@:2} + done + set +f + unset IFS + + cd "$cdir" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f19ae4de01 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "gradle" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + cooldown: + default-days: 30 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 30 + open-pull-requests-limit: 5 + commit-message: + prefix: "ci" + labels: + - "dependencies" + - "github-actions" diff --git a/.github/workflows/bounty.yml b/.github/workflows/bounty.yml new file mode 100644 index 0000000000..3dd7288517 --- /dev/null +++ b/.github/workflows/bounty.yml @@ -0,0 +1,93 @@ +name: Bounty detector + +on: + issues: + types: [labeled] + +permissions: + issues: write + pull-requests: read + +jobs: + notify: + runs-on: ubuntu-latest + if: startsWith(github.event.label.name, 'diff:') + steps: + - name: Comment bounty info + uses: actions/github-script@v9.0.0 + env: + FORUM_URL: "https://hub.jmonkeyengine.org/t/bounty-program-trial-starts-today/49394/" + RESERVE_HOURS: "48" + TIMER_SVG_BASE: "https://jme-bounty-reservation-indicator.rblb.workers.dev/timer.svg" + with: + script: | + const issue = context.payload.issue; + const actor = context.actor; + const issueOwner = issue.user?.login; + if (!issueOwner) return; + + const forumUrl = process.env.FORUM_URL || "TBD"; + const reserveHours = Number(process.env.RESERVE_HOURS || "48"); + const svgBase = process.env.TIMER_SVG_BASE || ""; + + // "previous contributor" = has at least one merged PR authored in this repo + const repoFull = `${context.repo.owner}/${context.repo.repo}`; + const q = `repo:${repoFull} type:pr author:${issueOwner} is:merged`; + + let isPreviousContributor = false; + try { + const search = await github.rest.search.issuesAndPullRequests({ q, per_page: 1 }); + isPreviousContributor = (search.data.total_count ?? 0) > 0; + } catch (e) { + isPreviousContributor = false; + } + + // Reserve only if previous contributor AND labeler is NOT the issue owner + const shouldReserve = isPreviousContributor && (actor !== issueOwner); + + const lines = []; + lines.push(`## 💰 This issue has a bounty`); + lines.push(`Resolve it to receive a reward.`); + lines.push(`For details (amount, rules, eligibility), see: ${forumUrl}`); + lines.push(""); + + lines.push(`If you want to start working on this, **comment on this issue** with your intent.`); + lines.push(`If accepted by a maintainer, the issue will be **assigned** to you.`); + lines.push(""); + + if (shouldReserve && svgBase) { + const reservedUntil = new Date(Date.now() + reserveHours * 60 * 60 * 1000); + const reservedUntilIso = reservedUntil.toISOString(); + + const svgUrl = + `${svgBase}` + + `?until=${encodeURIComponent(reservedUntilIso)}` + + `&user=${encodeURIComponent(issueOwner)}` + + `&theme=dark`; + + lines.push(`![bounty reservation](${svgUrl})`); + lines.push(""); + } + + // Avoid duplicate comments for the same label + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + per_page: 100, + }); + + const already = comments.data.some(c => + c.user?.login === "github-actions[bot]" && + typeof c.body === "string" && + c.body.includes("This issue has a bounty") + ); + + if (already) return; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: lines.join("\n"), + }); diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000000..7e860baf3f --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,21 @@ +name: auto-format +on: + push: + +jobs: + format: + runs-on: ubuntu-latest + if: ${{ false }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Prettify code + uses: creyD/prettier_action@8c18391fdc98ed0d884c6345f03975edac71b8f0 # v4.6 + with: + prettier_options: --tab-width 4 --print-width 110 --write **/**/*.java + prettier_version: "2.8.8" + only_changed: True + commit_message: "auto-format" + prettier_plugins: "prettier-plugin-java" diff --git a/.github/workflows/j3o-scan.yml b/.github/workflows/j3o-scan.yml new file mode 100644 index 0000000000..bcb1b57a71 --- /dev/null +++ b/.github/workflows/j3o-scan.yml @@ -0,0 +1,40 @@ +name: J3O Scan + +on: + pull_request: + paths: + - '**/*.j3o' + - 'jme3-desktop/src/main/resources/com/jme3/system/j3o-baseline.txt' + - 'jme3-desktop/src/main/java/com/jme3/system/J3OScanner.java' + - 'jme3-desktop/build.gradle' + - '.github/workflows/j3o-scan.yml' + push: + branches: + - master + - main + paths: + - '**/*.j3o' + - 'jme3-desktop/src/main/resources/com/jme3/system/j3o-baseline.txt' + - 'jme3-desktop/src/main/java/com/jme3/system/J3OScanner.java' + - 'jme3-desktop/build.gradle' + - '.github/workflows/j3o-scan.yml' + +permissions: + contents: read + +jobs: + scan-j3o: + name: Validate J3O assets + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '25' + + - name: Scan J3O assets + run: ./gradlew :jme3-desktop:scanJ3O --console=plain diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..03cc239810 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,478 @@ +###################################################################################### +# JME CI/CD +###################################################################################### +# Quick overview of what is going on in this script: +# - Build the engine, create the zip release, maven artifacts and javadoc +# - (only when building a release) Deploy everything else to github releases and Sonatype +# - (only when building a release) Update javadoc.jmonkeyengine.org +# Note: +# All the actions/upload-artifact and actions/download-artifact steps are used to pass +# stuff between jobs, github actions has some sort of storage that is local to the +# running workflow, we use it to store the result of each job since the filesystem +# is not maintained between jobs. +################# CONFIGURATIONS ##################################################### +# >> Configure SONATYPE RELEASE +# CENTRAL_PASSWORD=XXXXXX +# CENTRAL_USERNAME=XXXXXX +# >> Configure SIGNING +# SIGNING_KEY=XXXXXX +# SIGNING_PASSWORD=XXXXXX +# >> Configure PACKAGE REGISTRY RELEASE +# Nothing to do here, everything is autoconfigured to work with the account/org that +# is running the build. +# >> Configure JAVADOC +# JAVADOC_GHPAGES_REPO="riccardoblsandbox/javadoc.jmonkeyengine.org.git" +# Generate a deploy key +# ssh-keygen -t rsa -b 4096 -C "actions@users.noreply.github.com" -f javadoc_deploy +# Set +# JAVADOC_GHPAGES_DEPLOY_PRIVKEY="......." +# In github repo -> Settings, use javadoc_deploy.pub as Deploy key with write access +###################################################################################### +# Resources: +# - Github actions docs: https://help.github.com/en/articles/about-github-actions +# - Package registry docs: https://help.github.com/en/articles/about-github-package-registry +# - Official actions: https://github.com/actions +# - Community actions: https://github.com/sdras/awesome-actions +###################################################################################### +# - Riccardo Balbo +###################################################################################### + +name: Build jMonkeyEngine +on: + push: + branches: + - master + - v3.7 + - v3.6 + - v3.5 + - v3.4 + - v3.3 + - ios-2024_2 + pull_request: + release: + types: [published] + +jobs: + Checkstyle: + name: Run Checkstyle + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Run Checkstyle + run: | + ./gradlew checkstyleMain checkstyleTest --console=plain --stacktrace + - name: Upload Checkstyle Reports + uses: actions/upload-artifact@v7.0.1 + if: always() + with: + name: checkstyle-report + retention-days: 30 + path: | + **/build/reports/checkstyle/** + + SpotBugs: + name: Run SpotBugs + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Run SpotBugs + run: | + ./gradlew -PenableSpotBugs=true spotbugsMain spotbugsTest --console=plain --stacktrace + - name: Upload SpotBugs Reports + uses: actions/upload-artifact@v7.0.1 + if: always() + with: + name: spotbugs-report + retention-days: 30 + path: | + **/build/reports/spotbugs/** + + JavadocDoclint: + name: Run Javadoc Doclint + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Run Javadoc doclint + run: | + ./gradlew -PenableJavadocError=true javadoc mergedJavadoc --console=plain --stacktrace + - name: Report Javadoc doclint failure + if: failure() + run: | + echo "::notice title=Javadoc doclint failed::Javadoc warnings were found. Run ./gradlew -PenableJavadocError=true javadoc mergedJavadoc locally to reproduce." + + ScreenshotTests: + name: Run Screenshot Tests + runs-on: ubuntu-latest + container: + image: ghcr.io/onemillionworlds/opengl-docker-image:v1 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Start xvfb + run: | + Xvfb :99 -ac -screen 0 1024x768x16 & + export DISPLAY=:99 + echo "DISPLAY=:99" >> $GITHUB_ENV + - name: Report GL/Vulkan + run: | + set -x + echo "DISPLAY=$DISPLAY" + glxinfo | grep -E "OpenGL version|OpenGL renderer|OpenGL vendor" || true + vulkaninfo --summary || true + echo "VK_ICD_FILENAMES=$VK_ICD_FILENAMES" + echo "MESA_LOADER_DRIVER_OVERRIDE=$MESA_LOADER_DRIVER_OVERRIDE" + echo "GALLIUM_DRIVER=$GALLIUM_DRIVER" + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Test with Gradle Wrapper + run: | + ./gradlew :jme3-screenshot-test:screenshotTest + - name: Upload Test Reports + uses: actions/upload-artifact@v7.0.1 + if: always() + with: + name: screenshot-test-report + retention-days: 30 + path: | + **/build/reports/** + **/build/changed-images/** + **/build/test-results/** + + # Build the engine, we only deploy from ubuntu-latest jdk25 + BuildJMonkey: + name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + jdk: [25] + include: + - os: ubuntu-latest + osName: linux + deploy: true + - os: windows-latest + osName: windows + deploy: false + - os: macOS-latest + osName: mac + deploy: false + + steps: + - name: Clone the repo + uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: ${{ matrix.jdk }} + + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Build Engine + shell: bash + run: | + # Normal build plus ZIP distribution and merged javadoc + ./gradlew -PuseCommitHashAsVersionName=true \ + -x checkstyleMain -x checkstyleTest \ + build createZipDistribution mergedJavadoc + + if [ "${{ matrix.deploy }}" = "true" ]; + then + # We are going to need "zip" + sudo apt-get update + sudo apt-get install -y zip + + # We prepare the release for deploy + mkdir -p ./dist/release/ + mv build/distributions/*.zip dist/release/ + + # Install maven artifacts to ./dist/maven and sign them if possible + if [ "${{ secrets.SIGNING_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable signing:" + echo "SIGNING_KEY, SIGNING_PASSWORD" + + ./gradlew publishMavenPublicationToDistRepository \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + else + ./gradlew publishMavenPublicationToDistRepository \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + fi + + fi + + # Upload maven artifacts to be used later by the deploy job + - name: Upload maven artifacts + if: matrix.deploy==true + uses: actions/upload-artifact@v7.0.1 + with: + name: maven + path: dist/maven + + - name: Upload javadoc + if: matrix.deploy==true + uses: actions/upload-artifact@v7.0.1 + with: + name: javadoc + path: dist/javadoc + + # Upload release archive to be used later by the deploy job + - name: Upload release + if: github.event_name == 'release' && matrix.deploy==true + uses: actions/upload-artifact@v7.0.1 + with: + name: release + path: dist/release + + # This job deploys snapshots on the master branch + DeployJavaSnapshot: + needs: [BuildJMonkey] + name: Deploy Java Snapshot + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref_name == 'master' + permissions: + contents: read + steps: + + # We need to clone everything again for uploadToMaven.sh ... + - name: Clone the repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 1 + + # Setup jdk 25 used for building Maven-style artifacts + - name: Setup the java environment + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: 'temurin' + java-version: '25' + + - name: Rebuild the maven artifacts and upload them to Sonatype's maven-snapshots repo + run: | + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + else + ./gradlew publishMavenPublicationToSNAPSHOTRepository \ + -PcentralPassword=${{ secrets.CENTRAL_PASSWORD }} \ + -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + --console=plain --stacktrace + fi + + + # This job deploys the release + DeployRelease: + needs: [BuildJMonkey] + name: Deploy Release + runs-on: ubuntu-latest + if: github.event_name == 'release' + steps: + + # We need to clone everything again for uploadToCentral.sh ... + - name: Clone the repo + uses: actions/checkout@v6 + with: + fetch-depth: 1 + + # Setup jdk 25 used for building Sonatype artifacts + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + + # Download all the stuff... + - name: Download maven artifacts + uses: actions/download-artifact@v8.0.1 + with: + name: maven + path: dist/maven + + - name: Download release + uses: actions/download-artifact@v8.0.1 + with: + name: release + path: dist/release + + - name: Rebuild the maven artifacts and upload them to Sonatype's Central Publisher Portal + run: | + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + else + ./gradlew publishMavenPublicationToCentralRepository \ + -PcentralPassword=${{ secrets.CENTRAL_PASSWORD }} \ + -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + .github/actions/tools/uploadToCentral.sh \ + -p '${{ secrets.CENTRAL_PASSWORD }}' \ + -u '${{ secrets.CENTRAL_USERNAME }}' + fi + + - name: Deploy to GitHub Releases + run: | + # We need to get the release id (yeah, it's not the same as the tag) + echo "${GITHUB_EVENT_PATH}" + cat ${GITHUB_EVENT_PATH} + releaseId=$(jq --raw-output '.release.id' ${GITHUB_EVENT_PATH}) + + # Now that we have the id, we just upload the release zip from before + echo "Upload to release $releaseId" + filename="$(ls dist/release/*.zip)" + url="https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/$releaseId/assets?name=$(basename $filename)" + echo "Upload to $url" + curl -L \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/zip" \ + --data-binary @"$filename" \ + "$url" + + - name: Deploy to github package registry + run: | + source .github/actions/tools/uploadToMaven.sh + registry="https://maven.pkg.github.com/$GITHUB_REPOSITORY" + echo "Deploy to github package registry $registry" + uploadAllToMaven dist/maven/ $registry "token" ${{ secrets.GITHUB_TOKEN }} + + # Deploy the javadoc + DeployJavaDoc: + needs: [BuildJMonkey] + name: Deploy Javadoc + runs-on: ubuntu-latest + if: github.event_name == 'release' + steps: + + # We are going to need a deploy key for this, since we need + # to push to a different repo + - name: Set ssh key + run: | + mkdir -p ~/.ssh/ + echo "${{ secrets.JAVADOC_GHPAGES_DEPLOY_PRIVKEY }}" > $HOME/.ssh/deploy.key + chmod 600 $HOME/.ssh/deploy.key + + # We clone the javadoc repo + - name: Clone gh-pages + run: | + branch="gh-pages" + export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $HOME/.ssh/deploy.key" + git clone --single-branch --branch "$branch" git@github.com:${{ secrets.JAVADOC_GHPAGES_REPO }} . + + # Download the javadoc in the new directory "newdoc" + - name: Download javadoc + uses: actions/download-artifact@v8.0.1 + with: + name: javadoc + path: newdoc + + # The actual deploy + - name: Deploy to github pages + run: | + set -f + IFS=$'\n' + + # Get the tag for this release + version="`if [[ $GITHUB_REF == refs\/tags* ]]; then echo ${GITHUB_REF//refs\/tags\//}; fi`" + + # If there is no tag, then we do nothing. + if [ "$version" != "" ]; + then + echo "Deploy as $version" + + # Remove any older version of the javadoc for this tag + if [ -d "$version" ];then rm -Rf "$version"; fi + + # Rename newdoc with the version name + mv newdoc "$version" + + # if there isn't an index.txt we create one (we need this to list the versions) + if [ ! -f "index.txt" ]; then echo "" > index.txt ; fi + index="`cat index.txt`" + + # Check if this version is already in index.txt + addNew=true + for v in $index; + do + if [ "$v" = "$version" ]; + then + echo "$v" "$version" + addNew=false + break + fi + done + + # If not, we add it to the beginning + if [ "$addNew" = "true" ]; + then + echo -e "$version\n$index" > index.txt + index="`cat index.txt`" + fi + + # Regenerate the pages + chmod +x make.sh + ./make.sh + + # Configure git to use the deploy key + export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $HOME/.ssh/deploy.key" + + # Commit the changes + git config --global user.name "Github Actions" + git config --global user.email "actions@users.noreply.github.com" + + git add . || true + git commit -m "$version" || true + + branch="gh-pages" + git push origin "$branch" --force || true + + fi diff --git a/.github/workflows/screenshot-test-comment.yml b/.github/workflows/screenshot-test-comment.yml new file mode 100644 index 0000000000..39d259633f --- /dev/null +++ b/.github/workflows/screenshot-test-comment.yml @@ -0,0 +1,138 @@ +name: Screenshot Test PR Comment + +# This workflow is designed to safely comment on PRs from forks +# It uses pull_request_target which has higher permissions than pull_request +# Security note: This workflow does NOT check out or execute code from the PR +# It only monitors the status of the ScreenshotTests job and posts comments +# (If this commenting was done in the main worflow it would not have the permissions +# to create a comment) + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + monitor-screenshot-tests: + name: Monitor Screenshot Tests and Comment + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + pull-requests: write + contents: read + steps: + - name: Wait for GitHub to register the workflow run + run: sleep 120 + + - name: Wait for Screenshot Tests to complete + uses: lewagon/wait-on-check-action@9312864dfbc9fd208e9c0417843430751c042800 # v1.7.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + check-name: 'Run Screenshot Tests' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success,skipped,failure + - name: Check Screenshot Tests status + id: check-status + uses: actions/github-script@v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const ref = '${{ github.event.pull_request.head.sha }}'; + + // Get workflow runs for the PR + const runs = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + head_sha: ref + }); + + // Find the ScreenshotTests job + let screenshotTestRun = null; + let workflowRunId = null; + for (const run of runs.data.workflow_runs) { + if (run.name === 'Build jMonkeyEngine') { + workflowRunId = run.id; + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: run.id + }); + + for (const job of jobs.data.jobs) { + if (job.name === 'Run Screenshot Tests') { + screenshotTestRun = job; + break; + } + } + + if (screenshotTestRun) break; + } + } + + if (!screenshotTestRun) { + console.log('Screenshot test job not found'); + return; + } + + core.setOutput('run_url', `https://github.com/${owner}/${repo}/actions/runs/${workflowRunId}`); + + // Check if the job failed + if (screenshotTestRun.conclusion === 'failure') { + // Now check if the changed images artifact exists + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: workflowRunId + }); + + const artifact = artifacts.data.artifacts.find(a => a.name === 'screenshot-test-report'); + if (artifact) { + core.setOutput('failed', 'true'); + core.setOutput('artifact_url', `https://github.com/${owner}/${repo}/actions/runs/${workflowRunId}/artifacts/${artifact.id}`); + } else { + console.log('Job failed but no changed images were generated.'); + core.setOutput('failed', 'false'); + } + } else { + core.setOutput('failed', 'false'); + } + - name: Find Existing Comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 + id: existingCommentId + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Screenshot tests have failed. + + - name: Comment on PR if tests fail + if: steps.check-status.outputs.failed == 'true' + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + 🖼️ **Screenshot tests have failed.** + + The purpose of these tests is to ensure that changes introduced in this PR don't break visual features. They are visual unit tests. + + 📄 **Where to find the report:** + - **[Direct Download: screenshot-test-report](${{ steps.check-status.outputs.artifact_url }})** + - Alternatively, go to the [failed run summary](${{ steps.check-status.outputs.run_url }}) > Artifacts > screenshot-test-report + - Download the zip and open jme3-screenshot-tests/build/reports/ScreenshotDiffReport.html + + ⚠️ **If you didn't expect to change anything visual:** + Fix your changes so the screenshot tests pass. + + ✅ **If you did mean to change things:** + Review the replacement images in jme3-screenshot-tests/build/changed-images to make sure they really are improvements and then replace and commit the replacement images at jme3-screenshot-tests/src/test/resources. + + ✨ **If you are creating entirely new tests:** + Find the new images in jme3-screenshot-tests/build/changed-images and commit the new images at jme3-screenshot-tests/src/test/resources. + + **Note;** it is very important that the committed reference images are created on the build pipeline, locally created images are not reliable. Similarly tests will fail locally but you can look at the report to check they are "visually similar". + + See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information + + Contact @richardTingle (aka richtea) for guidance if required + edit-mode: replace + comment-id: ${{ steps.existingCommentId.outputs.comment-id }} diff --git a/.gitignore b/.gitignore index aa848af793..663ad075e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,10 @@ **/.classpath **/.settings **/.project +**/.vscode/** +**/out/ /.gradle/ +**/.gradle/ /.nb-gradle/ /.idea/ /dist/ @@ -12,34 +15,35 @@ /.classpath /.project /.settings +/local.properties *.dll *.so *.jnilib *.dylib *.iml +*.class +*.jtxt .gradletasknamecache .DS_Store /jme3-core/src/main/resources/com/jme3/system/version.properties /jme3-*/build/ -/jme3-bullet-native/bullet.zip +/jme3-*/bin/ /jme3-bullet-native/bullet3.zip -/jme3-bullet-native/bullet-2.82-r2704/ -/jme3-bullet-native/bullet3-2.83.7/ +/jme3-bullet-native/bullet3-*/ +/jme3-bullet-native/src/native/cpp/com_jme3_bullet_*.h /jme3-android-native/openal-soft/ /jme3-android-native/OpenALSoft.zip /jme3-android-native/src/native/jme_decode/STBI/ /jme3-android-native/src/native/jme_decode/Tremor/ -/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.h -/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.h /jme3-android-native/stb_image.h -!/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll -!/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll -!/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib -!/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib -!/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so -!/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so -!/jme3-vr/src/main/resources/**/*.dylib -!/jme3-vr/src/main/resources/**/*.so -!/jme3-vr/src/main/resources/**/*.so.dbg -!/jme3-vr/src/main/resources/**/*.dll -!/jme3-vr/src/main/resources/**/*.pdb +/jme3-examples/private/ +/buildMaven.bat +/private +.travis.yml +appveyor.yml +javadoc_deploy +javadoc_deploy.pub +!.vscode/settings.json +!.vscode/JME_style.xml +!.vscode/extensions.json +joysticks-*.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2dbd6c5a93..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,81 +0,0 @@ -language: java -sudo: false - -branches: - only: - - master - - v3.1 - -matrix: - include: - - os: linux - jdk: oraclejdk8 - env: UPLOAD=true UPLOAD_NATIVE=true - - os: linux - jdk: openjdk7 - - os: osx - env: UPLOAD_NATIVE=true - -addons: - ssh_known_hosts: github.com - apt: - packages: - - gcc-multilib - - g++-multilib - -before_install: - - '[ -n "$UPLOAD" ] && git fetch --unshallow || :' - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -install: - - '[ -n "$UPLOAD_NATIVE" ] && ./gradlew -PbuildNativeProjects=true assemble || ./gradlew assemble' - -script: - - ./gradlew check - -after_success: - - '[ "$TRAVIS_PULL_REQUEST" == "false" ] && [ -n "$UPLOAD_NATIVE" ] && ./private/upload_native.sh || :' - - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ -n "$UPLOAD" ] && ./gradlew bintrayUpload || :' - -notifications: - slack: - on_success: change - on_failure: always - rooms: - secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s=" - -before_deploy: - - ./gradlew createZipDistribution - - export RELEASE_DIST=$(ls build/distributions/*.zip) - -deploy: - provider: releases - api_key: - secure: PuEsJd6juXBH29ByITW3ntSAyrwWs0IeFvXJ5Y2YlhojhSMtTwkoWeB6YmDJWP4fhzbajk4TQ1HlOX2IxJXSW/8ShOEIUlGXz9fHiST0dkSM+iRAUgC5enCLW5ITPTiem7eY9ZhS9miIam7ngce9jHNMh75PTzZrEJtezoALT9w= - file_glob: true - file: "${RELEASE_DIST}" - skip_cleanup: true - on: - repo: jMonkeyEngine/jmonkeyengine - tags: true - - -# before_install: - # required libs for android build tools - # sudo apt-get update - # sudo apt-get install -qq p7zip-full - # sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch - # newest Android NDK - # wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin -O ndk.bin - # 7z x ndk.bin -y > /dev/null - # export ANDROID_NDK=`pwd`/android-ndk-r10c - - diff --git a/.vscode/JME_style.xml b/.vscode/JME_style.xml new file mode 100644 index 0000000000..b4014b077e --- /dev/null +++ b/.vscode/JME_style.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..a1538208b3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "vscjava.vscode-java-pack", + "slevesque.shader" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..e559d94247 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "java.compile.nullAnalysis.mode": "automatic", + "java.format.settings.url": "./.vscode/JME_style.xml", + "editor.formatOnPaste": false, + "editor.formatOnType": false, + "editor.formatOnSave": false, + "java.jdt.ls.vmargs": "-Xms512M -Xmx4G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:AdaptiveSizePolicyWeight=90" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82ffeb4e8b..c1cf2bbb68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,23 +9,146 @@ Communication always comes first. **All** code changes and other contributions s ### New Contributors +Check out the [Projects](https://github.com/jMonkeyEngine/jmonkeyengine/projects/1) tab, where the team has prioritized issues that you as a new contributor can undertake that will familiarize you to the workflow of contributing. This highlights some issues the team thinks would be a good start for new contributors but you are free to contribute on any other issues or integration you wish. + When you're ready to submit your code, just make a [pull request](https://help.github.com/articles/using-pull-requests). -- Do not commit your code until you have received proper feedback. - In your commit log message, please refer back to the originating forum thread (example) for a ‘full circle’ reference. Also please [reference related issues](https://help.github.com/articles/closing-issues-via-commit-messages) by typing the issue hashtag. - When committing, always be sure to run an update before you commit. If there is a conflict between the latest revision and your patch after the update, then it is your responsibility to track down the update that caused the conflict and determine the issue (and fix it). In the case where the breaking commit has no thread linked (and one cannot be found in the forum), then the contributor should contact an administrator and wait for feedback before committing. - If your code is committed and it introduces new functionality, please edit the wiki accordingly. We can easily roll back to previous revisions, so just do your best; point us to it and we’ll see if it sticks! p.s. We will try hold ourselves to a [certain standard](http://www.defmacro.org/2013/04/03/issue-etiquette.html) when it comes to GitHub etiquette. If at any point we fail to uphold this standard, let us know. -#### Core Contributors +There are many ways +to submit a pull request (PR) to the "jmonkeyengine" project repository, +depending on your knowledge of Git and which tools you prefer. + +
+ + Click to view step-by-step instructions for a reusable setup + using a web browser and a command-line tool such as Bash. + + +The setup described here allows you to reuse the same local repo for many PRs. + +#### Prerequisites + +These steps need only be done once... + +1. You'll need a personal account on https://github.com/ . + The "Sign up" and "Sign in" buttons are in the upper-right corner. +2. Create a GitHub access token, if you don't already have one: + + Browse to https://github.com/settings/tokens + + Click on the "Generate new token" button in the upper right. + + Follow the instructions. + + When specifying the scope of the token, check the box labeled "repo". + + Copy the generated token to a secure location from which you can + easily paste it into your command-line tool. +3. Create your personal fork of the "jmonkeyengine" repository at GitHub, + if you don't already have one: + + Browse to https://github.com/jMonkeyEngine/jmonkeyengine + + Click on the "Fork" button (upper right) + + Follow the instructions. + + If offered a choice of locations, choose your personal account. +4. Clone the fork to your development system: + + `git clone https://github.com/` ***yourGitHubUserName*** `/jmonkeyengine.git` + + As of 2021, this step consumes about 1.3 GBytes of filesystem storage. +5. Create a local branch for tracking the project repository: + + `cd jmonkeyengine` + + `git remote add project https://github.com/jMonkeyEngine/jmonkeyengine.git` + + `git fetch project` + + `git checkout -b project-master project/master` + +#### PR process + +1. Create a temporary, up-to-date, local branch for your PR changes: + + `git checkout project-master` + + `git pull` + + `git checkout -b tmpBranch` (replace "tmpBranch" with a descriptive name) +2. Make your changes in the working tree. +3. Test your changes. + Testing should, at a minimum, include building the Engine from scratch: + + `./gradlew clean build` +4. Add and commit your changes to your temporary local branch. +5. Push the PR commits to your fork at GitHub: + + `git push --set-upstream origin ` ***tmpBranchName*** + + Type your GitHub user name at the "Username" prompt. + + Paste your access token (from prerequisite step 2) at the "Password" prompt. +6. Initiate the pull request: + + Browse to [https://github.com/ ***yourGitHubUserName*** /jmonkeyengine]() + + Click on the "Compare & pull request" button at the top. + + The "base repository:" should be "jMonkeyEngine/jmonkeyengine". + + The "base:" should be "master". + + The "head repository:" should be your personal fork at GitHub. + + The "compare:" should be the name of your temporary branch. +7. Fill in the textboxes for the PR name and PR description, and + click on the "Create pull request" button. + +To amend an existing PR: + + `git checkout tmpBranch` + + Repeat steps 2 through 5. + +To submit another PR using the existing local repository, +repeat the PR process using a new temporary branch with a different name. + +If you have an integrated development environment (IDE), +it may provide an interface to Git that's more intuitive than a command line. +
+ +Generic instructions for creating GitHub pull requests can be found at +https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request + +### Core Contributors Developers in the Contributors team can push directly to Main instead of submitting pull requests, however for new features it is often a good idea to do a pull request as a means to get a last code review. -## Building the engine +## Customs around integration, branching, tagging, and releases + +- Most pull requests are integrated directly into the master branch of the repository. +- Integrators should note, unless the history of the pull request is important, it should be integrated to a single commit using “squash and merge”. If the history is important, favor “rebase and merge”. Don’t create a merge commit unless GitHub cannot rebase the PR. +- For each major release (such as v3.0 or v3.3), an appropriately named release branch is created in the repository. +- For each minor (or “dot-dot”) release (such as v3.2.3), an appropriately named tag is created in the repository. +- In general, library changes that plausibly might break existing apps appear only in major releases, not minor ones. + + +## How to build the Engine from source + +### Prerequisites + +These steps need only be done once... + +1. Install a Java Development Kit (JDK), if you don't already have one. +2. Set the JAVA_HOME environment variable: + + using Bash: `export JAVA_HOME="` *path to your JDK* `"` + + using PowerShell: `$env:JAVA_HOME = '` *path to your JDK* `'` + + using Windows Command Prompt: `set JAVA_HOME="` *path to your JDK* `"` + + Tip: The path names a directory containing "bin" and "lib" subdirectories. + On Linux it might be something like "/usr/lib/jvm/java-17-openjdk-amd64" + + Tip: You may be able to skip this step + if the JDK binaries are in your system path. +3. Clone the project repository from GitHub: + + `git clone https://github.com/jmonkeyengine/jmonkeyengine.git` + + `cd jmonkeyengine` + + As of 2021, this step consumes about 1.3 GBytes of filesystem storage. -1. Install [Gradle](http://www.gradle.org/) -2. Navigate to the project directory and run 'gradle build' from command line to build the engine. +### Build command + +Run the Gradle wrapper: ++ using Bash or PowerShell: `./gradlew build` ++ using Windows Command Prompt: `.\gradlew build` + +After a successful build, +snapshot jars will be found in the "*/build/libs" subfolders. + +### Related Gradle tasks + +You can install the Maven artifacts to your local repository: + + using Bash or PowerShell: `./gradlew install` + + using Windows Command Prompt: `.\gradlew install` + +You can restore the project to a pristine state: + + using Bash or PowerShell: `./gradlew clean` + + using Windows Command Prompt: `.\gradlew clean` ## Best Practices @@ -38,9 +161,29 @@ Developers in the Contributors team can push directly to Main instead of submitt general testing tips? WIP +### Coding Style + ++ Our preferred style for Java source code is + [Google style](https://google.github.io/styleguide/javaguide.html) with the following 8 modifications: + 1. No blank line before a `package` statement. (Section 3) + 2. Logical ordering of class contents is encouraged but not required. (Section 3.4.2) + 3. Block indentation of +4 spaces instead of +2. (Section 4.2) + 4. Column limit of 110 instead of 100. (Section 4.4) + 5. Continuation line indentation of +8 spaces instead of +4. (Section 4.5.2) + 6. Commented-out code need not be indented at the same level as surrounding code. (Section 4.8.6.1) + 7. The names of test classes need not end in "Test". (Section 5.2.2) + 8. No trailing whitespace. ++ Any pull request that adds new Java source files shall apply our preferred style to those files. ++ Any pull request that has style improvement as its primary purpose + shall apply our preferred style, or specific aspect(s) thereof, to every file it modifies. ++ Any pull request that modifies a pre-existing source file AND + doesn't have style improvement as it's primary purpose shall either: + 1. conform to the prevailing style of that file OR + 2. apply our preferred style, but only to the portions modified for the PR's primary purpose. + ### Code Quality -We generally abide by the standard Java Code Conventions. Besides that, just make an effort to write elegant code: +Make an effort to write elegant code: 1. Handles errors gracefully 2. Only reinvents the wheel when there is a measurable benefit in doing so. @@ -55,5 +198,5 @@ We generally abide by the standard Java Code Conventions. Besides that, just mak ## Documentation -- How to edit the wiki - WIP +- How to edit the [wiki](https://github.com/jMonkeyEngine/wiki). - How to edit JavaDocs - WIP diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..bb12c971e2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +Copyright (c) 2009-2025 jMonkeyEngine. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 68d2d6204a..3ad9fcc75b 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,58 @@ -jMonkeyEngine +jMonkeyEngine ============= -[![Build Status](https://travis-ci.org/jMonkeyEngine/jmonkeyengine.svg?branch=master)](https://travis-ci.org/jMonkeyEngine/jmonkeyengine) +[![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions) -jMonkeyEngine is a 3D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. 3.1.0 is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll release 3.1.x updates until the major 3.2 release arrives. +jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. +v3.8.0 is the latest stable version of the engine. The engine is used by several commercial game studios and computer-science courses. Here's a taste: ![jME3 Games Mashup](https://i.imgur.com/nF8WOW6.jpg) - - [jME powered games on IndieDB](http://www.indiedb.com/engines/jmonkeyengine/games) - - [Maker's Tale](http://steamcommunity.com/sharedfiles/filedetails/?id=93461954t) - - [Boardtastic 2](https://play.google.com/store/apps/details?id=com.boardtastic.skateboarding) - - [Copod](http://herebeben.com/copod) - - [Attack of the Gelatinous Blob](http://attackofthegelatinousblob.com/) - - [Chaos](http://4realms.net/) + - [jME powered games on IndieDB](https://www.indiedb.com/engines/jmonkeyengine/games) + - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk) + - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/) - [Mythruna](https://mythruna.com/) - - [PirateHell](http://www.desura.com/games/piratehell) - - [3089 (on steam)](http://store.steampowered.com/app/263360/) - - [3079 (on steam)](http://store.steampowered.com/app/259620/) - - [Lightspeed Frontier](http://www.lightspeedfrontier.com/) + - [PirateHell (on Steam)](https://store.steampowered.com/app/321080/Pirate_Hell/) + - [3089 (on Steam)](https://store.steampowered.com/app/263360/3089__Futuristic_Action_RPG/) + - [3079 (on Steam)](https://store.steampowered.com/app/259620/3079__Block_Action_RPG/) + - [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/) - [Skullstone](http://www.skullstonegame.com/) - -## Getting started + - [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/) + - [Nine Circles of Hell (on Steam)](https://store.steampowered.com/app/1200600/Nine_Circles_of_Hell/) + - [Leap](https://gamejolt.com/games/leap/313308) + - [Jumping Jack Flag](http://timealias.bplaced.net/jack/) + - [PapaSpace Flight Simulation](https://www.papaspace.at/) + - [Cubic Nightmare (on Itch)](https://jaredbgreat.itch.io/cubic-nightmare) + - [Chatter Games](https://chatter-games.com) + - [Exotic Matter](https://exoticmatter.io) + - [Demon Lord (on Google Play)](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1) + - [Marvelous Marbles (on Steam)](https://store.steampowered.com/app/2244540/Marvelous_Marbles/) + - [Boxer (on Google Play)](https://play.google.com/store/apps/details?id=com.tharg.boxer) + - [Depthris (on Itch)](https://codewalker.itch.io/depthris) + - [Stranded (on Itch)](https://tgiant.itch.io/stranded) + - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests) + - [Star Colony: Beyond Horizons (on Google Play)](https://play.google.com/store/apps/details?id=game.colony.ColonyBuilder) + - [High Impact (on Steam)](https://store.steampowered.com/app/3059050/High_Impact/) + +## Getting Started Go to https://github.com/jMonkeyEngine/sdk/releases to download the jMonkeyEngine SDK. -[Read the wiki](https://jmonkeyengine.github.io/wiki) for a complete install guide. Power up with some SDK Plugins and AssetPacks and you are off to the races. At this point you're gonna want to [join the forum](http://hub.jmonkeyengine.org/) so our tribe can grow stronger. +Read [the wiki](https://jmonkeyengine.github.io/wiki) for the installation guide and tutorials. +Join [the discussion forum](https://hub.jmonkeyengine.org/) to participate in our community, +get your questions answered, and share your projects. -Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION, it will break constantly during development of the stable jME versions! +Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION. ### Technology Stack - - Java - - NetBeans Platform - - Gradle + - windowed, multi-platform IDE derived from NetBeans + - libraries for GUI, networking, physics, SFX, terrain, importing assets, etc. + - platform-neutral core library for scene graph, animation, rendering, math, etc. + - LWJGL v2/v3 (to access GLFW, OpenAL, OpenGL, and OpenVR) or Android or iOS + - Java Virtual Machine (v8 or higher) -Plus a bunch of awesome libraries & tight integrations like Bullet, Blender, NiftyGUI and other goodies. - ### Documentation Did you miss it? Don't sweat it, [here it is again](https://jmonkeyengine.github.io/wiki). @@ -47,5 +63,91 @@ Read our [contribution guide](https://github.com/jMonkeyEngine/jmonkeyengine/blo ### License -New BSD (3-clause) License. In other words, you do whatever makes you happy! +[New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE.md) + +### How to Build the Engine from Source + +1. Install a Java Development Kit (JDK), + if you don't already have one. +2. Point the `JAVA_HOME` environment variable to your JDK installation: + (In other words, set it to the path of a directory/folder + containing a "bin" that contains a Java executable. + That path might look something like + "C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot" + or "/usr/lib/jvm/java-17-openjdk-amd64/" or + "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" .) + + using Bash or Zsh: `export JAVA_HOME="` *path to installation* `"` + + using Fish: `set -g JAVA_HOME "` *path to installation* `"` + + using Windows Command Prompt: `set JAVA_HOME="` *path to installation* `"` + + using PowerShell: `$env:JAVA_HOME = '` *path to installation* `'` +3. Download and extract the engine source code from GitHub: + + using Git: + + `git clone https://github.com/jMonkeyEngine/jmonkeyengine.git` + + `cd jmonkeyengine` + + `git checkout -b latest v3.7.0-stable` (unless you plan to do development) + + using a web browser: + + browse to [the latest release](https://github.com/jMonkeyEngine/jmonkeyengine/releases/latest) + + follow the "Source code (zip)" link at the bottom of the page + + save the ZIP file + + extract the contents of the saved ZIP file + + `cd` to the extracted directory/folder +4. Run the Gradle wrapper: + + using Bash or Fish or PowerShell or Zsh: `./gradlew build` + + using Windows Command Prompt: `.\gradlew build` + +After a successful build, +fresh JARs will be found in "*/build/libs". + +You can install the JARs to your local Maven repository: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew install` ++ using Windows Command Prompt: `.\gradlew install` + +You can run the "jme3-examples" app: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew run` ++ using Windows Command Prompt: `.\gradlew run` + +You can restore the project to a pristine state: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew clean` ++ using Windows Command Prompt: `.\gradlew clean` + + +## Running examples + +The engine comes with some examples that you can run with: + +```bash +./gradlew runExamples +``` + +You can optionally use the `-Pexample` property to specify an example to start without the need to navigate the test chooser, e.g.: + +```bash +./gradlew runExamples -Pexample=jme3test.light.pbr.TestPBRSimple +``` + +### Android examples + +You can run the Android examples on a local android emulator with: + +```bash +./gradlew runAndroidExamples +# or for a specific example: +# ./gradlew runAndroidExamples -Pexample=jme3test.post.TestBloom +``` + +*Make sure to have the SDK installed and configured properly, and the emulator running before executing the command.* + + +## Running Tests + +To run all tests and generate a JaCoCo code coverage report, run the `testCodeCoverageReport` Gradle task. To avoid the generation of the report, use the `test` task instead. + +This runs all subproject tests and produces two sets of HTML reports: + +- **Aggregated report** (all modules combined): + `build/reports/jacoco/testCodeCoverageReport/html/index.html` +- **Per-module reports**: + `/build/reports/jacoco/test/html/index.html` +A summary index linking to every per-module report is also generated at: +`build/reports/jacoco/index.html` diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 7c8340356f..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: 1.0.{build}.{branch} - -branches: - only: - - master - -only_commits: - files: - - jme3-bullet-native/ - -skip_tags: true - -max_jobs: 1 - -clone_depth: 1 - -image: Visual Studio 2013 - -environment: - encrypted_f0a0b284e2e8_iv: - secure: aImQXs4g7zMXm1nWRvlh2wPK1UQvozS1fOVNthpyoEDFZ2FvBSdXqh5NPbGh44+F - encrypted_f0a0b284e2e8_key: - secure: Ek2lqC2e19qQDRRdlvnYyLFBq3TNj6YwKTAPuJ2VElJsxi9lQg+9ZP+VbP4kbHTx6Zaa++vtmOuxLZL7gdILrEEPa1Jix2BBLBfcxBUxe6w= - -install: - - cmd: del C:\Users\appveyor\.gradle\caches\modules-2\modules-2.lock - -build_script: - - cmd: gradlew.bat -PbuildNativeProjects=true :jme3-bullet-native:assemble - -cache: -- C:\Users\appveyor\.gradle\caches -- C:\Users\appveyor\.gradle\wrapper -- jme3-bullet-native\bullet3.zip - -test: off -deploy: off - -on_success: -- cmd: >- - openssl aes-256-cbc -K %encrypted_f0a0b284e2e8_key% -iv %encrypted_f0a0b284e2e8_iv% -in private\key.enc -out c:\users\appveyor\.ssh\id_rsa -d - - git checkout -q %APPVEYOR_REPO_BRANCH% - - git add -- jme3-bullet-native/libs/native/windows/ - - git commit -m "[ci skip] bullet: update windows natives" - - git pull -q --rebase - - git push git@github.com:jMonkeyEngine/jmonkeyengine.git diff --git a/bintray.gradle b/bintray.gradle deleted file mode 100644 index 0deafc8cf6..0000000000 --- a/bintray.gradle +++ /dev/null @@ -1,29 +0,0 @@ -// -// This file is to be applied to some subproject. -// - -apply plugin: 'com.jfrog.bintray' - -bintray { - user = bintray_user - key = bintray_api_key - configurations = ['archives'] - dryRun = false - pkg { - repo = 'org.jmonkeyengine' - userOrg = 'jmonkeyengine' - name = project.name - desc = POM_DESCRIPTION - websiteUrl = POM_URL - licenses = ['BSD New'] - vcsUrl = POM_SCM_URL - labels = ['jmonkeyengine'] - } -} - -bintrayUpload.dependsOn(writeFullPom) - -bintrayUpload.onlyIf { - (bintray_api_key.length() > 0) && - !(version ==~ /.*SNAPSHOT/) -} diff --git a/build.gradle b/build.gradle index e1af45a555..f4ea4a4f4f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,59 +1,147 @@ buildscript { repositories { - jcenter() + mavenCentral() + google() + maven { + url = "https://plugins.gradle.org/m2/" + } } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.5' + classpath libs.android.build.gradle + classpath libs.spotbugs.gradle.plugin } } +plugins { + id 'org.jmonkeyengine.nativeimage' apply false +} + +import org.gradle.api.file.RelativePath + +// Set the license for IDEs that understand this +ext.license = file("$rootDir/source-file-header-template.txt") + apply plugin: 'base' apply from: file('version.gradle') +allprojects { + repositories { + mavenCentral() + google() + } + tasks.withType(Jar) { + duplicatesStrategy = 'include' + } +} + // This is applied to all sub projects subprojects { if(!project.name.equals('jme3-android-examples')) { apply from: rootProject.file('common.gradle') - if (!['jme3-testdata', 'sdk'].contains(project.name)) { - apply from: rootProject.file('bintray.gradle') - } } else { apply from: rootProject.file('common-android-app.gradle') } + + def isAndroidApp = project.plugins.hasPlugin('com.android.application') + if (!project.name.endsWith("-native") && !isAndroidApp && enableSpotBugs != "false" ) { + apply plugin: 'com.github.spotbugs' + + // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical. + spotbugs { + ignoreFailures = true + toolVersion = libs.versions.spotbugs.get() + } + + tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) { + reports { + html.required = !project.hasProperty("xml-reports") + xml.required = project.hasProperty("xml-reports") + } + } + } } -task run(dependsOn: ':jme3-examples:run') { +def nativeImagePluginBuild = gradle.includedBuild('jme3-nativeimage-plugin') +def nativeImagePluginPublishTasks = [ + publish : ':publish', + publishToMavenLocal : ':publishToMavenLocal', + install : ':install', + publishAllPublicationsToCentralRepository : ':publishAllPublicationsToCentralRepository', + publishAllPublicationsToSNAPSHOTRepository : ':publishAllPublicationsToSNAPSHOTRepository', + publishAllPublicationsToDistRepository : ':publishAllPublicationsToDistRepository' +] + +subprojects { + tasks.configureEach { task -> + String includedTaskPath = nativeImagePluginPublishTasks[task.name] + if (includedTaskPath != null) { + task.dependsOn nativeImagePluginBuild.task(includedTaskPath) + } + } +} + +tasks.register('run') { + dependsOn ':jme3-examples:run' description = 'Run the jME3 examples' } +tasks.register('runAndroidExamples') { + description = 'Run the Android examples selector' + def androidExamplesProject = findProject(':jme3-android-examples') + if (androidExamplesProject != null) { + dependsOn ':jme3-android-examples:runAndroidExamples' + } else { + doLast { + throw new GradleException('Android examples are not available. Request an Android examples task and configure an Android SDK.') + } + } +} + +tasks.register('runIosExamples') { + description = 'Run the iOS examples selector' + def iosExamplesProject = findProject(':jme3-ios-examples') + if (iosExamplesProject != null) { + dependsOn ':jme3-ios-examples:runIosExamples' + } else { + doLast { + throw new GradleException('iOS examples are not available. Request an iOS examples task.') + } + } +} + defaultTasks 'run' -task libDist(dependsOn: subprojects.build, description: 'Builds and copies the engine binaries, sources and javadoc to build/libDist') { +def libDist = tasks.register('libDist') { + dependsOn(subprojects.collect { it.tasks.named('build') }) + description = 'Builds and copies the engine binaries, sources and javadoc to build/libDist' + doLast { File libFolder = mkdir("$buildDir/libDist/lib") File sourceFolder = mkdir("$buildDir/libDist/sources") File javadocFolder = mkdir("$buildDir/libDist/javadoc") subprojects.each {project -> - if(project.ext.mainClass == ''){ + if(!project.hasProperty('mainClassName')){ project.tasks.withType(Jar).each {archiveTask -> - if(archiveTask.classifier == "sources"){ + String classifier = archiveTask.archiveClassifier.get() + String ext = archiveTask.archiveExtension.get() + File archiveFile = archiveTask.archiveFile.get().asFile + if (classifier == "sources") { copy { - from archiveTask.archivePath + from archiveFile into sourceFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + classifier + '.' + ext} } - } else if(archiveTask.classifier == "javadoc"){ + } else if (classifier == "javadoc") { copy { - from archiveTask.archivePath + from archiveFile into javadocFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + classifier + '.' + ext} } } else{ copy { - from archiveTask.archivePath + from archiveFile into libFolder - rename {project.name + '.' + archiveTask.extension} + rename {project.name + '.' + ext} } } } @@ -62,120 +150,75 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e } } -task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"Package the nightly zip distribution"){ - archiveName "jME" + jmeFullVersion + ".zip" +tasks.register('createZipDistribution', Zip) { + dependsOn(tasks.named('dist'), libDist) + description = 'Package the nightly zip distribution' + archiveFileName = provider { + "jME" + jmeFullVersion + ".zip" + } into("/") { from {"./dist"} - } + } into("/sources") { - from {"$buildDir/libDist/sources"} + from {"$buildDir/libDist/sources"} } } -task copyLibs(type: Copy){ +tasks.register('copyLibs', Copy) { // description 'Copies the engine dependencies to build/libDist' from { - subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() + subprojects*.configurations*.implementation*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() } into "$buildDir/libDist/lib-ext" //buildDir.path + '/' + libsDirName + '/lib' } -task dist(dependsOn: [':jme3-examples:dist', 'mergedJavadoc']){ - description 'Creates a jME3 examples distribution with all jme3 binaries, sources, javadoc and external libraries under ./dist' +tasks.register('dist') { + dependsOn ':jme3-examples:dist', tasks.named('mergedJavadoc') + description = 'Creates a jME3 examples distribution with all jme3 binaries, sources, javadoc and external libraries under ./dist' } -task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the projects.') { +def mergedJavadocSubprojects = [ + ":jme3-android", + ":jme3-core", + ":jme3-desktop", + ":jme3-effects", + ":jme3-ios", + ":jme3-jbullet", + ":jme3-jogg", + ":jme3-lwjgl", + ":jme3-lwjgl3", + ":jme3-networking", + ":jme3-niftygui", + ":jme3-plugins", + ":jme3-terrain", +] +tasks.register('mergedJavadoc', Javadoc) { + description = 'Creates Javadoc from all the projects.' title = 'jMonkeyEngine3' - destinationDir = mkdir("dist/javadoc") - - options.encoding = 'UTF-8' + destinationDir = file("dist/javadoc") - // Allows Javadoc to be generated on Java 8 despite doclint errors. - if (JavaVersion.current().isJava8Compatible()) { + def javadocErrorsEnabled = findProperty('enableJavadocError') == 'true' + failOnError = javadocErrorsEnabled + options.encoding = 'UTF-8' + if (javadocErrorsEnabled) { + options.addBooleanOption('Werror', true) + options.addBooleanOption('Xdoclint:all,-missing', true) + } else { options.addStringOption('Xdoclint:none', '-quiet') } - - options.overview = file("javadoc-overview.html") - // Note: The closures below are executed lazily. - source subprojects.collect {project -> - project.sourceSets*.allJava - } -// classpath = files(subprojects.collect {project -> -// project.sourceSets*.compileClasspath}) - // source { - // subprojects*.sourceSets*.main*.allSource - // } - classpath.from { - subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() - } -} - -task mergedSource(type: Copy){ + options.overview = file("javadoc-overview.html") + source = mergedJavadocSubprojects.collect { project(it).sourceSets.main.allJava } + classpath = files(mergedJavadocSubprojects.collect { project(it).sourceSets.main.compileClasspath }) } -task wrapper(type: Wrapper, description: 'Creates and deploys the Gradle wrapper to the current directory.') { - gradleVersion = '3.2.1' -} - -ext { - ndkCommandPath = "" - ndkExists = false +def cleanMergedJavadoc = tasks.register('cleanMergedJavadoc', Delete) { + delete file('dist/javadoc') } -task configureAndroidNDK { - def ndkBuildFile = "ndk-build" - // if windows, use ndk-build.cmd instead - if (System.properties['os.name'].toLowerCase().contains('windows')) { - ndkBuildFile = "ndk-build.cmd" - } - - // ndkPath is defined in the root project gradle.properties file - String ndkBuildPath = ndkPath + File.separator + ndkBuildFile - //Use the environment variable for the NDK location if defined - if (System.env.ANDROID_NDK != null) { - ndkBuildPath = System.env.ANDROID_NDK + File.separator + ndkBuildFile - } - - if (new File(ndkBuildPath).exists()) { - ndkExists = true - ndkCommandPath = ndkBuildPath - } +tasks.named('clean') { + dependsOn cleanMergedJavadoc } -//class IncrementalReverseTask extends DefaultTask { -// @InputDirectory -// def File inputDir -// -// @OutputDirectory -// def File outputDir -// -// @Input -// def inputProperty -// -// @TaskAction -// void execute(IncrementalTaskInputs inputs) { -// println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date" -// inputs.outOfDate { change -> -// println "out of date: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.text = change.file.text.reverse() -// } -// -// inputs.removed { change -> -// println "removed: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.delete() -// } -// } -//} - -//allprojects { -// tasks.withType(JavaExec) { -// enableAssertions = true // false by default -// } -// tasks.withType(Test) { -// enableAssertions = true // true by default -// } -//} +apply from: 'gradle/jacoco.gradle' diff --git a/common-android-app.gradle b/common-android-app.gradle index ff5dea0b16..8670bf7e78 100644 --- a/common-android-app.gradle +++ b/common-android-app.gradle @@ -1,13 +1,11 @@ apply plugin: 'com.android.application' group = 'org.jmonkeyengine' -version = jmeVersion + '-' + jmeVersionTag - -sourceCompatibility = '1.6' +version = jmeFullVersion repositories { mavenCentral() maven { - url "http://nifty-gui.sourceforge.net/nifty-maven-repo" + url "https://nifty-gui.sourceforge.net/nifty-maven-repo" } } diff --git a/common.gradle b/common.gradle index d15f1c5825..30ad1731c7 100644 --- a/common.gradle +++ b/common.gradle @@ -2,112 +2,245 @@ // This file is to be applied to every subproject. // -apply plugin: 'java' -apply plugin: 'maven' +apply plugin: 'java-library' +apply plugin: 'groovy' +apply plugin: 'maven-publish' +apply plugin: 'signing' +apply plugin: 'eclipse' +apply plugin: 'checkstyle' +eclipse.jdt.file.withProperties { props -> + props.setProperty "org.eclipse.jdt.core.circularClasspath", "warning" +} group = 'org.jmonkeyengine' -version = jmePomVersion +version = jmeFullVersion + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + toolchain { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + } +} + +apply plugin: 'org.jmonkeyengine.nativeimage' -sourceCompatibility = '1.7' -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +tasks.withType(JavaCompile) { // compile-time options: + //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings + options.compilerArgs.addAll(['-Xlint:unchecked', '-Xlint:-options', '-Werror']) + options.encoding = 'UTF-8' + options.release = 8 +} repositories { mavenCentral() - maven { - url "http://nifty-gui.sourceforge.net/nifty-maven-repo" + flatDir { + dirs rootProject.file('lib') } } dependencies { // Adding dependencies here will add the dependencies to each subproject. - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.mockito', name: 'mockito-core', version: '2.0.28-beta' - testCompile group: 'org.easytesting', name: 'fest-assert-core', version: '2.0M10' + testImplementation platform(libs.junit.bom) + testImplementation libs.junit.jupiter + testImplementation libs.mokito.core + testImplementation libs.mokito.junit.jupiter + testImplementation libs.groovy.test + testRuntimeOnly libs.junit.platform.launcher +} + +// Uncomment if you want to see the status of every test that is run and +// the test output. +/* +test { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + } } +*/ jar { manifest { attributes 'Implementation-Title': 'jMonkeyEngine', - 'Implementation-Version': jmeFullVersion + 'Implementation-Version': jmeFullVersion, + 'Automatic-Module-Name': "${project.name.replace("-", ".")}", + 'Created-By': "${JavaVersion.current()} (${System.getProperty("java.vendor")})" } } javadoc { - failOnError = false + def javadocErrorsEnabled = rootProject.findProperty('enableJavadocError') == 'true' + failOnError = javadocErrorsEnabled + if (javadocErrorsEnabled) { + options.addBooleanOption('Werror', true) + options.addBooleanOption('Xdoclint:all,-missing', true) + } else { + options.addStringOption('Xdoclint:none', '-quiet') + } options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED - options.docTitle = "jMonkeyEngine ${jmeMainVersion} ${project.name} Javadoc" - options.windowTitle = "jMonkeyEngine ${jmeMainVersion} ${project.name} Javadoc" - options.header = "jMonkeyEngine ${jmeMainVersion} ${project.name}" + options.docTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc" + options.windowTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc" + options.header = "jMonkeyEngine ${jmeFullVersion} ${project.name}" options.author = "true" options.use = "true" - //disable doclint for JDK8, more quiet output - if (JavaVersion.current().isJava8Compatible()){ - options.addStringOption('Xdoclint:none', '-quiet') - } + options.charSet = "UTF-8" + options.encoding = "UTF-8" + source = sourceSets.main.allJava // main only, exclude tests } test { + useJUnitPlatform() testLogging { exceptionFormat = 'full' } } task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets*.allSource } task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from the javadoc files.') { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } ext.pomConfig = { - name POM_NAME - description POM_DESCRIPTION - url POM_URL - inceptionYear POM_INCEPTION_YEAR + name = POM_NAME + description = POM_DESCRIPTION + url = POM_URL + inceptionYear = POM_INCEPTION_YEAR scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEVELOPER_CONNECTION + url = POM_SCM_URL + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEVELOPER_CONNECTION } licenses { license { - name POM_LICENSE_NAME - url POM_LICENSE_URL - distribution POM_LICENSE_DISTRIBUTION + name = POM_LICENSE_NAME + url = POM_LICENSE_URL + distribution = POM_LICENSE_DISTRIBUTION } } developers { developer { - name 'jMonkeyEngine Team' - id 'jMonkeyEngine' + name = 'jMonkeyEngine Team' + id = 'jMonkeyEngine' } } } -// workaround to be able to use same custom pom with 'maven' and 'bintray' plugin -task writeFullPom { - ext.pomFile = "$mavenPomDir/${project.name}-${project.version}.pom" - outputs.file pomFile - doLast { - pom { - project pomConfig - }.writeTo(pomFile) +tasks.named('assemble') { + dependsOn sourcesJar + if (buildJavaDoc == "true") { + dependsOn javadocJar } } -assemble.dependsOn(writeFullPom) -install.dependsOn(writeFullPom) -uploadArchives.dependsOn(writeFullPom) +publishing { + publications { + maven(MavenPublication) { + artifact javadocJar + artifact sourcesJar + from components.java + pom { + description = POM_DESCRIPTION + developers { + developer { + id = 'jMonkeyEngine' + name = 'jMonkeyEngine Team' + } + } + inceptionYear = POM_INCEPTION_YEAR + licenses { + license { + distribution = POM_LICENSE_DISTRIBUTION + name = POM_LICENSE_NAME + url = POM_LICENSE_URL + } + } + name = POM_NAME + scm { + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEVELOPER_CONNECTION + url = POM_SCM_URL + } + url = POM_URL + } + version = project.version + } + } -artifacts { - archives jar - archives sourcesJar - if (buildJavaDoc == "true") { - archives javadocJar + repositories { + maven { + name = 'Dist' + url = gradle.rootProject.projectDir.absolutePath + '/dist/maven' + } + + // Uploading to Sonatype relies on the existence of 2 properties + // (centralUsername and centralPassword) + // which should be set using -P options on the command line. + + maven { + // for uploading release builds to the default repo in Sonatype's OSSRH staging area + credentials { + username = gradle.rootProject.hasProperty('centralUsername') ? centralUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('centralPassword') ? centralPassword : 'Unknown password' + } + name = 'Central' + url = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/' + } + maven { + // for uploading snapshot builds to Sonatype's maven-snapshots repo + credentials { + username = gradle.rootProject.hasProperty('centralUsername') ? centralUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('centralPassword') ? centralPassword : 'Unknown password' + } + name = 'SNAPSHOT' + url = 'https://central.sonatype.com/repository/maven-snapshots/' + } } - archives writeFullPom.outputs.files[0] } +publishToMavenLocal.doLast { + println 'published ' + project.getName() + "-${jmeFullVersion} to mavenLocal" +} +task('install') { + dependsOn 'publishToMavenLocal' +} + +signing { + def signingKey = gradle.rootProject.findProperty('signingKey') + def signingPassword = gradle.rootProject.findProperty('signingPassword') + useInMemoryPgpKeys(signingKey, signingPassword) + + sign publishing.publications.maven +} +tasks.withType(Sign) { + onlyIf { gradle.rootProject.hasProperty('signingKey') } +} + +def checkstyleSupported = JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21) + +checkstyle { + toolVersion = libs.versions.checkstyle.get() + configFile = file("${gradle.rootProject.rootDir}/config/checkstyle/checkstyle.xml") +} + +checkstyleMain { + source ='src/main/java' +} + +checkstyleTest { + source ='src/test/java' +} + +tasks.withType(Checkstyle) { + enabled = checkstyleSupported + reports { + xml.required.set(false) + html.required.set(true) + } + include("**/com/jme3/renderer/**/*.java") +} diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 0000000000..919d0a916f --- /dev/null +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..927c3779c9 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/jacoco/jacoco-custom.css b/config/jacoco/jacoco-custom.css new file mode 100644 index 0000000000..67efc5f315 --- /dev/null +++ b/config/jacoco/jacoco-custom.css @@ -0,0 +1,413 @@ +/* jMonkeyEngine JaCoCo Report Custom Styles */ + +/* Based on jmonkeyengine.org color scheme: + - Background: #1b1b1b, #272727 + - Highlight: #ffd000 + - Text: #b1b1b1, #c5c5c5, #e7e7e7 +*/ + +@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=PT+Mono&display=swap'); + +* { + box-sizing: border-box; +} + +:root { + --fontFamily: 'Ubuntu', Arial, sans-serif; + --foreground: #b1b1b1; + --foreground2: #c5c5c5; + --foreground3: #e7e7e7; + --highlightFg: #ffd000; + --highlightFg2: #c07302; + --background: #1b1b1b; + --background4: #111; + --background3: #000000; + --background2: #272727; +} + +body { + font-family: var(--fontFamily); + background: var(--background) !important; + color: var(--foreground); + margin: 0; + padding: 0; + font-size: 0.9rem; + line-height: 1.4; +} + +/* Header */ +h1, h2, h3 { + color: var(--highlightFg); + margin: 0; + padding: 0; + margin-bottom: 0.5rem; +} + +h1 { + font-size: 1.8rem; + padding: 1rem 0; + text-align: center; +} + +h2 { + font-size: 1.4rem; + padding: 0.8rem 0; +} + +pre { + background: var(--background2); + color: var(--foreground3); + padding: 1rem; + font-family: 'PT Mono', monospace; + overflow: auto; +} + +code, pre, .Tree .Element { + font-family: 'PT Mono', monospace; +} + +/* Links */ +a { + color: var(--highlightFg); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* Breadcrumb */ +.BreadCrumb, +.breadcrumb { + background: var(--background2); + border-radius: 0.3rem; + padding: 0.8rem 1.2rem; + margin-bottom: 1rem; +} + +.BreadCrumb a, +.breadcrumb a { + color: var(--foreground2); + margin-right: 0.5rem; +} + +.breadcrumb .info { + display: inline; +} + +.breadcrumb .el_home::after, +.breadcrumb .info a::after { + content: " | "; + color: var(--foreground); + padding: 0 0.2rem; +} + +.breadcrumb .info a:last-of-type::after { + content: ""; +} + +.breadcrumb .el_home { + color: var(--highlightFg); + font-weight: bold; +} + +.breadcrumb .el_report, +.breadcrumb .el_package { + display: inline-block; + margin: 0 0.3rem; +} + +/* Report container */ +.Report { + padding: 1rem; +} + +.Report .header { + background: var(--background2); + padding: 1rem; + margin-bottom: 1rem; + border-radius: 0.3rem; + text-align: center; +} + +.Report .footer { + background: var(--background2); + padding: 0.8rem; + margin-top: 1rem; + border-radius: 0.3rem; + text-align: center; + font-size: 0.8rem; + color: var(--foreground2); +} + +/* Counter */ +.Counter { + display: inline-block; + padding: 0.2rem 0.5rem; + border-radius: 0.3rem; + margin: 0.1rem; + font-family: 'PT Mono', monospace; + font-weight: bold; +} + +.Counter.total { + background: var(--background2); + color: var(--foreground3); +} + +.Counter.hit { + background: #2d5a2d; + color: #8fd98f; +} + +.Counter.missed { + background: #5a2d2d; + color: #f59898; +} + +.Counter.skipped { + background: #4a4a2d; + color: #f5e698; +} + +/* Report sections */ +section { + margin: 1rem 0; +} + +section > article { + background: var(--background2); + border-radius: 0.3rem; + margin: 0.5rem 0; +} + +article .content { + padding: 0.8rem; +} + +/* Package list tables */ +table { + width: 100%; + border-collapse: collapse; + margin: 0.5rem 0; +} + +th, td { + text-align: left; + padding: 0.6rem 0.8rem; + border-bottom: 1px solid var(--background); +} + +th { + background: var(--background4); + color: var(--highlightFg); + font-weight: bold; +} + +tr:hover { + background: var(--background4); +} + +td { + color: var(--foreground2); +} + +a.group, .Group a { + color: var(--highlightFg); +} + +/* Color differences for coverage */ +.Pct0 { color: #ff6b6b; } +.Pct0-39 { color: #f5b075; } +.Pct40-59 { color: #f5e675; } +.Pct60-89 { color: #b5f575; } +.Pct90-100 { color: #8fd98f; } + +/* Bar graphs */ +.bar { + display: inline-block; + height: 1rem; + border-radius: 0.2rem; + margin-right: 0.3rem; +} + +.bar.hit { + background: #4caf50; +} + +.bar.missed { + background: #f44336; +} + +.bar.covered { + background: #4caf50; +} + +.bar.unchecked { + background: #ff9800; +} + +/* Source files */ +.Source { + list-style: none; + padding: 0; + margin: 0; +} + +.Source li { + padding: 0.3rem 0; + border-bottom: 1px solid var(--background); +} + +/* Code lines */ +pre.Code { + margin: 0.5rem 0; + padding: 0.5rem; + border-radius: 0.3rem; + background: var(--background3); + overflow-x: auto; +} + +/* Source view line coverage highlighting */ +.fc { background-color: #1a5c1a; color: #ffffff; } +.nc { background-color: #5c1a1a; color: #ffffff; } +.pc { background-color: #5c4a00; color: #ffffff; } + +.bfc { background-color: #1a5c1a; } +.bpc { background-color: #5c4a00; } +.bnc { background-color: #5c1a1a; } + +code.linenumber { + color: var(--foreground2); + padding-right: 0.8rem; +} + +/* Instructions */ +.Pc, +.Mi, +.Si { + display: inline-block; + min-width: 1.5rem; + text-align: right; + padding: 0.1rem 0.3rem; + border-radius: 0.2rem; + font-family: 'PT Mono', monospace; + font-size: 0.85rem; +} + +.Pc { + background: #2d5a2d; + color: #8fd98f; +} + +.Mi { + background: #5a2d2d; + color: #f59898; +} + +.Si { + background: #4a4a2d; + color: #f5e698; +} + +/* Tags */ +.tag { + padding: 0.1rem 0.3rem; + border-radius: 0.2rem; + font-size: 0.75rem; + margin-right: 0.3rem; +} + +/* Session */ +.session .title { + background: var(--background4); + padding: 0.5rem 1rem; + border-radius: 0.3rem 0.3rem 0 0; +} + +/* Container */ +.container { + background: var(--background2); + border-radius: 0.3rem; + padding: 1rem; +} + +/* Results */ +.Results { + margin-top: 1rem; +} + +.Results li { + padding: 0.3rem 0; +} + +/* Information */ +.Information { + padding: 0.8rem; + background: var(--background4); + border-radius: 0.3rem; + margin: 0.5rem 0; +} + +/* Table headers in content */ +table.legacy, +table.legacy td, +table.legacy th { + border: 1px solid var(--background4); +} + +table.legacy th { + background: var(--background4); +} + +/* Tree styling */ +.Tree div { + padding: 0.3rem 0; +} + +.Tree .Element { + font-family: 'PT Mono', monospace; +} + +.Tree .Element a { + color: var(--highlightFg); +} + +/* Percent */ +.Percent { + font-family: 'PT Mono', monospace; + color: var(--highlightFg); + font-weight: bold; +} + +/* Container row */ +.Container .container_row { + padding: 0.3rem; + border-bottom: 1px solid var(--background); +} + +.Container .container_row:hover { + background: var(--background4); +} + +/* Errors */ +.error { + color: #ff6b6b; + background: #3d2020; + padding: 0.5rem; + border-radius: 0.3rem; +} + +/* Screen reader only */ +.sr_only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} \ No newline at end of file diff --git a/config/jacoco/prettify-custom.css b/config/jacoco/prettify-custom.css new file mode 100644 index 0000000000..be8289d557 --- /dev/null +++ b/config/jacoco/prettify-custom.css @@ -0,0 +1,13 @@ +/* Pretty printing styles — jMonkeyEngine dark theme. Used with prettify.js. */ + +.pln { color: #e7e7e7; } /* plain text */ +.kwd { color: #ffd000; font-weight: bold; } /* keyword — gold accent */ +.str { color: #7fcc00; } /* string — green */ +.com { color: #6a7f8c; font-style: italic; } /* comment — muted blue-grey */ +.typ { color: #80cfff; } /* type — light blue */ +.lit { color: #ff9f43; } /* literal — orange */ +.pun { color: #b1b1b1; } /* punctuation — dim */ +.tag { color: #ffd000; } /* tag */ +.atn { color: #80cfff; } /* attribute name */ +.atv { color: #7fcc00; } /* attribute value */ +.dec { color: #ff9f43; } /* declaration */ diff --git a/gradle.properties b/gradle.properties index e8e1fc8f14..c664edf60b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,40 +1,35 @@ -# Version number used for plugins, only 3 numbers (e.g. 3.1.3) -jmeVersion = 3.2.0 -# Version used for application and settings folder, no spaces! -jmeMainVersion = 3.2 -# Version addition pre-alpha-svn, Stable, Beta -jmeVersionTag = SNAPSHOT -# Increment this each time jmeVersionTag changes but jmeVersion stays the same -jmeVersionTagID = 0 +# Version number: Major.Minor.SubMinor (e.g. 3.3.0) +jmeVersion = 3.10.0 -# specify if JavaDoc should be built -buildJavaDoc = false +# Leave empty to autogenerate +# (use -PjmeVersionName="myVersion" from commandline to specify a custom version name ) +jmeVersionName = + +# If true, the version name will contain the commit hash +useCommitHashAsVersionName = false -# specify if SDK and Native libraries get built -buildNativeProjects = false -buildAndroidExamples = false +# Set to true if a non-master branch name should be included in the automatically +# generated version. +includeBranchInVersion = false -# Path to android NDK for building native libraries -#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7 -ndkPath = /opt/android-ndk-r10c +# specify if JavaDoc should be built +buildJavaDoc = true -# Path for downloading native Bullet -bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.83.7.zip -bulletFolder = bullet3-2.83.7 -bulletZipFile = bullet3.zip +# Let Gradle fetch missing JDKs for toolchains automatically. +org.gradle.java.installations.auto-download=true -# POM settings -POM_NAME=jMonkeyEngine -POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers +# Enable spotbugs +enableSpotBugs=false + +# POM settings +POM_NAME=jMonkeyEngine +POM_DESCRIPTION=jMonkeyEngine is a 3-D game engine for adventurous Java developers POM_URL=http://jmonkeyengine.org POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git POM_LICENSE_NAME=New BSD (3-clause) License -POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause -POM_LICENSE_DISTRIBUTION=repo -POM_INCEPTION_YEAR=2009 +POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause +POM_LICENSE_DISTRIBUTION=repo +POM_INCEPTION_YEAR=2009 -# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline -bintray_user= -bintray_api_key= diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle new file mode 100644 index 0000000000..5491d4eb55 --- /dev/null +++ b/gradle/jacoco.gradle @@ -0,0 +1,172 @@ +import groovy.xml.MarkupBuilder + +// --------------------------------------------------------------------------- +// JaCoCo coverage — root project configuration +// --------------------------------------------------------------------------- + +apply plugin: 'jacoco-report-aggregation' + +// Apply jacoco to every subproject (common.gradle also does this, but being +// explicit here keeps all JaCoCo concerns in one place). +subprojects { + apply plugin: 'jacoco' + + // Per-subproject report: enable both XML (for aggregation) and HTML. + tasks.withType(JacocoReport).configureEach { + reports { + xml.required = true + html.required = true + } + + inputs.file("$rootDir/config/jacoco/jacoco-custom.css") + inputs.file("$rootDir/config/jacoco/prettify-custom.css") + + doLast { + if (reports.html.required.get()) { + def htmlDir = reports.html.outputLocation.get().asFile + def resourcesDir = new File(htmlDir, 'jacoco-resources') + injectCss(resourcesDir, rootDir) + + // Patch HTML files: add a Home link on every page. + def rootIndexFile = rootProject.file("build/reports/jacoco/index.html") + htmlDir.eachFileRecurse { f -> + if (f.name.endsWith('.html') && !f.name.startsWith('jacoco-')) { + def homeHref = f.toPath().parent.relativize(rootIndexFile.toPath()).toString().replace('\\', '/') + def content = f.getText('UTF-8') + if (!content.contains('class="el_home"')) { + content = content.replaceAll( + ~/()/, + 'Home $1' + ) + f.setText(content, 'UTF-8') + } + } + } + } + } + } +} + +// --------------------------------------------------------------------------- +// Aggregated report +// --------------------------------------------------------------------------- + +reporting { + reports { + testCodeCoverageReport(JacocoCoverageReport) { + testSuiteName = "test" + } + } +} + +def jacocoSubprojects = subprojects.findAll { p -> + (p.file("src/main/java").exists() || p.file("src/main/groovy").exists()) && + (p.file("src/test/java").exists() || p.file("src/test/groovy").exists()) && + p.tasks.findByName('test') && + p.tasks.findByName('jacocoTestReport') +} + +dependencies { + jacocoAggregation jacocoSubprojects +} + +testCodeCoverageReport { + jacocoSubprojects.each { proj -> + dependsOn proj.tasks.named('test') + dependsOn proj.tasks.named('jacocoTestReport') + } +} + +// --------------------------------------------------------------------------- +// Post-processing tasks +// --------------------------------------------------------------------------- + +def injectJacocoCustomCss = tasks.register('injectJacocoCustomCss') { + dependsOn testCodeCoverageReport + inputs.file("$rootDir/config/jacoco/jacoco-custom.css") + inputs.file("$rootDir/config/jacoco/prettify-custom.css") + doLast { + def resourcesDir = file("$buildDir/reports/jacoco/testCodeCoverageReport/html/jacoco-resources") + injectCss(resourcesDir, rootDir) + } +} + +def createProjectIndex = tasks.register('createProjectIndex') { + dependsOn testCodeCoverageReport + doLast { + def outputDir = file("$buildDir/reports/jacoco") + outputDir.mkdirs() + + def writer = new StringWriter() + def html = new MarkupBuilder(writer) + + html.html { + head { + title('jMonkeyEngine Code Coverage') + style(type: 'text/css', ''' + body { font-family: Arial, sans-serif; margin: 40px; background: #1b1b1b; color: #e7e7e7; } + h1 { color: #ffd000; } + table { border-collapse: collapse; margin-top: 20px; } + th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #444; } + th { background: #272727; } + a { color: #ffd000; text-decoration: none; } + a:hover { text-decoration: underline; } + .low { color: #ff6600; } + .high { color: #7fcc00; } + ''') + } + body { + h1('jMonkeyEngine Code Coverage') + table { + tr { th('Project'); th('Coverage'); th('Report') } + def parser = new groovy.xml.XmlSlurper() + parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false) + parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) + + jacocoSubprojects.each { proj -> + def jacocoTask = proj.tasks.findByName('jacocoTestReport') + if (jacocoTask && jacocoTask.reports.xml.required.get()) { + def xmlFile = jacocoTask.reports.xml.outputLocation.get().asFile + if (xmlFile.exists()) { + def counter = parser.parse(xmlFile) + .counter.find { it.@type == 'INSTRUCTION' } + def missed = counter ? counter.@missed.toFloat() : 0f + def covered = counter ? counter.@covered.toFloat() : 0f + def total = missed + covered + def pct = total > 0 ? ((covered / total) * 100).round(1) : 0 + def css = pct < 50 ? 'low' : 'high' + def reportFile = jacocoTask.reports.html.entryPoint + def href = outputDir.toPath().relativize(reportFile.toPath()).toString().replace('\\', '/') + tr { + td(proj.name) + td(class: css, "${pct}%") + td { a(href: href, proj.name) } + } + } else { + tr { td(proj.name); td('-'); td('-') } + } + } + } + } + } + } + + new File(outputDir, 'index.html').text = '\n' + writer.toString() + println("\nJaCoCo project index: file://${outputDir.absolutePath}/index.html") + } +} + +testCodeCoverageReport.finalizedBy injectJacocoCustomCss, createProjectIndex + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +def injectCss(File resourcesDir, File rootDir) { + def report = new File(resourcesDir, 'report.css') + def prettify = new File(resourcesDir, 'prettify.css') + def srcReport = new File(rootDir, 'config/jacoco/jacoco-custom.css') + def srcPrettify = new File(rootDir, 'config/jacoco/prettify-custom.css') + if (srcReport.exists() && report.parentFile.exists()) report.text = srcReport.text + if (srcPrettify.exists() && prettify.parentFile.exists()) prettify.text = srcPrettify.text +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..b0eca459c0 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,82 @@ +## catalog of libraries and plugins used to build the jmonkeyengine project + +[versions] + +checkstyle = "13.3.0" +jacoco = "0.8.12" +lwjgl3 = "3.4.1" +angle = "2026-05-09" +libjglios = "0.6" +saferalloc = "0.0.10" +nifty = "1.4.3" +spotbugs = "4.9.8" +jmeAndroidNatives = "3.10.0-xt16kb-alloc" + +[libraries] + +androidx-annotation = "androidx.annotation:annotation:1.10.0" +androidx-fragment = "androidx.fragment:fragment:1.8.9" +androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.7.0" +android-build-gradle = "com.android.tools.build:gradle:9.1.0" +android-support-appcompat = "com.android.support:appcompat-v7:28.0.0" +androidx-test-runner = "androidx.test:runner:1.7.0" +groovy-test = "org.apache.groovy:groovy-test:4.0.31" +gson = "com.google.code.gson:gson:2.14.0" +j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.6" +jme3-android-natives = { module = "org.jmonkeyengine:jme3-android-native", version.ref = "jmeAndroidNatives" } +jbullet = "com.github.stephengold:jbullet:1.0.3" +jinput = "net.java.jinput:jinput:2.0.9" +jna = "net.java.dev.jna:jna:5.18.1" +jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12" +junit-bom = "org.junit:junit-bom:5.13.4" +junit4 = "junit:junit:4.13.2" +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } +lwjgl2 = "org.jmonkeyengine:lwjgl:2.9.5" +lwjgl3-awt = "org.jmonkeyengine:lwjgl3-awt:0.2.4" + +lwjgl3-base = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl3" } +lwjgl3-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl3" } +lwjgl3-jawt = { module = "org.lwjgl:lwjgl-jawt", version.ref = "lwjgl3" } +lwjgl3-jemalloc = { module = "org.lwjgl:lwjgl-jemalloc", version.ref = "lwjgl3" } +lwjgl3-openal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl3" } +lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" } +lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" } +lwjgl3-opengles = { module = "org.lwjgl:lwjgl-opengles", version.ref = "lwjgl3" } +lwjgl3-egl = { module = "org.lwjgl:lwjgl-egl", version.ref = "lwjgl3" } + +mokito-core = "org.mockito:mockito-core:5.23.0" +mokito-junit-jupiter = "org.mockito:mockito-junit-jupiter:5.23.0" + +nifty = { module = "com.github.nifty-gui:nifty", version.ref = "nifty" } +nifty-default-controls = { module = "com.github.nifty-gui:nifty-default-controls", version.ref = "nifty" } +nifty-examples = { module = "com.github.nifty-gui:nifty-examples", version.ref = "nifty" } +nifty-style-black = { module = "com.github.nifty-gui:nifty-style-black", version.ref = "nifty" } + +spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.4.8" +vecmath = "javax.vecmath:vecmath:1.5.2" + +stb-image = "org.ngengine:stb-image:2.30.5" +imagewebp = "org.ngengine:image-webp-decoder:1.3.0" +angle = { module = "org.ngengine:angle-natives", version.ref = "angle" } +libjglios-angle-ios = { module = "org.ngengine:libjglios-angle-ios", version.ref = "libjglios" } +libjglios-core-ios = { module = "org.ngengine:libjglios-core-ios", version.ref = "libjglios" } +libjglios-gles-ios = { module = "org.ngengine:libjglios-gles-ios", version.ref = "libjglios" } +libjglios-gradle-plugin = { module = "org.ngengine:libjglios-gradle-plugin", version.ref = "libjglios" } +libjglios-openal-ios = { module = "org.ngengine:libjglios-openal-ios", version.ref = "libjglios" } +libjglios-sdl3-ios = { module = "org.ngengine:libjglios-sdl3-ios", version.ref = "libjglios" } +saferalloc = { module = "org.ngengine:saferalloc", version.ref = "saferalloc" } +saferalloc-natives-linux-x8664 = { module = "org.ngengine:saferalloc-natives-linux-x86_64", version.ref = "saferalloc" } +saferalloc-natives-linux-aarch64 = { module = "org.ngengine:saferalloc-natives-linux-aarch64", version.ref = "saferalloc" } +saferalloc-natives-windows-x8664 = { module = "org.ngengine:saferalloc-natives-windows-x86_64", version.ref = "saferalloc" } +saferalloc-natives-windows-aarch64 = { module = "org.ngengine:saferalloc-natives-windows-aarch64", version.ref = "saferalloc" } +saferalloc-natives-macos-x8664 = { module = "org.ngengine:saferalloc-natives-macos-x86_64", version.ref = "saferalloc" } +saferalloc-natives-macos-aarch64 = { module = "org.ngengine:saferalloc-natives-macos-aarch64", version.ref = "saferalloc" } +saferalloc-natives-android = { module = "org.ngengine:saferalloc-natives-android", version.ref = "saferalloc" } +saferalloc-natives-ios = { module = "org.ngengine:saferalloc-natives-ios", version.ref = "saferalloc" } + +[bundles] +saferalloc = ["saferalloc", "saferalloc-natives-linux-x8664", "saferalloc-natives-linux-aarch64", "saferalloc-natives-windows-x8664", "saferalloc-natives-windows-aarch64", "saferalloc-natives-macos-x8664", "saferalloc-natives-macos-aarch64", "saferalloc-natives-android", "saferalloc-natives-ios"] + +[plugins] +jacoco = { id = "jacoco", version.ref = "jacoco" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ca78035ef0..d997cfc60f 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 e89f72e3dd..c61a118f7d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Fri Nov 25 13:05:50 EST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip diff --git a/gradlew b/gradlew index 27309d9231..739907dfd1 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,128 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/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/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,84 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f6d5974e72..e509b2dd8f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,25 +27,29 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +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 @@ -35,54 +57,35 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +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 -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/javadoc-overview.html b/javadoc-overview.html index 9313bccb1c..83a0dc1466 100644 --- a/javadoc-overview.html +++ b/javadoc-overview.html @@ -12,7 +12,7 @@ in Java aimed at wide accessibility and quick deployment to desktop, web, and mobile platforms. -

Key Features

+

Key Features

  • Free, open-source software (under the New BSD license) – Use our free engine for commercial, educational, or hobby game development
  • Minimal adaptations for cross-compatibility – Create games that run on any OpenGL 2 and 3-ready device with the Java Virtual Machine – web, desktop, or mobile.
  • @@ -23,4 +23,3 @@

    Key Features

    - diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index 023b662f04..91e8ecddbf 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -1,10 +1,12 @@ apply plugin: 'com.android.application' +def examplesJar = project(':jme3-examples').tasks.named('jar') + android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + namespace "org.jmonkeyengine.jme3androidexamples" + compileSdk gradle.ext.androidExamplesCompileSdk - lintOptions { + lint { // Fix nifty gui referencing "java.awt" package. disable 'InvalidPackage' abortOnError false @@ -12,19 +14,25 @@ android { defaultConfig { applicationId "org.jmonkeyengine.jme3androidexamples" - minSdkVersion 15 // Android 4.0.3 ICE CREAM SANDWICH - targetSdkVersion 22 // Android 5.1 LOLLIPOP + minSdk 30 // Android 11 R + targetSdk 34 // Android 14 versionCode 1 versionName "1.0" // TODO: from settings.gradle + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + sourceSets { main { java { @@ -40,20 +48,94 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.3.0' - - compile project(':jme3-core') - compile project(':jme3-android') - compile project(':jme3-android-native') - compile project(':jme3-effects') - compile project(':jme3-bullet') - compile project(':jme3-bullet-native-android') - compile project(':jme3-networking') - compile project(':jme3-niftygui') - compile project(':jme3-plugins') - compile project(':jme3-terrain') - compile fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*']) -// compile project(':jme3-examples') + implementation fileTree(dir: 'libs', include: ['*.jar']) + androidTestImplementation libs.junit4 + androidTestImplementation libs.androidx.test.runner + implementation libs.androidx.fragment + implementation project(':jme3-core') + implementation project(':jme3-android') + implementation project(':jme3-effects') + implementation project(':jme3-jbullet') + implementation project(':jme3-jogg') + implementation project(':jme3-networking') + implementation project(':jme3-niftygui') + implementation project(':jme3-plugins') + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') + implementation project(':jme3-saferallocator') + implementation project(':jme3-terrain') + implementation files(examplesJar.flatMap { it.archiveFile }) +} + +tasks.named('preBuild') { + dependsOn examplesJar +} + +tasks.register('installAndroidExamples', Exec) { + group = 'application' + description = 'Install the Android examples selector on a connected emulator or device.' + + dependsOn tasks.named('assembleDebug') + + doFirst { + def adbExecutable = findAdbExecutable() + if (adbExecutable == null) { + throw new GradleException("ADB not found. Set -Pandroid.sdk.path, ANDROID_HOME, or ANDROID_SDK_ROOT.") + } + def apkFile = layout.buildDirectory.file('outputs/apk/debug/jme3-android-examples-debug.apk').get().asFile + if (!apkFile.isFile()) { + throw new GradleException("Debug APK not found at ${apkFile}.") + } + executable adbExecutable.absolutePath + args 'install', '-r', apkFile.absolutePath + } +} + +tasks.register('runAndroidExamples', Exec) { + group = 'application' + description = 'Install and launch Android examples on a connected emulator or device. Use -Pexample= to run a specific test directly.' + + dependsOn tasks.named('installAndroidExamples') + + doFirst { + def adbExecutable = findAdbExecutable() + if (adbExecutable == null) { + throw new GradleException("ADB not found. Set -Pandroid.sdk.path, ANDROID_HOME, or ANDROID_SDK_ROOT.") + } + executable adbExecutable.absolutePath + + def exampleClass = project.findProperty('example')?.toString()?.trim() + if (exampleClass) { + def verboseLogging = project.findProperty('verboseLogging')?.toString()?.trim() ?: 'false' + + args 'shell', 'am', 'start', + '-n', 'org.jmonkeyengine.jme3androidexamples/.TestActivity', + '--es', 'Selected_App_Class', exampleClass, + '--ez', 'Verbose_Logging', verboseLogging + } else { + args 'shell', 'am', 'start', + '-n', 'org.jmonkeyengine.jme3androidexamples/.MainActivity' + } + } +} + +def findAdbExecutable() { + def adbName = System.properties['os.name'].toLowerCase().contains('windows') ? 'adb.exe' : 'adb' + def sdkDirs = [] + if (project.findProperty('android.sdk.path')) { + sdkDirs << file(project.findProperty('android.sdk.path')) + } + if (System.env.ANDROID_HOME) { + sdkDirs << file(System.env.ANDROID_HOME) + } + if (System.env.ANDROID_SDK_ROOT) { + sdkDirs << file(System.env.ANDROID_SDK_ROOT) + } + sdkDirs << file("${System.properties['user.home']}/Android/Sdk") + + def expandedSdkDirs = sdkDirs.collectMany { sdkDir -> + [sdkDir, new File(sdkDir, 'Sdk')] + }.unique { it.absolutePath } + + return expandedSdkDirs.collect { new File(new File(it, 'platform-tools'), adbName) }.find { it.isFile() } } diff --git a/jme3-android-examples/src/androidTest/java/org/jmonkeyengine/jme3androidexamples/ApplicationTest.java b/jme3-android-examples/src/androidTest/java/org/jmonkeyengine/jme3androidexamples/ApplicationTest.java index dff82ddb86..b10ebf4789 100644 --- a/jme3-android-examples/src/androidTest/java/org/jmonkeyengine/jme3androidexamples/ApplicationTest.java +++ b/jme3-android-examples/src/androidTest/java/org/jmonkeyengine/jme3androidexamples/ApplicationTest.java @@ -1,13 +1,19 @@ package org.jmonkeyengine.jme3androidexamples; -import android.app.Application; -import android.test.ApplicationTestCase; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; /** * Testing Fundamentals */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); +public class ApplicationTest { + @Test + public void testApplicationPackage() { + String packageName = InstrumentationRegistry.getInstrumentation() + .getTargetContext() + .getPackageName(); + assertEquals("org.jmonkeyengine.jme3androidexamples", packageName); } -} \ No newline at end of file +} diff --git a/jme3-android-examples/src/main/AndroidManifest.xml b/jme3-android-examples/src/main/AndroidManifest.xml index cf9cc7c2dc..075d2a515c 100644 --- a/jme3-android-examples/src/main/AndroidManifest.xml +++ b/jme3-android-examples/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -20,15 +20,16 @@ - + @@ -42,6 +43,7 @@ android:normalScreens="true" android:smallScreens="true"/> + diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidResources.java b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidResources.java index 7484b1ec89..68e5897792 100644 --- a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidResources.java +++ b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidResources.java @@ -1,15 +1,9 @@ package jme3test.android; import com.jme3.app.SimpleApplication; -import com.jme3.light.AmbientLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; /** * Test case to look for images stored in the Android drawable and mipmap directories. Image files are diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java index 3805435d9a..c6e3bcc8c4 100644 --- a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java +++ b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java @@ -17,6 +17,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Line; +import com.jme3.system.AppSettings; import com.jme3.texture.Texture; import com.jme3.util.IntMap; @@ -38,7 +39,7 @@ public class TestAndroidSensors extends SimpleApplication implements ActionListe private Geometry geomZero = null; // Map of joysticks saved with the joyId as the key - private IntMap joystickMap = new IntMap(); + private IntMap joystickMap = new IntMap<>(); // flag to allow for the joystick axis to be calibrated on startup private boolean initialCalibrationComplete = false; // mappings used for onAnalog @@ -79,6 +80,12 @@ public class TestAndroidSensors extends SimpleApplication implements ActionListe // Make sure to set joystickEventsEnabled = true in MainActivity for Android + public static void configureSettings(AppSettings settings) { + settings.setUseJoysticks(true); + settings.setUseAndroidSensorJoystick(true); + settings.setVirtualJoystick(AppSettings.VIRTUAL_JOYSTICK_DISABLED); + } + private float toDegrees(float rad) { return rad * FastMath.RAD_TO_DEG; } @@ -228,7 +235,7 @@ public void simpleUpdate(float tpf) { } } - + @Override public void onAction(String string, boolean pressed, float tpf) { if (string.equalsIgnoreCase("MouseClick") && pressed) { // Calibrate the axis (set new zero position) if the axis @@ -256,7 +263,7 @@ public void onAction(String string, boolean pressed, float tpf) { } } - + @Override public void onAnalog(String string, float value, float tpf) { logger.log(Level.INFO, "onAnalog for {0}, value: {1}, tpf: {2}", new Object[]{string, value, tpf}); @@ -311,4 +318,4 @@ public void onAnalog(String string, float value, float tpf) { } } -} \ No newline at end of file +} diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java index 4d38087964..cbdd59d4e5 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java @@ -23,11 +23,11 @@ public class CustomArrayAdapter extends ArrayAdapter { private int selectedPosition = -1; /* Background Color of selected item */ private int selectedBackgroundColor = 0xffff00; - /* Background Color of non selected item */ + /* Background Color of non-selected items */ private int nonselectedBackgroundColor = 0x000000; /* Background Drawable Resource ID of selected item */ private int selectedBackgroundResource = 0; - /* Background Drawable Resource ID of non selected items */ + /* Background Drawable Resource ID of non-selected items */ private int nonselectedBackgroundResource = 0; /* Variables to support list filtering */ @@ -53,7 +53,7 @@ public void setSelectedBackgroundColor(int selectedBackgroundColor) { this.selectedBackgroundColor = selectedBackgroundColor; } - /** Setter for non selected background color */ + /** Setter for non-selected background color */ public void setNonSelectedBackgroundColor(int nonselectedBackgroundColor) { this.nonselectedBackgroundColor = nonselectedBackgroundColor; } @@ -63,7 +63,7 @@ public void setSelectedBackgroundResource(int selectedBackgroundResource) { this.selectedBackgroundResource = selectedBackgroundResource; } - /** Setter for non selected background resource id*/ + /** Setter for non-selected background resource id*/ public void setNonSelectedBackgroundResource(int nonselectedBackgroundResource) { this.nonselectedBackgroundResource = nonselectedBackgroundResource; } @@ -125,13 +125,13 @@ protected FilterResults performFiltering(CharSequence constraint){ String prefix = constraint.toString().toLowerCase(); Log.i(TAG, "performFiltering: entries size: " + entries.size()); if (prefix == null || prefix.length() == 0){ - ArrayList list = new ArrayList(entries); + ArrayList list = new ArrayList<>(entries); results.values = list; results.count = list.size(); Log.i(TAG, "clearing filter with size: " + list.size()); }else{ - final ArrayList list = new ArrayList(entries); - final ArrayList nlist = new ArrayList(); + final ArrayList list = new ArrayList<>(entries); + final ArrayList nlist = new ArrayList<>(); int count = list.size(); for (int i = 0; i clazz = Class.forName(appClass); + LegacyApplication application = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + AppSettings settings = new AppSettings(true); + settings.setEmulateMouse(true); + settings.setEmulateKeyboard(true); + application.setSettings(settings); + return application; + } + } diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java index 7bde9d9c9a..acfe4a89d4 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java @@ -1,10 +1,9 @@ package org.jmonkeyengine.jme3androidexamples; -import android.app.Activity; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.app.Activity; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -21,6 +20,7 @@ import dalvik.system.DexFile; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.List; @@ -31,7 +31,7 @@ * applications that are started via TestsHarness Activity. * @author iwgeric */ -public class MainActivity extends AppCompatActivity implements OnItemClickListener, View.OnClickListener, TextWatcher { +public class MainActivity extends Activity implements OnItemClickListener, View.OnClickListener, TextWatcher { private static final String TAG = "MainActivity"; /** @@ -48,35 +48,18 @@ public class MainActivity extends AppCompatActivity implements OnItemClickListen */ public static final String SELECTED_LIST_POSITION = "Selected_List_Position"; - /** - * Static String to pass the key for the setting for enabling mouse events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_MOUSE_EVENTS = "Enable_Mouse_Events"; - - /** - * Static String to pass the key for the setting for enabling joystick events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_JOYSTICK_EVENTS = "Enable_Joystick_Events"; - - /** - * Static String to pass the key for the setting for enabling key events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_KEY_EVENTS = "Enable_Key_Events"; - /** * Static String to pass the key for the setting for verbose logging to the * savedInstanceState Bundle. */ public static final String VERBOSE_LOGGING = "Verbose_Logging"; + public static final boolean DEFAULT_VERBOSE_LOGGING = false; /* Fields to contain the current position and display contents of the spinner */ private int currentPosition = 0; private String currentSelection = ""; - private List classNames = new ArrayList(); - private List exclusions = new ArrayList(); + private List classNames = new ArrayList<>(); + private List exclusions = new ArrayList<>(); private String rootPackage; /* ListView that displays the test application class names. */ @@ -93,10 +76,7 @@ public class MainActivity extends AppCompatActivity implements OnItemClickListen EditText editFilterText; /* Custom settings for the test app */ - private boolean enableMouseEvents = true; - private boolean enableJoystickEvents = false; - private boolean enableKeyEvents = true; - private boolean verboseLogging = false; + private boolean verboseLogging = DEFAULT_VERBOSE_LOGGING; /** @@ -113,10 +93,7 @@ public void onCreate(Bundle savedInstanceState) { ); currentPosition = savedInstanceState.getInt(SELECTED_LIST_POSITION, 0); currentSelection = savedInstanceState.getString(SELECTED_APP_CLASS); - enableMouseEvents = savedInstanceState.getBoolean(ENABLE_MOUSE_EVENTS, true); - enableJoystickEvents = savedInstanceState.getBoolean(ENABLE_JOYSTICK_EVENTS, false); - enableKeyEvents = savedInstanceState.getBoolean(ENABLE_KEY_EVENTS, true); - verboseLogging = savedInstanceState.getBoolean(VERBOSE_LOGGING, true); + verboseLogging = savedInstanceState.getBoolean(VERBOSE_LOGGING, DEFAULT_VERBOSE_LOGGING); } @@ -128,13 +105,14 @@ public void onCreate(Bundle savedInstanceState) { editFilterText = (EditText) findViewById(R.id.txtFilter); - /* Define the root package to start with */ + /* Define the root package shared by the desktop and Android examples. */ rootPackage = "jme3test"; /* Create an array of Strings to define which classes to exclude */ exclusions.add("$"); // inner classes exclusions.add("TestChooser"); // Desktop test chooser class exclusions.add("awt"); // Desktop test chooser class + exclusions.add("package-info"); // mExclusions.add(""); @@ -148,24 +126,25 @@ public void onCreate(Bundle savedInstanceState) { ApplicationInfo ai = this.getApplicationInfo(); String classPath = ai.sourceDir; DexFile dex = null; - Enumeration apkClassNames = null; try { dex = new DexFile(classPath); - apkClassNames = dex.entries(); + Enumeration apkClassNames = dex.entries(); while (apkClassNames.hasMoreElements()) { String className = apkClassNames.nextElement(); - if (checkClassName(className) && checkClassType(className)) { + if (checkClassName(className) && checkClassType(className) && !classNames.contains(className)) { classNames.add(className); } -// classNames.add(className); } + Collections.sort(classNames); } catch (IOException e) { e.printStackTrace(); } finally { - try { - dex.close(); - } catch (IOException e) { - e.printStackTrace(); + if (dex != null) { + try { + dex.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } @@ -214,7 +193,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) /** * User clicked a view on the screen. Check for the OK and Cancel buttons - * and either start the applicaiton or exit. + * and either start the application or exit. * @param view */ public void onClick(View view) { @@ -222,25 +201,12 @@ public void onClick(View view) { /* Get selected class, pack it in the intent and start the test app */ Log.d(TAG, "User selected OK for class: " + currentSelection); Intent intent = new Intent(this, TestActivity.class); -// intent.putExtra(SELECTED_APP_CLASS, currentSelection); -// intent.putExtra(ENABLE_MOUSE_EVENTS, enableMouseEvents); -// intent.putExtra(ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); -// intent.putExtra(ENABLE_KEY_EVENTS, enableKeyEvents); Bundle args = new Bundle(); args.putString(MainActivity.SELECTED_APP_CLASS, currentSelection); // Log.d(this.getClass().getSimpleName(), "AppClass="+currentSelection); - args.putBoolean(MainActivity.ENABLE_MOUSE_EVENTS, enableMouseEvents); -// Log.d(TestActivity.class.getSimpleName(), "MouseEnabled="+enableMouseEvents); - - args.putBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); -// Log.d(TestActivity.class.getSimpleName(), "JoystickEnabled="+enableJoystickEvents); - - args.putBoolean(MainActivity.ENABLE_KEY_EVENTS, enableKeyEvents); -// Log.d(TestActivity.class.getSimpleName(), "KeyEnabled="+enableKeyEvents); - args.putBoolean(MainActivity.VERBOSE_LOGGING, verboseLogging); // Log.d(TestActivity.class.getSimpleName(), "VerboseLogging="+verboseLogging); @@ -264,7 +230,7 @@ private boolean checkClassName(String className) { boolean include = true; /* check to see if the class in inside the rootPackage package */ if (className.startsWith(rootPackage)) { - /* check to see if the class contains any of the exlusion strings */ + /* check to see if the class contains any of the exclusion strings */ for (int i = 0; i < exclusions.size(); i++) { if (className.contains(exclusions.get(i))) { Log.d(TAG, "Skipping Class " + className + ". Includes exclusion string: " + exclusions.get(i) + "."); @@ -287,7 +253,7 @@ private boolean checkClassName(String className) { private boolean checkClassType(String className) { boolean include = true; try { - Class clazz = (Class) Class.forName(className); + Class clazz = Class.forName(className, false, getClassLoader()); if (Application.class.isAssignableFrom(clazz)) { Log.d(TAG, "Class " + className + " is a jME Application"); } else { @@ -295,9 +261,9 @@ private boolean checkClassType(String className) { Log.d(TAG, "Skipping Class " + className + ". Not a jME Application"); } - } catch (NoClassDefFoundError ncdf) { + } catch (LinkageError ncdf) { include = false; - Log.d(TAG, "Skipping Class " + className + ". No Class Def found."); + Log.d(TAG, "Skipping Class " + className + ". Could not link class."); } catch (ClassNotFoundException cnfe) { include = false; Log.d(TAG, "Skipping Class " + className + ". Class not found."); @@ -327,9 +293,6 @@ public void onSaveInstanceState(Bundle savedInstanceState) { Log.d(TAG, "Saving selections in onSaveInstanceState: " + "position: " + currentPosition + ", " + "class: " + currentSelection + ", " - + "mouseEvents: " + enableMouseEvents + ", " - + "joystickEvents: " + enableJoystickEvents + ", " - + "keyEvents: " + enableKeyEvents + ", " + "VerboseLogging: " + verboseLogging + ", " ); // Save current selections to the savedInstanceState. @@ -337,9 +300,6 @@ public void onSaveInstanceState(Bundle savedInstanceState) { // killed and restarted. savedInstanceState.putString(SELECTED_APP_CLASS, currentSelection); savedInstanceState.putInt(SELECTED_LIST_POSITION, currentPosition); - savedInstanceState.putBoolean(ENABLE_MOUSE_EVENTS, enableMouseEvents); - savedInstanceState.putBoolean(ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); - savedInstanceState.putBoolean(ENABLE_KEY_EVENTS, enableKeyEvents); savedInstanceState.putBoolean(VERBOSE_LOGGING, verboseLogging); } @@ -365,7 +325,7 @@ public void onTextChanged(CharSequence cs, int startPos, int beforePos, int coun setSelection(-1); } - public void afterTextChanged(Editable edtbl) { + public void afterTextChanged(Editable editable) { } @Override @@ -386,36 +346,6 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu (Menu menu) { MenuItem item; - item = menu.findItem(R.id.optionMouseEvents); - if (item != null) { - Log.d(TAG, "Found EnableMouseEvents menu item"); - if (enableMouseEvents) { - item.setTitle(R.string.strOptionDisableMouseEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableMouseEventsTitle); - } - } - - item = menu.findItem(R.id.optionJoystickEvents); - if (item != null) { - Log.d(TAG, "Found EnableJoystickEvents menu item"); - if (enableJoystickEvents) { - item.setTitle(R.string.strOptionDisableJoystickEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableJoystickEventsTitle); - } - } - - item = menu.findItem(R.id.optionKeyEvents); - if (item != null) { - Log.d(TAG, "Found EnableKeyEvents menu item"); - if (enableKeyEvents) { - item.setTitle(R.string.strOptionDisableKeyEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableKeyEventsTitle); - } - } - item = menu.findItem(R.id.optionVerboseLogging); if (item != null) { Log.d(TAG, "Found EnableVerboseLogging menu item"); @@ -431,29 +361,16 @@ public boolean onPrepareOptionsMenu (Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.optionMouseEvents: - enableMouseEvents = !enableMouseEvents; - Log.d(TAG, "enableMouseEvents set to: " + enableMouseEvents); - break; - case R.id.optionJoystickEvents: - enableJoystickEvents = !enableJoystickEvents; - Log.d(TAG, "enableJoystickEvents set to: " + enableJoystickEvents); - break; - case R.id.optionKeyEvents: - enableKeyEvents = !enableKeyEvents; - Log.d(TAG, "enableKeyEvents set to: " + enableKeyEvents); - break; - case R.id.optionVerboseLogging: - verboseLogging = !verboseLogging; - Log.d(TAG, "verboseLogging set to: " + verboseLogging); - break; - default: - return super.onOptionsItemSelected(item); + int itemId = item.getItemId(); + if (itemId == R.id.optionVerboseLogging) { + verboseLogging = !verboseLogging; + Log.d(TAG, "verboseLogging set to: " + verboseLogging); + } else { + return super.onOptionsItemSelected(item); } return true; } -} \ No newline at end of file +} diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java index 918bce4c05..7473e1403b 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java @@ -1,15 +1,14 @@ package org.jmonkeyengine.jme3androidexamples; -import android.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; -import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentTransaction; import com.jme3.system.JmeSystem; -public class TestActivity extends AppCompatActivity { +public class TestActivity extends FragmentActivity { JmeFragment fragment; @Override @@ -31,26 +30,15 @@ protected void onCreate(Bundle savedInstanceState) { args.putString(MainActivity.SELECTED_APP_CLASS, appClass); // Log.d(TestActivity.class.getSimpleName(), "AppClass="+appClass); - boolean mouseEnabled = bundle.getBoolean(MainActivity.ENABLE_MOUSE_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_MOUSE_EVENTS, mouseEnabled); -// Log.d(TestActivity.class.getSimpleName(), "MouseEnabled="+mouseEnabled); - - boolean joystickEnabled = bundle.getBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, joystickEnabled); -// Log.d(TestActivity.class.getSimpleName(), "JoystickEnabled="+joystickEnabled); - - boolean keyEnabled = bundle.getBoolean(MainActivity.ENABLE_KEY_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_KEY_EVENTS, keyEnabled); -// Log.d(TestActivity.class.getSimpleName(), "KeyEnabled="+keyEnabled); - - boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING, true); + boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING, + MainActivity.DEFAULT_VERBOSE_LOGGING); args.putBoolean(MainActivity.VERBOSE_LOGGING, verboseLogging); // Log.d(TestActivity.class.getSimpleName(), "VerboseLogging="+verboseLogging); fragment.setArguments(args); - FragmentTransaction transaction = getFragmentManager().beginTransaction(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back @@ -84,13 +72,11 @@ public boolean onPrepareOptionsMenu (Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.optionToggleKeyboard: - toggleKeyboard(true); -// Log.d(this.getClass().getSimpleName(), "showing soft keyboard"); - break; - default: - return super.onOptionsItemSelected(item); + if (item.getItemId() == R.id.optionToggleKeyboard) { + toggleKeyboard(true); +// Log.d(this.getClass().getSimpleName(), "showing soft keyboard"); + } else { + return super.onOptionsItemSelected(item); } return true; diff --git a/jme3-android-examples/src/main/res/layout-land/test_chooser_layout.xml b/jme3-android-examples/src/main/res/layout-land/test_chooser_layout.xml new file mode 100644 index 0000000000..4285fa3c5f --- /dev/null +++ b/jme3-android-examples/src/main/res/layout-land/test_chooser_layout.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + +