diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/.claude/skills/review-plugin/SKILL.md b/.claude/skills/review-plugin/SKILL.md new file mode 100644 index 000000000..79b5a7011 --- /dev/null +++ b/.claude/skills/review-plugin/SKILL.md @@ -0,0 +1,137 @@ +--- +name: review-plugin +description: Guides reviewing, testing, and merge-checking a third-party plugin submission or update pull request against the gephi-plugins repo, from checking out the contributor's fork branch through a final disposable-worktree merge into master-forge to catch pom.xml -list conflicts before they're real. Enforces this repo's three-branch model so master-forge and master are never pushed to directly, and gates every GitHub-visible or fork-visible action (pushes, PR comments/reviews, merges) on explicit user approval. Use whenever asked to review, test, fix, or merge-check a gephi-plugins pull request; when given a github.com/gephi/gephi-plugins PR URL or number; or when asked to resolve a plugin's pom.xml conflicts against master-forge, check whether a plugin update still builds, or work through a plugin-submission checklist — even if the user doesn't mention branches, forks, or worktrees explicitly. +--- + +# Reviewing a gephi-plugins PR + +This skill is the *process* for reviewing a plugin PR: which branch to stand on, when you're allowed +to push versus only allowed to suggest, and how to rehearse the merge before it's real. It +deliberately does not restate *what* to check in the code — that content already lives in this +repo's own docs and goes stale if duplicated here. Read from these as the relevant phase below calls +for them, don't summarize them in advance: + +| Doc | What's there | +|---|---| +| `CONTRIBUTING.md` → "Reviewing a plugin submission PR" | The actual review checklist (build, pom.xml, manifest, SPI correctness, licensing, file hygiene, tests, PR completeness) | +| `CONTRIBUTING.md` → "Documenting a plugin in master-forge's `` list" | Exact format for the name/origin/status comment block a new `` entry needs | +| `CONTRIBUTING.md` → "Maintaining a plugin after approval" | The unresponsive-fork/branch-adoption exception — a separate maintainer decision, out of scope for a routine review (see Guardrails) | +| `ARCHITECTURE.md` → "Repository / branch model" | What `master` / `master-forge` / `parent-pom` are each for, and why | +| `README.md` → "Submit a plugin" / "Update a plugin" | The fork+PR flow from the contributor's side, including "allow edits from maintainers" | +| `AGENTS.md` | Build/test commands (`mvn clean package`, single-module builds, etc.) | + +## Guardrails — read before doing anything + +This is someone else's code, on a public OSS project, and the repo has a branch model that's easy to +violate by accident. A few rules, and the reasoning behind each: + +- **Never push to `master` or `master-forge` directly, ever — including "just to test."** + `master-forge` aggregates every plugin into one build; `build.yml` (main CI) explicitly excludes it + from its triggers, so a direct push skips CI entirely and there's no PR to attribute the change to. + The only legitimate way something lands on `master-forge` is a merged PR. +- **Your own fix commits belong on the contributor's fork branch, not a branch in this repo.** When + you have push access (see "Access check" below), you're pushing to *their* branch on *their* fork — + the same branch the PR is already open from — not creating something new here. That's a different + thing from the "never push to master/master-forge" rule above; don't let the two blur together. + `gh pr checkout` sets this up correctly by itself. +- **The branch-adoption exception (`CONTRIBUTING.md`'s "When a plugin's fork stops being a reliable + source of truth") is not part of a routine review.** If a review surfaces an unresponsive + maintainer, flag it and stop — adopting a plugin onto a maintained branch is a separate, deliberate + decision the user makes explicitly, not something to fold into fixing a PR. +- **Prefer suggesting over rewriting.** Even with push access, a substantial rewrite of someone else's + logic isn't yours to make unilaterally — reserve direct pushes for mechanical fixes (a version bump, + a pom.xml conflict resolution, a checklist item like a missing license file) and leave anything + touching the plugin's actual behavior as a review comment for the contributor to act on. +- **Confirm with the user before anything that leaves your machine or is hard to undo**: pushing a + commit anywhere (including to the fork, even with access), posting a PR comment or review, approving + or requesting changes, or merging. Checking out, building, and the worktree merge rehearsal in Phase + 2 are local and disposable, so you can move through those without stopping at every step — but say + what you're about to do before you build code from someone's fork, since that means running their + Maven/Java code on this machine, not a sandboxed CI runner. +- **The Phase 2 worktree merge is a rehearsal, never a delivery.** Nothing from it gets pushed anywhere + under any circumstances — it exists purely to prove the PR *would* merge cleanly (or to work out + what the conflict resolution should look like) and gets deleted afterward win or lose. + +## Access check — which mode are you in? + +Before touching anything, find out whether you can actually push fixes or only suggest them: + +``` +gh pr view --repo gephi/gephi-plugins --json baseRefName,headRefName,headRepositoryOwner,headRepository,maintainerCanModify,mergeable +``` + +- `baseRefName` should be `master-forge`. If it's `master` instead, that's itself a review finding + (see `CONTRIBUTING.md`'s "PR completeness" section) — flag it to the user before going further, + since the rest of this workflow assumes the PR targets `master-forge`. +- `maintainerCanModify: true` means the contributor enabled "allow edits from maintainers" and you can + push fix commits to their branch. `false` means **comment-only mode**: you can build, test, and work + through the checklist locally, but any fix becomes a suggestion (a review comment, or a `gh pr + review` with a diff suggestion) rather than something you push. +- `mergeable: CONFLICTING` is common and expected — it usually just means `master-forge`'s + `` list has moved since the fork branched off. That's exactly what Phase 2 works out. +- Tell the user which mode you're in and what `mergeable` says before starting Phase 1, so they're not + surprised later when a fix turns into "here's a comment" instead of "here's a push." + +## Phase 1 — Isolated review and test + +Work entirely on the contributor's own branch here; nothing in this phase involves `master-forge`. + +1. `gh pr checkout ` — this fetches the fork, adds a remote for it if needed, and checks out a + local branch tracking the contributor's branch directly. This *is* "the source branch" the plugin's + commits live in — anything you commit here, if pushed, goes back to their fork. +2. Work through `CONTRIBUTING.md`'s "Reviewing a plugin submission PR" checklist against this branch, + including actually running `mvn clean package` from the repo root per `AGENTS.md` rather than + trusting a green PR description. Since only this contributor's plugin(s) are added to `pom.xml` on + their branch, this build is naturally isolated from every other plugin. +3. Report findings against specific named checklist items, per `CONTRIBUTING.md`'s own instruction — + not general impressions. +4. For anything you and the user agree is worth fixing directly (see Guardrails on what's in scope for + a direct push): + - **`maintainerCanModify: true`**: commit on this branch, confirm the diff with the user, then push + to the fork's remote (the one `gh pr checkout` set up) — never to a remote or branch in + `gephi/gephi-plugins` itself. + - **`maintainerCanModify: false`**: don't commit anything. Draft the fix as a PR comment or a `gh pr + review` suggestion instead, and only post it once the user says to. + +## Phase 2 — Integration check: does it still merge into master-forge? + +This is the final gate, and it's a rehearsal — it happens in a disposable worktree outside your normal +checkout, never in the branch you were just reviewing on and never by touching the real +`master-forge` branch. + +1. Fetch the current state: `git fetch origin master-forge`. +2. Create a throwaway worktree off it, in your scratchpad directory rather than inside this checkout: + `git worktree add /-integration-check origin/master-forge`. +3. In that worktree, merge the branch you reviewed in Phase 1: `git merge `. Expect + git to report the *entire* `` list as one conflict block, not a small localized one — it + can't tell the two sides only touched different entries, since both diverged from the same + near-empty list on `master`. The resolution is still mechanical: take `master-forge`'s (`HEAD`) + copy of the list wholesale and add this plugin's own name/origin/status comment block plus + `` line into it, per `CONTRIBUTING.md`'s documentation-convention section (link above) — + don't try to hand-merge the conflict markers line by line, and don't improvise the block's format + from memory. + - Only treat a conflict *outside* that list as worth surfacing on its own — but check what it + actually is before calling it a problem: compare against `master-forge`, not `master`. `master` + (the template) and `master-forge` can legitimately disagree on shared settings like + `gephi.version` or root build-plugin versions — `master` moving ahead doesn't mean + `master-forge` is due for the same bump, that's a deliberate, separate maintainer decision (see + `CONTRIBUTING.md`/`ARCHITECTURE.md` on the branch model). If you see a difference here, flag it + as a fact for the user to weigh in on rather than resolving it by picking either side. +4. Build from the worktree. A full `mvn clean package` from the root is the authoritative check — it + validates and builds *everything* currently on `master-forge`, not just this plugin — but that + means every other plugin already merged there, so budget real time for it. For a fast first pass, + `mvn -pl modules/ -am clean package` proves the plugin itself still builds against the + merged parent/config without paying for the whole reactor. +5. Report the result (clean merge or not, build passed or not, what the conflict resolution looked + like, and anything you flagged instead of resolving) to the user. Then remove the worktree + regardless of outcome — `git worktree remove ` (add `--force` if the merge left it dirty) — + since nothing in it is meant to persist. If the user wants the actual conflict resolution applied + for real, that's a Phase 1 fix-and-push (or a comment, in comment-only mode) using what you just + worked out here, not something you push from the worktree itself. + +## Wrap-up + +Summarize for the user: checklist findings, which mode you were in, whether the integration check +passed, and exactly what (if anything) you pushed or posted and where. If nothing was pushed or +posted, say so explicitly — "reviewed and tested, nothing sent anywhere yet" is a normal, complete +outcome for a comment-only-mode review. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 215bfabb9..f1f4f852a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,4 +19,9 @@ jobs: java-version: '17' distribution: 'temurin' - name: Build and validate project - run: mvn -B package --file pom.xml \ No newline at end of file + run: | + if [ -z "$(find modules -mindepth 2 -maxdepth 2 -name pom.xml 2>/dev/null)" ]; then + echo "No plugin modules present under modules/; skipping build/validate (see test-generation workflow for scaffold validation)." + exit 0 + fi + mvn -B package --file pom.xml \ No newline at end of file diff --git a/.github/workflows/test-generation.yml b/.github/workflows/test-generation.yml new file mode 100644 index 000000000..c1794c069 --- /dev/null +++ b/.github/workflows/test-generation.yml @@ -0,0 +1,63 @@ +name: test-generation + +# Integration test for the plugin generation process (gephi-maven-plugin's +# generate/validate/build-metadata/create-autoupdate goals). Runs on every +# master commit and on PRs targeting master (master has no plugin modules of +# its own, so build.yml's build_and_test can't validate scaffold changes) so +# a version bump of gephi-maven-plugin in pom.xml is regression-tested before +# real plugin repos pick it up. +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + generation-integration-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + + - name: Generate a throwaway fixture plugin + run: | + set -e + timeout 120 mvn -B org.gephi:gephi-maven-plugin:generate <<'EOF' + org.gephi.citest + ci-integration-test-plugin + 1.0.0 + CiIntegrationTestPlugin + CI Integration Test Plugin + Tool + CI Bot + + https://github.com/gephi/gephi-plugins + Apache 2.0 + CI test plugin for gephi-maven-plugin generation. + Generated by CI to validate the gephi-maven-plugin generation process end to end. + no + EOF + + - name: Sanity-check the generated fixture + run: | + set -e + test -f modules/CiIntegrationTestPlugin/pom.xml + grep -q 'modules/CiIntegrationTestPlugin' pom.xml + + - name: Build, validate and generate the autoupdate site + run: mvn -B package -P release --file pom.xml + + - name: Assert the generation process produced a working autoupdate site + run: | + set -e + GEPHI_MINOR=$(sed -nE 's/.*([0-9]+\.[0-9]+)\.[0-9]+<\/gephi\.version>.*/\1/p' pom.xml | head -1) + UPDATES_XML="target/site/plugins/${GEPHI_MINOR}/updates.xml" + NBM_FILE="target/site/plugins/${GEPHI_MINOR}/ci-integration-test-plugin-1.0.0.nbm" + + test -f "$UPDATES_XML" + test -f "$NBM_FILE" + grep -q 'codenamebase="org.gephi.citest.ci.integration.test.plugin"' "$UPDATES_XML" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1082a1c96 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,79 @@ +# AGENTS.md + +Instructions for AI coding agents (Claude Code, Cursor, Codex, etc.) working in this repository. + +## Project overview + +`gephi-plugins` is the scaffold and Maven build harness contributors fork to develop and submit +plugins for [Gephi](https://gephi.org), the graph visualization platform. A plugin is a NetBeans +module that implements one of Gephi's extension points (SPIs) — this repo does not contain Gephi +itself or, on `master`, any plugin source by default. See `README.md` for the day-to-day +getting-started flow (create/build/run/submit a plugin) and `ARCHITECTURE.md` for how the repository +(including its unusual three-branch model), the `gephi-maven-plugin` build lifecycle, and Gephi's +SPI/Lookup extension mechanism fit together. + +Read `ARCHITECTURE.md` before assuming something is broken: an empty `` list in `pom.xml`, +or the absence of `modules/pom.xml`, is expected on `master` — see its "Repository / branch model" +section. + +## Build and test + +Requires JDK 17 and Maven. + +- Scaffold a new plugin (interactive prompts): `mvn org.gephi:gephi-maven-plugin:generate` +- Build and validate every plugin currently listed in `pom.xml`: `mvn clean package` +- Build/test a single plugin module: `mvn -pl modules/ clean package` +- Run a single module's unit tests only: `mvn -pl modules/ test` +- Run Gephi with the module(s) installed (build first): `mvn org.gephi:gephi-maven-plugin:run` +- IDE run/debug configs are defined in `nbactions.xml`; the IntelliJ debug setup (remote debugger + + `-Drun.params.debug` VM option) is documented in `README.md`. + +`mvn package` always runs `gephi-maven-plugin:validate` at the `validate` phase — if a build fails +there, the error names the specific manifest/pom problem; fix that rather than working around it. + +## Adding or changing a plugin + +- One plugin (or one suite of related modules) per folder under `modules/`, added to the root + `pom.xml`'s `` list — `generate` does both steps for you; do it manually the same way if + extending an existing plugin's suite. +- A plugin's `pom.xml` inherits from `org.gephi:gephi-plugin-parent` (published from this repo's + `parent-pom` branch — not present in a normal `master` checkout). Add dependencies without a + ``; the parent's `dependencyManagement` supplies the version matching the target Gephi + release. See `ARCHITECTURE.md`. +- Register SPI implementations with `@ServiceProvider(service = ...)`; see `ARCHITECTURE.md`'s + "How Gephi can be extended" section for which SPI fits a given feature, and the + [core Gephi ARCHITECTURE.md](https://github.com/gephi/gephi/blob/master/ARCHITECTURE.md) for the + full API/SPI/Lookup design. +- Bump the plugin's own `` in its `pom.xml` on every update — the autoupdate site keys off + it, and reviewers check for it. +- Only list packages meant for other modules to use under `` in the plugin's + `pom.xml`. + +## Code style + +See `CONTRIBUTING.md`'s "Code quality" section rather than duplicating it here. + +## PR / commit guidelines + +- Plugin submissions (new plugin or update) target the `master-forge` branch, not `master` — see + README's "Submit a plugin" / "Update a plugin" sections. Changes to the scaffold itself (root + `pom.xml`, `.github/workflows`, this file, `ARCHITECTURE.md`) target `master`. +- Use `.github/issue_template.md`'s structure when filing or triaging bug reports. +- Keep commits scoped to one plugin or one logical change — a PR touching unrelated plugins in the + same suite stands out during review. + +### Reviewing a third-party plugin PR + +Most activity in this repo is reviewing plugin submissions rather than writing plugin code. When +asked to review one, work through `CONTRIBUTING.md`'s "Reviewing a plugin submission PR" checklist +(build, `pom.xml` config, manifest/branding, SPI registration, licensing, file hygiene, tests, PR +template completeness) and report findings against specific, named items from it rather than general +impressions — these PRs reliably have the same handful of holes, and the checklist exists to catch +them without re-deriving them each time. + +## Security + +- Never commit secrets, API keys, or tokens. `release-pom.yml` publishes to Maven Central using + repository secrets (GPG key, OSSRH credentials) — never hardcode credentials locally to bypass it. +- This is a public repository — don't add personal or employer-internal tooling references (private + registries, internal URLs, machine-specific paths) to any committed file. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..9ed93fac2 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,152 @@ +# gephi-plugins Architecture + +## What this repository is + +`gephi-plugins` is not a plugin, and (on the branch you're most likely looking at) it is not a +collection of plugins either. It is the scaffold and Maven build harness that contributors fork to +develop and submit plugins for [Gephi](https://gephi.org), the graph visualization platform built on +the Apache NetBeans Platform. + +```text +Your plugin module(s) (modules/, added by you or by `generate`) + │ implements a Gephi SPI, declares Maven deps on Gephi/NetBeans API modules + ▼ +Gephi (downloaded as a build dependency; see root pom.xml) + │ +NetBeans Platform (module system, Lookup, `.nbm` packaging) + │ + Java +``` + +A plugin is nothing more than another NetBeans module, built the same way Gephi's own modules are +built, that Gephi discovers at startup because it's on the classpath. This repository exists to make +that build set up correctly (manifest, packaging, dependency versions) without contributors needing +to hand-roll it. + +## Repository / branch model + +This is the part that isn't obvious from a single checkout: the same GitHub repository serves three +different purposes on three different branches. Confusion here (e.g. "why is `` empty?", +"where is `modules/pom.xml`?") almost always traces back to not knowing which branch does what. + +| Branch | Purpose | +|---|---| +| `master` | The template. `pom.xml`'s `` list starts empty. Contributors fork this branch and add their own plugin(s) to it locally — this is what `mvn org.gephi:gephi-maven-plugin:generate` does. Most day-to-day plugin development happens against a `master`-based fork. | +| `master-forge` | Where plugin submissions land. It accumulates every community plugin in one big multi-module build, and is what generates the plugin listing at gephi.org. PRs submitting or updating a plugin target this branch, not `master` (see README's "Submit a plugin"). | +| `parent-pom` | Hosts `modules/pom.xml`, the actual `gephi-plugin-parent` Maven artifact (see below). Pushing to this branch triggers `release-pom.yml`, which deploys `org.gephi:gephi-plugin-parent` to Maven Central. | + +`build.yml` (the main CI workflow) runs on every branch push *except* `master-forge`, `master`, +`parent-pom`, and `gh-pages` — i.e. on the feature/topic branches contributors actually push to. +`test-generation.yml` runs on `master` and is an integration test for the scaffold itself: it runs +`generate`, `package`, and the `release` profile's `build-metadata`/`create-autoupdate` goals against +a throwaway fixture plugin, so a `gephi-maven-plugin` version bump in `pom.xml` is caught before real +plugin repos pick it up. + +## How Gephi can be extended + +Gephi's extensibility model is what determines what a plugin *is*. This section summarizes the +mechanism; see the [core Gephi ARCHITECTURE.md](https://github.com/gephi/gephi/blob/master/ARCHITECTURE.md) +for the full treatment (API/SPI design philosophy, controllers/models, Lookup internals). + +Gephi separates **APIs** (functionality a module offers to others, e.g. `ProjectController`) from +**SPIs** (Service Provider Interfaces — extension points meant to be implemented by core modules +*and* plugins alike, e.g. `Importer`, `Layout`, `Statistics`). A plugin always extends an SPI; it +never needs to modify Gephi core to add functionality. + +| SPI | Extension point | +|---|---| +| Import SPI | File, database, and wizard importers | +| Layout SPI | Layout algorithms | +| Statistics SPI | Metrics and other graph algorithms | +| Tools SPI | Tools in the visualization toolbar | +| Export SPI | File exporters for graphs and graphics | +| Filters SPI | Filters | +| Preview SPI | Preview builders and renderers | +| Generator SPI | Graph generators | +| Data Laboratory SPI | Data Laboratory manipulators | +| Appearance SPI | Transformers for ranking and partitioning nodes/edges | +| Project SPI | Persistence providers for `.gephi` project files | +| Visualization SPI | Renderers for the newer `VisualizationEngine` module (work in progress) | + +Implementations are discovered at runtime through **Lookup**, NetBeans's service-registry mechanism, +not through any Gephi-specific plugin registry: + +```java +@ServiceProvider(service = Layout.class) +public class MyLayout implements Layout { +} +``` + +Annotating a class this way is what makes it show up in Gephi's layout list, exporter list, filter +list, etc. — implementing the interface alone is not enough; without `@ServiceProvider`, `Lookup` +will not find it. This is also the whole mechanism: there's no separate "plugin API" beyond the SPI +you're implementing and this annotation. + +## Anatomy of a plugin module + +`mvn org.gephi:gephi-maven-plugin:generate` produces this layout under `modules//`: + +```text +modules// +├── src/ +│ └── main/ +│ ├── java/ # SPI implementation(s), e.g. org.foo.myplugin.MyLayout +│ ├── resources/ # Bundle.properties, icons +│ └── nbm/ +│ └── manifest.mf # OpenIDE-Module-* branding/description/category entries +└── pom.xml # parent = org.gephi:gephi-plugin-parent, packaging = nbm +``` + +The generated `pom.xml` sets `` to `org.gephi:gephi-plugin-parent` (see below) and +configures `nbm-maven-plugin` with the plugin's author/license and a `` list — +only packages listed there are visible to other modules/plugins, mirroring the public-package +convention used throughout core Gephi. `manifest.mf` carries the branding shown in Gephi's Plugin +Manager (`OpenIDE-Module-Name`, `-Short-Description`, `-Long-Description`, `-Display-Category`), or +alternatively an `OpenIDE-Module-Localizing-Bundle` pointer into `Bundle.properties` when the text is +too long for the manifest format. + +A plugin can also be a **suite**: several modules in the same top-level folder that split API, +implementation, and UI concerns (the same four-role convention — `XxxAPI` / `XxxPlugin` / +`XxxPluginUI` / `DesktopXxx` — used across core Gephi's own modules). This is only worth doing for +plugins complex enough to need a shared API surface between multiple sub-modules; a single module is +enough for the vast majority of plugins. + +## Dependency management: `gephi-plugin-parent` + +Every plugin's `pom.xml` inherits from `org.gephi:gephi-plugin-parent` — an artifact built from this +repo's `parent-pom` branch, published to Maven Central, and versioned alongside Gephi itself (e.g. +`0.11.3`). It supplies: + +- Java/compiler settings (JDK 17 target). +- A `` entry for every Gephi and NetBeans Platform module a plugin might + depend on (`graph-api`, `layout-api`, `org-openide-util-lookup`, etc.), so a plugin's own `pom.xml` + can declare a dependency without a `` and get the right one for the Gephi version it + targets. + +This is why the root `pom.xml` on `master` and `master-forge` looks different from +`modules/pom.xml` on `parent-pom`: the former is the reactor POM that aggregates whichever plugin +modules exist in this checkout (`pom`, lists ``); the latter is the +plugin parent POM those modules inherit build configuration and dependency versions from. + +## The `gephi-maven-plugin` build lifecycle + +[`gephi-maven-plugin`](https://github.com/gephi/gephi-maven-plugin) is the Maven plugin that drives +everything above. Its goals, bound in this repo's root `pom.xml` or invoked directly: + +| Goal | When it runs | What it does | +|---|---|---| +| `generate` | Invoked manually | Interactively scaffolds a new plugin module and adds it to `pom.xml`'s `` | +| `validate` | Bound to the `validate` phase (every `mvn package`) | Checks every listed module's manifest/pom configuration is well-formed; fails the build with a specific reason if not | +| `run` | Invoked manually | Downloads/builds the matching Gephi distribution and launches it with the current modules installed, for manual testing | +| `build-metadata` / `create-autoupdate` | Bound to the `package` phase under the `release` profile | Generates the autoupdate site (`updates.xml` + `.nbm` files) published to `metadataUrl` — this is what powers Gephi's in-app Plugin Manager and the gephi.org plugin listing | +| `migrate` | Invoked manually | Helps update an existing plugin's configuration when the target Gephi version changes | + +## Root files + +```text +.github/workflows/ # build.yml (feature branches), test-generation.yml (master), release-pom.yml (parent-pom) +modules/ # one folder per plugin/suite; empty list on master until you add one +plugins/ # static assets (images) used by the gephi.org plugin listing, not source code +pom.xml # reactor POM for this checkout's plugin modules — NOT the plugin parent POM +nbactions.xml # NetBeans IDE run/debug actions (`mvn package org.gephi:gephi-maven-plugin:run`) +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..71bfa655d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,446 @@ +# Contributing to gephi-plugins + +This covers code-quality expectations for plugin code and, since most activity in this repository is +reviewing plugin submissions from third parties, a checklist for reviewing those PRs. For how to set +up, build, and submit a plugin in the first place, see `README.md`. For how the repository and +Gephi's extension mechanism fit together, see `ARCHITECTURE.md`. + +## Code quality + +- Write code, comments, commit messages, and PR descriptions in English. +- Match the existing style of the file being edited; don't reformat unrelated code in the same PR. +- Remove debug leftovers before submitting: ad-hoc debug output (see "Logging" below for what to use + instead), commented-out code blocks, placeholder text left over from the `generate` template (e.g. + "Plugin catch-phrase", "Insert dependencies here"). +- Plugins pick their own license (the `generate` goal defaults to Apache 2.0) — don't carry over + core Gephi's CDDL/GPL dual-license header into plugin code. + +## Gephi platform conventions + +Core Gephi has established conventions for cross-cutting concerns. Plugins should follow them +instead of introducing their own — this keeps a plugin's runtime behavior and log/UI output +consistent with core Gephi and every other installed plugin. + +### Logging + +Declare a per-class logger field and use it — this is the pattern throughout core Gephi, not SLF4J +or Log4j: + +```java +private static final Logger LOGGER = Logger.getLogger(ProjectControllerImpl.class.getName()); +``` + +(real example: `modules/ProjectAPI/.../ProjectControllerImpl.java`). Scope the logger to your own +class, never a shared or hardcoded name: the fully-qualified class name +(`org...SomeClass`) is what makes a log line traceable back to your plugin +specifically, as opposed to an `org.gephi.*` core line or another plugin's output. + +Use `SEVERE`/`WARNING`/`INFO`/`FINE` levels appropriately; don't invent your own scale. When logging +a caught exception, pass it as the log call's `Throwable` argument so the stack trace is preserved, +e.g.: + +```java +Logger.getLogger(SQLiteDriver.class.getName()).log(Level.SEVERE, null, ex); +// or, with a message: +Logger.getLogger("").log(Level.SEVERE, "Error while setting value for property '" + getName() + "'", e); +``` + +(real examples: `modules/DBDrivers/.../SQLiteDriver.java`, `modules/FiltersAPI/.../FilterProperty.java`). +Don't use `exception.printStackTrace()` — it bypasses the logger, so the message never respects the +user's configured log level or destination. Log messages are developer-facing, plain Java strings — +don't route them through `Bundle.properties`/`NbBundle`. + +### Localization + +No hardcoded English UI strings (labels, tooltips, dialog text, user-facing error messages) in Java +code. Put the string in a `Bundle.properties` file in the same package as the class +(`src/main/resources//Bundle.properties`) and reference it with +`NbBundle.getMessage(YourClass.class, "key")`: + +```properties +# org/gephi/datalab/api/Bundle.properties +DataLaboratoryHelper.ui.okButton.text=Ok +SettingsPanel.title={0} +``` +```java +NbBundle.getMessage(DataLaboratoryHelper.class, "SettingsPanel.title", ui.getDisplayName()); +``` + +The real key convention observed throughout core Gephi (320 `Bundle.properties` files, 477 +`NbBundle.getMessage` call sites) is `ClassName.member.property`, e.g. +`DataLaboratoryHelper.ui.okButton.text` for a field/button named `okButton`'s `text`. For +parameterized text, use `{0}`, `{1}`, ... `MessageFormat` placeholders in the property value and +pass the extra arguments to `NbBundle.getMessage(Class, String, Object...)` — as in the +`SettingsPanel.title={0}` example above. + +If you build UI with the NetBeans GUI builder (a `.form` file next to the `.java` file), it +generates `ResourceString` entries in the `.form` XML itself (with a `bundle=".../Bundle.properties"` +and `key="..."` pair per field) and writes the corresponding `NbBundle.getMessage(...)` call into +the generated code for you — you still need the key/value present in `Bundle.properties`, but you +don't hand-write the `NbBundle.getMessage` call for form fields. + +### Preferences + +Persist a **per-user, cross-session, cross-project** setting (e.g. "always export in millimeters") +with `NbPreferences.forModule(YourClass.class)`: + +```java +boolean defaultMM = NbPreferences.forModule(UIExporterPDF.class).getBoolean("Default_Millimeter", false); +millimeter = NbPreferences.forModule(UIExporterPDF.class).getBoolean("Millimeter", defaultMM); +// ... +NbPreferences.forModule(UIExporterPDF.class).putBoolean("Millimeter", millimeter); +``` + +(real example: `modules/PreviewExportUI/.../UIExporterPDFPanel.java`; `AbstractExporterSettings` in +the same module wraps the same pattern for `getInt`/`putInt`/`getFloat`/`putFloat`). Don't build a +plugin-invented properties file, serialization scheme, or static field for this. + +`NbPreferences` is backed by the NetBeans user directory and is global to the user's Gephi +installation — it is the wrong place for anything scoped to a single project or workspace (layout +parameters, filter configuration, anything that should be saved and reopened *with* the project). +That belongs in a Project SPI persistence provider that serializes into the `.gephi` file instead — +see `ARCHITECTURE.md`'s Project SPI row. + +### Library dependencies and version collisions + +Before adding a library directly, check whether Gephi already wraps it in one of its `*Wrapper` +modules and depend on that Maven artifact instead: + +- `core-library-wrapper` (`org.gephi:core-library-wrapper`) — e.g. `gson`, `commons-math3`, + `jfreechart`, `trove4j`, `commons-csv`, `commons-codec`, `commons-compress`. +- `org.gephi:ui-library-wrapper` — the Flamingo ribbon UI library. +- `org.gephi:batik-wrapper` — Batik/SVG rendering. + +```xml + + org.gephi + core-library-wrapper + +``` + +Two copies of the same library on the classpath (one via the wrapper, one direct) is wasteful and +can behave inconsistently — depend on the wrapper's Maven coordinate above instead of the raw +library's. + +If a plugin needs library X, and X transitively pulls in a different version of a library Y that +Gephi already provides (directly or via a wrapper), don't let both versions coexist on the +classpath — exclude X's transitive dependency on Y. `batik-wrapper`'s own `pom.xml` does exactly +this for `batik-transcoder`: + +```xml + + org.apache.xmlgraphics + batik-transcoder + ${gephi.batik.version} + + + xml-apis + xml-apis + + + commons-logging + commons-logging + + + +``` + +Only reach for `` when there's no wrapper module to depend on instead of the raw +library. Before submitting, run `mvn dependency:tree` in your plugin's module (add +`-Dincludes=:` to filter to one suspect library) to check whether a dependency +you added is pulling in a second, different version of something Gephi already provides. + +### Icons + +Use SVG, not PNG/GIF, for toolbar, menu, and branding icons. Load one the same way core Gephi does, via +`ImageUtilities.loadImageIcon(...)` from an SPI's `getIcon()`: + +```java +@Override +public Icon getIcon() { + return ImageUtilities.loadImageIcon("DataLaboratoryPlugin/settle.svg", false); +} +``` + +The path argument is `"/.svg"`, resolved as a classpath resource — for your own +plugin, put the file at `src/main/resources//icon.svg` in that same module. +Use the mandated pixel size: `viewBox="0 0 32 32"`. + +### Suite folder/package naming + +When a plugin has multiple modules, keep folder and package names consistent across the suite. For +example, a suite named `MyPlugin` from an org `com.foo`, mirroring `ARCHITECTURE.md`'s +API/SPI/implementation/UI package-role table, would look like: + +```text +modules/ +├── MyPluginAPI/ com.foo.myplugin.api (public API interfaces) +│ com.foo.myplugin.spi (SPI interfaces, if the suite defines its own) +│ com.foo.myplugin (API implementation) +└── MyPluginUI/ com.foo.myplugin.ui (Swing panels/wizards implementing the UI) +``` + +Don't mix naming schemes within one suite — e.g. `MyPluginCore` next to `myplugin.ui` next to +`MyPluginModuleThree` — pick one convention (ideally the one above) and apply it to every module +folder and every module's base package. + +### Test utilities + +For graph fixtures in unit tests, depend on GraphAPI's (`org.gephi:graph-api`) test-jar and reuse +`org.gephi.graph.GraphGenerator`'s static factory methods rather than hand-building a `GraphModel`: + +```java +GraphModel model = GraphGenerator.generateCompleteUndirectedGraph(10); +// also available: generateNullUndirectedGraph(int), generateCyclicDirectedGraph(int), +// generatePathUndirectedGraph(int), generateStarUndirectedGraph(int), and directed variants +``` + +It's the only class GraphAPI's `pom.xml` explicitly includes in its test-jar build (its own +comment: "Only this class is intended to be re-used other modules" — other GraphAPI test classes are +excluded from the jar). Declare the dependency as: + +```xml + + org.gephi + graph-api + test + test-jar + +``` + +(real consumer example: `modules/AppearanceAPI/pom.xml`). For import fixtures, depend the same way +on `org.gephi:io-importer-api`'s test-jar (unlike GraphAPI's, it isn't filtered to one class) and +use: + +```java +GraphModel model = GraphImporter.importGraph(new File("path/to/test.gexf")); +// or, to load a fixture bundled as a classpath resource next to a test class: +GraphModel model = GraphImporter.importGraph(MyPluginTest.class, "fixture.gexf"); +``` + +to build a `GraphModel` from a test file through the real import pipeline (real consumer example: +`modules/StatisticsPlugin/pom.xml`, depending on `io-importer-api` with `test-jar`), +instead of hand-rolling either one. + +### Comments + +Keep code comments short and about the current code — what invariant holds, what a non-obvious +value means — not the history of why it got that way or what alternative was rejected. That +belongs in the commit message or PR description, not the source, where it rots as the code +evolves. For example: + +```java +// Bad: explains history/rationale, will be stale the moment someone changes this again +// We used to retry 3 times here but that caused timeouts in slow environments, so now it's 1. +int maxRetries = 1; + +// Good: states a fact about the current code +// Must stay <= server-side rate limit of 1 req/s; see the ImporterUI cap for the same constant. +int maxRetries = 1; +``` + +## Reviewing a plugin submission PR + +Third-party plugin PRs are usually incomplete in the same handful of ways. Work through this list +before approving; when something's missing, name the specific item rather than asking generically +for "more polish." + +### Build actually passes + +- `mvn clean package` succeeds from the repository root, including the `gephi-maven-plugin:validate` + goal that runs automatically at the `validate` phase — don't take a green PR description at face + value, run it. +- If the PR adds a suite (multiple modules in one plugin folder), every sub-module folder is added to + the root `pom.xml`'s `` list, not just the first one. +- The PR touches only its own plugin's folder plus its own line in root `pom.xml`'s `` list + — changes to another plugin's code, to `gephi-plugin-parent`-managed versions, or to unrelated + workflow/config files are a red flag in a plugin-submission PR. + +### `pom.xml` configuration + +- `` is `org.gephi:gephi-plugin-parent` with a `` matching this repo's + `gephi.version` property — not a hardcoded fork or an unrelated parent. +- Dependencies on Gephi/NetBeans modules are declared **without** an explicit `` — the + parent's `dependencyManagement` should supply it. An explicit version there usually means the + author copy-pasted from an old example instead of relying on the parent, and can silently drift + from the Gephi version this repo targets. +- `` lists only packages meant for other modules to consume, not implementation + packages — an empty list, or a list that includes an obvious `impl`/internal package, is worth + flagging either way (the former may be intentional, the latter usually isn't). +- For an **update** to an existing plugin, the `` was actually bumped from the previous + release — this is easy to miss and the autoupdate site keys off it. +- `author`, and `licenseName`/`licenseFile` (or an equivalent license declaration), are filled in on + the `nbm-maven-plugin` configuration — not left as generated placeholders. + +### Manifest / branding + +- `src/main/nbm/manifest.mf` (or the `Bundle.properties` it points to via + `OpenIDE-Module-Localizing-Bundle`) has real values for module name, short description, long + description, and category — not leftover template placeholders. +- The declared category (`Layout`, `Export`, `Import`, `Data Laboratory`, `Filter`, `Generator`, + `Metric`, `Preview`, `Tool`, `Appearance`) actually matches the SPI the plugin implements (see + next section) — a layout algorithm categorized as "Tool" will be hard for users to find. + +### SPI implementation correctness + +- The class implementing the plugin's functionality is annotated `@ServiceProvider(service = ...)` + for the correct SPI interface. Without the annotation, Gephi's `Lookup` will not discover it and + the plugin will silently do nothing at runtime — this is a common, easy-to-miss failure mode that + the build will not catch. See `ARCHITECTURE.md`'s "How Gephi can be extended" section for the SPI + list. +- In a suite, UI-only code (Swing panels, wizards) lives in the UI/plugin-UI module, not mixed into + the same module as the core SPI implementation. +- If the plugin implements a cancellable long-running operation (a `Generator`, `Importer`, + `Statistics`, or other `LongTask`), check that `cancel()` actually stops the work instead of being + a stub that always returns `false` — a no-op cancel passes `mvn package` cleanly and only shows up + as an unresponsive UI at runtime. +- Any code that reads or writes the graph inside such a task must release its lock + (`Graph.readUnlock()`/`writeUnlock()`) on every exit path, including the cancellation path — a leak + here breaks graph consistency for the whole session, not just this plugin's feature (see + `ARCHITECTURE.md`'s locking model). + +### Platform conventions + +See "Gephi platform conventions" above for the reasoning; on review, check that the plugin: + +- Logs via `java.util.logging` with a per-class logger — not `System.out`, not SLF4J/Log4j. +- Has no hardcoded English UI strings — routed through `Bundle.properties` + `NbBundle.getMessage`. +- Persists settings via `NbPreferences.forModule(...)`, not a homemade scheme. +- Depends on a `*Wrapper` module instead of re-adding a library Gephi already wraps, and uses + `` rather than silently duplicating a library at a different version — `mvn + dependency:tree` on the plugin's module is the fast way to check. +- Uses SVG icons loaded via `ImageUtilities.loadImageIcon(...)`, not PNG/GIF. +- Uses consistent folder/package naming across suite modules, if any. +- Reuses `GraphGenerator`/`GraphImporter` from GraphAPI/ImportAPI's test-jars in tests rather than + reinventing fixture construction. +- Has comments that state facts about the current code, not rationale or history. + +### Licensing and attribution + +- A license file is actually included and matches what's declared in `pom.xml` (e.g. a plugin + declaring GPLv3 ships a `gpl-3.0.txt` or equivalent, not just the pom field). +- Any bundled third-party library, native binary, or copy-pasted code is disclosed and its license is + compatible with the plugin's declared license — don't assume silence means it's fine. + +### File hygiene + +- No build artifacts committed (`target/`, `*.class`, stray `*.jar` outside a deliberately vendored + native dependency, or a built `*.nbm` file — the packaged plugin binary the build produces, not + something to submit as source). +- No IDE metadata committed (`.idea/`, `.classpath`, `.project`, `*.iml`) unless the existing plugin + already tracks it as a convention. +- No secrets, tokens, or credentials anywhere in the diff. + +### Tests + +- Unit tests are included where the plugin's logic is non-trivial (e.g. algorithm implementations), + using the `org-netbeans-modules-nbjunit` dependency per `README.md`'s testing section. +- Tests are not disabled or skipped (`@Ignore`, `-DskipTests` baked into the module's own `pom.xml`) + to force a green build. + +### PR completeness + +- `.github/pull_request_template.md`'s checkboxes reflect what was actually done, not left checked + by default. +- The "What is the purpose of this plugin?" and "How to test your plugin in Gephi?" sections are + filled in with real, reproducible content — not left as the template's empty numbered list. +- The PR targets the `master-forge` branch for a plugin submission or update, not `master` (see + README's "Submit a plugin"). A PR against `master` touching only `modules/` is itself worth + questioning. + +## Maintaining a plugin after approval + +Once merged into `master-forge`, a plugin's contributor keeps their fork as the source of truth for +future updates — the flow is identical to the initial submission (see README's "Update a plugin"): +sync the fork with `master`, commit there, and open a new PR against `master-forge`. Don't grant a +contributor write access to this repository, or a branch of it, by default; a normal PR is the +standard path for every update, not just the first one. + +### When a plugin's fork stops being a reliable source of truth + +Some plugin maintainers go unresponsive, or their fork disappears. When that blocks an update the +plugin genuinely needs (e.g. following a new Gephi release) and there's no fork-based PR to wait on, +a maintainer can adopt the plugin onto its own branch instead. Decide this case by case — it's not +triggered by a fixed inactivity window, only by a real block a fork-based PR can't route around. + +- Cut a branch named after the plugin (e.g. `geolayout-plugin`) from its current state in + `master-forge`. +- Grant write access to that branch only, via a branch protection rule scoped to the name pattern — + to whoever is adopting maintenance (a `gephi-plugins` maintainer, or a new vetted community + adopter). Never grant blanket write access to the whole repository for this. +- Changes still land on `master-forge` only through a PR from that branch, the same as a fork would + — never a direct push. `master-forge` is a merge/build target, not something to develop against + directly, and `build.yml` (the main CI workflow) explicitly excludes `master-forge` from its + triggers, so a direct push there skips CI entirely. +- Never fork `master-forge` itself for this, or anything else — it aggregates every plugin in one + multi-module build, so a fork of it drags in every other plugin along with the one being adopted. + +The same applies to maintainer-driven bulk updates across many plugins at once (e.g. a Gephi version +bump): push to a short-lived branch and open a PR into `master-forge` rather than committing to it +directly, so the change still gets a CI run before landing. + +### Documenting a plugin in master-forge's `` list + +`master-forge`'s root `pom.xml` is the source of truth for which plugins are currently built and +published, so its `` list is also the only place that records where each plugin's code +actually comes from. Precede every `` entry with three one-line comments, in this order: + +```xml + + + +modules/ +``` + +- **name** is what the plugin calls itself — `OpenIDE-Module-Name` in `manifest.mf`, or the + `Bundle.properties` key it points to via `OpenIDE-Module-Localizing-Bundle` — not the `modules/` + folder name, which is often close but not guaranteed to match. +- **origin** is `fork (/)` for the normal case: a contributor's fork stays the source of + truth for future updates (see "Maintaining a plugin after approval" above). Use + `branch ()` when a maintainer has adopted the plugin onto its own branch in this + repository instead (per "When a plugin's fork stops being a reliable source of truth" above). If + neither can be determined — e.g. history predating PR-linked commits — use + `unknown (git author: )` rather than guessing. +- **status** is `active` for a plugin currently building, or `disabled — ` when the + `` line itself is commented out. Keep the reason short but specific enough that a future + maintainer knows what needs fixing before re-enabling it (e.g. a dependency that no longer + resolves, or an API the plugin needs to migrate off of) — not just that something's wrong. + +A disabled entry comments out all four lines: + +```xml + + + + +``` + +**Suites** (a plugin split across multiple `modules/` folders — API/Impl/UI, or a bundled dependency +like the streaming plugin's `JettyWrapper`) share one origin and one status, so don't repeat the same +three comments once per folder. Precede the whole run of `` lines with a single block +instead, using the suite's main module for `name` — the one module in the group whose `manifest.mf` +does *not* set `AutoUpdate-Show-In-Client: false`. That flag is how the other modules (API, Impl, +UI, or a bundled library) mark themselves as implementation details hidden from Gephi's plugin +manager, so its absence is what identifies the module the suite is actually known as: + +```xml + + + +modules/GraphStreaming +modules/DesktopStreaming +modules/StreamingAPI +modules/StreamingImpl +modules/JettyWrapper +modules/StreamingServer +``` + +Only group modules that share **both** the same origin and the same status — two folders from the +same fork owner but a different PR are two separate plugin submissions, not a suite, and keep their +own three-comment block each. If one module in an otherwise-grouped suite is later disabled while its +siblings stay active, split it back out into its own block rather than forcing a mismatched status +into the shared one. + +Update these comments whenever a plugin's status or origin changes — disabling or re-enabling it, +or adopting an unresponsive contributor's plugin onto a branch — don't leave them describing a +stale state. diff --git a/README.md b/README.md index 6220fa5dc..2fd2316f0 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,7 @@ It's the same thing. We say module because Gephi is a modular application and is This error appears when you try to run a module. To run Gephi with your plugin you need to run the `gephi-plugins` project, not your module. -## Best practices +## Contributing -### Code quality - -- Write your code in English, so it can be best reviewed and maintained. \ No newline at end of file +See [CONTRIBUTING.md](CONTRIBUTING.md) for code-quality expectations and, for maintainers, the +checklist used to review plugin submission PRs. \ No newline at end of file diff --git a/modules/Neo4jPlugin/pom.xml b/modules/Neo4jPlugin/pom.xml index 58626e6f1..82e3c4bdf 100644 --- a/modules/Neo4jPlugin/pom.xml +++ b/modules/Neo4jPlugin/pom.xml @@ -11,12 +11,12 @@ ouestware neo4j-plugin Neo4j Plugin - 4.0.1 + 5.0.0 nbm - 4.3.4 - 1.17.3 + 5.28.9 + 1.21.3 2.2.21 @@ -112,9 +112,6 @@ Apache 2.0 Apache v2.txt Benoit Simard - contact@bsimard.com - https://wwww.ouestware.com/en - https://github.com/gephi/gephi-plugins diff --git a/pom.xml b/pom.xml index 95d7dc5ff..29de2174f 100644 --- a/pom.xml +++ b/pom.xml @@ -293,9 +293,9 @@ modules/PositionRanking - - - + + + modules/Neo4jPlugin @@ -385,12 +385,12 @@ org.apache.maven.plugins maven-dependency-plugin - 3.6.1 + 3.11.0 org.apache.netbeans.utilities nbm-maven-plugin - 14.4 + 14.5 true @@ -401,12 +401,12 @@ org.apache.maven.plugins maven-scm-publish-plugin - 3.2.1 + 3.3.0 org.sonatype.central central-publishing-maven-plugin - 0.10.0 + 0.11.0 true