Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ jobs:
- name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

- name: Run Gradle Plugin Tests
run: |
cd plugin-build
./gradlew test

- name: Publish Test Report
uses: mikepenz/action-junit-report@v6
if: success() || failure() # always run even if the previous step fails
with:
report_paths: 'plugin-build/plugin/build/test-results/test/TEST-*.xml'

- name: Publish plugin locally
run: |
cd plugin-build
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ docs
/vendor/*
.kotlin/

iosApp/iosApp.xcodeproj
iosApp/iosApp.xcodeproj
/gradle-user-home/
/tmp/
13 changes: 13 additions & 0 deletions plugin-build/plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ dependencies {

// lint rules
lintChecks(baseLibs.android.lint.gradle)

// test dependencies
testImplementation(gradleTestKit())
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.named<Test>("test") {
useJUnitPlatform()
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
})
}

mavenPublishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ private fun configureAndroidTasks(project: Project, extension: AboutLibrariesExt
it.group = ""
it.variant.set(variant.name)
it.outputDirectory.set(resultsResDirectory)
it.configureOutputFile(resultsDirectory.map { dir ->
@Suppress("DEPRECATION")
dir.file(extension.export.outputFileName.get())
it.configureOutputFile(resultsDirectory.flatMap { dir ->
extension.export.outputFileName.map { filename ->
@Suppress("DEPRECATION")
dir.file(filename)
}
})
it.configure()
}
Expand All @@ -52,9 +54,11 @@ private fun configureAndroidTasks(project: Project, extension: AboutLibrariesExt
// task to generate libraries, and their license into the build folder (not hooked to the build task)
project.tasks.configure("exportLibraryDefinitions${variantName}", AboutLibrariesTask::class.java) {
it.variant.set(variant.name)
it.configureOutputFile(resultsDirectory.map { dir ->
@Suppress("DEPRECATION")
dir.file(extension.export.outputFileName.get())
it.configureOutputFile(resultsDirectory.flatMap { dir ->
extension.export.outputFileName.map { filename ->
@Suppress("DEPRECATION")
dir.file(filename)
}
})
it.configure()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,21 @@ abstract class AboutLibrariesTask : BaseAboutLibrariesTask() {
} else {
val projectDirectory = project.layout.projectDirectory
val buildDirectory = project.layout.buildDirectory

// Capture extension values during configuration time to avoid accessing
// extension (which references project) during Provider evaluation
val exports = extension.exports
val export = extension.export

@Suppress("DEPRECATION")
val fileNameProvider = project.provider {
val config = extension.exports.findByName(variant.getOrElse(""))
config?.outputFileName?.orNull ?: extension.export.outputFileName.get()
val config = exports.findByName(variant.getOrElse(""))
config?.outputFileName?.orNull ?: export.outputFileName.get()
}

val outputFileProvider = project.provider {
val config = extension.exports.findByName(variant.getOrElse(""))
config?.outputFile?.orNull ?: extension.export.outputFile.orNull
val config = exports.findByName(variant.getOrElse(""))
config?.outputFile?.orNull ?: export.outputFile.orNull
}

val providers = project.providers
Expand All @@ -61,10 +66,10 @@ abstract class AboutLibrariesTask : BaseAboutLibrariesTask() {
this.outputFile.set(
providers.gradleProperty("${PROP_PREFIX}${PROP_EXPORT_OUTPUT_FILE}").map { path -> projectDirectory.file(path) }.orElse(
providers.gradleProperty("${PROP_PREFIX}${PROP_EXPORT_OUTPUT_PATH}").map { path -> projectDirectory.file(path) }.orElse(
providers.gradleProperty("${PROP_PREFIX}${PROP_EXPORT_PATH}").map { path -> projectDirectory.dir(path).file(fileNameProvider.get()) }.orElse(
providers.gradleProperty(PROP_EXPORT_PATH).map { path -> projectDirectory.dir(path).file(fileNameProvider.get()) }).orElse(
providers.gradleProperty("${PROP_PREFIX}${PROP_EXPORT_PATH}").flatMap { path -> fileNameProvider.map { filename -> projectDirectory.dir(path).file(filename) } }.orElse(
providers.gradleProperty(PROP_EXPORT_PATH).flatMap { path -> fileNameProvider.map { filename -> projectDirectory.dir(path).file(filename) } }).orElse(
outputFileProvider.orElse(
buildDirectory.dir("generated/aboutLibraries/").map { it.file(fileNameProvider.get()) }
buildDirectory.dir("generated/aboutLibraries/").flatMap { dir -> fileNameProvider.map { filename -> dir.file(filename) } }
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import com.mikepenz.aboutlibraries.plugin.util.LibraryPostProcessor
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.slf4j.LoggerFactory

abstract class BaseAboutLibrariesTask : DefaultTask() {
Expand Down Expand Up @@ -91,6 +97,9 @@ abstract class BaseAboutLibrariesTask : DefaultTask() {
@get:Internal
internal abstract val variantToDependencyData: MapProperty<String, List<DependencyData>>

@get:Internal
internal abstract val configurationNames: ListProperty<String>

open fun configure() {
excludeFields.set(project.provider {
val config = extension.exports.findByName(variant.getOrElse(""))
Expand Down Expand Up @@ -123,7 +132,9 @@ abstract class BaseAboutLibrariesTask : DefaultTask() {

val filter = filterVariants.get() + (variant.orNull?.let { arrayOf(it) } ?: emptyArray())

val dependencies = project.configurations.filterNot { config ->
// PERFORMANCE: Store only configuration names during configuration time
// Actual resolution happens during task execution when variantToDependencyData is accessed
val selectedConfigNames = project.configurations.filterNot { config ->
config.shouldSkip(includeTestVariants.get())
}.filter { config ->
val cn = config.name
Expand Down Expand Up @@ -160,14 +171,25 @@ abstract class BaseAboutLibrariesTask : DefaultTask() {
false
}
}
}.associate { config ->
config.name to DependencyCollector(includePlatform.get())
.loadDependenciesFromConfiguration(project, config.incoming.resolutionResult.rootComponent)
}
}.map { it.name }

configurationNames.set(selectedConfigNames)

// PERFORMANCE: Wrap resolution in a provider that's only evaluated during task execution
variantToDependencyData.set(project.providers.provider {
if (LOGGER.isDebugEnabled) LOGGER.debug("==> ABOUTLIBRARIES: Provider evaluated - dependency resolution starting (EXECUTION TIME)")
val target = mutableMapOf<String, List<DependencyData>>()
dependencies.onEach { (name, result) -> target[name] = result.get() }
for (configName in configurationNames.get()) {
val config = project.configurations.getByName(configName)
// Resolution happens HERE during task execution, not during configuration
val dependencyData = DependencyCollector(includePlatform.get())
.loadDependenciesFromConfiguration(
project,
config.incoming.resolutionResult.rootComponent
).get()
target[configName] = dependencyData
}
if (LOGGER.isDebugEnabled) LOGGER.debug("==> ABOUTLIBRARIES: Provider evaluation complete")
target
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package com.mikepenz.aboutlibraries.plugin.util

import com.mikepenz.aboutlibraries.plugin.mapping.*
import com.mikepenz.aboutlibraries.plugin.mapping.Developer
import com.mikepenz.aboutlibraries.plugin.mapping.Funding
import com.mikepenz.aboutlibraries.plugin.mapping.License
import com.mikepenz.aboutlibraries.plugin.mapping.Organization
import com.mikepenz.aboutlibraries.plugin.mapping.Scm
import org.apache.maven.model.Dependency
import org.apache.maven.model.Model
import org.apache.maven.model.Parent
import org.apache.maven.model.Repository
import org.apache.maven.model.building.*
import org.apache.maven.model.building.DefaultModelBuilderFactory
import org.apache.maven.model.building.DefaultModelBuildingRequest
import org.apache.maven.model.building.FileModelSource
import org.apache.maven.model.building.ModelBuildingRequest
import org.apache.maven.model.building.ModelSource2
import org.apache.maven.model.resolution.ModelResolver
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
Expand All @@ -18,7 +26,9 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.artifacts.result.ResolvedVariantResult
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Category.*
import org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE
import org.gradle.api.attributes.Category.ENFORCED_PLATFORM
import org.gradle.api.attributes.Category.REGULAR_PLATFORM
import org.gradle.api.provider.Provider
import org.slf4j.LoggerFactory
import java.io.File
Expand Down Expand Up @@ -228,6 +238,9 @@ internal class DependencyCollector(

/**
* Fetches the pom files for all [ResolvedVariantResult]s.
*
* PERFORMANCE: Uses ArtifactView API instead of deprecated lenientConfiguration to avoid
* triggering configuration resolution during Gradle configuration phase.
*
* Original Code is based on: https://github.com/cashapp/licensee/blob/1.13.0/src/main/kotlin/app/cash/licensee/task.kt#L152
* Copyright (C) 2021 Square, Inc.
Expand All @@ -237,10 +250,17 @@ internal class DependencyCollector(
dependencies: DependencyHandler,
configurations: ConfigurationContainer,
): List<DependencyCoordinatesWithPomFile> {
fun Configuration.artifacts() = resolvedConfiguration.lenientConfiguration.allModuleDependencies.flatMap { it.allModuleArtifacts }
if (LOGGER.isDebugEnabled) LOGGER.debug("==> ABOUTLIBRARIES: fetchPomFiles called - resolving ${this.size} dependencies")
// Use ArtifactView API which is configuration-cache compatible and lazy
fun Configuration.artifactsViaView() = incoming.artifactView { config ->
config.lenient(true)
}.artifacts.map { it.file to it.id.componentIdentifier }
Comment on lines +254 to +257
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The call to artifactsViaView() on line 273 triggers artifact resolution immediately, which happens during the provider evaluation. While this is better than configuration-time resolution, the function name and comment suggest lazy resolution, but incoming.artifactView().artifacts is not completely lazy - it resolves when accessed.

The artifacts property returns a ArtifactCollection that resolves when iterated. This means the resolution still occurs, just at a different time (during task execution when the provider is evaluated, rather than during configuration). The improvement is real but the implementation could be clearer about when resolution actually happens.

Copilot uses AI. Check for mistakes.

val pomDependencies = map { dependencies.create(it.pomCoordinate()) }.toTypedArray()

val withVariants = configurations.detachedConfiguration(*pomDependencies).apply {
isCanBeConsumed = false
isCanBeResolved = true
for (variant in variants) {
attributes {
val variantAttrs = variant.attributes
Expand All @@ -250,13 +270,19 @@ internal class DependencyCollector(
}
}
}
}.artifacts()

val withoutVariants = configurations.detachedConfiguration(*pomDependencies).artifacts()
return (withVariants + withoutVariants).map {
// Cast is safe because all resolved artifacts are pom files.
val coordinates = (it.id.componentIdentifier as ModuleComponentIdentifier).toDependencyCoordinates()
DependencyCoordinatesWithPomFile(coordinates, it.file)
}.artifactsViaView()

val withoutVariants = configurations.detachedConfiguration(*pomDependencies).apply {
isCanBeConsumed = false
isCanBeResolved = true
}.artifactsViaView()

return (withVariants + withoutVariants).mapNotNull { (file, componentId) ->
// Only process module components (not project components)
if (componentId is ModuleComponentIdentifier) {
val coordinates = componentId.toDependencyCoordinates()
DependencyCoordinatesWithPomFile(coordinates, file)
} else null
}.distinctBy { it.dependencyCoordinates }
}

Expand Down
Loading
Loading