ecosystems-bibliothecary 15.2.0 → 15.3.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 +11 -0
- data/README.md +52 -0
- 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/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 +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c213dba45d6c84c67ecad66538e68fbd7ff843ec5fb6f6b1d80b757ae9ab5f15
|
|
4
|
+
data.tar.gz: a0b4a803b35d16820ed19b071ae8984efb9004ba712ca6457e6f9d9412d331e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ade8a88ba728b3d95e333000cdcde1ad8ed9ae885ab775678a1b45c3d3521705f553c293be26ce5d63002f5ec86666e29108b7b456b672c647a58d56af8dc568
|
|
7
|
+
data.tar.gz: e5fdf2c94bde72360926307a0df9cc6d796d476aa983a5236fa5d13691c4bcf74a5e20bd3c0090725ddd37bfad8a4d0733cc6a2d99b6cf25a64a92ac6a7d507b
|
data/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
|
|
14
14
|
### Removed
|
|
15
15
|
|
|
16
|
+
## [15.3.0]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- npm: pnpm-workspace.yaml support for catalog dependencies (pnpm 9+)
|
|
21
|
+
- alpm: Arch Linux PKGBUILD parser for depends, makedepends, and checkdepends
|
|
22
|
+
- apk: Alpine Linux APKBUILD parser for depends, makedepends, and checkdepends
|
|
23
|
+
- deb: Debian control file parser for Build-Depends, Depends, Pre-Depends, Recommends, Suggests
|
|
24
|
+
- rpm: RPM spec file parser for BuildRequires and Requires
|
|
25
|
+
- integrity: Added `integrity` field to Dependency class for lockfile hashes (npm package-lock.json, pnpm-lock.yaml, yarn.lock v4, bun.lock, go.sum, Gemfile.lock, deno.lock, composer.lock, stack.yaml.lock, Cargo.lock, Podfile.lock, mix.lock, rebar.lock, manifest.toml, poetry.lock, uv.lock)
|
|
26
|
+
|
|
16
27
|
## [15.2.0]
|
|
17
28
|
|
|
18
29
|
### Added
|
data/README.md
CHANGED
|
@@ -42,6 +42,48 @@ Bibliothecary.analyse('./')
|
|
|
42
42
|
|
|
43
43
|
All available config options are in: https://github.com/ecosyste-ms/bibliothecary/blob/master/lib/bibliothecary/configuration.rb
|
|
44
44
|
|
|
45
|
+
## Dependency fields
|
|
46
|
+
|
|
47
|
+
Each parsed dependency is a `Bibliothecary::Dependency` with:
|
|
48
|
+
|
|
49
|
+
| Field | Type | Description |
|
|
50
|
+
|-------|------|-------------|
|
|
51
|
+
| `name` | String | Package name |
|
|
52
|
+
| `requirement` | String | Version requirement (defaults to `"*"`) |
|
|
53
|
+
| `platform` | String | Package manager platform (e.g. `"npm"`, `"maven"`) |
|
|
54
|
+
| `type` | String | Dependency scope: `"runtime"`, `"development"`, `"test"`, etc. |
|
|
55
|
+
| `direct` | Boolean | Direct dependency (vs transitive) |
|
|
56
|
+
| `deprecated` | Boolean | Deprecated dependency |
|
|
57
|
+
| `local` | Boolean | Local/file path dependency |
|
|
58
|
+
| `optional` | Boolean | Optional dependency |
|
|
59
|
+
| `original_name` | String | Original name before aliasing/normalization |
|
|
60
|
+
| `original_requirement` | String | Original requirement before resolution |
|
|
61
|
+
| `source` | String | Path to the manifest file |
|
|
62
|
+
| `integrity` | String | Lockfile integrity hash (see table below) |
|
|
63
|
+
|
|
64
|
+
## Integrity hash support
|
|
65
|
+
|
|
66
|
+
The `integrity` field is populated for lockfiles that include per-dependency hashes:
|
|
67
|
+
|
|
68
|
+
| Lockfile | Platform | Hash format |
|
|
69
|
+
|----------|----------|-------------|
|
|
70
|
+
| package-lock.json | npm | `sha512-...` |
|
|
71
|
+
| pnpm-lock.yaml | npm | `sha512-...` |
|
|
72
|
+
| yarn.lock (v2+) | npm | `sha512-...` |
|
|
73
|
+
| bun.lock | npm | `sha512-...` |
|
|
74
|
+
| deno.lock | deno | `sha512-...` |
|
|
75
|
+
| go.sum | go | `h1:...` |
|
|
76
|
+
| Gemfile.lock | rubygems | `sha256=...` |
|
|
77
|
+
| poetry.lock | pypi | `sha256:...` |
|
|
78
|
+
| uv.lock | pypi | `sha256:...` |
|
|
79
|
+
| composer.lock | packagist | `sha1=...` |
|
|
80
|
+
| Cargo.lock | cargo | `sha256=...` |
|
|
81
|
+
| Podfile.lock | cocoapods | `sha1=...` |
|
|
82
|
+
| mix.lock | hex | `sha256=...` |
|
|
83
|
+
| rebar.lock | hex | `sha256=...` |
|
|
84
|
+
| manifest.toml (Gleam) | hex | `sha256=...` |
|
|
85
|
+
| stack.yaml.lock | hackage | `sha256=...` |
|
|
86
|
+
|
|
45
87
|
## Supported package manager file formats
|
|
46
88
|
|
|
47
89
|
- Actions
|
|
@@ -49,9 +91,13 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
|
|
|
49
91
|
- action.yaml
|
|
50
92
|
- .github/workflows/\*.yml
|
|
51
93
|
- .github/workflows/\*.yaml
|
|
94
|
+
- Alpm
|
|
95
|
+
- PKGBUILD
|
|
52
96
|
- Anaconda
|
|
53
97
|
- environment.yml
|
|
54
98
|
- environment.yaml
|
|
99
|
+
- Apk
|
|
100
|
+
- APKBUILD
|
|
55
101
|
- BentoML
|
|
56
102
|
- bentofile.yaml
|
|
57
103
|
- Bower
|
|
@@ -86,6 +132,9 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
|
|
|
86
132
|
- CRAN
|
|
87
133
|
- DESCRIPTION
|
|
88
134
|
- renv.lock
|
|
135
|
+
- Deb
|
|
136
|
+
- debian/control
|
|
137
|
+
- control
|
|
89
138
|
- Deno
|
|
90
139
|
- deno.json
|
|
91
140
|
- deno.jsonc
|
|
@@ -165,6 +214,7 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
|
|
|
165
214
|
- npm-shrinkwrap.json
|
|
166
215
|
- yarn.lock
|
|
167
216
|
- pnpm-lock.yaml
|
|
217
|
+
- pnpm-workspace.yaml
|
|
168
218
|
- bun.lock
|
|
169
219
|
- npm-ls.json
|
|
170
220
|
- Nuget
|
|
@@ -200,6 +250,8 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
|
|
|
200
250
|
- pdm.lock
|
|
201
251
|
- pip-resolved-dependencies.txt
|
|
202
252
|
- pip-dependency-graph.json
|
|
253
|
+
- Rpm
|
|
254
|
+
- \*.spec
|
|
203
255
|
- RubyGems
|
|
204
256
|
- Gemfile
|
|
205
257
|
- Gemfile.lock
|
|
@@ -18,6 +18,8 @@ module Bibliothecary
|
|
|
18
18
|
# for cases where it did not match the resolved name. This can be used for features like aliasing.
|
|
19
19
|
# @source [String] source An optional string to store the location of the manifest that contained this
|
|
20
20
|
# dependency, e.g. "src/package.json".
|
|
21
|
+
# @attr_reader [String] integrity An optional integrity hash from the lockfile, stored as-is
|
|
22
|
+
# (e.g. "sha512-abc123..." for npm, "h1:xyz..." for go.sum).
|
|
21
23
|
class Dependency
|
|
22
24
|
FIELDS = %i[
|
|
23
25
|
name
|
|
@@ -31,6 +33,7 @@ module Bibliothecary
|
|
|
31
33
|
optional
|
|
32
34
|
original_name
|
|
33
35
|
source
|
|
36
|
+
integrity
|
|
34
37
|
].freeze
|
|
35
38
|
|
|
36
39
|
attr_reader(*FIELDS)
|
|
@@ -46,7 +49,8 @@ module Bibliothecary
|
|
|
46
49
|
local: nil,
|
|
47
50
|
optional: nil,
|
|
48
51
|
original_name: nil,
|
|
49
|
-
source: nil
|
|
52
|
+
source: nil,
|
|
53
|
+
integrity: nil
|
|
50
54
|
)
|
|
51
55
|
@name = name
|
|
52
56
|
@platform = platform
|
|
@@ -59,6 +63,7 @@ module Bibliothecary
|
|
|
59
63
|
@optional = optional
|
|
60
64
|
@original_name = original_name
|
|
61
65
|
@source = source
|
|
66
|
+
@integrity = integrity
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
def eql?(other)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module Parsers
|
|
5
|
+
class Alpm
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
|
|
8
|
+
def self.file_patterns
|
|
9
|
+
["PKGBUILD"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.mapping
|
|
13
|
+
{
|
|
14
|
+
match_filename("PKGBUILD") => {
|
|
15
|
+
kind: "manifest",
|
|
16
|
+
parser: :parse_pkgbuild,
|
|
17
|
+
can_have_lockfile: false,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse_pkgbuild(file_contents, options: {})
|
|
23
|
+
source = options.fetch(:filename, "PKGBUILD")
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
# Parse depends (runtime)
|
|
27
|
+
extract_variable(file_contents, "depends").each do |dep|
|
|
28
|
+
name, requirement = parse_dependency(dep)
|
|
29
|
+
dependencies << Dependency.new(
|
|
30
|
+
name: name,
|
|
31
|
+
requirement: requirement || "*",
|
|
32
|
+
type: "runtime",
|
|
33
|
+
source: source,
|
|
34
|
+
platform: platform_name
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Parse makedepends (build)
|
|
39
|
+
extract_variable(file_contents, "makedepends").each do |dep|
|
|
40
|
+
name, requirement = parse_dependency(dep)
|
|
41
|
+
dependencies << Dependency.new(
|
|
42
|
+
name: name,
|
|
43
|
+
requirement: requirement || "*",
|
|
44
|
+
type: "build",
|
|
45
|
+
source: source,
|
|
46
|
+
platform: platform_name
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse checkdepends (test)
|
|
51
|
+
extract_variable(file_contents, "checkdepends").each do |dep|
|
|
52
|
+
name, requirement = parse_dependency(dep)
|
|
53
|
+
dependencies << Dependency.new(
|
|
54
|
+
name: name,
|
|
55
|
+
requirement: requirement || "*",
|
|
56
|
+
type: "test",
|
|
57
|
+
source: source,
|
|
58
|
+
platform: platform_name
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ParserResult.new(dependencies: dependencies)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.extract_variable(contents, var_name)
|
|
66
|
+
# PKGBUILD uses bash array syntax: depends=('pkg1' 'pkg2') or depends=(pkg1 pkg2)
|
|
67
|
+
# Can also span multiple lines
|
|
68
|
+
pattern = /^#{var_name}=\(([^)]*)\)/m
|
|
69
|
+
match = contents.match(pattern)
|
|
70
|
+
return [] unless match
|
|
71
|
+
|
|
72
|
+
# Extract items, handling both quoted and unquoted formats
|
|
73
|
+
# 'pkg1' 'pkg2' or "pkg1" "pkg2" or pkg1 pkg2
|
|
74
|
+
items = match[1].scan(/['"]([^'"]+)['"]|(\S+)/).flatten.compact
|
|
75
|
+
items.reject { |d| d.empty? || d.start_with?("$") }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.parse_dependency(dep_string)
|
|
79
|
+
# Parse version constraints like "glibc>=2.17" or "openssl>1.1"
|
|
80
|
+
# Operators: >=, <=, >, <, =
|
|
81
|
+
if dep_string =~ /^(.+?)([><=]+)(.+)$/
|
|
82
|
+
[$1, "#{$2}#{$3}"]
|
|
83
|
+
else
|
|
84
|
+
[dep_string, nil]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module Parsers
|
|
5
|
+
class Apk
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
|
|
8
|
+
def self.file_patterns
|
|
9
|
+
["APKBUILD"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.mapping
|
|
13
|
+
{
|
|
14
|
+
match_filename("APKBUILD") => {
|
|
15
|
+
kind: "manifest",
|
|
16
|
+
parser: :parse_apkbuild,
|
|
17
|
+
can_have_lockfile: false,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse_apkbuild(file_contents, options: {})
|
|
23
|
+
source = options.fetch(:filename, "APKBUILD")
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
# Parse depends (runtime)
|
|
27
|
+
extract_variable(file_contents, "depends").each do |dep|
|
|
28
|
+
name, requirement = parse_dependency(dep)
|
|
29
|
+
dependencies << Dependency.new(
|
|
30
|
+
name: name,
|
|
31
|
+
requirement: requirement || "*",
|
|
32
|
+
type: "runtime",
|
|
33
|
+
source: source,
|
|
34
|
+
platform: platform_name
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Parse makedepends (build)
|
|
39
|
+
extract_variable(file_contents, "makedepends").each do |dep|
|
|
40
|
+
name, requirement = parse_dependency(dep)
|
|
41
|
+
dependencies << Dependency.new(
|
|
42
|
+
name: name,
|
|
43
|
+
requirement: requirement || "*",
|
|
44
|
+
type: "build",
|
|
45
|
+
source: source,
|
|
46
|
+
platform: platform_name
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse checkdepends (test)
|
|
51
|
+
extract_variable(file_contents, "checkdepends").each do |dep|
|
|
52
|
+
name, requirement = parse_dependency(dep)
|
|
53
|
+
dependencies << Dependency.new(
|
|
54
|
+
name: name,
|
|
55
|
+
requirement: requirement || "*",
|
|
56
|
+
type: "test",
|
|
57
|
+
source: source,
|
|
58
|
+
platform: platform_name
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ParserResult.new(dependencies: dependencies)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.extract_variable(contents, var_name)
|
|
66
|
+
# Match variable assignment with double or single quotes, handling multi-line with backslash
|
|
67
|
+
# Examples:
|
|
68
|
+
# depends="foo bar"
|
|
69
|
+
# makedepends="foo
|
|
70
|
+
# bar"
|
|
71
|
+
# checkdepends='foo bar'
|
|
72
|
+
pattern = /^#{var_name}=["']([^"']*?)["']/m
|
|
73
|
+
match = contents.match(pattern)
|
|
74
|
+
return [] unless match
|
|
75
|
+
|
|
76
|
+
# Split on whitespace and filter out empty strings, negated packages (!), and variable references ($)
|
|
77
|
+
match[1].split(/\s+/).reject { |d| d.empty? || d.start_with?("!") || d.start_with?("$") }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.parse_dependency(dep_string)
|
|
81
|
+
# Parse version constraints like "openssl-dev>3" or "zlib-dev>=1.2.3"
|
|
82
|
+
# Operators: >=, <=, >, <, =, ~
|
|
83
|
+
if dep_string =~ /^(.+?)([><=~]+)(.+)$/
|
|
84
|
+
[$1, "#{$2}#{$3}"]
|
|
85
|
+
else
|
|
86
|
+
[dep_string, nil]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -55,6 +55,7 @@ module Bibliothecary
|
|
|
55
55
|
name = block[/name\s*=\s*"([^"]+)"/, 1]
|
|
56
56
|
version = block[/version\s*=\s*"([^"]+)"/, 1]
|
|
57
57
|
source = block[/source\s*=\s*"([^"]+)"/, 1]
|
|
58
|
+
checksum = block[/checksum\s*=\s*"([^"]+)"/, 1]
|
|
58
59
|
|
|
59
60
|
# Skip packages without a registry source (local/workspace packages)
|
|
60
61
|
next unless source&.start_with?("registry+")
|
|
@@ -64,7 +65,8 @@ module Bibliothecary
|
|
|
64
65
|
requirement: version,
|
|
65
66
|
type: "runtime",
|
|
66
67
|
source: options.fetch(:filename, nil),
|
|
67
|
-
platform: platform_name
|
|
68
|
+
platform: platform_name,
|
|
69
|
+
integrity: checksum ? "sha256=#{checksum}" : nil
|
|
68
70
|
)
|
|
69
71
|
end
|
|
70
72
|
ParserResult.new(dependencies: dependencies)
|
|
@@ -49,6 +49,9 @@ module Bibliothecary
|
|
|
49
49
|
source = options.fetch(:filename, nil)
|
|
50
50
|
dependencies = []
|
|
51
51
|
|
|
52
|
+
# Parse SPEC CHECKSUMS section to build lookup table
|
|
53
|
+
checksums = parse_spec_checksums(file_contents)
|
|
54
|
+
|
|
52
55
|
# Match pod entries: " - Name (version)" or " - Name/Subspec (version)"
|
|
53
56
|
# Only process lines in PODS section (before DEPENDENCIES section)
|
|
54
57
|
pods_section = file_contents.split(/^DEPENDENCIES:/)[0]
|
|
@@ -60,13 +63,38 @@ module Bibliothecary
|
|
|
60
63
|
name: base_name,
|
|
61
64
|
requirement: version,
|
|
62
65
|
type: "runtime",
|
|
63
|
-
source: source
|
|
66
|
+
source: source,
|
|
67
|
+
integrity: checksums[base_name]
|
|
64
68
|
)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
ParserResult.new(dependencies: dependencies)
|
|
68
72
|
end
|
|
69
73
|
|
|
74
|
+
def self.parse_spec_checksums(file_contents)
|
|
75
|
+
checksums = {}
|
|
76
|
+
in_checksums = false
|
|
77
|
+
|
|
78
|
+
file_contents.each_line do |line|
|
|
79
|
+
if line.start_with?("SPEC CHECKSUMS:")
|
|
80
|
+
in_checksums = true
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
next unless in_checksums
|
|
85
|
+
|
|
86
|
+
# End of section (blank line or new section)
|
|
87
|
+
break if line.strip.empty? || (line !~ /^\s/ && line.strip != "")
|
|
88
|
+
|
|
89
|
+
# Match " Name: sha1hash"
|
|
90
|
+
if (match = line.match(/^\s+([^:]+):\s*([a-f0-9]+)\s*$/))
|
|
91
|
+
checksums[match[1]] = "sha1=#{match[2]}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
checksums
|
|
96
|
+
end
|
|
97
|
+
|
|
70
98
|
def self.parse_podspec(file_contents, options: {})
|
|
71
99
|
source = options.fetch(:filename, nil)
|
|
72
100
|
deps = []
|
|
@@ -16,10 +16,12 @@ module Bibliothecary
|
|
|
16
16
|
match_filename("environment.yml") => {
|
|
17
17
|
parser: :parse_conda,
|
|
18
18
|
kind: "manifest",
|
|
19
|
+
can_have_lockfile: false,
|
|
19
20
|
},
|
|
20
21
|
match_filename("environment.yaml") => {
|
|
21
22
|
parser: :parse_conda,
|
|
22
23
|
kind: "manifest",
|
|
24
|
+
can_have_lockfile: false,
|
|
23
25
|
},
|
|
24
26
|
}
|
|
25
27
|
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module Parsers
|
|
5
|
+
class Deb
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
|
|
8
|
+
def self.file_patterns
|
|
9
|
+
["debian/control", "control"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.mapping
|
|
13
|
+
{
|
|
14
|
+
match_filename("debian/control") => {
|
|
15
|
+
kind: "manifest",
|
|
16
|
+
parser: :parse_control,
|
|
17
|
+
can_have_lockfile: false,
|
|
18
|
+
},
|
|
19
|
+
match_filename("control") => {
|
|
20
|
+
kind: "manifest",
|
|
21
|
+
parser: :parse_control,
|
|
22
|
+
can_have_lockfile: false,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
BUILD_DEP_FIELDS = %w[Build-Depends Build-Depends-Indep Build-Depends-Arch].freeze
|
|
28
|
+
RUNTIME_DEP_FIELDS = %w[Depends Pre-Depends Recommends Suggests].freeze
|
|
29
|
+
|
|
30
|
+
def self.parse_control(file_contents, options: {})
|
|
31
|
+
source = options.fetch(:filename, nil)
|
|
32
|
+
dependencies = []
|
|
33
|
+
|
|
34
|
+
# Parse the control file - it's in RFC 822 format with continuation lines
|
|
35
|
+
# Fields can span multiple lines if continuation lines start with whitespace
|
|
36
|
+
fields = parse_fields(file_contents)
|
|
37
|
+
|
|
38
|
+
# Build dependencies
|
|
39
|
+
BUILD_DEP_FIELDS.each do |field_name|
|
|
40
|
+
next unless fields[field_name.downcase]
|
|
41
|
+
|
|
42
|
+
parse_dependency_list(fields[field_name.downcase]).each do |dep|
|
|
43
|
+
dependencies << Dependency.new(
|
|
44
|
+
name: dep[:name],
|
|
45
|
+
requirement: dep[:requirement] || "*",
|
|
46
|
+
type: "build",
|
|
47
|
+
source: source,
|
|
48
|
+
platform: platform_name
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Runtime dependencies
|
|
54
|
+
RUNTIME_DEP_FIELDS.each do |field_name|
|
|
55
|
+
next unless fields[field_name.downcase]
|
|
56
|
+
|
|
57
|
+
parse_dependency_list(fields[field_name.downcase]).each do |dep|
|
|
58
|
+
dependencies << Dependency.new(
|
|
59
|
+
name: dep[:name],
|
|
60
|
+
requirement: dep[:requirement] || "*",
|
|
61
|
+
type: "runtime",
|
|
62
|
+
source: source,
|
|
63
|
+
platform: platform_name
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
ParserResult.new(dependencies: dependencies)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.parse_fields(contents)
|
|
72
|
+
fields = {}
|
|
73
|
+
current_field = nil
|
|
74
|
+
current_value = []
|
|
75
|
+
|
|
76
|
+
contents.each_line do |line|
|
|
77
|
+
if line =~ /^(\S+):\s*(.*)$/
|
|
78
|
+
# Save previous field
|
|
79
|
+
if current_field
|
|
80
|
+
fields[current_field] = current_value.join(" ").strip
|
|
81
|
+
end
|
|
82
|
+
current_field = $1.downcase
|
|
83
|
+
current_value = [$2]
|
|
84
|
+
elsif line =~ /^\s+(.*)$/ && current_field
|
|
85
|
+
# Continuation line
|
|
86
|
+
current_value << $1
|
|
87
|
+
elsif line.strip.empty?
|
|
88
|
+
# Empty line - save current field and reset
|
|
89
|
+
if current_field
|
|
90
|
+
fields[current_field] = current_value.join(" ").strip
|
|
91
|
+
end
|
|
92
|
+
current_field = nil
|
|
93
|
+
current_value = []
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Save last field
|
|
98
|
+
if current_field
|
|
99
|
+
fields[current_field] = current_value.join(" ").strip
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
fields
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.parse_dependency_list(dep_string)
|
|
106
|
+
deps = []
|
|
107
|
+
|
|
108
|
+
# Dependencies are comma-separated
|
|
109
|
+
dep_string.split(/,/).each do |dep|
|
|
110
|
+
dep = dep.strip
|
|
111
|
+
next if dep.empty?
|
|
112
|
+
next if dep.start_with?("$") # Skip substitution variables like ${shlibs:Depends}
|
|
113
|
+
|
|
114
|
+
# Handle alternatives (pkg1 | pkg2) - just take the first one
|
|
115
|
+
dep = dep.split("|").first.strip
|
|
116
|
+
|
|
117
|
+
# Parse package name and optional version constraint
|
|
118
|
+
# Format: package (>= 1.0) or just package
|
|
119
|
+
if dep =~ /^(\S+)\s*\(([^)]+)\)$/
|
|
120
|
+
name = $1
|
|
121
|
+
constraint = $2.strip
|
|
122
|
+
deps << { name: name, requirement: constraint }
|
|
123
|
+
elsif dep =~ /^(\S+)$/
|
|
124
|
+
deps << { name: $1, requirement: nil }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
deps
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -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
|
|