Suggestion: Use Gradle dependency attributes to mark libraries that Reflekt will introspect
Opened this issue · 0 comments
Hi, I saw this note in the source code about marking which dependencies should be introspected.
I think a better way would be to use Gradle variant attributes.
The end result would, to users, look something like this:
implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.6.10") {
reflekt(introspect = true)
}
And in the plugin, it would require less config, and leverage Gradle task-avoidance more (faster, less unncessary work).
The steps would be
- Create a custom Attribute https://docs.gradle.org/current/userguide/variant_attributes.html#sec:declaring_attributes
- Create a helper extension function, to apply the attribute to dependencies
- Create a Configuration for gathering the dependencies https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing
- Update
ReflektSubPlugin.kt
to resolve the location of the libraries using the Configuration
Create a custom attribute
// ReflektSubPlugin.kt
companion object {
val REFLEKT_INTROSPECTION_ATTRIBUTE = Attribute.of("org.jetbrains.reflekt.introspect", Boolean::class.javaObjectType)
}
Manually this attribute could be applied
// build.gradle.kts
dependencies {
implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.6.10") {
attributes {
attributes.attribute(Attribute.of("org.jetbrains.reflekt.introspect", Boolean::class.javaObjectType), true)
}
}
}
Create 'apply introspect Attribute' extension
But that's a little clunky. I think an extension function, in reflekt-gradle-plugin, like this would work
// ReflektGradleExtension.kt
/** Designate this dependency as one Reflekt will analyse. */
fun ModuleDependency.reflekt(introspect: Boolean = true) {
attributes.attribute(REFLEKT_INTROSPECTION_ATTRIBUTE, introspect)
}
// build.gradle.kts
dependencies {
implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.6.10") {
reflekt(introspect = true)
}
}
Create a Configuration
Now Reflekt can create its own bucket of dependencies, called reflektIntrospect
.
// RefelktSubPlugin.kt
companion object {
const val REFLEKT_INTROSPECT_CONFIGURATION_NAME = "reflektIntrospect"
val REFLEKT_INTROSPECTION_ATTRIBUTE = Attribute.of("org.jetbrains.reflekt.introspect", Boolean::class.javaObjectType)
}
private fun Project.createReflektIntrospectConfiguration(): Configuration {
return configurations.create(REFLEKT_INTROSPECT_CONFIGURATION_NAME).apply {
description = "These dependencies will be introspected by Reflekt. "
// mark this as an 'inbound' collector. It is not a provider of dependencies.
isCanBeResolved = true
isCanBeConsumed = false
// filter to only include dependencies that are explicitly marked
attributes.attribute(REFLEKT_INTROSPECTION_ATTRIBUTE, true)
isTransitive = false
// extending from compileClasspath means this 'reflekt' configuration will also receive
// any dependencies that 'compileClasspath' receives.
// https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations
extendsFrom(configurations[JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME])
}
}
This configuration will be available for customisation in any project where the Reflekt Gradle Plugin is applied, so users can customise it. (For example, including testCompileClasspath
, or files from a local directory.)
Create the Configuration in ReflektSubPlugin
I think that this new configuration should be applied ASAP, and creating it in override fun applyToCompilation(...)
might be too late? But I'm not sure.
// ReflektSubPlugin.kt
override fun apply(target: Project) {
target.createReflektIntrospectConfiguration()
}
Resolve the Configuration in ReflektSubPlugin
And then in applyToCompilation
the configuration can be resolved. This will trigger Gradle tasks - so it's best to either use the correct Gradle methods that will return and map providers...
// get a provider for the config
val introspectConfigurationProvider: NamedDomainObjectProvider<Configuration> =
project.configurations.named(REFLEKT_INTROSPECT_CONFIGURATION_NAME)
val librariesToIntrospect: Provider<List<SubpluginOption>> =
introspectConfigurationProvider
.map { introspectLibs: Configuration ->
introspectLibs
.incoming
.artifactView { it.lenient(true) }
.files
.map { lib: File ->
SubpluginOption(key = LIBRARY_TO_INTROSPECT.name, value = lib.canonicalPath)
}
}
// (note: Provider.map is different to List<>.map)
Or do all the work inside a provider
return project.provider {
val introspectConfiguration: Configuration = project.configurations[REFLEKT_INTROSPECT_CONFIGURATION_NAME]
val librariesToIntrospect = introspectConfiguration.incoming
.artifactView { it.lenient(true) }
.files
.map { lib: File ->
SubpluginOption(key = LIBRARY_TO_INTROSPECT.name, value = lib.canonicalPath)
}
librariesToIntrospect + reflektMetaFilesOptions + dependencyJars +
SubpluginOption(key = ENABLED_OPTION_INFO.name, value = extension.enabled.toString()) +
SubpluginOption(key = OUTPUT_DIR_OPTION_INFO.name, value = generationPath) +
SubpluginOption(key = SAVE_METADATA_OPTION_INFO.name, value = extension.toSaveMetadata.toString()) +
SubpluginOption(key = REFLEKT_META_FILE_PATH.name, value = createReflektMeta(project.getResourcesPath()).absolutePath)
}