bibliothecary 14.2.0 → 14.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 +12 -0
- data/lib/bibliothecary/parsers/conan.rb +243 -0
- data/lib/bibliothecary/parsers/vcpkg.rb +111 -0
- data/lib/bibliothecary/purl_util.rb +2 -0
- data/lib/bibliothecary/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1f7fd4aeb8298f9fcb9b5edc69b7936eed8f112f3a6dde000edda36d641cfca
|
|
4
|
+
data.tar.gz: 4d9b619aab4813b3ee77a36b0fbb1588b561a3dd42275feeb216f01d19f8ea05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9a904ac75104ea34f45bfea2900475c89fd29a5d15ac065923d4e9d4c715ec6380d92ecea6499025063abe8f0146d2a89d9661f59f98cd216a4dcc6ce44b054
|
|
7
|
+
data.tar.gz: a4234dc007565e41587fa92ca7f970e5fdffd479debd202658e8a04edcf01943624df11aa1d0785398b87d10f694f3b84fb68fb985251315fbff67799f24fa2f
|
data/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
|
|
14
14
|
### Removed
|
|
15
15
|
|
|
16
|
+
## [14.4.0]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Add suppport for vcpkg parsing
|
|
21
|
+
|
|
22
|
+
## [14.3.0]
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Add suppport for conan parsing
|
|
27
|
+
|
|
16
28
|
## [14.2.0]
|
|
17
29
|
|
|
18
30
|
### Added
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported from Go code available at https://github.com/google/osv-scalibr/blob/f37275e81582aee924103d49d9a27c8e353477e7/extractor/filesystem/language/cpp/conanlock/conanlock.go
|
|
4
|
+
# Go code was made available under the Apache License, Version 2.0
|
|
5
|
+
|
|
6
|
+
module Bibliothecary
|
|
7
|
+
module Parsers
|
|
8
|
+
class Conan
|
|
9
|
+
include Bibliothecary::Analyser
|
|
10
|
+
|
|
11
|
+
def self.mapping
|
|
12
|
+
{
|
|
13
|
+
match_filename("conanfile.py") => {
|
|
14
|
+
kind: "manifest",
|
|
15
|
+
parser: :parse_conanfile_py,
|
|
16
|
+
},
|
|
17
|
+
match_filename("conanfile.txt") => {
|
|
18
|
+
kind: "manifest",
|
|
19
|
+
parser: :parse_conanfile_txt,
|
|
20
|
+
},
|
|
21
|
+
match_filename("conan.lock") => {
|
|
22
|
+
kind: "lockfile",
|
|
23
|
+
parser: :parse_lockfile,
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
29
|
+
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
30
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
31
|
+
|
|
32
|
+
def self.parse_conanfile_py(file_contents, options: {})
|
|
33
|
+
dependencies = []
|
|
34
|
+
|
|
35
|
+
# Parse self.requires() calls in conanfile.py
|
|
36
|
+
# Pattern matches: self.requires("package/version") or self.requires("package/version", force=True, options={...})
|
|
37
|
+
# Captures only the package spec string; additional keyword arguments are ignored
|
|
38
|
+
file_contents.scan(/self\.requires\(\s*["']([^"']+)["']/).each do |match|
|
|
39
|
+
manifest_dep = match[0]
|
|
40
|
+
reference = parse_conan_reference(manifest_dep)
|
|
41
|
+
|
|
42
|
+
# Skip entries with no name
|
|
43
|
+
next if reference[:name].nil? || reference[:name].empty?
|
|
44
|
+
|
|
45
|
+
dependencies << Dependency.new(
|
|
46
|
+
name: reference[:name],
|
|
47
|
+
requirement: reference[:version],
|
|
48
|
+
type: "runtime",
|
|
49
|
+
source: options.fetch(:filename, nil),
|
|
50
|
+
platform: platform_name
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ParserResult.new(dependencies: dependencies)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.parse_conanfile_txt(file_contents, options: {})
|
|
58
|
+
dependencies = []
|
|
59
|
+
current_section = nil
|
|
60
|
+
|
|
61
|
+
file_contents.each_line do |line|
|
|
62
|
+
line = line.strip
|
|
63
|
+
|
|
64
|
+
# Skip empty lines and comments
|
|
65
|
+
next if line.empty? || line.start_with?("#")
|
|
66
|
+
|
|
67
|
+
# Check for section headers
|
|
68
|
+
if line.match?(/^\[([^\]]+)\]$/)
|
|
69
|
+
current_section = line[1..-2]
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Parse dependencies in [requires] and [build_requires] sections
|
|
74
|
+
next unless %w[requires build_requires].include?(current_section)
|
|
75
|
+
|
|
76
|
+
reference = parse_conan_reference(manifest_dep)
|
|
77
|
+
next if reference[:name].nil? || reference[:name].empty?
|
|
78
|
+
|
|
79
|
+
dependencies << Dependency.new(
|
|
80
|
+
name: reference[:name],
|
|
81
|
+
requirement: reference[:version],
|
|
82
|
+
type: current_section == "requires" ? "runtime" : "development",
|
|
83
|
+
source: options.fetch(:filename, nil),
|
|
84
|
+
platform: platform_name
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
ParserResult.new(dependencies: dependencies)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.parse_lockfile(file_contents, options: {})
|
|
92
|
+
manifest = JSON.parse(file_contents)
|
|
93
|
+
|
|
94
|
+
# Auto-detect lockfile format version
|
|
95
|
+
if manifest.dig("graph_lock", "nodes")
|
|
96
|
+
parse_v1_lockfile(manifest, options: options)
|
|
97
|
+
else
|
|
98
|
+
parse_v2_lockfile(manifest, options: options)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.parse_v1_lockfile(lockfile, options: {})
|
|
103
|
+
dependencies = []
|
|
104
|
+
|
|
105
|
+
lockfile["graph_lock"]["nodes"].each_value do |node|
|
|
106
|
+
if node["path"] && !node["path"].empty?
|
|
107
|
+
# a local "conanfile.txt", skip
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
reference = nil
|
|
112
|
+
if node["pref"]
|
|
113
|
+
# old format 0.3 (conan 1.27-) lockfiles use "pref" instead of "ref"
|
|
114
|
+
reference = parse_conan_reference(node["pref"])
|
|
115
|
+
elsif node["ref"]
|
|
116
|
+
reference = parse_conan_reference(node["ref"])
|
|
117
|
+
else
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# skip entries with no name, they are most likely consumer's conanfiles
|
|
122
|
+
# and not dependencies to be searched in a database anyway
|
|
123
|
+
next if reference[:name].nil? || reference[:name].empty?
|
|
124
|
+
|
|
125
|
+
type = case node["context"]
|
|
126
|
+
when "build"
|
|
127
|
+
"development"
|
|
128
|
+
else
|
|
129
|
+
"runtime"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
dependencies << Dependency.new(
|
|
133
|
+
name: reference[:name],
|
|
134
|
+
requirement: reference[:version],
|
|
135
|
+
type: type,
|
|
136
|
+
source: options.fetch(:filename, nil),
|
|
137
|
+
platform: platform_name
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
ParserResult.new(dependencies: dependencies)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.parse_v2_lockfile(lockfile, options: {})
|
|
145
|
+
dependencies = []
|
|
146
|
+
|
|
147
|
+
parse_conan_requires(dependencies, lockfile["requires"], "runtime", options)
|
|
148
|
+
parse_conan_requires(dependencies, lockfile["build_requires"], "development", options)
|
|
149
|
+
parse_conan_requires(dependencies, lockfile["python_requires"], "development", options)
|
|
150
|
+
|
|
151
|
+
ParserResult.new(dependencies: dependencies)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Helper method to parse an array of Conan package references
|
|
155
|
+
# Similar to OSV Scalibr's parseConanRequires function
|
|
156
|
+
def self.parse_conan_requires(dependencies, requires, type, options)
|
|
157
|
+
return unless requires && !requires.empty?
|
|
158
|
+
|
|
159
|
+
requires.each do |ref|
|
|
160
|
+
reference = parse_conan_reference(ref)
|
|
161
|
+
|
|
162
|
+
# Skip entries with no name, they are most likely consumer's conanfiles
|
|
163
|
+
# and not dependencies to be searched in a database anyway
|
|
164
|
+
next if reference[:name].nil? || reference[:name].empty?
|
|
165
|
+
|
|
166
|
+
dependencies << Dependency.new(
|
|
167
|
+
name: reference[:name],
|
|
168
|
+
requirement: reference[:version] || "*",
|
|
169
|
+
type: type,
|
|
170
|
+
source: options.fetch(:filename, nil),
|
|
171
|
+
platform: platform_name
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Parse Conan reference
|
|
177
|
+
# Handles the full Conan reference format:
|
|
178
|
+
# name/version[@username[/channel]][#recipe_revision][:package_id[#package_revision]][%timestamp]
|
|
179
|
+
#
|
|
180
|
+
# Based on OSV Scalibr's parseConanReference implementation:
|
|
181
|
+
# https://github.com/google/osv-scalibr/blob/f37275e81582aee924103d49d9a27c8e353477e7/extractor/filesystem/language/cpp/conanlock/conanlock.go
|
|
182
|
+
#
|
|
183
|
+
# Returns a hash with keys: name, version, username, channel, recipe_revision, package_id, package_revision, timestamp
|
|
184
|
+
def self.parse_conan_reference(ref)
|
|
185
|
+
reference = {
|
|
186
|
+
name: nil,
|
|
187
|
+
version: nil,
|
|
188
|
+
username: nil,
|
|
189
|
+
channel: nil,
|
|
190
|
+
recipe_revision: nil,
|
|
191
|
+
package_id: nil,
|
|
192
|
+
package_revision: nil,
|
|
193
|
+
timestamp: nil,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return reference if ref.nil? || ref.empty?
|
|
197
|
+
|
|
198
|
+
# Validate that ref contains "/" (name/version format)
|
|
199
|
+
# This filters out invalid entries like "1.2.3" (version without name)
|
|
200
|
+
return reference unless ref.include?("/")
|
|
201
|
+
|
|
202
|
+
# Strip timestamp: name/version%1234 -> name/version
|
|
203
|
+
parts = ref.split("%", 2)
|
|
204
|
+
if parts.length == 2
|
|
205
|
+
ref = parts[0]
|
|
206
|
+
reference[:timestamp] = parts[1]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Strip package revision: name/version:pkgid#prev -> name/version
|
|
210
|
+
parts = ref.split(":", 2)
|
|
211
|
+
if parts.length == 2
|
|
212
|
+
ref = parts[0]
|
|
213
|
+
pkg_parts = parts[1].split("#", 2)
|
|
214
|
+
reference[:package_id] = pkg_parts[0]
|
|
215
|
+
reference[:package_revision] = pkg_parts[1] if pkg_parts.length == 2
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Strip recipe revision: name/version#rrev -> name/version
|
|
219
|
+
parts = ref.split("#", 2)
|
|
220
|
+
if parts.length == 2
|
|
221
|
+
ref = parts[0]
|
|
222
|
+
reference[:recipe_revision] = parts[1]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Strip username/channel: name/version@user/channel -> name/version
|
|
226
|
+
parts = ref.split("@", 2)
|
|
227
|
+
if parts.length == 2
|
|
228
|
+
ref = parts[0]
|
|
229
|
+
user_channel = parts[1].split("/", 2)
|
|
230
|
+
reference[:username] = user_channel[0]
|
|
231
|
+
reference[:channel] = user_channel[1] if user_channel.length == 2
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Split name/version: name/version -> [name, version]
|
|
235
|
+
parts = ref.split("/", 2)
|
|
236
|
+
reference[:name] = parts[0]
|
|
237
|
+
reference[:version] = parts.length == 2 ? parts[1] : nil
|
|
238
|
+
|
|
239
|
+
reference
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module Parsers
|
|
5
|
+
class Vcpkg
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
|
|
8
|
+
def self.mapping
|
|
9
|
+
{
|
|
10
|
+
match_filename("vcpkg.json") => {
|
|
11
|
+
kind: "manifest",
|
|
12
|
+
parser: :parse_vcpkg_json,
|
|
13
|
+
},
|
|
14
|
+
# _generated-vcpkg-list.json is the output of `vcpkg list --x-json`.
|
|
15
|
+
match_filename("_generated-vcpkg-list.json") => {
|
|
16
|
+
kind: "lockfile",
|
|
17
|
+
parser: :parse_vcpkg_list_json,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
23
|
+
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
24
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
25
|
+
|
|
26
|
+
def self.parse_vcpkg_json(file_contents, options: {})
|
|
27
|
+
dependencies = []
|
|
28
|
+
manifest = JSON.parse(file_contents)
|
|
29
|
+
deps = manifest["dependencies"]
|
|
30
|
+
|
|
31
|
+
if !deps || deps.empty?
|
|
32
|
+
return ParserResult.new(dependencies: dependencies)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
overrides = {}
|
|
36
|
+
manifest["overrides"]&.each do |override|
|
|
37
|
+
if override.is_a?(Hash) && override["name"]
|
|
38
|
+
override_version = override["version"] || override["version-semver"] || override["version-date"] || override["version-string"]
|
|
39
|
+
overrides[override["name"]] = format_requirement(override_version, override["port-version"])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
deps.each do |dep|
|
|
44
|
+
if dep.is_a?(String)
|
|
45
|
+
# Simple string format: "boost-system"
|
|
46
|
+
name = dep
|
|
47
|
+
requirement = nil
|
|
48
|
+
is_development = false
|
|
49
|
+
elsif dep.is_a?(Hash)
|
|
50
|
+
# Object format: { "name": "cpprestsdk", "version>=": "2.10.0", ... }
|
|
51
|
+
name = dep["name"]
|
|
52
|
+
requirement = if dep["version>="]
|
|
53
|
+
">=#{dep['version>=']}"
|
|
54
|
+
end
|
|
55
|
+
is_development = dep["host"] == true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Skip entries with no name
|
|
59
|
+
next if name.nil? || name.empty?
|
|
60
|
+
|
|
61
|
+
requirement = overrides[name] if overrides[name]
|
|
62
|
+
|
|
63
|
+
dependencies << Dependency.new(
|
|
64
|
+
platform: platform_name,
|
|
65
|
+
name: name,
|
|
66
|
+
requirement: requirement,
|
|
67
|
+
type: is_development ? "development" : "runtime",
|
|
68
|
+
source: options.fetch(:filename, nil)
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
ParserResult.new(dependencies: dependencies)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.parse_vcpkg_list_json(file_contents, options: {})
|
|
76
|
+
# parses the output of `vcpkg list --x-json`
|
|
77
|
+
dependencies = []
|
|
78
|
+
manifest = JSON.parse(file_contents)
|
|
79
|
+
|
|
80
|
+
manifest.each_value do |package_info|
|
|
81
|
+
name = package_info["package_name"]
|
|
82
|
+
version = package_info["version"]
|
|
83
|
+
port_version = package_info["port_version"]
|
|
84
|
+
|
|
85
|
+
# Skip entries with no name
|
|
86
|
+
next if name.nil? || name.empty?
|
|
87
|
+
|
|
88
|
+
dependencies << Dependency.new(
|
|
89
|
+
platform: platform_name,
|
|
90
|
+
name: name,
|
|
91
|
+
requirement: format_requirement(version, port_version),
|
|
92
|
+
type: "runtime",
|
|
93
|
+
source: options.fetch(:filename, nil)
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
ParserResult.new(dependencies: dependencies)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.format_requirement(version, port_version)
|
|
101
|
+
return "*" unless version
|
|
102
|
+
|
|
103
|
+
if port_version && port_version > 0
|
|
104
|
+
return "#{version}##{port_version}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
version
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -12,11 +12,13 @@ module Bibliothecary
|
|
|
12
12
|
"npm" => :npm,
|
|
13
13
|
"cargo" => :cargo,
|
|
14
14
|
"composer" => :packagist,
|
|
15
|
+
"conan" => :conan,
|
|
15
16
|
"conda" => :conda,
|
|
16
17
|
"cran" => :cran,
|
|
17
18
|
"gem" => :rubygems,
|
|
18
19
|
"nuget" => :nuget,
|
|
19
20
|
"pypi" => :pypi,
|
|
21
|
+
"vcpkg" => :vcpkg,
|
|
20
22
|
}.freeze
|
|
21
23
|
|
|
22
24
|
# @param purl [PackageURL]
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bibliothecary
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 14.
|
|
4
|
+
version: 14.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Nesbitt
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-24 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: commander
|
|
@@ -135,6 +136,7 @@ dependencies:
|
|
|
135
136
|
- - ">="
|
|
136
137
|
- !ruby/object:Gem::Version
|
|
137
138
|
version: '0'
|
|
139
|
+
description:
|
|
138
140
|
email:
|
|
139
141
|
- andrewnez@gmail.com
|
|
140
142
|
executables:
|
|
@@ -181,6 +183,7 @@ files:
|
|
|
181
183
|
- lib/bibliothecary/parsers/bower.rb
|
|
182
184
|
- lib/bibliothecary/parsers/cargo.rb
|
|
183
185
|
- lib/bibliothecary/parsers/cocoapods.rb
|
|
186
|
+
- lib/bibliothecary/parsers/conan.rb
|
|
184
187
|
- lib/bibliothecary/parsers/conda.rb
|
|
185
188
|
- lib/bibliothecary/parsers/cpan.rb
|
|
186
189
|
- lib/bibliothecary/parsers/cran.rb
|
|
@@ -198,6 +201,7 @@ files:
|
|
|
198
201
|
- lib/bibliothecary/parsers/pypi.rb
|
|
199
202
|
- lib/bibliothecary/parsers/rubygems.rb
|
|
200
203
|
- lib/bibliothecary/parsers/shard.rb
|
|
204
|
+
- lib/bibliothecary/parsers/vcpkg.rb
|
|
201
205
|
- lib/bibliothecary/purl_util.rb
|
|
202
206
|
- lib/bibliothecary/related_files_info.rb
|
|
203
207
|
- lib/bibliothecary/runner.rb
|
|
@@ -209,6 +213,7 @@ licenses:
|
|
|
209
213
|
- AGPL-3.0
|
|
210
214
|
metadata:
|
|
211
215
|
rubygems_mfa_required: 'true'
|
|
216
|
+
post_install_message:
|
|
212
217
|
rdoc_options: []
|
|
213
218
|
require_paths:
|
|
214
219
|
- lib
|
|
@@ -223,7 +228,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
223
228
|
- !ruby/object:Gem::Version
|
|
224
229
|
version: '0'
|
|
225
230
|
requirements: []
|
|
226
|
-
rubygems_version: 3.
|
|
231
|
+
rubygems_version: 3.4.19
|
|
232
|
+
signing_key:
|
|
227
233
|
specification_version: 4
|
|
228
234
|
summary: Find and parse manifests
|
|
229
235
|
test_files: []
|