Adding new dependencies to a pom when making a far jar

I need to publish an additional artefact that contains the contents of the current project and the contents of 1 or more dependencies. When I do this, I also want to publish it with a POM that accurately describes the published artefact.

I have this working (the fat jar) fine but the POM is currently incomplete and I’m considering the best way to do this. For example, given a simple dependency tree (and assume ‘->’ refers to a dependency)

me -> you -> them

I want to publish an artefact such that

us = {me,you}
us -> them

In order to do this, I need to add a dependency (on them) to my published POM.

My 1st pass was a bit crufty but in pseudocode;

work out which dependencies I have bundled into the artefact
for each one
  find the ResolvedDependency
  find the matching pom Dependency and remove it
  for each child of the ResolvedDependency that is not already in the pom, create a Maven Dependency and add it to pom

This causes gradle to blow up with a NoClassDefFound error when I attempt to instantiate a ‘org.apache.maven.model.Dependency’. I don’t understand this at all given that this class must be present because it is what is in the (effective) pom.

It would be interesting to know what is going on there but it made me think that perhaps an alternative approach would be better, namely

create a configuration
for each filtered out ResolvedDependency
   locate the underlying Dependency
   add it to my new configuration
use the existing PomDependenciesConverter code to marshal these dependencies into the pom

Any advice on the best way to proceed is welcome.

I implemented the latter approach successfully albeit in a somewhat ugly fashion.

I then came up with a more concise approach that intends to do;

create new configurations filteredOut and bundledIn
add conf2scope mappings to map these to some appropriately named (custom) maven scopes with a higher priority than anything else
in a task that runs at the appropriate time
    add the missing transitive dependencies to the bundledIn configuration
    add the filtered out dependencies to the filtered out configuration
when the pom is generated
    munge the custom scoped dependencies so they are either removed or moved back to a standard maven scope

This would work except for the fact that the conf2scope mappings are copied from ‘project.convention.plugins.maven.conf2ScopeMappings’ to the PomFilterContainer which means I need to insert those mappings before the PomFilterContainer is created. This in turn means, I think, I need to defer configuration of the maven publication repositories until after I have set this up (which means moving this to a task).

I suppose the Q is … I’m using an assortment of internals here which is obviously a bit brittle but some internals are more stable than others, does this look like something that is on the drawing board for substantial change soon?

In case anyone has a similar need, the task code mentioned above is listed below. bundle is an extension added to the project that lets you supply a list of regexes to match group/name against (as seen in the ‘bundle.includes.any {}’ line).

@TaskAction void populate() {
        final srcConfig = project.configurations.getByName(bundle.configuration)
        srcConfig.allDependencies.findAll { dep ->
            bundle.includes.any { dep.group =~ it[0] && dep.name =~ it[1] }
        }.each { dep ->
            project.dependencies.add('filteredOut', "${dep.group}:${dep.name}:${dep.version}")
            unrollDependency(srcConfig.resolvedConfiguration.firstLevelModuleDependencies.find {
                it.moduleGroup == dep.group && it.moduleName == dep.name
            })
        }
        // now set up the scope mappings
        Conf2ScopeMappingContainer conf2Scopes = project.convention.plugins.maven.conf2ScopeMappings
        conf2Scopes.addMapping(MAX_VALUE, project.configurations.filteredOut, 'filteredOut')
        conf2Scopes.addMapping(MAX_VALUE, project.configurations.bundledIn, "bundled-${getTargetScope(conf2Scopes, srcConfig)}")
    }
      // TODO this needs to be the scope that corresponds to the root of dependency that introduced the transitive dep not just a blanket single scope
    // (because default -> runtime -> compile so deps really come from runtime and compile not default)
    private String getTargetScope(conf2Scopes, srcConfig) {
        final targetScope = conf2Scopes.getMapping(srcConfig.hierarchy)?.scope
        if (targetScope == null) {
            throw new InvalidEspritProjectException("Unable to find maven scope for configuration $bundle.configuration")
        }
        targetScope
    }
      /**
     * Assess the given dependency against the filter, if it matches recursively call this function with the children
     * until you hit a dependency that doesn't match the filter. At this point, add that dependency to the bundledIn
     * configuration.
     * @param rd the resolved dependency.
     * @param filters the filters.
     * @return the dependencies we need to reinstate to produce a valid pom.
     */
    protected void unrollDependency(ResolvedDependency rd) {
        if (rd != null) {
            if (bundle.includes.any { rd.moduleGroup =~ it[0] && rd.moduleName =~ it[1]} ) {
                rd.children.each{ unrollDependency(it) }
            } else {
                project.dependencies.add('bundledIn', "${rd.moduleGroup}:${rd.moduleName}:${rd.moduleVersion}")
            }
        }
    }

I implemented the latter approach successfully albeit in a somewhat ugly fashion.

I then came up with a more concise approach that intends to do;

create new configurations filteredOut and bundledIn
add conf2scope mappings to map these to some appropriately named (custom) maven scopes with a higher priority than anything else
in a task that runs at the appropriate time
    add the missing transitive dependencies to the bundledIn configuration
    add the filtered out dependencies to the filtered out configuration
when the pom is generated
    munge the custom scoped dependencies so they are either removed or moved back to a standard maven scope

This would work except for the fact that the conf2scope mappings are copied from ‘project.convention.plugins.maven.conf2ScopeMappings’ to the PomFilterContainer which means I need to insert those mappings before the PomFilterContainer is created. This in turn means, I think, I need to defer configuration of the maven publication repositories until after I have set this up (which means moving this to a task).

I suppose the Q is … I’m using an assortment of internals here which is obviously a bit brittle but some internals are more stable than others, does this look like something that is on the drawing board for substantial change soon?

In case anyone has a similar need, the task code mentioned above is listed below. bundle is an extension added to the project that lets you supply a list of regexes to match group/name against (as seen in the ‘bundle.includes.any {}’ line).

@TaskAction void populate() {
        final srcConfig = project.configurations.getByName(bundle.configuration)
        srcConfig.allDependencies.findAll { dep ->
            bundle.includes.any { dep.group =~ it[0] && dep.name =~ it[1] }
        }.each { dep ->
            project.dependencies.add('filteredOut', "${dep.group}:${dep.name}:${dep.version}")
            unrollDependency(srcConfig.resolvedConfiguration.firstLevelModuleDependencies.find {
                it.moduleGroup == dep.group && it.moduleName == dep.name
            })
        }
        // now set up the scope mappings
        Conf2ScopeMappingContainer conf2Scopes = project.convention.plugins.maven.conf2ScopeMappings
        conf2Scopes.addMapping(MAX_VALUE, project.configurations.filteredOut, 'filteredOut')
        conf2Scopes.addMapping(MAX_VALUE, project.configurations.bundledIn, "bundled-${getTargetScope(conf2Scopes, srcConfig)}")
    }
      // TODO this needs to be the scope that corresponds to the root of dependency that introduced the transitive dep not just a blanket single scope
    // (because default -> runtime -> compile so deps really come from runtime and compile not default)
    private String getTargetScope(conf2Scopes, srcConfig) {
        final targetScope = conf2Scopes.getMapping(srcConfig.hierarchy)?.scope
        if (targetScope == null) {
            throw new InvalidEspritProjectException("Unable to find maven scope for configuration $bundle.configuration")
        }
        targetScope
    }
      /**
     * Assess the given dependency against the filter, if it matches recursively call this function with the children
     * until you hit a dependency that doesn't match the filter. At this point, add that dependency to the bundledIn
     * configuration.
     * @param rd the resolved dependency.
     * @param filters the filters.
     * @return the dependencies we need to reinstate to produce a valid pom.
     */
    protected void unrollDependency(ResolvedDependency rd) {
        if (rd != null) {
            if (bundle.includes.any { rd.moduleGroup =~ it[0] && rd.moduleName =~ it[1]} ) {
                rd.children.each{ unrollDependency(it) }
            } else {
                project.dependencies.add('bundledIn', "${rd.moduleGroup}:${rd.moduleName}:${rd.moduleVersion}")
            }
        }
    }