@socketsecurity/cli-with-sentry 1.9.1 → 1.10.1
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 +31 -0
- package/README.md +5 -5
- package/dist/cli.js +379 -127
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/manifest-scripts/socket-facts.init.gradle +244 -56
- package/dist/manifest-scripts/socket-facts.plugin.scala +290 -73
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +5 -2
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +5 -2
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +5 -2
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/enumerate-workspaces.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +8 -1
- 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/handle-manifest-dynamic-sbom-inference.d.mts +2 -1
- package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +4 -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/facts.d.mts +3 -2
- package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/records.d.mts +10 -4
- package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +3 -2
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +3 -3
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +29 -8
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts +14 -3
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/run.d.mts +2 -1
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
- package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
- package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/warn-socket-json-only-flags.d.mts +2 -0
- package/dist/types/commands/scan/warn-socket-json-only-flags.d.mts.map +1 -0
- package/dist/types/constants.d.mts +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -53,7 +53,6 @@ gradle.ext.socketFactsState = [
|
|
|
53
53
|
// Whole configs whose resolution threw (vs a single unresolved dep in `failures`).
|
|
54
54
|
unscannable : Collections.synchronizedList([]),
|
|
55
55
|
scannedConfigs : Collections.synchronizedSet([] as Set),
|
|
56
|
-
projectKeys : Collections.synchronizedSet([] as Set),
|
|
57
56
|
// Only populated with `-Psocket.withFiles=true`: reading `artifact.file` downloads the artifact.
|
|
58
57
|
paths : Collections.synchronizedMap([:]),
|
|
59
58
|
perSub : Collections.synchronizedMap([:]),
|
|
@@ -63,10 +62,12 @@ gradle.ext.socketFactsState = [
|
|
|
63
62
|
projectArtifactExt : Collections.synchronizedMap([:]),
|
|
64
63
|
// Project path -> "group:name", to tell this build's projects from an included build's.
|
|
65
64
|
projectGaByPath : Collections.synchronizedMap([:]),
|
|
65
|
+
// Project path -> "group:name:version", the coordinate a failed project dependency is reported by.
|
|
66
|
+
projectGavByPath : Collections.synchronizedMap([:]),
|
|
66
67
|
]
|
|
67
68
|
|
|
68
|
-
// Capture every project's
|
|
69
|
-
//
|
|
69
|
+
// Capture every project's identity before collectors run so they can recognize intra-project deps
|
|
70
|
+
// without an ordering dependency on other subprojects.
|
|
70
71
|
gradle.projectsEvaluated { g ->
|
|
71
72
|
def rootPath = g.rootProject.projectDir.toPath()
|
|
72
73
|
// '/'-normalized so values are stable across OSes and portable to another machine.
|
|
@@ -75,7 +76,6 @@ gradle.projectsEvaluated { g ->
|
|
|
75
76
|
r.isEmpty() ? '.' : r
|
|
76
77
|
}
|
|
77
78
|
g.rootProject.allprojects.each { p ->
|
|
78
|
-
g.socketFactsState.projectKeys.add("${p.group ?: ''}:${p.name}")
|
|
79
79
|
// The module's real artifact extension (jar for java; '' for a project that builds no archive,
|
|
80
80
|
// e.g. a pure aggregator). Used to stamp intra-project deps that resolve without an artifact.
|
|
81
81
|
def artExt = ''
|
|
@@ -90,12 +90,11 @@ gradle.projectsEvaluated { g ->
|
|
|
90
90
|
} catch (Exception ignore) {}
|
|
91
91
|
g.socketFactsState.projectArtifactExt[p.path] = artExt
|
|
92
92
|
g.socketFactsState.projectGaByPath[p.path] = "${p.group ?: ''}:${p.name}".toString()
|
|
93
|
+
g.socketFactsState.projectGavByPath[p.path] = "${p.group ?: ''}:${p.name}:${p.version ?: ''}".toString()
|
|
93
94
|
// A wholly excluded subproject emits no project record and its configs are never resolved (see the
|
|
94
|
-
// collector).
|
|
95
|
-
// still recognizes it as a first-party module.
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
}
|
|
95
|
+
// collector). The project maps above stay populated so a KEPT project depending on it
|
|
96
|
+
// 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.socketIsExcluded.call(rel(p.projectDir))
|
|
99
98
|
// Guarded: projects without the java `sourceSets` convention (pure aggregators, AGP-only
|
|
100
99
|
// Android modules) contribute empty lists rather than failing.
|
|
101
100
|
// ALL source sets are emitted (not just main/test) so custom ones (integrationTest,
|
|
@@ -133,6 +132,7 @@ gradle.projectsEvaluated { g ->
|
|
|
133
132
|
sources: (sources as List).sort(),
|
|
134
133
|
targets: (targets as List).sort(),
|
|
135
134
|
buildFiles: buildFiles,
|
|
135
|
+
excluded: excluded,
|
|
136
136
|
])
|
|
137
137
|
}
|
|
138
138
|
}
|
|
@@ -157,13 +157,15 @@ allprojects { project ->
|
|
|
157
157
|
// visit/upsertNode closures capture them); `failures`/`scannedConfigs` stay shared.
|
|
158
158
|
def nodes = [:]
|
|
159
159
|
def directIds = [] as Set
|
|
160
|
+
def configName = ''
|
|
160
161
|
// "group:name:version" -> path of the build project this config resolved it to.
|
|
161
162
|
def projectByGav = [:]
|
|
162
163
|
def failures = state.failures
|
|
163
164
|
def scannedConfigs = state.scannedConfigs
|
|
164
|
-
def projectKeys = state.projectKeys
|
|
165
165
|
def projectArtifactExt = state.projectArtifactExt
|
|
166
166
|
def paths = [:]
|
|
167
|
+
// Ids of nodes whose requested artifact yielded no file.
|
|
168
|
+
def unfetched = [] as Set
|
|
167
169
|
def withFiles = gradle.socketProp.call(project, 'socket.withFiles')?.toString()?.toLowerCase() == 'true'
|
|
168
170
|
// Optional `-Psocket.populateFilesFor=<file>` (newline-delimited GAVs): scope `--with-files`
|
|
169
171
|
// materialization to those GAVs. This run walks EVERY resolvable config (not told the SBOM's
|
|
@@ -203,10 +205,6 @@ allprojects { project ->
|
|
|
203
205
|
[coord.groupId, coord.artifactId, coord.ext, coord.classifier, coord.version].findAll { it }.join(':')
|
|
204
206
|
}
|
|
205
207
|
|
|
206
|
-
def isIntraProject = { String group, String name ->
|
|
207
|
-
projectKeys.contains("${group ?: ''}:${name}")
|
|
208
|
-
}
|
|
209
|
-
|
|
210
208
|
// A first-party module dep can resolve with no published artifact (classes-dir variant, or
|
|
211
209
|
// variant-ambiguous moduleArtifacts), leaving it ext-less. Stamp the module's real artifact
|
|
212
210
|
// extension so it keeps a full, stable coordinate instead of an ext-less one.
|
|
@@ -234,6 +232,24 @@ allprojects { project ->
|
|
|
234
232
|
id
|
|
235
233
|
}
|
|
236
234
|
|
|
235
|
+
def causeMessages = { problem ->
|
|
236
|
+
def out = []
|
|
237
|
+
def t = problem
|
|
238
|
+
int guard = 0
|
|
239
|
+
while (t != null && guard++ < 12) { out << t.message; t = t.cause }
|
|
240
|
+
out
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The deepest cause's full message: the TS report classifies it, so nothing is categorized here.
|
|
244
|
+
def rawDetail = { problem ->
|
|
245
|
+
def messages = causeMessages(problem).findAll { it }
|
|
246
|
+
return (messages ? messages[-1] : 'unknown resolution failure').toString().trim()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
def recordFailure = { String coord, String detail ->
|
|
250
|
+
failures << [coord: coord, detail: detail, config: configName, project: project.path]
|
|
251
|
+
}
|
|
252
|
+
|
|
237
253
|
// Walk a resolved dependency and its transitive closure. We never touch `artifact.file` here —
|
|
238
254
|
// that forces Gradle to download the file (catastrophic on builds declaring distribution
|
|
239
255
|
// archives as deps); `artifact.extension`/`artifact.classifier` read already-resolved metadata.
|
|
@@ -251,15 +267,23 @@ allprojects { project ->
|
|
|
251
267
|
|
|
252
268
|
// `moduleArtifacts` forces ARTIFACT-level variant selection, which can throw on a
|
|
253
269
|
// variant-ambiguous dep even when the dependency GRAPH resolves fine. Guard per-dep: on
|
|
254
|
-
// failure
|
|
270
|
+
// failure keep walking so one ambiguous artifact can't sink the config.
|
|
271
|
+
def gav = "${dep.moduleGroup ?: ''}:${dep.moduleName}:${dep.moduleVersion ?: ''}".toString()
|
|
272
|
+
def shouldMaterialize = withFiles && (populateGavs == null || populateGavs.contains(gav))
|
|
273
|
+
def projPath = projectByGav[gav]
|
|
255
274
|
def artifacts
|
|
275
|
+
def artifactsError = null
|
|
256
276
|
try {
|
|
257
277
|
artifacts = dep.moduleArtifacts
|
|
258
278
|
} catch (Exception e) {
|
|
259
279
|
artifacts = [] as Set
|
|
280
|
+
artifactsError = e
|
|
260
281
|
}
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
if (artifactsError != null && projPath == null) {
|
|
283
|
+
// No artifact was selected, so there is no coordinate to emit: report the module and
|
|
284
|
+
// bubble its children up (below), as for a skipped first-party module.
|
|
285
|
+
recordFailure(gav, rawDetail(artifactsError))
|
|
286
|
+
} else if (artifacts.isEmpty()) {
|
|
263
287
|
def ext = effExt(projPath, '')
|
|
264
288
|
// Skip a no-artifact first-party module (aggregator / build root): it builds no archive, so
|
|
265
289
|
// it's fully described by `projects` and is never a real artifact dependency.
|
|
@@ -273,17 +297,6 @@ allprojects { project ->
|
|
|
273
297
|
], isProd, projPath)
|
|
274
298
|
}
|
|
275
299
|
} else {
|
|
276
|
-
// Build the GAV scope key only when a scope is set: the no-scope path never reads it and
|
|
277
|
-
// this is the hottest line under --parallel.
|
|
278
|
-
def shouldMaterialize
|
|
279
|
-
if (!withFiles) {
|
|
280
|
-
shouldMaterialize = false
|
|
281
|
-
} else if (populateGavs == null) {
|
|
282
|
-
shouldMaterialize = true
|
|
283
|
-
} else {
|
|
284
|
-
def gav = "${dep.moduleGroup ?: ''}:${dep.moduleName}:${dep.moduleVersion ?: ''}".toString()
|
|
285
|
-
shouldMaterialize = populateGavs.contains(gav)
|
|
286
|
-
}
|
|
287
300
|
artifacts.each { a ->
|
|
288
301
|
// Directory variants (java-classes-directory etc.) carry no extension; for a first-party
|
|
289
302
|
// module fall back to its real artifact ext (never artifact.type, a Gradle variant attr).
|
|
@@ -310,7 +323,13 @@ allprojects { project ->
|
|
|
310
323
|
set << path
|
|
311
324
|
}
|
|
312
325
|
} catch (Exception e) {
|
|
313
|
-
println "[socket-facts] could not materialize ${aid}: ${e.
|
|
326
|
+
println "[socket-facts] could not materialize ${aid}: ${rawDetail(e).readLines().find()}"
|
|
327
|
+
// A requested artifact that yields no file is a failure: without one, the dependency
|
|
328
|
+
// would read as resolved with nothing to analyse.
|
|
329
|
+
if (projPath == null) {
|
|
330
|
+
recordFailure(gav, rawDetail(e))
|
|
331
|
+
unfetched << aid
|
|
332
|
+
}
|
|
314
333
|
}
|
|
315
334
|
}
|
|
316
335
|
}
|
|
@@ -320,9 +339,9 @@ allprojects { project ->
|
|
|
320
339
|
dep.children.each { child ->
|
|
321
340
|
childIds.addAll(visit(child, isProd, cache))
|
|
322
341
|
}
|
|
323
|
-
// A skipped no-artifact first-party module
|
|
324
|
-
//
|
|
325
|
-
//
|
|
342
|
+
// A skipped no-artifact first-party module, or a module whose artifacts could not be
|
|
343
|
+
// selected, produced no node, so bubble its resolved children up to the parent (return
|
|
344
|
+
// childIds); otherwise the module's transitives would be attached to nothing.
|
|
326
345
|
if (producedIds.isEmpty()) {
|
|
327
346
|
cache[dep] = childIds
|
|
328
347
|
return childIds
|
|
@@ -408,13 +427,168 @@ allprojects { project ->
|
|
|
408
427
|
return true
|
|
409
428
|
}
|
|
410
429
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
430
|
+
// A failed edge is reported by the selector Gradle ATTEMPTED (after forces and substitutions).
|
|
431
|
+
// A project of this build is named by its own coordinate, as its resolved node would be.
|
|
432
|
+
def selectorCoord = { sel ->
|
|
433
|
+
if (sel instanceof org.gradle.api.artifacts.component.ModuleComponentSelector) {
|
|
434
|
+
return "${sel.group}:${sel.module}${sel.version ? ':' + sel.version : ''}".toString()
|
|
435
|
+
}
|
|
436
|
+
if (sel instanceof org.gradle.api.artifacts.component.ProjectComponentSelector) {
|
|
437
|
+
def build = sel.metaClass.respondsTo(sel, 'getBuildPath') ? sel.buildPath :
|
|
438
|
+
sel.metaClass.respondsTo(sel, 'getBuildName') ? sel.buildName : ':'
|
|
439
|
+
def gav = build == ':' ? state.projectGavByPath[sel.projectPath] : null
|
|
440
|
+
if (gav != null) {
|
|
441
|
+
return gav
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
sel.displayName.toString()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Gradle verifies only artifact-set reads, never the walk's `ResolvedArtifact.file`, and fails
|
|
448
|
+
// them with every fatal failure so far; only STRICT throws, from 6.2, with the root build's metadata.
|
|
449
|
+
def rootBuild = gradle
|
|
450
|
+
while (rootBuild.parent != null) {
|
|
451
|
+
rootBuild = rootBuild.parent
|
|
452
|
+
}
|
|
453
|
+
def startParameter = gradle.startParameter
|
|
454
|
+
def verifyArtifacts = withFiles &&
|
|
455
|
+
new File(rootBuild.rootProject.rootDir, 'gradle/verification-metadata.xml').isFile() &&
|
|
456
|
+
startParameter.metaClass.respondsTo(startParameter, 'getDependencyVerificationMode') &&
|
|
457
|
+
startParameter.dependencyVerificationMode.toString() == 'STRICT'
|
|
458
|
+
def gavOf = { Map c -> "${c.groupId}:${c.artifactId}:${c.version}".toString() }
|
|
459
|
+
def verificationMessage = { Throwable e ->
|
|
460
|
+
causeMessages(e).find { it?.startsWith('Dependency verification failed') }
|
|
461
|
+
}
|
|
462
|
+
// Old Gradle splits a module into one ResolvedDependency per target configuration, of which only
|
|
463
|
+
// some carry its artifacts: an artifact-less one is the same component, so fold it into them.
|
|
464
|
+
def foldArtifactless = {
|
|
465
|
+
if (!nodes.values().any { n -> !n.coord.ext && n.project == null }) {
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
def withExt = [:]
|
|
469
|
+
nodes.each { id, n ->
|
|
470
|
+
if (n.coord.ext) {
|
|
471
|
+
withExt.get(gavOf(n.coord), []) << id
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
def folds = [:]
|
|
475
|
+
nodes.each { id, n ->
|
|
476
|
+
def siblings = (!n.coord.ext && n.project == null) ? withExt[gavOf(n.coord)] : null
|
|
477
|
+
if (siblings) {
|
|
478
|
+
folds[id] = siblings
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (folds.isEmpty()) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
def replaced = { Collection ids -> ids.collectMany { c -> folds[c] ?: [c] } }
|
|
485
|
+
folds.each { id, siblings ->
|
|
486
|
+
def folded = nodes.remove(id)
|
|
487
|
+
siblings.each { sid ->
|
|
488
|
+
nodes[sid].children.addAll(replaced(folded.children).findAll { it != sid })
|
|
489
|
+
if (folded.prod) {
|
|
490
|
+
nodes[sid].prod = true
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
nodes.each { nid, n ->
|
|
495
|
+
if (n.children.any { folds.containsKey(it) }) {
|
|
496
|
+
def children = replaced(n.children).findAll { it != nid }
|
|
497
|
+
n.children.clear()
|
|
498
|
+
n.children.addAll(children)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
def direct = replaced(directIds)
|
|
502
|
+
directIds.clear()
|
|
503
|
+
directIds.addAll(direct)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Drops the files of this config's artifacts that failed verification and reports them.
|
|
507
|
+
def checkVerification = { cfg ->
|
|
508
|
+
def external = paths.keySet().findAll { id -> nodes[id].project == null }
|
|
509
|
+
if (external.isEmpty()) {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
def fetched = external.collect { id -> gavOf(nodes[id].coord) } as Set
|
|
513
|
+
def message = null
|
|
514
|
+
try {
|
|
515
|
+
cfg.incoming.artifactView {
|
|
516
|
+
lenient(true)
|
|
517
|
+
componentFilter { id ->
|
|
518
|
+
id instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier &&
|
|
519
|
+
fetched.contains("${id.group}:${id.module}:${id.version}".toString())
|
|
520
|
+
}
|
|
521
|
+
}.artifacts.each { it.file }
|
|
522
|
+
} catch (Exception e) {
|
|
523
|
+
message = verificationMessage(e)
|
|
524
|
+
if (message == null) {
|
|
525
|
+
// Any other failure concerns files the walk already read without it.
|
|
526
|
+
println "[socket-facts] could not check artifact verification for ${cfg.name}: ${rawDetail(e).readLines().find()}"
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (message == null) {
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
// Each failed artifact is named by its identifier's display name, "<file> (<group>:<module>:<version>)",
|
|
533
|
+
// whose file part is `<name>-<version>[-<classifier>].<ext>` rather than the cached file's own name.
|
|
534
|
+
def artifactRef = /(\S+) \(([^\s():]+):([^\s():]+):([^\s()]+)\)/
|
|
535
|
+
def lines = message.readLines()
|
|
536
|
+
def header = lines ? lines[0] : ''
|
|
537
|
+
// The verbose report (the Socket CLI sets org.gradle.dependency.verification.console=verbose) gives
|
|
538
|
+
// each artifact's problems: "On artifact <ref> in repository '<repo>': <problem>", or "On artifact
|
|
539
|
+
// <ref> multiple problems reported:" followed by one "in repository '<repo>': <problem>" line each.
|
|
540
|
+
def failed = []
|
|
541
|
+
def current = null
|
|
542
|
+
lines.each { raw ->
|
|
543
|
+
def line = raw.trim().replaceFirst(/^- /, '')
|
|
544
|
+
def m = line =~ ('^On artifact ' + artifactRef + ' (.*)$')
|
|
545
|
+
if (m.matches()) {
|
|
546
|
+
current = [gav: "${m.group(2)}:${m.group(3)}:${m.group(4)}".toString(), file: m.group(1), problems: []]
|
|
547
|
+
failed << current
|
|
548
|
+
if (m.group(5) != 'multiple problems reported:') {
|
|
549
|
+
current.problems << m.group(5)
|
|
550
|
+
}
|
|
551
|
+
} else if (current != null && line.startsWith("in repository '")) {
|
|
552
|
+
current.problems << line
|
|
553
|
+
} else {
|
|
554
|
+
current = null
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
failed.each { f ->
|
|
558
|
+
f.detail = ([header] + f.problems.collect { "On artifact ${f.file} (${f.gav}) ${it}" }).join('\n')
|
|
559
|
+
}
|
|
560
|
+
if (failed.isEmpty()) {
|
|
561
|
+
// The summary report names only the artifacts.
|
|
562
|
+
(message =~ artifactRef).each { m ->
|
|
563
|
+
failed << [gav: "${m[2]}:${m[3]}:${m[4]}".toString(), file: m[1], detail: message]
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
def markUnfetched = { Collection ids ->
|
|
567
|
+
ids.each { id ->
|
|
568
|
+
paths.remove(id)
|
|
569
|
+
unfetched << id
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (failed.isEmpty()) {
|
|
573
|
+
// No artifact can be told apart: the walked graph stands, without any of its external files.
|
|
574
|
+
state.unscannable << [config: cfg.name, detail: message, project: project.path]
|
|
575
|
+
markUnfetched(external)
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
failed.each { f ->
|
|
579
|
+
def ids = external.findAll { id -> gavOf(nodes[id].coord) == f.gav }
|
|
580
|
+
// A failed metadata file (pom, module) names none of the module's artifacts: all are suspect.
|
|
581
|
+
// A failed artifact this config did not fetch leaves it untouched.
|
|
582
|
+
def hit = (f.file.endsWith('.pom') || f.file.endsWith('.module')) ? ids : ids.findAll { id ->
|
|
583
|
+
def c = nodes[id].coord
|
|
584
|
+
def named = "${c.artifactId}-${c.version}${c.classifier ? '-' + c.classifier : ''}${c.ext ? '.' + c.ext : ''}"
|
|
585
|
+
f.file == named.toString() || paths[id]?.any { p -> f.file == new File(p).name }
|
|
586
|
+
}
|
|
587
|
+
if (!hit.isEmpty()) {
|
|
588
|
+
recordFailure(f.gav, f.detail)
|
|
589
|
+
markUnfetched(hit)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
418
592
|
}
|
|
419
593
|
|
|
420
594
|
targetConfigs.each { cfg ->
|
|
@@ -426,10 +600,13 @@ allprojects { project ->
|
|
|
426
600
|
// Fresh tree for THIS resolution root — captured by visit/upsertNode, so no union with siblings.
|
|
427
601
|
nodes = [:]
|
|
428
602
|
directIds = [] as Set
|
|
603
|
+
configName = cfg.name
|
|
429
604
|
paths = [:]
|
|
605
|
+
unfetched = [] as Set
|
|
430
606
|
projectByGav = [:]
|
|
431
607
|
// Per-config try/catch: AGP-style configs can fail variant ambiguity from an init-script
|
|
432
608
|
// context lacking the consumer attributes AGP sets internally.
|
|
609
|
+
def walked = false
|
|
433
610
|
try {
|
|
434
611
|
def lenient = cfg.resolvedConfiguration.lenientConfiguration
|
|
435
612
|
// The resolution graph names a project dependency by project path; within one graph a
|
|
@@ -453,31 +630,37 @@ allprojects { project ->
|
|
|
453
630
|
firstLevel.each { dep ->
|
|
454
631
|
directIds.addAll(visit(dep, isProd, cache))
|
|
455
632
|
}
|
|
633
|
+
foldArtifactless()
|
|
456
634
|
// Unresolved deps drive the root aggregator's abort/warn decision but are NOT emitted as
|
|
457
635
|
// nodes — their selector-only coordinates would surface as half-formed phantom artifacts.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
636
|
+
// hasError() covers every unresolved dependency, project ones included.
|
|
637
|
+
if (cfg.resolvedConfiguration.hasError()) {
|
|
638
|
+
def reported = [] as Set
|
|
639
|
+
cfg.incoming.resolutionResult.allDependencies.each { d ->
|
|
640
|
+
if (d instanceof org.gradle.api.artifacts.result.UnresolvedDependencyResult) {
|
|
641
|
+
def coord = selectorCoord(d.attempted ?: d.requested)
|
|
642
|
+
if (reported.add(coord)) {
|
|
643
|
+
recordFailure(coord, rawDetail(d.failure))
|
|
644
|
+
}
|
|
645
|
+
}
|
|
462
646
|
}
|
|
463
|
-
failures << [
|
|
464
|
-
coord : "${sel.group}:${sel.name}${sel.version ? ':' + sel.version : ''}".toString(),
|
|
465
|
-
detail: rawDetail(dep.problem),
|
|
466
|
-
config: cfg.name,
|
|
467
|
-
]
|
|
468
647
|
}
|
|
648
|
+
if (verifyArtifacts) {
|
|
649
|
+
checkVerification(cfg)
|
|
650
|
+
}
|
|
651
|
+
walked = true
|
|
469
652
|
} catch (Exception e) {
|
|
470
653
|
// A config-LEVEL resolution failure (a resolutionStrategy/beforeResolve rule that throws,
|
|
471
654
|
// an artifact-transform failure, a broken repository, ...) rather than a per-dep failure.
|
|
472
655
|
// Record it instead of swallowing; the build still exits cleanly and the TS layer surfaces it.
|
|
473
|
-
state.unscannable << [config: cfg.name, detail: rawDetail(e)]
|
|
656
|
+
state.unscannable << [config: cfg.name, detail: rawDetail(e), project: project.path]
|
|
474
657
|
}
|
|
475
658
|
// Stash this resolution root's tree; the key only has to be unique per root. Skip empty
|
|
476
|
-
// configs
|
|
477
|
-
if (!nodes.isEmpty()) {
|
|
659
|
+
// configs, which contribute nothing, and a partial walk, which can orphan components.
|
|
660
|
+
if (walked && !nodes.isEmpty()) {
|
|
478
661
|
synchronized (state.perSub) {
|
|
479
662
|
state.perSub["${project.path}::${cfg.name}".toString()] =
|
|
480
|
-
[nodes: nodes, direct: directIds, paths: paths, projectPath: project.path, config: cfg.name, prod: isProd]
|
|
663
|
+
[nodes: nodes, direct: directIds, paths: paths, unfetched: unfetched, projectPath: project.path, config: cfg.name, prod: isProd]
|
|
481
664
|
}
|
|
482
665
|
}
|
|
483
666
|
}
|
|
@@ -517,8 +700,12 @@ rootProject { rp ->
|
|
|
517
700
|
def projectsInfo
|
|
518
701
|
synchronized (state.projectsInfo) { projectsInfo = new ArrayList(state.projectsInfo) }
|
|
519
702
|
projectsInfo.each { pi ->
|
|
520
|
-
|
|
521
|
-
|
|
703
|
+
if (pi.excluded) {
|
|
704
|
+
if (withFilesProjects) rec(['excludedProject', pi.path])
|
|
705
|
+
} else {
|
|
706
|
+
rec(['project', pi.path, pi.group ?: '', pi.name, pi.version ?: '', pi.dir])
|
|
707
|
+
pi.buildFiles.each { f -> rec(['projectBuild', pi.path, f]) }
|
|
708
|
+
}
|
|
522
709
|
if (withFilesProjects) {
|
|
523
710
|
pi.sources.each { s -> rec(['projectSrc', pi.path, s]) }
|
|
524
711
|
pi.targets.each { t -> rec(['projectTgt', pi.path, t]) }
|
|
@@ -540,6 +727,7 @@ rootProject { rp ->
|
|
|
540
727
|
node.children.each { childId -> rec(['edge', rootId, coordId, childId]) }
|
|
541
728
|
def fs = tree.paths[coordId]
|
|
542
729
|
if (fs) { (fs as List).sort().each { p -> rec(['file', rootId, coordId, p]) } }
|
|
730
|
+
if (tree.unfetched.contains(coordId)) rec(['unfetched', rootId, coordId])
|
|
543
731
|
}
|
|
544
732
|
}
|
|
545
733
|
|
|
@@ -549,10 +737,10 @@ rootProject { rp ->
|
|
|
549
737
|
scanned.each { name -> rec(['scanned', name]) }
|
|
550
738
|
def failures
|
|
551
739
|
synchronized (state.failures) { failures = new ArrayList(state.failures) }
|
|
552
|
-
failures.each { f -> rec(['failure', f.coord, f.detail, f.config]) }
|
|
740
|
+
failures.each { f -> rec(['failure', f.coord, f.detail, f.config, f.project]) }
|
|
553
741
|
def unscannable
|
|
554
742
|
synchronized (state.unscannable) { unscannable = new ArrayList(state.unscannable) }
|
|
555
|
-
unscannable.each { u -> rec(['unscannable', u.config, u.detail]) }
|
|
743
|
+
unscannable.each { u -> rec(['unscannable', u.config, u.detail, u.project]) }
|
|
556
744
|
|
|
557
745
|
def outFile = new File(recordsFileOverride ?: defaultRecordsFile)
|
|
558
746
|
outFile.parentFile?.mkdirs()
|