diff --git a/.all-contributorsrc b/.all-contributorsrc index 28d946f30a6..61e7b7ad2b1 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -7,7 +7,7 @@ "docs/pages/pmd/projectdocs/credits.md" ], "imageSize": 100, - "commit": true, + "commit": false, "commitConvention": "none", "contributors": [ { @@ -6749,7 +6749,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/8797018?v=4", "profile": "https://www.stokpop.nl/", "contributions": [ - "code" + "code", + "bug" ] }, { @@ -7308,7 +7309,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/705618?v=4", "profile": "https://erik.thauvin.net/", "contributions": [ - "doc" + "doc", + "bug" ] }, { @@ -7353,7 +7355,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/3007876?v=4", "profile": "https://github.com/marcindabrowski", "contributions": [ - "code" + "code", + "bug" ] }, { @@ -7453,7 +7456,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/16755668?v=4", "profile": "https://github.com/emouty", "contributions": [ - "code" + "code", + "bug" ] }, { @@ -7516,6 +7520,7 @@ "avatar_url": "https://avatars.githubusercontent.com/u/18402464?v=4", "profile": "https://github.com/mitchspano", "contributions": [ + "code", "bug" ] }, @@ -7543,7 +7548,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/633348?v=4", "profile": "https://github.com/cowwoc", "contributions": [ - "bug" + "bug", + "code" ] }, { @@ -7790,7 +7796,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/48957581?v=4", "profile": "https://github.com/lukasgraef", "contributions": [ - "code" + "code", + "bug" ] }, { @@ -7819,6 +7826,644 @@ "contributions": [ "bug" ] + }, + { + "login": "thesunlover", + "name": "Iskren Stanislavov", + "avatar_url": "https://avatars.githubusercontent.com/u/6734600?v=4", + "profile": "https://interop.io/", + "contributions": [ + "bug" + ] + }, + { + "login": "gudzpoz", + "name": "gudzpoz", + "avatar_url": "https://avatars.githubusercontent.com/u/14026120?v=4", + "profile": "https://kyo.iroiro.party/", + "contributions": [ + "bug" + ] + }, + { + "login": "phansys", + "name": "Javier Spagnoletti", + "avatar_url": "https://avatars.githubusercontent.com/u/1231441?v=4", + "profile": "https://github.com/phansys", + "contributions": [ + "bug" + ] + }, + { + "login": "Aryant-Tripathi", + "name": "Aryant Tripathi", + "avatar_url": "https://avatars.githubusercontent.com/u/60316716?v=4", + "profile": "https://github.com/Aryant-Tripathi", + "contributions": [ + "code" + ] + }, + { + "login": "jdupak", + "name": "Jakub Dupak", + "avatar_url": "https://avatars.githubusercontent.com/u/22683640?v=4", + "profile": "https://github.com/jdupak", + "contributions": [ + "code" + ] + }, + { + "login": "chenguangqi", + "name": "天热吃西瓜", + "avatar_url": "https://avatars.githubusercontent.com/u/6231010?v=4", + "profile": "http://chenguangqi.github.io/", + "contributions": [ + "bug" + ] + }, + { + "login": "wahajenius", + "name": "Willem A. Hajenius", + "avatar_url": "https://avatars.githubusercontent.com/u/7836322?v=4", + "profile": "https://github.com/wahajenius", + "contributions": [ + "code" + ] + }, + { + "login": "VitaliiIevtushenko", + "name": "Vitalii Yevtushenko", + "avatar_url": "https://avatars.githubusercontent.com/u/11145125?v=4", + "profile": "https://github.com/VitaliiIevtushenko", + "contributions": [ + "bug" + ] + }, + { + "login": "sam-gearset", + "name": "samc-gearset", + "avatar_url": "https://avatars.githubusercontent.com/u/110605614?v=4", + "profile": "https://github.com/sam-gearset", + "contributions": [ + "doc" + ] + }, + { + "login": "kursataktas", + "name": "Kursat Aktas", + "avatar_url": "https://avatars.githubusercontent.com/u/17837825?v=4", + "profile": "https://github.com/kursataktas", + "contributions": [ + "doc" + ] + }, + { + "login": "esc-sbarden", + "name": "Sven Barden", + "avatar_url": "https://avatars.githubusercontent.com/u/108530649?v=4", + "profile": "https://github.com/esc-sbarden", + "contributions": [ + "bug" + ] + }, + { + "login": "caiocarvalhotero", + "name": "caiocarvalhotero", + "avatar_url": "https://avatars.githubusercontent.com/u/143206673?v=4", + "profile": "https://github.com/caiocarvalhotero", + "contributions": [ + "bug" + ] + }, + { + "login": "gbq6", + "name": "Balazs Glatz", + "avatar_url": "https://avatars.githubusercontent.com/u/24852447?v=4", + "profile": "https://github.com/gbq6", + "contributions": [ + "doc" + ] + }, + { + "login": "punkratz312", + "name": "Vincent Potucek", + "avatar_url": "https://avatars.githubusercontent.com/u/8830888?v=4", + "profile": "https://github.com/punkratz312", + "contributions": [ + "code" + ] + }, + { + "login": "manuel-a-romeiro-alb", + "name": "Manuel Romeiro", + "avatar_url": "https://avatars.githubusercontent.com/u/170322479?v=4", + "profile": "https://github.com/manuel-a-romeiro-alb", + "contributions": [ + "bug" + ] + }, + { + "login": "mladjan-gadzic", + "name": "Mladjan Gadzic", + "avatar_url": "https://avatars.githubusercontent.com/u/30688679?v=4", + "profile": "https://github.com/mladjan-gadzic", + "contributions": [ + "bug" + ] + }, + { + "login": "slovdahl", + "name": "Sebastian Lövdahl", + "avatar_url": "https://avatars.githubusercontent.com/u/1417619?v=4", + "profile": "https://github.com/slovdahl", + "contributions": [ + "bug" + ] + }, + { + "login": "Ferada", + "name": "Olof-Joachim Frahm (欧雅福)", + "avatar_url": "https://avatars.githubusercontent.com/u/13713?v=4", + "profile": "https://macrolet.net/", + "contributions": [ + "bug" + ] + }, + { + "login": "toKrause", + "name": "Torsten Krause", + "avatar_url": "https://avatars.githubusercontent.com/u/6521030?v=4", + "profile": "https://github.com/toKrause", + "contributions": [ + "bug" + ] + }, + { + "login": "dymul", + "name": "Krzysztof Dymek", + "avatar_url": "https://avatars.githubusercontent.com/u/4218370?v=4", + "profile": "https://github.com/dymul", + "contributions": [ + "bug" + ] + }, + { + "login": "Wolf2323", + "name": "Wolf2323", + "avatar_url": "https://avatars.githubusercontent.com/u/4036106?v=4", + "profile": "https://github.com/Wolf2323", + "contributions": [ + "bug" + ] + }, + { + "login": "pankratz76", + "name": "pankratz76", + "avatar_url": "https://avatars.githubusercontent.com/u/8830888?v=4", + "profile": "https://github.com/pankratz76", + "contributions": [ + "bug" + ] + }, + { + "login": "Miuler", + "name": "Hector Miuler Malpica Gallegos", + "avatar_url": "https://avatars.githubusercontent.com/u/9974?v=4", + "profile": "https://miuler.com/", + "contributions": [ + "bug" + ] + }, + { + "login": "fesse", + "name": "Mathias Lagerwall", + "avatar_url": "https://avatars.githubusercontent.com/u/668267?v=4", + "profile": "https://github.com/fesse", + "contributions": [ + "bug" + ] + }, + { + "login": "jetmore", + "name": "John Jetmore", + "avatar_url": "https://avatars.githubusercontent.com/u/978659?v=4", + "profile": "https://jetmore.org/john/", + "contributions": [ + "doc" + ] + }, + { + "login": "julees7", + "name": "julees7", + "avatar_url": "https://avatars.githubusercontent.com/u/98476761?v=4", + "profile": "https://github.com/julees7", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "yarlpavaworkday", + "name": "yarlpavaworkday", + "avatar_url": "https://avatars.githubusercontent.com/u/73499430?v=4", + "profile": "https://github.com/yarlpavaworkday", + "contributions": [ + "bug" + ] + }, + { + "login": "dwgrth", + "name": "Douglas Griffith", + "avatar_url": "https://avatars.githubusercontent.com/u/37941350?v=4", + "profile": "https://github.com/dwgrth", + "contributions": [ + "doc" + ] + }, + { + "login": "MiladSadinam", + "name": "MiladSadinam", + "avatar_url": "https://avatars.githubusercontent.com/u/8689490?v=4", + "profile": "https://github.com/MiladSadinam", + "contributions": [ + "bug" + ] + }, + { + "login": "Frederick888", + "name": "Frederick Zhang", + "avatar_url": "https://avatars.githubusercontent.com/u/4507647?v=4", + "profile": "https://onee3.org/", + "contributions": [ + "bug" + ] + }, + { + "login": "elharo", + "name": "Elliotte Rusty Harold", + "avatar_url": "https://avatars.githubusercontent.com/u/1005544?v=4", + "profile": "https://www.elharo.com/blog/", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "ax-lothas", + "name": "lothas", + "avatar_url": "https://avatars.githubusercontent.com/u/98159917?v=4", + "profile": "https://github.com/ax-lothas", + "contributions": [ + "bug" + ] + }, + { + "login": "Daniel-Ventura-25", + "name": "Daniel Ventura", + "avatar_url": "https://avatars.githubusercontent.com/u/194499410?v=4", + "profile": "https://github.com/Daniel-Ventura-25", + "contributions": [ + "bug" + ] + }, + { + "login": "Ledmington", + "name": "Filippo Barbari", + "avatar_url": "https://avatars.githubusercontent.com/u/68538713?v=4", + "profile": "https://github.com/Ledmington", + "contributions": [ + "bug" + ] + }, + { + "login": "Pankraz76", + "name": "Pankraz76", + "avatar_url": "https://avatars.githubusercontent.com/u/8830888?v=4", + "profile": "https://github.com/Pankraz76", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "jongpie", + "name": "Jonathan Gillespie", + "avatar_url": "https://avatars.githubusercontent.com/u/1267157?v=4", + "profile": "https://linkedin.com/in/jongpie", + "contributions": [ + "financial" + ] + }, + { + "login": "cybozu", + "name": "Cybozu", + "avatar_url": "https://avatars.githubusercontent.com/u/2433152?v=4", + "profile": "https://www.kintone.com/", + "contributions": [ + "financial" + ] + }, + { + "login": "pkernevez", + "name": "Kernevez", + "avatar_url": "https://avatars.githubusercontent.com/u/338699?v=4", + "profile": "https://github.com/pkernevez", + "contributions": [ + "bug" + ] + }, + { + "login": "UncleOwen", + "name": "UncleOwen", + "avatar_url": "https://avatars.githubusercontent.com/u/15789140?v=4", + "profile": "https://github.com/UncleOwen", + "contributions": [ + "code", + "bug", + "doc" + ] + }, + { + "login": "zbynek", + "name": "Zbynek Konecny", + "avatar_url": "https://avatars.githubusercontent.com/u/1105305?v=4", + "profile": "http://www.geogebra.org/u/zbynek", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "mrclmh", + "name": "mrclmh", + "avatar_url": "https://avatars.githubusercontent.com/u/2975481?v=4", + "profile": "https://github.com/mrclmh", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "judepereira", + "name": "Jude Pereira", + "avatar_url": "https://avatars.githubusercontent.com/u/747087?v=4", + "profile": "https://judepereira.com/", + "contributions": [ + "code" + ] + }, + { + "login": "csrma", + "name": "csrma", + "avatar_url": "https://avatars.githubusercontent.com/u/213656372?v=4", + "profile": "https://github.com/csrma", + "contributions": [ + "bug" + ] + }, + { + "login": "stevenschlansker", + "name": "Steven Schlansker", + "avatar_url": "https://avatars.githubusercontent.com/u/129097?v=4", + "profile": "https://github.com/stevenschlansker", + "contributions": [ + "bug" + ] + }, + { + "login": "StevanWhite", + "name": "Steve White", + "avatar_url": "https://avatars.githubusercontent.com/u/1576377?v=4", + "profile": "https://github.com/StevanWhite", + "contributions": [ + "bug" + ] + }, + { + "login": "malik-n", + "name": "Malik", + "avatar_url": "https://avatars.githubusercontent.com/u/160581225?v=4", + "profile": "https://github.com/malik-n", + "contributions": [ + "bug" + ] + }, + { + "login": "abobov", + "name": "Anton Bobov", + "avatar_url": "https://avatars.githubusercontent.com/u/162897?v=4", + "profile": "https://github.com/abobov", + "contributions": [ + "code" + ] + }, + { + "login": "AndrewStopchenko-SO", + "name": "AndrewStopchenko-SO", + "avatar_url": "https://avatars.githubusercontent.com/u/63401193?v=4", + "profile": "https://github.com/AndrewStopchenko-SO", + "contributions": [ + "bug" + ] + }, + { + "login": "vzorge", + "name": "Vincent Zorge", + "avatar_url": "https://avatars.githubusercontent.com/u/3940229?v=4", + "profile": "https://nl.linkedin.com/in/vincentzorge", + "contributions": [ + "bug" + ] + }, + { + "login": "Jallibad", + "name": "Jordan Alligood", + "avatar_url": "https://avatars.githubusercontent.com/u/568244?v=4", + "profile": "https://github.com/Jallibad", + "contributions": [ + "bug" + ] + }, + { + "login": "bmeier-pros", + "name": "bmeier-pros", + "avatar_url": "https://avatars.githubusercontent.com/u/141971923?v=4", + "profile": "https://github.com/bmeier-pros", + "contributions": [ + "bug" + ] + }, + { + "login": "delanym", + "name": "Delany", + "avatar_url": "https://avatars.githubusercontent.com/u/5310238?v=4", + "profile": "https://github.com/delanym", + "contributions": [ + "bug" + ] + }, + { + "login": "pkozuchowski", + "name": "Piotr Kożuchowski", + "avatar_url": "https://avatars.githubusercontent.com/u/4470967?v=4", + "profile": "https://github.com/pkozuchowski", + "contributions": [ + "bug" + ] + }, + { + "login": "rubenporras", + "name": "rubenporras", + "avatar_url": "https://avatars.githubusercontent.com/u/43636626?v=4", + "profile": "https://github.com/rubenporras", + "contributions": [ + "bug" + ] + }, + { + "login": "mebigfatguy", + "name": "Dave Brosius", + "avatar_url": "https://avatars.githubusercontent.com/u/170161?v=4", + "profile": "http://www.jroller.com/dbrosius/", + "contributions": [ + "bug" + ] + }, + { + "login": "kdandoy107255", + "name": "kdandoy107255", + "avatar_url": "https://avatars.githubusercontent.com/u/221576972?v=4", + "profile": "https://github.com/kdandoy107255", + "contributions": [ + "bug" + ] + }, + { + "login": "Juneezee", + "name": "Eng Zer Jun", + "avatar_url": "https://avatars.githubusercontent.com/u/20135478?v=4", + "profile": "https://github.com/Juneezee", + "contributions": [ + "bug" + ] + }, + { + "login": "estekhin", + "name": "Oleg Estekhin", + "avatar_url": "https://avatars.githubusercontent.com/u/6337779?v=4", + "profile": "https://github.com/estekhin", + "contributions": [ + "bug" + ] + }, + { + "login": "frankk3", + "name": "frankk3", + "avatar_url": "https://avatars.githubusercontent.com/u/28110843?v=4", + "profile": "https://github.com/frankk3", + "contributions": [ + "bug" + ] + }, + { + "login": "FabioMarangonSMI", + "name": "FabioMarangonSMI", + "avatar_url": "https://avatars.githubusercontent.com/u/74663033?v=4", + "profile": "https://github.com/FabioMarangonSMI", + "contributions": [ + "bug" + ] + }, + { + "login": "Frettman", + "name": "Patrick Schmidt", + "avatar_url": "https://avatars.githubusercontent.com/u/24453813?v=4", + "profile": "https://github.com/Frettman", + "contributions": [ + "bug" + ] + }, + { + "login": "sarser2048", + "name": "sarser.eth", + "avatar_url": "https://avatars.githubusercontent.com/u/120093137?v=4", + "profile": "https://github.com/sarser2048", + "contributions": [ + "bug" + ] + }, + { + "login": "styurin", + "name": "Sergey Tyurin", + "avatar_url": "https://avatars.githubusercontent.com/u/2167790?v=4", + "profile": "https://github.com/styurin", + "contributions": [ + "bug" + ] + }, + { + "login": "gianmarcoschifone", + "name": "Gianmarco", + "avatar_url": "https://avatars.githubusercontent.com/u/133696105?v=4", + "profile": "https://github.com/gianmarcoschifone", + "contributions": [ + "code" + ] + }, + { + "login": "mdhamed238", + "name": "Mohamed Hamed", + "avatar_url": "https://avatars.githubusercontent.com/u/79021260?v=4", + "profile": "https://www.mdhamed.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "metalshark", + "name": "Beech Horn", + "avatar_url": "https://avatars.githubusercontent.com/u/146708?v=4", + "profile": "https://github.com/metalshark", + "contributions": [ + "doc" + ] + }, + { + "login": "kelunik", + "name": "Niklas Keller", + "avatar_url": "https://avatars.githubusercontent.com/u/2743004?v=4", + "profile": "https://blog.kelunik.com/", + "contributions": [ + "bug" + ] + }, + { + "login": "parsam97", + "name": "parsam97", + "avatar_url": "https://avatars.githubusercontent.com/u/32430185?v=4", + "profile": "https://github.com/parsam97", + "contributions": [ + "bug" + ] + }, + { + "login": "thomasleplus", + "name": "Thomas Leplus", + "avatar_url": "https://avatars.githubusercontent.com/u/1929743?v=4", + "profile": "https://www.leplus.org/", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "goto-dev-null", + "name": "Jade", + "avatar_url": "https://avatars.githubusercontent.com/u/2322497?v=4", + "profile": "https://codeberg.org/cat-dev-null", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.ci/README.md b/.ci/README.md deleted file mode 100644 index b2ce6f60b89..00000000000 --- a/.ci/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# PMD CI Scripts - -This folder contains scripts used for CI, that are PMD specific. -It uses the common scripts from [build-tools](https://github.com/pmd/build-tools). - -## .ci/files/public-env.gpg - -This files contains the following environment variables: - -* DANGER_GITHUB_API_TOKEN: Token for danger to add comments to PRs as . - The token needs the scope "public_repo". Note: The default GITHUB_TOKEN can't be used, because - danger runs in pull request builds from fork and the default GITHUB_TOKEN has read-only access there - and can't write comments. Therefore the personal access token of the bot account "pmd-test" is used. - pmd-test has no commit permissions, but can comment on any public repo, including pmd/pmd. -* PMD_CI_CHUNK_TOKEN: Token for uploading reports to chunk.io - -The file is encrypted, so that the tokens are not automatically disabled when github detects them -in clear text. - -**Decrypting**: - - gpg --batch --yes --decrypt --passphrase="GnxdjywUEPveyCD1RLiTd7t8CImnefYr" \ - --output .ci/files/public-env .ci/files/public-env.gpg - -**Encrypting**: - - gpg --batch --symmetric --cipher-algo AES256 \ - --armor --passphrase="GnxdjywUEPveyCD1RLiTd7t8CImnefYr" \ - --output .ci/files/public-env.gpg .ci/files/public-env - -## Local tests with docker - -Using the same docker container as described in [build-env @ build-tools](https://github.com/pmd/build-tools). - -### Testing a push build (snapshot) - -Start docker without binding to local directory, so that we can do a fresh checkout - - $ docker run \ - --interactive \ - --tty \ - --name pmd-build-env_pmd \ - pmd-build-env:latest - - -``` -export LANG=en_US.UTF-8 -export PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/main/scripts - -export PMD_CI_SECRET_PASSPHRASE="xyz" -export PMD_CI_DEBUG=true - -MAIN_BRANCH="main" -eval $(~/create-gh-actions-env.sh push pmd/pmd $MAIN_BRANCH) - -cd /workspaces/pmd -rmdir pmd && mkdir pmd -cd pmd -git init -git remote add origin https://github.com/pmd/pmd -git fetch --no-tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/${MAIN_BRANCH}:refs/remotes/origin/${MAIN_BRANCH} -git checkout --progress --force -B ${MAIN_BRANCH} refs/remotes/origin/${MAIN_BRANCH} - - -f=check-environment.sh; \ - mkdir -p .ci && \ - ( [ -e .ci/$f ] || curl -sSL "${PMD_CI_SCRIPTS_URL}/$f" > ".ci/$f" ) && \ - chmod 755 .ci/$f && \ - .ci/$f - -.ci/build.sh -``` - -### Testing a pull request - -Same as the above, but this line changes: - -``` -eval $(~/create-gh-actions-env.sh pull_request pmd/pmd $MAIN_BRANCH) -``` - -Maybe update `/workspaces/event.json` to fill in a real pull request number, so that -danger can comment the correct PR. - -And the checkout must be different. Example for PR 3220: - -``` -PMD_CI_PULL_REQUEST_NUMBER=3220 -cd /workspace/pmd -rmdir pmd && mkdir pmd -cd pmd -git init -git remote add origin https://github.com/pmd/pmd -git fetch --no-tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/pull/${PMD_CI_PULL_REQUEST_NUMBER}/merge:refs/remotes/pull/${PMD_CI_PULL_REQUEST_NUMBER}/merge -git checkout --progress --force refs/remotes/pull/${PMD_CI_PULL_REQUEST_NUMBER}/merge -``` - -### Forked build - -A build executing on a forked repository. - -``` -$(~/create-gh-actions-env.sh push adangel/pmd $MAIN_BRANCH) -``` - - -### Performing a release (push) build - -``` -export LANG=en_US.UTF-8 -export PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/main/scripts - -export PMD_CI_SECRET_PASSPHRASE="xyz" -export PMD_CI_DEBUG=true - -TAG_NAME=pmd_releases/6.33.0 - -eval $(~/create-gh-actions-env.sh push pmd/pmd refs/tags/$TAG_NAME) - -cd /workspaces/pmd -rmdir pmd && mkdir pmd -cd pmd -git init -git remote add origin https://github.com/pmd/pmd -git fetch --no-tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/tags/$TAG_NAME:refs/tags/$TAG_NAME -git checkout --progress --force refs/tags/$TAG_NAME - -f=check-environment.sh; \ - mkdir -p .ci && \ - ( [ -e .ci/$f ] || curl -sSL "${PMD_CI_SCRIPTS_URL}/$f" > ".ci/$f" ) && \ - chmod 755 .ci/$f && \ - .ci/$f - -# -# .ci/build.sh -# -``` - -Calling `.ci/build.sh` directly would re-release the tag $TAG_NAME - that's why it is commented out. -All the side-effects of a release would be carried out like creating and publishing a release on github, -uploading the release to sourceforge, uploading the docs to docs.pmd-code.org, uploading a -new baseline for the regression tester and so on. While the release should be reproducible and therefore should -produce exactly the same artifacts, re-uploading artifacts is not desired just for testing. - -Note that maven-central would not be changed, since this is skipped via MAVEN_OPTS: -`MAVEN_OPTS` contains `-DskipRemoteStaging=true`, so that no maven artifacts are deployed -to maven central (this is set by `create-gh-actions-env.sh`). - -So for now in order to test the build script, you need to manually edit the script and comment out the -critical lines... (like publish github releases, uploading files to sourceforge ...). Later a -"dry-run" mode could be added. - -Make sure to cleanup after the test, e.g. discard the draft github release. - -## Workflow git-repo-sync - -Synchronizes the github git repository pmd/pmd on every push to sourceforge. diff --git a/.ci/build.sh b/.ci/build.sh deleted file mode 100755 index cd5200588a6..00000000000 --- a/.ci/build.sh +++ /dev/null @@ -1,371 +0,0 @@ -#!/usr/bin/env bash - -# Exit this script immediately if a command/function exits with a non-zero status. -set -e - -SCRIPT_INCLUDES="log.bash utils.bash setup-secrets.bash openjdk.bash maven.bash github-releases-api.bash - sourceforge-api.bash pmd-doc.inc pmd-code-api.inc regression-tester.inc" -# shellcheck source=inc/fetch_ci_scripts.bash -source "$(dirname "$0")/inc/fetch_ci_scripts.bash" && fetch_ci_scripts - -function build() { - pmd_ci_log_group_start "Prepare Java 8+11+17+21, Bundler" - pmd_ci_openjdk_install_adoptium 11 - pmd_ci_openjdk_setdefault 11 - PMD_MAVEN_EXTRA_OPTS=() - if [ "$(pmd_ci_utils_get_os)" = "linux" ]; then - pmd_ci_log_info "Install openjdk8 for integration tests and pmd-regression-tests" - pmd_ci_openjdk_install_adoptium 8 - pmd_ci_log_info "Install openjdk17 for integration tests and pmd-regression-tests" - pmd_ci_openjdk_install_adoptium 17 - pmd_ci_log_info "Install openjdk21 for integration tests and pmd-regression-tests" - pmd_ci_openjdk_install_adoptium 21 - PMD_MAVEN_EXTRA_OPTS=( - -Djava8.home="${HOME}/openjdk8" - -Djava17.home="${HOME}/openjdk17" - -Djava21.home="${HOME}/openjdk21" - ) - fi - pmd_ci_build_setup_bundler - pmd_ci_log_group_end - - echo - pmd_ci_maven_display_info_banner - pmd_ci_utils_determine_build_env pmd/pmd - echo - - if pmd_ci_utils_is_fork_or_pull_request; then - pmd_ci_log_group_start "Build with mvnw" - ./mvnw clean install --show-version --errors --batch-mode -Pgenerate-rule-docs "${PMD_MAVEN_EXTRA_OPTS[@]}" - pmd_ci_log_group_end - - # Execute danger and dogfood only for pull requests in our own repository - if [[ "${PMD_CI_IS_FORK}" = "false" && -n "${PMD_CI_PULL_REQUEST_NUMBER}" ]]; then - # Danger is executed only on the linux runner - if [ "$(pmd_ci_utils_get_os)" = "linux" ]; then - pmd_ci_log_group_start "Executing danger" - regression_tester_setup_ci - regression_tester_executeDanger - pmd_ci_log_group_end - - # also run dogfood for PRs (only on linux) - pmd_ci_log_group_start "Executing PMD dogfood test with ${PMD_CI_MAVEN_PROJECT_VERSION}" - pmd_ci_dogfood - pmd_ci_log_group_end - fi - fi - - exit 0 - fi - - # stop early for invalid maven version and branch/tag combination - pmd_ci_maven_verify_version || exit 0 - - # skip tests when doing a release build - this makes the process faster - # it's a manual task now to verify that a release is only started, when the main branch - # was green before. This is usually checked via a local build, see ./do-release.sh - if pmd_ci_maven_isReleaseBuild; then - PMD_MAVEN_EXTRA_OPTS+=(-DskipTests=true) - fi - - # make sure, BUILD_CLI_DIST_ONLY is set to false by default - pmd_ci_log_info "BUILD_CLI_DIST_ONLY=${BUILD_CLI_DIST_ONLY}" - : "${BUILD_CLI_DIST_ONLY:=false}" - pmd_ci_log_info "BUILD_CLI_DIST_ONLY=${BUILD_CLI_DIST_ONLY}" - - if [ "$(pmd_ci_utils_get_os)" != "linux" ]; then - pmd_ci_log_group_start "Build with mvnw verify on $(pmd_ci_utils_get_os)" - if pmd_ci_maven_isReleaseBuild; then - pmd_ci_log_info "This is a release version build..." - # There are two possible (release) builds: - if [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then - # a) everything without pmd-cli and pmd-dist - ./mvnw clean verify -Dskip-cli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - else - # b) only pmd-cli and pmd-dist - # - # In the first stage build (without pmd-cli and pmd-dist), cyclonedx:makeAggregateBom tries to - # fetch the jars of the to-be-released modules, which don't exist yet. This is recorded in *.lastUpdated - # files in the local repo and might end up in the cache, that is used for this 2nd stage build. - # Trying to delete the files now, if they exist. - # Alternatively, we could run maven with flag "-U" to force update all dependencies... - pmd_ci_log_info "Cleanup local maven repo..." - find ~/.m2/repository -wholename "*/net/sourceforge/pmd/*/${PMD_CI_MAVEN_PROJECT_VERSION}/*.lastUpdated" | xargs rm -v - pmd_ci_log_info "Cleanup local maven repo finished." - - ./mvnw clean verify -pl pmd-cli,pmd-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - fi - else - # snapshot build - just verify on the different OS - ./mvnw clean verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - fi - pmd_ci_log_group_end - - pmd_ci_log_info "Stopping build here, because os is not linux" - exit 0 - fi - - # only builds on pmd/pmd on linux continue here - pmd_ci_log_group_start "Setup environment" - pmd_ci_setup_secrets_private_env - pmd_ci_setup_secrets_gpg_key - pmd_ci_setup_secrets_ssh - pmd_ci_maven_setup_settings - pmd_ci_log_group_end - - pmd_ci_log_group_start "Build and Deploy" - pmd_ci_build_run - pmd_ci_deploy_build_artifacts - pmd_ci_log_group_end - - pmd_ci_log_group_start "Build and Upload documentation" - pmd_ci_build_and_upload_doc - pmd_ci_log_group_end - - # release is published only for the case b) pmd-cli/pmd-dist release - if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then - pmd_ci_log_group_start "Publishing Release" - pmd_ci_gh_releases_publishRelease "$GH_RELEASE" - pmd_ci_sourceforge_selectDefault "${PMD_CI_MAVEN_PROJECT_VERSION}" - # reconstruct the SF_BLOG_URL - the news entry has been created as draft when running the - # first release stage - local news_title - news_title="PMD ${PMD_CI_MAVEN_PROJECT_VERSION} ($(date -u +%d-%B-%Y)) released" - news_title="${news_title// /-}" - news_title="${news_title//\./}" - news_title="${news_title//\(/}" - news_title="${news_title//\)/}" - news_title="${news_title,,}" # convert to lowercase - SF_BLOG_URL="https://sourceforge.net/rest/p/pmd/news/$(date -u +%Y)/$(date -u +%m)/${news_title// /_}" - pmd_ci_sourceforge_publishBlogPost "$SF_BLOG_URL" - pmd_ci_log_group_end - fi - - # create a baseline for snapshot builds (when pmd-dist is built) - # or for release builds for case b) when pmd-cli/pmd-dist is released - if pmd_ci_maven_isSnapshotBuild || [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then - pmd_ci_log_group_start "Creating new baseline for regression tester" - regression_tester_setup_ci - regression_tester_uploadBaseline - pmd_ci_log_group_end - fi - - # - # everything from here runs only on snapshots, not on release builds - # - if pmd_ci_maven_isSnapshotBuild; then - pmd_ci_log_group_start "Executing PMD dogfood test with ${PMD_CI_MAVEN_PROJECT_VERSION}" - pmd_ci_dogfood - pmd_ci_log_group_end - - pmd_ci_log_group_start "Executing build with sonar" - pmd_ci_openjdk_setdefault 17 - # Note: Sonar also needs GITHUB_TOKEN (!) - ./mvnw \ - --show-version --errors --batch-mode \ - clean package \ - sonar:sonar -Dsonar.token="${SONAR_TOKEN}" -Psonar,fastSkip - pmd_ci_log_success "New sonar results: https://sonarcloud.io/dashboard?id=net.sourceforge.pmd%3Apmd" - pmd_ci_log_group_end - - pmd_ci_log_group_start "Executing build with coveralls" - pmd_ci_openjdk_setdefault 11 - export CI_NAME="github actions" - export CI_BUILD_URL="${PMD_CI_JOB_URL}" - export CI_BRANCH="${PMD_CI_BRANCH}" - # first create jacoco report - ./mvnw \ - --show-version --errors --batch-mode \ - clean package \ - jacoco:report -Pcoveralls,fastSkip - - # workaround, maybe https://github.com/jacoco/jacoco/issues/654 - # we use $ as a regex separator, not a shell variable, so no expansion - # shellcheck disable=SC2016 - sed -i 's$Comparisons.kt$ApexTreeBuilder.kt$g' pmd-apex/target/site/jacoco/jacoco.xml - - # then create and send coveralls report - # note: generate-sources is needed, so that antlr4 generated directories are on the compileSourceRoots - ./mvnw \ - --show-version --errors --batch-mode \ - generate-sources \ - coveralls:report -DrepoToken="${COVERALLS_REPO_TOKEN}" -Pcoveralls,fastSkip - - pmd_ci_log_success "New coveralls result: https://coveralls.io/github/pmd/pmd" - pmd_ci_log_group_end - fi -} - - -# -# Bundler should be already installed - it should be included in the ruby distribution. -# Bundler is needed for doc generation and regression tester -# -function pmd_ci_build_setup_bundler() { - pmd_ci_log_info "Checking bundler version..." - bundle --version -} - -# -# Performs the actual build. -# Deploys the artifacts to maven central. -# Also generates rule documentation. -# -function pmd_ci_build_run() { - local mvn_profiles="sign,generate-rule-docs" - - if pmd_ci_maven_isReleaseBuild; then - pmd_ci_log_info "This is a release build" - mvn_profiles="${mvn_profiles},pmd-release" - - # There are two possible (release) builds: - if [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then - # a) everything without pmd-cli and pmd-dist - ./mvnw clean deploy -P"${mvn_profiles}" -Dskip-cli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - else - # b) only pmd-cli and pmd-dist - ./mvnw clean deploy -P"${mvn_profiles}" -pl pmd-cli,pmd-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - fi - else - pmd_ci_log_info "This is a snapshot build" - ./mvnw clean deploy -P"${mvn_profiles}" --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" - fi - -} - -# -# Deploys the binary distribution -# -function pmd_ci_deploy_build_artifacts() { - if pmd_ci_maven_isSnapshotBuild; then - # Deploy to sourceforge files https://sourceforge.net/projects/pmd/files/pmd/ - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-bin.zip" - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-src.zip" - # Deploy SBOM - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.xml" - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.json" - fi - - # release build case a): everything without pmd-cli and pmd-dist is released - if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then - # create a draft github release - pmd_ci_gh_releases_createDraftRelease "${PMD_CI_TAG}" "$(git rev-list -n 1 "${PMD_CI_TAG}")" - GH_RELEASE="$RESULT" - fi - - # release build case b): only pmd-cli and pmd-dist are released - if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then - # Deploy to sourceforge files https://sourceforge.net/projects/pmd/files/pmd/ - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-bin.zip" - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-src.zip" - # Deploy SBOM - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.xml" - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.json" - - # draft release has already been created - pmd_ci_gh_releases_getLatestDraftRelease - GH_RELEASE="$RESULT" - - # Deploy to github releases - pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-bin.zip" - pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-src.zip" - # Deploy SBOM - pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.xml" - pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.json" - fi -} - -# -# Builds and uploads the documentation site -# Renders release notes and uploads them as ReadMe.md to sourceforge -# -function pmd_ci_build_and_upload_doc() { - # generate the site only for snapshots from main and for release builds for case a) (everything without cli/dist) - # to avoid building it twice during a release... - if pmd_ci_maven_isSnapshotBuild && [ "${PMD_CI_BRANCH}" = "main" ] || [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then - pmd_doc_generate_jekyll_site - pmd_doc_create_archive - - pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip" - - if pmd_ci_maven_isReleaseBuild; then - pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip" - fi - - # Deploy doc to https://docs.pmd-code.org/pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}/ - pmd_code_uploadDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip" - # Deploy javadoc to https://docs.pmd-code.org/apidocs/*/${PMD_CI_MAVEN_PROJECT_VERSION}/ - pmd_code_uploadJavadoc "${PMD_CI_MAVEN_PROJECT_VERSION}" "$(pwd)" - - # render release notes - # updating github release text - rm -f .bundle/config - bundle config set --local path vendor/bundle - bundle config set --local with release_notes_preprocessing - bundle install - # renders, and skips the first 6 lines - the Jekyll front-matter - local rendered_release_notes - rendered_release_notes=$(bundle exec docs/render_release_notes.rb docs/pages/release_notes.md | tail -n +6) - local release_name - release_name="PMD ${PMD_CI_MAVEN_PROJECT_VERSION} ($(date -u +%d-%B-%Y))" - # Upload to https://sourceforge.net/projects/pmd/files/pmd/${PMD_CI_MAVEN_PROJECT_VERSION}/ReadMe.md - pmd_ci_sourceforge_uploadReleaseNotes "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "${rendered_release_notes}" - fi - - if pmd_ci_maven_isSnapshotBuild && [ "${PMD_CI_BRANCH}" = "main" ]; then - # only for snapshot builds from branch main: https://docs.pmd-code.org/snapshot -> pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION} - pmd_code_createSymlink "${PMD_CI_MAVEN_PROJECT_VERSION}" "snapshot" - - # update github pages https://pmd.github.io/pmd/ - pmd_doc_publish_to_github_pages - # rsync site to https://pmd.sourceforge.io/snapshot - pmd_ci_sourceforge_rsyncSnapshotDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}" "snapshot" - fi - - if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then - # documentation is already uploaded to https://docs.pmd-code.org/pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION} - # we only need to setup symlinks for the released version - pmd_code_createSymlink "${PMD_CI_MAVEN_PROJECT_VERSION}" "latest" - # remove old doc and point to the new version - pmd_code_removeDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}-SNAPSHOT" - pmd_code_createSymlink "${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}-SNAPSHOT" - # remove old javadoc - pmd_code_removeJavadoc "${PMD_CI_MAVEN_PROJECT_VERSION}-SNAPSHOT" - - # github release only for releases - pmd_ci_gh_releases_updateRelease "$GH_RELEASE" "$release_name" "${rendered_release_notes}" - - local rendered_release_notes_with_links - rendered_release_notes_with_links=" -* Downloads: https://github.com/pmd/pmd/releases/tag/pmd_releases%2F${PMD_CI_MAVEN_PROJECT_VERSION} -* Documentation: https://docs.pmd-code.org/pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}/ - -${rendered_release_notes}" - pmd_ci_sourceforge_createDraftBlogPost "${release_name} released" "${rendered_release_notes_with_links}" "pmd,release" - SF_BLOG_URL="${RESULT}" - - # rsync site to https://pmd.sourceforge.io/pmd-${PMD_CI_MAVEN_PROJECT_VERSION} - pmd_ci_sourceforge_rsyncSnapshotDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-${PMD_CI_MAVEN_PROJECT_VERSION}" - fi -} - -# -# Runs the dogfood ruleset with the currently built pmd against itself -# -function pmd_ci_dogfood() { - local mpmdVersion=() - ./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION%-SNAPSHOT}-dogfood-SNAPSHOT" -DgenerateBackupPoms=false - sed -i 's/[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}.*<\/version>\( *\)/'"${PMD_CI_MAVEN_PROJECT_VERSION}"'<\/version>\1/' pom.xml - ./mvnw verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" \ - "${mpmdVersion[@]}" \ - -DskipTests \ - -Dmaven.javadoc.skip=true \ - -Dmaven.source.skip=true \ - -Dcheckstyle.skip=true - ./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}" -DgenerateBackupPoms=false - git checkout -- pom.xml -} - -build - -exit 0 diff --git a/.ci/files/Gemfile b/.ci/files/Gemfile new file mode 100644 index 00000000000..273eb271404 --- /dev/null +++ b/.ci/files/Gemfile @@ -0,0 +1,12 @@ +source 'https://rubygems.org/' + +# bleeding edge from git +#gem 'pmdtester', :git => 'https://github.com/pmd/pmd-regression-tester.git', branch: 'main' + +gem 'pmdtester' + +gem 'logger' +gem 'ostruct' +gem 'base64' + +# vim: syntax=ruby diff --git a/.ci/files/Gemfile.lock b/.ci/files/Gemfile.lock new file mode 100644 index 00000000000..c2a1bfe6f08 --- /dev/null +++ b/.ci/files/Gemfile.lock @@ -0,0 +1,50 @@ +GEM + remote: https://rubygems.org/ + specs: + base64 (0.3.0) + bigdecimal (3.2.3) + concurrent-ruby (1.3.5) + differ (0.1.2) + et-orbi (1.3.0) + tzinfo + fugit (1.11.2) + et-orbi (~> 1, >= 1.2.11) + raabro (~> 1.4) + liquid (5.8.7) + bigdecimal + strscan (>= 3.1.1) + logger (1.7.0) + logger-colors (1.0.0) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + ostruct (0.6.3) + pmdtester (1.6.2) + base64 (~> 0.3) + bigdecimal (~> 3.2) + differ (~> 0.1) + liquid (~> 5.8) + logger (~> 1.7) + logger-colors (~> 1.0) + nokogiri (~> 1.18) + rufus-scheduler (~> 3.9) + slop (~> 4.10) + raabro (1.4.0) + racc (1.8.1) + rufus-scheduler (3.9.2) + fugit (~> 1.1, >= 1.11.1) + slop (4.10.1) + strscan (3.1.5) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + base64 + logger + ostruct + pmdtester + +BUNDLED WITH + 2.6.9 diff --git a/.ci/files/all-regression-rules.xml b/.ci/files/all-regression-rules.xml index 95a1423ca09..00555c0d2d4 100644 --- a/.ci/files/all-regression-rules.xml +++ b/.ci/files/all-regression-rules.xml @@ -4,7 +4,7 @@ xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - Every apex and java rule in PMD which is used for the regression tests with pmdtester + Every apex, plsql and java rule in PMD which is used for the regression tests with pmdtester @@ -15,6 +15,15 @@ + + + + + + + + + diff --git a/.ci/files/pmdtester.rb b/.ci/files/pmdtester.rb new file mode 100644 index 00000000000..efcda90ea94 --- /dev/null +++ b/.ci/files/pmdtester.rb @@ -0,0 +1,86 @@ +require 'pmdtester' +require 'time' +require 'logger' +require 'fileutils' +require 'etc' + +@logger = Logger.new(STDOUT) + +def get_args(base_branch, autogen = true, patch_config = './pmd/.ci/files/all-regression-rules.xml') + ['--local-git-repo', './pmd', + '--list-of-project', './pmd/.ci/files/project-list.xml', + '--base-branch', base_branch, + '--patch-branch', 'HEAD', + '--patch-config', patch_config, + '--mode', 'online', + autogen ? '--auto-gen-config' : '--filter-with-patch-config', + '--keep-reports', + '--error-recovery', + '--baseline-download-url', 'https://pmd-code.org/pmd-regression-tester/', + '--threads', Etc.nprocessors.to_s, + # '--debug', + ] +end + +def run_pmdtester + Dir.chdir('..') do + begin + @base_branch = ENV['PMD_CI_BRANCH'] + @logger.info "Run against PR base #{@base_branch}" + @summary = PmdTester::Runner.new(get_args(@base_branch)).run + + if Dir.exist?('target/reports/diff') + message = create_message + conclusion = determine_conclusion + else + message = "No regression tested rules have been changed." + conclusion = "skipped" + end + + rescue StandardError => e + message = "⚠️ Running pmdtester failed, this message is mainly used to remind the maintainers of PMD." + conclusion = "failure" + @logger.error "Running pmdtester failed: #{e.inspect}" + end + + unless Dir.exist?('target/reports/diff') + Dir.mkdir('target/reports/diff') + end + summary_file = 'target/reports/diff/summary.txt' + File.write(summary_file, message) + @logger.info "Wrote summary file #{summary_file}:\n\n#{message}" + conclusion_file = 'target/reports/diff/conclusion.txt' + File.write(conclusion_file, conclusion) + @logger.info "Wrote conclusion file #{conclusion_file}: #{conclusion}" + end +end + +def create_message + "Compared to #{@base_branch}:\n"\ + "This changeset " \ + "changes #{@summary[:violations][:changed]} violations,\n" \ + "introduces #{@summary[:violations][:new]} new violations, " \ + "#{@summary[:errors][:new]} new errors and " \ + "#{@summary[:configerrors][:new]} new configuration errors,\n" \ + "removes #{@summary[:violations][:removed]} violations, "\ + "#{@summary[:errors][:removed]} errors and " \ + "#{@summary[:configerrors][:removed]} configuration errors.\n" +end + +def determine_conclusion + total_changes = @summary[:violations][:changed] + + @summary[:violations][:new] + + @summary[:violations][:removed] + + @summary[:errors][:new] + + @summary[:configerrors][:new] + if total_changes > 0 + "neutral" + else + "success" + end +end + +# Perform regression testing +run_pmdtester + +# vim: syntax=ruby diff --git a/.ci/files/pmdtester_start.sh b/.ci/files/pmdtester_start.sh new file mode 100755 index 00000000000..a94e13719c3 --- /dev/null +++ b/.ci/files/pmdtester_start.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -e + +case "${GITHUB_EVENT_NAME}" in + pull_request) + echo "Detected pull request..." + PMD_REGRESSION_TESTER_BASE_BRANCH="${GITHUB_BASE_REF}" + PMD_REGRESSION_TESTER_HEAD_BRANCH="pull/${GITHUB_REF_NAME%/merge}/head" + PMD_REGRESSION_TESTER_2ND_REF="${PMD_REGRESSION_TESTER_HEAD_BRANCH}_HEAD_FETCH" + ;; + push) + echo "Detected push onto branch ${GITHUB_REF_NAME}...." + case "${GITHUB_REF_NAME}" in + main) + PMD_REGRESSION_TESTER_BASE_BRANCH=main + PMD_REGRESSION_TESTER_HEAD_BRANCH=main + PMD_REGRESSION_TESTER_2ND_REF="${PMD_REGRESSION_TESTER_PUSH_BEFORE}" + ;; + *) + PMD_REGRESSION_TESTER_BASE_BRANCH=main + PMD_REGRESSION_TESTER_HEAD_BRANCH="${GITHUB_REF_NAME}" + PMD_REGRESSION_TESTER_2ND_REF="${PMD_REGRESSION_TESTER_HEAD_BRANCH}_HEAD_FETCH" + ;; + esac + ;; + *) + echo "Unsupported event: ${GITHUB_EVENT_NAME}" + exit 1 +esac + + +echo "PMD_REGRESSION_TESTER_BASE_BRANCH=${PMD_REGRESSION_TESTER_BASE_BRANCH}" +echo "PMD_REGRESSION_TESTER_HEAD_BRANCH=${PMD_REGRESSION_TESTER_HEAD_BRANCH}" +echo "PMD_REGRESSION_TESTER_2ND_REF=${PMD_REGRESSION_TESTER_2ND_REF}" + +if [ "${PMD_REGRESSION_TESTER_BASE_BRANCH}" != "main" ]; then + echo "Only main is supported as base branch, and not: ${PMD_REGRESSION_TESTER_BASE_BRANCH}" + exit 1 +fi + +if [ "${PMD_REGRESSION_TESTER_2ND_REF}" == "0000000000000000000000000000000000000000" ]; then + echo "No changes has been pushed." + # create files that are added to artifact "pmd-regression-tester" + mkdir -p ../target/reports/diff + echo -n "No regression tested rules have been changed." > ../target/reports/diff/summary.txt + echo -n "skipped" > ../target/reports/diff/conclusion.txt + exit 0 +fi + +echo "::group::Fetching additional commits" +# actions/checkout (git clone) initially only fetched with depth 2. Regression tester +# needs more history, so we'll fetch more here +# we also create local branches, so that we can use them in "merge-base" calls +# the local branches are: +# - ${PMD_REGRESSION_TESTER_BASE_BRANCH}_BASE_FETCH +# - ${PMD_REGRESSION_TESTER_HEAD_BRANCH}_HEAD_FETCH + +# For release builds, HEAD points to a tag, which is not a normal reference +head_references_tag="" +if git show-ref -q --verify "refs/tags/${PMD_REGRESSION_TESTER_HEAD_BRANCH}" 2>/dev/null; then + head_references_tag="refs/tags/" +fi + +echo "Fetching 25 commits for ${PMD_REGRESSION_TESTER_BASE_BRANCH} and ${PMD_REGRESSION_TESTER_HEAD_BRANCH}" +git fetch --no-tags --depth=25 origin \ + "${PMD_REGRESSION_TESTER_BASE_BRANCH}:${PMD_REGRESSION_TESTER_BASE_BRANCH}_BASE_FETCH" \ + "${head_references_tag}${PMD_REGRESSION_TESTER_HEAD_BRANCH}:${head_references_tag}${PMD_REGRESSION_TESTER_HEAD_BRANCH}_HEAD_FETCH" + +# if the PR/branch is older, base might have advanced more than 25 commits... fetch more, up to 150 +# until we find a merge base, so that we are sure, regression tester can find all the changed files on +# the branch/pull request. +for i in $(seq 1 3); do + if [ -z "$( git merge-base "${PMD_REGRESSION_TESTER_BASE_BRANCH}_BASE_FETCH" "${PMD_REGRESSION_TESTER_2ND_REF}" )" ]; then + echo "No merge-base yet - fetching more commits... (try $i)" + git fetch --no-tags --deepen=50 origin \ + "${PMD_REGRESSION_TESTER_BASE_BRANCH}:${PMD_REGRESSION_TESTER_BASE_BRANCH}_BASE_FETCH" \ + "${head_references_tag}${PMD_REGRESSION_TESTER_HEAD_BRANCH}:${head_references_tag}${PMD_REGRESSION_TESTER_HEAD_BRANCH}_HEAD_FETCH" + fi +done +merge_base="$( git merge-base "${PMD_REGRESSION_TESTER_BASE_BRANCH}_BASE_FETCH" "${PMD_REGRESSION_TESTER_2ND_REF}" )" +echo "Found merge base: ${merge_base}" +if [ "$(git symbolic-ref HEAD 2>/dev/null)" = "refs/heads/main" ]; then + # rename main branch to free up the name "main" + git branch -m "original_main" +fi +git branch "main" "${merge_base}" +echo "::endgroup::" + +export PMD_CI_BRANCH="main" +echo "::group::Running pmdtester against base branch ${PMD_CI_BRANCH}" +if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then + export PMD_CI_PULL_REQUEST_NUMBER="${GITHUB_REF_NAME%/merge}" +fi +bundle exec ruby .ci/files/pmdtester.rb +echo "::endgroup::" diff --git a/.ci/files/project-list.xml b/.ci/files/project-list.xml index 50893c2df02..ffb92e23aed 100644 --- a/.ci/files/project-list.xml +++ b/.ci/files/project-list.xml @@ -21,14 +21,14 @@ fi set -e -# Make sure to use java11. This is already installed by build.sh +# Make sure to use java11. This is already installed by build.yml/publish-snapshot.yml/publish-release.yml export JAVA_HOME=${HOME}/openjdk11 export PATH=$JAVA_HOME/bin:$PATH mvn test-compile -B mvn dependency:build-classpath -DincludeScope=test -Dmdep.outputFile=classpath.txt -B ]]> - echo -n "$(pwd)/target/classes:$(pwd)/target/test-classes:"; cat classpath.txt + echo -n "${HOME}/openjdk11/lib/jrt-fs.jar:$(pwd)/target/classes:$(pwd)/target/test-classes:"; cat classpath.txt @@ -38,6 +38,7 @@ mvn dependency:build-classpath -DincludeScope=test -Dmdep.outputFile=classpath.t v5.3.13 .*/build/generated-sources/.* + .*/build/resources/.* classpath.txt ]]> - cat classpath.txt + echo -n "${HOME}/openjdk11/lib/jrt-fs.jar:"; cat classpath.txt @@ -165,6 +166,7 @@ EOF https://github.com/openjdk/jdk jdk-11+28 src/java.base + echo -n "${HOME}/openjdk11/lib/jrt-fs.jar" @@ -196,4 +198,11 @@ EOF main realpath java-regression-tests-*.jar + + + OracleDBUtils + git + https://github.com/Qualtagh/OracleDBUtils + 0513fe6b053b31e6c09ac6f86eb2064733ecf32d + diff --git a/.ci/files/typos.toml b/.ci/files/typos.toml new file mode 100644 index 00000000000..651f8e45fbd --- /dev/null +++ b/.ci/files/typos.toml @@ -0,0 +1,81 @@ +# +# Configuration for [typos](https://github.com/crate-ci/typos) - source spell checker +# +# To run the tool locally: +# +# TYPOS_VERSION="v1.42.1" +# wget https://github.com/crate-ci/typos/releases/download/${TYPOS_VERSION}/typos-${TYPOS_VERSION}-x86_64-unknown-linux-musl.tar.gz +# tar -x -v -f typos-${TYPOS_VERSION}-x86_64-unknown-linux-musl.tar.gz ./typos +# ./typos -c .ci/files/typos.toml --format brief . +# + +[files] +extend-exclude = [ + "*.min.js", + "*.min.js.map", + "*.min.css", + "*.svg", + "sample.txt", + "sample.for", + "release_notes_old.md", + "big_sample.txt", + "templateStrings.txt", + "Swift4.2.txt", + "Issue628.txt", + "sample-matlab.txt", + "sample_python.txt", + "btrfs.txt" +] + +[default.extend-words] +# public API +verbatium="verbatium" +# Apex terminology +Createable = "Createable" +# lowercase for TestNG +# +# TSQL token name +iif="iif" +# Unicode range +nd = "nd" +# part of PROCEDURE_SCHEM +schem="schem" +# Short strings used as identifiers +typ = "typ" +ba = "ba" +fo = "fo" +parm = "parm" +strat = "strat" +ois = "ois" +parms = "parms" +opps = "opps" + +[default.extend-identifiers] +# public APIs +isWilcardParameterized="isWilcardParameterized" +checkSelfDescendantAbreviation="checkSelfDescendantAbreviation" +ColonSubsript="ColonSubsript" +XMLTableColum="XMLTableColum" +ASTXMLTableColum="ASTXMLTableColum" +# renamed but mentioned in release_notes_old.md / category/ecmascript/errorprone.xml +InnaccurateNumericLiteral="InnaccurateNumericLiteral" +# removed but mentioned in credits.md +AvoidConcateningNonLiteralsInStringBuffer="AvoidConcateningNonLiteralsInStringBuffer" +# removed method, mentioned in release_notes_old.md +isSupressed="isSupressed" +# fingerprint +9CAF="9CAF" +# used in BSD license of Lua parser +NAMEs="NAMEs" +# used in parser +IMPLication="IMPLication" +#names in credits/license headers +McCann="McCann" +TehBakker="TehBakker" +Stach="Stach" +Loder="Loder" +Hendler="Hendler" +Ure="Ure" + +[default] +extend-ignore-re = ["pn\\\\3\\\\2", "thr\\(ough", "org.testng" ] diff --git a/.ci/git-repo-sync.sh b/.ci/git-repo-sync.sh deleted file mode 100755 index af0635d9baa..00000000000 --- a/.ci/git-repo-sync.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Exit this script immediately if a command/function exits with a non-zero status. -set -e - -SCRIPT_INCLUDES="log.bash utils.bash setup-secrets.bash" -# shellcheck source=inc/fetch_ci_scripts.bash -source "$(dirname "$0")/inc/fetch_ci_scripts.bash" && fetch_ci_scripts - -function git_repo_sync() { - echo - pmd_ci_utils_determine_build_env pmd/pmd - echo - - if pmd_ci_utils_is_fork_or_pull_request; then - pmd_ci_log_error "This should not run on forked repositories or pull requests" - exit 0 - fi - - # only builds on pmd/pmd continue here - pmd_ci_log_group_start "Setup environment" - pmd_ci_setup_secrets_private_env - pmd_ci_setup_secrets_gpg_key - pmd_ci_setup_secrets_ssh - pmd_ci_log_group_end - - pmd_ci_log_group_start "Git Sync" - git remote add pmd-sf "${PMD_SF_USER}@git.code.sf.net:/p/pmd/code" - if [ -n "${PMD_CI_BRANCH}" ]; then - retry 5 git push pmd-sf "${PMD_CI_BRANCH}:${PMD_CI_BRANCH}" - pmd_ci_log_success "Successfully pushed ${PMD_CI_BRANCH} to sourceforge" - elif [ -n "${PMD_CI_TAG}" ]; then - git push pmd-sf tag "${PMD_CI_TAG}" - pmd_ci_log_success "Successfully pushed tag ${PMD_CI_TAG} to sourceforge" - else - pmd_ci_log_error "Don't know what to do: neither PMD_CI_BRANCH nor PMD_CI_TAG is set" - exit 1 - fi - pmd_ci_log_group_end -} - - -# -# From: https://gist.github.com/sj26/88e1c6584397bb7c13bd11108a579746 -# -# Retry a command up to a specific number of times until it exits successfully, -# with exponential back off. -# -# $ retry 5 echo Hello -# Hello -# -# $ retry 5 false -# Retry 1/5 exited 1, retrying in 1 seconds... -# Retry 2/5 exited 1, retrying in 2 seconds... -# Retry 3/5 exited 1, retrying in 4 seconds... -# Retry 4/5 exited 1, retrying in 8 seconds... -# Retry 5/5 exited 1, no more retries left. -# -function retry { - local retries=$1 - shift - - local count=0 - until "$@"; do - exit=$? - wait=$((2 ** $count)) - count=$(($count + 1)) - if [ $count -lt $retries ]; then - echo "Retry $count/$retries exited $exit, retrying in $wait seconds..." - sleep $wait - else - echo "Retry $count/$retries exited $exit, no more retries left." - return $exit - fi - done - return 0 -} - -git_repo_sync - -exit 0 diff --git a/.ci/inc/fetch_ci_scripts.bash b/.ci/inc/fetch_ci_scripts.bash deleted file mode 100644 index 835fe6782ee..00000000000 --- a/.ci/inc/fetch_ci_scripts.bash +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -function fetch_ci_scripts() { - local inc_dir - local inc_url - inc_dir="$(dirname "$0")/inc" - inc_url="${PMD_CI_SCRIPTS_URL:-https://raw.githubusercontent.com/pmd/build-tools/main/scripts}/inc" - - mkdir -p "${inc_dir}" - - for f in ${SCRIPT_INCLUDES}; do - if [ ! -e "${inc_dir}/$f" ]; then - curl -sSL "${inc_url}/$f" > "${inc_dir}/$f" - fi - [ "$PMD_CI_DEBUG" = "true" ] && echo "loading ${inc_dir}/$f in ${MODULE:-$0}" - # shellcheck source=/dev/null - source "${inc_dir}/$f" || exit 1 - done -} diff --git a/.ci/inc/pmd-code-api.inc b/.ci/inc/pmd-code-api.inc deleted file mode 100644 index e28eaf5767c..00000000000 --- a/.ci/inc/pmd-code-api.inc +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash - -MODULE="pmd-code-api" -SCRIPT_INCLUDES="log.bash" -# shellcheck source=inc/fetch_ci_scripts.bash -source "$(dirname "$0")/inc/fetch_ci_scripts.bash" && fetch_ci_scripts - -PMD_CODE_SSH_USER=pmd -PMD_CODE_DOCS_PATH=/docs.pmd-code.org/ - -function pmd_code_uploadDocumentation() { - local -r pmdVersion="$1" - local -r filename="$2" - local -r basefilename="$(basename "$filename")" - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion filename=$filename" - - scp "${filename}" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ - ( test -h pmd-doc-${pmdVersion} && rm pmd-doc-${pmdVersion} || true ) && \ - unzip -qo \"${basefilename}\" && \ - rm \"${basefilename}\"" - pmd_ci_log_info "Docs updated: https://docs.pmd-code.org/pmd-doc-${pmdVersion}/" -} - -function pmd_code_removeDocumentation() { - local pmdVersion="$1" - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion" - - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ - rm -rf \"pmd-doc-${pmdVersion}/\"" - pmd_ci_log_info "Removed docs: https://docs.pmd-code.org/pmd-doc-${pmdVersion}/" -} - -function pmd_code_createSymlink() { - local -r pmdVersion="$1" - local -r name="$2" - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion name=$name" - - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ - rm -f \"$name\" && \ - ln -s \"pmd-doc-${pmdVersion}\" \"$name\"" - pmd_ci_log_info "Symlink created: https://docs.pmd-code.org/$name/ -> https://docs.pmd-code.org/pmd-doc-${pmdVersion}/" -} - -function pmd_code_uploadJavadoc() { - local -r pmdVersion="$1" - local -r basePath="$2" - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion basePath=$basePath" - - for i in "${basePath}"/*/target/*-javadoc.jar */*/target/*-javadoc.jar; do - pmd_code_uploadJavadocModule "$pmdVersion" "$i" - done - - # make sure https://docs.pmd-code.org/apidocs/ shows directory index - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}/apidocs\" && \ - echo 'Options +Indexes' > .htaccess" - pmd_ci_log_info "Directory index enabled for https://docs.pmd-code.org/apidocs/" -} - -function pmd_code_uploadJavadocModule() { - local -r pmdVersion="$1" - local -r moduleJavadocJar="$2" - local -r moduleJavadocJarBasename="$(basename "$moduleJavadocJar")" - local -r module=${moduleJavadocJarBasename%%-${pmdVersion}-javadoc.jar} - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion moduleJavadocJar=$moduleJavadocJar module=$module" - - scp "$moduleJavadocJar" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ - mkdir -p \"apidocs/${module}/${pmdVersion}\" && \ - unzip -qo -d \"apidocs/${module}/${pmdVersion}\" \"${moduleJavadocJarBasename}\" && \ - rm \"${moduleJavadocJarBasename}\"" - pmd_ci_log_info "JavaDoc for $module uploaded: https://docs.pmd-code.org/apidocs/${module}/${pmdVersion}/" -} - -function pmd_code_removeJavadoc() { - local -r pmdVersion="$1" - - pmd_ci_log_debug "${FUNCNAME[0]} pmdVersion=$pmdVersion" - # shellcheck disable=SC2029 - ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ - rm -rf apidocs/*/\"${pmdVersion}\"" - pmd_ci_log_info "Removed Javadoc: https://docs.pmd-code.org/apidocs/*/${pmdVersion}/ is gone" -} diff --git a/.ci/inc/pmd-doc.inc b/.ci/inc/pmd-doc.inc deleted file mode 100644 index baba63d43f3..00000000000 --- a/.ci/inc/pmd-doc.inc +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash - -MODULE="pmd-doc" -SCRIPT_INCLUDES="log.bash" -# shellcheck source=inc/fetch_ci_scripts.bash -source "$(dirname "$0")/inc/fetch_ci_scripts.bash" && fetch_ci_scripts - -# Used env vars: -# PMD_CI_JOB_URL -# PMD_CI_PUSH_COMMIT_COMPARE - - -# -# Executes jekyll and generates the documentation -# The documentation will be generated in the directory "docs/_site". -# -function pmd_doc_generate_jekyll_site() { - pushd docs || { echo "Directory 'docs' doesn't exist"; exit 1; } - - echo -e "\n\n" - pmd_ci_log_info "Building documentation using jekyll..." - bundle config set --local path vendor/bundle - bundle install - bundle exec jekyll build - - popd || exit 1 -} - -# -# Creates the pmd-doc.zip archive. It will be placed in "docs/". -# -function pmd_doc_create_archive() { - pushd docs || { echo "Directory 'docs' doesn't exist"; exit 1; } - - echo -e "\n\n" - pmd_ci_log_info "Creating pmd-doc archive..." - mv _site "pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}" - zip -qr "pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip" "pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}/" - pmd_ci_log_success "Successfully created pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip" - - popd || exit 1 -} - -# -# Updates github pages branch "gh-pages" of the main repository, -# so that https://pmd.github.io/pmd/ has the latest (snapshot) content -# -function pmd_doc_publish_to_github_pages() { - echo -e "\n\n" - pmd_ci_log_info "Pushing the new site to github pages..." - git clone --branch gh-pages --depth 1 --origin origin https://github.com/pmd/pmd.git pmd-gh-pages - # clear the files first - rm -rf pmd-gh-pages/* - # copy the new site - cp -a "docs/pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}"/* pmd-gh-pages/ - ( - cd pmd-gh-pages || { echo "Directory 'pmd-gh-pages' doesn't exist"; exit 1; } - git config user.name "PMD CI (pmd-bot)" - git config user.email "pmd-bot@users.noreply.github.com" - git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n "x-access-token:${GITHUB_TOKEN}"|base64)" - git add -A - MSG="Update documentation - -${PMD_CI_JOB_URL} -${PMD_CI_PUSH_COMMIT_COMPARE}" - git commit -q -m "$MSG" - git push origin HEAD:gh-pages - git config --local --unset-all http.https://github.com/.extraheader - pmd_ci_log_success "Successfully pushed site to https://pmd.github.io/pmd/" - ) -} diff --git a/.ci/inc/regression-tester.inc b/.ci/inc/regression-tester.inc deleted file mode 100644 index 5131e3390df..00000000000 --- a/.ci/inc/regression-tester.inc +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash - -MODULE="pmd-doc" -SCRIPT_INCLUDES="log.bash openjdk.bash" -# shellcheck source=inc/fetch_ci_scripts.bash -source "$(dirname "$0")/inc/fetch_ci_scripts.bash" && fetch_ci_scripts - -# -# The functions here require the following environment variables: -# PMD_CI_BRANCH -# -# GITHUB_TOKEN -# PMD_CI_CHUNK_TOKEN - -function regression_tester_setup_ci() { - gpg --batch --yes --decrypt --passphrase="GnxdjywUEPveyCD1RLiTd7t8CImnefYr" \ - --output .ci/files/public-env .ci/files/public-env.gpg - # shellcheck disable=SC1091 - source .ci/files/public-env >/dev/null 2>&1 - rm .ci/files/public-env - - if hash "bundle" 2>/dev/null; then - pmd_ci_log_debug "Bundler is already installed" - bundle --version - else - pmd_ci_log_info "Installing bundler..." - gem install bundler - fi - - rm -f .bundle/config - bundle config set --local path vendor/bundle - bundle config set --local with release_notes_preprocessing - bundle install -} - -# -# Generate a new baseline and upload it to pmd-code.org -# -function regression_tester_uploadBaseline() { - local pmdcodeUrl="https://pmd-code.org/pmd-regression-tester/" - local baseline_branch="${PMD_CI_BRANCH:-$PMD_CI_TAG}" - pmd_ci_log_debug "${FUNCNAME[0]} branch=${baseline_branch}" - - pmd_ci_log_info "Generating and uploading baseline for pmdtester (${baseline_branch})..." - pushd .. - rm -f .bundle/config - bundle config set --local gemfile pmd/Gemfile - bundle exec pmdtester \ - --mode single \ - --local-git-repo ./pmd \ - --patch-branch "${baseline_branch}" \ - --patch-config ./pmd/.ci/files/all-regression-rules.xml \ - --list-of-project ./pmd/.ci/files/project-list.xml --html-flag \ - --threads "$(nproc)" \ - --error-recovery - pushd target/reports || { echo "Directory 'target/reports' doesn't exist"; exit 1; } - BRANCH_FILENAME="${baseline_branch/\//_}" - zip -q -r "${BRANCH_FILENAME}-baseline.zip" "${BRANCH_FILENAME}/" - # ssh-key for pmd-code.org is setup already by pmd_ci_setup_secrets_ssh - scp "${BRANCH_FILENAME}-baseline.zip" pmd@pmd-code.org:/httpdocs/pmd-regression-tester/ - pmd_ci_log_success "Successfully uploaded ${BRANCH_FILENAME}-baseline.zip to ${pmdcodeUrl}" - popd || exit 1 - popd || exit 1 -} - -# -# Execute danger, which executes pmd-regression-tester (via Dangerfile). -# -function regression_tester_executeDanger() { - pmd_ci_log_debug "${FUNCNAME[0]}" - - # git clone initially only fetched with depth 2. Danger and regression tester - # need more history, so we'll fetch more here - # and create local branches as well (${PMD_CI_BRANCH} and pr-fetch) - - pmd_ci_log_info "Fetching 25 commits for ${PMD_CI_BRANCH} and pull/${PMD_CI_PULL_REQUEST_NUMBER}/head" - git fetch --no-tags --depth=25 origin "${PMD_CI_BRANCH}:${PMD_CI_BRANCH}" "pull/${PMD_CI_PULL_REQUEST_NUMBER}/head:pr-fetch" - - # if the PR is older, base might have advanced more than 25 commits... fetch more, up to 150 - for i in $(seq 1 3); do - if [ -z "$( git merge-base "${PMD_CI_BRANCH}" "pr-fetch" )" ]; then - pmd_ci_log_info "No merge-base yet - fetching more commits... (try $i)" - git fetch --no-tags --deepen=50 origin "${PMD_CI_BRANCH}:" "pull/${PMD_CI_PULL_REQUEST_NUMBER}/head:pr-fetch" - fi - done - pmd_ci_log_info "Merge base is: $( git merge-base "${PMD_CI_BRANCH}" "pr-fetch" )" - - pmd_ci_log_info "Running danger on branch ${PMD_CI_BRANCH}" - bundle exec danger --verbose - pmd_ci_log_success "Executed danger successfully" -} diff --git a/.ci/tools/check-all-contributors.sh b/.ci/tools/check-all-contributors.sh new file mode 100755 index 00000000000..8a9029467a7 --- /dev/null +++ b/.ci/tools/check-all-contributors.sh @@ -0,0 +1,148 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "$0 " + exit 1 +fi + +BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +NEW_VERSION="$1" + +CURL_API_HEADER=(--header "X-GitHub-Api-Version: 2022-11-28") +CURL_AUTH_HEADER=() +if [ -z "$GITHUB_TOKEN" ]; then + echo -n "GITHUB_TOKEN=" + IFS= read -r GITHUB_TOKEN + if [ -n "$GITHUB_TOKEN" ]; then + export GITHUB_TOKEN + echo "Using provided GITHUB_TOKEN..." + else + echo "Not using GITHUB_TOKEN" + fi +fi + +if [ -n "$GITHUB_TOKEN" ]; then + echo "Will use env var GITHUB_TOKEN for github REST API" + CURL_AUTH_HEADER=(--header "Authorization: Bearer $GITHUB_TOKEN") +fi + +# determine current milestone +MILESTONE_JSON=$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s "https://api.github.com/repos/pmd/pmd/milestones?state=all&direction=desc&per_page=10&page=1"|jq ".[] | select(.title == \"$NEW_VERSION\")") +#DEBUG ONLY +#MILESTONE_JSON='{"number":80,"closed_issues":40}' +#DEBUG ONLY +MILESTONE=$(echo "$MILESTONE_JSON" | jq .number) + +PAGE="1" +HAS_NEXT="true" +ISSUES_JSON="" +while [ "$HAS_NEXT" = "true" ]; do + echo "Fetching issues for milestone ${MILESTONE} page ${PAGE}..." + URL="https://api.github.com/repos/pmd/pmd/issues?state=closed&sort=created&direction=asc&per_page=30&page=${PAGE}&milestone=${MILESTONE}" + RESPONSE="$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s -w "\nLink: %header{link}" "$URL")" + + #DEBUG ONLY + #echo "$RESPONSE" > issues-response-${PAGE}.txt + #RESPONSE="$(cat issues-response-${PAGE}.txt)" + #DEBUG ONLY + + LINK_HEADER="$(echo "$RESPONSE" | tail -1)" + BODY="$(echo "$RESPONSE" | head -n -1)" + + #DEBUG ONLY + #echo "$BODY" > "issues-response-page-${PAGE}.txt" + #BODY="$(cat "issues-response-page-${PAGE}.txt")" + #DEBUG ONLY + + COMMA="," + if [ "$PAGE" -eq 1 ]; then + COMMA="" + fi + ISSUES_JSON="${ISSUES_JSON}${COMMA}${BODY}" + + if [[ $LINK_HEADER == *"; rel=\"next\""* ]]; then + HAS_NEXT="true" + else + HAS_NEXT="false" + fi + PAGE=$((PAGE + 1)) + + #DEBUG ONLY + #HAS_NEXT="true" + #if [ "$PAGE" -gt 2 ]; then break; fi + #DEBUG ONLY + + # stop after 10 pages + if [ "$PAGE" -gt 10 ]; then + echo + echo + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "!!!!!!!!!!!!!! reached page 10, stopping now !!!!!!!!!!!!!" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo + echo + break; + fi +done + + +ISSUES_JSON="$(echo "[ $ISSUES_JSON ]" | jq 'flatten(1)')" +echo "Found $(echo "$ISSUES_JSON" | jq length) issues/pull requests" +#DEBUG ONLY +# echo "$ISSUES_JSON" > issues-all.txt +#DEBUG ONLY + +# Users which reported a bug, that has been fixed now (assigned to the milestone) +BUG_USERS="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request") | not)) | [ .[].user.login ] | unique')" + +COL_GREEN="\e[32m" +COL_RED="\e[31m" +COL_WHITE="\e[37m" +COL_RESET="\e[0m" + +echo +echo "Checking for bug contributions..." +# for each user, check if "bug" is already present +for login in $(echo "$BUG_USERS" | jq --raw-output .[]); do + USER_JSON="$(jq '.contributors[] | select(.login == "'"$login"'")' < "$BASEDIR"/.all-contributorsrc)" + BUGS="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request") | not)) | map(select(.user.login == "'"$login"'")) | .[] | { number: .number, title: .title, login: .user.login }')" + echo -en "${COL_WHITE}$login:${COL_RESET} " + if [ -z "$USER_JSON" ]; then + echo -e "${COL_RED}Missing user entirely${COL_RESET}" + echo "$BUGS" + else + HAS_BUG="$(echo "$USER_JSON" | jq --raw-output 'if .contributions | contains(["bug"]) | not then "false" else "true" end')" + if [ "$HAS_BUG" = "true" ]; then + echo -e "${COL_GREEN}ok${COL_RESET}" + else + echo -e "${COL_RED}bug is missing${COL_RESET}" + echo "$BUGS" + fi + fi +done + +echo +echo "Checking for code contributions..." +# Users which submitted a pull request that has been merged (assigned to the milestone) +CODE_USERS="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request"))) | [ .[].user.login ] | unique')" + +# for each user, check if "code" is already present +for login in $(echo "$CODE_USERS" | jq --raw-output .[]); do + USER_JSON="$(jq '.contributors[] | select(.login == "'"$login"'")' < "$BASEDIR"/.all-contributorsrc)" + PRS="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request"))) | map(select(.user.login == "'"$login"'")) | .[] | { number: .number, title: .title, login: .user.login }')" + echo -en "${COL_WHITE}$login:${COL_RESET} " + if [ -z "$USER_JSON" ]; then + echo -e "${COL_RED}Missing user entirely${COL_RESET}" + echo "$PRS" + else + HAS_CODE="$(echo "$USER_JSON" | jq --raw-output 'if .contributions | contains(["code"]) | not then "false" else "true" end')" + if [ "$HAS_CODE" = "true" ]; then + echo -e "${COL_GREEN}ok${COL_RESET}" + else + echo -e "${COL_RED}code is missing${COL_RESET}" + echo "$PRS" + fi + fi +done + diff --git a/.ci/tools/release-notes-add-pr.sh b/.ci/tools/release-notes-add-pr.sh new file mode 100755 index 00000000000..df92ac559c0 --- /dev/null +++ b/.ci/tools/release-notes-add-pr.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "$0 " + exit 1 +fi + +BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PULL_NUMBER="$1" + +CURL_API_HEADER=(--header "X-GitHub-Api-Version: 2022-11-28") +CURL_AUTH_HEADER=() +if [ -n "$GITHUB_TOKEN" ]; then + echo "Will use env var GITHUB_TOKEN for github REST API" + CURL_AUTH_HEADER=(--header "Authorization: Bearer $GITHUB_TOKEN") +fi + + +PULL_JSON=$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s "https://api.github.com/repos/pmd/pmd/pulls/$PULL_NUMBER") +#echo "$PULL_JSON" > pull-response-$PULL_NUMBER.txt +#DEBUG ONLY +#PULL_JSON=$(cat pull-response-$PULL_NUMBER.txt) +#DEBUG ONLY + +PULL_ITEM="$(echo "$PULL_JSON" | jq --raw-output '"* [#\(.number)](https://github.com/pmd/pmd/pull/\(.number)): \(.title | gsub("@"; "@") | gsub("\\["; "\\[")) - @\(.user.login)"')" + +USER="$(echo "$PULL_JSON" | jq --raw-output .user.login)" +USER_JSON="$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s "https://api.github.com/users/$USER")" +#DEBUG ONLY +#USER_JSON="{\"login\": \"$USER\", \"name\": \"foo $USER\"}" +#DEBUG_ONLY +USER_NAME="$(echo "$USER_JSON" | jq --raw-output ".name // \"$USER\"")" +search=" - \@$USER" +replacement=" - [$USER_NAME](https://github.com/$USER) (@$USER)" +PULL_ITEM="${PULL_ITEM//${search}/${replacement}}" + +RELEASE_NOTES_FILE="${BASEDIR}/docs/pages/release_notes.md" +RELEASE_NOTES=$(cat "$RELEASE_NOTES_FILE") + +line="$(echo "$RELEASE_NOTES" | grep -n "### 📦️ Dependency updates" | cut -d ":" -f 1)" +RELEASE_NOTES="$(echo "$RELEASE_NOTES" | head -n "$((line - 1))") +$PULL_ITEM +$(echo "$RELEASE_NOTES" | tail -n "+$((line - 1))") +" +echo "$RELEASE_NOTES" > "$RELEASE_NOTES_FILE" diff --git a/.ci/tools/release-notes-generate.sh b/.ci/tools/release-notes-generate.sh new file mode 100755 index 00000000000..549d38222ba --- /dev/null +++ b/.ci/tools/release-notes-generate.sh @@ -0,0 +1,163 @@ +#!/bin/bash +set -e + +if [ -z "$1" ] || [ -z "$2" ]; then + echo "$0 " + exit 1 +fi + +BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LAST_VERSION="$1" +RELEASE_VERSION="$2" + +CURL_API_HEADER=(--header "X-GitHub-Api-Version: 2022-11-28") +CURL_AUTH_HEADER=() +if [ -n "$GITHUB_TOKEN" ]; then + echo "Will use env var GITHUB_TOKEN for github REST API" + CURL_AUTH_HEADER=(--header "Authorization: Bearer $GITHUB_TOKEN") +fi + +# determine current milestone +MILESTONE_JSON=$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s "https://api.github.com/repos/pmd/pmd/milestones?state=all&direction=desc&per_page=10&page=1"|jq ".[] | select(.title == \"$RELEASE_VERSION\")") +#DEBUG ONLY +#MILESTONE_JSON='{"number":80,"closed_issues":40}' +#DEBUG ONLY +MILESTONE=$(echo "$MILESTONE_JSON" | jq .number) + +PAGE="1" +HAS_NEXT="true" +ISSUES_JSON="" +while [ "$HAS_NEXT" = "true" ]; do + echo "Fetching issues for milestone ${MILESTONE} page ${PAGE}..." + URL="https://api.github.com/repos/pmd/pmd/issues?state=closed&sort=created&direction=asc&per_page=30&page=${PAGE}&milestone=${MILESTONE}" + RESPONSE="$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s -w "\nLink: %header{link}" "$URL")" + + #DEBUG ONLY + #echo "$RESPONSE" > issues-response-${PAGE}.txt + #RESPONSE="$(cat issues-response-${PAGE}.txt)" + #DEBUG ONLY + + LINK_HEADER="$(echo "$RESPONSE" | tail -1)" + BODY="$(echo "$RESPONSE" | head -n -1)" + + #DEBUG ONLY + #echo "$BODY" > "issues-response-page-${PAGE}.txt" + #BODY="$(cat "issues-response-page-${PAGE}.txt")" + #DEBUG ONLY + + COMMA="," + if [ "$PAGE" -eq 1 ]; then + COMMA="" + fi + ISSUES_JSON="${ISSUES_JSON}${COMMA}${BODY}" + + if [[ $LINK_HEADER == *"; rel=\"next\""* ]]; then + HAS_NEXT="true" + else + HAS_NEXT="false" + fi + PAGE=$((PAGE + 1)) + + #DEBUG ONLY + #HAS_NEXT="true" + #if [ "$PAGE" -gt 2 ]; then break; fi + #DEBUG ONLY + + # stop after 10 pages + if [ "$PAGE" -gt 10 ]; then + echo + echo + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "!!!!!!!!!!!!!! reached page 10, stopping now !!!!!!!!!!!!!" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo + echo + break; + fi +done + + +ISSUES_JSON="$(echo "[ $ISSUES_JSON ]" | jq 'flatten(1)')" +echo "Found $(echo "$ISSUES_JSON" | jq length) issues/pull requests" +#DEBUG ONLY +#echo "$ISSUES_JSON" > issues-all.txt +#DEBUG ONLY + +FIXED_ISSUES_JSON="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request") | not))')" +FIXED_ISSUES="$(echo "$FIXED_ISSUES_JSON" | jq --raw-output '.[] | "* [#\(.number)](https://github.com/pmd/pmd/issues/\(.number)): \(.title | gsub("@"; "@") | gsub("\\["; "\\["))"')" +FIXED_ISSUES="### 🐛️ Fixed Issues + +$FIXED_ISSUES +" + +PULL_REQUESTS_JSON="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request"))) | map(select(contains({labels: [{name: "dependencies"}]}) | not))')" +PULL_REQUESTS="$(echo "$PULL_REQUESTS_JSON" | jq --raw-output '.[] | "* [#\(.number)](https://github.com/pmd/pmd/pull/\(.number)): \(.title | gsub("@"; "@") | gsub("\\["; "\\[")) - @\(.user.login)"')" + +AUTHORS="$(echo "$PULL_REQUESTS_JSON" | jq --raw-output '.[].user.login' | sort | uniq)" +echo "Resolving $(echo "$AUTHORS" | wc -l) author names in pull requests..." +for login in $AUTHORS; do + USER_JSON="$(curl "${CURL_API_HEADER[@]}" "${CURL_AUTH_HEADER[@]}" -s "https://api.github.com/users/$login")" + #DEBUG ONLY + #USER_JSON="{\"login\": \"$login\", \"name\": \"foo $login\"}" + #DEBUG_ONLY + USER_NAME="$(echo "$USER_JSON" | jq --raw-output ".name // \"$login\"")" + search=" - \@$login" + replacement=" - [$USER_NAME](https://github.com/$login) (@$login)" + PULL_REQUESTS="${PULL_REQUESTS//${search}/${replacement}}" +done + +PULL_REQUESTS="### ✨️ Merged pull requests + +$PULL_REQUESTS +" + +DEPENDENCY_UPDATES_JSON="$(echo "$ISSUES_JSON" | jq 'map(select(has("pull_request"))) | map(select(contains({labels: [{name: "dependencies"}]})))')" +DEPENDENCY_UPDATES="$(echo "$DEPENDENCY_UPDATES_JSON" | jq --raw-output '.[] | "* [#\(.number)](https://github.com/pmd/pmd/pull/\(.number)): \(.title | gsub("@"; "@") | gsub("\\["; "\\["))"')" +DEPENDENCY_UPDATES_COUNT=$(echo "$DEPENDENCY_UPDATES_JSON" | jq length) +if [ -z "$DEPENDENCY_UPDATES" ]; then DEPENDENCY_UPDATES="No dependency updates."; fi +DEPENDENCY_UPDATES="### 📦️ Dependency updates + +$DEPENDENCY_UPDATES +" + +# calculating stats for release notes (excluding dependency updates) +STATS_CLOSED_ISSUES=$(echo "$MILESTONE_JSON" | jq .closed_issues) +STATS=$( +echo "### 📈️ Stats" +echo "" +echo "* $(git log pmd_releases/"${LAST_VERSION}"..HEAD --oneline --no-merges |wc -l) commits" +echo "* $((STATS_CLOSED_ISSUES - DEPENDENCY_UPDATES_COUNT)) closed tickets & PRs" +echo "* Days since last release: $(( ( $(date +%s) - $(git log --max-count=1 --format="%at" pmd_releases/"${LAST_VERSION}") ) / 86400))" +echo +) + +function insert() { + local FULL_TEXT="$1" + local FROM_MARKER="$2" + local END_MARKER="$3" + local INSERTION="$4" + local fromLine + local endLine + local headText + local tailText + fromLine="$(echo "$FULL_TEXT" | grep -n "$FROM_MARKER" | cut -d ":" -f 1)" + endLine="$(echo "$FULL_TEXT" | grep -n "$END_MARKER" | cut -d ":" -f 1)" + headText="$(echo "$FULL_TEXT" | head -n "$((fromLine - 1))")" + tailText="$(echo "$FULL_TEXT" | tail -n "+$endLine")" + echo "$headText + +$INSERTION + +$tailText" +} + +RELEASE_NOTES_FILE="${BASEDIR}/docs/pages/release_notes.md" +echo "Updating $RELEASE_NOTES_FILE now..." + +RELEASE_NOTES=$(cat "$RELEASE_NOTES_FILE") +#RELEASE_NOTES="$(insert "$RELEASE_NOTES" "### 🐛️ Fixed Issues" "### ✨️ Merged pull requests" "$FIXED_ISSUES")" +RELEASE_NOTES="$(insert "$RELEASE_NOTES" "### ✨️ Merged pull requests" "### 📦️ Dependency updates" "$PULL_REQUESTS")" +RELEASE_NOTES="$(insert "$RELEASE_NOTES" "### 📦️ Dependency updates" "### 📈️ Stats" "$DEPENDENCY_UPDATES")" +RELEASE_NOTES="$(insert "$RELEASE_NOTES" "### 📈️ Stats" "{% endtocmaker %}" "$STATS")" + +echo "$RELEASE_NOTES" > "$RELEASE_NOTES_FILE" diff --git a/.gitattributes b/.gitattributes index 7b1011705c7..bf1d63a518a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,3 +12,6 @@ mvnw.cmd text eol=crlf *.jpg -text *.svgz -text *.jar -text +# always use LF for pom.xml to be able to reuse github action caching +# see .github/workflows/build.yml +pom.xml text eol=lf diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 41efb1e92c5..254b530afa5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,7 @@ + + + + ## Describe the PR @@ -6,7 +10,7 @@ -- Fixes # +- Fix # ## Ready? diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 75a3d100ebd..e09c0b4e470 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,19 +5,23 @@ updates: directory: "/" schedule: interval: "weekly" + day: "wednesday" + # Allow up to 10 open pull requests for maven dependencies + open-pull-requests-limit: 10 - package-ecosystem: "bundler" directories: - - "/" + - "/.ci/files" - "/docs" schedule: interval: "weekly" - groups: - all-gems: - patterns: [ "*" ] + day: "wednesday" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - groups: - all-actions: - patterns: [ "*" ] + day: "wednesday" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "wednesday" diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 00000000000..194ac4d6b03 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,11 @@ +name: Build Pull Request + +on: + pull_request: + merge_group: + types: [checks_requested] + +jobs: + build: + name: Build Pull Request + uses: ./.github/workflows/build.yml diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 00000000000..a63a429378a --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,13 @@ +name: Build Release + +on: + push: + tags: + - 'pmd_releases/*' + - '!pmd_releases/*-SNAPSHOT' + workflow_dispatch: + +jobs: + build: + name: Build Release + uses: ./.github/workflows/build.yml diff --git a/.github/workflows/build-snapshot.yml b/.github/workflows/build-snapshot.yml new file mode 100644 index 00000000000..00e2bd6d865 --- /dev/null +++ b/.github/workflows/build-snapshot.yml @@ -0,0 +1,17 @@ +name: Build Snapshot + +on: + push: + branches: + - '**' + # don't run on dependabot branches. Dependabot will create pull requests, which will then be run instead + - '!dependabot/**' + workflow_dispatch: + schedule: + # build it monthly: At 04:00 on day-of-month 1. + - cron: '0 4 1 * *' + +jobs: + build: + name: Build Snapshot + uses: ./.github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c98a1196bdd..07470c188e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,86 +1,370 @@ -name: build +name: Build on: - push: - branches: - - main - tags: - - '**' - pull_request: - merge_group: - schedule: - # build it monthly: At 04:00 on day-of-month 1. - - cron: '0 4 1 * *' - workflow_dispatch: - inputs: - build_cli_dist_only: - description: "Build only modules cli and dist" - required: true - type: boolean - default: false + workflow_call: + +# if another commit is added to the same branch or PR (same github.ref), +# then cancel already running jobs and start a new build. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: contents: read # to fetch code (actions/checkout) +env: + LANG: 'en_US.UTF-8' + jobs: - build: + compile: + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - name: Fast Build with Maven + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + verify -PfastSkip -DskipTests -Dcyclonedx.skip=false \ + deploy:deploy -DaltDeploymentRepository="dogfood::file://$(pwd)/target/staging" + - name: Cleanup local repository + run: | + # Cleanup local repository to not poison the shared cache with our SNAPSHOTS of the current build. + # Some information is stored in maven-metadata-*.xml files which tells maven which + # version exactly is available in the staging repo. But if we rerun the build using the + # older cache, it points to the old staging versions, which are not available anymore. + find .m2/repository/net/sourceforge/pmd -type d -name "*-SNAPSHOT" -and -not -path "*/pmd-designer/*" -print0 | xargs -0 rm -vrf + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: compile-artifact + if-no-files-found: error + path: | + */target + */*/target + !pmd-dist/target/pmd-dist-*-bin.zip + !pmd-dist/target/pmd-dist-*-src.zip + !pmd-dist/target/pmd-*-cyclonedx.xml + !pmd-dist/target/pmd-*-cyclonedx.json + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: staging-repository + if-no-files-found: error + path: target/staging + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: dist-artifact + if-no-files-found: error + path: | + pmd-dist/target/pmd-dist-*-bin.zip + pmd-dist/target/pmd-dist-*-src.zip + pmd-dist/target/pmd-*-cyclonedx.xml + pmd-dist/target/pmd-*-cyclonedx.json + + verify: + needs: compile + timeout-minutes: 30 + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: compile-artifact + - name: Full Build with Maven + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + verify -DskipTests + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: javadocs-artifact + if-no-files-found: error + path: | + */target/*-javadoc.jar + */*/target/*-javadoc.jar + + verify-unittests: + needs: compile + timeout-minutes: 30 runs-on: ${{ matrix.os }} - permissions: - # read to fetch code (actions/checkout) - # write to push code to gh-pages, create releases - # note: forked repositories will have maximum read access - contents: write - continue-on-error: false + defaults: + run: + shell: bash strategy: + # don't fail fast - we want to know the results of all runs + fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] - if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - uses: actions/cache@v4 - with: - path: | - ~/.m2/repository - ~/.gradle/caches - ~/.cache - ~/work/pmd/target/repositories - vendor/bundle - key: v3-${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - restore-keys: | - v3-${{ runner.os }}- - - name: Set up Ruby 3.3 - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.3 - - name: Setup Environment - shell: bash - run: | - echo "LANG=en_US.UTF-8" >> $GITHUB_ENV - echo "MAVEN_OPTS=-Daether.connector.http.connectionMaxTtl=180 -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30" >> $GITHUB_ENV - echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/27/scripts" >> $GITHUB_ENV - - name: Check Environment - shell: bash - run: | - f=check-environment.sh; \ - mkdir -p .ci && \ - ( [ -e .ci/$f ] || curl -sSL "${PMD_CI_SCRIPTS_URL}/$f" > ".ci/$f" ) && \ - chmod 755 .ci/$f && \ - .ci/$f - - name: Build - run: .ci/build.sh - shell: bash - env: - BUILD_CLI_DIST_ONLY: ${{ inputs.build_cli_dist_only }} - PMD_CI_SECRET_PASSPHRASE: ${{ secrets.PMD_CI_SECRET_PASSPHRASE }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Workaround actions/upload-artifact#176 - run: | - echo "artifacts_path=$(realpath ..)" >> $GITHUB_ENV - - name: Upload regression tester report - uses: actions/upload-artifact@v4 - with: - name: pmd-regression-tester - path: ${{ env.artifacts_path }}/target/pr-*-diff-report-*.tar.gz - if-no-files-found: ignore + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + # under linux we execute more extensive integration tests with various java versions + if: ${{ runner.os == 'Linux' }} + with: + distribution: 'temurin' + java-version: | + 8 + 11 + 17 + 25 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + # default java version for all os is 21 + with: + distribution: 'temurin' + java-version: '21' + # only restore the cache, don't create a new cache + # Note: this works under Windows only if pom.xml files use LF, so that hashFiles('**/pom.xml') + # gives the same result (line endings...). + # see .gitattributes for pom.xml - it should always be using lf. + - uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + # we can only reuse compile-artifacts under linux due to file timestamp issues and + # platform specific line endings in test resource files. + if: ${{ runner.os == 'Linux' }} + with: + name: compile-artifact + - name: Build with Maven and run unit and integration tests (linux) + # under linux we execute more extensive integration tests with various java versions + if: ${{ runner.os == 'Linux' }} + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + verify \ + -PfastSkip -Dcyclonedx.skip=false \ + -Djava8.home="${JAVA_HOME_8_X64}" \ + -Djava11.home="${JAVA_HOME_11_X64}" \ + -Djava17.home="${JAVA_HOME_17_X64}" \ + -Djava25.home="${JAVA_HOME_25_X64}" + - name: Build with Maven and run unit tests + if: ${{ runner.os != 'Linux' }} + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + verify \ + -PfastSkip -Dcyclonedx.skip=false + + dogfood: + needs: compile + timeout-minutes: 30 + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: compile-artifact + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: staging-repository + path: target/staging + - name: Run PMD on PMD + run: | + if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then + dogfood_name="pr-$(jq -r ".number" "${GITHUB_EVENT_PATH}")" + else + dogfood_name="sha-${GITHUB_SHA}" + fi + + current_pmd_version=$(./mvnw --batch-mode --no-transfer-progress \ + help:evaluate -Dexpression=project.version -q -DforceStdout || echo "failed_to_determine_current_pmd_version") + echo "Determined current pmd version: ${current_pmd_version}" + + new_version="${current_pmd_version}-dogfood-${dogfood_name}-SNAPSHOT" + echo "::group::Set version to ${new_version}" + ./mvnw -Dmaven.repo.local=.m2/repository \ + versions:set --quiet -DnewVersion="${new_version}" -DgenerateBackupPoms=false + sed -i 's/[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}.*<\/version>\( *\)/'"${current_pmd_version}"'<\/version>\1/' pom.xml + echo "::endgroup::" + + echo "::group::Generate settings.xml to use dogfoodStagingRepo" + maven_settings_file="$(pwd)/target/staging/settings.xml" + cat > "${maven_settings_file}" < + + + dogfood + + + dogfood + file://$(pwd)/target/staging + true + true + + + + + + EOF + echo "new file ${maven_settings_file}:" + cat "${maven_settings_file}" + echo "::endgroup::" + + echo "::group::Run ./mvnw verify" + ./mvnw --show-version --errors --batch-mode \ + --settings "${maven_settings_file}" \ + -Dmaven.repo.local=.m2/repository \ + verify \ + -PfastSkip,dogfood \ + -DskipTests \ + -Dpmd.skip=false \ + -Dcpd.skip=false + echo "::endgroup::" + + echo "::group::Restore version to ${current_pmd_version}" + ./mvnw -Dmaven.repo.local=.m2/repository \ + versions:set --quiet -DnewVersion="${current_pmd_version}" -DgenerateBackupPoms=false + git checkout -- pom.xml + echo "::endgroup::" + + documentation: + needs: compile + timeout-minutes: 30 + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: compile-artifact + - name: Generate rule docs + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + verify \ + -Pgenerate-rule-docs,fastSkip \ + -DskipTests -Dassembly.skipAssembly=true + - name: Set up Ruby 3.3 + uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 #v1.286.0 + with: + ruby-version: 3.3 + - name: Setup bundler + run: | + cd docs + bundle config set --local path vendor/bundle + bundle install + - name: Build documentation + run: | + cd docs + bundle exec jekyll build + - name: Create Markdown formatted Release Notes + run: | + cd docs + bundle exec render_release_notes.rb pages/release_notes.md | tail -n +6 > _site/pmd_release_notes.md + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: docs-artifact + if-no-files-found: error + path: docs/_site + + regressiontester: + needs: compile + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + timeout-minutes: 60 + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + fetch-depth: 2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: | + 11 + 21 + - name: Set up Ruby 3.3 + uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 #v1.286.0 + with: + ruby-version: 3.3 + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + path: | + ~/.m2/repository + ~/.gradle/caches + ~/work/pmd/target/repositories + .ci/files/vendor/bundle + key: regressiontester-${{ hashFiles('.ci/files/project-list.xml', '.ci/files/Gemfile.lock') }} + restore-keys: regressiontester- + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + path: pmd-dist/target + - name: Setup bundler + run: | + bundle config set --local gemfile .ci/files/Gemfile + bundle config set --local path vendor/bundle + bundle install + - name: Prepare HOME/openjdk11 + run: | + ln -sfn "${JAVA_HOME_11_X64}" "${HOME}/openjdk11" + - name: Run pmdtester + env: + # this variable is available for "push" builds only, otherwise it's empty + PMD_REGRESSION_TESTER_PUSH_BEFORE: ${{ github.event.before }} + run: .ci/files/pmdtester_start.sh + - name: Workaround actions/upload-artifact#176 + run: | + echo "artifacts_path=$(realpath ..)" >> "${GITHUB_ENV}" + - name: Upload regression tester report + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: pmd-regression-tester + if-no-files-found: error + path: ${{ env.artifacts_path }}/target/reports/diff diff --git a/.github/workflows/git-repo-sync.yml b/.github/workflows/git-repo-sync.yml index 7665bc6d3ba..f25e187f860 100644 --- a/.github/workflows/git-repo-sync.yml +++ b/.github/workflows/git-repo-sync.yml @@ -5,7 +5,8 @@ on: branches: - main tags: - - '**' + - 'pmd_releases/*' + - '!pmd_releases/*-SNAPSHOT' workflow_dispatch: permissions: @@ -13,20 +14,100 @@ permissions: jobs: build: + # only run in the official pmd repo, where we have access to the secrets and not on forks + if: ${{ github.repository == 'pmd/pmd' }} runs-on: ubuntu-latest + timeout-minutes: 20 + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS + environment: + name: sourceforge + url: https://sourceforge.net/p/pmd/code/ci/main/tree/ + defaults: + run: + shell: bash continue-on-error: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 with: fetch-depth: 100 - - name: Setup Environment - shell: bash + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + GIT_CODE_SF_NET_KNOWN_HOSTS: ${{ vars.PMD_GIT_CODE_SF_NET_KNOWN_HOSTS }} run: | - echo "LANG=en_US.UTF-8" >> $GITHUB_ENV - echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/27/scripts" >> $GITHUB_ENV - - name: Sync - run: .ci/git-repo-sync.sh - shell: bash + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host git.code.sf.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${GIT_CODE_SF_NET_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + - name: Git Sync env: - PMD_CI_SECRET_PASSPHRASE: ${{ secrets.PMD_CI_SECRET_PASSPHRASE }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PMD_SF_USER: adangel + run: | + # + # From: https://gist.github.com/sj26/88e1c6584397bb7c13bd11108a579746 + # + # Retry a command up to a specific number of times until it exits successfully, + # with exponential back off. + # + # $ retry 5 echo Hello + # Hello + # + # $ retry 5 false + # Retry 1/5 exited 1, retrying in 1 seconds... + # Retry 2/5 exited 1, retrying in 2 seconds... + # Retry 3/5 exited 1, retrying in 4 seconds... + # Retry 4/5 exited 1, retrying in 8 seconds... + # Retry 5/5 exited 1, no more retries left. + # + function retry { + local retries=$1 + shift + + local count=0 + until "$@"; do + exit=$? + wait=$((2 ** $count)) + count=$(($count + 1)) + if [ $count -lt $retries ]; then + echo "Retry $count/$retries exited $exit, retrying in $wait seconds..." + sleep $wait + else + echo "Retry $count/$retries exited $exit, no more retries left." + return $exit + fi + done + return 0 + } + + if [[ "${GITHUB_REF}" == refs/heads/* ]]; then + PMD_CI_BRANCH=${GITHUB_REF##refs/heads/} + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + PMD_CI_TAG=${GITHUB_REF##refs/tags/} + else + echo "::error ::Unknown branch/tag: GITHUB_REF=${GITHUB_REF}" + exit 1 + fi + + + git remote add pmd-sf "${PMD_SF_USER}@git.code.sf.net:/p/pmd/code" + if [ -n "${PMD_CI_BRANCH}" ]; then + retry 5 git push pmd-sf "${PMD_CI_BRANCH}:${PMD_CI_BRANCH}" + echo "Successfully pushed ${PMD_CI_BRANCH} to sourceforge" + elif [ -n "${PMD_CI_TAG}" ]; then + git push pmd-sf tag "${PMD_CI_TAG}" + echo "Successfully pushed tag ${PMD_CI_TAG} to sourceforge" + else + echo "::error ::Don't know what to do: neither PMD_CI_BRANCH nor PMD_CI_TAG is set" + exit 1 + fi + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" diff --git a/.github/workflows/publish-pull-requests.yml b/.github/workflows/publish-pull-requests.yml new file mode 100644 index 00000000000..00d7a01ffd0 --- /dev/null +++ b/.github/workflows/publish-pull-requests.yml @@ -0,0 +1,215 @@ +name: Publish Results from Pull Requests +run-name: Publish Results for "${{ github.event.workflow_run.display_title }}" +on: + workflow_run: + workflows: [Build Pull Request] + types: + - completed + +permissions: {} + +jobs: + publish: + # only run in the official pmd repo, where we have access to the secrets and not on forks + # and only run for _successful_ pull requests workflow runs. + if: ${{ github.repository == 'pmd/pmd' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1 + id: pmd-actions-helper-app-token + with: + app-id: ${{ secrets.PMD_ACTIONS_HELPER_ID }} + private-key: ${{ secrets.PMD_ACTIONS_HELPER_PRIVATE_KEY }} + owner: pmd + repositories: pmd + permission-actions: read # download workflow run artifacts + permission-checks: write + permission-statuses: write + permission-pull-requests: write # in order to add/update a comment + - name: 'Get PR context' + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + # If the PR is from a fork, prefix it with `:`, otherwise only the PR branch name is relevant: + PR_BRANCH: |- + ${{ + (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) + && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) + || github.event.workflow_run.head_branch + }} + PR_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + # Query the PR number by repo + branch, then assign to step output: + PR_NUMBER="$(gh pr view --repo pmd/pmd "${PR_BRANCH}" --json 'number' --jq '.number')" + echo "PR_NUMBER=${PR_NUMBER}" >> "${GITHUB_ENV}" + echo "PR_BRANCH=${PR_BRANCH}" >> "${GITHUB_ENV}" + echo "PR_SHA=${PR_SHA}" >> "${GITHUB_ENV}" + - name: Add Job Summary + env: + WORKFLOW_RUN_DISPLAY_TITLE: ${{ github.event.workflow_run.display_title }} + WORKFLOW_RUN_NAME: ${{ github.event.workflow_run.name }} + WORKFLOW_RUN_NUMBER: ${{ github.event.workflow_run.run_number }} + WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} + run: | + echo "### Run Info" >> "${GITHUB_STEP_SUMMARY}" + echo "Triggered for PR [#${PR_NUMBER}](https://github.com/pmd/pmd/pull/${PR_NUMBER})" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + echo "Called by [${WORKFLOW_RUN_DISPLAY_TITLE} (${WORKFLOW_RUN_NAME} #${WORKFLOW_RUN_NUMBER})](${WORKFLOW_RUN_HTML_URL})" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + - name: Download docs-artifact + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + run: | + mkdir docs-artifact + cd docs-artifact + # gh run download "${RUN_ID}" --repo pmd/pmd --name docs-artifact + # apply workaround for https://github.com/cli/cli/issues/12437 + download_url=$(gh api \ + "/repos/pmd/pmd/actions/runs/$RUN_ID/artifacts?name=docs-artifact" \ + --jq '.artifacts|sort_by(.created_at)|.[-1]|.archive_download_url' \ + ) + curl -L \ + -H "Authorization: Bearer $GH_TOKEN" \ + --output artifact.zip \ + $download_url + unzip -q artifact.zip + rm artifact.zip + - name: Upload docs-artifact to s3://pmd-pull-requests + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_S3_PMD_PULL_REQUESTS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_PMD_PULL_REQUESTS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: eu-central-1 + run: | + aws s3 sync docs-artifact "s3://pmd-pull-requests/pr-${PR_NUMBER}/${PR_SHA}/docs" --no-progress + echo "public url: https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/docs" + - name: Add commit status (Documentation) + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/pmd/pmd/statuses/${PR_SHA}" \ + -f "state=success" -f "target_url=https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/docs" \ + -f "description=PMD Documentation Preview" -f "context=Published Results / Documentation" + - name: Add Check Status (Documentation) + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + timestamp="$(date -uIs)" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/pmd/check-runs \ + -f "name=Documentation" -f "head_sha=${PR_SHA}" -f "status=completed" \ + -f "started_at=${timestamp}" -f "conclusion=success" -f "completed_at=${timestamp}" \ + -f "output[title]=Documentation Preview" -f "output[summary]=[Documentation Preview](https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/docs)" + + - name: Download pmd-regression-tester + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + run: | + mkdir pmd-regression-tester + cd pmd-regression-tester + # gh run download "${RUN_ID}" --repo pmd/pmd --name pmd-regression-tester + # apply workaround for https://github.com/cli/cli/issues/12437 + download_url=$(gh api \ + "/repos/pmd/pmd/actions/runs/$RUN_ID/artifacts?name=pmd-regression-tester" \ + --jq '.artifacts|sort_by(.created_at)|.[-1]|.archive_download_url' \ + ) + curl -L \ + -H "Authorization: Bearer $GH_TOKEN" \ + --output artifact.zip \ + $download_url + unzip -q artifact.zip + rm artifact.zip + - name: Upload regression report to s3://pmd-pull-requests + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_S3_PMD_PULL_REQUESTS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_PMD_PULL_REQUESTS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: eu-central-1 + run: | + if [ -e "pmd-regression-tester/index.html" ]; then + aws s3 sync pmd-regression-tester "s3://pmd-pull-requests/pr-${PR_NUMBER}/${PR_SHA}/regression" --no-progress + echo "public url: https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/regression" + echo "REGRESSION_REPORT_UPLOADED=1" >> "${GITHUB_ENV}" + else + echo "No report available." + echo "REGRESSION_REPORT_UPLOADED=0" >> "${GITHUB_ENV}" + fi + - name: Add commit status (Regression Tester) + if: ${{ env.REGRESSION_REPORT_UPLOADED == 1 }} + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/pmd/pmd/statuses/${PR_SHA}" \ + -f "state=success" -f "target_url=https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/regression" \ + -f "description=PMD Regression Tester Report" -f "context=Published Results / Regression Tester" + - name: Prepare regression summary + run: | + summary="$(cat pmd-regression-tester/summary.txt)" + if [ "$REGRESSION_REPORT_UPLOADED" = "1" ]; then + summary=" + ${summary} + + [Regression Tester Report](https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/regression) + " + fi + echo "${summary}" > pmd-regression-tester/summary.txt + - name: Add Check Status (Regression Tester) + env: + # Token required for GH CLI: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + timestamp="$(date -uIs)" + summary="$(cat pmd-regression-tester/summary.txt)" + + # conclusion can be one of: action_required, cancelled, failure, neutral, success, skipped, stale, timed_out + conclusion="$(cat pmd-regression-tester/conclusion.txt)" + + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/pmd/check-runs \ + -f "name=Regression Tester" -f "head_sha=${PR_SHA}" -f "status=completed" \ + -f "started_at=${timestamp}" -f "conclusion=${conclusion}" -f "completed_at=${timestamp}" \ + -f "output[title]=Regression Tester Report" -f "output[summary]=${summary}" + - name: Prepare comment text + run: | + summary="$(cat pmd-regression-tester/summary.txt)" + comment="[Documentation Preview](https://pull-requests.pmd-code.org/pr-${PR_NUMBER}/${PR_SHA}/docs) + + ${summary} + + (comment created at $(date -u --rfc-3339=seconds) for ${PR_SHA}) + " + echo "${comment}" > comment.txt + + echo "### Result" >> "${GITHUB_STEP_SUMMARY}" + echo "${comment}" >> "${GITHUB_STEP_SUMMARY}" + - uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4 + with: + GITHUB_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + header: pmd-publish-pull-requests + number: ${{ env.PR_NUMBER }} + path: comment.txt diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000000..06b9284ede7 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,797 @@ +name: Publish Release + +on: + workflow_run: + workflows: [Build Release] + types: + - completed + branches: + - '**' + workflow_dispatch: + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + LANG: 'en_US.UTF-8' + +jobs: + check-version: + # only run in the official pmd/pmd repo, where we have access to the secrets and not on forks + # and only run for _successful_ push workflow runs on tags. + if: ${{ github.repository == 'pmd/pmd' + && contains(fromJSON('["push", "workflow_dispatch"]'), github.event.workflow_run.event) + && github.event.workflow_run.head_branch != 'main' + && github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + outputs: + PMD_VERSION: ${{ steps.version.outputs.PMD_VERSION }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_branch }} + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' + - name: Determine Version + id: version + env: + REF: ${{ github.event.workflow_run.head_branch }} + run: | + if ! git show-ref --exists "refs/tags/$REF"; then + echo "::error ::Tag $REF does not exist, aborting." + exit 1 + fi + + PMD_VERSION=$(./mvnw --batch-mode --no-transfer-progress help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "Determined PMD_VERSION=$PMD_VERSION" + if [[ "$PMD_VERSION" = *-SNAPSHOT ]]; then + echo "::error ::PMD_VERSION=$PMD_VERSION is a snapshot version, aborting." + exit 1 + fi + echo "PMD_VERSION=$PMD_VERSION" >> "$GITHUB_OUTPUT" + - name: Add Job Summary + env: + WORKFLOW_RUN_DISPLAY_TITLE: ${{ github.event.workflow_run.display_title }} + WORKFLOW_RUN_NAME: ${{ github.event.workflow_run.name }} + WORKFLOW_RUN_NUMBER: ${{ github.event.workflow_run.run_number }} + WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} + VERSION: ${{ steps.version.outputs.PMD_VERSION }} + TAG: ${{ github.event.workflow_run.head_branch }} + run: | + echo "### Run Info" >> "${GITHUB_STEP_SUMMARY}" + echo "Building Version: ${VERSION}" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + echo "Tag: ${TAG}" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + echo "Called by [${WORKFLOW_RUN_DISPLAY_TITLE} (${WORKFLOW_RUN_NAME} #${WORKFLOW_RUN_NUMBER})](${WORKFLOW_RUN_HTML_URL})" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + + deploy-to-maven-central: + needs: [check-version] + # use environment maven-central, where secrets are configured for MAVEN_CENTRAL_PORTAL_* + environment: + name: maven-central + url: https://repo.maven.apache.org/maven2/net/sourceforge/pmd/ + runs-on: ubuntu-latest + timeout-minutes: 180 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_branch }} + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: compile-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Build and publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_PORTAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PORTAL_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + # note: we can't use artifact staging-repository, as the jars are unsigned and javadoc+sources are missing. + run: | + ./mvnw --show-version --errors --batch-mode \ + deploy \ + -DskipTests \ + -PfastSkip,sign,pmd-release \ + -Dcyclonedx.skip=false \ + -Dmaven.javadoc.skip=false \ + -Ddokka.skip=false \ + -Dmaven.source.skip=false + + deploy-to-sourceforge-files: + needs: [check-version, deploy-to-maven-central] + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS and PMD_SF_APIKEY + environment: + name: sourceforge + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: dist + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup GPG + env: + PMD_CI_GPG_PRIVATE_KEY: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + run: | + mkdir -p "${HOME}/.gpg" + chmod 700 "${HOME}/.gpg" + printenv PMD_CI_GPG_PRIVATE_KEY | gpg --batch --import + + gpg --list-keys --fingerprint --keyid-format=long + gpg --list-secret-keys --fingerprint --keyid-format=long + + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + WEB_SF_KNOWN_HOSTS: ${{ vars.PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host web.sourceforge.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${WEB_SF_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + mv docs "pmd-doc-${PMD_VERSION}" + zip -qr "dist/pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + + - name: Sign files + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + run: | + cd dist + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-bin.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-src.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-doc.zip" + + - name: Upload to sourceforge + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + # Deploy to sourceforge files https://sourceforge.net/projects/pmd/files/pmd/ + basePath="pmd/${PMD_VERSION}" + uploadUrl="adangel@web.sourceforge.net:/home/frs/project/pmd/${basePath}" + + rsync -avh "dist/pmd-dist-${PMD_VERSION}-bin.zip" "${uploadUrl}/" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-bin.zip.asc" "${uploadUrl}/" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-src.zip" "${uploadUrl}/" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-src.zip.asc" "${uploadUrl}/" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-doc.zip" "${uploadUrl}/" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-doc.zip.asc" "${uploadUrl}/" + rsync -avh "dist/pmd-${PMD_VERSION}-cyclonedx.xml" "${uploadUrl}/" + rsync -avh "dist/pmd-${PMD_VERSION}-cyclonedx.json" "${uploadUrl}/" + rsync -avh "pmd-doc-${PMD_VERSION}/pmd_release_notes.md" "${uploadUrl}/ReadMe.md" + + targetUrl="https://sourceforge.net/projects/pmd/files/${basePath}" + echo "TargetUrl: ${targetUrl}" + echo "url_output=${targetUrl}" >> "$GITHUB_OUTPUT" + + - name: Select latest release as default on sourceforge + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_SF_APIKEY: ${{ secrets.PMD_SF_APIKEY }} + run: | + # + # Select the given version as the new default download. + # + # https://sourceforge.net/p/forge/documentation/Using%20the%20Release%20API/ + # https://sourceforge.net/projects/pmd/best_release.json + # + curl -H "Accept: application/json" \ + -X PUT \ + -d "api_key=${PMD_SF_APIKEY}" \ + -d "default=windows&default=mac&default=linux&default=bsd&default=solaris&default=others" \ + "https://sourceforge.net/projects/pmd/files/pmd/${PMD_VERSION}/pmd-dist-${PMD_VERSION}-bin.zip" + + - name: Cleanup ssh and gpg + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + rm -rf "${HOME}/.gpg" + + deploy-to-sourceforge-io: + needs: [check-version, deploy-to-maven-central] + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS + environment: + name: sourceforge + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + WEB_SF_KNOWN_HOSTS: ${{ vars.PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host web.sourceforge.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${WEB_SF_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Upload to sourceforge + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + rsync -ah --stats --delete "docs/" "adangel@web.sourceforge.net:/home/project-web/pmd/htdocs/pmd-${PMD_VERSION}/" + echo "url_output=https://pmd.sourceforge.io/pmd-${PMD_VERSION}/" >> "$GITHUB_OUTPUT" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + deploy-to-pmd-code-doc: + needs: [check-version, deploy-to-maven-central] + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + mv docs "pmd-doc-${PMD_VERSION}" + zip -qr "pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + + - name: Upload to pmd-code.org + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_CODE_SSH_USER: pmd + PMD_CODE_DOCS_PATH: /docs.pmd-code.org/ + run: | + filename="pmd-dist-${PMD_VERSION}-doc.zip" + + scp "${filename}" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -rf \"pmd-doc-${PMD_VERSION}\" && \ + unzip -qo \"${filename}\" && \ + rm \"${filename}\"" + + # only for release builds: https://docs.pmd-code.org/latest -> pmd-doc-${PMD_VERSION} + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -f \"latest\" && \ + ln -s \"pmd-doc-${PMD_VERSION}\" \"latest\"" + + targetUrl="https://docs.pmd-code.org/pmd-doc-${PMD_VERSION}/" + echo "TargetUrl: ${targetUrl}" + echo "url_output=${targetUrl}" >> "$GITHUB_OUTPUT" + + # remove old snapshot doc and point to the new version + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -rf \"pmd-doc-${PMD_VERSION}-SNAPSHOT/\" && \ + ln -s \"pmd-doc-${PMD_VERSION}\" \"pmd-doc-${PMD_VERSION}-SNAPSHOT\"" + echo "Symlink created: https://docs.pmd-code.org/pmd-doc-${PMD_VERSION}-SNAPSHOT/ -> https://docs.pmd-code.org/pmd-doc-${PMD_VERSION}/" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + deploy-to-pmd-code-javadoc: + needs: [check-version, deploy-to-maven-central] + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: https://docs.pmd-code.org/apidocs/ + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: javadocs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Upload javadocs to pmd-code.org + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_CODE_SSH_USER: pmd + PMD_CODE_DOCS_PATH: /docs.pmd-code.org/ + run: | + for moduleJavadocJar in */target/*-javadoc.jar */*/target/*-javadoc.jar; do + moduleJavadocJarBasename="$(basename "$moduleJavadocJar")" + module=${moduleJavadocJarBasename%%-${PMD_VERSION}-javadoc.jar} + + echo "Copying module ${moduleJavadocJar}..." + scp "$moduleJavadocJar" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} + echo "Extracting remotely ${module}..." + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -rf \"apidocs/${module}/${PMD_VERSION}\" && \ + mkdir -p \"apidocs/${module}/${PMD_VERSION}\" && \ + unzip -qo -d \"apidocs/${module}/${PMD_VERSION}\" \"${moduleJavadocJarBasename}\" && \ + rm \"${moduleJavadocJarBasename}\"" + done + + # remove old snapshot javadoc + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -rf apidocs/*/\"${PMD_VERSION}-SNAPSHOT\"" + + # create a symlink from SNAPSHOT to released version + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + for i in apidocs/*/\"${PMD_VERSION}\"; do \ + ln -s \"${PMD_VERSION}\" \"\$i-SNAPSHOT\"; \ + done" + + # (re)create a symlink from latest to released version + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + for i in apidocs/*/\"${PMD_VERSION}\"; do \ + ln -snf \"${PMD_VERSION}\" \"\${i%%${PMD_VERSION}}latest\"; \ + done" + + echo "(Re)creating .htaccess" + # make sure https://docs.pmd-code.org/apidocs/ shows directory index + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}/apidocs\" && \ + echo 'Options +Indexes' > .htaccess" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + github-release: + needs: [check-version, deploy-to-maven-central] + environment: + name: github + url: ${{ steps.release.outputs.release_url }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: dist + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup GPG + env: + PMD_CI_GPG_PRIVATE_KEY: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + run: | + mkdir -p "${HOME}/.gpg" + chmod 700 "${HOME}/.gpg" + printenv PMD_CI_GPG_PRIVATE_KEY | gpg --batch --import + + gpg --list-keys --fingerprint --keyid-format=long + gpg --list-secret-keys --fingerprint --keyid-format=long + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + mv docs "pmd-doc-${PMD_VERSION}" + zip -qr "dist/pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + + - name: Sign files + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + run: | + cd dist + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-bin.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-src.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-doc.zip" + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1 + id: pmd-actions-helper-app-token + with: + app-id: ${{ secrets.PMD_ACTIONS_HELPER_ID }} + private-key: ${{ secrets.PMD_ACTIONS_HELPER_PRIVATE_KEY }} + owner: pmd + repositories: pmd + permission-contents: write # create a release + - name: Delete old GitHub Pre-Release + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + tagName="pmd_releases/${PMD_VERSION}-SNAPSHOT" + existingTagName="$(gh --repo pmd/pmd release list --json tagName,isPrerelease --jq ".[] | select(.tagName == \"${tagName}\" and .isPrerelease == true) | .tagName")" + + if [ "${existingTagName}" = "${tagName}" ]; then + echo "Pre-release for tag ${tagName} exists - deleting it..." + gh release delete "${tagName}" \ + --repo pmd/pmd \ + --cleanup-tag \ + --yes + else + echo "No pre-release ${tagName} exists, nothing to delete" + fi + - name: Create GitHub Release + id: release + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + release_name="PMD ${PMD_VERSION} ($(date -u +%d-%B-%Y))" + gh release create "pmd_releases/${PMD_VERSION}" \ + --repo pmd/pmd \ + --verify-tag \ + --title "$release_name" \ + --notes-file "pmd-doc-${PMD_VERSION}/pmd_release_notes.md" \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip" \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip.asc" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.xml" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.json" + echo "release_url=https://github.com/pmd/pmd/releases/tag/pmd_releases%2F${PMD_VERSION}" >> "$GITHUB_OUTPUT" + - name: Cleanup gpg + if: ${{ always() }} + run: | + rm -rf "${HOME}/.gpg" + + create-sourceforge-blog-post: + needs: [check-version, deploy-to-maven-central] + # use environment sourceforge, where secrets/vars are configured for PMD_SF_BEARER_TOKEN + environment: + name: sourceforge + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Create blog post + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_SF_BEARER_TOKEN: ${{ secrets.PMD_SF_BEARER_TOKEN }} + run: | + rendered_release_notes="$(cat docs/pmd_release_notes.md)" + echo " + * Downloads: https://github.com/pmd/pmd/releases/tag/pmd_releases%2F${PMD_VERSION} + * Documentation: https://docs.pmd-code.org/pmd-doc-${PMD_VERSION}/ + + ${rendered_release_notes}" > docs/pmd_release_notes.md + release_name="PMD ${PMD_VERSION} ($(date -u +%d-%B-%Y)) released" + + # See https://sourceforge.net/p/forge/documentation/Allura%20API/ + curl --request POST \ + --header "Authorization: Bearer ${PMD_SF_BEARER_TOKEN}" \ + --form "labels=pmd,release" \ + --form "state=published" \ + --form "text=> "$GITHUB_OUTPUT" + + create-regression-tester-baseline: + needs: [check-version, deploy-to-maven-central] + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: https://pmd-code.org/pmd-regression-tester/ + runs-on: ubuntu-latest + timeout-minutes: 60 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_branch }} + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: | + 11 + 21 + - name: Set up Ruby 3.3 + uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 #v1.286.0 + with: + ruby-version: 3.3 + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + path: | + ~/.m2/repository + ~/.gradle/caches + ~/work/pmd/target/repositories + .ci/files/vendor/bundle + key: regressiontester-${{ hashFiles('.ci/files/project-list.xml', '.ci/files/Gemfile.lock') }} + restore-keys: regressiontester- + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: pmd-dist/target + - name: Setup bundler + run: | + bundle config set --local gemfile .ci/files/Gemfile + bundle config set --local path vendor/bundle + bundle install + - name: Prepare HOME/openjdk11 + run: | + ln -sfn "${JAVA_HOME_11_X64}" "${HOME}/openjdk11" + - name: Run pmdtester + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + cd .. + rm -f .bundle/config + bundle config set --local gemfile pmd/.ci/files/Gemfile + bundle config set --local path vendor/bundle + bundle exec pmdtester \ + --mode single \ + --local-git-repo ./pmd \ + --patch-branch "pmd_releases/${PMD_VERSION}" \ + --patch-config ./pmd/.ci/files/all-regression-rules.xml \ + --list-of-project ./pmd/.ci/files/project-list.xml --html-flag \ + --threads "$(nproc)" \ + --error-recovery + pushd target/reports || { echo "Directory 'target/reports' doesn't exist"; exit 1; } + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + - name: Upload baseline + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + cd ../target/reports + baseline_name="pmd_releases_${PMD_VERSION}" + zip -q -r "${baseline_name}-baseline.zip" "${baseline_name}/" + cd ../../pmd + mv ../target/reports/"${baseline_name}-baseline.zip" . + scp "${baseline_name}-baseline.zip" pmd@pmd-code.org:/httpdocs/pmd-regression-tester/ + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 + with: + name: regression-tester-baseline + path: ${{ format('pmd_releases_{0}-baseline.zip', needs.check-version.outputs.PMD_VERSION) }} + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + upload-regression-tester-baseline-sourceforge: + needs: [check-version, deploy-to-maven-central, create-regression-tester-baseline] + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS and PMD_SF_APIKEY + environment: + name: sourceforge + url: https://sourceforge.net/projects/pmd/files/pmd-regression-tester/ + runs-on: ubuntu-latest + timeout-minutes: 60 + defaults: + run: + shell: bash + steps: + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + WEB_SF_KNOWN_HOSTS: ${{ vars.PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host web.sourceforge.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${WEB_SF_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: regression-tester-baseline + - name: Upload to sourceforge + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + uploadUrl="adangel@web.sourceforge.net:/home/frs/project/pmd/pmd-regression-tester/" + rsync -avh "pmd_releases_${PMD_VERSION}-baseline.zip" "${uploadUrl}" + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + create-docker: + needs: [check-version, github-release] + environment: + name: github + url: https://github.com/pmd/docker/actions + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1 + id: pmd-actions-helper-app-token + with: + app-id: ${{ secrets.PMD_ACTIONS_HELPER_ID }} + private-key: ${{ secrets.PMD_ACTIONS_HELPER_PRIVATE_KEY }} + owner: pmd + repositories: docker + permission-actions: write + - name: Trigger docker build + env: + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + # split semantic version by dot + IFS="." read -ra VERSION_ARRAY <<< "${PMD_VERSION}" + all_tags="" + new_tag="" + for i in "${VERSION_ARRAY[@]}"; do + if [ -z "$new_tag" ]; then + new_tag="${i}" + else + new_tag="${new_tag}.${i}" + fi + all_tags="${all_tags}${new_tag}," + done + all_tags="${all_tags}latest" + echo "version: ${PMD_VERSION}" + echo "tags: ${all_tags}" + + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/docker/actions/workflows/publish-docker-image.yaml/dispatches \ + -f "ref=main" -f "inputs[version]=${PMD_VERSION}" -f "inputs[tags]=${all_tags}" diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 00000000000..1224cd1d8d0 --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,801 @@ +name: Publish Snapshot + +on: + workflow_run: + workflows: [Build Snapshot] + types: + - completed + branches: + - main + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + LANG: 'en_US.UTF-8' + +jobs: + check-version: + # only run in the official pmd repo, where we have access to the secrets and not on forks + # and only run for _successful_ push workflow runs on branch "main". + if: ${{ github.repository == 'pmd/pmd' + && contains(fromJSON('["push", "workflow_dispatch", "schedule"]'), github.event.workflow_run.event) + && github.event.workflow_run.head_branch == 'main' + && github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + outputs: + PMD_VERSION: ${{ steps.version.outputs.PMD_VERSION }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: main + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - name: Determine Version + id: version + run: | + PMD_VERSION=$(./mvnw --batch-mode --no-transfer-progress help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "Determined PMD_VERSION=$PMD_VERSION" + if [[ "$PMD_VERSION" != *-SNAPSHOT ]]; then + echo "::error ::PMD_VERSION=$PMD_VERSION is not a snapshot version, aborting." + exit 1 + fi + echo "PMD_VERSION=$PMD_VERSION" >> "$GITHUB_OUTPUT" + - name: Add Job Summary + env: + WORKFLOW_RUN_DISPLAY_TITLE: ${{ github.event.workflow_run.display_title }} + WORKFLOW_RUN_NAME: ${{ github.event.workflow_run.name }} + WORKFLOW_RUN_NUMBER: ${{ github.event.workflow_run.run_number }} + WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} + VERSION: ${{ steps.version.outputs.PMD_VERSION }} + BRANCH: ${{ github.event.workflow_run.head_branch }} + run: | + echo "### Run Info" >> "${GITHUB_STEP_SUMMARY}" + echo "Building Version: ${VERSION}" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + echo "Branch: ${BRANCH}" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + echo "Called by [${WORKFLOW_RUN_DISPLAY_TITLE} (${WORKFLOW_RUN_NAME} #${WORKFLOW_RUN_NUMBER})](${WORKFLOW_RUN_HTML_URL})" >> "${GITHUB_STEP_SUMMARY}" + echo "" >> "${GITHUB_STEP_SUMMARY}" + + deploy-to-maven-central: + needs: check-version + # use environment maven-central, where secrets are configured for MAVEN_CENTRAL_PORTAL_* + environment: + name: maven-central + url: https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/net/sourceforge/pmd/ + runs-on: ubuntu-latest + timeout-minutes: 180 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: main + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: compile-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Build and publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_PORTAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PORTAL_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + # note: we can't use artifact staging-repository, as the jars are unsigned and javadoc+sources are missing. + run: | + ./mvnw --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + deploy \ + -DskipTests \ + -PfastSkip,sign,pmd-release \ + -Dcyclonedx.skip=false \ + -Dmaven.javadoc.skip=false \ + -Ddokka.skip=false \ + -Dmaven.source.skip=false + + deploy-to-sourceforge-files: + needs: check-version + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS + environment: + name: sourceforge + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: dist + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup GPG + env: + PMD_CI_GPG_PRIVATE_KEY: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + run: | + mkdir -p "${HOME}/.gpg" + chmod 700 "${HOME}/.gpg" + printenv PMD_CI_GPG_PRIVATE_KEY | gpg --batch --import + + gpg --list-keys --fingerprint --keyid-format=long + gpg --list-secret-keys --fingerprint --keyid-format=long + + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + WEB_SF_KNOWN_HOSTS: ${{ vars.PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host web.sourceforge.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${WEB_SF_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + mv docs "pmd-doc-${PMD_VERSION}" + zip -qr "dist/pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + + - name: Sign files + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + run: | + cd dist + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-bin.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-src.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-doc.zip" + + - name: Upload to sourceforge + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_SF_USER: adangel + run: | + # Deploy to sourceforge files https://sourceforge.net/projects/pmd/files/pmd/ + basePath="pmd/${PMD_VERSION}" + uploadUrl="${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/${basePath}/" + + rsync -avh "dist/pmd-dist-${PMD_VERSION}-bin.zip" "${uploadUrl}" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-bin.zip.asc" "${uploadUrl}" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-src.zip" "${uploadUrl}" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-src.zip.asc" "${uploadUrl}" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-doc.zip" "${uploadUrl}" + rsync -avh "dist/pmd-dist-${PMD_VERSION}-doc.zip.asc" "${uploadUrl}" + rsync -avh "dist/pmd-${PMD_VERSION}-cyclonedx.xml" "${uploadUrl}" + rsync -avh "dist/pmd-${PMD_VERSION}-cyclonedx.json" "${uploadUrl}" + rsync -avh "pmd-doc-${PMD_VERSION}/pmd_release_notes.md" "${uploadUrl}/ReadMe.md" + + targetUrl="https://sourceforge.net/projects/pmd/files/${basePath}" + echo "TargetUrl: ${targetUrl}" + echo "url_output=${targetUrl}" >> "$GITHUB_OUTPUT" + + - name: Cleanup ssh and gpg + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + rm -rf "${HOME}/.gpg" + + deploy-to-sourceforge-io: + needs: check-version + # use environment sourceforge, where secrets/vars are configured for PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY + # and PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS + environment: + name: sourceforge + url: https://pmd.sourceforge.io/snapshot/ + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup ssh key for sourceforge + env: + WEB_SF_DEPLOY_KEY: ${{ secrets.PMD_WEB_SOURCEFORGE_NET_DEPLOY_KEY }} + WEB_SF_KNOWN_HOSTS: ${{ vars.PMD_WEB_SOURCEFORGE_NET_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv WEB_SF_DEPLOY_KEY > "${HOME}/.ssh/web.sourceforge.net_deploy_key" + chmod 600 "${HOME}/.ssh/web.sourceforge.net_deploy_key" + echo " + Host web.sourceforge.net + IdentityFile=$HOME/.ssh/web.sourceforge.net_deploy_key + " > "$HOME/.ssh/config" + echo "${WEB_SF_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Upload to sourceforge + env: + PMD_SF_USER: adangel + run: | + rsync -ah --stats --delete "docs/" "${PMD_SF_USER}@web.sourceforge.net:/home/project-web/pmd/htdocs/snapshot/" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + deploy-to-pmd-code-doc: + needs: check-version + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: ${{ steps.upload.outputs.url_output }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: docs + + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: | + mv docs "pmd-doc-${PMD_VERSION}" + zip -qr "pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + + - name: Upload to pmd-code.org + id: upload + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_CODE_SSH_USER: pmd + PMD_CODE_DOCS_PATH: /docs.pmd-code.org/ + run: | + filename="pmd-dist-${PMD_VERSION}-doc.zip" + + scp "${filename}" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + ( test -h pmd-doc-${PMD_VERSION} && rm pmd-doc-${PMD_VERSION} || true ) && \ + unzip -qo \"${filename}\" && \ + rm \"${filename}\"" + + # only for snapshot builds from branch main: https://docs.pmd-code.org/snapshot -> pmd-doc-${PMD_VERSION} + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + rm -f \"snapshot\" && \ + ln -s \"pmd-doc-${PMD_VERSION}\" \"snapshot\"" + + targetUrl="https://docs.pmd-code.org/pmd-doc-${PMD_VERSION}/" + echo "TargetUrl: ${targetUrl}" + echo "url_output=${targetUrl}" >> "$GITHUB_OUTPUT" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + deploy-to-pmd-code-javadoc: + needs: check-version + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: https://docs.pmd-code.org/apidocs/ + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: javadocs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + + - name: Upload javadocs to pmd-code.org + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + PMD_CODE_SSH_USER: pmd + PMD_CODE_DOCS_PATH: /docs.pmd-code.org/ + run: | + for moduleJavadocJar in */target/*-javadoc.jar */*/target/*-javadoc.jar; do + moduleJavadocJarBasename="$(basename "$moduleJavadocJar")" + module=${moduleJavadocJarBasename%%-${PMD_VERSION}-javadoc.jar} + + echo "Copying module ${moduleJavadocJar}..." + scp "$moduleJavadocJar" ${PMD_CODE_SSH_USER}@pmd-code.org:${PMD_CODE_DOCS_PATH} + echo "Extracting remotely ${module}..." + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}\" && \ + mkdir -p \"apidocs/${module}/${PMD_VERSION}\" && \ + unzip -qo -d \"apidocs/${module}/${PMD_VERSION}\" \"${moduleJavadocJarBasename}\" && \ + rm \"${moduleJavadocJarBasename}\"" + done + + echo "(Re)creating .htaccess" + # make sure https://docs.pmd-code.org/apidocs/ shows directory index + # shellcheck disable=SC2029 + ssh ${PMD_CODE_SSH_USER}@pmd-code.org "cd \"${PMD_CODE_DOCS_PATH}/apidocs\" && \ + echo 'Options +Indexes' > .htaccess" + + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + deploy-to-github-pages: + needs: check-version + runs-on: ubuntu-latest + environment: + name: github-pages + url: https://pmd.github.io/pmd/ + timeout-minutes: 10 + permissions: + contents: write # to push to branch gh-pages + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: gh-pages + - name: Clear old files + run: rm -rf * + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Commit and push new page + env: + WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} + run: | + # https://api.github.com/users/pmd-actions-helper[bot] + git config user.name "pmd-actions-helper[bot]" + git config user.email "207160486+pmd-actions-helper[bot]@users.noreply.github.com" + git add -A + MSG="Update documentation + + Updated by: https://github.com/pmd/pmd/actions/runs/$GITHUB_RUN_ID + Triggered by: ${WORKFLOW_RUN_HTML_URL}" + + echo "Commit Message:" + echo "$MSG" + + git commit -q -m "$MSG" + git push + + github-prerelease: + needs: check-version + environment: + name: github + url: ${{ steps.release.outputs.release_url }} + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: dist + + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: docs-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: pmd-doc-${{ needs.check-version.outputs.PMD_VERSION }} + - name: Create docs zip + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + run: zip -qr "dist/pmd-dist-${PMD_VERSION}-doc.zip" "pmd-doc-${PMD_VERSION}/" + - name: Setup GPG + env: + PMD_CI_GPG_PRIVATE_KEY: ${{ secrets.PMD_CI_GPG_PRIVATE_KEY }} + run: | + mkdir -p "${HOME}/.gpg" + chmod 700 "${HOME}/.gpg" + printenv PMD_CI_GPG_PRIVATE_KEY | gpg --batch --import + + gpg --list-keys --fingerprint --keyid-format=long + gpg --list-secret-keys --fingerprint --keyid-format=long + - name: Sign files + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.PMD_CI_GPG_PASSPHRASE }} + run: | + cd dist + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-bin.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-src.zip" + printenv MAVEN_GPG_PASSPHRASE | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --no-tty \ + --status-fd 1 --armor --detach-sign --sign "pmd-dist-${PMD_VERSION}-doc.zip" + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1 + id: pmd-actions-helper-app-token + with: + app-id: ${{ secrets.PMD_ACTIONS_HELPER_ID }} + private-key: ${{ secrets.PMD_ACTIONS_HELPER_PRIVATE_KEY }} + owner: pmd + repositories: pmd + permission-contents: write # create a release, remove/create a tag + permission-workflows: write # needed for creating a tag, in case some workflow files have been modified + - name: Prepare tag for GitHub Pre-Release + id: tags + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + run: | + existingTagName="$(gh --repo pmd/pmd release list --json tagName,isPrerelease --jq ".[] | select(.isPrerelease == true) | .tagName")" + previousTagName="" + + if [ -n "${existingTagName}" ]; then + # we can't simply delete the old tag, as the existing pre-release would then become a draft and + # we would need to republish it again, which we want to avoid to avoid notifications for every pre-release. + # strategy: + # 1. create a new tag with suffix "-previous-SNAPSHOT" + previousSha="$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/pmd/pmd/git/ref/tags/${existingTagName}" --jq ".object.sha")" + previousTagName="${existingTagName}-previous-SNAPSHOT" + echo "Creating tag ${previousTagName} -> ${previousSha}" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/pmd/git/refs \ + -f "ref=refs/tags/${previousTagName}" -f "sha=${previousSha}" + # 2. update pre-release to point to this new tag + echo "Update existing pre-release to use tag ${previousTagName}" + gh release edit "${existingTagName}" \ + --repo pmd/pmd \ + --tag "${previousTagName}" \ + --verify-tag + # 3. remove the old, existing tag + echo "Deleting tag ${existingTagName}" + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/pmd/pmd/git/refs/tags/${existingTagName}" + fi + + # create a new tag + mainSha="$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/pmd/git/ref/heads/main --jq ".object.sha")" + tagName="pmd_releases/${PMD_VERSION}" + echo "Creating tag ${tagName} -> ${mainSha}" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/pmd/pmd/git/refs \ + -f "ref=refs/tags/${tagName}" -f "sha=${mainSha}" + + echo "tagName=${tagName}" >> "${GITHUB_OUTPUT}" + echo "previousTagName=${previousTagName}" >> "${GITHUB_OUTPUT}" + - name: Create or Update GitHub Pre-Release + id: release + env: + PMD_VERSION: ${{ needs.check-version.outputs.PMD_VERSION }} + GH_TOKEN: ${{ steps.pmd-actions-helper-app-token.outputs.token }} + TAG_NAME: ${{ steps.tags.outputs.tagName }} + PREVIOUS_TAG_NAME: ${{ steps.tags.outputs.previousTagName }} + run: | + release_name="PMD ${PMD_VERSION} ($(date -u +%d-%B-%Y))" + if [ -z "${PREVIOUS_TAG_NAME}" ]; then + echo "Create new release ${TAG_NAME}" + gh release create "${TAG_NAME}" \ + --repo pmd/pmd \ + --verify-tag \ + --prerelease \ + --title "$release_name" \ + --notes-file "pmd-doc-${PMD_VERSION}/pmd_release_notes.md" \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip" \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip.asc" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.xml" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.json" + else + echo "Update release ${PREVIOUS_TAG_NAME} -> ${TAG_NAME}" + gh release edit "${PREVIOUS_TAG_NAME}" \ + --repo pmd/pmd \ + --tag "${TAG_NAME}" \ + --verify-tag \ + --prerelease \ + --title "$release_name" \ + --notes-file "pmd-doc-${PMD_VERSION}/pmd_release_notes.md" + echo "Deleting old release assets" + for name in $(gh release view "${TAG_NAME}" --repo pmd/pmd --json assets --jq ".assets[].name"); do + gh release delete-asset "${TAG_NAME}" "$name" \ + --repo pmd/pmd \ + --yes + done + echo "Uploading new release assets" + gh release upload "${TAG_NAME}" \ + --repo pmd/pmd \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip" \ + "dist/pmd-dist-${PMD_VERSION}-bin.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip" \ + "dist/pmd-dist-${PMD_VERSION}-src.zip.asc" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip" \ + "dist/pmd-dist-${PMD_VERSION}-doc.zip.asc" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.xml" \ + "dist/pmd-${PMD_VERSION}-cyclonedx.json" + + echo "Deleting tag ${PREVIOUS_TAG_NAME}" + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/pmd/pmd/git/refs/tags/${PREVIOUS_TAG_NAME}" + fi + echo "release_url=https://github.com/pmd/pmd/releases/tag/pmd_releases%2F${PMD_VERSION}" >> "$GITHUB_OUTPUT" + - name: Cleanup gpg + if: ${{ always() }} + run: | + rm -rf "${HOME}/.gpg" + + create-regression-tester-baseline: + needs: check-version + runs-on: ubuntu-latest + # use environment pmd-code, where secrets/vars are configured for PMD_CODE_ORG_DEPLOY_KEY + # and PMD_CODE_ORG_KNOWN_HOSTS + environment: + name: pmd-code + url: https://pmd-code.org/pmd-regression-tester/ + timeout-minutes: 60 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: main + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: | + 11 + 21 + - name: Set up Ruby 3.3 + uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 #v1.286.0 + with: + ruby-version: 3.3 + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + path: | + ~/.m2/repository + ~/.gradle/caches + ~/work/pmd/target/repositories + .ci/files/vendor/bundle + key: regressiontester-${{ hashFiles('.ci/files/project-list.xml', '.ci/files/Gemfile.lock') }} + restore-keys: regressiontester- + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 + with: + name: dist-artifact + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + path: pmd-dist/target + - name: Setup bundler + run: | + bundle config set --local gemfile .ci/files/Gemfile + bundle config set --local path vendor/bundle + bundle install + - name: Prepare HOME/openjdk11 + run: | + ln -sfn "${JAVA_HOME_11_X64}" "${HOME}/openjdk11" + - name: Run pmdtester + run: | + cd .. + rm -f .bundle/config + bundle config set --local gemfile pmd/.ci/files/Gemfile + bundle config set --local path vendor/bundle + bundle exec pmdtester \ + --mode single \ + --local-git-repo ./pmd \ + --patch-branch "main" \ + --patch-config ./pmd/.ci/files/all-regression-rules.xml \ + --list-of-project ./pmd/.ci/files/project-list.xml --html-flag \ + --threads "$(nproc)" \ + --error-recovery + pushd target/reports || { echo "Directory 'target/reports' doesn't exist"; exit 1; } + - name: Setup ssh key for pmd-code + env: + PMD_CODE_ORG_DEPLOY_KEY: ${{ secrets.PMD_CODE_ORG_DEPLOY_KEY }} + PMD_CODE_ORG_KNOWN_HOSTS: ${{ vars.PMD_CODE_ORG_KNOWN_HOSTS }} + run: | + mkdir -p "${HOME}/.ssh" + chmod 700 "${HOME}/.ssh" + printenv PMD_CODE_ORG_DEPLOY_KEY > "${HOME}/.ssh/pmd-code.org_deploy_key" + chmod 600 "${HOME}/.ssh/pmd-code.org_deploy_key" + echo " + Host pmd-code.org + IdentityFile=$HOME/.ssh/pmd-code.org_deploy_key + " > "$HOME/.ssh/config" + echo "${PMD_CODE_ORG_KNOWN_HOSTS}" > "$HOME/.ssh/known_hosts" + - name: Upload report + run: | + cd ../target/reports + zip -q -r "main-baseline.zip" "main/" + scp "main-baseline.zip" pmd@pmd-code.org:/httpdocs/pmd-regression-tester/ + - name: Cleanup ssh + if: ${{ always() }} + run: | + rm -rf "${HOME}/.ssh" + + run-sonar: + needs: check-version + runs-on: ubuntu-latest + # use environment sonarcloud, where secrets are configured for SONAR_TOKEN + environment: + name: sonarcloud + url: https://sonarcloud.io/dashboard?id=net.sourceforge.pmd%3Apmd + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: main + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - name: Build and upload to sonar cloud + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Note: Sonar also needs GITHUB_TOKEN (!) + ./mvnw \ + --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + package \ + sonar:sonar -Psonar,fastSkip + + run-coveralls: + needs: check-version + runs-on: ubuntu-latest + # use environment sonarcloud, where secrets are configured for COVERALLS_REPO_TOKEN + environment: + name: coveralls + url: https://coveralls.io/github/pmd/pmd + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + with: + ref: main + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 #v5.0.2 + with: + key: maven-${{ hashFiles('**/pom.xml') }} + restore-keys: maven- + path: .m2/repository + enableCrossOsArchive: true + - name: Create Jacoco Report + run: | + ./mvnw \ + --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + package \ + jacoco:report -Pcoveralls,fastSkip + + # workaround, maybe https://github.com/jacoco/jacoco/issues/654 + # we use $ as a regex separator, not a shell variable, so no expansion + # shellcheck disable=SC2016 + sed -i 's$Comparisons.kt$ApexTreeBuilder.kt$g' pmd-apex/target/site/jacoco/jacoco.xml + - name: Build and upload to coveralls report + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + # note: generate-sources is needed, so that antlr4 generated directories are on the compileSourceRoots + # test-compile is needed to that dependency resolution within this multi-module build works + ./mvnw \ + --show-version --errors --batch-mode \ + -Dmaven.repo.local=.m2/repository \ + generate-sources test-compile \ + coveralls:report -DrepoToken="${COVERALLS_REPO_TOKEN}" -Pcoveralls,fastSkip diff --git a/.github/workflows/troubleshooting.yml b/.github/workflows/troubleshooting.yml deleted file mode 100644 index 91351ed96f4..00000000000 --- a/.github/workflows/troubleshooting.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: troubleshooting - -on: workflow_dispatch - -permissions: - contents: read - -jobs: - build: - runs-on: ${{ matrix.os }} - continue-on-error: false - strategy: - matrix: - #os: [ ubuntu-latest, windows-latest, macos-latest ] - os: [ ubuntu-latest ] - - steps: - - uses: actions/checkout@v4 - - uses: actions/cache@v4 - with: - path: | - ~/.m2/repository - ~/.gradle/caches - ~/.cache - ~/work/pmd/target/repositories - vendor/bundle - key: v3-${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - restore-keys: | - v3-${{ runner.os }}- - - name: Set up Ruby 3.3 - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.3 - - name: Setup Environment - shell: bash - run: | - echo "LANG=en_US.UTF-8" >> $GITHUB_ENV - echo "MAVEN_OPTS=-Daether.connector.http.connectionMaxTtl=180 -DstagingProgressTimeoutMinutes=30" >> $GITHUB_ENV - echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/27/scripts" >> $GITHUB_ENV - - name: Check Environment - shell: bash - run: | - f=check-environment.sh; \ - mkdir -p .ci && \ - ( [ -e .ci/$f ] || curl -sSL "${PMD_CI_SCRIPTS_URL}/$f" > ".ci/$f" ) && \ - chmod 755 .ci/$f && \ - .ci/$f - - name: Build - run: | - f=openjdk.bash; \ - mkdir -p .ci/inc && \ - ( [ -e .ci/inc/$f ] || curl -sSL "${PMD_CI_SCRIPTS_URL}/inc/$f" > ".ci/inc/$f" ) && \ - source .ci/inc/$f ; \ - pmd_ci_openjdk_install_adoptium 11 ; \ - pmd_ci_openjdk_setdefault 11 - shell: bash - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 diff --git a/.gitignore b/.gitignore index a4967e344f0..95978b24728 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ bin/ .ruleset .settings/ *.iml -.idea +.idea/* +!.idea/icon.svg +!.idea/vcs.xml *.patch */src/site/site.xml pmd-core/dependency-reduced-pom.xml @@ -21,4 +23,4 @@ node_modules # rule docs are generated docs/pages/pmd/rules -.history/* \ No newline at end of file +.history/* diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 00000000000..6782d8aaa76 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000000..39eb3fa282f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index f95f1ee8071..8dea6c227c0 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/BUILDING.md b/BUILDING.md deleted file mode 100644 index 6a44fb82db0..00000000000 --- a/BUILDING.md +++ /dev/null @@ -1,44 +0,0 @@ -# How to build PMD - -PMD uses [Maven](https://maven.apache.org/) and requires at least Java 11 for building. -You can get Java 11 from [Oracle](http://www.oracle.com/technetwork/java/javase/downloads/index.html) -or from [AdoptOpenJdk](https://adoptopenjdk.net/). - -PMD uses the [maven wrapper](https://maven.apache.org/wrapper/), so you can simply build PMD as following: - -* `./mvnw clean verify` (on Unix-like platform such as Linux and Mac OS X) -* `mvnw.cmd clean verify` (on Windows) - -This will create the zip files in the directory `pmd-dist/target`: - - cd pmd-dist/target - ls *.zip - -That's all ! - -**Note:** While Java 11 is required for building, running PMD only requires Java 7 -(or Java 8 for Apex, JavaScript, Scala, Visualforce, and the Designer). - -**Note:** With PMD 6.24.0, we are creating [Reproducible Builds](https://reproducible-builds.org/). Since we use -[Maven](https://maven.apache.org/guides/mini/guide-reproducible-builds.html) for building, the following -limitations apply: - -* Generally give **different results on Windows and Unix** because of different newlines. - (carriage return linefeed on Windows, linefeed on Unixes). - - We build our releases under **Linux** on [Github Actions](https://github.com/pmd/pmd/actions). - -* Generally depend on the **major version of the JDK** used to compile. (Even with source/target defined, - each major JDK version changes the generated bytecode.). - - We build our releases using OpenJDK 11. - -## How to build the documentation? - - cd docs - bundle install # once - bundle exec jekyll build - -You'll find the built site in the directory `_site/`. - -For more info, see [README in docs directory](docs/README.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1622cb421f7..b94594f559d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,45 +2,22 @@ First off, thanks for taking the time to contribute! -Please note that this project is released with a Contributor Code of Conduct. -By participating in this project you agree to abide by its terms. +Detailed contributor info can be found in our documentation on . -You can find the code of conduct in the file [code_of_conduct.md](code_of_conduct.md). +**Pull Requests** are welcome. Create them against the `main` branch. -| NB: the rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Please refer to the specific [contributor documentation](https://github.com/pmd/pmd-designer/blob/main/CONTRIBUTING.md) if your issue, feature request or PR touches the designer. | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +**Building:** Make sure, you can build PMD locally. You need Java 11+, then run `./mvnw clean verify`. +Guides for using various IDEs are available at . -## Pull requests +**Code of Conduct:** -* Please create your pull request against the `main` branch. We will rebase/merge it to the maintenance - branches, if necessary. +**Bug Reports / Features:** We use the issue tracker on GitHub. Please report new bugs and feature requests at . -* We are using [checkstyle](http://checkstyle.sourceforge.net/) to enforce a common code style. - The check is integrated into the default build - so, make sure, you can [build PMD](BUILDING.md) without errors. - See [code style](#code-style) for more info. +**Reporting Security Issues:** +**Main Documentation:** -## Bug reports - -We use the issue tracker on Github. Please report new bugs at . - -When filing a bug report, please provide as much information as possible, so that we can reproduce the issue: - -* The name of the rule, that is buggy -* A code snippet, which triggers a false positive/negative or crash -* How do you execute PMD? (command line, ant, maven, gradle, other) - - -## Documentation - -There is some documentation available under . Feel free to create a bug report if -documentation is missing, incomplete or outdated. See [Bug reports](#bug-reports). - -The documentation is generated as a Jekyll site, the source is available at: . You can find build instructions there. -For more on contributing documentation check - -## Questions - +**Questions:** There are various channels, on which you can ask questions: * On [StackOverflow](https://stackoverflow.com/questions/tagged/pmd): Make sure, to tag your question with "pmd". @@ -49,28 +26,4 @@ There are various channels, on which you can ask questions: * Ask your question in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im). -## Code Style - -PMD uses [checkstyle](http://checkstyle.sourceforge.net/) to enforce a common code style. - -See [pmd-checkstyle-config.xml](https://github.com/pmd/build-tools/blob/main/src/main/resources/net/sourceforge/pmd/pmd-checkstyle-config.xml) for the configuration and -[the eclipse configuration files](https://github.com/pmd/build-tools/tree/main/eclipse) that can -be imported into a fresh workspace. - -## Add yourself as contributor - -We use [All Contributors](https://allcontributors.org/en). - -To add yourself to the table of contributors, follow the -[bot usage instructions](https://allcontributors.org/docs/en/bot/usage) ;). - -Or use the CLI: - -1. Install the CLI: `npm i` (in PMD's top level directory) -2. Add yourself: `npx all-contributors add ` - -Where `username` is your GitHub username and `contribution` is a `,`-separated list -of contributions. See [Emoji Key](https://allcontributors.org/docs/en/emoji-key) for a list -of valid types. Common types are: "code", "doc", "bug", "blog", "talk", "test", "tutorial". - -See also [cli documentation](https://allcontributors.org/docs/en/cli/usage) +* Ask your question our [PMD Guru at Gurubase](https://gurubase.io/g/pmd). diff --git a/Dangerfile b/Dangerfile deleted file mode 100644 index dea08b69604..00000000000 --- a/Dangerfile +++ /dev/null @@ -1,98 +0,0 @@ -require 'pmdtester' -require 'time' -require 'logger' -require 'fileutils' -require 'etc' - -@logger = Logger.new(STDOUT) - -def get_args(base_branch, autogen = true, patch_config = './pmd/.ci/files/all-regression-rules.xml') - ['--local-git-repo', './pmd', - '--list-of-project', './pmd/.ci/files/project-list.xml', - '--base-branch', base_branch, - '--patch-branch', 'HEAD', - '--patch-config', patch_config, - '--mode', 'online', - autogen ? '--auto-gen-config' : '--filter-with-patch-config', - '--keep-reports', - '--error-recovery', - '--baseline-download-url', 'https://pmd-code.org/pmd-regression-tester/', - '--threads', Etc.nprocessors.to_s, - # '--debug', - ] -end - -def run_pmdtester - Dir.chdir('..') do - begin - @base_branch = ENV['PMD_CI_BRANCH'] - @logger.info "\n\n--------------------------------------" - @logger.info "Run against PR base #{@base_branch}" - @summary = PmdTester::Runner.new(get_args(@base_branch)).run - - unless Dir.exist?('target/reports/diff') - message("No regression tested rules have been changed.", sticky: true) - return - end - - # move the generated report out of the way - FileUtils.mv 'target/reports/diff', 'target/diff1' - message1 = create_message - - # run against main branch (if the PR is not already against main) - unless ENV['PMD_CI_BRANCH'] == 'main' - @base_branch = 'main' - @logger.info "\n\n--------------------------------------" - @logger.info "Run against #{@base_branch}" - @summary = PmdTester::Runner.new(get_args(@base_branch, false, 'target/diff1/patch_config.xml')).run - - # move the generated report out of the way - FileUtils.mv 'target/reports/diff', 'target/diff2' - message2 = create_message - end - - tar_report - - message1 += "[Download full report as build artifact](#{ENV['PMD_CI_JOB_URL']}?pr=#{ENV['PMD_CI_PULL_REQUEST_NUMBER']})" - # set value of sticky to true and the message is kept after new commits are submitted to the PR - message(message1, sticky: true) - - if message2 - message2 += "[Download full report as build artifact](#{ENV['PMD_CI_JOB_URL']}?pr=#{ENV['PMD_CI_PULL_REQUEST_NUMBER']})" - # set value of sticky to true and the message is kept after new commits are submitted to the PR - message(message2, sticky: true) - end - - rescue StandardError => e - warn("Running pmdtester failed, this message is mainly used to remind the maintainers of PMD.") - @logger.error "Running pmdtester failed: #{e.inspect}" - end - end -end - -def create_message - "Compared to #{@base_branch}:\n"\ - "This changeset " \ - "changes #{@summary[:violations][:changed]} violations,\n" \ - "introduces #{@summary[:violations][:new]} new violations, " \ - "#{@summary[:errors][:new]} new errors and " \ - "#{@summary[:configerrors][:new]} new configuration errors,\n" \ - "removes #{@summary[:violations][:removed]} violations, "\ - "#{@summary[:errors][:removed]} errors and " \ - "#{@summary[:configerrors][:removed]} configuration errors.\n" -end - -def tar_report - Dir.chdir('target') do - tar_filename = "pr-#{ENV['PMD_CI_PULL_REQUEST_NUMBER']}-diff-report-#{Time.now.strftime("%Y-%m-%dT%H-%M-%SZ")}.tar.gz" - - `tar czf #{tar_filename} diff1/ diff2/` - tar_size = (10 * File.size(tar_filename) / 1024 / 1024)/10.0 - @logger.info "Created file #{tar_filename} (#{tar_size}mb)" - end -end - -# Perform regression testing -run_pmdtester - -# vim: syntax=ruby diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 6fccc49416c..00000000000 --- a/Gemfile +++ /dev/null @@ -1,19 +0,0 @@ -source 'https://rubygems.org/' - -# bleeding edge from git -#gem 'pmdtester', :git => 'https://github.com/pmd/pmd-regression-tester.git', branch: 'main' - -gem 'pmdtester' -gem 'danger' - -# This group is only needed for rendering release notes (docs/render_release_notes.rb) -# this happens during release (.ci/build.sh and do-release.sh) -# but also during regular builds (.ci/build.sh) -group :release_notes_preprocessing do - gem 'liquid' - gem 'safe_yaml' - gem 'rouge' - gem 'bigdecimal' -end - -# vim: syntax=ruby diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b8adad72722..00000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,101 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - bigdecimal (3.1.8) - claide (1.1.0) - claide-plugins (0.9.2) - cork - nap - open4 (~> 1.3) - colored2 (3.1.2) - concurrent-ruby (1.3.4) - cork (0.3.0) - colored2 (~> 3.1) - danger (9.5.0) - claide (~> 1.0) - claide-plugins (>= 0.9.2) - colored2 (~> 3.1) - cork (~> 0.1) - faraday (>= 0.9.0, < 3.0) - faraday-http-cache (~> 2.0) - git (~> 1.13) - kramdown (~> 2.3) - kramdown-parser-gfm (~> 1.0) - octokit (>= 4.0) - terminal-table (>= 1, < 4) - differ (0.1.2) - et-orbi (1.2.11) - tzinfo - faraday (2.11.0) - faraday-net_http (>= 2.0, < 3.4) - logger - faraday-http-cache (2.5.1) - faraday (>= 0.8) - faraday-net_http (3.3.0) - net-http - fugit (1.11.1) - et-orbi (~> 1, >= 1.2.11) - raabro (~> 1.4) - git (1.19.1) - addressable (~> 2.8) - rchardet (~> 1.8) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (5.5.1) - logger (1.6.0) - logger-colors (1.0.0) - nap (1.1.0) - net-http (0.4.1) - uri - nokogiri (1.16.7-x86_64-linux) - racc (~> 1.4) - octokit (9.1.0) - faraday (>= 1, < 3) - sawyer (~> 0.9) - open4 (1.3.4) - pmdtester (1.5.5) - differ (~> 0.1) - liquid (~> 5.4) - logger-colors (~> 1.0) - nokogiri (~> 1.13) - rufus-scheduler (~> 3.8) - slop (~> 4.9) - public_suffix (6.0.1) - raabro (1.4.0) - racc (1.8.1) - rchardet (1.8.0) - rexml (3.3.6) - strscan - rouge (4.4.0) - rufus-scheduler (3.9.1) - fugit (~> 1.1, >= 1.1.6) - safe_yaml (1.0.5) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - slop (4.10.1) - strscan (3.1.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) - uri (0.13.1) - -PLATFORMS - x86_64-linux - -DEPENDENCIES - bigdecimal - danger - liquid - pmdtester - rouge - safe_yaml - -BUNDLED WITH - 2.5.3 diff --git a/README.md b/README.md index 7a4fde521ce..71ec501be7a 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ ![PMD Logo](https://raw.githubusercontent.com/pmd/pmd/main/docs/images/logo/pmd-logo-300px.png) [![Join the chat](https://img.shields.io/gitter/room/pmd/pmd)](https://app.gitter.im/#/room/#pmd_pmd:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://github.com/pmd/pmd/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/pmd/pmd/actions) +[![Build Snapshot](https://github.com/pmd/pmd/actions/workflows/build-snapshot.yml/badge.svg?branch=main)](https://github.com/pmd/pmd/actions/workflows/build-snapshot.yml) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd) [![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-central/tree/master/content/net/sourceforge/pmd#readme) -[![Coverage Status](https://coveralls.io/repos/github/pmd/pmd/badge.svg)](https://coveralls.io/github/pmd/pmd) +[![Coverage Status](https://coveralls.io/repos/github/pmd/pmd/badge.svg?branch=main&v=1)](https://coveralls.io/github/pmd/pmd?branch=main) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ea550046a02344ec850553476c4aa2ca)](https://app.codacy.com/organizations/gh/pmd/dashboard) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](code_of_conduct.md) [![Documentation (latest)](https://img.shields.io/badge/docs-latest-green)](https://docs.pmd-code.org/latest/) +[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20PMD%20Guru-006BFF)](https://gurubase.io/g/pmd) +[![Docker Image Version](https://img.shields.io/docker/v/pmdcode/pmd?sort=semver&label=Docker)](https://hub.docker.com/r/pmdcode/pmd) **PMD** is an extensible multilanguage static code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It's mainly concerned with **Java and @@ -18,15 +20,15 @@ extended with custom rules. It uses JavaCC and Antlr to parse source files into (AST) and runs rules against them to find violations. Rules can be written in Java or using a XPath query. Currently, PMD supports Java, JavaScript, Salesforce.com Apex and Visualforce, -Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL. +Kotlin, Swift, Modelica, PL/SQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL. Scala is supported, but there are currently no Scala rules available. Additionally, it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in -Coco, C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin, -Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and +Coco, C/C++, C#, CSS, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin, +Lua, Matlab, Modelica, Objective-C, Perl, PHP, PL/SQL, Python, Ruby, Salesforce.com Apex and Visualforce, Scala, Swift, T-SQL, Typescript, Apache Velocity, WSDL, XML and XSL. -## 🚀 Installation and Usage +## 🚀️ Installation and Usage Download the latest binary zip from the [releases](https://github.com/pmd/pmd/releases/latest) and extract it somewhere. @@ -52,33 +54,33 @@ See [Tools / Integrations](https://docs.pmd-code.org/latest/pmd_userdocs_tools.h or on [discussions](https://github.com/pmd/pmd/discussions). * I got this error and I'm sure it's a bug -- file an [issue](https://github.com/pmd/pmd/issues). * I have an idea/request/question -- create a new [discussion](https://github.com/pmd/pmd/discussions). -* I have a quick question -- ask in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im). +* I have a quick question -- ask in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im) + or our [PMD Guru at Gurubase](https://gurubase.io/g/pmd). * Where's your documentation? -- -## 🤝 Contributing +## 🤝️ Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Our latest source of PMD can be found on [GitHub](https://github.com/pmd/pmd). Fork us! -* [How to build PMD](BUILDING.md) -* [How to contribute to PMD](CONTRIBUTING.md) +For details, see [How to contribute to PMD](https://docs.pmd-code.org/latest/pmd_devdocs_contributing.html). The rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Please see [its README](https://github.com/pmd/pmd-designer#contributing) for developer documentation. -## 💵 Financial Contributors +## 💵️ Financial Contributors Become a financial contributor and help us sustain our community. [Contribute](https://opencollective.com/pmd/contribute) -## ✨ Contributors +## ✨️ Contributors This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! See [credits](docs/pages/pmd/projectdocs/credits.md) for the complete list. -## 📝 License +## 📝️ License [BSD Style](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..8ade2dfa24b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Reporting Security Issues + +The PMD team and community take security bugs in PMD seriously. We appreciate your efforts to responsibly disclose +your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/pmd/pmd/security/advisories/new) tab. + +The PMD team will send a response indicating the next steps in handling your report. After the initial reply to your +report, the security team will keep you informed of the progress towards a fix and full announcement, +and may ask for additional information or guidance. + +Report security bugs in third-party modules to the person or team maintaining the module. diff --git a/SPONSORS.md b/SPONSORS.md index 6376a0e9725..92b8fe2fe4b 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -2,11 +2,14 @@ Many thanks to all our sponsors: -* [Matt Hargett](https://github.com/matthargett) (@matthargett) -* [Oliver Siegmar](https://github.com/osiegmar) (@osiegmar) -* [screamingfrog](https://github.com/screamingfrog) (@screamingfrog) -* John Kuhl via opencollective -* [flxbl-io](https://github.com/flxbl-io) (@flxbl-io) +* [Matt Hargett](https://github.com/matthargett) (@matthargett) (7/2022 till 3/2023) +* [Oliver Siegmar](https://github.com/osiegmar) (@osiegmar) (9/2022) +* [screamingfrog](https://github.com/screamingfrog) (@screamingfrog) (11/2022) +* John Kuhl via opencollective (11/2023) +* [flxbl-io](https://github.com/flxbl-io) (@flxbl-io) (12/2024 till 05/2025) +* [Jonathan Gillespie](https://github.com/jongpie) (@jongpie) (12/2024 till 04/2025) +* [Cybozu](https://github.com/cybozu) (@cybozu) (05/2025 till 09/2025) +* [Cybozu](https://opencollective.com/cybozu) via opencollective (since 10/2025) If you also want to sponsor PMD, you have two options: diff --git a/antlr4-wrapper.xml b/antlr4-wrapper.xml index 7a827340227..fac0445d012 100644 --- a/antlr4-wrapper.xml +++ b/antlr4-wrapper.xml @@ -11,10 +11,11 @@ - root-node-name: name of the root node without prefix (eg "TopLevel"), will be made to implement RootNode See AntlrGeneratedParserBase - --> + + @@ -32,14 +33,100 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -72,7 +159,9 @@ tofile="${parser-file}"/> - + + @@ -83,7 +172,9 @@ - + + diff --git a/do-release.sh b/do-release.sh index 3440db6fc50..6248163f8db 100755 --- a/do-release.sh +++ b/do-release.sh @@ -22,16 +22,14 @@ fi # set +e # don't stop for error "command not found" - it is handled ruby_version_full=$(ruby --version 2>&1) -ruby_version=$(echo "${ruby_version_full}" | grep "ruby 3" | head -1 2>&1) -if [ $? -eq 0 ] && [ -n "${ruby_version}" ]; then +if ruby_version=$(echo "${ruby_version_full}" | grep "ruby 3" | head -1 2>&1) && [ -n "${ruby_version}" ]; then echo "Using ${ruby_version_full}" else echo "Wrong ruby version! Expected ruby 3" echo "${ruby_version_full}" exit 1 fi -bundler_version=$(bundler --version 2>&1) -if [ $? -eq 0 ]; then +if bundler_version=$(bundler --version 2>&1); then echo "Using ${bundler_version}" else echo "Missing bundler!" @@ -95,6 +93,22 @@ echo echo "Press enter to continue... (or CTRL+C to cancel)" read -r +if [ -z "$GITHUB_TOKEN" ]; then + echo + echo "Please enter a GITHUB_TOKEN (https://github.com/settings/tokens) that can be used to query github" + echo "when generating release notes. If you don't have one, you can just press enter, then anonymous access" + echo "will be used, but access might be rate limited (https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28)." + echo + echo -n "GITHUB_TOKEN=" + IFS= read -r GITHUB_TOKEN + if [ -n "$GITHUB_TOKEN" ]; then + export GITHUB_TOKEN + echo "Using provided GITHUB_TOKEN..." + else + echo "Not using GITHUB_TOKEN" + fi +fi + export LAST_VERSION export RELEASE_VERSION export DEVELOPMENT_VERSION @@ -108,6 +122,9 @@ if [ "${BUILD_TOOLS_VERSION}" != "${BUILD_TOOLS_VERSION_RELEASE}" ]; then exit 1 fi +echo "* Run '.ci/tools/check-all-contributors $RELEASE_VERSION' and update contributors" +echo " by calling 'npx all-contributors add bug,code' accordingly" +echo echo "* Update version info in **docs/_config.yml**." echo " remove the SNAPSHOT from site.pmd.version" echo @@ -120,65 +137,32 @@ echo echo "* Update **../pmd.github.io/_config.yml** to mention the new release" echo echo "* Update property \`pmd-designer.version\` in **pom.xml** to reference the version, that will be released" -echo " later in this process." +echo " later in this process. ⚠️ WARNING! This does not work. You need to select an already released version." +echo " See ." echo echo "Press enter to continue..." read -r - -# determine current milestone -MILESTONE_JSON=$(curl -s "https://api.github.com/repos/pmd/pmd/milestones?state=all&direction=desc&per_page=5"|jq ".[] | select(.title == \"$RELEASE_VERSION\")") -MILESTONE=$(echo "$MILESTONE_JSON" | jq .number) - -# determine dependency updates -DEPENDENCIES_JSON=$(curl -s "https://api.github.com/repos/pmd/pmd/issues?labels=dependencies&state=closed&direction=asc&per_page=50&page=1&milestone=${MILESTONE}") -DEPENDENCIES_COUNT=$(echo "$DEPENDENCIES_JSON" | jq length) -DEPENDENCIES="" -if [ $DEPENDENCIES_COUNT -gt 0 ]; then - DEPENDENCIES=$( - echo "### 📦 Dependency updates" - echo "$DEPENDENCIES_JSON" | jq --raw-output '.[] | "* [#\(.number)](https://github.com/pmd/pmd/issues/\(.number)): \(.title)"' - ) -else - DEPENDENCIES=$( - echo "### 📦 Dependency updates" - echo "No dependency updates" - ) -fi - -# calculating stats for release notes (excluding dependency updates) -STATS_CLOSED_ISSUES=$(echo "$MILESTONE_JSON" | jq .closed_issues) -STATS=$( -echo "### 📈 Stats" -echo "* $(git log pmd_releases/"${LAST_VERSION}"..HEAD --oneline --no-merges |wc -l) commits" -echo "* $(($STATS_CLOSED_ISSUES - $DEPENDENCIES_COUNT)) closed tickets & PRs" -echo "* Days since last release: $(( ( $(date +%s) - $(git log --max-count=1 --format="%at" pmd_releases/"${LAST_VERSION}") ) / 86400))" -) - - -TEMP_RELEASE_NOTES=$(cat docs/pages/release_notes.md) -TEMP_RELEASE_NOTES=${TEMP_RELEASE_NOTES/\{\% endtocmaker \%\}/${DEPENDENCIES//\&/\\\&}$'\n'$'\n'${STATS//\&/\\\&}$'\n'$'\n'\{\% endtocmaker \%\}} -echo "${TEMP_RELEASE_NOTES}" > docs/pages/release_notes.md +# updating release notes +.ci/tools/release-notes-generate.sh "$LAST_VERSION" "$RELEASE_VERSION" echo -echo "Updated dependencies and stats in release notes:" -echo "$DEPENDENCIES" -echo "$STATS" -echo +echo "Updated merged pull requests, dependency updates and stats in release notes:" echo "Please verify docs/pages/release_notes.md" echo echo "Press enter to continue..." read -r -# install bundles needed for rendering release notes +# install bundles needed for rendering release notes and execute rendering +pushd docs || { echo "Directory 'docs' doesn't exist"; exit 1; } bundle config set --local path vendor/bundle -bundle config set --local with release_notes_preprocessing bundle install +NEW_RELEASE_NOTES=$(bundle exec render_release_notes.rb pages/release_notes.md | tail -n +6) +popd || exit 1 RELEASE_NOTES_POST="_posts/$(date -u +%Y-%m-%d)-PMD-${RELEASE_VERSION}.md" export RELEASE_NOTES_POST echo "Generating ../pmd.github.io/${RELEASE_NOTES_POST}..." -NEW_RELEASE_NOTES=$(bundle exec docs/render_release_notes.rb docs/pages/release_notes.md | tail -n +6) cat > "../pmd.github.io/${RELEASE_NOTES_POST}" <HEAD|pmd_releases/${RELEASE_VERSION}|" pom.xml -echo "Run the project tests against the changed POMs to confirm everything is in running order (skipping cli and dist)" -# note: skipping pmd in order to avoid failures due to #4757 -./mvnw clean verify -Dskip-cli-dist -Dpmd.skip=true -Dcpd.skip=true -Pgenerate-rule-docs +echo "Run the project tests against the changed POMs to confirm everything is in running order" +./mvnw clean verify -Pgenerate-rule-docs echo "Commit and create tag" git commit -a -m "[release] prepare release pmd_releases/${RELEASE_VERSION}" git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}" "pmd_releases/${RELEASE_VERSION}" @@ -225,16 +208,17 @@ git push origin tag "pmd_releases/${RELEASE_VERSION}" echo echo "Tag has been pushed.... now check github actions: " echo -echo "Now wait, until first stage of the release is finished successfully..." -echo "You don't need to wait until artifacts are in maven central, just the GitHub Action must be successful." +echo "Now wait, until the workflows 'Build Release' and 'Publish Release' finished successfully..." +echo "You don't need to wait until artifacts are in maven central, just the GitHub Actions must be successful." echo -echo "If it is failing, you can fix the code/scripts and force push the tag via" +echo "If it is failing already at the first job (deploy-to-maven-central), you can fix the code/scripts and force push the tag via" echo echo " git tag -d \"pmd_releases/${RELEASE_VERSION}\"" echo " git tag -m \"[release] copy for tag pmd_releases/${RELEASE_VERSION}\" \"pmd_releases/${RELEASE_VERSION}\"" echo " git push origin tag \"pmd_releases/${RELEASE_VERSION}\" --force" echo echo "However: This is only possible, if the artefacts have not been pushed to maven central yet..." +echo " And doing this later will destroy reproducible builds." echo echo "Press enter to continue, once the GitHub Action finished successfully..." read -r @@ -276,6 +260,16 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- +{% if is_release_notes_processor %} +{% comment %} +This allows to use links e.g. [Basic CLI usage]({{ baseurl }}pmd_userdocs_installation.html) that work both +in the release notes on GitHub (as an absolute url) and on the rendered documentation page (as a relative url). +{% endcomment %} +{% capture baseurl %}https://docs.pmd-code.org/pmd-doc-{{ site.pmd.version }}/{% endcapture %} +{% else %} +{% assign baseurl = "" %} +{% endif %} + ## {{ site.pmd.date | date: "%d-%B-%Y" }} - {{ site.pmd.version }} The PMD team is pleased to announce PMD {{ site.pmd.version }}. @@ -284,53 +278,30 @@ This is a {{ site.pmd.release_type }} release. {% tocmaker is_release_notes_processor %} -### 🚀 New and noteworthy +### 🚀️ New and noteworthy + +### 🐛️ Fixed Issues -### 🐛 Fixed Issues +### 🚨️ API Changes -### 🚨 API Changes +### ✨️ Merged pull requests + -### ✨ External Contributions +### 📦️ Dependency updates + + +### 📈️ Stats + {% endtocmaker %} EOF echo "Committing current changes on branch ${CURRENT_BRANCH}" -# note: using [skip ci] as only the first stage is done and the full build -# requires pmd-designer to be present, which might not be the case yet... -git commit -a -m "[release] Prepare next development version [skip ci]" +git commit -a -m "[release] Prepare next development version" echo "Push branch ${CURRENT_BRANCH}" git push origin "${CURRENT_BRANCH}" -echo -echo -echo -echo "* Wait until the new version is synced to maven central and appears as latest version in" -echo " ." -echo -echo -echo "Then proceed with releasing pmd-designer..." -echo "" -echo -echo "Press enter to continue when pmd-designer is available in maven-central..." -echo "." -echo -echo "Note: If there is no new pmd-designer release needed, you can directly proceed." -read -r - -echo -echo "Continuing with release of pmd-cli and pmd-dist..." -echo "Before proceeding however, wait another 10 minutes, so that the freshly released artefacts" -echo "are indeed available from maven central. The GitHub runners might not yet see them..." -echo "If that happens, the build job needs to be started again, maybe the runner cache needs to be cleared as well." -echo -echo "Go to and manually trigger a new build" -echo "from tag 'pmd_releases/${RELEASE_VERSION}' and with option 'Build only modules cli and dist' checked." -echo -echo "This triggers the second stage release and eventually publishes the release on GitHub." -echo -echo "Now check github actions: " echo echo echo "Verification: (see also )" @@ -343,13 +314,12 @@ echo " * Default download should be new version" echo " * All assets are there (bin, src, doc, cyclondx.json, cyclondx.xml, ReadMe.md)" echo "* News entry on sourceforge: " echo "* Latest documentation points to new release: " -echo "* JavaDoc API Doc is available: " -echo "* All artefacts are on maven central, especially pmd-cli" -echo " * " +echo "* JavaDoc API Doc is available: " +echo "* All artefacts are on maven central" echo " * " echo " * " -echo " * " echo "* Regression Tester baseline has been created: " +echo "* Docker images have been created: / " echo echo "* Send out an announcement mail to the mailing list:" echo diff --git a/docs/Gemfile b/docs/Gemfile index a5fccd840b0..9f7c3b733cd 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -4,3 +4,7 @@ gem 'jekyll' gem 'github-pages' gem "webrick" gem "csv" +gem 'liquid' +gem 'safe_yaml' +gem 'rouge' +gem 'bigdecimal' diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index a8439253e33..170786af6db 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,8 +1,9 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.2.1) + activesupport (8.0.2) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) @@ -12,35 +13,40 @@ GEM minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) base64 (0.2.0) - bigdecimal (3.1.8) + benchmark (0.4.1) + bigdecimal (4.0.1) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.12.2) colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) - csv (3.3.0) - dnsruby (1.72.2) + commonmarker (0.23.11) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + csv (3.3.5) + dnsruby (1.72.4) + base64 (~> 0.2.0) + logger (~> 1.6.5) simpleidn (~> 0.2.1) - drb (2.2.1) + drb (2.2.3) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.9.1) - faraday (2.11.0) - faraday-net_http (>= 2.0, < 3.4) + execjs (2.10.0) + faraday (2.13.3) + faraday-net_http (>= 2.0, < 3.5) + json logger - faraday-net_http (3.3.0) - net-http - ffi (1.17.0-x86_64-linux-gnu) + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.17.2-x86_64-linux-gnu) forwardable-extended (2.6.0) gemoji (4.1.0) github-pages (232) @@ -99,7 +105,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.14.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) jekyll (3.10.0) addressable (~> 2.4) @@ -211,6 +217,7 @@ GEM gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) + json (2.13.0) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) @@ -219,16 +226,16 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.0) + logger (1.6.6) mercenary (0.3.6) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.25.1) - net-http (0.4.1) + minitest (5.25.5) + net-http (0.6.0) uri - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) @@ -240,10 +247,9 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.3.6) - strscan + rexml (3.4.4) rouge (3.30.0) - rubyzip (2.3.2) + rubyzip (2.4.1) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -253,9 +259,8 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - securerandom (0.3.1) + securerandom (0.4.1) simpleidn (0.2.3) - strscan (3.1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) typhoeus (1.4.1) @@ -263,17 +268,21 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (1.8.0) - uri (0.13.1) - webrick (1.8.2) + uri (1.0.4) + webrick (1.9.2) PLATFORMS x86_64-linux DEPENDENCIES + bigdecimal csv github-pages jekyll + liquid + rouge + safe_yaml webrick BUNDLED WITH - 2.5.3 + 2.6.6 diff --git a/docs/_config.yml b/docs/_config.yml index de55fdfd104..20df81e5541 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,9 +1,9 @@ repository: pmd/pmd pmd: - version: 7.6.0 - previous_version: 7.5.0 - date: 2024-09-27 + version: 7.21.0 + previous_version: 7.20.0 + date: 2026-01-30 # release types: major, minor, bugfix release_type: minor diff --git a/docs/_data/definitions.yml b/docs/_data/definitions.yml deleted file mode 100644 index 0da85d3d87b..00000000000 --- a/docs/_data/definitions.yml +++ /dev/null @@ -1,9 +0,0 @@ -elephant: "This is a sample definition." - -baseball: "Baseball is considered America's pasttime sport, though that may be more of a historical term than a current one. There's a lot more excitement about football than baseball. A baseball game is somewhat of a snooze to watch, for the most part." - -basketball: "Basketball is a sport involving two teams of five players each competing to put a ball through a small circular rim 10 feet above the ground. Basketball requires players to be in top physical condition, since they spend most of the game running back and forth along a 94-foot-long floor." - -football: "No doubt the most fun sport to watch, football also manages to accrue the most injuries with the players. From concussions to blown knees, football players have short sport lives." - -soccer: "If there's one sport that dominates the world landscape, it's soccer. However, US soccer fans are few and far between. Apart from the popularity of soccer during the World Cup, most people don't even know the name of the professional soccer organization in their area." \ No newline at end of file diff --git a/docs/_data/glossary.yml b/docs/_data/glossary.yml deleted file mode 100644 index e953fe73bfd..00000000000 --- a/docs/_data/glossary.yml +++ /dev/null @@ -1,11 +0,0 @@ -jekyll_platform: "Jekyll is a static site generator that builds sites using most modern web technologies." - -fractious: "Like a little mischevious child, full of annoying and constant trouble." - -gratuitous: "Something that is unwarranted and uncouth, like the social equivalent of a flagrant foul." - -haughty: "Proud and flaunting it. Holding your head high up like a snooty, too-good-for-everything rich person." - -impertinent: "Someone acting rude and insensitive to others." - -intrepid: "Brave and courageous especially in a difficult, dangerous situation." \ No newline at end of file diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index babf53daf79..9d7dadda5ba 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -35,6 +35,9 @@ entries: - title: Support lifecycle url: /pmd_about_support_lifecycle.html output: web, pdf + - title: Security + url: /pmd_about_security.html + output: web, pdf - title: User Documentation output: web, pdf folderitems: @@ -68,6 +71,9 @@ entries: - title: 3rd party rulesets output: web, pdf url: /pmd_userdocs_3rdpartyrulesets.html + - title: Signed Releases + output: web, pdf + url: /pmd_userdocs_signed_releases.html - title: null output: web, pdf subfolders: @@ -137,6 +143,9 @@ entries: - title: CI integrations output: web, pdf url: /pmd_userdocs_tools_ci.html + - title: IDE Plugins + output: web, pdf + url: /pmd_userdocs_tools_ide_plugins.html - title: Other Tools / Integrations output: web, pdf url: /pmd_userdocs_tools.html @@ -434,6 +443,9 @@ entries: - title: C# url: /pmd_languages_cs.html output: web, pdf + - title: CSS + url: /pmd_languages_css.html + output: web, pdf - title: Coco url: /pmd_languages_coco.html output: web, pdf @@ -496,6 +508,9 @@ entries: - title: Ruby url: /pmd_languages_ruby.html output: web, pdf + - title: Rust + url: /pmd_languages_rust.html + output: web, pdf - title: Scala url: /pmd_languages_scala.html output: web, pdf @@ -517,24 +532,60 @@ entries: - title: Developer Documentation output: web, pdf folderitems: - - title: Developer resources - url: /pmd_devdocs_development.html - output: web, pdf - - title: Building PMD from source - url: /pmd_devdocs_building.html - output: web, pdf - - title: Contributing - external_url: https://github.com/pmd/pmd/blob/main/CONTRIBUTING.md + - title: null output: web, pdf - - title: Writing documentation - url: /pmd_devdocs_writing_documentation.html + subfolders: + - title: Contributing + output: web, pdf + subfolderitems: + - title: Contributing + url: /pmd_devdocs_contributing.html + output: web, pdf + - title: Developer resources + url: /pmd_devdocs_development.html + output: web, pdf + - title: Newcomers' Guide + url: /pmd_devdocs_contributing_newcomers_guide.html + output: web, pdf + - title: Writing documentation + url: /pmd_devdocs_writing_documentation.html + output: web, pdf + - title: null output: web, pdf + subfolders: + - title: Building PMD + output: web, pdf + subfolderitems: + - title: General Info + url: /pmd_devdocs_building_general.html + output: web, pdf + - title: Building PMD from source + url: /pmd_devdocs_building.html + output: web, pdf + - title: Building PMD with IntelliJ IDEA + url: /pmd_devdocs_building_intellij.html + output: web, pdf + - title: Building PMD with Eclipse IDE + url: /pmd_devdocs_building_eclipse.html + output: web, pdf + - title: Building PMD with VS Code IDE + url: /pmd_devdocs_building_vscode.html + output: web, pdf + - title: Building PMD with Netbeans IDE + url: /pmd_devdocs_building_netbeans.html + output: web, pdf - title: Roadmap url: /pmd_devdocs_roadmap.html output: web, pdf + - title: GitHub Actions Workflows + url: /pmd_devdocs_github_actions_workflows.html + output: web, pdf - title: How PMD works url: /pmd_devdocs_how_pmd_works.html output: web, pdf + - title: Logging + url: /pmd_devdocs_logging.html + output: web, pdf - title: Pmdtester url: /pmd_devdocs_pmdtester.html output: web, pdf @@ -550,6 +601,9 @@ entries: - title: Rule Guidelines url: /pmd_devdocs_major_rule_guidelines.html output: web, pdf + - title: Adding a new dialect + url: /pmd_devdocs_major_adding_dialect.html + output: web, pdf - title: Adding a new language (JavaCC) url: /pmd_devdocs_major_adding_new_language_javacc.html output: web, pdf diff --git a/docs/_data/strings.yml b/docs/_data/strings.yml deleted file mode 100644 index d7c13924f5a..00000000000 --- a/docs/_data/strings.yml +++ /dev/null @@ -1,5 +0,0 @@ - - -# placed here for translation purposes -search_placeholder_text: search... -search_no_results_text: No results found. diff --git a/docs/_data/topnav.yml b/docs/_data/topnav.yml deleted file mode 100644 index 0a4aa13660d..00000000000 --- a/docs/_data/topnav.yml +++ /dev/null @@ -1,12 +0,0 @@ -## Topnav single links -## if you want to list an external url, use external_url instead of url. the theme will apply a different link base. -topnav: -- title: Topnav - items: - - title: Download - external_url: https://github.com/pmd/pmd/releases/latest - - title: Fork us on github - external_url: https://github.com/pmd/pmd - -#Topnav dropdowns -topnav_dropdowns: diff --git a/docs/_includes/links.html b/docs/_includes/links.html index 004229987a7..61c64a3f34a 100644 --- a/docs/_includes/links.html +++ b/docs/_includes/links.html @@ -20,27 +20,3 @@ {% endfor %} {% endfor %} {% endfor %} - - -{% comment %} Get links from topnav {% endcomment %} - -{% for entry in site.data.topnav.topnav %} -{% for item in entry.items %} -{% if item.external_url == null %} -[{{item.url | remove: "/" | remove: ".html"}}]: {{item.url | remove: "/"}} -{% endif %} -{% endfor %} -{% endfor %} - -{% comment %}Get links from topnav dropdowns {% endcomment %} - -{% for entry in site.data.topnav.topnav_dropdowns %} -{% for folder in entry.folders %} -{% for folderitem in folder.folderitems %} -{% if folderitem.external_url == null %} -[{{folderitem.url | remove: "/" | remove: ".html"}}]: {{folderitem.url | remove: "/"}} -{% endif %} -{% endfor %} -{% endfor %} -{% endfor %} - diff --git a/docs/_includes/note.html b/docs/_includes/note.html index e8ed7ab24a7..8159b490697 100644 --- a/docs/_includes/note.html +++ b/docs/_includes/note.html @@ -1 +1,5 @@ - + diff --git a/docs/_includes/topnav.html b/docs/_includes/topnav.html index daf50ba7c40..d4bcca7189e 100644 --- a/docs/_includes/topnav.html +++ b/docs/_includes/topnav.html @@ -12,40 +12,17 @@ - {% for entry in site.data.topnav.topnav %} - {% for item in entry.items %} - {% if item.external_url %} - - {% elsif page.url contains item.url %} - - {% else %} - - {% endif %} - {% endfor %} - {% endfor %} + + - - {% for entry in site.data.topnav.topnav_dropdowns %} - {% for folder in entry.folders %} - - {% endfor %} - {% endfor %} +
- +
    diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 5e0065208f0..7dd26ea93d0 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -47,10 +47,11 @@ - + + {% if page.additional_js %} {% for js in page.additional_js %} diff --git a/docs/_plugins/custom_filters.rb b/docs/_plugins/custom_filters.rb index c3cdec7a0a9..4c67f80f5bd 100644 --- a/docs/_plugins/custom_filters.rb +++ b/docs/_plugins/custom_filters.rb @@ -106,6 +106,24 @@ def random_alphabetic(length) ('a'..'z').to_a.shuffle[0, length].join end + def escape_json(text) + if text && text.is_a?(String) + res = text + res = res.gsub(/\\/, '\\\\\\') + res = res.gsub(/"/, '\\"') + res = res.gsub(/\n/, '\\n') + res = res.gsub(/\r/, '\\r') + res = res.gsub(/\f/, '\\f') + res = res.gsub(/\t/, '\\t') + res = res.gsub(/[\b]/, '\\b') + elsif text && text.is_a?(Array) + text.map {|s| escape_json(s) } + end + end + + def separate_words(text) + text.gsub(/([A-Z][a-z])/, ' \1').strip if text + end private diff --git a/docs/_plugins/hooks/check_rule_docs.rb b/docs/_plugins/hooks/check_rule_docs.rb new file mode 100644 index 00000000000..a1485d9bedb --- /dev/null +++ b/docs/_plugins/hooks/check_rule_docs.rb @@ -0,0 +1,131 @@ +# +# A jekyll hook, that verifies, that the generated rule documentation pages are existing. +# They would be generated by calling "./mvnw package -Pgenerate-rule-docs -pl pmd-doc", but are +# missing on a fresh clone/checkout. +# This is to avoid creating PMD documentation without rule documentation. +# +# Note: This does exclude various ruleset categories, as they don't contain rules and we don't +# generate documentation for these. +# + +Jekyll::Hooks.register :site, :after_init do |site| + ENV_VAR_NAME='PMD_DOC_IGNORE_MISSING_RULE_DOC' + + if ENV[ENV_VAR_NAME] then + Jekyll.logger.warn "Not verifying that generated rule doc pages exist. The generated documentation might be incomplete!" + else + + def check_file(filename, mtime = nil, messages) + if not File.exist?(filename) + messages << "File #{filename} does not exist!" + elsif mtime != nil and File.stat(filename).mtime < mtime + messages << "File #{filename} needs to be regenerated!" + end + end + + sourceDir = site.source + Jekyll.logger.info "Verifying that generated rule doc pages exist in #{sourceDir}..." + + languages = {} + categories = {} + Dir.glob('**/src/main/resources/category/*/*.xml', base: "#{sourceDir}/..").each {|file| + mtime = File.stat("#{sourceDir}/../#{file}").mtime + %r<.+src/main/resources/category/([^/]+)/([^.]+)\.xml> =~ file + languages[$1] = 1 + categories["#{$1}/#{$2}.md"] = mtime + } + + messages = [] + + languages_exceptions = [ + 'wsdl' + ] + languages.each { |lang, _| + check_file "#{sourceDir}/pages/pmd/rules/#{lang}.md", messages unless languages_exceptions.any?(lang) + } + + categories_exceptions = [ + 'apex/multithreading.md', + 'html/codestyle.md', + 'html/design.md', + 'html/documentation.md', + 'html/errorprone.md', + 'html/multithreading.md', + 'html/performance.md', + 'html/security.md', + 'ecmascript/design.md', + 'ecmascript/documentation.md', + 'ecmascript/multithreading.md', + 'ecmascript/security.md', + 'jsp/documentation.md', + 'jsp/multithreading.md', + 'jsp/performance.md', + 'plsql/documentation.md', + 'plsql/multithreading.md', + 'plsql/performance.md', + 'plsql/security.md', + 'scala/bestpractices.md', + 'scala/codestyle.md', + 'scala/design.md', + 'scala/documentation.md', + 'scala/errorprone.md', + 'scala/multithreading.md', + 'scala/performance.md', + 'scala/security.md', + 'swift/codestyle.md', + 'swift/design.md', + 'swift/documentation.md', + 'swift/multithreading.md', + 'swift/performance.md', + 'swift/security.md', + 'velocity/codestyle.md', + 'velocity/documentation.md', + 'velocity/multithreading.md', + 'velocity/performance.md', + 'velocity/security.md', + 'visualforce/bestpractices.md', + 'visualforce/codestyle.md', + 'visualforce/design.md', + 'visualforce/documentation.md', + 'visualforce/errorprone.md', + 'visualforce/multithreading.md', + 'visualforce/performance.md', + 'pom/bestpractices.md', + 'pom/codestyle.md', + 'pom/design.md', + 'pom/documentation.md', + 'pom/multithreading.md', + 'pom/performance.md', + 'pom/security.md', + 'wsdl/bestpractices.md', + 'wsdl/codestyle.md', + 'wsdl/design.md', + 'wsdl/documentation.md', + 'wsdl/errorprone.md', + 'wsdl/multithreading.md', + 'wsdl/performance.md', + 'wsdl/security.md', + 'xml/codestyle.md', + 'xml/design.md', + 'xml/documentation.md', + 'xml/multithreading.md', + 'xml/performance.md', + 'xml/security.md', + 'xsl/bestpractices.md', + 'xsl/design.md', + 'xsl/documentation.md', + 'xsl/errorprone.md', + 'xsl/multithreading.md', + 'xsl/security.md' + ] + categories.each { |cat, mtime| + check_file "#{sourceDir}/pages/pmd/rules/#{cat}", mtime, messages unless categories_exceptions.any?(cat) + } + + if not messages.empty? + messages.each {|m| Jekyll.logger.error m} + Jekyll.logger.abort_with "Please execute `./mvnw package -Pgenerate-rule-docs -pl pmd-doc` before"\ + "generating pmd documentation or ignore by setting env variable #{ENV_VAR_NAME}" + end + end +end diff --git a/docs/_plugins/jdoc_namespace_tag.rb b/docs/_plugins/jdoc_namespace_tag.rb index 4688e860ce8..3becd9c7f21 100644 --- a/docs/_plugins/jdoc_namespace_tag.rb +++ b/docs/_plugins/jdoc_namespace_tag.rb @@ -99,10 +99,10 @@ def self.parse_fqcn(fqcn, var_ctx, allow_sym = true) private JDOC_NAMESPACE_MAP = "jdoc_nspaces" - RESERVED_NSPACES = ['ant', 'apex', 'cli', 'coco', 'core', 'cpp', 'cs', 'dart', 'dist', 'doc', + RESERVED_NSPACES = ['ant', 'apex', 'cli', 'coco', 'core', 'cpp', 'cs', 'css', 'dart', 'dist', 'doc', 'fortran', 'gherkin', 'go', 'groovy', 'html', 'java', 'javascript', 'jsp', 'julia', - 'kotlin', 'lang-test', 'lua', 'matlab', 'objectivec', 'perl', 'php', 'plsql', 'python', 'ruby', 'scala', 'swift', + 'kotlin', 'lang-test', 'lua', 'matlab', 'objectivec', 'perl', 'php', 'plsql', 'python', 'ruby', 'rust', 'scala', 'swift', 'test', 'test-schema', 'tsql', 'ui', 'modelica', 'visualforce', 'velocity', 'xml', 'vm', # pre-pmd7 name for velocity, only useful for jdoc_old or jdoc_package_old diff --git a/docs/assets/README.md b/docs/assets/README.md index 48125954f80..15b0b7f6c68 100644 --- a/docs/assets/README.md +++ b/docs/assets/README.md @@ -26,14 +26,9 @@ https://github.com/bryanbraun/anchorjs/releases/tag/4.2.2 https://github.com/tefra/navgoco/releases/tag/0.2.1 -## JQuery UI - -https://jqueryui.com/ -https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip - ## Simple Jekyll Search -https://github.com/christian-fei/Simple-Jekyll-Search +https://github.com/neilboyd/Simple-Jekyll-Search ## Shuffle diff --git a/docs/assets/Simple-Jekyll-Search-1.0.8/dest/jekyll-search.js b/docs/assets/Simple-Jekyll-Search-1.0.8/dest/jekyll-search.js deleted file mode 100644 index 32eba0f36db..00000000000 --- a/docs/assets/Simple-Jekyll-Search-1.0.8/dest/jekyll-search.js +++ /dev/null @@ -1 +0,0 @@ -!function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o=0)}}module.exports=new LiteralSearchStrategy},{}],5:[function(require,module,exports){function setOptions(_opt){opt=_opt||{},opt.templatePattern=_opt.templatePattern||/\{(.*?)\}/g}function render(t,data){return t.replace(opt.templatePattern,function(match,prop){return data[prop]||match})}module.exports={render:render,setOptions:setOptions};var opt={};opt.templatePattern=/\{(.*?)\}/g},{}],6:[function(require,module,exports){!function(window,document,undefined){"use strict";function initWithJSON(json){store.put(opt.json),registerInput()}function initWithURL(url){jsonLoader.load(url,function(err,json){err?throwError("failed to get JSON ("+url+")"):(store.put(json),registerInput())})}function throwError(message){throw new Error("SimpleJekyllSearch --- "+message)}function validateOptions(_opt){for(var i=0;i{title}',noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]};window.SimpleJekyllSearch=function(_opt){opt=validateOptions(_opt),store.setOptions(_opt),isJSON(opt.json)?initWithJSON(opt.json):initWithURL(opt.json)},window.SimpleJekyllSearch.init=window.SimpleJekyllSearch}(window,document)},{"./JSONLoader":1,"./Repository":2,"./Templater":5}]},{},[6]); \ No newline at end of file diff --git a/docs/assets/Simple-Jekyll-Search-1.14.0/dest/simple-jekyll-search.min.js b/docs/assets/Simple-Jekyll-Search-1.14.0/dest/simple-jekyll-search.min.js new file mode 100644 index 00000000000..47c14d6c810 --- /dev/null +++ b/docs/assets/Simple-Jekyll-Search-1.14.0/dest/simple-jekyll-search.min.js @@ -0,0 +1,6 @@ +/*! + * Simple-Jekyll-Search 1.14.0 + * Copyright 2015-2025, Christian Fei, Neil Boyd + * Licensed under the MIT License. + */ +!function(){"use strict";var u={compile:function(n){return i.template.replace(i.pattern,function(t,e){var r=i.middleware(e,n[e],i.template,n.query);return void 0!==r?r:n[e]||t})},setOptions:function(t){i.pattern=t.pattern||i.pattern,i.template=t.template||i.template,"function"==typeof t.middleware&&(i.middleware=t.middleware)}};const i={};i.pattern=/\{(.*?)\}/g,i.template="",i.middleware=function(){};var e=function(t,e){var r=e.length,n=t.length;if(rt.isWordLike).map(t=>t.segment)),this},this.matches=function(e){return!!e&&0!==this.critArray.length&&(e=e.trim().toUpperCase(),this.critArray.filter(t=>0<=e.indexOf(t)).length===this.critArray.length)}};const o=new Intl.Segmenter([],{granularity:"word"});var s={put:function(t){if(f(t))return h(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(r){var n=[];l();for(let t=0,e=r.length;t=r.limit));t++){var i=function(t,e){for(const r in t)if("query"!==r&&!function(r,n){for(let t=0,e=n.length;t{if(0!==e.length){let t=0;for(;;){if((t=i.toUpperCase().indexOf(e.toUpperCase(),t))<0)break;var r=t+e.length,n=""+i.substring(t,r)+"";i=i.substring(0,t)+n+i.substring(r),t+=n.length}}});let n=i.indexOf("");n>r?n-=r:n=0;t=(i=i.substring(n,n+e)).indexOf("<",e-3);-1!==t&&(i=i.substring(0,t));r=i.lastIndexOf(""),e=i.lastIndexOf("");if(e";return i}};{var g=window;let i={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'
  • {title}
  • ',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[],onSearch:Function.prototype},r;const x=function(t,e){e?(clearTimeout(r),r=setTimeout(t,e)):t.call()},S=["searchInput","resultsContainer","json"],z=t({required:S});function m(t){s.put(t),i.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(y(),x(function(){v(t.target.value)},i.debounceTime))})}function y(){i.resultsContainer.innerHTML=""}function w(t){i.resultsContainer.innerHTML+=t}function v(t){var e;(e=t)&&0 .ui-controlgroup-item{float:left;margin-left:0;margin-right:0}.ui-controlgroup > .ui-controlgroup-item:focus,.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus{z-index:9999}.ui-controlgroup-vertical > .ui-controlgroup-item{display:block;float:none;width:100%;margin-top:0;margin-bottom:0;text-align:left}.ui-controlgroup-vertical .ui-controlgroup-item{box-sizing:border-box}.ui-controlgroup .ui-controlgroup-label{padding:.4em 1em}.ui-controlgroup .ui-controlgroup-label span{font-size:80%}.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item{border-left:none}.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item{border-top:none}.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content{border-right:none}.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content{border-bottom:none}.ui-controlgroup-vertical .ui-spinner-input{width:75%;width:calc( 100% - 2.4em )}.ui-controlgroup-vertical .ui-spinner .ui-spinner-up{border-top-style:solid}.ui-checkboxradio-label .ui-icon-background{box-shadow:inset 1px 1px 1px #ccc;border-radius:.12em;border:none}.ui-checkboxradio-radio-label .ui-icon-background{width:16px;height:16px;border-radius:1em;overflow:visible;border:none}.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon{background-image:none;width:8px;height:8px;border-width:4px;border-style:solid}.ui-checkboxradio-disabled{pointer-events:none}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-n{height:2px;top:0}.ui-dialog .ui-resizable-e{width:2px;right:0}.ui-dialog .ui-resizable-s{height:2px;bottom:0}.ui-dialog .ui-resizable-w{width:2px;left:0}.ui-dialog .ui-resizable-se,.ui-dialog .ui-resizable-sw,.ui-dialog .ui-resizable-ne,.ui-dialog .ui-resizable-nw{width:7px;height:7px}.ui-dialog .ui-resizable-se{right:0;bottom:0}.ui-dialog .ui-resizable-sw{left:0;bottom:0}.ui-dialog .ui-resizable-ne{right:0;top:0}.ui-dialog .ui-resizable-nw{left:0;top:0}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-text{display:block;margin-right:20px;overflow:hidden;text-overflow:ellipsis}.ui-selectmenu-button.ui-button{text-align:left;white-space:nowrap;width:14em}.ui-selectmenu-icon.ui-icon{float:right;margin-top:0}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:.222em 0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:2em}.ui-spinner-button{width:1.6em;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top-style:none;border-bottom-style:none;border-right-style:none}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.003;filter:Alpha(Opacity=.3)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file diff --git a/docs/assets/jquery-ui-1.12.1/jquery-ui.min.js b/docs/assets/jquery-ui-1.12.1/jquery-ui.min.js deleted file mode 100644 index 25398a16741..00000000000 --- a/docs/assets/jquery-ui-1.12.1/jquery-ui.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("
    "))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
    "),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

    ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
    ").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
    ").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("