Skip to content
Open
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
246 changes: 246 additions & 0 deletions src/Get-Obsoletions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
<#
.SYNOPSIS
Lists obsoletions, deprecations, and outdated dependencies for a Maven project.

.DESCRIPTION
This script runs various Maven plugins to gather information about:
1. Code deprecations (calls to @Deprecated methods/classes).
2. Outdated dependencies (using versions-maven-plugin).
3. Outdated plugins (using versions-maven-plugin).
4. Unused declared dependencies (using maven-dependency-plugin).

.PARAMETER IncludeTransitive
If set, includes transitive dependencies in the outdated dependencies report.

.PARAMETER OnlyDeprecations
If set, only checks for code deprecations and skips dependency/plugin checks.

.PARAMETER PomFile
Path to the pom.xml file. Defaults to "pom.xml" in the script's directory.
#>
param(
[switch]$IncludeTransitive,
[switch]$OnlyDeprecations,
[string]$PomFile
)

$ErrorActionPreference = "Continue"

if ($PomFile) {
$pomPath = Resolve-Path $PomFile
} else {
$pomPath = Join-Path $PSScriptRoot "pom.xml"
Comment on lines +30 to +32
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The PomFile parameter is not validated for existence before being used. If a user provides a non-existent file path, Resolve-Path will throw an error that will be difficult to understand. Consider adding validation to check if the file exists and provide a clear error message if it doesn't.

Suggested change
$pomPath = Resolve-Path $PomFile
} else {
$pomPath = Join-Path $PSScriptRoot "pom.xml"
if (-not (Test-Path -LiteralPath $PomFile)) {
throw "The specified PomFile '$PomFile' does not exist. Please provide a valid path to pom.xml."
}
$pomPath = Resolve-Path -LiteralPath $PomFile
} else {
$defaultPom = Join-Path $PSScriptRoot "pom.xml"
if (-not (Test-Path -LiteralPath $defaultPom)) {
throw "No PomFile was specified and the default pom.xml was not found at '$defaultPom'."
}
$pomPath = Resolve-Path -LiteralPath $defaultPom

Copilot uses AI. Check for mistakes.
}

# Check Java version mismatch
try {
$javaExe = (Get-Command java -ErrorAction Stop).Source
# Resolve symlinks if possible or just take the path
$javaItem = Get-Item $javaExe
if ($javaItem.LinkType -eq "SymbolicLink") {
$javaExe = $javaItem.Target
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The Target property of a symbolic link can be a string or an array, depending on the platform and PowerShell version. Directly assigning it to a scalar variable may cause issues. Consider handling both cases or using the first element if it's an array.

Suggested change
$javaExe = $javaItem.Target
$target = $javaItem.Target
if ($target -is [Array]) {
$javaExe = $target[0]
} else {
$javaExe = $target
}

Copilot uses AI. Check for mistakes.
}
$javaExePath = Split-Path (Split-Path $javaExe)

if ($env:JAVA_HOME) {
$envJavaHome = (Get-Item $env:JAVA_HOME).FullName.TrimEnd('\/')
$exeJavaHome = (Get-Item $javaExePath).FullName.TrimEnd('\/')

if ($envJavaHome -ne $exeJavaHome) {
Write-Warning "JAVA_HOME ($env:JAVA_HOME) differs from 'java' in PATH ($javaExePath). Maven might use an unexpected version."
}
}
} catch {
# Ignore
}

function Get-MavenOutput {
param(
[string[]]$Goals,
[string[]]$Params
)
$mvnCmd = "mvn"
if ($IsWindows) { $mvnCmd = "mvn.cmd" }
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The variable $IsWindows is not available in Windows PowerShell 5.1 and below (only in PowerShell Core 6+). This will cause the script to fail on older Windows systems. Consider adding a check to define $IsWindows if it doesn't exist, such as: if (-not (Test-Path variable:IsWindows)) { $IsWindows = $true }

Copilot uses AI. Check for mistakes.

# -B for batch mode (less noise in logs)
$cmdArgs = @("-B", "-f", $pomPath) + $Goals + $Params

# Capture all output (stdout and stderr)
$output = & $mvnCmd $cmdArgs 2>&1
return $output
}

