Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.jengelman.gradle.plugins.shadow.impl

import com.github.jengelman.gradle.plugins.shadow.ShadowStats
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction
import java.util.regex.Pattern
import org.objectweb.asm.commons.Remapper

/**
* Modified from `org.apache.maven.plugins.shade.DefaultShader.java#RelocatorRemapper`
*
* @author John Engelman
*/
open class RelocatorRemapper(
private val relocators: List<Relocator>,
private val stats: ShadowStats,
) : Remapper() {
private val classPattern: Pattern = Pattern.compile("(\\[*)?L(.+)")

open fun hasRelocators(): Boolean = relocators.isNotEmpty()

override fun mapValue(value: Any): Any {
return if (value is String) {
map(value)
} else {
super.mapValue(value)
}
}

override fun map(name: String): String {
var newName = name
var prefix = ""
var suffix = ""

val matcher = classPattern.matcher(newName)
if (matcher.matches()) {
prefix = matcher.group(1) + "L"
suffix = ""
newName = matcher.group(2)
}

for (relocator in relocators) {
if (relocator.canRelocateClass(newName)) {
val classContext = RelocateClassContext.builder().className(newName).stats(stats).build()
return prefix + relocator.relocateClass(classContext) + suffix
} else if (relocator.canRelocatePath(newName)) {
val pathContext = RelocatePathContext.builder().path(newName).stats(stats).build()
return prefix + relocator.relocatePath(pathContext) + suffix
}
}

return name
}

open fun mapPath(path: String): String {
return map(path.substring(0, path.indexOf('.')))
}

open fun mapPath(path: ShadowCopyAction.RelativeArchivePath): String {
return mapPath(path.pathString)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import groovy.lang.Closure
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.file.FileCollection
import org.gradle.api.specs.Spec
import org.gradle.api.specs.Specs

internal sealed class AbstractDependencyFilter(
private val project: Project,
) : DependencyFilter {
protected val includeSpecs: MutableList<Spec<ResolvedDependency>> = mutableListOf()
protected val excludeSpecs: MutableList<Spec<ResolvedDependency>> = mutableListOf()

protected abstract fun resolve(
dependencies: Set<ResolvedDependency>,
includedDependencies: MutableSet<ResolvedDependency>,
excludedDependencies: MutableSet<ResolvedDependency>,
)

override fun resolve(configuration: FileCollection): FileCollection {
val includedDeps = mutableSetOf<ResolvedDependency>()
val excludedDeps = mutableSetOf<ResolvedDependency>()
configuration as Configuration
resolve(configuration.resolvedConfiguration.firstLevelModuleDependencies, includedDeps, excludedDeps)
return project.files(configuration.files) -
project.files(excludedDeps.flatMap { it.moduleArtifacts.map(ResolvedArtifact::getFile) })
}

override fun resolve(configurations: Collection<FileCollection>): FileCollection {
return configurations.map { resolve(it) }
.reduceOrNull { acc, fileCollection -> acc + fileCollection }
?: project.files()
}

/**
* Exclude dependencies that match the provided spec.
*/
override fun exclude(spec: Spec<ResolvedDependency>): DependencyFilter = apply {
excludeSpecs.add(spec)
}

/**
* Include dependencies that match the provided spec.
*/
override fun include(spec: Spec<ResolvedDependency>): DependencyFilter = apply {
includeSpecs.add(spec)
}

/**
* Create a spec that matches the provided project notation on group, name, and version.
*/
override fun project(notation: Map<String, *>): Spec<ResolvedDependency> {
return dependency(dependency = project.dependencies.project(notation))
}

/**
* Create a spec that matches the default configuration for the provided project path on group, name, and version.
*/
override fun project(notation: String): Spec<ResolvedDependency> {
return dependency(
dependency = project.dependencies.project(
mapOf(
"path" to notation,
"configuration" to "default",
),
),
)
}

/**
* Create a spec that matches dependencies using the provided notation on group, name, and version.
*/
override fun dependency(notation: Any): Spec<ResolvedDependency> {
return dependency(dependency = project.dependencies.create(notation))
}

/**
* Create a spec that matches the provided dependency on group, name, and version.
*/
override fun dependency(dependency: Dependency): Spec<ResolvedDependency> {
return Spec<ResolvedDependency> { resolvedDependency ->
(dependency.group == null || resolvedDependency.moduleGroup.matches(dependency.group!!.toRegex())) &&
resolvedDependency.moduleName.matches(dependency.name.toRegex()) &&
(dependency.version == null || resolvedDependency.moduleVersion.matches(dependency.version!!.toRegex()))
}
}

/**
* Create a spec that matches the provided closure.
*/
override fun dependency(closure: Closure<*>): Spec<ResolvedDependency> {
return Specs.convertClosureToSpec(closure)
}

protected fun ResolvedDependency.isIncluded(): Boolean {
val include = includeSpecs.isEmpty() || includeSpecs.any { it.isSatisfiedBy(this) }
val exclude = excludeSpecs.isNotEmpty() && excludeSpecs.any { it.isSatisfiedBy(this) }
return include && !exclude
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedDependency

internal class DefaultDependencyFilter(
project: Project,
) : AbstractDependencyFilter(project) {
override fun resolve(
dependencies: Set<ResolvedDependency>,
includedDependencies: MutableSet<ResolvedDependency>,
excludedDependencies: MutableSet<ResolvedDependency>,
) {
dependencies.forEach {
if (if (it.isIncluded()) includedDependencies.add(it) else excludedDependencies.add(it)) {
resolve(it.children, includedDependencies, excludedDependencies)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedDependency

internal class MinimizeDependencyFilter(
project: Project,
) : AbstractDependencyFilter(project) {
override fun resolve(
dependencies: Set<ResolvedDependency>,
includedDependencies: MutableSet<ResolvedDependency>,
excludedDependencies: MutableSet<ResolvedDependency>,
) {
dependencies.forEach {
if (it.isIncluded() && !isParentExcluded(excludedDependencies, it)) {
includedDependencies.add(it)
} else {
excludedDependencies.add(it)
}
resolve(it.children, includedDependencies, excludedDependencies)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was wrongly converted!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

private fun isParentExcluded(
excludedDependencies: Set<ResolvedDependency>,
dependency: ResolvedDependency,
): Boolean {
return excludedDependencies.any { it in dependency.parents }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import java.io.File
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.SelfResolvingDependency
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.InputFiles
import org.vafer.jdependency.Clazzpath
import org.vafer.jdependency.ClazzpathUnit

/** Tracks unused classes in the project classpath. */
internal class UnusedTracker private constructor(
classDirs: Iterable<File>,
classJars: FileCollection,
private val _toMinimize: FileCollection,
) {
private val projectUnits: List<ClazzpathUnit>
private val cp = Clazzpath()

init {
projectUnits = classDirs.map { cp.addClazzpathUnit(it) } + classJars.map { cp.addClazzpathUnit(it) }
}

@get:InputFiles
val toMinimize: FileCollection get() = _toMinimize

fun findUnused(): Set<String> {
val unused = cp.clazzes.toMutableSet()
for (cpu in projectUnits) {
unused.removeAll(cpu.clazzes)
unused.removeAll(cpu.transitiveDependencies)
}
return unused.map { it.name }.toSet()
}

fun addDependency(jarOrDir: File) {
if (_toMinimize.contains(jarOrDir)) {
cp.addClazzpathUnit(jarOrDir)
}
}

companion object {
@JvmStatic
fun forProject(
apiJars: FileCollection,
sourceSetsClassesDirs: Iterable<File>,
toMinimize: FileCollection,
): UnusedTracker {
return UnusedTracker(sourceSetsClassesDirs, apiJars, toMinimize)
}

@JvmStatic
fun getApiJarsFromProject(project: Project): FileCollection {
val apiDependencies = project.configurations.findByName("api")?.dependencies
?: return project.files()
val runtimeConfiguration = project.configurations.findByName("runtimeClasspath")
?: project.configurations.getByName("runtime")
val apiJars = mutableListOf<File>()
apiDependencies.forEach { dep ->
when (dep) {
is ProjectDependency -> {
apiJars.addAll(getApiJarsFromProject(dep.dependencyProject))
addJar(runtimeConfiguration, dep, apiJars)
}
is SelfResolvingDependency -> {
apiJars.addAll(dep.resolve())
}
else -> {
addJar(runtimeConfiguration, dep, apiJars)
apiJars.add(runtimeConfiguration.find { it.name.startsWith("${dep.name}-") } as File)
}
}
}
return project.files(apiJars)
}

private fun addJar(config: Configuration, dep: Dependency, result: MutableList<File>) {
config.find { isProjectDependencyFile(it, dep) }?.let { result.add(it) }
}

private fun isProjectDependencyFile(file: File, dep: Dependency): Boolean {
val fileName = file.name
val dependencyName = dep.name
return fileName == "$dependencyName.jar" ||
(fileName.startsWith("$dependencyName-") && fileName.endsWith(".jar"))
}
}
}
Loading