Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support upcoming PMD 7.0.0-rc1 release #176

Merged
merged 4 commits into from
Mar 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ See also [Uploading a SARIF file to GitHub](https://docs.github.com/en/code-secu
|------------|---|--------|---------------|
|`token` |no |"github.token"|Personal access token (PAT) used to query the latest PMD release via api.github.com and to determine the modified files of a push/pull request (see option "analyzeModifiedFilesOnly").<br>By default the automatic token for GitHub Actions is used.<br>If this action is used in GHES environment (e.g. the baseUrl is not "api.github.com"), then the token is only used for querying the modified files of a push/pull request. The token won't be used to query the latest PMD release.<br>[Learn more about automatic token authentication](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)<br>[Learn more about creating and using encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)|
|`version` |no |"latest"|PMD version to use. Using "latest" automatically downloads the latest version.<br>Available versions: <https://github.com/pmd/pmd/releases><br>Note: Only PMD 6.31.0 and later is supported due to required support for [Sarif report format](https://pmd.github.io/latest/pmd_userdocs_report_formats.html#sarif).|
|`downloadUrl`|no|"" |Manually specify the download URL from where the PMD binary distribution will be downloaded. By default, this parameter is empty and the download URL is automatically determined by querying the PMD releases at <https://github.com/pmd/pmd/releases>.<br>This can be used to test PMD versions that are not official releases.<br>If a downloadUrl is specified, then the version must not be "latest". You need to specify a concrete version. The downloaded PMD won't be cached and will always be downloaded again.|
|`sourcePath`|no |"." |Root directory for sources. Uses by default the current directory|
|`rulesets` |yes| |Comma separated list of ruleset names to use.|
|`analyzeModifiedFilesOnly`|no|"true"|Instead of analyze all files under "sourcePath", only the files that have been touched in a pull request or push will be analyzed. This makes the analysis faster and helps especially bigger projects which gradually want to introduce PMD. This helps in enforcing that no new code violation is introduced.<br>Depending on the analyzed language, the results might be less accurate results. At the moment, this is not a problem, as PMD mostly analyzes each file individually, but that might change in the future.<br>If the change is very big, not all files might be analyzed. Currently the maximum number of modified files is 300.<br>Note: When using PMD as a code scanner in order to create "Code scanning alerts" on GitHub, all files should be analyzed in order to produce a complete picture of the project. Otherwise alerts might get closed too soon.|
Expand Down
29 changes: 25 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ description: Execute PMD static code analysis.
inputs:
token:
description: >-
Personal access token (PAT) used to query the latest PMD release and the
download URL for PMD.
Personal access token (PAT) used to query the latest PMD release via
api.github.com and to determine the modified files of a push/pull request
(see option "analyzeModifiedFilesOnly").

By default the automatic token for GitHub Actions is used. [Learn more
about automatic token
By default the automatic token for GitHub Actions is used.

If this action is used in GHES environment (e.g. the baseUrl is not
"api.github.com"), then the token is only used for querying the modified
files of a push/pull request. The token won't be used to query the latest
PMD release.

[Learn more about automatic token
authentication](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)

[Learn more about creating and using encrypted
Expand All @@ -26,6 +33,20 @@ inputs:
format](https://pmd.github.io/latest/pmd_userdocs_report_formats.html#sarif).
required: false
default: latest
downloadUrl:
description: >-
Manually specify the download URL from where the PMD binary distribution
will be downloaded. By default, this parameter is empty and the download
URL is automatically determined by querying the PMD releases at
<https://github.com/pmd/pmd/releases>.

This can be used to test PMD versions that are not official releases.

If a downloadUrl is specified, then the version must not be "latest". You
need to specify a concrete version. The downloaded PMD won't be cached and
will always be downloaded again.
required: false
default: ''
sourcePath:
description: Root directory for sources
required: false
Expand Down
6 changes: 3 additions & 3 deletions dist/index.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ async function main() {
try {
pmdInfo = await util.downloadPmd(
validator.validateVersion(core.getInput('version'), { required: true }),
token
token,
validator.validateDownloadUrl(core.getInput('downloadUrl'), { required: true })
);

if (core.getInput('analyzeModifiedFilesOnly', { required: true }) === 'true') {
Expand Down
39 changes: 38 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const { Octokit } = require('@octokit/rest');
// repos/compareCommits API calls.
const MAX_PAGE = 10;

const downloadPmd = async function(version, token) {
async function downloadPmdRelease(version, token) {
let pmdVersion = version;
let cachedPmdPath = tc.find('pmd', version);
core.debug(`cached path result: ${cachedPmdPath}`);
Expand All @@ -33,12 +33,45 @@ const downloadPmd = async function(version, token) {
}
}

async function downloadPmdUrl(version, downloadUrl) {
let pmdVersion = version;
const pathToZipDistribution = await tc.downloadTool(downloadUrl);
const pmdExtractedFolder = await tc.extractZip(pathToZipDistribution);
core.info(`Downloaded PMD ${pmdVersion} from ${downloadUrl} to ${pmdExtractedFolder}`);
const files = await fs.readdir(pmdExtractedFolder);
core.debug(`ZIP archive content: ${files}`);
let subpath = files[0];
core.debug(`Using the first entry as basepath for PMD: ${subpath}`)
return {
version: pmdVersion,
path: path.join(pmdExtractedFolder, subpath)
}
}

const downloadPmd = async function(version, token, downloadUrl) {
if (version === 'latest' && downloadUrl !== undefined && downloadUrl !== '')
throw `Can't combine version=${version} with custom downloadUrl=${downloadUrl}`

if (downloadUrl === undefined || downloadUrl === '') {
return downloadPmdRelease(version, token);
} else {
return downloadPmdUrl(version, downloadUrl);
}
}

const executePmd = async function(pmdInfo, fileListOrSourcePath, ruleset, reportFormat, reportFile) {
let pmdExecutable = '/bin/run.sh pmd';
if (isPmd7Cli(pmdInfo.version)) {
pmdExecutable = '/bin/pmd';
}
if (os.platform() === 'win32') {
pmdExecutable = '\\bin\\pmd.bat';
}

if (isPmd7Cli(pmdInfo.version)) {
pmdExecutable += ' check --no-progress';
}

let sourceParameter = ['-d', fileListOrSourcePath];
if (Array.isArray(fileListOrSourcePath)) {
await writeFileList(fileListOrSourcePath);
Expand Down Expand Up @@ -69,6 +102,10 @@ function useNewArgsFormat(pmdVersion) {
return semver.gte(pmdVersion, '6.41.0');
}

function isPmd7Cli(pmdVersion) {
return semver.major(pmdVersion) >= 7;
}

async function determinePmdRelease(pmdVersion, token) {
core.debug(`determine release info for ${pmdVersion}`);

Expand Down
10 changes: 9 additions & 1 deletion lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ const validateRulesets = function(rulesets) {
return normalized;
}

const validateDownloadUrl = function(url) {
if (typeof(url) === 'string' && (url === '' || url.match(/^https?:\/\//)))
// valid
return url;

throw 'Invalid downloadUrl';
}

module.exports.validateVersion = validateVersion;
module.exports.validateSourcePath = validateSourcePath;
module.exports.validateRulesets = validateRulesets;

module.exports.validateDownloadUrl = validateDownloadUrl;
2 changes: 2 additions & 0 deletions tests/data/create-zips.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
zip -r pmd-bin-6.39.0.zip pmd-bin-6.39.0
zip -r pmd-bin-6.40.0.zip pmd-bin-6.40.0
zip -r pmd-bin-6.41.0.zip pmd-bin-6.41.0
zip -r pmd-bin-7.0.0-rc1.zip pmd-bin-7.0.0-rc1
zip -r pmd-bin-7.0.0-SNAPSHOT.zip pmd-bin-7.0.0-SNAPSHOT
Binary file added tests/data/pmd-bin-7.0.0-SNAPSHOT.zip
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/data/pmd-bin-7.0.0-SNAPSHOT/bin/pmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

echo "Running PMD 7.0.0-SNAPSHOT with: $@"

echo '{
"runs": [
{
"tool": {
"driver": {
"name": "PMD",
"version": "7.0.0-SNAPSHOT"
}
}
}
]
}' > pmd-report.sarif

17 changes: 17 additions & 0 deletions tests/data/pmd-bin-7.0.0-SNAPSHOT/bin/pmd.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@echo off
echo Running PMD 7.0.0-SNAPSHOT with: %*

(
echo {
echo "runs": [
echo {
echo "tool": {
echo "driver": {
echo "name": "PMD",
echo "version": "7.0.0-SNAPSHOT"
echo }
echo }
echo }
echo ]
echo }
)>"pmd-report.sarif"
Binary file added tests/data/pmd-bin-7.0.0-rc1.zip
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/data/pmd-bin-7.0.0-rc1/bin/pmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

echo "Running PMD 7.0.0-rc1 with: $@"

echo '{
"runs": [
{
"tool": {
"driver": {
"name": "PMD",
"version": "7.0.0-rc1"
}
}
}
]
}' > pmd-report.sarif

17 changes: 17 additions & 0 deletions tests/data/pmd-bin-7.0.0-rc1/bin/pmd.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@echo off
echo Running PMD 7.0.0-rc1 with: %*

(
echo {
echo "runs": [
echo {
echo "tool": {
echo "driver": {
echo "name": "PMD",
echo "version": "7.0.0-rc1"
echo }
echo }
echo }
echo ]
echo }
)>"pmd-report.sarif"
11 changes: 11 additions & 0 deletions tests/data/releases-7.0.0-rc1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "2",
"name": "latest pmd release for test (7.0.0-rc1)",
"tag_name": "pmd_releases/7.0.0-rc1",
"assets": [
{
"browser_download_url": "https://github.com/pmd/pmd/releases/download/pmd_releases/7.0.0-rc1/pmd-bin-7.0.0-rc1.zip",
"name": "pmd-bin-7.0.0-rc1.zip"
}
]
}
57 changes: 57 additions & 0 deletions tests/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const fs = require('fs').promises;
const exec = require('@actions/exec');
const util = require('../lib/util');
const github_utils = require('@actions/github/lib/utils')
const semver = require('semver');

const cachePath = path.join(__dirname, 'CACHE')
const tempPath = path.join(__dirname, 'TEMP')
Expand Down Expand Up @@ -403,6 +404,62 @@ describe('pmd-github-action-util', function () {
expect(fileList).toStrictEqual(['src/main/java/AvoidCatchingThrowableSample.java', 'src/main/java2/NewFile.java', 'src/main/java/ChangedFile.java', 'README.md']
.map(f => path.normalize(f)));
})

test('can execute PMD 7 with correct parameters', async () => {
nock('https://api.github.com')
.get('/repos/pmd/pmd/releases/tags/pmd_releases%2F7.0.0-rc1')
.replyWithFile(200, __dirname + '/data/releases-7.0.0-rc1.json', {
'Content-Type': 'application/json',
})
nock('https://github.com')
.get('/pmd/pmd/releases/download/pmd_releases/7.0.0-rc1/pmd-bin-7.0.0-rc1.zip')
.replyWithFile(200, __dirname + '/data/pmd-bin-7.0.0-rc1.zip')

const pmdInfo = await util.downloadPmd('7.0.0-rc1', 'my_test_token');
const execOutput = await util.executePmd(pmdInfo, ['src/file1.txt', 'src/file2.txt'], 'ruleset.xml', 'sarif', 'pmd-report.sarif');
const pmdFilelist = path.join('.', 'pmd.filelist');
await expect(fs.access(pmdFilelist)).resolves.toBe(undefined);
const pmdFilelistContent = await fs.readFile(pmdFilelist, 'utf8');
expect(pmdFilelistContent).toBe('src/file1.txt,src/file2.txt');
expect(execOutput.exitCode).toBe(0);
expect(execOutput.stdout.trim()).toBe('Running PMD 7.0.0-rc1 with: check --no-progress --no-cache --file-list pmd.filelist -f sarif -R ruleset.xml -r pmd-report.sarif');
await io.rmRF(pmdFilelist);
await io.rmRF(path.join('.', 'pmd-report.sarif'));
});

test('PMD 7 release candidates and final release version ordering', () => {
// see method util#isPmd7Cli
expect(semver.major('6.55.0') >= 7).toBe(false);
expect(semver.major('7.0.0-SNAPSHOT') >= 7).toBe(true);
expect(semver.major('7.0.0-rc1') >= 7).toBe(true);
expect(semver.major('7.0.0-rc2') >= 7).toBe(true);
expect(semver.major('7.0.0-rc3') >= 7).toBe(true);
expect(semver.major('7.0.0') >= 7).toBe(true);
expect(semver.major('7.0.1') >= 7).toBe(true);
expect(semver.major('7.1.0') >= 7).toBe(true);
});

test('Use downloadUrl', async () => {
nock('https://sourceforge.net')
.get('/projects/pmd/files/pmd/7.0.0-SNAPSHOT/pmd-bin-7.0.0-SNAPSHOT.zip/download')
.replyWithFile(200, __dirname + '/data/pmd-bin-7.0.0-SNAPSHOT.zip')

const pmdInfo = await util.downloadPmd('7.0.0-SNAPSHOT', 'my-token', 'https://sourceforge.net/projects/pmd/files/pmd/7.0.0-SNAPSHOT/pmd-bin-7.0.0-SNAPSHOT.zip/download');

const execOutput = await util.executePmd(pmdInfo, '.', 'ruleset.xml', 'sarif', 'pmd-report.sarif');
const reportFile = path.join('.', 'pmd-report.sarif');
await expect(fs.access(reportFile)).resolves.toBe(undefined);
const report = JSON.parse(await fs.readFile(reportFile, 'utf8'));
expect(report.runs[0].tool.driver.version).toBe('7.0.0-SNAPSHOT');
expect(execOutput.exitCode).toBe(0);
expect(execOutput.stdout.trim()).toBe('Running PMD 7.0.0-SNAPSHOT with: check --no-progress --no-cache -d . -f sarif -R ruleset.xml -r pmd-report.sarif');
await io.rmRF(reportFile)
});

test('Use downloadUrl invalid version', async () => {
await expect(util.downloadPmd('latest', 'my-token', 'https://example.org/download')).rejects
.toBe('Can\'t combine version=latest with custom downloadUrl=https://example.org/download');
});
});

function setGlobal(key, value) {
Expand Down
7 changes: 7 additions & 0 deletions tests/validator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ describe('pmd-github-action-validator', function() {
it('validate rulesets should throw', () => {
expect(() => validator.validateRulesets('; /bin/bash')).toThrow('Invalid rulesets');
});

test('validate download url', () => {
expect(() => validator.validateDownloadUrl('foo')).toThrow('Invalid downloadUrl');
expect(validator.validateDownloadUrl('')).toBe('');
expect(validator.validateDownloadUrl('https://sourceforge.net/projects/pmd/files/pmd/7.0.0-SNAPSHOT/pmd-bin-7.0.0-SNAPSHOT.zip/download'))
.toBe('https://sourceforge.net/projects/pmd/files/pmd/7.0.0-SNAPSHOT/pmd-bin-7.0.0-SNAPSHOT.zip/download');
});
});