forked from phoedos/pmd
Merge branch 'master' into java-ast-updates
This commit is contained in:
commit
ed0cff6da9
@ -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",
|
||||
|
47
.ci/build.sh
47
.ci/build.sh
@ -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
|
||||
}
|
||||
|
||||
|
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@ -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
|
||||
|
2
.github/workflows/git-repo-sync.yml
vendored
2
.github/workflows/git-repo-sync.yml
vendored
@ -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
|
||||
|
2
.github/workflows/troubleshooting.yml
vendored
2
.github/workflows/troubleshooting.yml
vendored
@ -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: |
|
||||
|
16
Gemfile.lock
16
Gemfile.lock
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -106,6 +106,8 @@ in the Migration Guide.
|
||||
* core
|
||||
* [#1027](https://github.com/pmd/pmd/issues/1027): \[core] Apply the new PropertyDescriptor<Pattern> 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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user