Skip to content

Suggestion: Use Gradle dependency attributes to mark libraries that Reflekt will introspect #104

@aSemy

Description

@aSemy

Hi, I saw this note in the source code about marking which dependencies should be introspected.

// TODO: check if it's okay to add files based on libraries' names in their paths

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

  1. Create a custom Attribute https://docs.gradle.org/current/userguide/variant_attributes.html#sec:declaring_attributes
  2. Create a helper extension function, to apply the attribute to dependencies
  3. Create a Configuration for gathering the dependencies https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing
  4. 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)
        }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions