Skip to content
This repository was archived by the owner on May 4, 2025. It is now read-only.

Commit 4bcb975

Browse files
authored
Merge pull request #41 from ckipp01/purl
fix: ensure invalid PURLs are not included
2 parents 8b5da93 + cadff8f commit 4bcb975

File tree

3 files changed

+127
-10
lines changed

3 files changed

+127
-10
lines changed

build.sc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ object plugin extends Common with BuildInfo {
6262

6363
override def moduleDeps = Seq(domain)
6464
override def compileIvyDeps = super.compileIvyDeps() ++ Agg(
65-
ivy"com.lihaoyi::mill-scalalib:$millVersion",
65+
ivy"com.lihaoyi::mill-scalalib:$millVersion"
66+
)
67+
68+
override def ivyDeps = super.ivyDeps() ++ Agg(
6669
ivy"com.lihaoyi::upickle:2.0.0",
67-
ivy"com.lihaoyi::requests:0.7.1"
70+
ivy"com.lihaoyi::requests:0.7.1",
71+
ivy"com.github.package-url:packageurl-java:1.4.1"
6872
)
6973

7074
override def buildInfoMembers = Map(
@@ -100,6 +104,9 @@ object itest extends MillIntegrationTestModule {
100104
),
101105
PathRef(testBase / "range") -> Seq(
102106
TestInvocation.Targets(Seq("verify"), noServer = true)
107+
),
108+
PathRef(testBase / "reconciledRange") -> Seq(
109+
TestInvocation.Targets(Seq("verify"), noServer = true)
103110
)
104111
)
105112
}

itest/src/reconciledRange/build.sc

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import mill._, scalalib._
2+
import $exec.plugins
3+
import io.kipp.mill.github.dependency.graph.Graph
4+
import mill.eval.Evaluator
5+
import $ivy.`org.scalameta::munit:0.7.29`
6+
import munit.Assertions._
7+
8+
object minimalDep extends ScalaModule {
9+
def scalaVersion = "2.13.8"
10+
11+
def ivyDeps = Agg(
12+
ivy"com.fasterxml.jackson.core:jackson-core:2.13.3"
13+
)
14+
}
15+
16+
object minimal extends ScalaModule {
17+
def moduleDeps = Seq(`minimalDep`)
18+
19+
def scalaVersion = "2.13.8"
20+
21+
def ivyDeps = Agg(
22+
ivy"org.jongo:jongo:1.5.0"
23+
)
24+
}
25+
26+
def verify(ev: Evaluator) = T.command {
27+
val manifestMapping = Graph.generate(ev)()
28+
assert(manifestMapping.size == 2)
29+
30+
// OK, this is a super weird one. Jongo here has a range dep which is fine, and when
31+
// it's resolved by itself, it correctly gets 2.12.3 like you can see below:
32+
//
33+
// ❯ cs resolve org.jongo:jongo:1.5.0
34+
// com.fasterxml.jackson.core:jackson-annotations:2.12.3:default
35+
// com.fasterxml.jackson.core:jackson-core:2.12.3:default
36+
// com.fasterxml.jackson.core:jackson-databind:2.12.3:default
37+
// de.undercouch:bson4jackson:2.12.0:default
38+
// org.jongo:jongo:1.5.0:default
39+
//
40+
// The issue enteres with a setup like the above. For some reason coursier
41+
// will actually retain the range as the reconciledVersion in the
42+
// DependencyTree when it should be the actual reconciled versions.
43+
//
44+
// dep version: [2.7.0,2.12.3]
45+
// retained version: [2.7.0,2.12.3]
46+
// reconciled version: [2.7.0,2.12.3]
47+
//
48+
// Since I believe this to be a bug in coursier for now we'll just throw them
49+
// out to ensure we're not creating invalid PURLs.
50+
val expected = Set(
51+
"org.scala-lang:scala-library:2.13.8",
52+
"com.fasterxml.jackson.core:jackson-core:2.13.3",
53+
// NOTICE that com.fasterxml.jackson.core:jackson-core:[2.7.0,2.12.3] is not here
54+
"com.fasterxml.jackson.core:jackson-core:2.12.3",
55+
"com.fasterxml.jackson.core:jackson-databind:2.12.3",
56+
"com.fasterxml.jackson.core:jackson-annotations:2.12.3",
57+
"org.jongo:jongo:1.5.0",
58+
"de.undercouch:bson4jackson:2.12.0"
59+
)
60+
61+
val results = manifestMapping.foldLeft[Set[String]](Set.empty) {
62+
case (set, mapping) =>
63+
set ++ mapping._2.resolved.keySet
64+
}
65+
66+
assertEquals(results, expected)
67+
}

