cyclonedx-cocoapods 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|