function Parse-VersionsOutput {
param(
[string[]]$OutputLines,
[switch]$IncludeTransitive
)

$results = @()
$bufferedArtifact = ""
$currentSection = "None"

foreach ($line in $OutputLines) {
# Detect section headers
if ($line -match "The following dependencies in Dependencies have newer versions:") {
$currentSection = "Direct"
$bufferedArtifact = ""
continue
}
if ($line -match "The following dependencies in Dependency Management have newer versions:") {
$currentSection = "Transitive"
$bufferedArtifact = ""
continue
}
Comment on lines +90 to +94
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The regex pattern assumes transitive dependencies come from "Dependency Management" section, but the versions-maven-plugin output may vary. Some transitive dependencies might appear in different sections or not be clearly marked. Consider documenting this assumption or verifying it handles all cases correctly.

Copilot uses AI. Check for mistakes.
if ($line -match "The following plugin updates are available:") {
$currentSection = "Direct" # Plugins are treated as direct/always show
$bufferedArtifact = ""
continue
}

# Determine if we should capture this section
$capture = $false
if ($currentSection -eq "Direct") { $capture = $true }
if ($currentSection -eq "Transitive" -and $IncludeTransitive) { $capture = $true }

if (-not $capture) {
continue
}

$cleanLine = $line -replace "^\[INFO\]\s*", ""

# Check for update line (contains "->")
if ($cleanLine -match "\s->\s") {
# Check if this line also contains the artifact name (group:artifact)
# Regex: start of line, non-spaces, colon, non-spaces
if ($cleanLine -match "^[^: ]+:[^: ]+") {
# Full line
$results += $cleanLine
$bufferedArtifact = ""
} elseif ($bufferedArtifact) {
# Continuation line
# Remove trailing dots from buffer
$prefix = $bufferedArtifact -replace "\.+$", ""
# Remove trailing spaces from prefix
$prefix = $prefix.Trim()
$results += "$prefix " + $cleanLine.Trim()
$bufferedArtifact = ""
} else {
# Orphan update line (shouldn't happen if logic is correct, but just in case)
$results += $cleanLine
}
}
# Check for artifact line (group:artifact, no "->")
# We look for lines starting with group:artifact
elseif ($cleanLine -match "^(?<name>[^: ]+:[^: ]+)") {
$bufferedArtifact = $cleanLine
}
}
return $results | Select-Object -Unique
}

Write-Host "Gathering information... (this may take a minute)" -ForegroundColor Gray

# 1. Code Deprecations
Write-Host "`n1. Code Deprecations (Calls to @Deprecated)" -ForegroundColor Cyan
Write-Host "------------------------------------------" -ForegroundColor DarkCyan
# We need clean compile to ensure we see warnings for all files
$compileOut = Get-MavenOutput -Goals @("clean", "compile") -Params @("-Dmaven.compiler.showDeprecation=true", "-Dmaven.compiler.showWarnings=true")
# Check for build failure
if ($compileOut -match "BUILD FAILURE") {
Write-Host "Build failed! Some results may be missing." -ForegroundColor Red
}

# Filter for [deprecation] tag or "has been deprecated" message
$deprecations = $compileOut | Where-Object { $_ -match "\[deprecation\]" -or $_ -match "has been deprecated" } | Select-Object -Unique

if ($deprecations) {
foreach ($line in $deprecations) {
# Format: [WARNING] /path/to/file.java:[12,34] [deprecation] ...
# Strip [WARNING] for cleaner output
Write-Host ($line -replace "^\[WARNING\]\s*", "")
}
} else {
Write-Host "None found." -ForegroundColor Green
}

