Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
## [Unreleased]
[Unreleased]: https://github.com/cashapp/licensee/compare/1.14.1...HEAD]

**Added**

- Support ignoring dependencies by regex.

**Changed**

- The minimum-supported Gradle version is now 9.0.
Expand Down
2 changes: 2 additions & 0 deletions api/licensee.api
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public abstract interface class app/cash/licensee/LicenseeExtension {
public fun ignoreDependencies (Ljava/lang/String;Ljava/lang/String;)V
public abstract fun ignoreDependencies (Ljava/lang/String;Ljava/lang/String;Lorg/gradle/api/Action;)V
public fun ignoreDependencies (Ljava/lang/String;Lorg/gradle/api/Action;)V
public fun ignoreDependenciesByRegex (Ljava/lang/String;)V
public abstract fun ignoreDependenciesByRegex (Ljava/lang/String;Lorg/gradle/api/Action;)V
public abstract fun unusedAction (Lapp/cash/licensee/UnusedAction;)V
public abstract fun violationAction (Lapp/cash/licensee/ViolationAction;)V
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/app/cash/licensee/dependencyGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.gradle.api.logging.Logger
internal data class DependencyConfig(
val ignoredGroupIds: Map<String, IgnoredData>,
val ignoredCoordinates: Map<String, Map<String, IgnoredData>>,
val ignoredRegexes: Map<Regex, IgnoredData>,
) : Serializable

internal data class IgnoredData(val reason: String?, val transitive: Boolean) : Serializable
Expand All @@ -45,6 +46,7 @@ internal fun loadDependencyCoordinates(

val unusedGroupIds = config.ignoredGroupIds.keys.toMutableSet()
val unusedCoordinates = mutableSetOf<Pair<String, String>>()
val unusedRegexes = config.ignoredRegexes.keys.toMutableSet()
for ((groupId, artifacts) in config.ignoredCoordinates) {
val redundant = groupId in config.ignoredGroupIds
for (artifactId in artifacts.keys) {
Expand All @@ -63,6 +65,7 @@ internal fun loadDependencyCoordinates(
config,
unusedGroupIds,
unusedCoordinates,
unusedRegexes,
coordinates,
mutableSetOf(),
depth = 1,
Expand All @@ -74,6 +77,9 @@ internal fun loadDependencyCoordinates(
for ((groupId, artifactId) in unusedCoordinates) {
warnings += "Dependency ignore for $groupId:$artifactId is unused"
}
for (unusedRegex in unusedRegexes) {
warnings += "Dependency ignore for regex '$unusedRegex' is unused"
}

return DependencyResolutionResult(coordinates, warnings)
}
Expand All @@ -92,6 +98,7 @@ private fun loadDependencyCoordinates(
config: DependencyConfig,
unusedGroupIds: MutableSet<String>,
unusedCoordinates: MutableSet<Pair<String, String>>,
unusedRegexes: MutableSet<Regex>,
destination: MutableSet<DependencyCoordinates>,
seen: MutableSet<ComponentIdentifier>,
depth: Int,
Expand All @@ -116,12 +123,17 @@ private fun loadDependencyCoordinates(
// Assuming flat-dir repository dependency, do nothing.
ignoreSuffix = " ignoring because flat-dir repository artifact has no metadata"
} else {
val moduleCoordinate = "${id.group}:${id.module}"
val ignoredData =
null
?: config.ignoredGroupIds[id.group]?.also { unusedGroupIds -= id.group }
?: config.ignoredCoordinates[id.group]?.get(id.module)?.also {
unusedCoordinates -= id.group to id.module
}
?: config.ignoredRegexes.entries
.find { (regex, _) -> regex.matches(moduleCoordinate) }
?.also { (regex, _) -> unusedRegexes -= regex }
?.value
if (ignoredData != null) {
ignoreSuffix = buildString {
append(" ignoring")
Expand Down Expand Up @@ -166,6 +178,7 @@ private fun loadDependencyCoordinates(
config,
unusedGroupIds,
unusedCoordinates,
unusedRegexes,
destination,
seen,
depth + 1,
Expand Down
83 changes: 71 additions & 12 deletions src/main/kotlin/app/cash/licensee/pluginExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,45 @@ interface LicenseeExtension {
allowDependency(dependencyProvider = dependencyProvider, options = {})
}

/**
* ```groovy
* licensee {
* ignoreDependenciesByRegex('com.mycompany.internal')
* ignoreDependenciesByRegex('com.mycompany.utils', 'utils')
* }
* ```
*
* A reason string can be supplied to document why the dependencies are being ignored.
*
* ```groovy
* licensee {
* ignoreDependenciesByRegex('com.example.sdk', 'sdk') {
* because "commercial SDK"
* }
* }
* ```
*
* An ignore can be marked as transitive which will ignore an entire branch of the dependency
* tree. This will ignore the target artifact's dependencies regardless of the artifact
* coordinates or license info. Since it is especially dangerous, a reason string is required.
*
* ```groovy
* licensee {
* ignoreDependenciesByRegex('com.other.sdk', 'sdk') {
* transitive = true
* because "commercial SDK"
* }
* }
* ```
*
* @see ignoreDependencies
*/
fun ignoreDependenciesByRegex(regex: String, options: Action<IgnoreDependencyOptions>)

fun ignoreDependenciesByRegex(regex: String) {
ignoreDependenciesByRegex(regex = regex, options = {})
}

/**
* Ignore a single dependency or group of dependencies during dependency graph resolution.
* Artifacts targeted with this method will not be analyzed for license information and will not
Expand Down Expand Up @@ -166,6 +205,8 @@ interface LicenseeExtension {
* }
* }
* ```
*
* @see ignoreDependenciesByRegex
*/
fun ignoreDependencies(
groupId: String,
Expand Down Expand Up @@ -262,6 +303,7 @@ internal abstract class MutableLicenseeExtension : LicenseeExtension {
internal abstract val allowedUrls: MapProperty<String, Optional<String>>
internal abstract val allowedDependencies: MapProperty<DependencyCoordinates, Optional<String>>
internal abstract val ignoredGroupIds: MapProperty<String, IgnoredData>
internal abstract val ignoredRegexes: MapProperty<String, IgnoredData>
internal abstract val ignoredCoordinates: NamedDomainObjectContainer<IgnoredCoordinate>
internal abstract val violationAction: Property<ViolationAction>
internal abstract val unusedAction: Property<UnusedAction>
Expand All @@ -274,12 +316,13 @@ internal abstract class MutableLicenseeExtension : LicenseeExtension {
}

fun toDependencyTreeConfig(): Provider<DependencyConfig> {
return ignoredGroupIds.map { ignoredGroupIds ->
return ignoredGroupIds.zip(ignoredRegexes) { ignoredGroupIds, ignoredRegexes ->
DependencyConfig(
ignoredGroupIds.toMap(),
ignoredCoordinates
.groupBy({ it.name }) { it.ignoredDatas.get() }
.mapValues { it.value.single() },
ignoredRegexes.mapKeys { (regex, _) -> regex.toRegex() },
)
}
}
Expand Down Expand Up @@ -367,17 +410,7 @@ internal abstract class MutableLicenseeExtension : LicenseeExtension {
artifactId: String?,
options: Action<IgnoreDependencyOptions>,
) {
val option =
object : IgnoreDependencyOptions {
var setReason: String? = null

override fun because(reason: String) {
setReason = reason
}

override var transitive: Boolean = false
}

val option = DefaultIgnoreDependencyOptions()
options.execute(option)
if (option.transitive && option.setReason == null) {
throw RuntimeException(
Expand All @@ -400,6 +433,22 @@ internal abstract class MutableLicenseeExtension : LicenseeExtension {
}
}

override fun ignoreDependenciesByRegex(regex: String, options: Action<IgnoreDependencyOptions>) {
val option = DefaultIgnoreDependencyOptions()
options.execute(option)
if (option.transitive && option.setReason == null) {
throw RuntimeException(
buildString {
append("Transitive dependency ignore on regex '")
append(regex)
append("' is dangerous and requires a reason string")
}
)
}
val ignoredData = IgnoredData(option.setReason, option.transitive)
ignoredRegexes.put(regex, ignoredData)
}

override fun violationAction(level: ViolationAction) {
violationAction.set(level)
}
Expand All @@ -409,6 +458,16 @@ internal abstract class MutableLicenseeExtension : LicenseeExtension {
}
}

private class DefaultIgnoreDependencyOptions : IgnoreDependencyOptions {
var setReason: String? = null

override fun because(reason: String) {
setReason = reason
}

override var transitive: Boolean = false
}

private fun <T : Any, L : Any, R : Any, V : Any> Provider<T>.zip2(
left: Provider<L>,
right: Provider<R>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
id("java-library")
alias(libs.plugins.licensee)
}

dependencies {
implementation 'com.example:example-a:1.0.0'
}

licensee {
allow('Apache-2.0')
ignoreDependenciesByRegex('com\\.example:.*') {
transitive = true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-a</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-b</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.other</groupId>
<artifactId>other</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-b</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</project>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.other</groupId>
<artifactId>other</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pluginManagement {
includeBuild("../../test-build-logic")
}

plugins {
id("licenseeTests")
}
13 changes: 13 additions & 0 deletions src/test/fixtures/ignore-group-regex/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id("java-library")
alias(libs.plugins.licensee)
}

dependencies {
implementation 'com.example:example-a:1.0.0'
}

licensee {
allow('Apache-2.0')
ignoreDependenciesByRegex('com\\.example:.*')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"groupId": "com.other",
"artifactId": "other",
"version": "1.0.0",
"spdxLicenses": [
{
"identifier": "Apache-2.0",
"name": "Apache License 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
}
]
}
]
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-a</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-b</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.other</groupId>
<artifactId>other</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-b</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</project>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.other</groupId>
<artifactId>other</artifactId>
<version>1.0.0</version>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</project>
7 changes: 7 additions & 0 deletions src/test/fixtures/ignore-group-regex/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pluginManagement {
includeBuild("../../test-build-logic")
}

plugins {
id("licenseeTests")
}
Loading