plugin/src/io/kipp/mill/github/dependency/graph/ModuleTrees.scala

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.kipp.mill.github.dependency.graph
22

3+
import com.github.packageurl.PackageURLBuilder
34
import coursier.graph.DependencyTree
45
import io.kipp.github.dependency.graph.domain._
56
import mill.scalalib.JavaModule
67

78
import scala.collection.mutable
9+
import scala.util.Try
810

911
/** Represents a project modules an the dependency trees that belong to it.
1012
*
@@ -25,7 +27,9 @@ final case class ModuleTrees(
2527
* @return Mapping of the name of the dependency and the DependencyNode that
2628
* corresponds to it. The format of the name is org:module:version.
2729
*/
28-
def toFlattenedNodes(): Map[String, DependencyNode] = {
30+
def toFlattenedNodes()(implicit
31+
ctx: mill.api.Ctx
32+
): Map[String, DependencyNode] = {
2933

3034
val allDependencies = mutable.Map[String, DependencyNode]()
3135

@@ -37,16 +41,34 @@ final case class ModuleTrees(
3741

3842
def putTogether: DependencyNode = {
3943
// TODO consider classifiers
40-
val packageUrl =
41-
s"pkg:maven/${dep.module.organization.value}/${dep.module.name.value}@${reconciledVersion}"
44+
45+
val purl = Try(
46+
PackageURLBuilder
47+
.aPackageURL()
48+
.withType("maven")
49+
.withNamespace(dep.module.organization.value)
50+
.withName(dep.module.name.value)
51+
.withVersion(reconciledVersion)
52+
.build()
53+
).fold(
54+
e => {
55+
ctx.log.error(
56+
s"PURL can't be created from: ${dep.module.orgName}:${reconciledVersion}"
57+
)
58+
ctx.log.error(e.getMessage())
59+
None
60+
},
61+
validPurl => Some(validPurl.toString())
62+
)
63+
4264
val relationShip: DependencyRelationship =
4365
if (root) DependencyRelationship.direct
4466
else DependencyRelationship.indirect
4567
val dependencies = tree.children.map { child =>
4668
s"${child.dependency.module.orgName}:${child.reconciledVersion}"
4769
}
4870
DependencyNode(
49-
Some(packageUrl),
71+
purl,
5072
// TODO we can check if original == reconciled here and add metadata that it is a reconciled version
5173
Map.empty,
5274
Some(relationShip),
@@ -60,15 +82,36 @@ final case class ModuleTrees(
6082

6183
allDependencies.get(name) match {
6284
// If the node is found and the relationship is correct just do nothing
63-
case Some(node) if verifyRelationship(node) => ()
85+
case Some(node) if verifyRelationship(node) =>
86+
ctx.log.debug(
87+
s"Already seen ${name} with this relationship in this manifest, so skipping..."
88+
)
6489
// If the node is found and the relationship is incorrect, but it's a
6590
// root node, then make sure to mark it as direct
6691
case Some(node) if root =>
92+
ctx.log.debug(
93+
s"Already seen ${name} but we're at the root level so marking as direct..."
94+
)
6795
val updated =
6896
node.copy(relationship = Some(DependencyRelationship.direct))
6997
allDependencies += ((name, updated))
70-
// Should never really happen, but it it does do nothing
71-
case Some(_) => ()
98+
case Some(_) =>
99+
ctx.log.debug(
100+
s"Found ${name}, but it's already marked as direct so skipping..."
101+
)
102+
// Not a very elegant check, but we don't want to include a range in
103+
// here. These shouldn't still be a range at this point, but it is for
104+
// whatever reason. For now ignore it. This should be incredibly rare
105+
// and I believe a bug in coursier.
106+
case None if reconciledVersion.contains(",") =>
107+
ctx.log.error(
108+
s"""Found what I think is a range version that shouldn't be here...
109+
|
110+
|${dep.module.organization.value}:${dep.module.name.value}:${reconciledVersion}
111+
|
112+
|If you see this, report it. Skipping...
113+
|""".stripMargin
114+
)
72115
// Unseen dependency, create a node for it
73116
case None =>
74117
val node = putTogether
@@ -82,7 +125,7 @@ final case class ModuleTrees(
82125
allDependencies.toMap
83126
}
84127

85-
def toManifest() = {
128+
def toManifest()(implicit ctx: mill.api.Ctx) = {
86129
// NOTE: That this may seem odd when reading the spec that we have a
87130
// manifest per module basically, but we did check with the GitHub team and
88131
// they verified the manifests that we showed them.

0 commit comments

Comments
 (0)