cyclonedx-cocoapods 1.1.1 → 1.2.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 -0
- data/README.md +35 -0
- data/lib/cyclonedx/cocoapods/bom_builder.rb +28 -9
- data/lib/cyclonedx/cocoapods/cli_runner.rb +4 -2
- data/lib/cyclonedx/cocoapods/component.rb +2 -0
- data/lib/cyclonedx/cocoapods/license.rb +2 -0
- data/lib/cyclonedx/cocoapods/pod.rb +3 -1
- data/lib/cyclonedx/cocoapods/pod_attributes.rb +2 -0
- data/lib/cyclonedx/cocoapods/podfile_analyzer.rb +51 -7
- data/lib/cyclonedx/cocoapods/source.rb +2 -0
- data/lib/cyclonedx/cocoapods/version.rb +2 -5
- metadata +18 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9377b14e9d7b5f41db0b12693b58dd37367e05e72f8ab208178c6d79f38234b
|
4
|
+
data.tar.gz: a8402aabb0a9eb157bbbaab4782a3fc6ce731003b48037c54cb1dc7e2d5fb289
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fe8629cb7313126a55d2cbae4e7f69ea6fc365336b56093eab5a23e3a27d4fde848a892926ce2fc201509af9c9e4adb9cf59e0940841a03023e344817be2aa2
|
7
|
+
data.tar.gz: 8cc8b64ddcf4292e14c4698e55899a2b6c72de7ed1ed5ee7adcef316fa315286e52b108a5d000a5fa6f6e42333fd752633b7eeb0142fa603b615b05fef688c8e
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,24 @@ 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.2.0]
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Includes dependency relationship information for each of the components. ([Issue #58](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/58)) [@fnxpt](https://github.com/fnxpt).
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- Components and dependencies are output in alphabetically sorted order by `purl` to increase reproducability of BOM generation. ([Issue #59](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/59)) [@macblazer](https://github.com/macblazer).
|
14
|
+
|
15
|
+
## [1.1.2]
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Updated gem dependency for cocoapods to be minimum v1.10.1 up to anything less than v2. ([Issue #51](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/51)) [@macblazer](https://github.com/macblazer).
|
19
|
+
- Updated gem dependency for nokogiri to be minimum v1.11.2 up to anything less than v2. [@macblazer](https://github.com/macblazer).
|
20
|
+
- Updated README.md with a description of what happens with pods or Podfiles that use subspecs. ([Issue #52](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/52)) [@macblazer](https://github.com/macblazer).
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- Fixed parsing of a Podfile that uses CocoaPods plugins. ([PR #55](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/55)) [@DwayneCoussement](https://github.com/DwayneCoussement).
|
24
|
+
|
7
25
|
## [1.1.1]
|
8
26
|
|
9
27
|
### Changed
|
data/README.md
CHANGED
@@ -80,6 +80,41 @@ then these two commands were run in the checked out code directory.
|
|
80
80
|
% cyclonedx-cocoapods -n "kizitonwose/PodsUpdater" -v 1.0.3 -t application --output example_bom.xml
|
81
81
|
```
|
82
82
|
|
83
|
+
### A Note About CocoaPod Subspecs
|
84
|
+
|
85
|
+
Many CocoaPods make use of [subspec functionality](https://guides.cocoapods.org/syntax/podspec.html#subspec).
|
86
|
+
Podfiles can require whole pods, or just subspecs; pods themselves may require whole pods or subspecs of other
|
87
|
+
pods. In complex projects such as React Native apps this often results in a single pod being included as a
|
88
|
+
dependency multiple times as several of its subspecs are included individually.
|
89
|
+
|
90
|
+
*cyclonedx-cocoapods* works properly with this, and adds a dependency in the BOM output for each subspec that is
|
91
|
+
required by the Podfile and throughout the chain of dependencies. Each subspec will only appear once in the BOM
|
92
|
+
file. This gives you granular detail in the BOM of which subspecs of which pods are used. This is easiest seen
|
93
|
+
with an example.
|
94
|
+
|
95
|
+
The Podfile
|
96
|
+
```ruby
|
97
|
+
target 'SampleProject' do
|
98
|
+
pod 'SamplePod/firstsubspec'
|
99
|
+
pod 'SamplePod/secondsubspec'
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
If the SamplePod is at v2.1, running *cyclonedx-cocoapods* on this will output a BOM file with two `component`
|
104
|
+
dependencies:
|
105
|
+
- `pkg:cocoapods/SamplePod@2.1#firstsubspec` at `https://github.com/example/SamplePod`
|
106
|
+
- `pkg:cocoapods/SamplePod@2.1#secondsubspec` at `https://github.com/example/SamplePod`
|
107
|
+
|
108
|
+
[Dependency Track](https://dependencytrack.org) (DT) is a tool that many organizations use to help automate SBOM
|
109
|
+
related tasks. When uploading an SBOM that contains multiple subspecs from the same pod, or a single subspec
|
110
|
+
alongside the complete pod dependency, the initial upload will indicate a number of dependencies equal to the number
|
111
|
+
of `component` objects within the BOM. However, DT analysis then looks for unique repositories in use which will
|
112
|
+
merge all of the subspecs of a particular pod into a single entry. On later uploads to DT of the same or similar BOM
|
113
|
+
it will indicate just the number of unique repositories.
|
114
|
+
|
115
|
+
Uploading the above SamplePod BOM file to DT will initially see two dependencies. Later analysis by DT notices
|
116
|
+
that both dependencies resolve to the same repository, so DT will then only show a single dependency.
|
117
|
+
|
83
118
|
## Contributing
|
84
119
|
|
85
120
|
To set up for local development, make a fork of this repo, make a branch on your fork named after the issue or workflow you are improving, checkout your branch, then run `bundle install`.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# This file is part of CycloneDX CocoaPods
|
3
5
|
#
|
@@ -26,8 +28,8 @@ module CycloneDX
|
|
26
28
|
module CocoaPods
|
27
29
|
module Source
|
28
30
|
class CocoaPodsRepository
|
29
|
-
LEGACY_REPOSITORY = 'https://github.com/CocoaPods/Specs.git'
|
30
|
-
CDN_REPOSITORY = 'trunk'
|
31
|
+
LEGACY_REPOSITORY = 'https://github.com/CocoaPods/Specs.git'
|
32
|
+
CDN_REPOSITORY = 'trunk'
|
31
33
|
|
32
34
|
def source_qualifier
|
33
35
|
url == LEGACY_REPOSITORY || url == CDN_REPOSITORY ? {} : { repository_url: url }
|
@@ -54,8 +56,8 @@ module CycloneDX
|
|
54
56
|
end
|
55
57
|
|
56
58
|
class Pod
|
57
|
-
CHECKSUM_ALGORITHM = 'SHA-1'
|
58
|
-
HOMEPAGE_REFERENCE_TYPE = 'website'
|
59
|
+
CHECKSUM_ALGORITHM = 'SHA-1'
|
60
|
+
HOMEPAGE_REFERENCE_TYPE = 'website'
|
59
61
|
|
60
62
|
def purl
|
61
63
|
purl_name = CGI.escape(name.split('/').first)
|
@@ -82,6 +84,7 @@ module CycloneDX
|
|
82
84
|
end
|
83
85
|
end
|
84
86
|
xml.purl purl
|
87
|
+
xml.bomRef purl
|
85
88
|
unless homepage.nil?
|
86
89
|
xml.externalReferences do
|
87
90
|
xml.reference(type: HOMEPAGE_REFERENCE_TYPE) do
|
@@ -115,12 +118,14 @@ module CycloneDX
|
|
115
118
|
end
|
116
119
|
|
117
120
|
class BOMBuilder
|
118
|
-
NAMESPACE = 'http://cyclonedx.org/schema/bom/1.4'
|
121
|
+
NAMESPACE = 'http://cyclonedx.org/schema/bom/1.4'
|
119
122
|
|
120
|
-
attr_reader :component, :pods
|
123
|
+
attr_reader :component, :pods, :dependencies
|
121
124
|
|
122
|
-
def initialize(component: nil,
|
123
|
-
@
|
125
|
+
def initialize(pods:, component: nil, dependencies: nil)
|
126
|
+
@pods = pods.sort_by(&:purl)
|
127
|
+
@component = component
|
128
|
+
@dependencies = dependencies&.sort
|
124
129
|
end
|
125
130
|
|
126
131
|
def bom(version: 1)
|
@@ -134,12 +139,26 @@ module CycloneDX
|
|
134
139
|
pod.add_to_bom(xml)
|
135
140
|
end
|
136
141
|
end
|
142
|
+
|
143
|
+
xml.dependencies do
|
144
|
+
bom_dependencies(xml, dependencies)
|
145
|
+
end
|
137
146
|
end
|
138
147
|
end.to_xml
|
139
148
|
end
|
140
149
|
|
141
150
|
private
|
142
151
|
|
152
|
+
def bom_dependencies(xml, dependencies)
|
153
|
+
dependencies&.each do |key, array|
|
154
|
+
xml.dependency(ref: key) do
|
155
|
+
array.sort.each do |value|
|
156
|
+
xml.dependency(ref: value)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
143
162
|
def bom_metadata(xml)
|
144
163
|
xml.metadata do
|
145
164
|
xml.timestamp Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
@@ -155,4 +174,4 @@ module CycloneDX
|
|
155
174
|
end
|
156
175
|
end
|
157
176
|
end
|
158
|
-
end
|
177
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# This file is part of CycloneDX CocoaPods
|
4
5
|
#
|
@@ -39,10 +40,11 @@ module CycloneDX
|
|
39
40
|
|
40
41
|
analyzer = PodfileAnalyzer.new(logger: @logger, exclude_test_targets: options[:exclude_test_targets])
|
41
42
|
podfile, lockfile = analyzer.ensure_podfile_and_lock_are_present(options)
|
42
|
-
pods = analyzer.parse_pods(podfile, lockfile)
|
43
|
+
pods, dependencies = analyzer.parse_pods(podfile, lockfile)
|
43
44
|
analyzer.populate_pods_with_additional_info(pods)
|
44
45
|
|
45
|
-
|
46
|
+
builder = BOMBuilder.new(pods: pods, component: component_from_options(options), dependencies: dependencies)
|
47
|
+
bom = builder.bom(version: options[:bom_version] || 1)
|
46
48
|
write_bom_to_file(bom: bom, options: options)
|
47
49
|
rescue StandardError => e
|
48
50
|
@logger.error ([e.message] + e.backtrace).join($/)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# This file is part of CycloneDX CocoaPods
|
4
5
|
#
|
@@ -19,6 +20,7 @@
|
|
19
20
|
#
|
20
21
|
|
21
22
|
require 'cocoapods'
|
23
|
+
require 'cocoapods-core'
|
22
24
|
require 'logger'
|
23
25
|
|
24
26
|
require_relative 'pod'
|
@@ -35,6 +37,23 @@ module CycloneDX
|
|
35
37
|
@exclude_test_targets = exclude_test_targets
|
36
38
|
end
|
37
39
|
|
40
|
+
def load_plugins(podfile_path)
|
41
|
+
podfile_contents = File.read(podfile_path)
|
42
|
+
plugin_syntax = /\s*plugin\s+['"]([^'"]+)['"]/
|
43
|
+
plugin_names = podfile_contents.scan(plugin_syntax).flatten
|
44
|
+
|
45
|
+
plugin_names.each do |plugin_name|
|
46
|
+
@logger.debug("Loading plugin #{plugin_name}")
|
47
|
+
begin
|
48
|
+
plugin_spec = Gem::Specification.find_by_name(plugin_name)
|
49
|
+
plugin_spec.activate if plugin_spec
|
50
|
+
load(plugin_spec.gem_dir + '/lib/cocoapods_plugin.rb') if plugin_spec
|
51
|
+
rescue Gem::LoadError => e
|
52
|
+
@logger.warn("Failed to load plugin #{plugin_name}. #{e.message}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
38
57
|
def ensure_podfile_and_lock_are_present(options)
|
39
58
|
project_dir = Pathname.new(options[:path] || Dir.pwd)
|
40
59
|
raise PodfileParsingError, "#{options[:path]} is not a valid directory." unless File.directory?(project_dir)
|
@@ -47,6 +66,7 @@ module CycloneDX
|
|
47
66
|
|
48
67
|
lockfile = ::Pod::Lockfile.from_file(options[:podfile_lock_path])
|
49
68
|
verify_synced_sandbox(lockfile)
|
69
|
+
load_plugins(options[:podfile_path])
|
50
70
|
|
51
71
|
return ::Pod::Podfile.from_file(options[:podfile_path]), lockfile
|
52
72
|
end
|
@@ -54,10 +74,25 @@ module CycloneDX
|
|
54
74
|
|
55
75
|
def parse_pods(podfile, lockfile)
|
56
76
|
@logger.debug "Parsing pods from #{podfile.defined_in_file}"
|
57
|
-
included_pods = create_list_of_included_pods(podfile, lockfile)
|
58
|
-
|
77
|
+
included_pods, dependencies = create_list_of_included_pods(podfile, lockfile)
|
78
|
+
|
79
|
+
pods = lockfile.pod_names.select { |name| included_pods.include?(name) }.map do |name|
|
59
80
|
Pod.new(name: name, version: lockfile.version(name), source: source_for_pod(podfile, lockfile, name), checksum: lockfile.checksum(name))
|
60
81
|
end
|
82
|
+
|
83
|
+
pod_dependencies = { }
|
84
|
+
dependencies.each {|key, value|
|
85
|
+
if lockfile.pod_names.include? key
|
86
|
+
pod = Pod.new(name: key, version: lockfile.version(key), source: source_for_pod(podfile, lockfile, key), checksum: lockfile.checksum(key))
|
87
|
+
|
88
|
+
pod_dependencies[pod.purl] = lockfile.pod_names.select { |name| value.include?(name) }.map do |name|
|
89
|
+
pod = Pod.new(name: name, version: lockfile.version(name), source: source_for_pod(podfile, lockfile, name), checksum: lockfile.checksum(name))
|
90
|
+
pod.purl
|
91
|
+
end
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
return pods, pod_dependencies
|
61
96
|
end
|
62
97
|
|
63
98
|
|
@@ -69,7 +104,6 @@ module CycloneDX
|
|
69
104
|
return pods
|
70
105
|
end
|
71
106
|
|
72
|
-
|
73
107
|
private
|
74
108
|
|
75
109
|
|
@@ -104,9 +138,12 @@ module CycloneDX
|
|
104
138
|
pods_hash
|
105
139
|
end
|
106
140
|
|
141
|
+
|
107
142
|
def append_all_pod_dependencies(pods_used, pods_cache)
|
108
143
|
result = pods_used
|
109
144
|
original_number = 0
|
145
|
+
dependencies_hash = { }
|
146
|
+
|
110
147
|
# Loop adding pod dependencies until we are not adding any more dependencies to the result
|
111
148
|
# This brings in all the transitive dependencies of every top level pod.
|
112
149
|
# Note this also handles two edge cases:
|
@@ -114,13 +151,20 @@ module CycloneDX
|
|
114
151
|
# 2. Having a pod that has a platform-specific dependency that is unused for this Podfile.
|
115
152
|
while result.length != original_number
|
116
153
|
original_number = result.length
|
154
|
+
|
117
155
|
pods_used.each { |pod_name|
|
118
|
-
|
156
|
+
if pods_cache.key?(pod_name)
|
157
|
+
result.push(*pods_cache[pod_name])
|
158
|
+
dependencies_hash[pod_name] = pods_cache[pod_name].empty? ? [] : pods_cache[pod_name]
|
159
|
+
end
|
119
160
|
}
|
161
|
+
|
120
162
|
result = result.uniq
|
163
|
+
# maybe additional dependency processing needed here???
|
121
164
|
pods_used = result
|
122
165
|
end
|
123
|
-
|
166
|
+
|
167
|
+
return result, dependencies_hash
|
124
168
|
end
|
125
169
|
|
126
170
|
def create_list_of_included_pods(podfile, lockfile)
|
@@ -132,9 +176,9 @@ module CycloneDX
|
|
132
176
|
|
133
177
|
topLevelDeps = includedTargets.map(&:dependencies).flatten.uniq
|
134
178
|
pods_used = topLevelDeps.map(&:name).uniq
|
135
|
-
pods_used = append_all_pod_dependencies(pods_used, pods_cache)
|
179
|
+
pods_used, dependencies = append_all_pod_dependencies(pods_used, pods_cache)
|
136
180
|
|
137
|
-
return pods_used.sort
|
181
|
+
return pods_used.sort, dependencies
|
138
182
|
end
|
139
183
|
|
140
184
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# This file is part of CycloneDX CocoaPods
|
4
5
|
#
|
@@ -20,10 +21,6 @@
|
|
20
21
|
|
21
22
|
module CycloneDX
|
22
23
|
module CocoaPods
|
23
|
-
VERSION = '1.
|
24
|
-
DEPENDENCIES = {
|
25
|
-
cocoapods: '~> 1.10.1',
|
26
|
-
nokogiri: '~> 1.11.2'
|
27
|
-
}
|
24
|
+
VERSION = '1.2.0'
|
28
25
|
end
|
29
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.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- José González
|
@@ -9,36 +9,48 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cocoapods
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: 1.10.1
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '2.0'
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
25
|
-
- - "
|
28
|
+
- - ">="
|
26
29
|
- !ruby/object:Gem::Version
|
27
30
|
version: 1.10.1
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
28
34
|
- !ruby/object:Gem::Dependency
|
29
35
|
name: nokogiri
|
30
36
|
requirement: !ruby/object:Gem::Requirement
|
31
37
|
requirements:
|
32
|
-
- - "
|
38
|
+
- - ">="
|
33
39
|
- !ruby/object:Gem::Version
|
34
40
|
version: 1.11.2
|
41
|
+
- - "<"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
35
44
|
type: :runtime
|
36
45
|
prerelease: false
|
37
46
|
version_requirements: !ruby/object:Gem::Requirement
|
38
47
|
requirements:
|
39
|
-
- - "
|
48
|
+
- - ">="
|
40
49
|
- !ruby/object:Gem::Version
|
41
50
|
version: 1.11.2
|
51
|
+
- - "<"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
42
54
|
- !ruby/object:Gem::Dependency
|
43
55
|
name: rake
|
44
56
|
requirement: !ruby/object:Gem::Requirement
|