bibliothecary 14.1.0 → 14.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 +18 -1
- data/lib/bibliothecary/parsers/conan.rb +243 -0
- data/lib/bibliothecary/parsers/npm.rb +1 -1
- data/lib/bibliothecary/parsers/nuget.rb +12 -0
- data/lib/bibliothecary/purl_util.rb +1 -0
- data/lib/bibliothecary/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6707fc0443320dc26cce55e6adcd048042c13b1648efe3a134952c62e5288f1f
|
|
4
|
+
data.tar.gz: 7e53ee490a0f3eb7ab97470d30332247176c8ea73fdf88182c82da152de50c56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eea5b5ac599bbd4043a1caf1ff3f89d8356619f9f30a45715a8bb81628673934b58cec80b4234ff863bb41c5b1cfde33a9a00a8ceaa58ce256ec267d99cab292
|
|
7
|
+
data.tar.gz: 7dc7d4532c3da16a34d60e70e10022c773a2b4c748ec15efec8dda0eb3f2718f71f0f1162da28cd886e8e01138f9f945ab16bf8bcf4f4f44ebcf2a292a4a6f54
|
data/CHANGELOG.md
CHANGED
|
@@ -13,13 +13,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
|
|
14
14
|
### Removed
|
|
15
15
|
|
|
16
|
+
## [14.3.0]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Add suppport for conan parsing
|
|
21
|
+
|
|
22
|
+
## [14.2.0]
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Dependencies from yarn.lock will return a nil "type" instead of assuming "runtime".
|
|
29
|
+
- In Nuget .csproj files, ignored <Reference> tags that don't have a version.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
16
33
|
## [14.1.0] - 2025-10-01
|
|
17
34
|
|
|
18
35
|
### Added
|
|
19
36
|
|
|
20
37
|
### Changed
|
|
21
38
|
|
|
22
|
-
- Dependencies from pom.xml without a scope will
|
|
39
|
+
- Dependencies from pom.xml without a scope will now return a "type" of nil instead of guessing "runtime".
|
|
23
40
|
|
|
24
41
|
### Removed
|
|
25
42
|
|
|
@@ -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
|
|
@@ -187,7 +187,7 @@ module Bibliothecary
|
|
|
187
187
|
original_name: dep[:original_name],
|
|
188
188
|
requirement: dep[:version],
|
|
189
189
|
original_requirement: dep[:original_requirement],
|
|
190
|
-
type:
|
|
190
|
+
type: nil, # yarn.lock doesn't report on the type of dependency
|
|
191
191
|
local: dep[:requirements]&.first&.start_with?("file:"),
|
|
192
192
|
source: options.fetch(:filename, nil),
|
|
193
193
|
platform: platform_name
|
|
@@ -153,6 +153,17 @@ module Bibliothecary
|
|
|
153
153
|
.select { |dep| dep.respond_to? "Include" }
|
|
154
154
|
.map do |dependency|
|
|
155
155
|
vals = *dependency.Include.split(",").map(&:strip)
|
|
156
|
+
|
|
157
|
+
# Skip <Reference> dependencies that only have the name value. Reasoning:
|
|
158
|
+
# Builtin assemblies like "System.Web" or "Microsoft.CSharp" can be required from the framework or by
|
|
159
|
+
# downloading via Nuget, and we only want to report on packages that are downloaded from Nuget. We are
|
|
160
|
+
# pretty sure that if they don't have a version in <Reference> then they're likely from the framework
|
|
161
|
+
# itself, which means they won't show up in the lockfile and we want to omit them.
|
|
162
|
+
# Note: if we omit a false positive here it should still show up in the lockfile, and it should be
|
|
163
|
+
# safer guess like this since <Reference> is an older standard.
|
|
164
|
+
# Note: this strategy could also skip on-disk 3rd-party packages with a <HintPath> but no version in <Reference>
|
|
165
|
+
next nil if vals.size == 1
|
|
166
|
+
|
|
156
167
|
name = vals.shift
|
|
157
168
|
vals = vals.to_h { |r| r.split("=", 2) }
|
|
158
169
|
|
|
@@ -164,6 +175,7 @@ module Bibliothecary
|
|
|
164
175
|
platform: platform_name
|
|
165
176
|
)
|
|
166
177
|
end
|
|
178
|
+
.compact
|
|
167
179
|
|
|
168
180
|
dependencies = packages.uniq(&:name)
|
|
169
181
|
ParserResult.new(dependencies: dependencies)
|
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.3.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-12 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
|
|
@@ -209,6 +212,7 @@ licenses:
|
|
|
209
212
|
- AGPL-3.0
|
|
210
213
|
metadata:
|
|
211
214
|
rubygems_mfa_required: 'true'
|
|
215
|
+
post_install_message:
|
|
212
216
|
rdoc_options: []
|
|
213
217
|
require_paths:
|
|
214
218
|
- lib
|
|
@@ -223,7 +227,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
223
227
|
- !ruby/object:Gem::Version
|
|
224
228
|
version: '0'
|
|
225
229
|
requirements: []
|
|
226
|
-
rubygems_version: 3.
|
|
230
|
+
rubygems_version: 3.4.19
|
|
231
|
+
signing_key:
|
|
227
232
|
specification_version: 4
|
|
228
233
|
summary: Find and parse manifests
|
|
229
234
|
test_files: []
|