Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create separate shadowJars for code and dependencies #443

Open
jayadev opened this issue Dec 21, 2018 · 6 comments
Open

Create separate shadowJars for code and dependencies #443

jayadev opened this issue Dec 21, 2018 · 6 comments

Comments

@jayadev
Copy link

jayadev commented Dec 21, 2018

Using the awesome shadow plugin version 4.0.2 and gradle 4.10, I wanted to create two separate shadowJars, one for my source code and another for my dependencies (since dependencies are large and rarely changes I don’t want to repackage them every time I change my source code). What I have in mind is to have a gradle plugin that add two separate tasks and which takes the same configurations supplied by user for shadowJar and overrides the configurations/sources used to create the shadowJar.

Below is what I have got so far, still trying to figure out a clean way to pass the shadow configs only once and whether there are other gotchas I need to worry about (ex: having two mergeServiceFiles will break etc)

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

task dependencyShadowJar(type: ShadowJar) {
    mergeServiceFiles()
    zip64 = true
    relocate 'com.google.common', 'com.shadow.google.common'
    classifier = 'dependencies'
    configurations = [project.configurations.runtime]
}

task userCodeShadowJar(type: ShadowJar) {
    mergeServiceFiles()
    zip64 = true
    relocate 'com.google.common', 'com.shadow.google.common'
    classifier = 'mycode'
    from sourceSets.main.output
}

task splitShadowJar {
    doLast {
        println "Building separate src and dependency shadowJars"
    }
}

splitShadowJar.dependsOn dependencyShadowJar
dependencyShadowJar.dependsOn userCodeShadowJar
  • Ideally I would like to have a shadowJar settings specified once and the tasks copies the same settings, does that require creating a custom Plugin task in groovy ?
  • Can I copy the settings from existing shadowJar that user specifies and just overrides the from or configurations part alone for my purpose

Anybody has attempted something similar ?

@johnrengelman
Copy link
Collaborator

That's a really interesting way to do this. Do the relocations apply correctly to the source files?

@jayadev
Copy link
Author

jayadev commented Dec 21, 2018

@johnrengelman doing a quick inspection of the strings in the class file, it looks like the relocation is applied in the source files. So it seems to do the right thing. I wanted to make this reusable across many projects so package it as a gradle plugin for easy usage.

Ideally I would like :

  • the users to specify their normal shadowJar configs in build.gradle.
  • applying my plugin would add these new tasks that take the same shadowJar configs and use it to create the two jars which can be used as needed.

I was hoping I could achieve this by extending what I have started with and some way copying the settings from shadowJar and calling the ShadowJar task with my updated settings. Is that doable using the above approach or will I have to create my own gradle task in groovy, any pointers ?

@dpkirchner
Copy link

I think I've got a solution -- at least, it's a solution that works for me. I've adapted jayadev's example to fit my needs:

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

// This just places the dependencies in a jar, not the app files. This is
// a partial copy of what the default shadowJar task does.
//
// This produces a jar named ${baseName}-dependencies-${version}.jar
task dependencyShadowJar(type: ShadowJar) {
    mergeServiceFiles()

    zip64 true

    relocate 'com.google.common', 'shadow.com.google.common'
    relocate 'com.google.protobuf', 'shadow.com.google.protobuf'

    destinationDirectory = file("build/libs")
    setAppendix("dependencies")

    configurations = [project.configurations.runtimeClasspath]
    exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class')
}

// This includes only files in the runtime directories (I think?), so just
// your app's code.
//
// This produces a jar named ${baseName}-app-${version}.jar
task appShadowJar(type: ShadowJar) {
    mergeServiceFiles()

    manifest {
        attributes 'Implementation-Title': 'example',
                'Implementation-Version': '0.0.1',
                'Main-Class': 'com.example.app'
    }

    zip64 true

    relocate 'com.google.common', 'shadow.com.google.common'
    relocate 'com.google.protobuf', 'shadow.com.google.protobuf'

    destinationDirectory = file("build/libs")
    setAppendix("app")

    from sourceSets.main.output
    configurations = [project.configurations.runtime]
}

tasks.assemble.dependsOn dependencyShadowJar
tasks.assemble.dependsOn appShadowJar

// We don't need the jar task any more.
task jar(type: Jar, overwrite: true) {
    // no-op
}

I'm not a gradle pro, so there's a darn good chance I'm not covering all scenarios I should.

@jschneider
Copy link

I run into the same problem. Thanks for your work - it helped me solving it.

The importat line is this:
configurations = [project.configurations.runtimeClasspath]

Would love to see this supported out of the box in shadowJars

@darylsze
Copy link

darylsze commented Aug 4, 2020

@jschneider add into main / module's build.gradle


configurations {
    runtimeClasspath // just define a separate configuration
}

@ctadlock
Copy link

I think I found an easier way to do this, at least in the case where you can filter by the ResolvedDependancy.

In my case I dont want two shadowed jars, just one for "my" app projects; the rest are fine as individual jars like usual without shadow.

Example to only include dependencies that are from Acme:

tasks.withType<ShadowJar> {
    dependencies {
        this.exclude {
            val exclude = it.name.contains("Acme", true).not()

            // helpful for debugging
            if (exclude) {
                println("Excluding: ${it.name}")
            } else {
                println("\tIncluding: ${it.name}")
            }

            exclude
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants