cyclonedx-cocoapods 1.3.0 → 1.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b02ca712eff5c74b3c7a4e3c59ab7a402a87cf2f3053be388f64a702ca0d3a1
4
- data.tar.gz: ca6e3d81e4b255dfcd43add1017e1b5dc939274544b6bade803da0246c544ebc
3
+ metadata.gz: 84ed77501efec7ca77fce507dd1dbc4a29ffb4b8cf45fc6b942eafe3901af95a
4
+ data.tar.gz: 85204bb25786de11c3dc7ec302d016431ebecd7078149081a6b294ba65f756aa
5
5
  SHA512:
6
- metadata.gz: 74af3ed1ceded419670e4e4cc2b69a955730f963ef92510159505809a02681ae39347c91a24ac99d988f142f987465998fdd4bf4629e799d0dc33e01f96fbb9a
7
- data.tar.gz: b1ce5ceae85c7b3ff993109203a53f9f7fa61397f8d58f6b18220ee2ed5e866ab1bc210b6ac48b375de3ec49e6928abe2245413fac56b437403317fb1c2ba783
6
+ metadata.gz: 7f4b84eb0a11f7f6488fe9fccef7806e786db41ea647806046b729b39952172175df7b8884b17c60e5cac0b246a9bfc6e56d8e53f69b3ad9521c9cde0f19726b
7
+ data.tar.gz: f719564347931af554dbc2705022a548405bbd95320db5c094f5616166a46ecd830d8fddedcaebd9e24fd76ff6e8be2e1917d3a3be8bc6ede3f21e77e763af77
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ 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
+ ## [1.4.1]
8
+
9
+ ### Changed
10
+ - 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.
11
+
12
+ ### Fixed
13
+ - Improved performance when analyzing a Podfile with many pods. ([Issue #78](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/78)) [@macblazer](https://github.com/macblazer).
14
+
15
+ ## [1.4.0]
16
+
17
+ ### Added
18
+ - Added `evidence` element to the component output to indicate that we are doing manifest analysis to generate the bom. ([Issue #69](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/69)) [@macblazer](https://github.com/macblazer).
19
+
20
+ ### Fixed
21
+ - Added top level dependencies when the metadata/component is specified (by using the `--name`, `--version`, and `--type` parameters). ([PR #70](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/70)) [@fnxpt](https://github.com/fnxpt)
22
+ - Properly concatenate paths to Podfile and Podfile.lock (with unit tests!). ([Issue #71](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/71)) [@macblazer](https://github.com/macblazer).
23
+
7
24
  ## [1.3.0]
8
25
 
9
26
  ### Added
@@ -104,10 +104,28 @@ module CycloneDX
104
104
  end
105
105
  end
106
106
 
107
- def add_to_bom(xml, trim_strings_length = 0)
107
+ # Add evidence of the purl identity.
108
+ # See https://github.com/CycloneDX/guides/blob/main/SBOM/en/0x60-Evidence.md for more info
109
+ def xml_add_evidence(xml, manifest_path)
110
+ xml.evidence do
111
+ xml.identity do
112
+ xml.field 'purl'
113
+ xml.confidence '0.6'
114
+ xml.methods_ do
115
+ xml.method_ do
116
+ xml.technique 'manifest-analysis'
117
+ xml.confidence '0.6'
118
+ xml.value manifest_path
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def add_to_bom(xml, manifest_path, trim_strings_length = 0)
108
126
  xml.component(type: 'library', 'bom-ref': purl) do
109
127
  xml_add_author(xml, trim_strings_length)
110
- xml.name name
128
+ xml.name_ name
111
129
  xml.version version.to_s
112
130
  xml.description { xml.cdata description } unless description.nil?
113
131
  unless checksum.nil?
@@ -126,6 +144,8 @@ module CycloneDX
126
144
  xml.purl purl.slice(0, trim_strings_length)
127
145
  end
128
146
  xml_add_homepage(xml)
147
+
148
+ xml_add_evidence(xml, manifest_path)
129
149
  end
130
150
  end
131
151
 
@@ -133,7 +153,7 @@ module CycloneDX
133
153
  def add_to_bom(xml)
134
154
  xml.license do
135
155
  xml.id identifier if identifier_type == :id
136
- xml.name identifier if identifier_type == :name
156
+ xml.name_ identifier if identifier_type == :name
137
157
  xml.text_ text unless text.nil?
138
158
  xml.url url unless url.nil?
139
159
  end
@@ -143,21 +163,23 @@ module CycloneDX
143
163
 
144
164
  class Component
145
165
  def add_to_bom(xml)
146
- xml.component(type: type) do
166
+ xml.component(type: type, 'bom-ref': bomref) do
147
167
  xml.group group unless group.nil?
148
- xml.name name
168
+ xml.name_ name
149
169
  xml.version version
150
170
  end
151
171
  end
152
172
  end
153
173
 
174
+ # Turns the internal model data into an XML bom.
154
175
  class BOMBuilder
155
176
  NAMESPACE = 'http://cyclonedx.org/schema/bom/1.5'
156
177
 
157
- attr_reader :component, :pods, :dependencies
178
+ attr_reader :component, :pods, :manifest_path, :dependencies
158
179
 
159
- def initialize(pods:, component: nil, dependencies: nil)
180
+ def initialize(pods:, manifest_path:, component: nil, dependencies: nil)
160
181
  @pods = pods.sort_by(&:purl)
182
+ @manifest_path = manifest_path
161
183
  @component = component
162
184
  @dependencies = dependencies&.sort
163
185
  end
@@ -184,17 +206,17 @@ module CycloneDX
184
206
  xml.bom(xmlns: NAMESPACE, version: version.to_i.to_s, serialNumber: "urn:uuid:#{SecureRandom.uuid}") do
185
207
  bom_metadata(xml)
186
208
 
187
- bom_components(xml, pods, trim_strings_length)
209
+ bom_components(xml, pods, manifest_path, trim_strings_length)
188
210
 
189
211
  bom_dependencies(xml, dependencies)
190
212
  end
191
213
  end.to_xml
192
214
  end
193
215
 
194
- def bom_components(xml, pods, trim_strings_length)
216
+ def bom_components(xml, pods, manifest_path, trim_strings_length)
195
217
  xml.components do
196
218
  pods.each do |pod|
197
- pod.add_to_bom(xml, trim_strings_length)
219
+ pod.add_to_bom(xml, manifest_path, trim_strings_length)
198
220
  end
199
221
  end
200
222
  end
@@ -223,7 +245,7 @@ module CycloneDX
223
245
  xml.tools do
224
246
  xml.tool do
225
247
  xml.vendor 'CycloneDX'
226
- xml.name 'cyclonedx-cocoapods'
248
+ xml.name_ 'cyclonedx-cocoapods'
227
249
  xml.version VERSION
228
250
  end
229
251
  end
@@ -39,9 +39,9 @@ module CycloneDX
39
39
  setup_logger(verbose: options[:verbose])
40
40
  @logger.debug "Running cyclonedx-cocoapods with options: #{options}"
41
41
 
42
- pods, dependencies = analyze(options)
42
+ component, pods, manifest_path, dependencies = analyze(options)
43
43
 
44
- build_and_write_bom(options, pods, dependencies)
44
+ build_and_write_bom(options, component, pods, manifest_path, dependencies)
45
45
  rescue StandardError => e
46
46
  @logger.error ([e.message] + e.backtrace).join($INPUT_RECORD_SEPARATOR)
47
47
  exit 1
@@ -100,12 +100,10 @@ module CycloneDX
100
100
  parsed_options[:name] = name
101
101
  end
102
102
  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
103
+ Gem::Version.new(version)
104
+ parsed_options[:version] = version
105
+ rescue StandardError => e
106
+ raise OptionParser::InvalidArgument, e.message
109
107
  end
110
108
  options.on('-t', '--type type',
111
109
  'Type of the component for which the BOM is generated ' \
@@ -136,11 +134,26 @@ module CycloneDX
136
134
  pods, dependencies = analyzer.parse_pods(podfile, lockfile)
137
135
  analyzer.populate_pods_with_additional_info(pods)
138
136
 
139
- [pods, dependencies]
137
+ component = component_from_options(options)
138
+
139
+ unless component.nil?
140
+ # add top level pods to main component
141
+ top_deps = analyzer.top_level_deps(podfile, lockfile)
142
+ dependencies[component.bomref] = top_deps
143
+ end
144
+
145
+ manifest_path = lockfile.defined_in_file
146
+ if manifest_path.absolute?
147
+ # Use the folder that we are building in, then the path to the manifest file
148
+ manifest_path = Pathname.pwd.basename + manifest_path.relative_path_from(Pathname.pwd)
149
+ end
150
+
151
+ [component, pods, manifest_path, dependencies]
140
152
  end
141
153
 
142
- def build_and_write_bom(options, pods, dependencies)
143
- builder = BOMBuilder.new(pods: pods, component: component_from_options(options), dependencies: dependencies)
154
+ def build_and_write_bom(options, component, pods, manifest_path, dependencies)
155
+ builder = BOMBuilder.new(pods: pods, manifest_path: manifest_path,
156
+ component: component, dependencies: dependencies)
144
157
  bom = builder.bom(version: options[:bom_version] || 1,
145
158
  trim_strings_length: options[:trim_strings_length] || 0)
146
159
  write_bom_to_file(bom: bom, options: options)
@@ -24,7 +24,7 @@ module CycloneDX
24
24
  class Component
25
25
  VALID_COMPONENT_TYPES = %w[application framework library container operating-system device firmware file].freeze
26
26
 
27
- attr_reader :group, :name, :version, :type
27
+ attr_reader :group, :name, :version, :type, :bomref
28
28
 
29
29
  def initialize(name:, version:, type:, group: nil)
30
30
  raise ArgumentError, 'Group, if specified, must be non empty' if !group.nil? && group.to_s.strip.empty?
@@ -39,6 +39,11 @@ module CycloneDX
39
39
  @name = name
40
40
  @version = version
41
41
  @type = type
42
+ @bomref = "#{name}@#{version}"
43
+
44
+ return if group.nil?
45
+
46
+ @bomref = "#{group}/#{@bomref}"
42
47
  end
43
48
  end
44
49
  end
@@ -74,6 +74,11 @@ module CycloneDX
74
74
  pods
75
75
  end
76
76
 
77
+ def top_level_deps(podfile, lockfile)
78
+ pods_used = top_level_pods(podfile)
79
+ dependencies_for_pod(pods_used, podfile, lockfile)
80
+ end
81
+
77
82
  private
78
83
 
79
84
  def load_plugins(podfile_path)
@@ -137,6 +142,8 @@ module CycloneDX
137
142
  end
138
143
 
139
144
  def initialize_cocoapods_config(project_dir)
145
+ # First, reset the ::Pod::Config instance in case we need to use this analyzer on multiple pods
146
+ ::Pod::Config.instance = nil
140
147
  ::Pod::Config.instance.installation_root = project_dir
141
148
  end
142
149
 
@@ -177,12 +184,18 @@ module CycloneDX
177
184
  end
178
185
  end
179
186
 
180
- def append_all_pod_dependencies(pods_used, pods_cache)
181
- 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
182
196
  original_number = 0
183
- dependencies_hash = {}
184
197
 
185
- # 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.
186
199
  # This brings in all the transitive dependencies of every top level pod.
187
200
  # Note this also handles two edge cases:
188
201
  # 1. Having a Podfile with no pods used.
@@ -190,29 +203,38 @@ module CycloneDX
190
203
  while result.length != original_number
191
204
  original_number = result.length
192
205
 
193
- pods_used.each do |pod_name|
206
+ top_level_pods.each do |pod_name|
194
207
  if pods_cache.key?(pod_name)
195
- result.push(*pods_cache[pod_name])
196
- 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])
197
210
  end
198
211
  end
199
212
 
200
- result = result.uniq
201
- 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] : []
202
220
  end
203
221
 
204
222
  [result, dependencies_hash]
205
223
  end
206
224
 
207
- def create_list_of_included_pods(podfile, lockfile)
208
- pods_cache = simple_hash_of_lockfile_pods(lockfile)
209
-
225
+ def top_level_pods(podfile)
210
226
  included_targets = podfile.target_definition_list.select { |target| include_target_named(target.label) }
211
227
  included_target_names = included_targets.map(&:label)
212
228
  @logger.debug "Including all pods for targets: #{included_target_names}"
213
229
 
214
230
  top_level_deps = included_targets.map(&:dependencies).flatten.uniq
215
- pods_used = top_level_deps.map(&:name).uniq
231
+ top_level_deps.map(&:name).uniq
232
+ end
233
+
234
+ def create_list_of_included_pods(podfile, lockfile)
235
+ pods_cache = simple_hash_of_lockfile_pods(lockfile)
236
+
237
+ pods_used = top_level_pods(podfile)
216
238
  pods_used, dependencies = append_all_pod_dependencies(pods_used, pods_cache)
217
239
 
218
240
  [pods_used.sort, dependencies]
@@ -21,6 +21,6 @@
21
21
 
22
22
  module CycloneDX
23
23
  module CocoaPods
24
- VERSION = '1.3.0'
24
+ VERSION = '1.4.1'
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cyclonedx-cocoapods
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - José González
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-02-08 00:00:00.000000000 Z
12
+ date: 2024-11-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cocoapods
@@ -51,48 +51,6 @@ dependencies:
51
51
  - - "<"
52
52
  - !ruby/object:Gem::Version
53
53
  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
54
  description: CycloneDX is a lightweight software bill-of-material (SBOM) specification
97
55
  designed for use in application security contexts and supply chain component analysis.
98
56
  This Gem generates CycloneDX BOMs from CocoaPods projects.
@@ -133,14 +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.4
101
+ rubygems_version: 3.5.23
144
102
  signing_key:
145
103
  specification_version: 4
146
104
  summary: CycloneDX software bill-of-material (SBOM) generation utility