Skip to content

Commit 9b861c5

Browse files
committed
(feat) Add Apache Ivy package manager support
- New Ivy plugin with direct parsing (default) and transitive resolution (opt-in) modes - Requires Ivy CLI only for transitive resolution - Pre-installed Apache Ivy 2.5.2 in Docker images - Enhanced Gradle plugin to handle Ivy dependencies - Includes functional tests and example configuration Signed-off-by: Alexandru Zănogeanu <[email protected]>
1 parent a8912e4 commit 9b861c5

File tree

21 files changed

+1472
-37
lines changed

21 files changed

+1472
-37
lines changed

.env.versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ DART_VERSION=2.18.4
1010
DOTNET_VERSION=6.0
1111
GO_VERSION=1.25.0
1212
HASKELL_STACK_VERSION=2.13.1
13+
IVY_VERSION=2.5.3
1314
JAVA_VERSION=21
1415
LICENSEE_VERSION=9.18.0
1516
NODEJS_VERSION=24.10.0

Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,23 @@ RUN curl -L https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_
363363
FROM scratch AS scala
364364
COPY --from=scalabuild /opt/sbt /opt/sbt
365365

366+
#------------------------------------------------------------------------
367+
# APACHE IVY
368+
FROM base AS ivybuild
369+
370+
ARG IVY_VERSION
371+
372+
ENV IVY_HOME=/opt/ivy
373+
ENV PATH=$PATH:$IVY_HOME/bin
374+
375+
RUN mkdir -p $IVY_HOME \
376+
&& curl -L https://archive.apache.org/dist/ant/ivy/$IVY_VERSION/apache-ivy-$IVY_VERSION-bin.tar.gz \
377+
| tar -xz -C $IVY_HOME --strip-components=1 \
378+
&& chmod a+x $IVY_HOME/bin/ivy
379+
380+
FROM scratch AS ivy
381+
COPY --from=ivybuild /opt/ivy /opt/ivy
382+
366383
#------------------------------------------------------------------------
367384
# SWIFT
368385
FROM base AS swiftbuild
@@ -540,6 +557,11 @@ ENV SBT_HOME=/opt/sbt
540557
ENV PATH=$PATH:$SBT_HOME/bin
541558
COPY --from=scala --chown=$USER:$USER $SBT_HOME $SBT_HOME
542559

560+
# Apache Ivy
561+
ENV IVY_HOME=/opt/ivy
562+
ENV PATH=$PATH:$IVY_HOME/bin
563+
COPY --from=ivy --chown=$USER:$USER $IVY_HOME $IVY_HOME
564+
543565
# Dart
544566
ENV DART_SDK=/opt/dart-sdk
545567
ENV PATH=$PATH:$DART_SDK/bin

examples/ivy.ort.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SPDX-FileCopyrightText: 2025 The ORT Project Authors <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Example ORT configuration for Apache Ivy projects
6+
7+
# Analyzer configuration
8+
analyzer:
9+
# Enable Ivy package manager
10+
enabled_package_managers:
11+
- Ivy
12+
13+
# Package manager specific configuration
14+
package_managers:
15+
Ivy:
16+
# Enable transitive dependency resolution using Ivy CLI
17+
# Requires Apache Ivy to be installed and available in PATH
18+
# Default: false
19+
resolveTransitive: true
20+
21+
# Skip excluded paths and scopes during analysis
22+
skip_excluded: true
23+
24+
# Path excludes
25+
excludes:
26+
paths:
27+
# Exclude test resources
28+
- pattern: "**/test/**"
29+
reason: "TEST_OF"
30+
comment: "Test code and resources"
31+
32+
# Scope excludes (Ivy configurations)
33+
scopes:
34+
- pattern: "test"
35+
reason: "TEST_DEPENDENCY_OF"
36+
comment: "Test dependencies are not distributed"
37+
38+
- pattern: "provided"
39+
reason: "PROVIDED_DEPENDENCY_OF"
40+
comment: "Provided dependencies are supplied by runtime environment"
41+
42+
# Curations for common issues
43+
curations:
44+
packages:
45+
# Example: Fix incorrect license for a package
46+
- id: "Maven:commons-lang:commons-lang:2.6"
47+
curations:
48+
declared_licenses:
49+
- "Apache-2.0"
50+

integrations/schemas/package-managers-schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"GoMod",
1616
"Gradle",
1717
"GradleInspector",
18+
"Ivy",
1819
"Maven",
1920
"NPM",
2021
"NuGet",

