ecosystems-bibliothecary 15.2.0 → 15.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/README.md +57 -1
- data/lib/bibliothecary/dependency.rb +6 -1
- data/lib/bibliothecary/parsers/alpm.rb +89 -0
- data/lib/bibliothecary/parsers/apk.rb +91 -0
- data/lib/bibliothecary/parsers/bazel.rb +65 -0
- data/lib/bibliothecary/parsers/bentoml.rb +1 -1
- data/lib/bibliothecary/parsers/bower.rb +1 -0
- data/lib/bibliothecary/parsers/cargo.rb +3 -1
- data/lib/bibliothecary/parsers/clojars.rb +1 -0
- data/lib/bibliothecary/parsers/cocoapods.rb +29 -1
- data/lib/bibliothecary/parsers/cog.rb +1 -1
- data/lib/bibliothecary/parsers/conda.rb +2 -0
- data/lib/bibliothecary/parsers/deb.rb +132 -0
- data/lib/bibliothecary/parsers/deno.rb +15 -1
- data/lib/bibliothecary/parsers/dub.rb +2 -0
- data/lib/bibliothecary/parsers/dvc.rb +1 -1
- data/lib/bibliothecary/parsers/go.rb +4 -2
- data/lib/bibliothecary/parsers/hackage.rb +4 -3
- data/lib/bibliothecary/parsers/haxelib.rb +1 -0
- data/lib/bibliothecary/parsers/hex.rb +22 -7
- data/lib/bibliothecary/parsers/luarocks.rb +1 -0
- data/lib/bibliothecary/parsers/meteor.rb +1 -0
- data/lib/bibliothecary/parsers/mlflow.rb +1 -1
- data/lib/bibliothecary/parsers/nimble.rb +1 -0
- data/lib/bibliothecary/parsers/npm.rb +81 -12
- data/lib/bibliothecary/parsers/ollama.rb +1 -1
- data/lib/bibliothecary/parsers/packagist.rb +28 -31
- data/lib/bibliothecary/parsers/pypi.rb +16 -2
- data/lib/bibliothecary/parsers/rpm.rb +80 -0
- data/lib/bibliothecary/parsers/rubygems.rb +34 -4
- data/lib/bibliothecary/version.rb +1 -1
- metadata +6 -1
|
@@ -52,16 +52,30 @@ module Bibliothecary
|
|
|
52
52
|
manifest = JSON.parse(file_contents)
|
|
53
53
|
source = options.fetch(:filename, nil)
|
|
54
54
|
|
|
55
|
+
# Build integrity lookup from jsr and npm sections
|
|
56
|
+
integrity_map = {}
|
|
57
|
+
manifest.fetch("jsr", {}).each do |key, value|
|
|
58
|
+
integrity_map["jsr:#{key}"] = value["integrity"] if value.is_a?(Hash) && value["integrity"]
|
|
59
|
+
end
|
|
60
|
+
manifest.fetch("npm", {}).each do |key, value|
|
|
61
|
+
integrity_map["npm:#{key}"] = value["integrity"] if value.is_a?(Hash) && value["integrity"]
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
dependencies = manifest.fetch("specifiers", {}).map do |specifier, resolved_version|
|
|
56
65
|
name, _requirement = parse_specifier(specifier)
|
|
57
66
|
next unless name
|
|
58
67
|
|
|
68
|
+
# Determine protocol (npm: or jsr:) and build lookup key
|
|
69
|
+
protocol = specifier.start_with?("jsr:") ? "jsr" : "npm"
|
|
70
|
+
integrity_key = "#{protocol}:#{name}@#{resolved_version}"
|
|
71
|
+
|
|
59
72
|
Dependency.new(
|
|
60
73
|
name: name,
|
|
61
74
|
requirement: resolved_version,
|
|
62
75
|
type: "runtime",
|
|
63
76
|
source: source,
|
|
64
|
-
platform: platform_name
|
|
77
|
+
platform: platform_name,
|
|
78
|
+
integrity: integrity_map[integrity_key]
|
|
65
79
|
)
|
|
66
80
|
end.compact
|
|
67
81
|
|
|
@@ -18,10 +18,12 @@ module Bibliothecary
|
|
|
18
18
|
match_filename("dub.json") => {
|
|
19
19
|
kind: "manifest",
|
|
20
20
|
parser: :parse_json_manifest,
|
|
21
|
+
can_have_lockfile: false,
|
|
21
22
|
},
|
|
22
23
|
match_filename("dub.sdl") => {
|
|
23
24
|
kind: "manifest",
|
|
24
25
|
parser: :parse_sdl_manifest,
|
|
26
|
+
can_have_lockfile: false,
|
|
25
27
|
},
|
|
26
28
|
}
|
|
27
29
|
end
|
|
@@ -209,10 +209,12 @@ module Bibliothecary
|
|
|
209
209
|
requirement: match[2].strip.split("/").first,
|
|
210
210
|
type: "runtime",
|
|
211
211
|
source: options.fetch(:filename, nil),
|
|
212
|
-
platform: platform_name
|
|
212
|
+
platform: platform_name,
|
|
213
|
+
integrity: match[3].strip
|
|
213
214
|
)
|
|
214
215
|
end
|
|
215
|
-
|
|
216
|
+
# Dedupe by name+requirement, keeping the first occurrence (h1 hash, not go.mod hash)
|
|
217
|
+
dependencies = deps.uniq { |d| [d.name, d.requirement] }
|
|
216
218
|
ParserResult.new(dependencies: dependencies)
|
|
217
219
|
end
|
|
218
220
|
|
|
@@ -12,8 +12,8 @@ module Bibliothecary
|
|
|
12
12
|
# Matches build-tool-depends format: package:tool == version
|
|
13
13
|
BUILD_TOOL_REGEXP = /^\s*([a-zA-Z][a-zA-Z0-9-]*):[a-zA-Z][a-zA-Z0-9-]*\s*((?:[<>=!]+\s*[\d.*]+(?:\s*&&\s*[<>=!]+\s*[\d.*]+)*)?)/
|
|
14
14
|
|
|
15
|
-
# Matches stack.yaml.lock hackage entries like: hackage: fuzzyset-0.2.4@sha256
|
|
16
|
-
STACK_LOCK_REGEXP = /hackage:\s*([a-zA-Z0-9-]+)-([0-9.]+)
|
|
15
|
+
# Matches stack.yaml.lock hackage entries like: hackage: fuzzyset-0.2.4@sha256:hash,size
|
|
16
|
+
STACK_LOCK_REGEXP = /hackage:\s*([a-zA-Z0-9-]+)-([0-9.]+)@sha256:([a-f0-9]+)/
|
|
17
17
|
|
|
18
18
|
def self.file_patterns
|
|
19
19
|
["*.cabal", "*cabal.config", "stack.yaml.lock", "cabal.project.freeze"]
|
|
@@ -198,7 +198,8 @@ module Bibliothecary
|
|
|
198
198
|
name: match[1],
|
|
199
199
|
requirement: match[2],
|
|
200
200
|
type: "runtime",
|
|
201
|
-
source: source
|
|
201
|
+
source: source,
|
|
202
|
+
integrity: "sha256=#{match[3]}"
|
|
202
203
|
)
|
|
203
204
|
end
|
|
204
205
|
|
|
@@ -5,13 +5,15 @@ module Bibliothecary
|
|
|
5
5
|
class Hex
|
|
6
6
|
include Bibliothecary::Analyser
|
|
7
7
|
|
|
8
|
-
# Matches mix.lock entries: "name": {:hex, :name, "version", ...
|
|
8
|
+
# Matches mix.lock entries: "name": {:hex, :name, "version", "hash", ...
|
|
9
9
|
# or "name": {:git, "url", "ref", ...
|
|
10
|
-
HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\s*"([^"]+)"/
|
|
10
|
+
HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\s*"([^"]+)",\s*"([^"]+)"/
|
|
11
11
|
GIT_LOCK_REGEXP = /"([^"]+)":\s*\{:git,\s*"([^"]+)",\s*"([^"]+)"/
|
|
12
12
|
|
|
13
13
|
# Matches rebar.lock entries: {<<"name">>,{pkg,<<"name">>,<<"version">>},N}
|
|
14
14
|
REBAR_LOCK_REGEXP = /\{<<"([^"]+)">>,\{pkg,<<"[^"]+">>,<<"([^"]+)">>},\d+\}/
|
|
15
|
+
# Matches rebar.lock pkg_hash entries: {<<"name">>, <<"HASH">>}
|
|
16
|
+
REBAR_PKG_HASH_REGEXP = /\{<<"([^"]+)">>,\s*<<"([A-F0-9]+)">>}/
|
|
15
17
|
|
|
16
18
|
def self.file_patterns
|
|
17
19
|
["mix.exs", "mix.lock", "gleam.toml", "manifest.toml", "rebar.lock"]
|
|
@@ -74,14 +76,15 @@ module Bibliothecary
|
|
|
74
76
|
source = options.fetch(:filename, "mix.lock")
|
|
75
77
|
deps = []
|
|
76
78
|
|
|
77
|
-
# Match hex packages: "name": {:hex, :name, "version", ...
|
|
78
|
-
file_contents.scan(HEX_LOCK_REGEXP) do |name, version|
|
|
79
|
+
# Match hex packages: "name": {:hex, :name, "version", "hash", ...
|
|
80
|
+
file_contents.scan(HEX_LOCK_REGEXP) do |name, version, hash|
|
|
79
81
|
deps << Dependency.new(
|
|
80
82
|
platform: platform_name,
|
|
81
83
|
name: name,
|
|
82
84
|
requirement: version,
|
|
83
85
|
type: "runtime",
|
|
84
|
-
source: source
|
|
86
|
+
source: source,
|
|
87
|
+
integrity: "sha256=#{hash}"
|
|
85
88
|
)
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -135,12 +138,14 @@ module Bibliothecary
|
|
|
135
138
|
manifest.fetch("packages", []).each do |pkg|
|
|
136
139
|
next unless pkg["source"] == "hex"
|
|
137
140
|
|
|
141
|
+
checksum = pkg["outer_checksum"]
|
|
138
142
|
deps << Dependency.new(
|
|
139
143
|
platform: platform_name,
|
|
140
144
|
name: pkg["name"],
|
|
141
145
|
requirement: pkg["version"],
|
|
142
146
|
type: "runtime",
|
|
143
|
-
source: source
|
|
147
|
+
source: source,
|
|
148
|
+
integrity: checksum ? "sha256=#{checksum.downcase}" : nil
|
|
144
149
|
)
|
|
145
150
|
end
|
|
146
151
|
|
|
@@ -151,13 +156,23 @@ module Bibliothecary
|
|
|
151
156
|
source = options.fetch(:filename, "rebar.lock")
|
|
152
157
|
deps = []
|
|
153
158
|
|
|
159
|
+
# Parse pkg_hash section to build lookup table
|
|
160
|
+
pkg_hashes = {}
|
|
161
|
+
pkg_hash_section = file_contents[/\{pkg_hash,\s*\[(.*?)\]\}/m, 1]
|
|
162
|
+
if pkg_hash_section
|
|
163
|
+
pkg_hash_section.scan(REBAR_PKG_HASH_REGEXP) do |name, hash|
|
|
164
|
+
pkg_hashes[name] = "sha256=#{hash.downcase}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
154
168
|
file_contents.scan(REBAR_LOCK_REGEXP) do |name, version|
|
|
155
169
|
deps << Dependency.new(
|
|
156
170
|
platform: platform_name,
|
|
157
171
|
name: name,
|
|
158
172
|
requirement: version,
|
|
159
173
|
type: "runtime",
|
|
160
|
-
source: source
|
|
174
|
+
source: source,
|
|
175
|
+
integrity: pkg_hashes[name]
|
|
161
176
|
)
|
|
162
177
|
end
|
|
163
178
|
|
|
@@ -11,7 +11,7 @@ module Bibliothecary
|
|
|
11
11
|
PACKAGE_LOCK_JSON_MAX_DEPTH = 10
|
|
12
12
|
|
|
13
13
|
def self.file_patterns
|
|
14
|
-
["package.json", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml", "bun.lock", "npm-ls.json"]
|
|
14
|
+
["package.json", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml", "pnpm-workspace.yaml", "bun.lock", "npm-ls.json"]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def self.mapping
|
|
@@ -32,6 +32,11 @@ module Bibliothecary
|
|
|
32
32
|
kind: "lockfile",
|
|
33
33
|
parser: :parse_pnpm_lock,
|
|
34
34
|
},
|
|
35
|
+
match_filename("pnpm-workspace.yaml") => {
|
|
36
|
+
kind: "manifest",
|
|
37
|
+
parser: :parse_pnpm_workspace,
|
|
38
|
+
related_to: ["lockfile"],
|
|
39
|
+
},
|
|
35
40
|
match_filename("npm-ls.json") => {
|
|
36
41
|
kind: "lockfile",
|
|
37
42
|
parser: :parse_ls,
|
|
@@ -101,7 +106,8 @@ module Bibliothecary
|
|
|
101
106
|
type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime",
|
|
102
107
|
local: dep.fetch("link", false),
|
|
103
108
|
source: source,
|
|
104
|
-
platform: platform_name
|
|
109
|
+
platform: platform_name,
|
|
110
|
+
integrity: dep["integrity"]
|
|
105
111
|
)
|
|
106
112
|
end
|
|
107
113
|
end
|
|
@@ -122,7 +128,8 @@ module Bibliothecary
|
|
|
122
128
|
requirement: version,
|
|
123
129
|
type: type,
|
|
124
130
|
source: source,
|
|
125
|
-
platform: platform_name
|
|
131
|
+
platform: platform_name,
|
|
132
|
+
integrity: requirement["integrity"]
|
|
126
133
|
)] + child_dependencies
|
|
127
134
|
end
|
|
128
135
|
end
|
|
@@ -191,7 +198,8 @@ module Bibliothecary
|
|
|
191
198
|
type: nil, # yarn.lock doesn't report on the type of dependency
|
|
192
199
|
local: dep[:requirements]&.first&.start_with?("file:"),
|
|
193
200
|
source: options.fetch(:filename, nil),
|
|
194
|
-
platform: platform_name
|
|
201
|
+
platform: platform_name,
|
|
202
|
+
integrity: dep[:integrity]
|
|
195
203
|
)
|
|
196
204
|
end
|
|
197
205
|
ParserResult.new(dependencies: dependencies)
|
|
@@ -232,11 +240,23 @@ module Bibliothecary
|
|
|
232
240
|
|
|
233
241
|
def self.parse_v2_yarn_lock(contents, source = nil)
|
|
234
242
|
deps = []
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
contents.
|
|
243
|
+
# Split into blocks by double newlines or by unquoted key lines
|
|
244
|
+
# Each block starts with "package@npm:req": and continues until the next package
|
|
245
|
+
blocks = contents.split(/\n\n+/)
|
|
246
|
+
|
|
247
|
+
blocks.each do |block|
|
|
248
|
+
# Match the package header: "package@npm:...":\n version: ...
|
|
249
|
+
match = block.match(/^"([^"]+)":\s*\n(.+)/m)
|
|
250
|
+
next unless match
|
|
251
|
+
|
|
252
|
+
packages_str = match[1]
|
|
253
|
+
body = match[2]
|
|
254
|
+
|
|
255
|
+
version = body[/version:\s*([^\n]+)/, 1]
|
|
256
|
+
checksum = body[/checksum:\s*([^\n]+)/, 1]
|
|
257
|
+
|
|
238
258
|
# Skip workspace/local packages and patches
|
|
239
|
-
next if version
|
|
259
|
+
next if version&.include?("use.local") && packages_str.include?("workspace")
|
|
240
260
|
next if packages_str.include?("@patch:")
|
|
241
261
|
|
|
242
262
|
packages = packages_str.split(", ")
|
|
@@ -251,6 +271,7 @@ module Bibliothecary
|
|
|
251
271
|
original_requirement: alias_name.nil? ? nil : version.to_s,
|
|
252
272
|
version: version.to_s,
|
|
253
273
|
source: source,
|
|
274
|
+
integrity: checksum,
|
|
254
275
|
}
|
|
255
276
|
end
|
|
256
277
|
deps
|
|
@@ -285,7 +306,8 @@ module Bibliothecary
|
|
|
285
306
|
original_requirement: original_requirement,
|
|
286
307
|
type: is_dev ? "development" : "runtime",
|
|
287
308
|
source: source,
|
|
288
|
-
platform: platform_name
|
|
309
|
+
platform: platform_name,
|
|
310
|
+
integrity: details.dig("resolution", "integrity")
|
|
289
311
|
)
|
|
290
312
|
end
|
|
291
313
|
end
|
|
@@ -322,7 +344,8 @@ module Bibliothecary
|
|
|
322
344
|
original_requirement: original_requirement,
|
|
323
345
|
type: is_dev ? "development" : "runtime",
|
|
324
346
|
source: source,
|
|
325
|
-
platform: platform_name
|
|
347
|
+
platform: platform_name,
|
|
348
|
+
integrity: details.dig("resolution", "integrity")
|
|
326
349
|
)
|
|
327
350
|
end
|
|
328
351
|
end
|
|
@@ -331,6 +354,7 @@ module Bibliothecary
|
|
|
331
354
|
dependencies = parsed_contents.fetch("importers", {}).fetch(".", {}).fetch("dependencies", {})
|
|
332
355
|
dev_dependencies = parsed_contents.fetch("importers", {}).fetch(".", {}).fetch("devDependencies", {})
|
|
333
356
|
dependency_mapping = dependencies.merge(dev_dependencies)
|
|
357
|
+
packages = parsed_contents.fetch("packages", {})
|
|
334
358
|
|
|
335
359
|
# "dependencies" is in "packages" for < v9 and in "snapshots" for >= v9
|
|
336
360
|
# as of https://github.com/pnpm/pnpm/pull/7700.
|
|
@@ -364,6 +388,10 @@ module Bibliothecary
|
|
|
364
388
|
dev_name == name && dev_details["version"] == version
|
|
365
389
|
end
|
|
366
390
|
|
|
391
|
+
# In v9, integrity is stored in packages section, not snapshots
|
|
392
|
+
package_key = "#{name}@#{version}"
|
|
393
|
+
integrity = packages.dig(package_key, "resolution", "integrity")
|
|
394
|
+
|
|
367
395
|
Dependency.new(
|
|
368
396
|
name: name,
|
|
369
397
|
requirement: version,
|
|
@@ -371,7 +399,8 @@ module Bibliothecary
|
|
|
371
399
|
original_requirement: original_requirement,
|
|
372
400
|
type: is_dev ? "development" : "runtime",
|
|
373
401
|
source: source,
|
|
374
|
-
platform: platform_name
|
|
402
|
+
platform: platform_name,
|
|
403
|
+
integrity: integrity
|
|
375
404
|
)
|
|
376
405
|
end
|
|
377
406
|
end
|
|
@@ -395,6 +424,45 @@ module Bibliothecary
|
|
|
395
424
|
ParserResult.new(dependencies: dependencies)
|
|
396
425
|
end
|
|
397
426
|
|
|
427
|
+
def self.parse_pnpm_workspace(contents, options: {})
|
|
428
|
+
parsed = YAML.load(contents)
|
|
429
|
+
source = options.fetch(:filename, nil)
|
|
430
|
+
|
|
431
|
+
dependencies = []
|
|
432
|
+
|
|
433
|
+
# Parse the default catalog (pnpm 9+)
|
|
434
|
+
if parsed["catalog"].is_a?(Hash)
|
|
435
|
+
parsed["catalog"].each do |name, requirement|
|
|
436
|
+
dependencies << Dependency.new(
|
|
437
|
+
name: name,
|
|
438
|
+
requirement: requirement,
|
|
439
|
+
type: "runtime",
|
|
440
|
+
source: source,
|
|
441
|
+
platform: platform_name
|
|
442
|
+
)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Parse named catalogs (pnpm 9+)
|
|
447
|
+
if parsed["catalogs"].is_a?(Hash)
|
|
448
|
+
parsed["catalogs"].each do |_catalog_name, catalog_deps|
|
|
449
|
+
next unless catalog_deps.is_a?(Hash)
|
|
450
|
+
|
|
451
|
+
catalog_deps.each do |name, requirement|
|
|
452
|
+
dependencies << Dependency.new(
|
|
453
|
+
name: name,
|
|
454
|
+
requirement: requirement,
|
|
455
|
+
type: "runtime",
|
|
456
|
+
source: source,
|
|
457
|
+
platform: platform_name
|
|
458
|
+
)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
ParserResult.new(dependencies: dependencies)
|
|
464
|
+
end
|
|
465
|
+
|
|
398
466
|
def self.parse_ls(file_contents, options: {})
|
|
399
467
|
manifest = JSON.parse(file_contents)
|
|
400
468
|
|
|
@@ -425,7 +493,8 @@ module Bibliothecary
|
|
|
425
493
|
type: dev_deps&.include?(name) ? "development" : "runtime",
|
|
426
494
|
local: is_local,
|
|
427
495
|
source: source,
|
|
428
|
-
platform: platform_name
|
|
496
|
+
platform: platform_name,
|
|
497
|
+
integrity: info[3]
|
|
429
498
|
)
|
|
430
499
|
end
|
|
431
500
|
ParserResult.new(dependencies: dependencies)
|
|
@@ -27,42 +27,39 @@ module Bibliothecary
|
|
|
27
27
|
|
|
28
28
|
def self.parse_lockfile(file_contents, options: {})
|
|
29
29
|
manifest = JSON.parse file_contents
|
|
30
|
-
|
|
31
|
-
requirement = dependency["version"]
|
|
32
|
-
|
|
33
|
-
# Store Drupal version if Drupal, but include the original manifest version for reference
|
|
34
|
-
if drupal_module?(dependency)
|
|
35
|
-
original_requirement = requirement
|
|
36
|
-
requirement = dependency.dig("source", "reference")
|
|
37
|
-
end
|
|
30
|
+
source = options.fetch(:filename, nil)
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
requirement: requirement,
|
|
42
|
-
type: "runtime",
|
|
43
|
-
original_requirement: original_requirement,
|
|
44
|
-
source: options.fetch(:filename, nil),
|
|
45
|
-
platform: platform_name
|
|
46
|
-
)
|
|
32
|
+
dependencies = manifest.fetch("packages", []).map do |dependency|
|
|
33
|
+
parse_composer_dependency(dependency, "runtime", source)
|
|
47
34
|
end + manifest.fetch("packages-dev", []).map do |dependency|
|
|
48
|
-
|
|
35
|
+
parse_composer_dependency(dependency, "development", source)
|
|
36
|
+
end
|
|
37
|
+
ParserResult.new(dependencies: dependencies)
|
|
38
|
+
end
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
requirement = dependency.dig("source", "reference")
|
|
54
|
-
end
|
|
40
|
+
def self.parse_composer_dependency(dependency, type, source)
|
|
41
|
+
requirement = dependency["version"]
|
|
42
|
+
original_requirement = nil
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
original_requirement: original_requirement,
|
|
61
|
-
source: options.fetch(:filename, nil),
|
|
62
|
-
platform: platform_name
|
|
63
|
-
)
|
|
44
|
+
# Store Drupal version if Drupal, but include the original manifest version for reference
|
|
45
|
+
if drupal_module?(dependency)
|
|
46
|
+
original_requirement = requirement
|
|
47
|
+
requirement = dependency.dig("source", "reference")
|
|
64
48
|
end
|
|
65
|
-
|
|
49
|
+
|
|
50
|
+
# Extract shasum from dist if present and non-empty
|
|
51
|
+
shasum = dependency.dig("dist", "shasum")
|
|
52
|
+
integrity = shasum && !shasum.empty? ? "sha1=#{shasum}" : nil
|
|
53
|
+
|
|
54
|
+
Dependency.new(
|
|
55
|
+
name: dependency["name"],
|
|
56
|
+
requirement: requirement,
|
|
57
|
+
type: type,
|
|
58
|
+
original_requirement: original_requirement,
|
|
59
|
+
source: source,
|
|
60
|
+
platform: platform_name,
|
|
61
|
+
integrity: integrity
|
|
62
|
+
)
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
def self.parse_manifest(file_contents, options: {})
|
|
@@ -131,12 +131,16 @@ module Bibliothecary
|
|
|
131
131
|
name = block[/name\s*=\s*"([^"]+)"/, 1]
|
|
132
132
|
version = block[/version\s*=\s*"([^"]+)"/, 1]
|
|
133
133
|
|
|
134
|
+
# Extract sdist hash: sdist = { url = "...", hash = "sha256:...", size = ... }
|
|
135
|
+
integrity = block[/^sdist\s*=\s*\{[^}]*hash\s*=\s*"([^"]+)"/m, 1]
|
|
136
|
+
|
|
134
137
|
dependencies << Dependency.new(
|
|
135
138
|
platform: platform_name,
|
|
136
139
|
name: name,
|
|
137
140
|
requirement: version,
|
|
138
141
|
type: "runtime", # All dependencies are considered runtime
|
|
139
|
-
source: source
|
|
142
|
+
source: source,
|
|
143
|
+
integrity: integrity
|
|
140
144
|
)
|
|
141
145
|
end
|
|
142
146
|
ParserResult.new(dependencies: dependencies)
|
|
@@ -306,6 +310,15 @@ module Bibliothecary
|
|
|
306
310
|
|
|
307
311
|
groups = ["runtime"] if groups.empty?
|
|
308
312
|
|
|
313
|
+
# Extract sdist hash from files array (look for .tar.gz entry)
|
|
314
|
+
integrity = nil
|
|
315
|
+
if (files_match = block[/^files\s*=\s*\[(.*?)\]/m, 1])
|
|
316
|
+
# Match .tar.gz file entry and extract hash
|
|
317
|
+
if (sdist_match = files_match.match(/\{file\s*=\s*"[^"]+\.tar\.gz",\s*hash\s*=\s*"([^"]+)"\}/))
|
|
318
|
+
integrity = sdist_match[1]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
309
322
|
groups.each do |group|
|
|
310
323
|
# Poetry lockfiles should already contain normalized names, but we'll
|
|
311
324
|
# apply it here as well just to be consistent with pyproject.toml parsing.
|
|
@@ -316,7 +329,8 @@ module Bibliothecary
|
|
|
316
329
|
requirement: version,
|
|
317
330
|
type: group,
|
|
318
331
|
source: options.fetch(:filename, nil),
|
|
319
|
-
platform: platform_name
|
|
332
|
+
platform: platform_name,
|
|
333
|
+
integrity: integrity
|
|
320
334
|
)
|
|
321
335
|
end
|
|
322
336
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module Parsers
|
|
5
|
+
class Rpm
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
|
|
8
|
+
def self.file_patterns
|
|
9
|
+
["*.spec"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.mapping
|
|
13
|
+
{
|
|
14
|
+
match_extension(".spec") => {
|
|
15
|
+
kind: "manifest",
|
|
16
|
+
parser: :parse_spec,
|
|
17
|
+
can_have_lockfile: false,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse_spec(file_contents, options: {})
|
|
23
|
+
source = options.fetch(:filename, nil)
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
# Parse BuildRequires (build dependencies)
|
|
27
|
+
file_contents.scan(/^BuildRequires:\s*(.+)$/i) do |match|
|
|
28
|
+
parse_dependency_line(match[0]).each do |dep|
|
|
29
|
+
dependencies << Dependency.new(
|
|
30
|
+
name: dep[:name],
|
|
31
|
+
requirement: dep[:requirement] || "*",
|
|
32
|
+
type: "build",
|
|
33
|
+
source: source,
|
|
34
|
+
platform: platform_name
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Parse Requires (runtime dependencies), including Requires(pre), Requires(post), etc.
|
|
40
|
+
file_contents.scan(/^Requires(?:\([^)]+\))?:\s*(.+)$/i) do |match|
|
|
41
|
+
parse_dependency_line(match[0]).each do |dep|
|
|
42
|
+
dependencies << Dependency.new(
|
|
43
|
+
name: dep[:name],
|
|
44
|
+
requirement: dep[:requirement] || "*",
|
|
45
|
+
type: "runtime",
|
|
46
|
+
source: source,
|
|
47
|
+
platform: platform_name
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
ParserResult.new(dependencies: dependencies)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.parse_dependency_line(line)
|
|
56
|
+
# Dependencies can be comma or whitespace separated
|
|
57
|
+
# Each dependency can have version constraints like: pkg >= 1.0
|
|
58
|
+
# Also filter out RPM macros like %{name}
|
|
59
|
+
deps = []
|
|
60
|
+
|
|
61
|
+
# Split on commas first, then handle each part
|
|
62
|
+
line.split(/,/).each do |part|
|
|
63
|
+
part = part.strip
|
|
64
|
+
next if part.empty?
|
|
65
|
+
next if part.start_with?("%") # Skip RPM macros
|
|
66
|
+
next if part.start_with?("/") # Skip file paths like /bin/sh
|
|
67
|
+
|
|
68
|
+
# Check for version constraint (pkg >= 1.0, pkg < 2.0, etc.)
|
|
69
|
+
if part =~ /^(\S+)\s+([<>=]+)\s*(\S+)$/
|
|
70
|
+
deps << { name: $1, requirement: "#{$2} #{$3}" }
|
|
71
|
+
elsif part =~ /^(\S+)$/
|
|
72
|
+
deps << { name: $1, requirement: nil }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
deps
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|