Metadata-Version: 2.4
Name: java-dependency-analyzer
Version: 1.5.0
Summary: Java Dependency Analyzer is a tool that inspects dependencies.
License: MIT License
         
         Copyright (c) 2026 Ron Webb
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
License-File: LICENSE
Author: Ron Webb
Author-email: ron@ronella.xyz
Requires-Python: >=3.14
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: beautifulsoup4 (>=4.14.3,<5.0.0)
Requires-Dist: click (>=8.3.1,<9.0.0)
Requires-Dist: httpx (>=0.28.1,<0.29.0)
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
Requires-Dist: lxml (>=6.0.2,<7.0.0)
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
Requires-Dist: rich (>=13.0.0,<14.0.0)
Description-Content-Type: text/markdown

# Java Dependency Analyzer 1.5.0

> A Python CLI tool that inspects Java dependency hierarchies in Maven and Gradle projects and reports known vulnerabilities.

## Prerequisites

- Python `^3.14`
- [Poetry](https://python-poetry.org/) `2.2`

## Installation

### Via pip (recommended)

Install directly from [PyPI](https://pypi.org/project/java-dependency-analyzer/):

```bash
pip install java-dependency-analyzer
```

### From source

Clone the repository and install all dependencies using Poetry:

```bash
git clone <repository-url>
cd java-dependency-analyzer
poetry install
```

## Usage

```
jda [--version] <COMMAND> [OPTIONS] [FILE]
```

`COMMAND` is one of `gradle`, `maven`, or `sbom`.

### Global options

| Option | Description |
|---|---|
| `--version` | Print the installed version and exit. |

### gradle

```
jda gradle [OPTIONS] [FILE]
```

`FILE` is the path to a `build.gradle` or `build.gradle.kts` file.
Omit `FILE` when supplying `--dependencies`.

#### Gradle-only options

| Option | Short | Default | Description |
|---|---|---|---|
| `--module` | | _(none)_ | Gradle module name. When supplied, the dependency task becomes `<module>:dependencies`. Can only be used with `--project`. |

### maven

```
jda maven [OPTIONS] [FILE]
```

`FILE` is the path to a `pom.xml` file.
Omit `FILE` when supplying `--dependencies`.

### sbom

```
jda sbom [OPTIONS] FILE
```

`FILE` is the path to an SBOM JSON file. Supported standards: SPDX 2.3 and CycloneDX 1.6.

#### sbom-only options

| Option | Short | Required | Default | Description |
|---|---|---|---|---|
| `--standard` | `-s` | No | `cyclonedx` | SBOM standard of the input file: `spdx` or `cyclonedx` (case-insensitive). |

### Options (gradle / maven subcommands)

| Option | Short | Default | Description |
|---|---|---|---|
| `--project` | `-p` | | Root directory of the project to analyse. When supplied, the dependency tree is generated automatically and `FILE` / `--dependencies` must not be used. |
| `--java-home` | | _(system `JAVA_HOME`)_ | Directory to use as `JAVA_HOME`. Can only be used with `--project`. |
| `--use-wrapper` | | `false` | Use the project wrapper script (`gradlew`/`mvnw`) instead of the system build tool. Can only be used with `--project`. |
| `--wrapper` | | _(none)_ | Custom wrapper script name to use instead of the default (`gradlew`/`gradlew.bat` for Gradle, `mvnw`/`mvnw.cmd` for Maven). Can only be used with `--use-wrapper`. |
| `--dependencies` | `-d` | | Path to a pre-resolved dependency tree text file (see below). When supplied, parsing and transitive resolution are skipped. |
| `--output-format` | `-f` | `all` | Report format: `json`, `html`, or `all` (both). |
| `--output-dir` | `-o` | `./reports` | Directory to write the report file(s) into. |
| `--no-transitive` | | `false` | Skip transitive dependency resolution; analyse direct dependencies only. |
| `--verbose` | `-v` | `false` | Print progress messages to the console. |
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |

### Options (sbom subcommand)

| Option | Short | Default | Description |
|---|---|---|---|
| `--standard` | `-s` | `cyclonedx` | SBOM standard of the input file: `spdx` or `cyclonedx`. |
| `--output-format` | `-f` | `all` | Report format: `json`, `html`, or `all` (both). |
| `--output-dir` | `-o` | `./reports` | Directory to write the report file(s) into. |
| `--no-transitive` | | `false` | Skip transitive dependency resolution; analyse direct dependencies only. |
| `--verbose` | `-v` | `false` | Print progress messages to the console. |
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |

### Exit Codes

| Code | Meaning |
|---|---|
| `0` | Scan completed successfully; no vulnerabilities found. |
| `10` | Scan completed successfully; at least one vulnerability was detected. |

### Analysing a project directly (`--project`)

When a Gradle or Maven project is available locally, pass its root directory to `--project` and `jda` will generate the dependency tree automatically before scanning:

```bash
# Gradle project using the system gradle
jda gradle --project /path/to/my-project

# Gradle multi-module project, analyse the :api module
jda gradle --project /path/to/my-project --module api

# Maven project using the project wrapper, with a custom JAVA_HOME
jda maven --project /path/to/my-project --use-wrapper --java-home /usr/lib/jvm/java-21

# Gradle project using a custom wrapper script name
jda gradle --project /path/to/my-project --use-wrapper --wrapper gradlew-local
```

- `--java-home` overrides the `JAVA_HOME` environment variable for the invocation. If neither is set, the command fails with a clear error.
- `--use-wrapper` invokes `gradlew`/`gradlew.bat` (Gradle) or `mvnw`/`mvnw.cmd` (Maven) from the project root. A `UsageError` is raised when the wrapper script is absent.
- `--wrapper` overrides the default wrapper script name used by `--use-wrapper`. Can only be used with `--use-wrapper`.
- `--module` (Gradle only) specifies a sub-module; the dependency task becomes `<module>:dependencies`. Can only be used with `--project`.
- `--project` is mutually exclusive with both `FILE` and `--dependencies`.

### Pre-resolved dependency trees (`--dependencies`)

When a Gradle or Maven project already has a dependency tree available (e.g. from CI), you can pass it directly to skip the parser and transitive resolver:

- **Gradle**: generate with `gradle dependencies --configuration runtimeClasspath > gradle.txt`
- **Maven**: generate with `mvn dependency:tree -Dscope=runtime > maven.txt`

The report will reflect the exact tree from the file, including all transitive dependencies.

### Generating SBOM files

Use one of the approaches below to produce an SBOM JSON file that `jda sbom` can consume.

#### CycloneDX 1.6

**Gradle** — add the [CycloneDX Gradle plugin](https://github.com/CycloneDX/cyclonedx-gradle-plugin) to `build.gradle`:

```groovy
plugins {
    id 'org.cyclonedx.bom' version '3.2.4'
}
```

Or for Kotlin DSL (`build.gradle.kts`):

```kotlin
plugins {
    id("org.cyclonedx.bom") version "3.2.4"
}
```

Then run:

```bash
gradle cyclonedxBom
```

Output: `build/reports/bom.json`

**Maven** — run the [CycloneDX Maven plugin](https://github.com/CycloneDX/cyclonedx-maven-plugin) without modifying `pom.xml`:

```bash
mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom
```

Output: `target/bom.json`

#### SPDX 2.3

JDA expects a SPDX JSON document where each package entry in the `packages` array has an `externalRefs` entry with `referenceType: "purl"` and a `referenceLocator` in the form `pkg:maven/{groupId}/{artifactId}@{version}`.

**Gradle and Maven** — use [syft](https://github.com/anchore/syft):

```bash
# Gradle project
syft /path/to/gradle-project -o spdx-json=sbom.spdx.json

# Maven project
syft /path/to/maven-project -o spdx-json=sbom.spdx.json
```

### Examples

Analyse a Maven POM and produce both JSON and HTML reports in the current directory:

```bash
jda maven pom.xml
```

Analyse a Gradle build file and write only an HTML report to `./reports/`:

```bash
jda gradle build.gradle -f html -o reports/
```

Analyse direct dependencies only, with verbose output:

```bash
jda gradle build.gradle.kts --no-transitive -v
```

Scan using a pre-resolved Gradle dependency tree (skips transitive resolution):

```bash
jda gradle --dependencies runtime.txt -f json -o reports/
```

Scan using a pre-resolved Maven dependency tree (skips transitive resolution):

```bash
jda maven --dependencies maven.txt -f json -o reports/
```

Analyse a Gradle project directly (auto-generates the dependency tree):

```bash
jda gradle --project /path/to/my-gradle-project --use-wrapper
```

Analyse a Maven project directly with a custom JAVA_HOME:

```bash
jda maven --project /path/to/my-maven-project --java-home /usr/lib/jvm/java-21
```

Analyse a specific Gradle module using a custom wrapper script:

```bash
jda gradle --project /path/to/my-gradle-project --module api --use-wrapper --wrapper gradlew-local
```

Scan a CycloneDX SBOM file and produce both JSON and HTML reports:

```bash
jda sbom --standard cyclonedx bom.json
```

Scan an SPDX SBOM file and write an HTML report to a custom directory:

```bash
jda sbom --standard spdx sbom.spdx.json -f html -o reports/
```

## Configuration

| Environment Variable | Required | Default | Description |
|---|---|---|---|
| `GITHUB_TOKEN` | No | _(none)_ | A [GitHub personal access token](https://github.com/settings/tokens). When set, the `GhsaScanner` uses it to authenticate requests to the GitHub Advisory Database REST API, which significantly increases the rate limit (from ~60 unauthenticated requests/hour to 5 000 authenticated requests/hour). Without it, scans with many dependencies may trigger HTTP 403/429 responses and fall back to the OSV.dev API. |
| `GHSA_API_URL` | No | `https://api.github.com/advisories` | Override the GitHub Advisory Database REST API endpoint used by `GhsaScanner`. Useful for proxies or air-gapped mirrors. |
| `OSV_QUERY_URL` | No | `https://api.osv.dev/v1/query` | Override the OSV.dev single-query endpoint used by `OsvScanner`. |
| `OSV_VULN_URL` | No | `https://osv.dev/vulnerability/` | Override the OSV.dev vulnerability detail base URL embedded in reports. |
| `MAVEN_CENTRAL_URL` | No | `https://repo1.maven.org/maven2` | Override the Maven Central repository URL used by `TransitiveResolver` to fetch POM files. |
| `JDA_CONFIG_DIR` | No | _(none)_ | Directory used to store and load a custom `logging.ini`. On first run, the bundled `logging.ini` is seeded into this directory. If not set, the bundled config is loaded directly from the package. |

Set it in your shell or in a `.env` file in the working directory before running `jda`:

```bash
# shell
export GITHUB_TOKEN=ghp_yourTokenHere

# or in .env
GITHUB_TOKEN=ghp_yourTokenHere
```

## Logging

The tool writes logs to `java_dependency_analyzer.log` in the current working directory and prints progress messages to the console via Rich.

A `logging.ini` is **bundled inside the package** and loaded automatically — no manual setup is required after installation.

### Custom logging configuration

To override the default logging settings, set the `JDA_CONFIG_DIR` environment variable to a directory path. On first run, `jda` seeds the bundled `logging.ini` into that directory; edit the copy there to customise log levels, file paths, or handlers:

```bash
# shell
export JDA_CONFIG_DIR=/path/to/my-config

# or in .env
JDA_CONFIG_DIR=/path/to/my-config
```

## Architecture

```mermaid
graph TD
    CLI["jda CLI (cli.py)"] --> Parser["DependencyParser (ABC)"]
    Parser --> MavenParser
    Parser --> GradleParser
    Parser --> MavenDepTreeParser
    Parser --> GradleDepTreeParser
    Parser --> SbomParser
    CLI --> Resolver["TransitiveResolver<br/>(Maven Central)"]
    CLI --> Scanner["VulnerabilityScanner (ABC)"]
    Scanner --> OsvScanner["OsvScanner<br/>(OSV.dev API)"]
    Scanner --> GhsaScanner["GhsaScanner<br/>(GitHub Advisory DB)"]
    OsvScanner --> Cache["VulnerabilityCache<br/>(SQLite)"]
    GhsaScanner --> Cache
    CLI --> Reporter["Reporter (ABC)"]
    Reporter --> JsonReporter
    Reporter --> HtmlReporter
    MavenParser --> Dependency["Dependency / Vulnerability<br/>Dataclasses"]
    GradleParser --> Dependency
    MavenDepTreeParser --> Dependency
    GradleDepTreeParser --> Dependency
    SbomParser --> Dependency
    Resolver --> Dependency
    OsvScanner --> Dependency
    GhsaScanner --> Dependency
    JsonReporter --> ScanResult["ScanResult"]
    HtmlReporter --> ScanResult
    Dependency --> ScanResult
```

### Components

| Component | Location | Responsibility |
|---|---|---|
| CLI | `java_dependency_analyzer/cli.py` | Entry point (`gradle` / `maven` / `sbom` subcommands); orchestrates parsing, resolving, scanning, and reporting. |
| `MavenParser` | `parsers/maven_parser.py` | Parses `pom.xml`, resolves `${property}` placeholders, filters by runtime scope. |
| `GradleParser` | `parsers/gradle_parser.py` | Parses Groovy DSL (`build.gradle`) and Kotlin DSL (`build.gradle.kts`) files. |
| `MavenDepTreeParser` | `parsers/maven_dep_tree_parser.py` | Parses `mvn dependency:tree` text output into a full dependency tree. |
| `GradleDepTreeParser` | `parsers/gradle_dep_tree_parser.py` | Parses `gradle dependencies` text output into a full dependency tree. |
| `SbomParser` | `parsers/sbom_parser.py` | Parses SBOM JSON files in SPDX 2.3 or CycloneDX 1.6 format; extracts Maven dependencies via package URLs (`pkg:maven/…`). |
| `TransitiveResolver` | `resolvers/transitive.py` | Fetches transitive dependencies by downloading POM files from Maven Central. |
| `OsvScanner` | `scanners/osv_scanner.py` | Queries the [OSV.dev](https://osv.dev/) batch API for known CVEs. |
| `GhsaScanner` | `scanners/ghsa_scanner.py` | Queries the [GitHub Advisory Database](https://github.com/advisories) REST API for security advisories; automatically falls back to OSV when rate-limited (HTTP 403/429). |
| `VulnerabilityCache` | `cache/vulnerability_cache.py` | SQLite-backed cache for raw vulnerability API payloads with configurable TTL. |
| `DatabaseManager` | `cache/db.py` | Manages SQLite connection lifecycle and schema initialisation. |
| `xml_helpers` | `util/xml_helpers.py` | Shared POM XML utilities: `POM_NS` constant and `detect_pom_namespace()` for handling namespace-qualified and namespace-free POM documents. |
| `JsonReporter` | `reporters/json_reporter.py` | Writes a `ScanResult` to a JSON file. |
| `HtmlReporter` | `reporters/html_reporter.py` | Renders a `ScanResult` to a styled HTML report via a Jinja2 template. |

## Development Setup

Install all dependencies (including dev tools):

```bash
poetry install
```

### Running Tests

Run the full test suite with coverage and generate an HTML report:

```bash
poetry run pytest --cov=java_dependency_analyzer tests --cov-report html
```

### Code Quality

Format and lint the source code (linter must score 10/10):

```bash
poetry run black java_dependency_analyzer
poetry run pylint java_dependency_analyzer
```

## Publishing to PyPI

### Prerequisites

- A [PyPI](https://pypi.org/) account with an API token.

### Configure the token

```bash
poetry config pypi-token.pypi <your-token>
```

### Build and publish

```bash
poetry publish --build
```

This builds the source distribution and wheel, then uploads them to PyPI in one step.

> **Note:** PyPI releases are immutable. Once a version is published, it cannot be overwritten.  
> To fix a mistake, yank the release via the PyPI web UI and publish a new version.

## [Changelog](CHANGELOG.md)

## Author

Ron Webb &lt;ron@ronella.xyz&gt;