model/src/main/kotlin/config/AnalyzerConfiguration.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ data class AnalyzerConfiguration(
5757
"Conan",
5858
"GoMod",
5959
"GradleInspector",
60+
"Ivy",
6061
"Maven",
6162
"NPM",
6263
"NuGet",

plugins/package-managers/gradle-inspector/src/main/kotlin/GradleDependencyHandler.kt

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,29 @@ internal class GradleDependencyHandler(
7272

7373
val id = identifierFor(dependency)
7474
val model = dependency.mavenModel ?: run {
75-
issues += createAndLogIssue(
76-
source = GradleInspectorFactory.descriptor.displayName,
77-
message = "No Maven model available for '${id.toCoordinates()}'."
78-
)
75+
// If no Maven model is available, this might be an Ivy dependency or artifact without metadata
76+
// Only warn for non-Ivy dependencies, as Ivy dependencies are expected to not have Maven POMs
77+
if (id.type != "Ivy") {
78+
issues += createAndLogIssue(
79+
source = GradleInspectorFactory.descriptor.displayName,
80+
message = "No POM found for component '${id.toCoordinates()}'.",
81+
severity = org.ossreviewtoolkit.model.Severity.WARNING
82+
)
83+
}
7984

80-
return null
85+
// Create a basic package with minimal information
86+
return Package(
87+
id = id,
88+
authors = emptySet(),
89+
declaredLicenses = emptySet(),
90+
declaredLicensesProcessed = DeclaredLicenseProcessor.process(emptySet()),
91+
description = "",
92+
homepageUrl = "",
93+
binaryArtifact = RemoteArtifact.EMPTY,
94+
sourceArtifact = RemoteArtifact.EMPTY,
95+
vcs = VcsInfo.EMPTY,
96+
vcsProcessed = VcsInfo.EMPTY
97+
)
8198
}
8299

83100
val isSpringMetadataProject = with(id) {
@@ -241,7 +258,23 @@ private fun createRemoteArtifact(
241258
extension: String? = null
242259
): RemoteArtifact {
243260
val algorithm = "sha1"
244-
val artifactBaseUrl = pomUrl?.removeSuffix(".pom") ?: return RemoteArtifact.EMPTY
261+
262+
// Handle both Maven POM files (.pom) and Ivy descriptor files (ivy-*.xml)
263+
val artifactBaseUrl = when {
264+
pomUrl == null -> return RemoteArtifact.EMPTY
265+
pomUrl.endsWith(".pom") -> pomUrl.removeSuffix(".pom")
266+
pomUrl.contains("/ivy-") && pomUrl.endsWith(".xml") -> {
267+
// For Ivy descriptors like .../ivy-4.9.2.4.xml, extract the artifact name and version
268+
// Pattern: .../[module]/[version]/ivy-[version].xml -> .../[module]/[version]/[module]-[version]
269+
val pathParts = pomUrl.split("/")
270+
val version = pathParts[pathParts.size - 2]
271+
val module = pathParts[pathParts.size - 3]
272+
val basePath = pathParts.dropLast(1).joinToString("/")
273+
"$basePath/$module-$version"
274+
}
275+
276+
else -> pomUrl.removeSuffix(".xml")
277+
}
245278

246279
val artifactUrl = buildString {
247280
append(artifactBaseUrl)

plugins/package-managers/gradle-inspector/src/main/resources/template.init.gradle

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,42 @@ initscript {
2727

2828
allprojects {
2929
apply plugin: OrtModelPlugin
30+
31+
// Force Gradle to use Ivy descriptors and disable Gradle metadata for Ivy repositories
32+
// This allows legacy targetConfiguration to work
33+
repositories.configureEach { repo ->
34+
if (repo instanceof org.gradle.api.artifacts.repositories.IvyArtifactRepository) {
35+
repo.metadataSources {
36+
ivyDescriptor()
37+
artifact()
38+
// Explicitly disable Gradle metadata which causes variant resolution issues
39+
// with targetConfiguration
40+
}
41+
42+
// Check if repository should preserve its custom pattern layout
43+
// Set repo.name = "customIvyLayout" in your build.gradle to skip ORT pattern configuration
44+
def skipPatternConfig = repo.name?.contains("customIvyLayout") ||
45+
System.getProperty("ort.ivy.preservePatterns") == "true"
46+
47+
if (!skipPatternConfig) {
48+
// Support multiple Ivy layout patterns for different Artifactory configurations
49+
// Gradle will try each pattern in order until it finds the artifact
50+
// m2compatible = false keeps dots in organization (e.g., com.artifactory stays as com.artifactory)
51+
repo.patternLayout {
52+
// Artifactory Ivy layout with 'ivys' subdirectory (e.g., com.artifactory/module/version/ivys/ivy-version.xml)
53+
ivy '[organisation]/[module]/[revision]/ivys/ivy-[revision].xml'
54+
artifact '[organisation]/[module]/[revision]/jars/[artifact]-[revision](-[classifier])(.[ext])'
55+
artifact '[organisation]/[module]/[revision]/[type]s/[artifact]-[revision](-[classifier])(.[ext])'
56+
57+
// Standard Ivy layout (e.g., com.artifactory/module/version/ivy-version.xml)
58+
ivy '[organisation]/[module]/[revision]/ivy-[revision].xml'
59+
artifact '[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])'
60+
61+
// Keep m2compatible = false to preserve dots in organization names
62+
// If you need Maven-style paths (com/artifactory), repositories should use Maven layout instead
63+
m2compatible = false
64+
}
65+
}
66+
}
67+
}
3068
}

plugins/package-managers/gradle-model/src/main/kotlin/Extensions.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradlemodel
2222
import OrtDependency
2323

2424
/**
25-
* The type of this Gradle dependency. In case of a project, it is [projectType]. Otherwise it is "Maven" unless there
26-
* is no POM, then it is "Unknown".
25+
* The type of this Gradle dependency. In case of a project, it is [projectType]. Otherwise it is "Maven" if a POM
26+
* file exists, "Ivy" if an ivy.xml exists, or "Unknown" if there is no metadata.
2727
*/
2828
fun OrtDependency.getIdentifierType(projectType: String) =
2929
when {
3030
isProjectDependency -> projectType
31+
pomFile?.contains("/ivy-") == true && pomFile?.endsWith(".xml") == true -> "Ivy"
3132
pomFile != null -> "Maven"
3233
else -> "Unknown"
3334
}

0 commit comments

Comments
 (0)