Merge branch 'master' into java-ast-updates

This commit is contained in:
Andreas Dangel 2024-01-11 10:28:21 +01:00
commit ed0cff6da9
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
33 changed files with 720 additions and 267 deletions

View File

@ -7279,6 +7279,15 @@
"bug"
]
},
{
"login": "nakul777",
"name": "Nakul Sharma",
"avatar_url": "https://avatars.githubusercontent.com/u/1551545?v=4",
"profile": "https://github.com/nakul777",
"contributions": [
"bug"
]
},
{
"login": "shai-bennathan",
"name": "Shai Bennathan",

View File

@ -36,7 +36,7 @@ function build() {
if pmd_ci_utils_is_fork_or_pull_request; then
pmd_ci_log_group_start "Build with mvnw"
./mvnw clean install -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
./mvnw clean install --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
pmd_ci_log_group_end
# Execute danger and dogfood only for pull requests in our own repository
@ -70,7 +70,7 @@ function build() {
if [ "$(pmd_ci_utils_get_os)" != "linux" ]; then
pmd_ci_log_group_start "Build with mvnw"
./mvnw clean verify -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
./mvnw clean verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
pmd_ci_log_group_end
pmd_ci_log_info "Stopping build here, because os is not linux"
@ -87,7 +87,7 @@ function build() {
if [ "${PMD_CI_BRANCH}" = "experimental-apex-parser" ]; then
pmd_ci_log_group_start "Build with mvnw"
./mvnw clean install -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
./mvnw clean install --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
pmd_ci_log_group_end
pmd_ci_log_group_start "Creating new baseline for regression tester"
@ -107,7 +107,7 @@ function build() {
pmd_ci_log_group_end
# release is published only for the case b) pmd-cli/pmd-dist release
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" == *-dist ]]; then
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}"
@ -117,7 +117,7 @@ function build() {
# 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 || [[ "${PMD_CI_TAG}" == *-dist ]]; then
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
@ -141,7 +141,7 @@ function build() {
-Dpmd.skip \
--show-version --errors --batch-mode \
clean package \
sonar:sonar -Dsonar.login="${SONAR_TOKEN}" -Psonar,cli-dist
sonar:sonar -Dsonar.login="${SONAR_TOKEN}" -Psonar
pmd_ci_log_success "New sonar results: https://sonarcloud.io/dashboard?id=net.sourceforge.pmd%3Apmd"
pmd_ci_log_group_end
@ -157,7 +157,7 @@ function build() {
-DrepoToken="${COVERALLS_REPO_TOKEN}" \
--show-version --errors --batch-mode \
clean package jacoco:report \
coveralls:report -Pcoveralls,cli-dist
coveralls:report -Pcoveralls
pmd_ci_log_success "New coveralls result: https://coveralls.io/github/pmd/pmd"
pmd_ci_log_group_end
fi
@ -169,7 +169,7 @@ function build() {
#
function pmd_ci_build_setup_bundler() {
pmd_ci_log_info "Installing bundler..."
gem install bundler
gem install bundler -v 2.4.22
}
#
@ -185,16 +185,16 @@ function pmd_ci_build_run() {
mvn_profiles="${mvn_profiles},pmd-release"
# There are two possible (release) builds:
if [[ "${PMD_CI_TAG}" != *-dist ]]; then
# a) pmd-core and languages modules
./mvnw clean deploy -P"${mvn_profiles}",'!cli-dist' --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
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) pmd-cli and pmd-dist
./mvnw clean deploy -P"${mvn_profiles},cli-dist" -pl pmd-cli,pmd-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
# 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},cli-dist" --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
./mvnw clean deploy -P"${mvn_profiles}" --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
fi
}
@ -214,15 +214,15 @@ function pmd_ci_deploy_build_artifacts() {
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): only pmd-core and language modules released
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" != *-dist ]]; then
# 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): pmd-cli and pmd-dist are released
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" == *-dist ]]; then
# 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"
@ -254,7 +254,7 @@ function pmd_ci_build_and_upload_doc() {
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 && [[ "${PMD_CI_TAG}" != *-dist ]]; then
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip"
fi
@ -263,7 +263,7 @@ function pmd_ci_build_and_upload_doc() {
# Deploy javadoc to https://docs.pmd-code.org/apidocs/*/${PMD_CI_MAVEN_PROJECT_VERSION}/
pmd_code_uploadJavadoc "${PMD_CI_MAVEN_PROJECT_VERSION}" "$(pwd)"
if pmd_ci_maven_isSnapshotBuild || [[ "${PMD_CI_TAG}" != *-dist ]]; then
if pmd_ci_maven_isSnapshotBuild || [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
# render release notes
# updating github release text
rm -f .bundle/config
@ -289,7 +289,7 @@ function pmd_ci_build_and_upload_doc() {
pmd_ci_sourceforge_rsyncSnapshotDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}" "snapshot"
fi
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" != *-dist ]]; then
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"
@ -321,16 +321,15 @@ ${rendered_release_notes}"
#
function pmd_ci_dogfood() {
local mpmdVersion=()
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}-dogfood" -DgenerateBackupPoms=false -Pcli-dist
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}-dogfood" -DgenerateBackupPoms=false
sed -i 's/<version>[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}.*<\/version>\( *<!-- pmd.dogfood.version -->\)/<version>'"${PMD_CI_MAVEN_PROJECT_VERSION}"'<\/version>\1/' pom.xml
./mvnw verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" \
-Pcli-dist \
"${mpmdVersion[@]}" \
-DskipTests \
-Dmaven.javadoc.skip=true \
-Dmaven.source.skip=true \
-Dcheckstyle.skip=true
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}" -DgenerateBackupPoms=false -Pcli-dist
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}" -DgenerateBackupPoms=false
git checkout -- pom.xml
}

View File

@ -14,6 +14,12 @@ on:
# 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
permissions:
contents: read # to fetch code (actions/checkout)
@ -55,7 +61,7 @@ jobs:
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/22/scripts" >> $GITHUB_ENV
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
- name: Check Environment
shell: bash
run: |
@ -68,6 +74,7 @@ jobs:
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

View File

@ -24,7 +24,7 @@ jobs:
shell: bash
run: |
echo "LANG=en_US.UTF-8" >> $GITHUB_ENV
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/22/scripts" >> $GITHUB_ENV
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
- name: Sync
run: .ci/git-repo-sync.sh
shell: bash

View File

@ -36,7 +36,7 @@ jobs:
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/22/scripts" >> $GITHUB_ENV
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
- name: Check Environment
shell: bash
run: |

View File

@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.5)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.2.0)
claide (1.1.0)
@ -13,7 +13,7 @@ GEM
concurrent-ruby (1.2.2)
cork (0.3.0)
colored2 (~> 3.1)
danger (9.4.0)
danger (9.4.2)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
@ -24,12 +24,12 @@ GEM
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (>= 6.0, < 8.0)
octokit (>= 4.0)
terminal-table (>= 1, < 4)
differ (0.1.2)
et-orbi (1.2.7)
tzinfo
faraday (2.7.11)
faraday (2.7.12)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
@ -51,10 +51,10 @@ GEM
mini_portile2 (2.8.5)
nap (1.1.0)
no_proxy_fix (0.1.2)
nokogiri (1.15.4)
nokogiri (1.15.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
octokit (7.2.0)
octokit (8.0.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
@ -65,7 +65,7 @@ GEM
nokogiri (~> 1.13)
rufus-scheduler (~> 3.8)
slop (~> 4.9)
public_suffix (5.0.3)
public_suffix (5.0.4)
raabro (1.4.0)
racc (1.7.3)
rchardet (1.8.0)
@ -96,4 +96,4 @@ DEPENDENCIES
safe_yaml
BUNDLED WITH
2.4.5
2.4.22

View File

@ -1,4 +1,6 @@
#!/usr/bin/env bash
# abort the script on the first failing sub command
set -e
# Make sure, everything is English...
@ -165,13 +167,32 @@ git commit -a -m "Prepare pmd release ${RELEASE_VERSION}"
fi
)
./mvnw -B release:clean release:prepare \
-Dtag="pmd_releases/${RELEASE_VERSION}" \
-DreleaseVersion="${RELEASE_VERSION}" \
-DdevelopmentVersion="${DEVELOPMENT_VERSION}" \
-DscmCommentPrefix="[release] " \
-Darguments='-Pgenerate-rule-docs,!cli-dist' \
'-Pgenerate-rule-docs,!cli-dist'
# check that there are no uncommitted changes
UNCOMMITTED_CHANGES=$(git status --short --untracked-files=no)
if [ -n "${UNCOMMITTED_CHANGES}" ]; then
echo "There are uncommitted changes:"
echo "${UNCOMMITTED_CHANGES}"
exit 1
fi
# check that there are no SNAPSHOT dependencies -> done by the enforcer plugin, see enforce-no-snapshots
echo "Change version in the POMs to ${RELEASE_VERSION} and update build timestamp"
./mvnw --quiet versions:set -DnewVersion="${RELEASE_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=always
echo "Transform the SCM information in the POM"
sed -i "s|<tag>.\+</tag>|<tag>pmd_releases/${RELEASE_VERSION}</tag>|" pom.xml
echo "Run the project tests against the changed POMs to confirm everything is in running order (skipping cli and dist)"
./mvnw clean verify -Dskip-cli-dist -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}"
echo "Update POMs to set the new development version ${DEVELOPMENT_VERSION}"
./mvnw --quiet versions:set -DnewVersion="${DEVELOPMENT_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=never
sed -i "s|<tag>.\+</tag>|<tag>HEAD</tag>|" pom.xml
echo "Commit"
git commit -a -m "[release] prepare for next development iteration"
echo "Push branch and tag pmd_releases/${RELEASE_VERSION}"
git push origin "${CURRENT_BRANCH}"
git push origin tag "pmd_releases/${RELEASE_VERSION}"
echo
echo "Tag has been pushed.... now check github actions: <https://github.com/pmd/pmd/actions>"
@ -232,9 +253,8 @@ This is a {{ site.pmd.release_type }} release.
EOF
git commit -a -m "Prepare next development version [skip ci]"
git commit -a -m "[release] Prepare next development version [skip ci]"
git push origin "${CURRENT_BRANCH}"
./mvnw -B release:clean
echo
echo
@ -248,28 +268,19 @@ echo "<https://github.com/pmd/pmd-designer/blob/master/releasing.md>"
echo
echo "Press enter to continue when pmd-designer is available in maven-central..."
echo "<https://repo.maven.apache.org/maven2/net/sourceforge/pmd/pmd-ui/maven-metadata.xml>."
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..."
git checkout "pmd_releases/${RELEASE_VERSION}"
./mvnw versions:update-parent -DparentVersion="${RELEASE_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
git add pmd-cli/pom.xml pmd-dist/pom.xml
git commit -m "[release] prepare release pmd_releases/${RELEASE_VERSION}-dist"
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}-dist" "pmd_releases/${RELEASE_VERSION}-dist"
git push origin tag "pmd_releases/${RELEASE_VERSION}-dist"
git checkout master
# make sure parent reference is correct
./mvnw versions:update-parent -DparentVersion="${DEVELOPMENT_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
git add pmd-cli/pom.xml pmd-dist/pom.xml
changes=$(git status --porcelain 2>/dev/null | grep -c -E "^[AMDRC]" || true)
if [ "$changes" -gt 0 ]; then
git commit -m "Prepare next development version [skip ci]"
git push origin "${CURRENT_BRANCH}"
fi
echo
echo "Second tag 'pmd_releases/${RELEASE_VERSION}-dist' has been pushed ... now check github actions: <https://github.com/pmd/pmd/actions>"
echo "Go to <https://github.com/pmd/pmd/actions/workflows/build.yml> 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: <https://github.com/pmd/pmd/actions>"
echo
echo
echo "Verify the new release on github: <https://github.com/pmd/pmd/releases/tag/pmd_releases/${RELEASE_VERSION}>"
@ -297,5 +308,3 @@ echo "------------------------------------------"
echo "Done."
echo "------------------------------------------"
echo

View File

@ -1,14 +1,20 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.8)
activesupport (7.1.2)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.5)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.1.1)
base64 (0.2.0)
bigdecimal (3.1.5)
coffee-script (2.4.1)
coffee-script-source
execjs
@ -16,8 +22,11 @@ GEM
colorator (1.1.0)
commonmarker (0.23.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
dnsruby (1.70.0)
simpleidn (~> 0.2.1)
drb (2.2.0)
ruby2_keywords
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
@ -25,12 +34,12 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.9.1)
faraday (2.7.11)
faraday (2.7.12)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
ffi (1.16.2)
ffi (1.16.3)
forwardable-extended (2.6.0)
gemoji (3.0.1)
github-pages (228)
@ -207,13 +216,14 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6)
mini_portile2 (2.8.4)
mini_portile2 (2.8.5)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.20.0)
nokogiri (1.15.4)
mutex_m (0.2.0)
nokogiri (1.15.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
octokit (4.25.1)
@ -222,7 +232,7 @@ GEM
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.7)
racc (1.7.1)
racc (1.7.3)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
@ -243,13 +253,13 @@ GEM
unf (~> 0.1.4)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.4.0)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unf_ext (0.0.9.1)
unicode-display_width (1.8.0)
webrick (1.8.1)
@ -262,4 +272,4 @@ DEPENDENCIES
webrick
BUNDLED WITH
2.4.5
2.4.22