if (-not $OnlyDeprecations) {
# 2. Outdated Dependencies
$depTitle = "2. Outdated Dependencies (Direct)"
if ($IncludeTransitive) { $depTitle = "2. Outdated Dependencies (Direct & Transitive)" }
Write-Host "`n$depTitle" -ForegroundColor Cyan
Write-Host "------------------------" -ForegroundColor DarkCyan
$verDepsOut = Get-MavenOutput -Goals @("versions:display-dependency-updates")

if ($verDepsOut -match "BUILD FAILURE") {
Write-Host "Build failed! Some results may be missing." -ForegroundColor Red
}

$outdatedDeps = Parse-VersionsOutput -OutputLines $verDepsOut -IncludeTransitive $IncludeTransitive

if ($outdatedDeps) {
foreach ($line in $outdatedDeps) {
Write-Host $line
}
} else {
Write-Host "None found." -ForegroundColor Green
}

# 3. Outdated Plugins
Write-Host "`n3. Outdated Plugins" -ForegroundColor Cyan
Write-Host "-------------------" -ForegroundColor DarkCyan
$verPluginsOut = Get-MavenOutput -Goals @("versions:display-plugin-updates")

if ($verPluginsOut -match "BUILD FAILURE") {
Write-Host "Build failed! Some results may be missing." -ForegroundColor Red
}

$outdatedPlugins = Parse-VersionsOutput -OutputLines $verPluginsOut -IncludeTransitive $true # Always show plugins

if ($outdatedPlugins) {
foreach ($line in $outdatedPlugins) {
Write-Host $line
}
} else {
Write-Host "None found." -ForegroundColor Green
}

# 4. Unused Dependencies
Write-Host "`n4. Unused Declared Dependencies" -ForegroundColor Cyan
Write-Host "-------------------------------" -ForegroundColor DarkCyan
$analyzeOut = Get-MavenOutput -Goals @("dependency:analyze") -Params @("-DignoreNonCompile=true")

if ($analyzeOut -match "BUILD FAILURE") {
Write-Host "Build failed! Some results may be missing." -ForegroundColor Red
}

$capturing = $false
$unusedDeps = @()
foreach ($line in $analyzeOut) {
if ($line -match "Unused declared dependencies found") {
$capturing = $true
continue
}

if ($capturing) {
# Stop capturing if we hit another section or INFO log that isn't an artifact
# Artifact lines in this section usually start with [WARNING] and contain colons like group:artifact:ver
if ($line -match "^\[WARNING\]\s+.*:.*:.*") {
Comment on lines +227 to +228
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The regex pattern "^[WARNING]\s+.:.:.*" is too permissive and may capture unrelated warning messages that happen to contain colons. A more specific pattern matching Maven artifact coordinates (groupId:artifactId:type:version:scope) would be more reliable, such as "^[WARNING]\s+[^:]+:[^:]+:[^:]+:[^:]+:[^:]+".

Suggested change
# Artifact lines in this section usually start with [WARNING] and contain colons like group:artifact:ver
if ($line -match "^\[WARNING\]\s+.*:.*:.*") {
# Artifact lines in this section usually start with [WARNING] and contain Maven coordinates like groupId:artifactId:type:version:scope
if ($line -match "^\[WARNING\]\s+[^:]+:[^:]+:[^:]+:[^:]+:[^:]+") {

Copilot uses AI. Check for mistakes.
$unusedDeps += ($line -replace "^\[WARNING\]\s*", "")
} elseif ($line -match "^\[INFO\]" -or ($line -match "^\[WARNING\]" -and $line -notmatch "dependencies found")) {
# If we hit a new warning header or info, stop
Comment on lines +226 to +231
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

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

The capturing logic may stop prematurely when encountering an INFO line. If there are INFO-level messages interspersed with the unused dependencies warnings (which can happen in Maven output), this will miss some unused dependencies. Consider a more robust approach that only stops when a clear section boundary is detected.

Suggested change
# Stop capturing if we hit another section or INFO log that isn't an artifact
# Artifact lines in this section usually start with [WARNING] and contain colons like group:artifact:ver
if ($line -match "^\[WARNING\]\s+.*:.*:.*") {
$unusedDeps += ($line -replace "^\[WARNING\]\s*", "")
} elseif ($line -match "^\[INFO\]" -or ($line -match "^\[WARNING\]" -and $line -notmatch "dependencies found")) {
# If we hit a new warning header or info, stop
# Stop capturing only when we clearly leave the unused-dependencies section.
# Artifact lines in this section usually start with [WARNING] and contain colons like group:artifact:ver
if ($line -match "^\[WARNING\]\s+.*:.*:.*") {
$unusedDeps += ($line -replace "^\[WARNING\]\s*", "")
} elseif (
# Non-artifact WARNING (e.g., a new warning header) ends this section
($line -match "^\[WARNING\]" -and $line -notmatch "dependencies found") -or
# Clear Maven section boundaries / build summary also end this section
$line -match "^\[INFO\]\s+---" -or # new plugin goal section
$line -match "^\[INFO\]\s+BUILD\s+(SUCCESS|FAILURE)" -or
$line -match "^\[INFO\]\s+-{10,}" # long separator line
) {

Copilot uses AI. Check for mistakes.
$capturing = $false
}
}
}

$unusedDeps = $unusedDeps | Select-Object -Unique
if ($unusedDeps) {
foreach ($item in $unusedDeps) {
Write-Host $item.Trim()
}
} else {
Write-Host "None found." -ForegroundColor Green
}
}

Loading