is there an easy way to gather all the source jars...
# plugin-development
c
is there an easy way to gather all the source jars for a
Configuration
? I really hoped something like
Copy code
def sourceJars = configurations.register('sourceJars') {
    extendsFrom otherConfiguration
    attributes {
        // but with the correct attribute
        attribute LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, 'source-jar'
    }
}
would just get me what I needed, rather than having to go through every jar then request the source jar using
dependencies.createArtifactResolutionQuery()
for each main jar I find?
looking at the output of the
outgoingVariants
for my project does not give me hope this exists…
so apparently this works, which is exactly what I wanted:
Copy code
def sourceJarsBundle = configurations.register('sourceJarsBundle') {
    extendsFrom configurations.mavenBundle
    attributes {
        attribute Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.DOCUMENTATION)
        attribute DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, DocsType.SOURCES)
    }
}
t
yeah thats the best way I've been able to cook up. then its just up to people to publish the sources jar with their library. I had to do that to get code coverage across sub projects. jacoco needs everything as an input if you want to measure it
c
ugh, this doesn’t seem to work for source jars in other projects
just ones that have been published
it finds some other random variant
t
here is what I did for kotlin("jvm") sub projects.
Copy code
val unzipSources = target.tasks.register<UnzipperTask>("unzipTestCoverageSources") {
            outDir.set(temporaryDir)
            zips.from(
                runtime.incoming.artifactView {
                    componentFilter { id -> id is ProjectComponentIdentifier }
                    withVariantReselection()
                    attributes {
                        attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                        attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
                        attribute(DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
                        attribute(CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
                        attribute(Attribute.of("org.gradle.jvm.environment", String::class.java), "standard-jvm")
                        attribute(Attribute.of("org.gradle.dependency.bundling", String::class.java), "external")
                    }
                    lenient(true)
                }.files
            )
        }

        val jvmClasses = runtime.incoming.artifactView {
            componentFilter { id -> id is ProjectComponentIdentifier }
            attributes {
                attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                attribute(CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
                attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
            }
            lenient(true)
        }

        val unzipClasses = target.tasks.register<UnzipperTask>("unzipTestCoverageClasses") {
            outDir.set(temporaryDir)
            zips.from(jvmClasses.files)
        }
then for android sub projects
Copy code
val unzipSourcesTaskName = "unzip${variant.name.capitalized()}CoverageSources"
        val unzipSources = runCatching { target.tasks.named<UnzipperTask>(unzipSourcesTaskName) }
            .getOrElse {
                target.tasks.register<UnzipperTask>("unzip${variant.name.capitalized()}CoverageSources") {
                    outDir.set(temporaryDir)
                    zips.from(
                        variant.runtimeConfiguration.incoming.artifactView {
                            componentFilter { id -> id is ProjectComponentIdentifier }
                            withVariantReselection()
                            attributes {
                                attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                                attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
                                attribute(DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
                                attribute(CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
                                variant.buildType?.let { buildType ->
                                    attribute(BuildTypeAttr.ATTRIBUTE, objects.named(buildType))
                                }
                                variant.productFlavors.forEach { (dimen, flavor) ->
                                    attribute(ProductFlavorAttr.of(dimen), objects.named(flavor))
                                }
                            }
                            lenient(true)
                        }.files
                    )
                    zips.from(
                        variant.runtimeConfiguration.incoming.artifactView {
                            componentFilter { id -> id is ProjectComponentIdentifier }
                            withVariantReselection()
                            attributes {
                                attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                                attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
                                attribute(DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
                                attribute(CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
                                attribute(Attribute.of("org.gradle.jvm.environment", String::class.java), "standard-jvm")
                                attribute(Attribute.of("org.gradle.dependency.bundling", String::class.java), "external")
                            }
                            lenient(true)
                        }.files
                    )
                }
            }

        val classesConfigName = "projectClasses${variant.name.capitalized()}"
        val classesConfig =
            runCatching {
                target.configurations.named(classesConfigName)
            }.getOrElse {
                target.configurations.resolvable(classesConfigName) {
                    extendsFrom(variant.runtimeConfiguration)
                    attributes {
                        attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                        attribute(CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
                        variant.buildType?.let { buildType ->
                            attribute(BuildTypeAttr.ATTRIBUTE, objects.named(buildType))
                        }
                        variant.productFlavors.forEach { (dimen, flavor) ->
                            attribute(ProductFlavorAttr.of(dimen), objects.named(flavor))
                        }
                    }
                }
            }

        val androidClasses = classesConfig.get().incoming.artifactView {
            componentFilter { id -> id is ProjectComponentIdentifier }
            attributes {
                attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
            }
            lenient(true)
        }
        val jvmClasses = classesConfig.get().incoming.artifactView {
            componentFilter { id -> id is ProjectComponentIdentifier }
            attributes {
                attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
                attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
            }
            lenient(true)
        }

        val unzipClassesName = "unzip${variant.name.capitalized()}CoverageClasses"
        val unzipClasses = runCatching {
            target.tasks.named<UnzipperTask>(unzipClassesName)
        }.getOrElse {
            target.tasks.register<UnzipperTask>(unzipClassesName) {
                outDir.set(temporaryDir)
                zips.from(androidClasses.files)
                zips.from(jvmClasses.files)
            }
        }
then in my publishing plugin I had to add a bit to the sources outgoing.
Copy code
val sources = target.tasks.register<Jar>("sourcesJar") {
                archiveClassifier.set("sources")
                from(
                    kotlinJvmExt
                        .sourceSets
                        .named<KotlinSourceSet>("main")
                        .get()
                        .kotlin
                ) {
                    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
                    into("main")
                }
                target.pluginManager.withPlugin("org.gradle.java-test-fixtures") {
                    from(
                        kotlinJvmExt
                            .sourceSets
                            .named<KotlinSourceSet>("testFixtures")
                            .get()
                            .kotlin
                    ) {
                        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
                        into("testFixtures")
                    }
                }
            }

            val objects = target.objects
            target.configurations.consumable("sourceElements") {
                isVisible = false
                attributes {
                    attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
                    attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
                    attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
                    attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
                    attribute(Attribute.of("org.gradle.jvm.environment", String::class.java), "standard-jvm")
                    attribute(Attribute.of("org.gradle.dependency.bundling", String::class.java), "external")
                }
                outgoing.artifact(sources) {
                    type = ArtifactTypeDefinition.JAR_TYPE
                }
            }
v
Using the source doc variant works fine, but only if auch a variant is published using Gradle Module Metadata. If there simply is a jar with source qualifier published, this is not enough. So if a project is published using Maven or any other way without GMM, it will not work. And even if a project is published using Gradle with GMM, but does not use
withSourcesJar()
which would configure that variant, but it's a manual
artifact(sourcesJar)
call on the publication, it will also not work. For those (both cases) you would then have to use a component metadata rule to add the missing sources variant, then it should probably work for all.
t
Yup exactly what happened with my setup. the AGP withSourcesJar worked fine then I had to add the missing bits for my
kotlin("jvm")
modules
luckily I was able to get most of our org using my publishing plugin so all our android teams pickup the behavior for free
c
ok, this has all been really useful info, thanks! I think I get how this would work: 1. For external deps, the component metadata rule allows me to add a new variant with my own attribute, which I can add the
sources
jar file to 2. For local project deps, I can apply a plugin on every project that will make a new variant/Configuration with an artifact being the output of the
sourcesJar
task with the same attribute 3. Resolve using an artifact view on the relevant Configuration giving my attribute
I was being dumb, this does work! My test was just bad. I’m trying with a component metadata rule, but while it does successfully resolve the right variant, there doesn’t seem to be any files:
Copy code
public static final class SourceJarForMavenBundling implements ComponentMetadataRule {
        @Override
        public void execute(ComponentMetadataContext context) {
            context.getDetails().addVariant("sourceJarForMavenBundling", variantMetadata -> {
                variantMetadata.getAttributes().attribute(MAVEN_BUNDLE_SOURCE_JAR, true);
                variantMetadata.withFiles(
                        files -> files.addFile(context.getDetails().getId().getName() + "-"
                                + context.getDetails().getId().getVersion() + "-sources.jar"));
            });
        }
    }
Copy code
project.getDependencies().getComponents().all(SourceJarForMavenBundling.class);
Copy code
mavenBundleConfiguration
    .getIncoming()
    .artifactView(av -> {
        av.attributes(attrs -> {
            attrs.attribute(MAVEN_BUNDLE_SOURCE_JAR, true);
        });
    })
    .getFiles()
    .getFiles(); // no files
Verified the sources jar exists: https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.12.0/ Looking at a build scan, the variant is selected - there are just no files:
any idea what I’m doing wrong?
t
definitely took me a ton of trial and error to figure stuff out
v
I don't think you should use an own attribute. Just use
withSourcesJar
for your projects so that for your projects the variant is properly set up, and in the component metadata rule add a variant that is set up like the one added by
withSourcesJar
. Then you can use those standard attributes to resolve them.
c
hmm, doesn’t seem to be working for me for local projects (and we use
withSourcesJar
). Likely we’re doing something odd though
fwiw I don’t see the sourcesElements(?) variant when running
./gradlew :project:outgoingVariants
v
Hm, you should. Hard to tell without an MCVE.