View File

@ -63,7 +63,7 @@ The semantic analysis roughly works like so:
3. The last pass resolves the types of expressions, which performs overload resolution on method calls, and type inference.
TODO describe
* why we need auxclasspath
* why we need auxclasspath, and how to put the java classes onto the auxclasspath (jre/lib/rt.jar or lib/jrt-fs.jar).
* how disambiguation can fail
## Type and symbol APIs

View File

@ -9,24 +9,28 @@ This page describes the current status of the release process.
Since 6.30.0, the automated release process is using [Github Actions](https://github.com/pmd/pmd/actions).
However, there are still a few steps, that need manual examination.
Since 7.0.0-rc4, the release happens in two phases: First pmd-core with all the languages are released.
This allows to release then pmd-designer or any other project, that just depends on pmd-core and the
languages. And in the second phase, pmd-cli and pmd-dist are released. These include e.g. pmd-designer.
While the release is mostly automated, there are still a few steps, that need manual examination.
## Overview
This page gives an overview which tasks are automated to do a full release of PMD. This knowledge is
required in order to verify that the release was successful or in case the automated process fails for
some reason. Then individual steps need to be executed manually. Because the build is reproducible, these
steps can be repeated again if the same tag is used.
steps can be repeated if the same tag is used.
There is one special case in this project: The release of PMD is done in two steps:
There is one special case in this project: As outlined above, the release of PMD consists of two phases or parts:
1. All modules except pmd-cli and pmd-dist are released. That means, pmd-core and all the language modules
are released. This is, so that these libs can be used by pmd-designer to create a new release.
2. pmd-cli and pmd-dist are released afterwards. Both depend on pmd-designer, and this two-step release
2. pmd-cli and pmd-dist are released after that. Both depend on pmd-designer, and this two-step release
process is used for now to break the cycling release dependency.
The three main steps are:
* Preparations (which creates the tags) - use `do-release.sh` for that
* Preparations (which sets the versions and creates the tags) - use `do-release.sh` for that
* The actual release (which is automated) - GitHub Actions will build the tags when they have been pushed.
* Prepare the next release (make sure the current main branch is ready for further development)
@ -57,9 +61,9 @@ Also make sure, that the repo "pmd.github.io" is locally up-to-date and has no l
Before the release, you need to verify the release notes: Does it contain all the relevant changes for the
release? Is it formatted properly? Are there any typos? Does it render properly?
As the release notes are part of the source code, it is not simple to change it afterwards. While the source
code for a tag cannot be changed anymore, the published release notes on the github releases pages or the
new posts can be changed afterwards (although that's an entirely manual process).
As the release notes are part of the source code, it is not that simple to change it afterward. While the source
code for a tag cannot be changed anymore, the published release notes on the GitHub Releases pages or the
news posts can be changed afterward (although that's an entirely manual process).
You can find the release notes here: `docs/pages/release_notes.md`.
@ -86,7 +90,12 @@ Add the new rules as comments to the quickstart rulesets:
The designer lives at [pmd/pmd-designer](https://github.com/pmd/pmd-designer).
Update property `pmd-designer.version` in **pom.xml** to reference the new version, that will be released
shortly. Note: This version does at the moment not exist.
shortly. Note: This version does at the moment not exist. That means, that a full build of the sources
will currently fail. That's why the first phase of the release will build only pmd-core and languages but
not pmd-cli and pmd-dist.
In case, there is no need for a new pmd-designer version, we could stick to the latest already available version.
Then we can skip the release of pmd-designer and immediately start the second phase of the release.
Starting with PMD 6.23.0 we'll provide small statistics for every release. This needs to be added
to the release notes as the last section. To count the closed issues and pull requests, the milestone
@ -115,7 +124,7 @@ Check in all (version) changes to branch master or any other branch, from which
### The Homepage
The github repo `pmd.github.io` hosts the homepage for [https://pmd.github.io](https://pmd.github.io).
The GitHub repo `pmd.github.io` hosts the homepage for [https://pmd.github.io](https://pmd.github.io).
All the following tasks are to be done in this repo.
The new version needs to be entered into `_config.yml`, e.g.:
@ -132,7 +141,22 @@ in this list, so remove the oldest version.
Then create a new page for the new release, e.g. `_posts/2021-04-24-PMD-6.34.0.md` and copy
the release notes into this page. This will appear under the news section.
Check in all (version) changes to branch master:
Note: The release notes typically contain some Jekyll macros for linking to the rule pages. These macros won't
work in a plain markdown version. Therefore, you need to render the release notes first:
```shell
# install bundles needed for rendering release notes
bundle config set --local path vendor/bundle
bundle config set --local with release_notes_preprocessing
bundle install
RELEASE_NOTES_POST="_posts/$(date -u +%Y-%m-%d)-PMD-${RELEASE_VERSION}.md"
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}" <<EOF
```
Check in all (version, blog post) changes to branch master:
$ git commit -a -m "Prepare pmd release <version>"
$ git push
@ -140,33 +164,45 @@ Check in all (version) changes to branch master:
## The actual release
The actual release starts with one last local command: calling **maven-release-plugin**.
The actual release is done by changing the versions, creating a tag and pushing this tag. Previously this was done
by calling _maven-release-plugin_, but these steps are done without the plugin to have more control. And since we
might reference a not yet released pmd-designer version, the test-build will fail.
This plugin changes the version by basically removing the "-SNAPSHOT" suffix, builds the changed project
locally, commits the version change, creates
a new tag from this commit, changes the version of the project to the next snapshot, commits this change
and pushes everything.
We first change the version of PMD and all modules by basically removing the "-SNAPSHOT" suffix, building the changed
project locally with tests (and with skipping pmd-cli and pmd-dist) in order to be sure, everything is in working
order. Then the version changes are committed and a new release tag is created. Then, the versions are changed to
the next snapshot. As last step, everything is pushed.
`RELEASE_VERSION` is the version of the release. It is reused for the tag. `DEVELOPMENT_VERSION` is the
next snapshot version after the release.
next snapshot version after the release. Skipping the builds of pmd-cli and pmd-dist is done by setting
the property `skip-cli-dist`.
```shell
RELEASE_VERSION=6.34.0
DEVELOPMENT_VERSION=6.35.0-SNAPSHOT
./mvnw -B release:clean release:prepare \
-Dtag="pmd_releases/${RELEASE_VERSION}" \
-DreleaseVersion="${RELEASE_VERSION}" \
-DdevelopmentVersion="${DEVELOPMENT_VERSION}" \
-DscmCommentPrefix="[release] " \
-Darguments='-Pgenerate-rule-docs,!cli-dist' \
'-Pgenerate-rule-docs,!cli-dist'
# Change version in the POMs to ${RELEASE_VERSION} and update build timestamp
./mvnw --quiet versions:set -DnewVersion="${RELEASE_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=always
# Transform the SCM information in the POM
sed -i "s|<tag>.\+</tag>|<tag>pmd_releases/${RELEASE_VERSION}</tag>|" pom.xml
# Run the project tests against the changed POMs to confirm everything is in running order (skipping cli and dist)
./mvnw clean verify -Dskip-cli-dist -Pgenerate-rule-docs
# 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}"
# Update POMs to set the new development version ${DEVELOPMENT_VERSION}
./mvnw --quiet versions:set -DnewVersion="${DEVELOPMENT_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=never
sed -i "s|<tag>.\+</tag>|<tag>HEAD</tag>|" pom.xml
git commit -a -m "[release] prepare for next development iteration"
# Push branch and tag pmd_releases/${RELEASE_VERSION}
git push origin "${CURRENT_BRANCH}"
git push origin tag "pmd_releases/${RELEASE_VERSION}"
```
Once the maven plugin has pushed the tag, github actions will start and build a new version from this tag. Since
it is a tag build and a released version build, the build script will do a couple of additional stuff.
Once we have pushed the tag, GitHub Actions take over and build a new version from this tag. Since
it is a tag build and a release version (version without SNAPSHOT), the build script will do a couple of additional stuff.
This is all automated in `.ci/build.sh`.
Note: The profile "cli-dist" is deactivated, so this release command doesn't include pmd-cli and pmd-dist.
Note: The property "skip-cli-dist" is activated, so this release command doesn't include pmd-cli and pmd-dist.
They will be released separately after pmd-designer is released. Since pmd-dist is not included in this first
step, no binaries are created yet.
@ -174,13 +210,15 @@ Here is, what happens:
* Deploy and release the build to maven central, so that it can be downloaded from
<https://repo.maven.apache.org/maven2/net/sourceforge/pmd/pmd/>. This is done automatically, if
all unit tests pass and the build doesn't fail for any other reason.
The plugin [nexus-staging-maven-plugin](https://github.com/sonatype/nexus-maven-plugins/tree/master/staging/maven-plugin) is used for that.
the build doesn't fail for any reason. Note, that unit tests are not executed anymore, since they have been
run already locally before pushing the tag.
The plugin [nexus-staging-maven-plugin](https://github.com/sonatype/nexus-maven-plugins/tree/master/staging/maven-plugin)
is used to upload and publish the artifacts to maven central.
* Create a draft release on GitHub and upload the release notes from `docs/pages/release_notes.md`.
Note: During the process, the release is a draft mode and not visible yet.
At the end of the process, the release will be published.
At the end of the process, the release will be published.
* Render the documentation in `docs/` with `bundle exec jekyll build` and create a zip file from it.
* Upload the doc zip file to the current github release under <https://github.com/pmd/pmd/releases> and
* Upload the doc zip file to the current (draft) GitHub Release under <https://github.com/pmd/pmd/releases> and
to <https://sourceforge.net/projects/pmd/files/pmd/>.
* Upload the documentation to <https://docs.pmd-code.org>, e.g. <https://docs.pmd-code.org/pmd-doc-6.34.0/> and
create a symlink, so that <https://docs.pmd-code.org/latest/> points to the new version.
@ -196,40 +234,21 @@ Here is, what happens:
<https://pmd.sourceforge.io/pmd-6.34.0/>. All previously copied versions are listed
under <https://pmd.sourceforge.io/archive.phtml>.
The release on github actions currently takes about 30-45 minutes. Once this is done, you
The release on GitHub Actions currently takes about 30-45 minutes. Once this is done, you
can proceed with releasing pmd designer, see <https://github.com/pmd/pmd-designer/blob/master/releasing.md>.
Make sure to release the version, you have used earlier for the property `pmd-designer.version`.
Once the pmd-designer release is done, you can proceed with part 2. We'll checkout the release tag, add
a new commit with the changed versions for pmd-cli and pmd-dist on top of it and create a new tag:
Once the pmd-designer release is done, you can proceed with part 2. This is simply triggering manually
a build on GitHub Actions: <https://github.com/pmd/pmd/actions/workflows/build.yml> from the same tag again, but
with the parameter "build_cli_dist_only" set to "true". With this parameter, the script `.ci/build.sh` will
perform the following steps:
```shell
git checkout "pmd_releases/${RELEASE_VERSION}"
./mvnw versions:update-parent -DparentVersion="${RELEASE_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
git add pmd-cli/pom.xml pmd-dist/pom.xml
git commit -m "[release] prepare release pmd_releases/${RELEASE_VERSION}-dist"
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}-dist" "pmd_releases/${RELEASE_VERSION}-dist"
git push origin tag "pmd_releases/${RELEASE_VERSION}-dist"
git checkout master
# make sure parent reference is correct
./mvnw versions:update-parent -DparentVersion="${DEVELOPMENT_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
git add pmd-cli/pom.xml pmd-dist/pom.xml
git commit -m "Prepare next development version [skip ci]"
git push origin "${CURRENT_BRANCH}"
```
Since pmd-cli/pmd-dist were not part of the first maven-release-plugin call, we might need to fix the parent references
manually to set to the new development version.
The new created tag ends with the suffix `-dist`, and this is used as a marker for the GitHub action. The same build
script `.ci/build.sh` is executed, but now, it does the following steps:
* Build only modules pmd-cli and pmd-dist
* Upload the new binaries to the existing draft release under <https://github.com/pmd/pmd/releases>.
* Build only modules pmd-cli and pmd-dist (via maven parameter `-pl pmd-cli,pmd-dist`).
* Upload the new binaries to the existing draft GitHub Release under <https://github.com/pmd/pmd/releases>.
* Upload the new binaries additionally to sourceforge, so that they can be downloaded from
<https://sourceforge.net/projects/pmd/files/pmd/>, including the release notes.
* After all this is done, the release on github (<https://github.com/pmd/pmd/releases>) is published
and the news post on sourceforge (https://sourceforge.net/p/pmd/news/> is published as well.
<https://sourceforge.net/projects/pmd/files/pmd/>.
* After all this is done, the release on GitHub (<https://github.com/pmd/pmd/releases>) is published
and the news post on sourceforge <https://sourceforge.net/p/pmd/news/> is published as well.
* The new binary at <https://sourceforge.net/projects/pmd/files/pmd/> is
selected as the new default for PMD.
* As a last step, a new baseline for the [regression tester](https://github.com/pmd/pmd-regression-tester)
@ -326,7 +345,7 @@ This is a {{ site.pmd.release_type }} release.
{%endraw%}
Finally commit and push the changes:
Finally, commit and push the changes:
$ git commit -m "Prepare next development version"
$ git push origin master

File diff suppressed because it is too large Load Diff

View File

@ -106,6 +106,8 @@ in the Migration Guide.
* core
* [#1027](https://github.com/pmd/pmd/issues/1027): \[core] Apply the new PropertyDescriptor&lt;Pattern&gt; type where applicable
* [#4674](https://github.com/pmd/pmd/issues/4674): \[core] WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass
* [#4694](https://github.com/pmd/pmd/pull/4694): \[core] Fix line/col numbers in TokenMgrError
* [#4717](https://github.com/pmd/pmd/issues/4717): \[core] XSLTRenderer doesn't close report file
* [#4750](https://github.com/pmd/pmd/pull/4750): \[core] Fix flaky SummaryHTMLRenderer
* doc
* [#3175](https://github.com/pmd/pmd/issues/3175): \[doc] Document language module features
@ -116,6 +118,7 @@ in the Migration Guide.
* [#4699](https://github.com/pmd/pmd/pull/4699): Make PMD buildable with java 21
* [#4586](https://github.com/pmd/pmd/pull/4586): Use explicit encoding in ruleset xml files
* [#4642](https://github.com/pmd/pmd/issues/4642): Update regression tests with Java 21 language features
* [#4736](https://github.com/pmd/pmd/issues/4736): \[ci] Improve build procedure
* [#4741](https://github.com/pmd/pmd/pull/4741): Add pmd-compat6 module for maven-pmd-plugin
* [#4749](https://github.com/pmd/pmd/pull/4749): Fixes NoSuchMethodError on processing errors in pmd-compat6
* apex-performance
@ -125,6 +128,7 @@ in the Migration Guide.
* java
* [#1307](https://github.com/pmd/pmd/issues/1307): \[java] AccessNode API changes
* [#3751](https://github.com/pmd/pmd/issues/3751): \[java] Rename some node types
* [#4628](https://github.com/pmd/pmd/pull/4628): \[java] Support loading classes from java runtime images
* [#4753](https://github.com/pmd/pmd/issues/4753): \[java] PMD crashes while using generics and wildcards
* java-codestyle
* [#2847](https://github.com/pmd/pmd/issues/2847): \[java] New Rule: Use Explicit Types
@ -557,6 +561,7 @@ See also [Detailed Release Notes for PMD 7]({{ baseurl }}pmd_release_notes_pmd7.
* [#4642](https://github.com/pmd/pmd/issues/4642): Update regression tests with Java 21 language features
* [#4691](https://github.com/pmd/pmd/issues/4691): \[CVEs] Critical and High CEVs reported on PMD and PMD dependencies
* [#4699](https://github.com/pmd/pmd/pull/4699): Make PMD buildable with java 21
* [#4736](https://github.com/pmd/pmd/issues/4736): \[ci] Improve build procedure
* [#4741](https://github.com/pmd/pmd/pull/4741): Add pmd-compat6 module for maven-pmd-plugin
* [#4749](https://github.com/pmd/pmd/pull/4749): Fixes NoSuchMethodError on processing errors in pmd-compat6
* ant
@ -605,6 +610,8 @@ See also [Detailed Release Notes for PMD 7]({{ baseurl }}pmd_release_notes_pmd7.
* [#4611](https://github.com/pmd/pmd/pull/4611): \[core] Fix loading language properties from env vars
* [#4621](https://github.com/pmd/pmd/issues/4621): \[core] Make `ClasspathClassLoader::getResource` child first
* [#4674](https://github.com/pmd/pmd/issues/4674): \[core] WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass
* [#4694](https://github.com/pmd/pmd/pull/4694): \[core] Fix line/col numbers in TokenMgrError
* [#4717](https://github.com/pmd/pmd/issues/4717): \[core] XSLTRenderer doesn't close report file
* [#4750](https://github.com/pmd/pmd/pull/4750): \[core] Fix flaky SummaryHTMLRenderer
* cli
* [#2234](https://github.com/pmd/pmd/issues/2234): \[core] Consolidate PMD CLI into a single command
@ -681,6 +688,7 @@ Language specific fixes:
* [#4401](https://github.com/pmd/pmd/issues/4401): \[java] PMD 7 fails to build under Java 19
* [#4405](https://github.com/pmd/pmd/issues/4405): \[java] Processing error with ArrayIndexOutOfBoundsException
* [#4583](https://github.com/pmd/pmd/issues/4583): \[java] Support JDK 21 (LTS)
* [#4628](https://github.com/pmd/pmd/pull/4628): \[java] Support loading classes from java runtime images
* [#4753](https://github.com/pmd/pmd/issues/4753): \[java] PMD crashes while using generics and wildcards
* java-bestpractices
* [#342](https://github.com/pmd/pmd/issues/342): \[java] AccessorMethodGeneration: Name clash with another public field not properly handled

View File

@ -13,6 +13,11 @@ public final class PmdCli {
private PmdCli() { }
public static void main(String[] args) {
// See https://github.com/remkop/picocli/blob/main/RELEASE-NOTES.md#-picocli-470
// and https://picocli.info/#_closures_in_annotations
// we don't use this feature. Disabling it avoids leaving the groovy jar open
// caused by Class.forName("groovy.lang.Closure")
System.setProperty("picocli.disable.closures", "true");
final CommandLine cli = new CommandLine(new PmdRootCommand())
.setCaseInsensitiveEnumValuesAllowed(true);

View File

@ -323,7 +323,8 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand<PMDConfiguration>
return CliExitCode.ERROR;
}
LOG.debug("Current classpath:\n{}", System.getProperty("java.class.path"));
LOG.debug("Runtime classpath:\n{}", System.getProperty("java.class.path"));
LOG.debug("Aux classpath: {}", configuration.getClassLoader());
if (showProgressBar) {
if (reportFile == null) {

View File

@ -7,14 +7,27 @@ package net.sourceforge.pmd.internal.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -33,29 +46,38 @@ public class ClasspathClassLoader extends URLClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(ClasspathClassLoader.class);
String javaHome;
private FileSystem fileSystem;
private Map<String, Set<String>> packagesDirsToModules;
static {
registerAsParallelCapable();
}
public ClasspathClassLoader(List<File> files, ClassLoader parent) throws IOException {
super(fileToURL(files), parent);
super(new URL[0], parent);
for (URL url : fileToURL(files)) {
addURL(url);
}
}
public ClasspathClassLoader(String classpath, ClassLoader parent) throws IOException {
super(initURLs(classpath), parent);
}
private static URL[] fileToURL(List<File> files) throws IOException {
List<URL> urlList = new ArrayList<>();
for (File f : files) {
urlList.add(f.toURI().toURL());
super(new URL[0], parent);
for (URL url : initURLs(classpath)) {
addURL(url);
}
return urlList.toArray(new URL[0]);
}
private static URL[] initURLs(String classpath) {
private List<URL> fileToURL(List<File> files) throws IOException {
List<URL> urlList = new ArrayList<>();
for (File f : files) {
urlList.add(createURLFromPath(f.getAbsolutePath()));
}
return urlList;
}
private List<URL> initURLs(String classpath) {
AssertionUtil.requireParamNotNull("classpath", classpath);
final List<URL> urls = new ArrayList<>();
try {
@ -69,10 +91,10 @@ public class ClasspathClassLoader extends URLClassLoader {
} catch (IOException e) {
throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e);
}
return urls.toArray(new URL[0]);
return urls;
}
private static void addClasspathURLs(final List<URL> urls, final String classpath) throws MalformedURLException {
private void addClasspathURLs(final List<URL> urls, final String classpath) throws MalformedURLException {
StringTokenizer toker = new StringTokenizer(classpath, File.pathSeparator);
while (toker.hasMoreTokens()) {
String token = toker.nextToken();
@ -81,7 +103,7 @@ public class ClasspathClassLoader extends URLClassLoader {
}
}
private static void addFileURLs(List<URL> urls, URL fileURL) throws IOException {
private void addFileURLs(List<URL> urls, URL fileURL) throws IOException {
try (BufferedReader in = new BufferedReader(new InputStreamReader(fileURL.openStream()))) {
String line;
while ((line = in.readLine()) != null) {
@ -95,9 +117,67 @@ public class ClasspathClassLoader extends URLClassLoader {
}
}
private static URL createURLFromPath(String path) throws MalformedURLException {
File file = new File(path);
return file.getAbsoluteFile().toURI().normalize().toURL();
private URL createURLFromPath(String path) throws MalformedURLException {
Path filePath = Paths.get(path).toAbsolutePath();
if (filePath.endsWith(Paths.get("lib", "jrt-fs.jar"))) {
initializeJrtFilesystem(filePath);
// don't add jrt-fs.jar to the normal aux classpath
return null;
}
return filePath.toUri().normalize().toURL();
}
/**
* Initializes a Java Runtime Filesystem that will be used to load class files.
* This allows end users to provide in the aux classpath another Java Runtime version
* than the one used for executing PMD.
*
* @param filePath path to the file "lib/jrt-fs.jar" inside the java installation directory.
* @see <a href="https://openjdk.org/jeps/220">JEP 220: Modular Run-Time Images</a>
*/
private void initializeJrtFilesystem(Path filePath) {
try {
LOG.debug("Detected Java Runtime Filesystem Provider in {}", filePath);
if (fileSystem != null) {
throw new IllegalStateException("There is already a jrt filesystem. Do you have multiple jrt-fs.jar files on the classpath?");
}
if (filePath.getNameCount() < 2) {
throw new IllegalArgumentException("Can't determine java home from " + filePath + " - please provide a complete path.");
}
try (URLClassLoader loader = new URLClassLoader(new URL[] { filePath.toUri().toURL() })) {
Map<String, String> env = new HashMap<>();
// note: providing java.home here is crucial, so that the correct runtime image is loaded.
// the class loader is only used to provide an implementation of JrtFileSystemProvider, if the current
// Java runtime doesn't provide one (e.g. if running in Java 8).
javaHome = filePath.getParent().getParent().toString();
env.put("java.home", javaHome);
LOG.debug("Creating jrt-fs with env {}", env);
fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env, loader);
}
packagesDirsToModules = new HashMap<>();
Path packages = fileSystem.getPath("packages");
try (Stream<Path> packagesStream = Files.list(packages)) {
packagesStream.forEach(p -> {
String packageName = p.getFileName().toString().replace('.', '/');
try (Stream<Path> modulesStream = Files.list(p)) {
Set<String> modules = modulesStream
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toSet());
packagesDirsToModules.put(packageName, modules);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
@ -105,7 +185,42 @@ public class ClasspathClassLoader extends URLClassLoader {
return getClass().getSimpleName()
+ "[["
+ StringUtils.join(getURLs(), ":")
+ "] parent: " + getParent() + ']';
+ "] jrt-fs: " + javaHome + " parent: " + getParent() + ']';
}
@Override
public InputStream getResourceAsStream(String name) {
// always first search in jrt-fs, if available
// note: we can't override just getResource(String) and return a jrt:/-URL, because the URL itself
// won't be connected to the correct JrtFileSystem and would just load using the system classloader.
if (fileSystem != null) {
int lastSlash = name.lastIndexOf('/');
String packageName = name.substring(0, Math.max(lastSlash, 0));
Set<String> moduleNames = packagesDirsToModules.get(packageName);
if (moduleNames != null) {
LOG.trace("Trying to find {} in jrt-fs with packageName={} and modules={}",
name, packageName, moduleNames);
for (String moduleCandidate : moduleNames) {
Path candidate = fileSystem.getPath("modules", moduleCandidate, name);
if (Files.exists(candidate)) {
LOG.trace("Found {}", candidate);
try {
// Note: The input streams from JrtFileSystem are ByteArrayInputStreams and do not
// need to be closed - we don't need to track these. The filesystem itself needs to be closed at the end.
// See https://github.com/openjdk/jdk/blob/970cd202049f592946f9c1004ea92dbd58abf6fb/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java#L334
return Files.newInputStream(candidate);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
}
// search in the other jars of the aux classpath.
// this will call this.getResource, which will do a child-first search, see below.
return super.getResourceAsStream(name);
}
@Override
@ -126,24 +241,22 @@ public class ClasspathClassLoader extends URLClassLoader {
@Override
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// checking local
c = findClass(name);
} catch (final ClassNotFoundException | SecurityException e) {
// checking parent
// This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
c = super.loadClass(name, resolve);
}
}
throw new IllegalStateException("This class loader shouldn't be used to load classes");
}
if (resolve) {
resolveClass(c);
@Override
public void close() throws IOException {
if (fileSystem != null) {
fileSystem.close();
// jrt created an own classloader to load the JrtFileSystemProvider class out of the
// jrt-fs.jar. This needs to be closed manually.
ClassLoader classLoader = fileSystem.getClass().getClassLoader();
if (classLoader instanceof URLClassLoader) {
((URLClassLoader) classLoader).close();
}
return c;
packagesDirsToModules = null;
fileSystem = null;
}
super.close();
}
}

View File

@ -4,6 +4,8 @@
package net.sourceforge.pmd.lang.ast;
import static java.lang.Math.max;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -25,14 +27,14 @@ public final class TokenMgrError extends FileAnalysisException {
*
* @param line Line number
* @param column Column number
* @param filename Filename. If unknown, it can be completed with {@link #setFileName(String)} later
* @param filename Filename. If unknown, it can be completed with {@link #setFileId(FileId)}} later
* @param message Message of the error
* @param cause Cause of the error, if any
*/
public TokenMgrError(int line, int column, @Nullable FileId filename, String message, @Nullable Throwable cause) {
super(message, cause);
this.line = line;
this.column = column;
this.line = max(line, 1);
this.column = max(column, 1);
if (filename != null) {
super.setFileId(filename);
}
@ -44,8 +46,8 @@ public final class TokenMgrError extends FileAnalysisException {
@InternalApi
public TokenMgrError(boolean eofSeen, String lexStateName, int errorLine, int errorColumn, String errorAfter, char curChar) {
super(makeReason(eofSeen, lexStateName, errorAfter, curChar));
line = errorLine;
column = errorColumn;
line = max(errorLine, 1);
column = max(errorColumn, 1);
}
public int getLine() {

View File

@ -96,6 +96,7 @@ public abstract class AbstractRenderer extends AbstractPropertySource implements
}
@Override
// TODO: consider to rename the flush method - this is actually closing the writer
public void flush() {
if (writer == null) {
// might happen, if no writer is set. E.g. in maven-pmd-plugin's PmdCollectingRenderer

View File

@ -28,6 +28,7 @@ import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import net.sourceforge.pmd.internal.util.IOUtil;
import net.sourceforge.pmd.properties.PropertyDescriptor;
import net.sourceforge.pmd.properties.PropertyFactory;
@ -129,6 +130,8 @@ public class XSLTRenderer extends XMLRenderer {
transformer.transform(source, result);
} catch (TransformerException e) {
throw new RuntimeException(e);
} finally {
IOUtil.closeQuietly(outputWriter);
}
}

View File

@ -0,0 +1,125 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.internal.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class ClasspathClassLoaderTest {
@TempDir
private Path tempDir;
@Test
void loadEmptyClasspathWithParent() throws IOException {
try (ClasspathClassLoader loader = new ClasspathClassLoader("", ClasspathClassLoader.class.getClassLoader())) {
try (InputStream resource = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(resource);
try (DataInputStream data = new DataInputStream(resource)) {
assertClassFile(data, Integer.valueOf(System.getProperty("java.specification.version")));
}
}
}
}
/**
* This test case just documents the current behavior: Eventually we load
* the class files from the system class loader, even if the auxclasspath
* is essentially empty and no parent is provided. This is an unavoidable
* behavior of {@link java.lang.ClassLoader#getResource(java.lang.String)}, which will
* search the class loader built into the VM (BootLoader).
*/
@Test
void loadEmptyClasspathNoParent() throws IOException {
try (ClasspathClassLoader loader = new ClasspathClassLoader("", null)) {
try (InputStream resource = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(resource);
try (DataInputStream data = new DataInputStream(resource)) {
assertClassFile(data, Integer.valueOf(System.getProperty("java.specification.version")));
}
}
}
}
@Test
void loadFromJar() throws IOException {
final String RESOURCE_NAME = "net/sourceforge/pmd/Sample.txt";
final String TEST_CONTENT = "Test\n";
Path jarPath = tempDir.resolve("custom.jar");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(jarPath))) {
out.putNextEntry(new ZipEntry(RESOURCE_NAME));
out.write(TEST_CONTENT.getBytes(StandardCharsets.UTF_8));
}
String classpath = jarPath.toString();
try (ClasspathClassLoader loader = new ClasspathClassLoader(classpath, null)) {
try (InputStream in = loader.getResourceAsStream(RESOURCE_NAME)) {
assertNotNull(in);
String s = IOUtil.readToString(in, StandardCharsets.UTF_8);
assertEquals(TEST_CONTENT, s);
}
}
}
/**
* Verifies, that we load the class files from the runtime image of the correct java home.
* This tests multiple versions, in order to avoid that the test accidentally is successful when
* testing e.g. java17 and running the build with java17. In that case, we might load java.lang.Object
* from the system classloader and not from jrt-fs.jar.
*
* <p>
* This test only runs, if you have a folder ${HOME}/openjdk{javaVersion}.
* </p>
*/
@ParameterizedTest
@ValueSource(ints = {11, 17, 21})
void loadFromJava(int javaVersion) throws IOException {
Path javaHome = Paths.get(System.getProperty("user.home"), "openjdk" + javaVersion);
assumeTrue(Files.isDirectory(javaHome), "Couldn't find java" + javaVersion + " installation at " + javaHome);
Path jrtfsPath = javaHome.resolve("lib/jrt-fs.jar");
assertTrue(Files.isRegularFile(jrtfsPath), "java" + javaVersion + " installation is incomplete. " + jrtfsPath + " not found!");
String classPath = jrtfsPath.toString();
try (ClasspathClassLoader loader = new ClasspathClassLoader(classPath, null)) {
assertEquals(javaHome.toString(), loader.javaHome);
try (InputStream stream = loader.getResourceAsStream("java/lang/Object.class")) {
assertNotNull(stream);
try (DataInputStream data = new DataInputStream(stream)) {
assertClassFile(data, javaVersion);
}
}
// should not fail for resources without a package
assertNull(loader.getResourceAsStream("ClassInDefaultPackage.class"));
}
}
private void assertClassFile(DataInputStream data, int javaVersion) throws IOException {
int magicNumber = data.readInt();
assertEquals(0xcafebabe, magicNumber);
data.readUnsignedShort(); // minorVersion
int majorVersion = data.readUnsignedShort();
assertEquals(44 + javaVersion, majorVersion);
}
}

View File

@ -0,0 +1,37 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.ast;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class TokenMgrErrorTest {
@Test
void invalidLocation() {
TokenMgrError error = new TokenMgrError(2, 0, null, "test", null);
// this shouldn't throw a IllegalArgumentException
assertEquals("line 2, column 1", error.location().startPosToString());
}
@Test
void invalidLocationJavaCC() {
TokenMgrError error = new TokenMgrError(false, "DEFAULT", 2, 0, "}", '\n');
// this shouldn't throw a IllegalArgumentException
assertEquals("line 2, column 1", error.location().startPosToString());
}
@Test
void validLocation() {
TokenMgrError error = new TokenMgrError(1, 1, null, "test", null);
assertEquals("line 1, column 1", error.location().startPosToString());
}
@Test
void validLocationJavaCC() {
TokenMgrError error = new TokenMgrError(false, "DEFAULT", 1, 1, "}", '\n');
assertEquals("line 1, column 1", error.location().startPosToString());
}
}

View File

@ -8,6 +8,8 @@ import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.ViolationSuppressor;
import net.sourceforge.pmd.lang.LanguageVersionHandler;
@ -36,6 +38,8 @@ import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguageProperties>
implements LanguageVersionHandler {
private static final Logger LOG = LoggerFactory.getLogger(JavaLanguageProcessor.class);
private final LanguageMetricsProvider myMetricsProvider = new JavaMetricsProvider();
private final JavaParser parser;
private final JavaParser parserWithoutProcessing;
@ -52,6 +56,7 @@ public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguagePr
public JavaLanguageProcessor(JavaLanguageProperties properties) {
this(properties, TypeSystem.usingClassLoaderClasspath(properties.getAnalysisClassLoader()));
LOG.debug("Using analysis classloader: {}", properties.getAnalysisClassLoader());
}
@Override
@ -124,4 +129,10 @@ public class JavaLanguageProcessor extends BatchLanguageProcessor<JavaLanguagePr
public void setTypeSystem(TypeSystem ts) {
this.typeSystem = Objects.requireNonNull(ts);
}
@Override
public void close() throws Exception {
this.typeSystem.logStats();
super.close();
}
}

View File

@ -81,7 +81,16 @@ public interface SymbolResolver {
}
return null;
}
@Override
public void logStats() {
stack.forEach(SymbolResolver::logStats);
}
};
}
/**
* Called at the end of the analysis in order to log out statistics of the resolved symbols.
*/
void logStats();
}

View File

@ -5,18 +5,20 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.net.URL;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.FailedLoader;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.UrlLoader;
import net.sourceforge.pmd.lang.java.symbols.internal.asm.Loader.StreamLoader;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
import net.sourceforge.pmd.util.AssertionUtil;
@ -24,6 +26,7 @@ import net.sourceforge.pmd.util.AssertionUtil;
* A {@link SymbolResolver} that reads class files to produce symbols.
*/
public class AsmSymbolResolver implements SymbolResolver {
private static final Logger LOG = LoggerFactory.getLogger(AsmSymbolResolver.class);
static final int ASM_API_V = Opcodes.ASM9;
@ -53,12 +56,12 @@ public class AsmSymbolResolver implements SymbolResolver {
String internalName = getInternalName(binaryName);
ClassStub found = knownStubs.computeIfAbsent(internalName, iname -> {
@Nullable URL url = getUrlOfInternalName(iname);
if (url == null) {
@Nullable InputStream inputStream = getStreamOfInternalName(iname);
if (inputStream == null) {
return failed;
}
return new ClassStub(this, iname, new UrlLoader(url), ClassStub.UNKNOWN_ARITY);
return new ClassStub(this, iname, new StreamLoader(binaryName, inputStream), ClassStub.UNKNOWN_ARITY);
});
if (!found.hasCanonicalName()) {
@ -84,7 +87,7 @@ public class AsmSymbolResolver implements SymbolResolver {
}
@Nullable
URL getUrlOfInternalName(String internalName) {
InputStream getStreamOfInternalName(String internalName) {
return classLoader.findResource(internalName + ".class");
}
@ -105,9 +108,38 @@ public class AsmSymbolResolver implements SymbolResolver {
if (prev != failed && prev != null) {
return prev;
}
@Nullable URL url = getUrlOfInternalName(iname);
Loader loader = url == null ? FailedLoader.INSTANCE : new UrlLoader(url);
@Nullable InputStream inputStream = getStreamOfInternalName(iname);
Loader loader = inputStream == null ? FailedLoader.INSTANCE : new StreamLoader(internalName, inputStream);
return new ClassStub(this, iname, loader, observedArity);
});
}
@Override
public void logStats() {
int numParsed = 0;
int numFailed = 0;
int numFailedQueries = 0;
int numNotParsed = 0;
for (ClassStub stub : knownStubs.values()) {
if (stub == failed) { // NOPMD CompareObjectsWithEquals
// Note that failed queries may occur under normal circumstances.
// Eg package names may be queried just to figure
// out whether they're packages or classes.
numFailedQueries++;
} else if (stub.isNotParsed()) {
numNotParsed++;
} else if (!stub.isFailed()) {
numParsed++;
} else {
numFailed++;
}
}
LOG.trace("Of {} distinct queries to the classloader, {} queries failed, "
+ "{} classes were found and parsed successfully, "
+ "{} were found but failed parsing (!), "
+ "{} were found but never parsed.",
knownStubs.size(), numFailedQueries, numParsed, numFailed, numNotParsed);
}
}

View File

@ -540,6 +540,14 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
return getSimpleName().isEmpty();
}
boolean isFailed() {
return this.parseLock.isFailed();
}
boolean isNotParsed() {
return this.parseLock.isNotParsed();
}
// </editor-fold>

View File

@ -4,7 +4,7 @@
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.net.URL;
import java.io.InputStream;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,9 +23,9 @@ public interface Classpath {
*
* @param resourcePath Resource path, as described in {@link ClassLoader#getResource(String)}
*
* @return A URL if the resource exists, otherwise null
* @return A InputStream if the resource exists, otherwise null
*/
@Nullable URL findResource(String resourcePath);
@Nullable InputStream findResource(String resourcePath);
// <editor-fold defaultstate="collapsed" desc="Transformation methods (defaults)">
@ -42,7 +42,7 @@ public interface Classpath {
default Classpath delegateTo(Classpath c) {
return path -> {
URL p = findResource(path);
InputStream p = findResource(path);
if (p != null) {
return p;
}
@ -56,11 +56,11 @@ public interface Classpath {
/**
* Returns a classpath instance that uses {@link ClassLoader#getResource(String)}
* Returns a classpath instance that uses {@link ClassLoader#getResourceAsStream(String)}
* to find resources.
*/
static Classpath forClassLoader(ClassLoader classLoader) {
return classLoader::getResource;
return classLoader::getResourceAsStream;
}
static Classpath contextClasspath() {

View File

@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -35,27 +34,23 @@ abstract class Loader {
}
static class UrlLoader extends Loader {
static class StreamLoader extends Loader {
private final @NonNull String name;
private final @NonNull InputStream stream;
private final @NonNull URL url;
UrlLoader(@NonNull URL url) {
assert url != null : "Null url";
this.url = url;
StreamLoader(@NonNull String name, @NonNull InputStream stream) {
this.name = name;
this.stream = stream;
}
@Override
@Nullable
InputStream getInputStream() throws IOException {
return url.openStream();
@NonNull InputStream getInputStream() {
return stream;
}
@Override
public String toString() {
return "(URL loader)";
return "(StreamLoader for " + name + ")";
}
}
}

View File

@ -58,6 +58,10 @@ abstract class ParseLock {
return getFinalStatus() == ParseStatus.FAILED;
}
public boolean isNotParsed() {
return status == ParseStatus.NOT_PARSED;
}
// will be called in the critical section after parse is done
protected void finishParse(boolean failed) {
// by default do nothing

View File

@ -8,6 +8,8 @@ import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
@ -16,6 +18,7 @@ import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
* A symbol resolver that knows about a few hand-picked symbols.
*/
final class MapSymResolver implements SymbolResolver {
private static final Logger LOG = LoggerFactory.getLogger(MapSymResolver.class);
private final Map<String, JClassSymbol> byCanonicalName;
private final Map<String, JClassSymbol> byBinaryName;
@ -35,4 +38,10 @@ final class MapSymResolver implements SymbolResolver {
public @Nullable JClassSymbol resolveClassFromCanonicalName(@NonNull String canonicalName) {
return byCanonicalName.get(canonicalName);
}
@Override
public void logStats() {
LOG.trace("Used {} classes by canonical name and {} classes by binary name",
byCanonicalName.size(), byBinaryName.size());
}
}

View File

@ -737,6 +737,13 @@ public final class TypeSystem {
return new TypeVarImpl.RegularTypeVar(this, symbol, HashTreePSet.empty());
}
/**
* Called at the end of the analysis to log statistics about the loaded types.
*/
public void logStats() {
resolver.logStats();
}
private static final class NullType implements JTypeMirror {
private final TypeSystem ts;

View File

@ -4,12 +4,21 @@
package net.sourceforge.pmd.lang.java.cpd;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import net.sourceforge.pmd.cpd.CpdLanguageProperties;
import net.sourceforge.pmd.cpd.Tokenizer;
import net.sourceforge.pmd.cpd.Tokens;
import net.sourceforge.pmd.cpd.test.CpdTextComparisonTest;
import net.sourceforge.pmd.cpd.test.LanguagePropertyConfig;
import net.sourceforge.pmd.lang.ast.TokenMgrError;
import net.sourceforge.pmd.lang.document.FileId;
import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
// TODO - enable tests
@ -29,6 +38,20 @@ class JavaTokenizerTest extends CpdTextComparisonTest {
doTest("StringTemplateReduction");
}
@Test
void testLexExceptionLocation() {
Tokenizer tokenizer = newTokenizer(defaultProperties());
Tokens tokens = new Tokens();
TokenMgrError lexException = assertThrows(TokenMgrError.class, () ->
Tokenizer.tokenize(tokenizer,
// note: the source deliberately contains an unbalanced quote, unterminated string literal
TextDocument.readOnlyString("class F {\n String s=\"abc\";\"\n}\n", FileId.UNKNOWN, getLanguage().getDefaultVersion()),
tokens)
);
// this shouldn't throw a IllegalArgumentException
assertThat(lexException.getMessage(), containsString("at line 3, column 1"));
}
@Test
void testStringTemplateReduction2() {
doTest("StringTemplateReduction2");

Some files were not shown because too many files have changed in this diff Show More