cyclonedx-cocoapods 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b3c1577d54844759e40218fb3b0876014aa8a2eac1a8ea2be2512cab13d79f9
4
- data.tar.gz: dd99bd2aa09a1d6ecd956fdd3118a064df6662026b6816ee86f9a2f553347068
3
+ metadata.gz: a7dce97ad0ab74a34fbe0e768172779ccbb53189ab786742275b1c5b5f00b7d2
4
+ data.tar.gz: bfb1e7e6a22778c85995bd7ee7c43cc6aa21896362d4b2cb4ad5fa4b137f4d61
5
5
  SHA512:
6
- metadata.gz: 5e82c25c27de0fbede464d04a06b9b7f06c3fc79550041835395c6fe5aa32a0f1c4bba1d391b988ff6d39107f696960f064730de43ef8c0f0e8000d576cd1010
7
- data.tar.gz: '048fa99979dd4e606b4952412dad3675bad2ebe3e45eccd8513089f18908d5594213031cb85130434f002d7a8edbafbb67818e602c648b49137768e8085c445b'
6
+ metadata.gz: 2b96c4c4d2225b5aa8f70b4fad6bab563ecfe863b3ec3a2018cfa9d0a5c12e99b8569ff43a52a8ce1b3ec31587af8c705b7a9c9fcba1f13598392a2be10c2dff
7
+ data.tar.gz: 0be60a77ebd3c442bd25481731eb52fdb53338e8b15d6996396d1b49f17ef778db0e686a9a14b12bd5e0c1902a68087b2926c19bd22852cf1fad16974270aec2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.0.0]
8
+
9
+ ### Added
10
+ - Added JSON output if the specified `output` has a `.json` suffix. ([Issue #62](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/62)) [@jeremylong](https://github.com/jeremylong).
11
+ - Added CLI options to set manufacturer metadata about the component being scanned (five separate parameters). ([Issue #72](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/72)) [@jeremylong](https://github.com/jeremylong).
12
+ - Added CLI options to set the VCS URL and build URL of the component being scanned. ([PR #82](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/82)) [@jeremylong](https://github.com/jeremylong).
13
+
14
+ ### Changed
15
+ - Updated to use v1.6 of the CycloneDX specification. ([PR #81](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/81)) [@jeremylong](https://github.com/jeremylong).
16
+ - Updated to use newer `tools` section elements. ([PR #80](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/80)) [@jeremylong](https://github.com/jeremylong).
17
+ - Updated to use a purl for the `bom-ref` of the component being scanned. When analyzing an app the purl will start with `pkg:generic`. ([PR #84](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/84)) [@jeremylong](https://github.com/jeremylong).
18
+ - Changed the short `-b` CLI parameter to specify the build URL instead of the bom file version. Use `--bom-version` to specify the bom file version if needed. ([PR #82](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/82)) [@jeremylong](https://github.com/jeremylong).
19
+ - Changed the short `-s` CLI parameter to specify the source VCS URL instead of the shortened string lengths. Use `--shortened-strings` to specify the max length of strings if needed. ([PR #82](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/82)) [@jeremylong](https://github.com/jeremylong).
20
+
21
+ ### Fixed
22
+ - Fixed XML output when Pod description contains a null byte. ([Issue #85](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/85)) [@fnxpt](https://github.com/fnxpt).
23
+
24
+ ## [1.4.1]
25
+
26
+ ### Changed
27
+ - Minimum Ruby version is now v2.6.3 so the [Array.union](https://apidock.com/ruby/v2_6_3/Array/union) function can be used.
28
+
29
+ ### Fixed
30
+ - Improved performance when analyzing a Podfile with many pods. ([Issue #78](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/78)) [@macblazer](https://github.com/macblazer).
31
+
7
32
  ## [1.4.0]
8
33
 
9
34
  ### Added
data/README.md CHANGED
@@ -9,7 +9,9 @@
9
9
 
10
10
  # CycloneDX CocoaPods (Objective-C/Swift)
11
11
 
12
- The CycloneDX CocoaPods Gem creates a valid CycloneDX software bill-of-material document from all [CocoaPods](https://cocoapods.org/) project dependencies. CycloneDX is a lightweight BoM specification that is easily created, human readable, and simple to parse.
12
+ The CycloneDX CocoaPods Gem creates a valid CycloneDX software bill-of-material document from all
13
+ [CocoaPods](https://cocoapods.org/) project dependencies. CycloneDX is a lightweight BOM specification
14
+ that is easily created, human readable, and simple to parse.
13
15
 
14
16
  ## Installation
15
17
 
@@ -21,18 +23,19 @@ The CycloneDX CocoaPods Gem creates a valid CycloneDX software bill-of-material
21
23
 
22
24
  ### From Source
23
25
 
24
- First, clone/copy the source code from GitHub. Then in the source code directory run these commands (substituting the actual version number for `x.x.x`):
26
+ First, clone/copy the source code from GitHub. Then in the source code directory run these
27
+ commands (substituting the actual version number for `x.x.x`):
25
28
 
26
29
  ```shell
27
30
  gem build cyclonedx-cocoapods.gemspec
28
31
  gem install cyclonedx-cocoapods-x.x.x.gem
29
32
  ```
30
33
 
31
- Building from source requires Ruby 2.4.0 or newer.
34
+ Building from source requires Ruby 2.6.3 or newer.
32
35
 
33
36
  ## Compatibility
34
37
 
35
- *cyclonedx-cocoapods* aims to produce SBOMs according to the latest CycloneDX specification, which currently is [1.5](https://cyclonedx.org/docs/1.5/xml/).
38
+ *cyclonedx-cocoapods* aims to produce SBOMs according to the latest CycloneDX specification, which currently is [1.6](https://cyclonedx.org/docs/1.6/xml/).
36
39
  You can use the [CycloneDX CLI](https://github.com/CycloneDX/cyclonedx-cli#convert-command) to convert between multiple BOM formats or specification versions.
37
40
 
38
41
  ## Usage
@@ -49,38 +52,53 @@ OPTIONS
49
52
 
50
53
  BOM Generation
51
54
  -p, --path path Path to CocoaPods project directory (default: current directory)
52
- -o, --output bom_file_path Path to output the bom.xml file to (default: "bom.xml")
53
- -b, --bom-version bom_version Version of the generated BOM (default: "1")
55
+ -o, --output bom_file_path Path to output the bom file to (default: "bom.xml"); if a *.json file is specified the output format will be JSON
56
+ --bom-version bom_version Version of the generated BOM (default: "1")
54
57
  -x, --exclude-test-targets Eliminate Podfile targets whose name contains the word "test"
55
- -s, --shortened-strings length Trim author, publisher, and purl to <length> characters; this may cause data loss but can improve compatibility with other systems
58
+ --shortened-strings length Trim author, publisher, and purl to <length> characters; this may cause data loss but can improve compatibility with other systems
56
59
 
57
60
  Component Metadata
61
+ If a podspec file is present the name, version, and type do not need to be specified as they will be set automatically.
58
62
  -n, --name name (If specified version and type are also required) Name of the component for which the BOM is generated
59
63
  -v, --version version Version of the component for which the BOM is generated
60
64
  -t, --type type Type of the component for which the BOM is generated (one of application|framework|library|container|operating-system|device|firmware|file)
61
65
  -g, --group group Group of the component for which the BOM is generated
66
+ -s, --source source_url Optional: The version control system URL of the component for the BOM is generated
67
+ -b, --build build_url Optional: The build URL of the component for which the BOM is generated
68
+
69
+ Manufacturer Metadata
70
+ --manufacturer-name name Name of the manufacturer
71
+ --manufacturer-url url URL of the manufacturer
72
+ --manufacturer-contact-name name
73
+ Name of the manufacturer contact
74
+ --manufacturer-email email Email of the manufacturer contact
75
+ --manufacturer-phone phone Phone number of the manufacturer contact
62
76
  ```
63
77
 
64
- **Output:** BoM file at specified location, `./bom.xml` if not specified
78
+ **Output:** BOM file at specified location, `./bom.xml` if not specified
65
79
 
66
80
  ### Example
67
81
 
68
82
  ```shell
69
- % cyclonedx-cocoapods --path /path/to/cocoapods/project --output /path/to/bom.xml --version 6
83
+ % cyclonedx-cocoapods --path /path/to/cocoapods/project --output /path/to/bom.xml --version 6
70
84
  ```
71
85
 
72
86
  #### Specific example
73
87
 
74
- This repo contains a file named `example_bom.xml` that was generated with this tool.
88
+ This repo contains files named `example_bom.xml` and `example_bom.json` that were generated with this tool.
75
89
 
76
- It represents the open source [PodsUpdater application](https://github.com/kizitonwose/PodsUpdater). The PodsUpdater code was checked out,
77
- then these two commands were run in the checked out code directory.
90
+ They represent the open source [PodsUpdater application](https://github.com/kizitonwose/PodsUpdater). The PodsUpdater
91
+ code was checked out, then these three commands were run in the checked out code directory.
78
92
 
79
93
  ```shell
80
94
  % pod install
81
- % cyclonedx-cocoapods -n "kizitonwose/PodsUpdater" -v 1.0.3 -t application --output example_bom.xml
95
+ % cyclonedx-cocoapods -n "kizitonwose-PodsUpdater" -v 1.0.3 -t application -s https://github.com/kizitonwose/PodsUpdater --output example_bom.xml
96
+ % cyclonedx-cocoapods -n "kizitonwose-PodsUpdater" -v 1.0.3 -t application -s https://github.com/kizitonwose/PodsUpdater --output example_bom.json
82
97
  ```
83
98
 
99
+ The JSON file here has also been run through a JSON formatter for easier reading by humans. The original JSON
100
+ output is one long line with no extra whitespace - great for computers, but difficult for humans.
101
+
84
102
  ### A Note About CocoaPod Subspecs
85
103
 
86
104
  Many CocoaPods make use of [subspec functionality](https://guides.cocoapods.org/syntax/podspec.html#subspec).
@@ -127,7 +127,8 @@ module CycloneDX
127
127
  xml_add_author(xml, trim_strings_length)
128
128
  xml.name_ name
129
129
  xml.version version.to_s
130
- xml.description { xml.cdata description } unless description.nil?
130
+ # Use `dump` to escape non-printing characters, then remove the starting/trailing double-quotes from `dump`.
131
+ xml.description { xml.cdata description.dump[1..-2] } unless description.nil?
131
132
  unless checksum.nil?
132
133
  xml.hashes do
133
134
  xml.hash_(checksum, alg: CHECKSUM_ALGORITHM)
@@ -149,7 +150,57 @@ module CycloneDX
149
150
  end
150
151
  end
151
152
 
153
+ def to_json_component(manifest_path, trim_strings_length = 0)
154
+ {
155
+ type: 'library',
156
+ 'bom-ref': purl,
157
+ author: trim(author, trim_strings_length),
158
+ publisher: trim(author, trim_strings_length),
159
+ name: name,
160
+ version: version.to_s,
161
+ description: description,
162
+ hashes: generate_json_hashes,
163
+ licenses: generate_json_licenses,
164
+ purl: purl,
165
+ externalReferences: generate_json_external_references,
166
+ evidence: generate_json_evidence(manifest_path)
167
+ }.compact
168
+ end
169
+
170
+ def generate_json_external_references
171
+ refs = []
172
+ refs << { type: HOMEPAGE_REFERENCE_TYPE, url: homepage } if homepage
173
+ refs.empty? ? nil : refs
174
+ end
175
+
176
+ def generate_json_evidence(manifest_path)
177
+ {
178
+ identity: {
179
+ field: 'purl',
180
+ confidence: 0.6,
181
+ methods: [
182
+ {
183
+ technique: 'manifest-analysis',
184
+ confidence: 0.6,
185
+ value: manifest_path
186
+ }
187
+ ]
188
+ }
189
+ }
190
+ end
191
+
152
192
  class License
193
+ def to_json_component
194
+ {
195
+ license: {
196
+ id: identifier_type == :id ? identifier : nil,
197
+ name: identifier_type == :name ? identifier : nil,
198
+ text: text,
199
+ url: url
200
+ }.compact
201
+ }
202
+ end
203
+
153
204
  def add_to_bom(xml)
154
205
  xml.license do
155
206
  xml.id identifier if identifier_type == :id
@@ -159,6 +210,20 @@ module CycloneDX
159
210
  end
160
211
  end
161
212
  end
213
+
214
+ private
215
+
216
+ def generate_json_licenses
217
+ license ? [license.to_json_component] : nil
218
+ end
219
+
220
+ def generate_json_hashes
221
+ checksum ? [{ alg: CHECKSUM_ALGORITHM, content: checksum }] : nil
222
+ end
223
+
224
+ def trim(str, trim_strings_length)
225
+ trim_strings_length.zero? ? str : str&.slice(0, trim_strings_length)
226
+ end
162
227
  end
163
228
 
164
229
  class Component
@@ -167,52 +232,214 @@ module CycloneDX
167
232
  xml.group group unless group.nil?
168
233
  xml.name_ name
169
234
  xml.version version
235
+
236
+ if !build_system.nil? || !vcs.nil?
237
+ xml.externalReferences do
238
+ if build_system
239
+ xml.reference(type: 'build-system') do
240
+ xml.url build_system
241
+ end
242
+ end
243
+
244
+ if vcs
245
+ xml.reference(type: 'vcs') do
246
+ xml.url vcs
247
+ end
248
+ end
249
+ end
250
+ end
251
+ xml.purl bomref
252
+ end
253
+ end
254
+
255
+ def to_json_component
256
+ {
257
+ type: type,
258
+ 'bom-ref': bomref,
259
+ group: group,
260
+ name: name,
261
+ version: version,
262
+ purl: bomref,
263
+ externalReferences: generate_json_external_references
264
+ }.compact
265
+ end
266
+
267
+ private
268
+
269
+ def generate_json_external_references
270
+ refs = []
271
+ refs << { type: 'build-system', url: build_system } if build_system
272
+ refs << { type: 'vcs', url: vcs } if vcs
273
+ refs.empty? ? nil : refs
274
+ end
275
+ end
276
+
277
+ # Represents manufacturer information in a CycloneDX BOM
278
+ # Handles generation of manufacturer XML elements including basic info and contact details
279
+ # Used when generating BOM metadata for CycloneDX specification
280
+ class Manufacturer
281
+ def add_to_bom(xml)
282
+ return if all_attributes_nil?
283
+
284
+ xml.manufacturer do
285
+ add_basic_info(xml)
286
+ add_contact_info(xml)
287
+ end
288
+ end
289
+
290
+ def to_json_manufacturer
291
+ return nil if all_attributes_nil?
292
+
293
+ {
294
+ name: name,
295
+ url: url,
296
+ contact: generate_json_contact
297
+ }.compact
298
+ end
299
+
300
+ private
301
+
302
+ def generate_json_contact
303
+ return nil if contact_info_nil?
304
+
305
+ [
306
+ {
307
+ name: contact_name,
308
+ email: email,
309
+ phone: phone
310
+ }.compact
311
+ ]
312
+ end
313
+
314
+ def all_attributes_nil?
315
+ [name, url, contact_name, email, phone].all?(&:nil?)
316
+ end
317
+
318
+ def add_basic_info(xml)
319
+ xml.name_ name unless name.nil?
320
+ xml.url url unless url.nil?
321
+ end
322
+
323
+ def add_contact_info(xml)
324
+ return if contact_info_nil?
325
+
326
+ xml.contact do
327
+ xml.name_ contact_name unless contact_name.nil?
328
+ xml.email email unless email.nil?
329
+ xml.phone phone unless phone.nil?
170
330
  end
171
331
  end
332
+
333
+ def contact_info_nil?
334
+ contact_name.nil? && email.nil? && phone.nil?
335
+ end
172
336
  end
173
337
 
174
338
  # Turns the internal model data into an XML bom.
175
339
  class BOMBuilder
176
- NAMESPACE = 'http://cyclonedx.org/schema/bom/1.5'
340
+ NAMESPACE = 'http://cyclonedx.org/schema/bom/1.6'
177
341
 
178
- attr_reader :component, :pods, :manifest_path, :dependencies
342
+ attr_reader :component, :pods, :manifest_path, :dependencies, :manufacturer
179
343
 
180
- def initialize(pods:, manifest_path:, component: nil, dependencies: nil)
344
+ def initialize(pods:, manifest_path:, component: nil, dependencies: nil, manufacturer: nil)
181
345
  @pods = pods.sort_by(&:purl)
182
346
  @manifest_path = manifest_path
183
347
  @component = component
184
348
  @dependencies = dependencies&.sort
349
+ @manufacturer = manufacturer
185
350
  end
186
351
 
187
- def bom(version: 1, trim_strings_length: 0)
188
- unless version.to_i.positive?
189
- raise ArgumentError,
190
- "Incorrect version: #{version} should be an integer greater than 0"
191
- end
192
-
193
- unless trim_strings_length.is_a?(Integer) && (trim_strings_length.positive? || trim_strings_length.zero?)
194
- raise ArgumentError,
195
- "Incorrect string length: #{trim_strings_length} should be an integer greater than 0"
196
- end
352
+ def bom(version: 1, trim_strings_length: 0, format: :xml)
353
+ validate_version(version)
354
+ validate_trim_length(trim_strings_length)
355
+ validate_format(format)
197
356
 
198
- unchecked_bom(version: version, trim_strings_length: trim_strings_length)
357
+ unchecked_bom(version: version, trim_strings_length: trim_strings_length, format: format)
199
358
  end
200
359
 
201
360
  private
202
361
 
362
+ def validate_version(version)
363
+ return if version.to_i.positive?
364
+
365
+ raise ArgumentError, "Incorrect version: #{version} should be an integer greater than 0"
366
+ end
367
+
368
+ def validate_trim_length(trim_strings_length)
369
+ return if trim_strings_length.is_a?(Integer) && (trim_strings_length.positive? || trim_strings_length.zero?)
370
+
371
+ raise ArgumentError, "Incorrect string length: #{trim_strings_length} should be an integer greater than 0"
372
+ end
373
+
374
+ def validate_format(format)
375
+ return if %i[xml json].include?(format)
376
+
377
+ raise ArgumentError, "Incorrect format: #{format} should be either :xml or :json"
378
+ end
379
+
203
380
  # does not verify parameters because the public method does that.
204
- def unchecked_bom(version: 1, trim_strings_length: 0)
381
+ def unchecked_bom(version:, trim_strings_length:, format:)
382
+ case format
383
+ when :json
384
+ generate_json(version: version, trim_strings_length: trim_strings_length)
385
+ when :xml
386
+ generate_xml(version: version, trim_strings_length: trim_strings_length)
387
+ end
388
+ end
389
+
390
+ def generate_xml(version:, trim_strings_length:)
205
391
  Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
206
392
  xml.bom(xmlns: NAMESPACE, version: version.to_i.to_s, serialNumber: "urn:uuid:#{SecureRandom.uuid}") do
207
393
  bom_metadata(xml)
208
-
209
394
  bom_components(xml, pods, manifest_path, trim_strings_length)
210
-
211
395
  bom_dependencies(xml, dependencies)
212
396
  end
213
397
  end.to_xml
214
398
  end
215
399
 
400
+ def generate_json(version:, trim_strings_length:)
401
+ {
402
+ '$schema': 'https://cyclonedx.org/schema/bom-1.6.schema.json',
403
+ bomFormat: 'CycloneDX',
404
+ specVersion: '1.6',
405
+ serialNumber: "urn:uuid:#{SecureRandom.uuid}",
406
+ version: version.to_s,
407
+ metadata: generate_json_metadata,
408
+ components: generate_json_components(trim_strings_length),
409
+ dependencies: generate_json_dependencies
410
+ }.to_json
411
+ end
412
+
413
+ def generate_json_metadata
414
+ {
415
+ timestamp: Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ'),
416
+ tools: {
417
+ components: [{
418
+ type: 'application',
419
+ group: 'CycloneDX',
420
+ name: 'cyclonedx-cocoapods',
421
+ version: VERSION
422
+ }]
423
+ },
424
+ component: component&.to_json_component,
425
+ manufacturer: manufacturer&.to_json_manufacturer
426
+ }.compact
427
+ end
428
+
429
+ def generate_json_components(trim_strings_length)
430
+ pods.map { |pod| pod.to_json_component(manifest_path, trim_strings_length) }
431
+ end
432
+
433
+ def generate_json_dependencies
434
+ return nil unless dependencies
435
+
436
+ dependencies.map do |ref, deps|
437
+ {
438
+ ref: ref,
439
+ dependsOn: deps.sort
440
+ }
441
+ end
442
+ end
216
443
  def bom_components(xml, pods, manifest_path, trim_strings_length)
217
444
  xml.components do
218
445
  pods.each do |pod|
@@ -238,15 +465,17 @@ module CycloneDX
238
465
  xml.timestamp Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
239
466
  bom_tools(xml)
240
467
  component&.add_to_bom(xml)
468
+ manufacturer&.add_to_bom(xml)
241
469
  end
242
470
  end
243
-
244
471
  def bom_tools(xml)
245
472
  xml.tools do
246
- xml.tool do
247
- xml.vendor 'CycloneDX'
248
- xml.name_ 'cyclonedx-cocoapods'
249
- xml.version VERSION
473
+ xml.components do
474
+ xml.component(type: 'application') do
475
+ xml.group 'CycloneDX'
476
+ xml.name_ 'cyclonedx-cocoapods'
477
+ xml.version VERSION
478
+ end
250
479
  end
251
480
  end
252
481
  end
@@ -25,7 +25,9 @@ require 'optparse'
25
25
 
26
26
  require_relative 'bom_builder'
27
27
  require_relative 'component'
28
+ require_relative 'manufacturer'
28
29
  require_relative 'podfile_analyzer'
30
+ require_relative 'podspec_analyzer'
29
31
 
30
32
  module CycloneDX
31
33
  module CocoaPods
@@ -36,12 +38,13 @@ module CycloneDX
36
38
  def run
37
39
  setup_logger # Needed in case we have errors while processing CLI parameters
38
40
  options = parse_options
41
+ determine_output_format(options)
39
42
  setup_logger(verbose: options[:verbose])
40
43
  @logger.debug "Running cyclonedx-cocoapods with options: #{options}"
41
44
 
42
- component, pods, manifest_path, dependencies = analyze(options)
45
+ component, manufacturer, pods, manifest_path, dependencies = analyze(options)
43
46
 
44
- build_and_write_bom(options, component, pods, manifest_path, dependencies)
47
+ build_and_write_bom(options, component, manufacturer, pods, manifest_path, dependencies)
45
48
  rescue StandardError => e
46
49
  @logger.error ([e.message] + e.backtrace).join($INPUT_RECORD_SEPARATOR)
47
50
  exit 1
@@ -76,7 +79,8 @@ module CycloneDX
76
79
  parsed_options[:path] = path
77
80
  end
78
81
  options.on('-o', '--output bom_file_path',
79
- 'Path to output the bom.xml file to (default: "bom.xml")') do |bom_file_path|
82
+ 'Path to output the bom file to (default: "bom.xml"); ' \
83
+ 'if a *.json file is specified the output format will be JSON') do |bom_file_path|
80
84
  parsed_options[:bom_file_path] = bom_file_path
81
85
  end
82
86
  options.on('-b', '--bom-version bom_version', Integer,
@@ -94,18 +98,18 @@ module CycloneDX
94
98
  end
95
99
 
96
100
  options.separator("\n Component Metadata\n")
101
+ options.separator(' If a podspec file is present the name, version, and type do not ' \
102
+ "need to be specified as they will be set automatically.\n")
97
103
  options.on('-n', '--name name',
98
104
  '(If specified version and type are also required) Name of the ' \
99
105
  'component for which the BOM is generated') do |name|
100
106
  parsed_options[:name] = name
101
107
  end
102
108
  options.on('-v', '--version version', 'Version of the component for which the BOM is generated') do |version|
103
- begin
104
- Gem::Version.new(version)
105
- parsed_options[:version] = version
106
- rescue StandardError => e
107
- raise OptionParser::InvalidArgument, e.message
108
- end
109
+ Gem::Version.new(version)
110
+ parsed_options[:version] = version
111
+ rescue StandardError => e
112
+ raise OptionParser::InvalidArgument, e.message
109
113
  end
110
114
  options.on('-t', '--type type',
111
115
  'Type of the component for which the BOM is generated ' \
@@ -120,6 +124,37 @@ module CycloneDX
120
124
  options.on('-g', '--group group', 'Group of the component for which the BOM is generated') do |group|
121
125
  parsed_options[:group] = group
122
126
  end
127
+ options.on('-s', '--source source_url',
128
+ 'Optional: The version control system URL of the component for the BOM is generated') do |vcs|
129
+ parsed_options[:vcs] = vcs
130
+ end
131
+ options.on('-b', '--build build_url',
132
+ 'Optional: The build URL of the component for which the BOM is generated') do |build|
133
+ parsed_options[:build] = build
134
+ end
135
+
136
+ # Add this section after the "Component Metadata" options group
137
+ options.separator("\n Manufacturer Metadata\n")
138
+ options.on('--manufacturer-name name',
139
+ 'Name of the manufacturer') do |name|
140
+ parsed_options[:manufacturer_name] = name
141
+ end
142
+ options.on('--manufacturer-url url',
143
+ 'URL of the manufacturer') do |url|
144
+ parsed_options[:manufacturer_url] = url
145
+ end
146
+ options.on('--manufacturer-contact-name name',
147
+ 'Name of the manufacturer contact') do |contact_name|
148
+ parsed_options[:manufacturer_contact_name] = contact_name
149
+ end
150
+ options.on('--manufacturer-email email',
151
+ 'Email of the manufacturer contact') do |email|
152
+ parsed_options[:manufacturer_email] = email
153
+ end
154
+ options.on('--manufacturer-phone phone',
155
+ 'Phone number of the manufacturer contact') do |phone|
156
+ parsed_options[:manufacturer_phone] = phone
157
+ end
123
158
  end.parse!
124
159
 
125
160
  if !parsed_options[:name].nil? && (parsed_options[:version].nil? || parsed_options[:type].nil?)
@@ -130,13 +165,15 @@ module CycloneDX
130
165
  parsed_options
131
166
  end
132
167
 
133
- def analyze(options)
134
- analyzer = PodfileAnalyzer.new(logger: @logger, exclude_test_targets: options[:exclude_test_targets])
135
- podfile, lockfile = analyzer.ensure_podfile_and_lock_are_present(options)
136
- pods, dependencies = analyzer.parse_pods(podfile, lockfile)
137
- analyzer.populate_pods_with_additional_info(pods)
168
+ def determine_output_format(options)
169
+ options[:format] = options[:bom_file_path]&.end_with?('.json') ? :json : :xml
170
+ end
138
171
 
139
- component = component_from_options(options)
172
+ def analyze(options)
173
+ analyzer, dependencies, lockfile, podfile, pods = analyze_podfile(options)
174
+ podspec = analyze_podspec(options)
175
+ component = component_from_options(options, podspec)
176
+ manufacturer = manufacturer_from_options(options)
140
177
 
141
178
  unless component.nil?
142
179
  # add top level pods to main component
@@ -150,22 +187,89 @@ module CycloneDX
150
187
  manifest_path = Pathname.pwd.basename + manifest_path.relative_path_from(Pathname.pwd)
151
188
  end
152
189
 
153
- [component, pods, manifest_path, dependencies]
190
+ [component, manufacturer, pods, manifest_path, dependencies]
191
+ end
192
+
193
+ def analyze_podspec(options)
194
+ spec_analyzer = PodspecAnalyzer.new(logger: @logger)
195
+ podspec = spec_analyzer.ensure_podspec_is_present(options)
196
+ spec = spec_analyzer.parse_podspec(podspec) unless podspec.nil?
197
+ spec
198
+ end
199
+
200
+ def analyze_podfile(options)
201
+ analyzer = PodfileAnalyzer.new(logger: @logger, exclude_test_targets: options[:exclude_test_targets])
202
+ podfile, lockfile = analyzer.ensure_podfile_and_lock_are_present(options)
203
+ pods, dependencies = analyzer.parse_pods(podfile, lockfile)
204
+ analyzer.populate_pods_with_additional_info(pods)
205
+ [analyzer, dependencies, lockfile, podfile, pods]
154
206
  end
155
207
 
156
- def build_and_write_bom(options, component, pods, manifest_path, dependencies)
208
+ def build_and_write_bom(options, component, manufacturer, pods, manifest_path, dependencies)
157
209
  builder = BOMBuilder.new(pods: pods, manifest_path: manifest_path,
158
- component: component, dependencies: dependencies)
210
+ component: component, manufacturer: manufacturer, dependencies: dependencies)
159
211
  bom = builder.bom(version: options[:bom_version] || 1,
160
- trim_strings_length: options[:trim_strings_length] || 0)
212
+ trim_strings_length: options[:trim_strings_length] || 0,
213
+ format: options[:format])
161
214
  write_bom_to_file(bom: bom, options: options)
162
215
  end
163
216
 
164
- def component_from_options(options)
165
- return unless options[:name]
217
+ def component_from_options(options, podspec)
218
+ return unless options[:name] || !podspec.nil?
219
+
220
+ ensure_options_match(options, podspec)
166
221
 
167
222
  Component.new(group: options[:group], name: options[:name], version: options[:version],
168
- type: options[:type])
223
+ type: options[:type], build_system: options[:build], vcs: options[:vcs])
224
+ end
225
+
226
+ def manufacturer_from_options(options)
227
+ Manufacturer.new(
228
+ name: options[:manufacturer_name],
229
+ url: options[:manufacturer_url],
230
+ contact_name: options[:manufacturer_contact_name],
231
+ email: options[:manufacturer_email],
232
+ phone: options[:manufacturer_phone]
233
+ )
234
+ end
235
+
236
+ def ensure_options_match(options, podspec)
237
+ validate_name_option(options, podspec)
238
+ validate_version_option(options, podspec)
239
+ validate_group_option(options, podspec)
240
+ validate_type_option(options, podspec)
241
+ end
242
+
243
+ def validate_name_option(options, podspec)
244
+ if !podspec.nil? && options[:name] && options[:name] != podspec.name
245
+ raise OptionParser::InvalidArgument,
246
+ "Component name '#{options[:name]}' does not match podspec name '#{podspec.name}'"
247
+ end
248
+ options[:name] ||= podspec&.name
249
+ end
250
+
251
+ def validate_version_option(options, podspec)
252
+ if !podspec.nil? && options[:version] && options[:version] != podspec.version.to_s
253
+ raise OptionParser::InvalidArgument,
254
+ "Component version '#{options[:version]}' does not match podspec version '#{podspec.version}'"
255
+ end
256
+ options[:version] ||= podspec&.version&.to_s
257
+ end
258
+
259
+ def validate_type_option(options, podspec)
260
+ if !podspec.nil? && options[:type] && ptions[:type] != 'library'
261
+ raise OptionParser::InvalidArgument, "Component type must be 'library' when using a podspec"
262
+ end
263
+
264
+ return if podspec.nil?
265
+
266
+ options[:type] = 'cocoapods'
267
+ end
268
+
269
+ def validate_group_option(options, podspec)
270
+ return if podspec.nil?
271
+ raise OptionParser::InvalidArgument, 'Component group must not be specified when using a podspec' unless
272
+ options[:group].nil?
169
273
  end
170
274
 
171
275
  def setup_logger(verbose: true)
@@ -21,29 +21,89 @@
21
21
 
22
22
  module CycloneDX
23
23
  module CocoaPods
24
+ # Represents a software component in the CycloneDX BOM specification
25
+ #
26
+ # A component is a self-contained unit of software that can be used as a building block
27
+ # in the architecture of a software system. Components can be of different types like
28
+ # libraries, frameworks, or applications.
29
+ #
30
+ # @attr_reader [String, nil] group The group/organization identifier of the component
31
+ # @attr_reader [String] name The name of the component
32
+ # @attr_reader [String] version The version string of the component
33
+ # @attr_reader [String] type The type of component (must be one of VALID_COMPONENT_TYPES)
34
+ # @attr_reader [String] bomref The unique reference ID for this component in the BOM
35
+ # @attr_reader [String, nil] build_system The build system information
36
+ # @attr_reader [String, nil] vcs The version control system information
37
+ #
38
+ # @example Creating a new component
39
+ # component = Component.new(
40
+ # name: "AFNetworking",
41
+ # version: "4.0.1",
42
+ # type: "library"
43
+ # )
24
44
  class Component
25
45
  VALID_COMPONENT_TYPES = %w[application framework library container operating-system device firmware file].freeze
26
46
 
27
- attr_reader :group, :name, :version, :type, :bomref
47
+ attr_reader :group, :name, :version, :type, :bomref, :build_system, :vcs
28
48
 
29
- def initialize(name:, version:, type:, group: nil)
30
- raise ArgumentError, 'Group, if specified, must be non empty' if !group.nil? && group.to_s.strip.empty?
31
- raise ArgumentError, 'Name must be non empty' if name.nil? || name.to_s.strip.empty?
49
+ def initialize(name:, version:, type:, group: nil, build_system: nil, vcs: nil)
50
+ # cocoapods is a special case to correctly build a purl
51
+ package_type = type == 'cocoapods' ? 'cocoapods' : 'generic'
52
+ @type = type == 'cocoapods' ? 'library' : type
32
53
 
33
- Gem::Version.new(version) # To check that the version string is well formed
34
- unless VALID_COMPONENT_TYPES.include?(type)
35
- raise ArgumentError, "#{type} is not valid component type (#{VALID_COMPONENT_TYPES.join('|')})"
36
- end
54
+ validate_attributes(name, version, @type, group)
37
55
 
38
56
  @group = group
39
57
  @name = name
40
58
  @version = version
41
- @type = type
42
- @bomref = "#{name}@#{version}"
59
+ @build_system = build_system
60
+ @vcs = vcs
61
+ @bomref = build_purl(package_type, name, group, version)
62
+ end
63
+
64
+ private
65
+
66
+ def build_purl(package_type, name, group, version)
67
+ if group.nil?
68
+ purl_name, subpath = parse_name(name)
69
+ else
70
+ purl_name = "#{CGI.escape(group)}/#{CGI.escape(name)}"
71
+ subpath = ''
72
+ end
73
+ "pkg:#{package_type}/#{purl_name}@#{CGI.escape(version.to_s)}#{subpath}"
74
+ end
75
+
76
+ private
43
77
 
44
- return if group.nil?
78
+ def validate_attributes(name, version, type, group)
79
+ raise ArgumentError, 'Group, if specified, must be non-empty' if exists_and_blank(group)
80
+ raise ArgumentError, 'Name must be non-empty' if missing(name)
81
+
82
+ Gem::Version.new(version) # To check that the version string is well-formed
83
+ return if VALID_COMPONENT_TYPES.include?(type)
84
+
85
+ raise ArgumentError, "#{type} is not valid component type (#{VALID_COMPONENT_TYPES.join('|')})"
86
+ end
87
+
88
+ def parse_name(name)
89
+ purls = name.split('/')
90
+ purl_name = CGI.escape(purls[0])
91
+ subpath = if purls.length > 1
92
+ "##{name.split('/').drop(1).map do |component|
93
+ CGI.escape(component)
94
+ end.join('/')}"
95
+ else
96
+ ''
97
+ end
98
+ [purl_name, subpath]
99
+ end
100
+
101
+ def missing(str)
102
+ str.nil? || str.to_s.strip.empty?
103
+ end
45
104
 
46
- @bomref = "#{group}/#{@bomref}"
105
+ def exists_and_blank(str)
106
+ !str.nil? && str.to_s.strip.empty?
47
107
  end
48
108
  end
49
109
  end
@@ -32,7 +32,7 @@ module CycloneDX
32
32
  attr_accessor :text, :url
33
33
 
34
34
  def initialize(identifier:)
35
- raise ArgumentError, 'License identifier must be non empty' if identifier.nil? || identifier.to_s.strip.empty?
35
+ raise ArgumentError, 'License identifier must be non-empty' if identifier.nil? || identifier.to_s.strip.empty?
36
36
 
37
37
  @identifier = SPDX_LICENSES.find { |license_id| license_id.downcase == identifier.to_s.downcase }
38
38
  @identifier_type = @identifier.nil? ? :name : :id
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This file is part of CycloneDX CocoaPods
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # SPDX-License-Identifier: Apache-2.0
19
+ # Copyright (c) OWASP Foundation. All Rights Reserved.
20
+ #
21
+
22
+ module CycloneDX
23
+ module CocoaPods
24
+ # Represents manufacturer information in a CycloneDX BOM
25
+ #
26
+ # The Manufacturer class holds details about the manufacturer of a component,
27
+ # including company information and contact details.
28
+ #
29
+ # @attr_reader [String] name The name of the manufacturing organization
30
+ # @attr_reader [String] url The URL of the manufacturer's website
31
+ # @attr_reader [String] contact_name Name of the manufacturer contact person
32
+ # @attr_reader [String] email Email address of the manufacturer contact
33
+ # @attr_reader [String] phone Phone number of the manufacturer contact
34
+ #
35
+ # @example Creating a manufacturer with basic info
36
+ # manufacturer = Manufacturer.new(
37
+ # name: "ACME Corp",
38
+ # url: "https://acme.example"
39
+ # )
40
+ #
41
+ # @example Creating a manufacturer with full contact details
42
+ # manufacturer = Manufacturer.new(
43
+ # name: "ACME Corp",
44
+ # url: "https://acme.example",
45
+ # contact_name: "John Doe",
46
+ # email: "john@acme.example",
47
+ # phone: "+1-555-123-4567"
48
+ # )
49
+ class Manufacturer
50
+ attr_reader :name, :url, :contact_name, :email, :phone
51
+
52
+ def initialize(name: nil, url: nil, contact_name: nil, email: nil, phone: nil)
53
+ validate_parameters(name, url, contact_name, email, phone)
54
+
55
+ @name = name
56
+ @url = url
57
+ @contact_name = contact_name
58
+ @email = email
59
+ @phone = phone
60
+ end
61
+
62
+ private
63
+
64
+ def validate_parameters(name, url, contact_name, email, phone)
65
+ raise ArgumentError, 'name, if specified, must be non-empty' if blank(name)
66
+ raise ArgumentError, 'URL, if specified, must be non-empty' if blank(url)
67
+ raise ArgumentError, 'Contact name, if specified, must be non-empty' if blank(contact_name)
68
+ raise ArgumentError, 'Email, if specified, must be non-empty' if blank(email)
69
+ raise ArgumentError, 'Phone, if specified, must be non-empty' if blank(phone)
70
+ end
71
+
72
+ def blank(str)
73
+ !str.nil? && str.to_s.strip.empty?
74
+ end
75
+ end
76
+ end
77
+ end
@@ -31,20 +31,20 @@ module CycloneDX
31
31
  attr_reader :version
32
32
  # Anything responding to :source_qualifier
33
33
  attr_reader :source
34
- # xs:anyURI - https://cyclonedx.org/docs/1.5/xml/#type_externalReference
34
+ # xs:anyURI - https://cyclonedx.org/docs/1.6/xml/#type_externalReference
35
35
  attr_reader :homepage
36
- # https://cyclonedx.org/docs/1.5/xml/#type_hashValue (We only use SHA-1 hashes - length == 40)
36
+ # https://cyclonedx.org/docs/1.6/xml/#type_hashValue (We only use SHA-1 hashes - length == 40)
37
37
  attr_reader :checksum
38
38
  # xs:normalizedString
39
39
  attr_reader :author
40
40
  # xs:normalizedString
41
41
  attr_reader :description
42
- # https://cyclonedx.org/docs/1.5/xml/#type_licenseType
42
+ # https://cyclonedx.org/docs/1.6/xml/#type_licenseType
43
43
  # We don't currently support several licenses or license expressions https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
44
44
  attr_reader :license
45
45
 
46
46
  def initialize(name:, version:, source: nil, checksum: nil)
47
- raise ArgumentError, 'Name must be non empty' if name.nil? || name.to_s.empty?
47
+ raise ArgumentError, 'Name must be non-empty' if name.nil? || name.to_s.empty?
48
48
  raise ArgumentError, "Name shouldn't contain spaces" if name.to_s.include?(' ')
49
49
  raise ArgumentError, "Name shouldn't start with a dot" if name.to_s.start_with?('.')
50
50
 
@@ -184,12 +184,18 @@ module CycloneDX
184
184
  end
185
185
  end
186
186
 
187
- def append_all_pod_dependencies(pods_used, pods_cache)
188
- result = pods_used
187
+ # Calculate simple array of all used pods plus their direct dependencies
188
+ #
189
+ # @param [Array<String>] top_level_pods List of pod names that are directly imported by the Podfile
190
+ # @param [Hash<String,Array<String>>] pods_cache Dependency information directly from the Podfile.lock;
191
+ # keys are string pod names, values are list of direct dependencies of the given pod.
192
+ # @return [Array<String>, Hash<String,Array<String>>] First element is list of all used pod names.
193
+ # Second element is a hash: keys are string pod names, values are the direct dependencies of that pod.
194
+ def append_all_pod_dependencies(top_level_pods, pods_cache)
195
+ result = top_level_pods
189
196
  original_number = 0
190
- dependencies_hash = {}
191
197
 
192
- # Loop adding pod dependencies until we are not adding any more dependencies to the result
198
+ # Loop adding pod dependencies until we are not adding any more dependencies to the result.
193
199
  # This brings in all the transitive dependencies of every top level pod.
194
200
  # Note this also handles two edge cases:
195
201
  # 1. Having a Podfile with no pods used.
@@ -197,15 +203,20 @@ module CycloneDX
197
203
  while result.length != original_number
198
204
  original_number = result.length
199
205
 
200
- pods_used.each do |pod_name|
206
+ top_level_pods.each do |pod_name|
201
207
  if pods_cache.key?(pod_name)
202
- result.push(*pods_cache[pod_name])
203
- dependencies_hash[pod_name] = pods_cache[pod_name].empty? ? [] : pods_cache[pod_name]
208
+ # Append all of the dependencies of this pod into the main list, if they aren't already in the list
209
+ result = result.union(pods_cache[pod_name])
204
210
  end
205
211
  end
206
212
 
207
- result = result.uniq
208
- pods_used = result
213
+ top_level_pods = result
214
+ end
215
+
216
+ # Now that we have the simple list of all unique pods, grab their direct dependencies
217
+ dependencies_hash = {}
218
+ result.each do |pod_name|
219
+ dependencies_hash[pod_name] = pods_cache.key?(pod_name) ? pods_cache[pod_name] : []
209
220
  end
210
221
 
211
222
  [result, dependencies_hash]
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This file is part of CycloneDX CocoaPods
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the “License”);
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an “AS IS” BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # SPDX-License-Identifier: Apache-2.0
19
+ # Copyright (c) OWASP Foundation. All Rights Reserved.
20
+ #
21
+
22
+ require 'cocoapods'
23
+ require 'cocoapods-core'
24
+ require 'logger'
25
+
26
+ require_relative 'pod'
27
+ require_relative 'pod_attributes'
28
+ require_relative 'source'
29
+
30
+ module CycloneDX
31
+ module CocoaPods
32
+ class PodspecParsingError < StandardError; end
33
+
34
+ # Analyzes CocoaPods podspec files to extract component information for CycloneDX BOM generation
35
+ #
36
+ # The PodspecAnalyzer is responsible for:
37
+ # - Validating and loading podspec files from a given path
38
+ # - Parsing podspec contents to extract pod metadata
39
+ # - Converting podspec source information into standardized Source objects
40
+ #
41
+ # @example
42
+ # analyzer = PodspecAnalyzer.new(logger: Logger.new(STDOUT))
43
+ # podspec = analyzer.ensure_podspec_is_present(path: '/path/to/project')
44
+ # pod = analyzer.parse_podspec(podspec)
45
+ #
46
+ class PodspecAnalyzer
47
+ def initialize(logger:)
48
+ @logger = logger
49
+ end
50
+
51
+ def ensure_podspec_is_present(options)
52
+ project_dir = Pathname.new(options[:path] || Dir.pwd)
53
+ validate_options(project_dir, options)
54
+ initialize_cocoapods_config(project_dir)
55
+
56
+ options[:podspec_path].nil? ? nil : ::Pod::Specification.from_file(options[:podspec_path])
57
+ end
58
+
59
+ def parse_podspec(podspec)
60
+ return nil if podspec.nil?
61
+
62
+ @logger.debug "Parsing podspec from #{podspec.defined_in_file}"
63
+
64
+ Pod.new(
65
+ name: podspec.name,
66
+ version: podspec.version.to_s,
67
+ source: source_from_podspec(podspec),
68
+ checksum: nil
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def validate_options(project_dir, options)
75
+ raise PodspecParsingError, "#{options[:path]} is not a valid directory." unless File.directory?(project_dir)
76
+
77
+ podspec_files = Dir.glob("#{project_dir}/*.podspec{.json,}")
78
+ options[:podspec_path] = podspec_files.first unless podspec_files.empty?
79
+ end
80
+
81
+ def initialize_cocoapods_config(project_dir)
82
+ ::Pod::Config.instance = nil
83
+ ::Pod::Config.instance.installation_root = project_dir
84
+ end
85
+
86
+ def source_from_podspec(podspec)
87
+ return unless podspec.source[:git]
88
+
89
+ Source::GitRepository.new(
90
+ url: podspec.source[:git],
91
+ type: determine_git_ref_type(podspec.source),
92
+ label: determine_git_ref_label(podspec.source)
93
+ )
94
+ end
95
+
96
+ def determine_git_ref_type(source)
97
+ return :tag if source[:tag]
98
+ return :commit if source[:commit]
99
+ return :branch if source[:branch]
100
+
101
+ nil
102
+ end
103
+
104
+ def determine_git_ref_label(source)
105
+ source[:tag] || source[:commit] || source[:branch]
106
+ end
107
+ end
108
+ end
109
+ end
@@ -21,6 +21,6 @@
21
21
 
22
22
  module CycloneDX
23
23
  module CocoaPods
24
- VERSION = '1.4.0'
24
+ VERSION = '2.0.0'
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cyclonedx-cocoapods
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - José González
8
8
  - Kyle Hammond
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2024-10-15 00:00:00.000000000 Z
11
+ date: 2025-01-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: cocoapods
@@ -51,48 +50,6 @@ dependencies:
51
50
  - - "<"
52
51
  - !ruby/object:Gem::Version
53
52
  version: '2.0'
54
- - !ruby/object:Gem::Dependency
55
- name: equivalent-xml
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: 0.6.0
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: 0.6.0
68
- - !ruby/object:Gem::Dependency
69
- name: rake
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '13.0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '13.0'
82
- - !ruby/object:Gem::Dependency
83
- name: rspec
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '3.0'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '3.0'
96
53
  description: CycloneDX is a lightweight software bill-of-material (SBOM) specification
97
54
  designed for use in application security contexts and supply chain component analysis.
98
55
  This Gem generates CycloneDX BOMs from CocoaPods projects.
@@ -113,9 +70,11 @@ files:
113
70
  - lib/cyclonedx/cocoapods/cli_runner.rb
114
71
  - lib/cyclonedx/cocoapods/component.rb
115
72
  - lib/cyclonedx/cocoapods/license.rb
73
+ - lib/cyclonedx/cocoapods/manufacturer.rb
116
74
  - lib/cyclonedx/cocoapods/pod.rb
117
75
  - lib/cyclonedx/cocoapods/pod_attributes.rb
118
76
  - lib/cyclonedx/cocoapods/podfile_analyzer.rb
77
+ - lib/cyclonedx/cocoapods/podspec_analyzer.rb
119
78
  - lib/cyclonedx/cocoapods/source.rb
120
79
  - lib/cyclonedx/cocoapods/spdx-licenses.json
121
80
  - lib/cyclonedx/cocoapods/version.rb
@@ -125,7 +84,6 @@ licenses:
125
84
  metadata:
126
85
  homepage_uri: https://github.com/CycloneDX/cyclonedx-cocoapods
127
86
  source_code_uri: https://github.com/CycloneDX/cyclonedx-cocoapods.git
128
- post_install_message:
129
87
  rdoc_options: []
130
88
  require_paths:
131
89
  - lib
@@ -133,15 +91,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
91
  requirements:
134
92
  - - ">="
135
93
  - !ruby/object:Gem::Version
136
- version: 2.4.0
94
+ version: 2.6.3
137
95
  required_rubygems_version: !ruby/object:Gem::Requirement
138
96
  requirements:
139
97
  - - ">="
140
98
  - !ruby/object:Gem::Version
141
99
  version: '0'
142
100
  requirements: []
143
- rubygems_version: 3.5.16
144
- signing_key:
101
+ rubygems_version: 3.6.2
145
102
  specification_version: 4
146
103
  summary: CycloneDX software bill-of-material (SBOM) generation utility
147
104
  test_files: []