Skip to content

Commit ba3a8bc

Browse files
committed
add missing file
1 parent 453e456 commit ba3a8bc

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package datadog.gradle.plugin.muzzle
2+
3+
import org.eclipse.aether.artifact.Artifact
4+
import org.gradle.api.Project
5+
import org.gradle.api.artifacts.Configuration
6+
import java.io.ByteArrayInputStream
7+
import java.io.FileNotFoundException
8+
import java.net.URI
9+
import java.nio.file.Files
10+
import java.nio.file.Path
11+
import javax.xml.parsers.DocumentBuilderFactory
12+
import javax.xml.transform.OutputKeys
13+
import javax.xml.transform.TransformerFactory
14+
import javax.xml.transform.dom.DOMSource
15+
import javax.xml.transform.stream.StreamResult
16+
import org.w3c.dom.Document
17+
import org.w3c.dom.Element
18+
19+
internal object MuzzleExcludedDependencySupport {
20+
fun applyTo(
21+
project: Project,
22+
configuration: Configuration,
23+
directive: MuzzleDirective,
24+
versionArtifact: Artifact?
25+
) {
26+
if (versionArtifact == null || directive.excludedDependencies.isEmpty()) {
27+
return
28+
}
29+
30+
val repositoryUris = directive.getRepositories(MuzzleMavenRepoUtils.MUZZLE_REPOS).map { URI.create(it.url) }
31+
val repoDir = project.layout.buildDirectory
32+
.dir("generated/muzzle-excluded-dependencies/${configuration.name}")
33+
.get()
34+
.asFile
35+
.toPath()
36+
Files.createDirectories(repoDir)
37+
38+
materializeArtifactHierarchy(
39+
repoDir = repoDir,
40+
repositories = repositoryUris,
41+
coordinates = Coordinates(versionArtifact.groupId, versionArtifact.artifactId, versionArtifact.version),
42+
excludedDependencies = directive.excludedDependencies.map { it.split(":", limit = 2) }.map { it[0] to it[1] }.toSet(),
43+
rootArtifact = versionArtifact,
44+
seen = LinkedHashSet()
45+
)
46+
47+
val generatedRepo = project.repositories.maven {
48+
name = "${configuration.name}MuzzleExcludedDependencies"
49+
url = repoDir.toUri()
50+
}
51+
project.repositories.remove(generatedRepo)
52+
project.repositories.addFirst(generatedRepo)
53+
}
54+
55+
private fun materializeArtifactHierarchy(
56+
repoDir: Path,
57+
repositories: List<URI>,
58+
coordinates: Coordinates,
59+
excludedDependencies: Set<Pair<String, String>>,
60+
rootArtifact: Artifact?,
61+
seen: MutableSet<Coordinates>
62+
) {
63+
if (!seen.add(coordinates)) {
64+
return
65+
}
66+
67+
val pomBytes = downloadRequired(repositories, coordinates.pomRelativePath)
68+
val pomDocument = parsePom(pomBytes)
69+
removeExcludedDependencies(pomDocument, excludedDependencies)
70+
writePom(repoDir.resolve(coordinates.pomRelativePath), pomDocument)
71+
72+
if (rootArtifact != null) {
73+
downloadOptional(repositories, coordinates.artifactRelativePath(rootArtifact.extension, rootArtifact.classifier))
74+
?.let { artifactBytes ->
75+
val artifactPath = repoDir.resolve(coordinates.artifactRelativePath(rootArtifact.extension, rootArtifact.classifier))
76+
Files.createDirectories(artifactPath.parent)
77+
Files.write(artifactPath, artifactBytes)
78+
}
79+
}
80+
81+
parseParentCoordinates(pomDocument)?.let { parent ->
82+
materializeArtifactHierarchy(
83+
repoDir = repoDir,
84+
repositories = repositories,
85+
coordinates = parent,
86+
excludedDependencies = excludedDependencies,
87+
rootArtifact = null,
88+
seen = seen
89+
)
90+
}
91+
}
92+
93+
private fun parsePom(bytes: ByteArray): Document =
94+
DocumentBuilderFactory.newInstance().apply {
95+
isNamespaceAware = false
96+
}.newDocumentBuilder().parse(ByteArrayInputStream(bytes))
97+
98+
private fun parseParentCoordinates(document: Document): Coordinates? {
99+
val project = document.documentElement ?: return null
100+
val parent = childElements(project).firstOrNull { it.tagName == "parent" } ?: return null
101+
val groupId = childText(parent, "groupId") ?: return null
102+
val artifactId = childText(parent, "artifactId") ?: return null
103+
val version = childText(parent, "version") ?: return null
104+
return Coordinates(groupId, artifactId, version)
105+
}
106+
107+
private fun removeExcludedDependencies(document: Document, excludedDependencies: Set<Pair<String, String>>) {
108+
if (excludedDependencies.isEmpty()) {
109+
return
110+
}
111+
112+
val dependencies = document.getElementsByTagName("dependency")
113+
val toRemove = mutableListOf<Element>()
114+
for (index in 0 until dependencies.length) {
115+
val dependency = dependencies.item(index) as? Element ?: continue
116+
val groupId = childText(dependency, "groupId") ?: continue
117+
val artifactId = childText(dependency, "artifactId") ?: continue
118+
if (excludedDependencies.contains(groupId to artifactId)) {
119+
toRemove.add(dependency)
120+
}
121+
}
122+
123+
toRemove.forEach { dependency ->
124+
dependency.parentNode?.removeChild(dependency)
125+
}
126+
}
127+
128+
private fun writePom(path: Path, document: Document) {
129+
Files.createDirectories(path.parent)
130+
TransformerFactory.newInstance().newTransformer().apply {
131+
setOutputProperty(OutputKeys.INDENT, "yes")
132+
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
133+
}.transform(DOMSource(document), StreamResult(path.toFile()))
134+
}
135+
136+
private fun downloadRequired(repositories: List<URI>, relativePath: String): ByteArray =
137+
downloadOptional(repositories, relativePath)
138+
?: error("Could not download $relativePath from repositories ${repositories.joinToString()}")
139+
140+
private fun downloadOptional(repositories: List<URI>, relativePath: String): ByteArray? {
141+
for (repository in repositories) {
142+
try {
143+
val resolvedUri = URI.create("${repository.toString().trimEnd('/')}/$relativePath")
144+
val bytes = when (resolvedUri.scheme) {
145+
"file" -> {
146+
val path = Path.of(resolvedUri)
147+
if (Files.exists(path)) Files.readAllBytes(path) else null
148+
}
149+
"http", "https" -> resolvedUri.toURL().openStream().use { it.readBytes() }
150+
else -> null
151+
}
152+
if (bytes != null) {
153+
return bytes
154+
}
155+
} catch (_: FileNotFoundException) {
156+
// Try the next repository until one has the requested artifact.
157+
}
158+
}
159+
return null
160+
}
161+
162+
private fun childText(parent: Element, tagName: String): String? =
163+
childElements(parent).firstOrNull { it.tagName == tagName }?.textContent?.trim()
164+
165+
private fun childElements(parent: Element): List<Element> {
166+
val children = mutableListOf<Element>()
167+
val nodeList = parent.childNodes
168+
for (index in 0 until nodeList.length) {
169+
val child = nodeList.item(index)
170+
if (child is Element) {
171+
children.add(child)
172+
}
173+
}
174+
return children
175+
}
176+
177+
private data class Coordinates(
178+
val groupId: String,
179+
val artifactId: String,
180+
val version: String
181+
) {
182+
val pomRelativePath: String
183+
get() = "${groupId.replace('.', '/')}/$artifactId/$version/$artifactId-$version.pom"
184+
185+
fun artifactRelativePath(extension: String, classifier: String?): String {
186+
val classifierSuffix = classifier?.takeIf { it.isNotBlank() }?.let { "-$it" } ?: ""
187+
return "${groupId.replace('.', '/')}/$artifactId/$version/$artifactId-$version$classifierSuffix.$extension"
188+
}
189+
}
190+
}

0 commit comments

Comments
 (0)