Skip to content
Merged
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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,71 @@ create the token and paste it to proceed.
We use the [codacy-plugins-test](https://github.com/codacy/codacy-plugins-test) to test our external tools integration.
You can follow the instructions there to make sure your tool is working as expected.

## Agent Playbook: Updating This Repository End-to-End

This section is written for an AI coding agent (or a human) tasked with updating this repo — most commonly bumping the wrapped PHPMD version, but also base image / orb bumps. Follow it top to bottom.

### 1. What this repository is

This is a **Codacy engine**: a thin Scala wrapper (`src/main/scala/codacy/phpmd/PhpMd.scala`, built on `codacy-engine-scala-seed`, invoked via `com.codacy.tools.scala.seed.DockerEngine`) that packages [PHPMD](http://phpmd.org/) — a PHP static analysis/mess-detection tool — as a Docker image Codacy's platform can run against a customer's PHP source code. PHPMD itself is a **PHP/Composer** package, not something built from this repo's Scala source: it is pulled in as a `vendor/` dependency via `composer.json`/`composer.lock` and installed inside the Dockerfile's `builder` stage, then copied (`vendor/`) into the final image alongside the compiled Scala `bin/codacy-phpmd` launcher.

`docs/` holds Codacy's "pattern" configuration for this tool:

- `docs/patterns.json` — the list of PHPMD rules ("patterns"), their metadata, and a top-level `"version"` field recording which PHPMD release the pattern list corresponds to. **Hand-maintained, not auto-generated** — there is no DocGenerator script in this repo.
- `docs/description/description.json` + `docs/description/*.md` — human-readable titles/descriptions per pattern, also hand-maintained.
- `docs/tests/*` and `docs/multiple-tests/*` — PHP fixtures used by `codacy-plugins-test` to validate real output.
- `docs/tool-description.md` — short blurb about the tool.

Per the repo's own "Update Documentation" section above, `docs/patterns.json` and `docs/description/*` are updated **manually** by diffing against the official rule docs in [phpmd/phpmd](https://github.com/phpmd/phpmd) — there is no generator command to run.

### 2. Files that encode versions — check all of these on every update

| File | What it controls | What to check |
|---|---|---|
| `composer.json` → `require.phpmd/phpmd` | Which PHPMD release is installed into the Docker image | Bump to the target version string (e.g. `"2.15.0"`). |
| `composer.lock` | Locked resolution of `phpmd/phpmd` and its transitive PHP deps (`pdepend/pdepend`, `symfony/*`, etc.) | Do **not** hand-edit — regenerate with `composer update` after changing `composer.json` (see step 3). |
| `docs/patterns.json` → top-level `"version"` | Records which PHPMD version the pattern list documents | Bump manually to match `composer.json`; add/remove/rename any patterns changed upstream. |
| `.circleci/config.yml` → `codacy/base` orb | Shared CircleCI steps (checkout, versioning, sbt build/test, docker build/publish, tagging) | Check the latest published orb version if asked to bump it; not required for a pure PHPMD bump. |
| `.circleci/config.yml` → `codacy/plugins-test` orb | Runs `codacy-plugins-test` in CI after the image is built | Same as above. |
| `Dockerfile` → base image (`alpine:3.20`) and `openjdk8-jre` | OS/runtime the packaged PHP CLI + Scala JVM launcher run on | Only bump if asked explicitly, or if the target PHPMD version raises PHP/extension requirements — check PHPMD's release notes for its required PHP version and Alpine's `php` package version. |
| `build.sbt` → `codacy-engine-scala-seed` dependency | Codacy's engine SDK | Not tied to PHPMD bumps; check Maven Central only if asked to update it separately. |

### 3. Step-by-step update procedure

1. **Bump `composer.json`**: set `require.phpmd/phpmd` to the target version.
2. **Regenerate `composer.lock`.** You need PHP + Composer available locally (or use the Dockerfile's `builder` stage). The repo's own instructions:
```
curl -sS https://getcomposer.org/installer | php
php composer.phar update
```
If Composer fails to fetch a package with a GitHub OAuth/rate-limit error, create a token at https://github.com/settings/tokens/new and supply it when prompted.
3. **Update `docs/patterns.json`** — bump the top-level `"version"` field to match, and manually reconcile the pattern list against the changelog/rule docs at https://github.com/phpmd/phpmd for the target version (added/removed/renamed rules). Update `docs/description/*` similarly if descriptions changed.
4. **Compile and test the Scala wrapper**: `sbt "scalafmtCheckAll; scalafmtSbtCheck; test; stage"`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: Using scalafmtAll and scalafmtSbt in the local development step is more efficient because it automatically applies formatting fixes instead of simply flagging errors.

Suggested change
4. **Compile and test the Scala wrapper**: `sbt "scalafmtCheckAll; scalafmtSbtCheck; test; stage"`.
4. **Compile and test the Scala wrapper**: `sbt "scalafmtAll; scalafmtSbt; test; stage"`.

5. **Build the Docker image**: `docker build -t codacy-phpmd .` (this re-runs `composer install --no-scripts` inside the `builder` stage, so it will fail fast if `composer.lock` is inconsistent with `composer.json`).
6. **Run `codacy-plugins-test` locally** before pushing — clone https://github.com/codacy/codacy-plugins-test and run it (this repo's CI runs it with `run_multiple_tests: true`, i.e. both the single-file `docs/tests/*` fixtures and the `docs/multiple-tests/*` scenarios) against your local image tag.
7. **Iterate on failures**, re-running only the relevant test command after each fix.
8. **Commit** the version bump(s) together with `composer.lock` and any `docs/` changes in one change.
9. **Push and open a PR.** CI (`.circleci/config.yml`) runs `codacy/checkout_and_version` → `codacy/sbt` (scalafmt checks, `test`, `stage`, `docker build`, `docker save`) → `codacy_plugins_test/run` (`run_multiple_tests: true`) → `codacy/publish_docker` (master only) → `codacy/tag_version`.
10. **Poll the PR's real CI checks until they all pass — local validation is NOT the finish line.** After every push, run `gh pr checks <pr-url>` and keep re-polling (short sleep while any check is `pending`) until all checks finish. If a check fails, fetch its actual log (CircleCI API/UI for the failing job — don't guess), find the true root cause, fix it, push again (never `--no-verify`, never force-push), and re-poll. Repeat until every check is green. The CI environment's toolchain (PHP version, Composer version, Alpine package versions) can differ subtly from a local machine, so a clean local build does not guarantee CI passes. Only stop iterating when every check passes, or you hit a genuine product/infra decision that needs a human — in which case explain it in the PR rather than guessing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: The GitHub CLI 'gh pr checks' command supports a '--watch' flag that handles polling automatically, making the manual 'sleep' loop unnecessary.

Suggested change
10. **Poll the PR's real CI checks until they all pass — local validation is NOT the finish line.** After every push, run `gh pr checks <pr-url>` and keep re-polling (short sleep while any check is `pending`) until all checks finish. If a check fails, fetch its actual log (CircleCI API/UI for the failing job — don't guess), find the true root cause, fix it, push again (never `--no-verify`, never force-push), and re-poll. Repeat until every check is green. The CI environment's toolchain (PHP version, Composer version, Alpine package versions) can differ subtly from a local machine, so a clean local build does not guarantee CI passes. Only stop iterating when every check passes, or you hit a genuine product/infra decision that needs a human — in which case explain it in the PR rather than guessing.
10. **Poll the PR's real CI checks until they all pass — local validation is NOT the finish line.** After every push, run `gh pr checks --watch`. If a check fails, fetch its actual log (CircleCI API/UI for the failing job — don't guess), find the true root cause, fix it, push again (never `--no-verify`, never force-push). Repeat until every check is green. The CI environment's toolchain (PHP version, Composer version, Alpine package versions) can differ subtly from a local machine, so a clean local build does not guarantee CI passes. Only stop iterating when every check passes, or you hit a genuine product/infra decision that needs a human — in which case explain it in the PR rather than guessing.


### 4. Common failure modes and fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| `composer update` fails with a GitHub rate-limit/OAuth error | Unauthenticated Composer hitting GitHub API limits when resolving `dist` zips | Generate a token at https://github.com/settings/tokens/new and provide it to Composer when prompted |
| `docker build` fails in the `builder` stage on `composer install` | `composer.lock` out of sync with `composer.json` (e.g. hand-edited version without regenerating the lock file) | Regenerate `composer.lock` via `composer update` rather than editing it directly |
| `pattern`/`json` plugins-test checks fail | A PHPMD rule was renamed/added/removed upstream between versions | Manually reconcile `docs/patterns.json` (and `docs/description/*`) against https://github.com/phpmd/phpmd's docs/changelog for the target version |
| `multiple` plugins-test checks fail on a specific fixture | Expected output in `docs/multiple-tests/*` stale for the new tool behavior | Update the expected results to match the new (verified correct) output |

### 5. Definition of done

- `composer.json` bumped and `composer.lock` regenerated to match (not hand-edited).
- `docs/patterns.json` version field and pattern list reconciled with the target PHPMD release; `docs/description/*` updated if descriptions changed.
- Local `sbt` build/format/test commands pass.
- Docker image builds successfully (this also validates the Composer install).
- `codacy-plugins-test` (single and multiple test modes) pass locally against the freshly built image.
- **After pushing and opening/updating the PR, every CI check on it is green.** Poll `gh pr checks <pr-url>` and iterate on any failure (fetch the real CI log, fix, push, re-poll) until all pass — a passing local build is not sufficient, because the CI toolchain can differ from your local one (see step 10).

## What is Codacy?

[Codacy](https://www.codacy.com/) is an Automated Code Review Tool that monitors your technical debt, helps you improve your code quality, teaches best practices to your developers, and helps you save time in Code Reviews.
Expand Down