@socketsecurity/cli-with-sentry 1.12.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -5
- package/dist/cli.js +2701 -1888
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/manifest-scripts/dotnet-tool/Microsoft.Build.Locator.dll +0 -0
- package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.deps.json +41 -0
- package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.dll +0 -0
- package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.dll.config +623 -0
- package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.runtimeconfig.json +15 -0
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/manifest-scripts/socket-facts.init.gradle +17 -4
- package/dist/manifest-scripts/socket-facts.plugin.scala +219 -42
- package/dist/manifest-scripts/socket-workspaces.init.gradle +16 -1
- package/dist/manifest-scripts/socket-workspaces.plugin.scala +15 -4
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/fix/generated-socket-facts.d.mts +3 -1
- package/dist/types/commands/fix/generated-socket-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-dotnet.d.mts +9 -0
- package/dist/types/commands/manifest/cmd-manifest-dotnet.d.mts.map +1 -0
- package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-dotnet-to-facts.d.mts +18 -0
- package/dist/types/commands/manifest/convert-dotnet-to-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts +4 -0
- package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts +12 -3
- package/dist/types/commands/manifest/discover-manifest-roots.d.mts.map +1 -1
- package/dist/types/commands/manifest/enumerate-workspaces.d.mts +3 -1
- package/dist/types/commands/manifest/enumerate-workspaces.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +19 -2
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +3 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/build-tool.d.mts +16 -4
- package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/exclude-paths-glob.d.mts +1 -0
- package/dist/types/commands/manifest/scripts/exclude-paths-glob.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/facts.d.mts +46 -2
- package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/records.d.mts +43 -5
- package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/resolution-report-nuget.d.mts +7 -0
- package/dist/types/commands/manifest/scripts/resolution-report-nuget.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/run.d.mts +14 -4
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/sidecar.d.mts +9 -1
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
- package/dist/types/commands/manifest/setup-manifest-config.d.mts +7 -4
- package/dist/types/commands/manifest/setup-manifest-config.d.mts.map +1 -1
- package/dist/types/commands/manifest/setup-recursive-manifest-config.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
- package/dist/types/commands/scan/reachability-flags.d.mts +1 -1
- package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
- package/dist/types/constants.d.mts +1 -1
- package/dist/types/utils/get-or-insert.d.mts +2 -0
- package/dist/types/utils/get-or-insert.d.mts.map +1 -0
- package/dist/types/utils/socket-json.d.mts +17 -0
- package/dist/types/utils/socket-json.d.mts.map +1 -1
- package/dist/utils.js +17 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -1
|
@@ -42,6 +42,21 @@ gradle.ext.socketIsExcluded = { String rel ->
|
|
|
42
42
|
if (c.isEmpty()) { return false }
|
|
43
43
|
gradle.ext.socketExcludeMatchers.call().any { p -> p.matcher(c).matches() }
|
|
44
44
|
}
|
|
45
|
+
// `-Psocket.excludeBase` (the scan root; the root project's dir by default) anchors `--exclude-paths`:
|
|
46
|
+
// a project relocated outside the build root still matches, one outside the scan root never does.
|
|
47
|
+
gradle.ext.socketExcludeBaseCache = null
|
|
48
|
+
gradle.ext.socketIsExcludedDir = { java.io.File dir ->
|
|
49
|
+
if (gradle.ext.socketExcludeMatchers.call().isEmpty()) { return false }
|
|
50
|
+
if (gradle.ext.socketExcludeBaseCache == null) {
|
|
51
|
+
def base = gradle.socketProp.call(gradle.rootProject, 'socket.excludeBase')?.toString()
|
|
52
|
+
gradle.ext.socketExcludeBaseCache = (base ? new java.io.File(base) : gradle.rootProject.projectDir).canonicalFile.toPath()
|
|
53
|
+
}
|
|
54
|
+
def path = dir.canonicalFile.toPath()
|
|
55
|
+
// On another Windows drive than the scan root, so outside it.
|
|
56
|
+
if (path.root != gradle.ext.socketExcludeBaseCache.root) { return false }
|
|
57
|
+
def rel = gradle.ext.socketExcludeBaseCache.relativize(path).toString().replace(File.separator, '/')
|
|
58
|
+
!(rel == '..' || rel.startsWith('../')) && gradle.ext.socketIsExcluded.call(rel.isEmpty() ? '.' : rel)
|
|
59
|
+
}
|
|
45
60
|
|
|
46
61
|
// Synchronized collections so --parallel-enabled builds don't race; lives on gradle.ext so every
|
|
47
62
|
// collector and the root aggregator share one instance.
|
|
@@ -94,7 +109,7 @@ gradle.projectsEvaluated { g ->
|
|
|
94
109
|
// A wholly excluded subproject emits no project record and its configs are never resolved (see the
|
|
95
110
|
// collector). The project maps above stay populated so a KEPT project depending on it
|
|
96
111
|
// still recognizes it as a first-party module, and its paths are kept so it is analysed with its code.
|
|
97
|
-
def excluded = g.ext.
|
|
112
|
+
def excluded = g.ext.socketIsExcludedDir.call(p.projectDir)
|
|
98
113
|
// Guarded: projects without the java `sourceSets` convention (pure aggregators) contribute empty
|
|
99
114
|
// lists rather than failing. Kotlin and Android code lives outside it (see below).
|
|
100
115
|
// ALL source sets are emitted (not just main/test) so custom ones (integrationTest,
|
|
@@ -200,9 +215,7 @@ allprojects { project ->
|
|
|
200
215
|
def state = gradle.socketFactsState
|
|
201
216
|
// Skip resolving a wholly excluded subproject: `--exclude-paths` covering its dir means we don't
|
|
202
217
|
// resolve for it at all (matches the project-record skip in projectsEvaluated).
|
|
203
|
-
|
|
204
|
-
def excludeRelDir = excludeRootPath.relativize(project.projectDir.toPath()).toString().replace(File.separator, '/')
|
|
205
|
-
if (gradle.ext.socketIsExcluded.call(excludeRelDir.isEmpty() ? '.' : excludeRelDir)) {
|
|
218
|
+
if (gradle.ext.socketIsExcludedDir.call(project.projectDir)) {
|
|
206
219
|
return
|
|
207
220
|
}
|
|
208
221
|
// Per-RESOLUTION-ROOT accumulators, LOCAL to this collector: one (subproject, configuration)
|
|
@@ -41,12 +41,23 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
41
41
|
// never resolved and emits no project record. Source-file-level exclusion is left to the analysis.
|
|
42
42
|
val excludeMatchers = parseExcludeMatchers()
|
|
43
43
|
val rootCanonPath = buildRoot.getCanonicalFile.toPath
|
|
44
|
-
def
|
|
45
|
-
val r =
|
|
44
|
+
def relTo(base: java.nio.file.Path)(f: File): String = {
|
|
45
|
+
val r = base.relativize(f.getCanonicalFile.toPath).toString.replace(java.io.File.separator, "/")
|
|
46
46
|
if (r.isEmpty) "." else r
|
|
47
47
|
}
|
|
48
|
-
def
|
|
49
|
-
|
|
48
|
+
def relOf(f: File): String = relTo(rootCanonPath)(f)
|
|
49
|
+
// `-Dsocket.excludeBase`: the directory `--exclude-paths` is relative to (the scan root; the
|
|
50
|
+
// build root by default); a project outside it never matches.
|
|
51
|
+
val excludeBase = sys.props.get("socket.excludeBase").filter(_.nonEmpty)
|
|
52
|
+
.map(b => new File(b).getCanonicalFile.toPath).getOrElse(rootCanonPath)
|
|
53
|
+
def isExcludedRef(ref: ProjectRef): Boolean = excludeMatchers.nonEmpty && {
|
|
54
|
+
val dir = extracted.get(baseDirectory.in(ref))
|
|
55
|
+
// Another Windows drive than the scan root is outside it.
|
|
56
|
+
dir.getCanonicalFile.toPath.getRoot == excludeBase.getRoot && {
|
|
57
|
+
val rel = relTo(excludeBase)(dir)
|
|
58
|
+
rel != ".." && !rel.startsWith("../") && isExcludedPath(rel, excludeMatchers)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
50
61
|
|
|
51
62
|
// Prefer `updateFull` (coursier `update` returns empty callers); fall back to `update` (sbt 0.13).
|
|
52
63
|
val hasUpdateFull =
|
|
@@ -175,11 +186,11 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
175
186
|
|
|
176
187
|
// ---- resolution ---------------------------------------------------------
|
|
177
188
|
|
|
178
|
-
private def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
private def crossOf(extracted: Extracted, ref: ProjectRef): ModuleID => ModuleID =
|
|
190
|
+
CrossVersion.apply(extracted.get(scalaVersion.in(ref)), extracted.get(scalaBinaryVersion.in(ref)))
|
|
191
|
+
|
|
192
|
+
private def rootIdOf(extracted: Extracted, ref: ProjectRef): ModuleID =
|
|
193
|
+
crossOf(extracted, ref)(extracted.get(projectID.in(ref)))
|
|
183
194
|
|
|
184
195
|
// GAV -> a build module's real artifact extension (jar for a normal project). coursier reports an
|
|
185
196
|
// inter-project dependency with no resolved artifact, so it would otherwise be ext-less and not
|
|
@@ -335,6 +346,47 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
335
346
|
dbf.newDocumentBuilder().parse(f)
|
|
336
347
|
}
|
|
337
348
|
|
|
349
|
+
// Child elements by local name; "*" for all.
|
|
350
|
+
private def pomKids(e: org.w3c.dom.Element, name: String): Seq[org.w3c.dom.Element] = {
|
|
351
|
+
val ns = e.getChildNodes
|
|
352
|
+
(0 until ns.getLength).map(ns.item).collect {
|
|
353
|
+
case c: org.w3c.dom.Element if name == "*" || c.getNodeName.split(':').last == name => c
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private def pomText(e: org.w3c.dom.Element, name: String): Option[String] =
|
|
358
|
+
pomKids(e, name).headOption.map(_.getTextContent.trim).filter(_.nonEmpty)
|
|
359
|
+
|
|
360
|
+
private def pomGroupId(e: org.w3c.dom.Element): String =
|
|
361
|
+
pomText(e, "groupId").orElse(pomKids(e, "parent").headOption.flatMap(pomText(_, "groupId"))).getOrElse("")
|
|
362
|
+
|
|
363
|
+
private val PropertyRef = "\\$\\{([^}]+)\\}".r
|
|
364
|
+
|
|
365
|
+
// A POM after inheritance: its dependencies (groupId:artifactId -> declared scope, optional), its
|
|
366
|
+
// managed scopes, the BOMs it imports, and its properties.
|
|
367
|
+
private final case class PomModel(
|
|
368
|
+
deps: Map[String, (String, Boolean)],
|
|
369
|
+
managed: Map[String, String],
|
|
370
|
+
imports: Seq[(String, String, String)],
|
|
371
|
+
props: Map[String, String])
|
|
372
|
+
|
|
373
|
+
private object PomModel {
|
|
374
|
+
val empty = PomModel(Map.empty, Map.empty, Nil, Map.empty)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Until nothing changes, as a property's value may refer to another.
|
|
378
|
+
private def interpolate(s: String, props: Map[String, String]): String = {
|
|
379
|
+
var cur = s
|
|
380
|
+
var prev = ""
|
|
381
|
+
var rounds = 0
|
|
382
|
+
while (cur != prev && rounds < 16) {
|
|
383
|
+
prev = cur
|
|
384
|
+
cur = PropertyRef.replaceAllIn(cur, m => java.util.regex.Matcher.quoteReplacement(props.getOrElse(m.group(1), m.matched)))
|
|
385
|
+
rounds += 1
|
|
386
|
+
}
|
|
387
|
+
cur
|
|
388
|
+
}
|
|
389
|
+
|
|
338
390
|
// Ivy's ResolveOptions.getDefaultResolveId: `<organisation>-<module>` of the resolved descriptor.
|
|
339
391
|
private def ivyResolveId(descriptor: File): Option[String] =
|
|
340
392
|
try {
|
|
@@ -453,10 +505,21 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
453
505
|
!m.evicted && !moduleExts.contains(gavKey(m.module)) && !unresolved.contains(gavKey(m.module))
|
|
454
506
|
|
|
455
507
|
def confsOf(r: ProjectRef): Seq[Configuration] = extracted.getOpt(ivyConfigurations.in(r)).getOrElse(Nil)
|
|
508
|
+
lazy val refByProject: Map[String, ProjectRef] = extracted.structure.allProjectRefs.map(r => r.project -> r).toMap
|
|
509
|
+
// Whether build project `r` declares module `c` in one of `confs`. A declaration without a
|
|
510
|
+
// configuration goes to Compile; the report names the module cross-versioned.
|
|
511
|
+
def declares(r: ProjectRef, c: ModuleID, confs: Set[String]): Boolean = {
|
|
512
|
+
val cross = crossOf(extracted, r)
|
|
513
|
+
extracted.getOpt(libraryDependencies.in(r)).getOrElse(Nil).exists { d =>
|
|
514
|
+
val masters = d.configurations.getOrElse("compile").split(';').flatMap(_.split("->").head.split(',')).map(_.trim)
|
|
515
|
+
d.organization == c.organization && cross(d).name == c.name && masters.exists(m => m == "*" || confs(m))
|
|
516
|
+
}
|
|
517
|
+
}
|
|
456
518
|
val buildDeps = extracted.get(buildDependencies)
|
|
457
519
|
|
|
458
|
-
// Classpaths.interSort, recording the (project -> dependency project) edges it follows
|
|
459
|
-
|
|
520
|
+
// Classpaths.interSort, recording the (project -> dependency project) edges it follows and the
|
|
521
|
+
// configurations of each project it reaches.
|
|
522
|
+
def interProjectEdges(conf: Configuration): (mutable.LinkedHashSet[(ProjectRef, ProjectRef)], Map[String, Set[String]]) = {
|
|
460
523
|
val masterConfs = confsOf(ref).map(_.name)
|
|
461
524
|
val visited = mutable.HashSet.empty[(ProjectRef, String)]
|
|
462
525
|
val edges = mutable.LinkedHashSet.empty[(ProjectRef, ProjectRef)]
|
|
@@ -477,7 +540,7 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
477
540
|
}
|
|
478
541
|
}
|
|
479
542
|
visit(ref, conf)
|
|
480
|
-
edges
|
|
543
|
+
(edges, visited.toSeq.groupBy(_._1.project).map { case (p, vs) => p -> vs.map(_._2).toSet })
|
|
481
544
|
}
|
|
482
545
|
|
|
483
546
|
def buildModuleCoord(r: ProjectRef): Coord = {
|
|
@@ -499,6 +562,7 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
499
562
|
val nodes = mutable.LinkedHashMap.empty[String, Node]
|
|
500
563
|
// org:name -> component ids (caller edges are module-level).
|
|
501
564
|
val midToIds = mutable.HashMap.empty[String, mutable.LinkedHashSet[String]]
|
|
565
|
+
var reachedConfs = Map.empty[String, Set[String]]
|
|
502
566
|
|
|
503
567
|
confsOf(ref).find(_.name == cfg).foreach { conf =>
|
|
504
568
|
// Keyed by project id, as projects need not have distinct coordinates.
|
|
@@ -511,7 +575,9 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
511
575
|
})
|
|
512
576
|
r.project
|
|
513
577
|
}
|
|
514
|
-
|
|
578
|
+
val (edges, reached) = interProjectEdges(conf)
|
|
579
|
+
reachedConfs = reached
|
|
580
|
+
edges.foreach { case (p, d) =>
|
|
515
581
|
val child = buildModuleNode(d)
|
|
516
582
|
if (p == ref) nodes(child).direct = true
|
|
517
583
|
else nodes(buildModuleNode(p)).children += child
|
|
@@ -538,19 +604,29 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
538
604
|
}
|
|
539
605
|
|
|
540
606
|
// Caller edges within this config: a root caller marks the child direct, any other becomes its parent.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
607
|
+
// Coursier resolves a Maven relocation's target in place of the relocating module, but reports
|
|
608
|
+
// neither the relocating module nor who declared it, so that caller is missing from the report.
|
|
609
|
+
// Its declarers are read off the build's own declarations and the POMs instead.
|
|
610
|
+
val candidates = cr.modules.filter(emittable)
|
|
611
|
+
// Drops self-edges (test → main resolving to the same coordinate), matching gradle.
|
|
612
|
+
def link(parentIds: Iterable[String], childIds: Iterable[String]): Unit =
|
|
613
|
+
parentIds.foreach(pid => childIds.foreach(cid => if (pid != cid) nodes(pid).children += cid))
|
|
614
|
+
def declaredBy(p: String, c: ModuleID): Boolean =
|
|
615
|
+
refByProject.get(p).exists(declares(_, c, reachedConfs.getOrElse(p, Set.empty)))
|
|
616
|
+
candidates.foreach { m =>
|
|
617
|
+
midToIds.get(moduleKey(m.module)).foreach { childIds =>
|
|
618
|
+
// lm-coursier gives a declared caller its declared revision, not the resolved one, so
|
|
619
|
+
// callers match by org:name (a configuration resolves one version per module).
|
|
620
|
+
m.callers.foreach { c =>
|
|
621
|
+
val callerKey = moduleKey(c.caller)
|
|
622
|
+
if (callerKey == rootKey) childIds.foreach(cid => nodes(cid).direct = true)
|
|
623
|
+
else midToIds.get(callerKey) match {
|
|
624
|
+
case Some(parentIds) => link(parentIds, childIds)
|
|
625
|
+
case None if !resolver.ivy && resolver.poms.relocates(c.caller, m) =>
|
|
626
|
+
if (declaredBy(ref.project, c.caller)) childIds.foreach(cid => nodes(cid).direct = true)
|
|
627
|
+
link(nodes.collect { case (nid, n) if n.project.nonEmpty && declaredBy(n.project, c.caller) => nid }, childIds)
|
|
628
|
+
resolver.poms.declaringModules(c.caller, candidates).foreach(p => midToIds.get(moduleKey(p)).foreach(link(_, childIds)))
|
|
629
|
+
case None =>
|
|
554
630
|
}
|
|
555
631
|
}
|
|
556
632
|
}
|
|
@@ -581,7 +657,7 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
581
657
|
|
|
582
658
|
// Artifacts the resolver expected and did not fetch. Under Ivy they come from its resolution report
|
|
583
659
|
// instead (see ivyUnfetchedOf).
|
|
584
|
-
private final class Resolver(val ivy: Boolean, poms: PomPackaging) {
|
|
660
|
+
private final class Resolver(val ivy: Boolean, val poms: PomPackaging) {
|
|
585
661
|
def missingOf(m: ModuleReport): List[Artifact] =
|
|
586
662
|
if (ivy) Nil else Option(m.missingArtifacts).toList.flatten.filterNot(poms.isUnexpectedDefault)
|
|
587
663
|
}
|
|
@@ -590,15 +666,126 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
590
666
|
// the POM declares a jar packaging. Only the module's own POM tells whether its artifact is a jar
|
|
591
667
|
// (packaging defaults to jar, so a parent's never applies); an unreadable POM counts as one.
|
|
592
668
|
private final class PomPackaging(csrCacheDir: Option[File]) {
|
|
593
|
-
private val
|
|
669
|
+
private val pomMemo = mutable.HashMap.empty[String, Option[org.w3c.dom.Element]]
|
|
670
|
+
|
|
671
|
+
private def pom(url: String): Option[org.w3c.dom.Element] =
|
|
672
|
+
pomMemo.getOrElseUpdate(url, coursierLocalFiles(url, csrCacheDir).find(_.isFile).flatMap { f =>
|
|
673
|
+
try Some(parseXml(f).getDocumentElement)
|
|
674
|
+
catch { case _: Exception => None }
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
// Maven repository layout.
|
|
678
|
+
private def moduleDir(base: String, org: String, name: String): String =
|
|
679
|
+
base + org.replace('.', '/') + "/" + name + "/"
|
|
680
|
+
|
|
681
|
+
private def pomUrl(base: String, org: String, name: String, rev: String): String =
|
|
682
|
+
moduleDir(base, org, name) + rev + "/" + name + "-" + rev + ".pom"
|
|
683
|
+
|
|
684
|
+
// The repository root a module's artifacts were fetched from.
|
|
685
|
+
private def repoBase(m: ModuleReport): Option[String] = {
|
|
686
|
+
val mid = m.module
|
|
687
|
+
val marker = moduleDir("/", mid.organization, mid.name) + orEmpty(mid.revision) + "/"
|
|
688
|
+
val urls = Option(m.artifacts).toList.flatten.flatMap(_._1.url) ++
|
|
689
|
+
Option(m.missingArtifacts).toList.flatten.flatMap(_.url)
|
|
690
|
+
urls.map(_.toString).collectFirst { case u if u.contains(marker) => u.substring(0, u.indexOf(marker) + 1) }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private val modelMemo = mutable.HashMap.empty[String, PomModel]
|
|
694
|
+
|
|
695
|
+
// Maven model inheritance and interpolation of a POM, as far as its dependencies' scopes need.
|
|
696
|
+
private def model(base: String, org: String, name: String, rev: String, depth: Int): PomModel = {
|
|
697
|
+
val url = pomUrl(base, org, name, rev)
|
|
698
|
+
modelMemo.getOrElseUpdate(url, pom(url) match {
|
|
699
|
+
case None => PomModel.empty
|
|
700
|
+
case Some(e) =>
|
|
701
|
+
val parent = pomKids(e, "parent").headOption
|
|
702
|
+
val pm = parent match {
|
|
703
|
+
case Some(p) if depth < 16 =>
|
|
704
|
+
model(base, pomText(p, "groupId").getOrElse(""), pomText(p, "artifactId").getOrElse(""),
|
|
705
|
+
pomText(p, "version").getOrElse(""), depth + 1)
|
|
706
|
+
case _ => PomModel.empty
|
|
707
|
+
}
|
|
708
|
+
val groupId = pomGroupId(e)
|
|
709
|
+
val artifactId = pomText(e, "artifactId").getOrElse("")
|
|
710
|
+
val version = pomText(e, "version").orElse(parent.flatMap(pomText(_, "version"))).getOrElse("")
|
|
711
|
+
val own = pomKids(e, "properties").flatMap(ps => pomKids(ps, "*")).map(p => p.getNodeName.split(':').last -> p.getTextContent.trim)
|
|
712
|
+
val props = pm.props ++ own ++ Seq(
|
|
713
|
+
"project.groupId" -> groupId, "pom.groupId" -> groupId, "groupId" -> groupId,
|
|
714
|
+
"project.artifactId" -> artifactId, "pom.artifactId" -> artifactId, "artifactId" -> artifactId,
|
|
715
|
+
"project.version" -> version, "pom.version" -> version, "version" -> version
|
|
716
|
+
) ++ parent.toSeq.flatMap { p =>
|
|
717
|
+
Seq("project.parent.groupId" -> pomText(p, "groupId").getOrElse(""),
|
|
718
|
+
"project.parent.version" -> pomText(p, "version").getOrElse(""))
|
|
719
|
+
}
|
|
720
|
+
def field(d: _root_.org.w3c.dom.Element, name: String) = interpolate(pomText(d, name).getOrElse(""), props)
|
|
721
|
+
def ga(d: _root_.org.w3c.dom.Element) = field(d, "groupId") + ":" + field(d, "artifactId")
|
|
722
|
+
val deps = pomKids(e, "dependencies").flatMap(ds => pomKids(ds, "dependency"))
|
|
723
|
+
val managed = pomKids(e, "dependencyManagement").flatMap(pomKids(_, "dependencies")).flatMap(pomKids(_, "dependency"))
|
|
724
|
+
val (imports, scoped) = managed.partition(d => field(d, "scope") == "import" && field(d, "type") == "pom")
|
|
725
|
+
PomModel(
|
|
726
|
+
pm.deps ++ deps.map(d => ga(d) -> ((field(d, "scope"), field(d, "optional") == "true"))),
|
|
727
|
+
pm.managed ++ scoped.map(d => ga(d) -> field(d, "scope")),
|
|
728
|
+
imports.map(d => (field(d, "groupId"), field(d, "artifactId"), field(d, "version"))) ++ pm.imports,
|
|
729
|
+
props)
|
|
730
|
+
})
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private val managedMemo = mutable.HashMap.empty[String, Map[String, String]]
|
|
734
|
+
|
|
735
|
+
// Managed scopes after BOM imports: the POM's own and inherited entries win, then the first import.
|
|
736
|
+
private def managedScopes(base: String, org: String, name: String, rev: String, depth: Int): Map[String, String] =
|
|
737
|
+
managedMemo.getOrElseUpdate(pomUrl(base, org, name, rev), {
|
|
738
|
+
val m = model(base, org, name, rev, 0)
|
|
739
|
+
m.imports.reverse.foldLeft(Map.empty[String, String]) { case (acc, (g, a, v)) =>
|
|
740
|
+
if (depth < 16) acc ++ managedScopes(base, g, a, v, depth + 1) else acc
|
|
741
|
+
} ++ m.managed
|
|
742
|
+
})
|
|
743
|
+
|
|
744
|
+
// org:name of the modules each relocating module's cached POMs relocate to.
|
|
745
|
+
private val relocationsMemo = mutable.HashMap.empty[(String, String), Set[String]]
|
|
746
|
+
|
|
747
|
+
// Whether `relocating` is a POM relocated to `target`'s module. The report gives the relocating
|
|
748
|
+
// module its declared revision, which may be a range, so every cached version is tried; the
|
|
749
|
+
// target may have resolved to another version than the relocation names.
|
|
750
|
+
def relocates(relocating: ModuleID, target: ModuleReport): Boolean =
|
|
751
|
+
repoBase(target).exists { base =>
|
|
752
|
+
relocationsMemo.getOrElseUpdate((base, gavKey(relocating)), {
|
|
753
|
+
val dir = moduleDir(base, relocating.organization, relocating.name)
|
|
754
|
+
val versions = orEmpty(relocating.revision) +: coursierLocalFiles(dir, csrCacheDir)
|
|
755
|
+
.flatMap(d => Option(d.listFiles).toSeq.flatten).filter(_.isDirectory).map(_.getName)
|
|
756
|
+
versions.distinct.flatMap { v =>
|
|
757
|
+
pom(pomUrl(base, relocating.organization, relocating.name, v)).toSeq.flatMap { e =>
|
|
758
|
+
pomKids(e, "distributionManagement").flatMap(pomKids(_, "relocation")).headOption.map { r =>
|
|
759
|
+
pomText(r, "groupId").getOrElse(pomGroupId(e)) + ":" +
|
|
760
|
+
pomText(r, "artifactId").orElse(pomText(e, "artifactId")).getOrElse("")
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}.toSet
|
|
764
|
+
})(moduleKey(target.module))
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// The modules among `candidates` whose POM passes `declared` on to their dependents: compile or
|
|
768
|
+
// runtime scope, declared or managed, and not optional.
|
|
769
|
+
def declaringModules(declared: ModuleID, candidates: Seq[ModuleReport]): Seq[ModuleID] = {
|
|
770
|
+
val key = moduleKey(declared)
|
|
771
|
+
candidates.filter { p =>
|
|
772
|
+
repoBase(p).exists { base =>
|
|
773
|
+
val m = model(base, p.module.organization, p.module.name, orEmpty(p.module.revision), 0)
|
|
774
|
+
m.deps.get(key).exists { case (scope, optional) =>
|
|
775
|
+
val effective =
|
|
776
|
+
if (scope.nonEmpty) scope
|
|
777
|
+
else managedScopes(base, p.module.organization, p.module.name, orEmpty(p.module.revision), 0).getOrElse(key, "")
|
|
778
|
+
Set("", "compile", "runtime")(effective) && !optional
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}.map(_.module)
|
|
782
|
+
}
|
|
594
783
|
|
|
595
784
|
def isUnexpectedDefault(a: Artifact): Boolean = {
|
|
596
785
|
val ext = extOf(a)
|
|
597
786
|
a.classifier.isEmpty && ext == "jar" && a.url.map(_.toString).exists { url =>
|
|
598
|
-
url.endsWith("." + ext) &&
|
|
599
|
-
|
|
600
|
-
coursierLocalFiles(pomUrl, csrCacheDir).find(_.isFile).flatMap(pomPackaging).exists(p => !JarPackagings(p))
|
|
601
|
-
})
|
|
787
|
+
url.endsWith("." + ext) &&
|
|
788
|
+
pom(url.dropRight(ext.length) + "pom").exists(e => !JarPackagings(pomText(e, "packaging").getOrElse("jar")))
|
|
602
789
|
}
|
|
603
790
|
}
|
|
604
791
|
}
|
|
@@ -632,16 +819,6 @@ object SocketFactsPlugin extends AutoPlugin {
|
|
|
632
819
|
if (ch > 128 || " %$&+,:;=?@<>#".indexOf(ch) >= 0) "%" + "%02X".format(ch.toInt) else ch.toString
|
|
633
820
|
}
|
|
634
821
|
|
|
635
|
-
// Maven's default packaging is jar.
|
|
636
|
-
private def pomPackaging(pom: File): Option[String] =
|
|
637
|
-
try {
|
|
638
|
-
val kids = parseXml(pom).getDocumentElement.getChildNodes
|
|
639
|
-
val declared = (0 until kids.getLength).map(kids.item).collectFirst {
|
|
640
|
-
case e: org.w3c.dom.Element if e.getNodeName.split(':').last == "packaging" => e.getTextContent.trim
|
|
641
|
-
}
|
|
642
|
-
Some(declared.filter(_.nonEmpty).getOrElse("jar"))
|
|
643
|
-
} catch { case _: Exception => None }
|
|
644
|
-
|
|
645
822
|
// ---- config selection ---------------------------------------------------
|
|
646
823
|
|
|
647
824
|
// With no includes the default is ALL configurations (captures build/tooling deps).
|
|
@@ -39,6 +39,21 @@ gradle.ext.socketIsExcluded = { String rel ->
|
|
|
39
39
|
if (c.isEmpty()) { return false }
|
|
40
40
|
gradle.ext.socketExcludeMatchers.call().any { p -> p.matcher(c).matches() }
|
|
41
41
|
}
|
|
42
|
+
// `-Psocket.excludeBase` (the scan root; the root project's dir by default) anchors `--exclude-paths`:
|
|
43
|
+
// a project relocated outside the build root still matches, one outside the scan root never does.
|
|
44
|
+
gradle.ext.socketExcludeBaseCache = null
|
|
45
|
+
gradle.ext.socketIsExcludedDir = { java.io.File dir ->
|
|
46
|
+
if (gradle.ext.socketExcludeMatchers.call().isEmpty()) { return false }
|
|
47
|
+
if (gradle.ext.socketExcludeBaseCache == null) {
|
|
48
|
+
def base = gradle.socketProp.call(gradle.rootProject, 'socket.excludeBase')?.toString()
|
|
49
|
+
gradle.ext.socketExcludeBaseCache = (base ? new java.io.File(base) : gradle.rootProject.projectDir).canonicalFile.toPath()
|
|
50
|
+
}
|
|
51
|
+
def path = dir.canonicalFile.toPath()
|
|
52
|
+
// On another Windows drive than the scan root, so outside it.
|
|
53
|
+
if (path.root != gradle.ext.socketExcludeBaseCache.root) { return false }
|
|
54
|
+
def rel = gradle.ext.socketExcludeBaseCache.relativize(path).toString().replace(File.separator, '/')
|
|
55
|
+
!(rel == '..' || rel.startsWith('../')) && gradle.ext.socketIsExcluded.call(rel.isEmpty() ? '.' : rel)
|
|
56
|
+
}
|
|
42
57
|
|
|
43
58
|
gradle.ext.socketWorkspacesInfo = Collections.synchronizedList([])
|
|
44
59
|
|
|
@@ -52,7 +67,7 @@ gradle.projectsEvaluated { g ->
|
|
|
52
67
|
r.isEmpty() ? '.' : r
|
|
53
68
|
}
|
|
54
69
|
g.rootProject.allprojects.each { p ->
|
|
55
|
-
if (g.ext.
|
|
70
|
+
if (g.ext.socketIsExcludedDir.call(p.projectDir)) {
|
|
56
71
|
return
|
|
57
72
|
}
|
|
58
73
|
def buildFiles = []
|
|
@@ -32,12 +32,23 @@ object SocketWorkspacesPlugin extends AutoPlugin {
|
|
|
32
32
|
// emits no project record. Mirrors socket-facts.plugin.scala / the gradle / maven producers.
|
|
33
33
|
val excludeMatchers = parseExcludeMatchers()
|
|
34
34
|
val rootCanonPath = buildRoot.getCanonicalFile.toPath
|
|
35
|
-
def
|
|
36
|
-
val r =
|
|
35
|
+
def relTo(base: java.nio.file.Path)(f: File): String = {
|
|
36
|
+
val r = base.relativize(f.getCanonicalFile.toPath).toString.replace(java.io.File.separator, "/")
|
|
37
37
|
if (r.isEmpty) "." else r
|
|
38
38
|
}
|
|
39
|
-
def
|
|
40
|
-
|
|
39
|
+
def relOf(f: File): String = relTo(rootCanonPath)(f)
|
|
40
|
+
// `-Dsocket.excludeBase`: the directory `--exclude-paths` is relative to (the scan root; the
|
|
41
|
+
// build root by default); a project outside it never matches.
|
|
42
|
+
val excludeBase = sys.props.get("socket.excludeBase").filter(_.nonEmpty)
|
|
43
|
+
.map(b => new File(b).getCanonicalFile.toPath).getOrElse(rootCanonPath)
|
|
44
|
+
def isExcludedRef(ref: ProjectRef): Boolean = excludeMatchers.nonEmpty && {
|
|
45
|
+
val dir = extracted.get(baseDirectory.in(ref))
|
|
46
|
+
// Another Windows drive than the scan root is outside it.
|
|
47
|
+
dir.getCanonicalFile.toPath.getRoot == excludeBase.getRoot && {
|
|
48
|
+
val rel = relTo(excludeBase)(dir)
|
|
49
|
+
rel != ".." && !rel.startsWith("../") && isExcludedPath(rel, excludeMatchers)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
41
52
|
|
|
42
53
|
def rootIdOf(ref: ProjectRef): ModuleID = {
|
|
43
54
|
val sv = extracted.get(scalaVersion.in(ref))
|