cyclonedx-cocoapods 1.2.0 → 1.3.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 +12 -0
- data/README.md +3 -2
- data/exe/cyclonedx-cocoapods +4 -2
- data/lib/cyclonedx/cocoapods/bom_builder.rb +95 -39
- data/lib/cyclonedx/cocoapods/cli_runner.rb +84 -49
- data/lib/cyclonedx/cocoapods/component.rb +11 -5
- data/lib/cyclonedx/cocoapods/license.rb +4 -6
- data/lib/cyclonedx/cocoapods/pod.rb +47 -31
- data/lib/cyclonedx/cocoapods/pod_attributes.rb +20 -5
- data/lib/cyclonedx/cocoapods/podfile_analyzer.rb +126 -85
- data/lib/cyclonedx/cocoapods/source.rb +6 -3
- data/lib/cyclonedx/cocoapods/version.rb +1 -1
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b02ca712eff5c74b3c7a4e3c59ab7a402a87cf2f3053be388f64a702ca0d3a1
|
4
|
+
data.tar.gz: ca6e3d81e4b255dfcd43add1017e1b5dc939274544b6bade803da0246c544ebc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74af3ed1ceded419670e4e4cc2b69a955730f963ef92510159505809a02681ae39347c91a24ac99d988f142f987465998fdd4bf4629e799d0dc33e01f96fbb9a
|
7
|
+
data.tar.gz: b1ce5ceae85c7b3ff993109203a53f9f7fa61397f8d58f6b18220ee2ed5e866ab1bc210b6ac48b375de3ec49e6928abe2245413fac56b437403317fb1c2ba783
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,18 @@ 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.3.0]
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Added optional `--shortened-strings` CLI parameter to limit the author, publisher, and purl lengths. ([Issue #65](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/65)) [@macblazer](https://github.com/macblazer).
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- Updated to use v1.5 of the CycloneDX specification. ([Issue #57](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/57)) [@macblazer](https://github.com/macblazer)
|
14
|
+
- Code cleanup based on [RuboCop](https://rubocop.org/) analysis. ([Issue #45](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/45)) [@macblazer](https://github.com/macblazer).
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
- Following the specification to put the `bom-ref` attribute on `component` instead of as a `bomRef` element of `component`. [@macblazer](https://github.com/macblazer).
|
18
|
+
|
7
19
|
## [1.2.0]
|
8
20
|
|
9
21
|
### Added
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ The CycloneDX CocoaPods Gem creates a valid CycloneDX software bill-of-material
|
|
21
21
|
|
22
22
|
### From Source
|
23
23
|
|
24
|
-
First, clone/copy the source code from GitHub. Then in the source code directory run these
|
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`):
|
25
25
|
|
26
26
|
```shell
|
27
27
|
gem build cyclonedx-cocoapods.gemspec
|
@@ -32,7 +32,7 @@ Building from source requires Ruby 2.4.0 or newer.
|
|
32
32
|
|
33
33
|
## Compatibility
|
34
34
|
|
35
|
-
*cyclonedx-cocoapods* aims to produce SBOMs according to the latest CycloneDX specification, which currently is [1.
|
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/).
|
36
36
|
You can use the [CycloneDX CLI](https://github.com/CycloneDX/cyclonedx-cli#convert-command) to convert between multiple BOM formats or specification versions.
|
37
37
|
|
38
38
|
## Usage
|
@@ -52,6 +52,7 @@ OPTIONS
|
|
52
52
|
-o, --output bom_file_path Path to output the bom.xml file to (default: "bom.xml")
|
53
53
|
-b, --bom-version bom_version Version of the generated BOM (default: "1")
|
54
54
|
-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
|
55
56
|
|
56
57
|
Component Metadata
|
57
58
|
-n, --name name (If specified version and type are also required) Name of the component for which the BOM is generated
|
data/exe/cyclonedx-cocoapods
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
#
|
3
5
|
# This file is part of CycloneDX CocoaPods
|
4
6
|
#
|
@@ -18,6 +20,6 @@
|
|
18
20
|
# Copyright (c) OWASP Foundation. All Rights Reserved.
|
19
21
|
#
|
20
22
|
|
21
|
-
require
|
23
|
+
require 'cyclonedx/cocoapods/cli_runner'
|
22
24
|
|
23
|
-
CycloneDX::CocoaPods::CLIRunner.new
|
25
|
+
CycloneDX::CocoaPods::CLIRunner.new.run
|
@@ -59,17 +59,54 @@ module CycloneDX
|
|
59
59
|
CHECKSUM_ALGORITHM = 'SHA-1'
|
60
60
|
HOMEPAGE_REFERENCE_TYPE = 'website'
|
61
61
|
|
62
|
+
def source_qualifier
|
63
|
+
return '' if source.nil? || source.source_qualifier.empty?
|
64
|
+
|
65
|
+
"?#{source.source_qualifier.map do |key, value|
|
66
|
+
"#{key}=#{CGI.escape(value)}"
|
67
|
+
end.join('&')}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def purl_subpath
|
71
|
+
return '' unless name.split('/').length > 1
|
72
|
+
|
73
|
+
"##{name.split('/').drop(1).map do |component|
|
74
|
+
CGI.escape(component)
|
75
|
+
end.join('/')}"
|
76
|
+
end
|
77
|
+
|
62
78
|
def purl
|
63
79
|
purl_name = CGI.escape(name.split('/').first)
|
64
|
-
|
65
|
-
|
66
|
-
|
80
|
+
src_qualifier = source_qualifier
|
81
|
+
subpath = purl_subpath
|
82
|
+
"pkg:cocoapods/#{purl_name}@#{CGI.escape(version.to_s)}#{src_qualifier}#{subpath}"
|
67
83
|
end
|
68
84
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
85
|
+
def xml_add_author(xml, trim_strings_length)
|
86
|
+
return if author.nil?
|
87
|
+
|
88
|
+
if trim_strings_length.zero?
|
89
|
+
xml.author author
|
90
|
+
xml.publisher author
|
91
|
+
else
|
92
|
+
xml.author author.slice(0, trim_strings_length)
|
93
|
+
xml.publisher author.slice(0, trim_strings_length)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def xml_add_homepage(xml)
|
98
|
+
return if homepage.nil?
|
99
|
+
|
100
|
+
xml.externalReferences do
|
101
|
+
xml.reference(type: HOMEPAGE_REFERENCE_TYPE) do
|
102
|
+
xml.url homepage
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_to_bom(xml, trim_strings_length = 0)
|
108
|
+
xml.component(type: 'library', 'bom-ref': purl) do
|
109
|
+
xml_add_author(xml, trim_strings_length)
|
73
110
|
xml.name name
|
74
111
|
xml.version version.to_s
|
75
112
|
xml.description { xml.cdata description } unless description.nil?
|
@@ -83,15 +120,12 @@ module CycloneDX
|
|
83
120
|
license.add_to_bom(xml)
|
84
121
|
end
|
85
122
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
xml.
|
90
|
-
xml.reference(type: HOMEPAGE_REFERENCE_TYPE) do
|
91
|
-
xml.url homepage
|
92
|
-
end
|
93
|
-
end
|
123
|
+
if trim_strings_length.zero?
|
124
|
+
xml.purl purl
|
125
|
+
else
|
126
|
+
xml.purl purl.slice(0, trim_strings_length)
|
94
127
|
end
|
128
|
+
xml_add_homepage(xml)
|
95
129
|
end
|
96
130
|
end
|
97
131
|
|
@@ -118,7 +152,7 @@ module CycloneDX
|
|
118
152
|
end
|
119
153
|
|
120
154
|
class BOMBuilder
|
121
|
-
NAMESPACE = 'http://cyclonedx.org/schema/bom/1.
|
155
|
+
NAMESPACE = 'http://cyclonedx.org/schema/bom/1.5'
|
122
156
|
|
123
157
|
attr_reader :component, :pods, :dependencies
|
124
158
|
|
@@ -128,32 +162,50 @@ module CycloneDX
|
|
128
162
|
@dependencies = dependencies&.sort
|
129
163
|
end
|
130
164
|
|
131
|
-
def bom(version: 1)
|
132
|
-
|
165
|
+
def bom(version: 1, trim_strings_length: 0)
|
166
|
+
unless version.to_i.positive?
|
167
|
+
raise ArgumentError,
|
168
|
+
"Incorrect version: #{version} should be an integer greater than 0"
|
169
|
+
end
|
170
|
+
|
171
|
+
unless trim_strings_length.is_a?(Integer) && (trim_strings_length.positive? || trim_strings_length.zero?)
|
172
|
+
raise ArgumentError,
|
173
|
+
"Incorrect string length: #{trim_strings_length} should be an integer greater than 0"
|
174
|
+
end
|
175
|
+
|
176
|
+
unchecked_bom(version: version, trim_strings_length: trim_strings_length)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
133
180
|
|
181
|
+
# does not verify parameters because the public method does that.
|
182
|
+
def unchecked_bom(version: 1, trim_strings_length: 0)
|
134
183
|
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
135
|
-
xml.bom(
|
184
|
+
xml.bom(xmlns: NAMESPACE, version: version.to_i.to_s, serialNumber: "urn:uuid:#{SecureRandom.uuid}") do
|
136
185
|
bom_metadata(xml)
|
137
|
-
xml.components do
|
138
|
-
pods.each do |pod|
|
139
|
-
pod.add_to_bom(xml)
|
140
|
-
end
|
141
|
-
end
|
142
186
|
|
143
|
-
xml
|
144
|
-
|
145
|
-
|
187
|
+
bom_components(xml, pods, trim_strings_length)
|
188
|
+
|
189
|
+
bom_dependencies(xml, dependencies)
|
146
190
|
end
|
147
191
|
end.to_xml
|
148
192
|
end
|
149
193
|
|
150
|
-
|
194
|
+
def bom_components(xml, pods, trim_strings_length)
|
195
|
+
xml.components do
|
196
|
+
pods.each do |pod|
|
197
|
+
pod.add_to_bom(xml, trim_strings_length)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
151
201
|
|
152
202
|
def bom_dependencies(xml, dependencies)
|
153
|
-
dependencies
|
154
|
-
|
155
|
-
|
156
|
-
|
203
|
+
xml.dependencies do
|
204
|
+
dependencies&.each do |key, array|
|
205
|
+
xml.dependency(ref: key) do
|
206
|
+
array.sort.each do |value|
|
207
|
+
xml.dependency(ref: value)
|
208
|
+
end
|
157
209
|
end
|
158
210
|
end
|
159
211
|
end
|
@@ -162,14 +214,18 @@ module CycloneDX
|
|
162
214
|
def bom_metadata(xml)
|
163
215
|
xml.metadata do
|
164
216
|
xml.timestamp Time.now.getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
165
|
-
xml
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
217
|
+
bom_tools(xml)
|
218
|
+
component&.add_to_bom(xml)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def bom_tools(xml)
|
223
|
+
xml.tools do
|
224
|
+
xml.tool do
|
225
|
+
xml.vendor 'CycloneDX'
|
226
|
+
xml.name 'cyclonedx-cocoapods'
|
227
|
+
xml.version VERSION
|
171
228
|
end
|
172
|
-
component.add_to_bom(xml) unless component.nil?
|
173
229
|
end
|
174
230
|
end
|
175
231
|
end
|
@@ -19,6 +19,7 @@
|
|
19
19
|
# Copyright (c) OWASP Foundation. All Rights Reserved.
|
20
20
|
#
|
21
21
|
|
22
|
+
require 'English'
|
22
23
|
require 'logger'
|
23
24
|
require 'optparse'
|
24
25
|
|
@@ -30,34 +31,26 @@ module CycloneDX
|
|
30
31
|
module CocoaPods
|
31
32
|
class BOMOutputError < StandardError; end
|
32
33
|
|
34
|
+
# Interprets CLI parameters and runs the main workflow.
|
33
35
|
class CLIRunner
|
34
36
|
def run
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@logger.debug "Running cyclonedx-cocoapods with options: #{options}"
|
40
|
-
|
41
|
-
analyzer = PodfileAnalyzer.new(logger: @logger, exclude_test_targets: options[:exclude_test_targets])
|
42
|
-
podfile, lockfile = analyzer.ensure_podfile_and_lock_are_present(options)
|
43
|
-
pods, dependencies = analyzer.parse_pods(podfile, lockfile)
|
44
|
-
analyzer.populate_pods_with_additional_info(pods)
|
45
|
-
|
46
|
-
builder = BOMBuilder.new(pods: pods, component: component_from_options(options), dependencies: dependencies)
|
47
|
-
bom = builder.bom(version: options[:bom_version] || 1)
|
48
|
-
write_bom_to_file(bom: bom, options: options)
|
49
|
-
rescue StandardError => e
|
50
|
-
@logger.error ([e.message] + e.backtrace).join($/)
|
51
|
-
exit 1
|
52
|
-
end
|
53
|
-
end
|
37
|
+
setup_logger # Needed in case we have errors while processing CLI parameters
|
38
|
+
options = parse_options
|
39
|
+
setup_logger(verbose: options[:verbose])
|
40
|
+
@logger.debug "Running cyclonedx-cocoapods with options: #{options}"
|
54
41
|
|
42
|
+
pods, dependencies = analyze(options)
|
55
43
|
|
56
|
-
|
44
|
+
build_and_write_bom(options, pods, dependencies)
|
45
|
+
rescue StandardError => e
|
46
|
+
@logger.error ([e.message] + e.backtrace).join($INPUT_RECORD_SEPARATOR)
|
47
|
+
exit 1
|
48
|
+
end
|
57
49
|
|
50
|
+
private
|
58
51
|
|
59
|
-
def
|
60
|
-
|
52
|
+
def parse_options
|
53
|
+
parsed_options = {}
|
61
54
|
component_types = Component::VALID_COMPONENT_TYPES
|
62
55
|
OptionParser.new do |options|
|
63
56
|
options.banner = <<~BANNER
|
@@ -71,7 +64,7 @@ module CycloneDX
|
|
71
64
|
BANNER
|
72
65
|
|
73
66
|
options.on('--[no-]verbose', 'Show verbose debugging output') do |v|
|
74
|
-
|
67
|
+
parsed_options[:verbose] = v
|
75
68
|
end
|
76
69
|
options.on('-h', '--help', 'Show help message') do
|
77
70
|
puts options
|
@@ -80,71 +73,113 @@ module CycloneDX
|
|
80
73
|
|
81
74
|
options.separator("\n BOM Generation")
|
82
75
|
options.on('-p', '--path path', 'Path to CocoaPods project directory (default: current directory)') do |path|
|
83
|
-
|
76
|
+
parsed_options[:path] = path
|
84
77
|
end
|
85
|
-
options.on('-o', '--output bom_file_path',
|
86
|
-
|
78
|
+
options.on('-o', '--output bom_file_path',
|
79
|
+
'Path to output the bom.xml file to (default: "bom.xml")') do |bom_file_path|
|
80
|
+
parsed_options[:bom_file_path] = bom_file_path
|
87
81
|
end
|
88
|
-
options.on('-b', '--bom-version bom_version', Integer,
|
89
|
-
|
82
|
+
options.on('-b', '--bom-version bom_version', Integer,
|
83
|
+
'Version of the generated BOM (default: "1")') do |version|
|
84
|
+
parsed_options[:bom_version] = version
|
90
85
|
end
|
91
|
-
options.on('-x', '--exclude-test-targets',
|
92
|
-
|
86
|
+
options.on('-x', '--exclude-test-targets',
|
87
|
+
'Eliminate Podfile targets whose name contains the word "test"') do |exclude|
|
88
|
+
parsed_options[:exclude_test_targets] = exclude
|
89
|
+
end
|
90
|
+
options.on('-s', '--shortened-strings length', Integer,
|
91
|
+
'Trim author, publisher, and purl to <length> characters; this may ' \
|
92
|
+
'cause data loss but can improve compatibility with other systems') do |shortened_strings|
|
93
|
+
parsed_options[:trim_strings_length] = shortened_strings
|
93
94
|
end
|
94
95
|
|
95
96
|
options.separator("\n Component Metadata\n")
|
96
|
-
options.on('-n', '--name name',
|
97
|
-
|
97
|
+
options.on('-n', '--name name',
|
98
|
+
'(If specified version and type are also required) Name of the ' \
|
99
|
+
'component for which the BOM is generated') do |name|
|
100
|
+
parsed_options[:name] = name
|
98
101
|
end
|
99
102
|
options.on('-v', '--version version', 'Version of the component for which the BOM is generated') do |version|
|
100
103
|
begin
|
101
104
|
Gem::Version.new(version)
|
102
|
-
|
105
|
+
parsed_options[:version] = version
|
103
106
|
rescue StandardError => e
|
104
107
|
raise OptionParser::InvalidArgument, e.message
|
105
108
|
end
|
106
109
|
end
|
107
|
-
options.on('-t', '--type type',
|
108
|
-
|
109
|
-
|
110
|
+
options.on('-t', '--type type',
|
111
|
+
'Type of the component for which the BOM is generated ' \
|
112
|
+
"(one of #{component_types.join('|')})") do |type|
|
113
|
+
unless component_types.include?(type)
|
114
|
+
raise OptionParser::InvalidArgument,
|
115
|
+
"Invalid value for component's type (#{type}). It must be one of #{component_types.join('|')}"
|
116
|
+
end
|
117
|
+
|
118
|
+
parsed_options[:type] = type
|
110
119
|
end
|
111
120
|
options.on('-g', '--group group', 'Group of the component for which the BOM is generated') do |group|
|
112
|
-
|
121
|
+
parsed_options[:group] = group
|
113
122
|
end
|
114
123
|
end.parse!
|
115
124
|
|
116
|
-
|
117
|
-
|
125
|
+
if !parsed_options[:name].nil? && (parsed_options[:version].nil? || parsed_options[:type].nil?)
|
126
|
+
raise OptionParser::InvalidArgument,
|
127
|
+
'You must also specify --version and --type if --name is provided'
|
128
|
+
end
|
129
|
+
|
130
|
+
parsed_options
|
118
131
|
end
|
119
132
|
|
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)
|
120
138
|
|
121
|
-
|
122
|
-
Component.new(group: options[:group], name: options[:name], version: options[:version], type: options[:type]) if options[:name]
|
139
|
+
[pods, dependencies]
|
123
140
|
end
|
124
141
|
|
142
|
+
def build_and_write_bom(options, pods, dependencies)
|
143
|
+
builder = BOMBuilder.new(pods: pods, component: component_from_options(options), dependencies: dependencies)
|
144
|
+
bom = builder.bom(version: options[:bom_version] || 1,
|
145
|
+
trim_strings_length: options[:trim_strings_length] || 0)
|
146
|
+
write_bom_to_file(bom: bom, options: options)
|
147
|
+
end
|
148
|
+
|
149
|
+
def component_from_options(options)
|
150
|
+
return unless options[:name]
|
151
|
+
|
152
|
+
Component.new(group: options[:group], name: options[:name], version: options[:version],
|
153
|
+
type: options[:type])
|
154
|
+
end
|
125
155
|
|
126
156
|
def setup_logger(verbose: true)
|
127
157
|
@logger ||= Logger.new($stdout)
|
128
158
|
@logger.level = verbose ? Logger::DEBUG : Logger::INFO
|
129
159
|
end
|
130
160
|
|
131
|
-
|
132
161
|
def write_bom_to_file(bom:, options:)
|
162
|
+
bom_file_path = prep_for_bom_write(options)
|
163
|
+
|
164
|
+
begin
|
165
|
+
File.write(bom_file_path, bom)
|
166
|
+
@logger.info "BOM written to #{bom_file_path}"
|
167
|
+
rescue StandardError
|
168
|
+
raise BOMOutputError, "Unable to write the BOM to #{bom_file_path}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def prep_for_bom_write(options)
|
133
173
|
bom_file_path = Pathname.new(options[:bom_file_path] || './bom.xml').expand_path
|
134
174
|
bom_dir = bom_file_path.dirname
|
135
175
|
|
136
176
|
begin
|
137
177
|
FileUtils.mkdir_p(bom_dir) unless bom_dir.directory?
|
138
|
-
rescue
|
178
|
+
rescue StandardError
|
139
179
|
raise BOMOutputError, "Unable to create the BOM output directory at #{bom_dir}"
|
140
180
|
end
|
141
181
|
|
142
|
-
|
143
|
-
File.open(bom_file_path, 'w') { |file| file.write(bom) }
|
144
|
-
@logger.info "BOM written to #{bom_file_path}"
|
145
|
-
rescue
|
146
|
-
raise BOMOutputError, "Unable to write the BOM to #{bom_file_path}"
|
147
|
-
end
|
182
|
+
bom_file_path
|
148
183
|
end
|
149
184
|
end
|
150
185
|
end
|
@@ -26,13 +26,19 @@ module CycloneDX
|
|
26
26
|
|
27
27
|
attr_reader :group, :name, :version, :type
|
28
28
|
|
29
|
-
def initialize(
|
30
|
-
raise ArgumentError,
|
31
|
-
raise ArgumentError,
|
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?
|
32
|
+
|
32
33
|
Gem::Version.new(version) # To check that the version string is well formed
|
33
|
-
|
34
|
+
unless VALID_COMPONENT_TYPES.include?(type)
|
35
|
+
raise ArgumentError, "#{type} is not valid component type (#{VALID_COMPONENT_TYPES.join('|')})"
|
36
|
+
end
|
34
37
|
|
35
|
-
@group
|
38
|
+
@group = group
|
39
|
+
@name = name
|
40
|
+
@version = version
|
41
|
+
@type = type
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
@@ -26,15 +26,13 @@ module CycloneDX
|
|
26
26
|
class Pod
|
27
27
|
class License
|
28
28
|
SPDX_LICENSES = JSON.parse(File.read("#{__dir__}/spdx-licenses.json")).freeze
|
29
|
-
IDENTIFIER_TYPES = [
|
29
|
+
IDENTIFIER_TYPES = %i[id name].freeze
|
30
30
|
|
31
|
-
attr_reader
|
32
|
-
|
33
|
-
attr_accessor :text
|
34
|
-
attr_accessor :url
|
31
|
+
attr_reader :identifier, :identifier_type
|
32
|
+
attr_accessor :text, :url
|
35
33
|
|
36
34
|
def initialize(identifier:)
|
37
|
-
raise ArgumentError,
|
35
|
+
raise ArgumentError, 'License identifier must be non empty' if identifier.nil? || identifier.to_s.strip.empty?
|
38
36
|
|
39
37
|
@identifier = SPDX_LICENSES.find { |license_id| license_id.downcase == identifier.to_s.downcase }
|
40
38
|
@identifier_type = @identifier.nil? ? :name : :id
|
@@ -25,26 +25,40 @@ require_relative 'license'
|
|
25
25
|
module CycloneDX
|
26
26
|
module CocoaPods
|
27
27
|
class Pod
|
28
|
-
|
29
|
-
attr_reader :
|
30
|
-
|
31
|
-
attr_reader :
|
32
|
-
|
33
|
-
attr_reader :
|
34
|
-
|
35
|
-
attr_reader :
|
36
|
-
|
28
|
+
# xs:normalizedString
|
29
|
+
attr_reader :name
|
30
|
+
# xs:normalizedString
|
31
|
+
attr_reader :version
|
32
|
+
# Anything responding to :source_qualifier
|
33
|
+
attr_reader :source
|
34
|
+
# xs:anyURI - https://cyclonedx.org/docs/1.5/xml/#type_externalReference
|
35
|
+
attr_reader :homepage
|
36
|
+
# https://cyclonedx.org/docs/1.5/xml/#type_hashValue (We only use SHA-1 hashes - length == 40)
|
37
|
+
attr_reader :checksum
|
38
|
+
# xs:normalizedString
|
39
|
+
attr_reader :author
|
40
|
+
# xs:normalizedString
|
41
|
+
attr_reader :description
|
42
|
+
# https://cyclonedx.org/docs/1.5/xml/#type_licenseType
|
43
|
+
# We don't currently support several licenses or license expressions https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
44
|
+
attr_reader :license
|
45
|
+
|
37
46
|
def initialize(name:, version:, source: nil, checksum: nil)
|
38
|
-
raise ArgumentError,
|
47
|
+
raise ArgumentError, 'Name must be non empty' if name.nil? || name.to_s.empty?
|
39
48
|
raise ArgumentError, "Name shouldn't contain spaces" if name.to_s.include?(' ')
|
40
49
|
raise ArgumentError, "Name shouldn't start with a dot" if name.to_s.start_with?('.')
|
50
|
+
|
41
51
|
# `pod create` also enforces no plus sign, but more than 500 public pods have a plus in the root name.
|
42
52
|
# https://github.com/CocoaPods/CocoaPods/blob/9461b346aeb8cba6df71fd4e71661688138ec21b/lib/cocoapods/command/lib/create.rb#L35
|
43
53
|
|
44
54
|
Gem::Version.new(version) # To check that the version string is well formed
|
45
|
-
raise ArgumentError,
|
55
|
+
raise ArgumentError, 'Invalid pod source' unless source.nil? || source.respond_to?(:source_qualifier)
|
46
56
|
raise ArgumentError, "#{checksum} is not valid SHA-1 hash" unless checksum.nil? || checksum =~ /[a-fA-F0-9]{40}/
|
47
|
-
|
57
|
+
|
58
|
+
@name = name.to_s
|
59
|
+
@version = version
|
60
|
+
@source = source
|
61
|
+
@checksum = checksum
|
48
62
|
end
|
49
63
|
|
50
64
|
def root_name
|
@@ -61,23 +75,21 @@ module CycloneDX
|
|
61
75
|
end
|
62
76
|
|
63
77
|
def to_s
|
64
|
-
"Pod<#{name}, #{version
|
78
|
+
"Pod<#{name}, #{version}>"
|
65
79
|
end
|
66
80
|
|
67
81
|
private
|
68
82
|
|
69
83
|
def populate_author(attributes)
|
70
84
|
authors = attributes[:author] || attributes[:authors]
|
71
|
-
case authors
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@author = nil
|
80
|
-
end
|
85
|
+
@author = case authors
|
86
|
+
when String
|
87
|
+
authors
|
88
|
+
when Array
|
89
|
+
authors.join(', ')
|
90
|
+
when Hash
|
91
|
+
authors.map { |name, email| "#{name} <#{email}>" }.join(', ')
|
92
|
+
end
|
81
93
|
end
|
82
94
|
|
83
95
|
def populate_description(attributes)
|
@@ -89,19 +101,23 @@ module CycloneDX
|
|
89
101
|
when String
|
90
102
|
@license = License.new(identifier: attributes[:license])
|
91
103
|
when Hash
|
92
|
-
attributes
|
93
|
-
identifier = attributes[:license][:type]
|
94
|
-
unless identifier.nil? || identifier.to_s.strip.empty?
|
95
|
-
@license = License.new(identifier: identifier)
|
96
|
-
@license.text = attributes[:license][:text]
|
97
|
-
else
|
98
|
-
@license = nil
|
99
|
-
end
|
104
|
+
populate_hashed_license(attributes)
|
100
105
|
else
|
101
106
|
@license = nil
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
110
|
+
def populate_hashed_license(attributes)
|
111
|
+
attributes[:license].transform_keys!(&:to_sym)
|
112
|
+
identifier = attributes[:license][:type]
|
113
|
+
if identifier.nil? || identifier.to_s.strip.empty?
|
114
|
+
@license = nil
|
115
|
+
else
|
116
|
+
@license = License.new(identifier: identifier)
|
117
|
+
@license.text = attributes[:license][:text]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
105
121
|
def populate_homepage(attributes)
|
106
122
|
@homepage = attributes[:homepage]
|
107
123
|
end
|
@@ -30,19 +30,35 @@ module CycloneDX
|
|
30
30
|
def self.searchable_source(url:, source_manager:)
|
31
31
|
source = CocoaPodsRepository.new(url: url)
|
32
32
|
source.source_manager = source_manager
|
33
|
-
|
33
|
+
source
|
34
34
|
end
|
35
35
|
|
36
36
|
def attributes_for(pod:)
|
37
37
|
specification_sets = @source_manager.search_by_name("^#{Regexp.escape(pod.root_name)}$")
|
38
|
-
|
39
|
-
raise SearchError, "More than one pod found named #{pod.name}; a pod in a private spec repo should not have the same name as a public pod" if specification_sets.length > 1
|
38
|
+
validate_spec_sets(specification_sets, pod)
|
40
39
|
|
41
40
|
paths = specification_sets[0].specification_paths_for_version(pod.version)
|
42
|
-
|
41
|
+
if paths.empty?
|
42
|
+
raise SearchError,
|
43
|
+
"Version #{pod.version} not found for pod #{pod.name}; run 'pod repo update' and try again"
|
44
|
+
end
|
43
45
|
|
44
46
|
::Pod::Specification.from_file(paths[0]).attributes_hash
|
45
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def validate_spec_sets(specification_sets, pod)
|
52
|
+
if specification_sets.empty?
|
53
|
+
raise SearchError,
|
54
|
+
"No pod found named #{pod.name}; run 'pod repo update' and try again"
|
55
|
+
end
|
56
|
+
return unless specification_sets.length > 1
|
57
|
+
|
58
|
+
raise SearchError,
|
59
|
+
"More than one pod found named #{pod.name}; a pod in a private spec repo " \
|
60
|
+
'should not have the same name as a public pod'
|
61
|
+
end
|
46
62
|
end
|
47
63
|
|
48
64
|
class GitRepository
|
@@ -64,7 +80,6 @@ module CycloneDX
|
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
67
|
-
|
68
83
|
class Pod
|
69
84
|
def complete_information_from_source
|
70
85
|
populate(source.attributes_for(pod: self))
|
@@ -31,36 +31,17 @@ module CycloneDX
|
|
31
31
|
module CocoaPods
|
32
32
|
class PodfileParsingError < StandardError; end
|
33
33
|
|
34
|
+
# Uses cocoapods to analyze the Podfile and Podfile.lock for component dependency information
|
34
35
|
class PodfileAnalyzer
|
35
36
|
def initialize(logger:, exclude_test_targets: false)
|
36
37
|
@logger = logger
|
37
38
|
@exclude_test_targets = exclude_test_targets
|
38
39
|
end
|
39
40
|
|
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
|
-
|
57
41
|
def ensure_podfile_and_lock_are_present(options)
|
58
42
|
project_dir = Pathname.new(options[:path] || Dir.pwd)
|
59
|
-
|
60
|
-
|
61
|
-
raise PodfileParsingError, "Missing Podfile in #{project_dir}. Please use the --path option if not running from the CocoaPods project directory." unless File.exist?(options[:podfile_path])
|
62
|
-
options[:podfile_lock_path] = project_dir + 'Podfile.lock'
|
63
|
-
raise PodfileParsingError, "Missing Podfile.lock, please run 'pod install' before generating BOM" unless File.exist?(options[:podfile_lock_path])
|
43
|
+
|
44
|
+
validate_options(project_dir, options)
|
64
45
|
|
65
46
|
initialize_cocoapods_config(project_dir)
|
66
47
|
|
@@ -68,81 +49,138 @@ module CycloneDX
|
|
68
49
|
verify_synced_sandbox(lockfile)
|
69
50
|
load_plugins(options[:podfile_path])
|
70
51
|
|
71
|
-
|
52
|
+
[::Pod::Podfile.from_file(options[:podfile_path]), lockfile]
|
72
53
|
end
|
73
54
|
|
74
|
-
|
75
55
|
def parse_pods(podfile, lockfile)
|
76
56
|
@logger.debug "Parsing pods from #{podfile.defined_in_file}"
|
77
57
|
included_pods, dependencies = create_list_of_included_pods(podfile, lockfile)
|
78
58
|
|
79
59
|
pods = lockfile.pod_names.select { |name| included_pods.include?(name) }.map do |name|
|
80
|
-
Pod.new(name: name, version: lockfile.version(name), source: source_for_pod(podfile, lockfile, name),
|
60
|
+
Pod.new(name: name, version: lockfile.version(name), source: source_for_pod(podfile, lockfile, name),
|
61
|
+
checksum: lockfile.checksum(name))
|
81
62
|
end
|
82
63
|
|
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
|
-
}
|
64
|
+
pod_dependencies = parse_dependencies(dependencies, podfile, lockfile)
|
94
65
|
|
95
|
-
|
66
|
+
[pods, pod_dependencies]
|
96
67
|
end
|
97
68
|
|
98
|
-
|
99
69
|
def populate_pods_with_additional_info(pods)
|
100
70
|
pods.each do |pod|
|
101
71
|
@logger.debug "Completing information for #{pod.name}"
|
102
72
|
pod.complete_information_from_source
|
103
73
|
end
|
104
|
-
|
74
|
+
pods
|
105
75
|
end
|
106
76
|
|
107
77
|
private
|
108
78
|
|
79
|
+
def load_plugins(podfile_path)
|
80
|
+
podfile_contents = File.read(podfile_path)
|
81
|
+
plugin_syntax = /\s*plugin\s+['"]([^'"]+)['"]/
|
82
|
+
plugin_names = podfile_contents.scan(plugin_syntax).flatten
|
83
|
+
|
84
|
+
plugin_names.each do |plugin_name|
|
85
|
+
load_one_plugin(plugin_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_one_plugin(plugin_name)
|
90
|
+
@logger.debug("Loading plugin #{plugin_name}")
|
91
|
+
begin
|
92
|
+
plugin_spec = Gem::Specification.find_by_name(plugin_name)
|
93
|
+
plugin_spec&.activate
|
94
|
+
load("#{plugin_spec.gem_dir}/lib/cocoapods_plugin.rb") if plugin_spec
|
95
|
+
rescue Gem::LoadError => e
|
96
|
+
@logger.warn("Failed to load plugin #{plugin_name}. #{e.message}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_options(project_dir, options)
|
101
|
+
raise PodfileParsingError, "#{options[:path]} is not a valid directory." unless File.directory?(project_dir)
|
102
|
+
|
103
|
+
options[:podfile_path] = project_dir + 'Podfile'
|
104
|
+
unless File.exist?(options[:podfile_path])
|
105
|
+
raise PodfileParsingError, "Missing Podfile in #{project_dir}. Please use the --path option if " \
|
106
|
+
'not running from the CocoaPods project directory.'
|
107
|
+
end
|
108
|
+
|
109
|
+
options[:podfile_lock_path] = project_dir + 'Podfile.lock'
|
110
|
+
return if File.exist?(options[:podfile_lock_path])
|
111
|
+
|
112
|
+
raise PodfileParsingError, "Missing Podfile.lock, please run 'pod install' before generating BOM"
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_dependencies(dependencies, podfile, lockfile)
|
116
|
+
pod_dependencies = {}
|
117
|
+
dependencies.each do |key, podname_array|
|
118
|
+
next unless lockfile.pod_names.include? key
|
119
|
+
|
120
|
+
pod = Pod.new(name: key, version: lockfile.version(key), source: source_for_pod(podfile, lockfile, key),
|
121
|
+
checksum: lockfile.checksum(key))
|
122
|
+
|
123
|
+
pod_dependencies[pod.purl] = dependencies_for_pod(podname_array, podfile, lockfile)
|
124
|
+
end
|
125
|
+
|
126
|
+
pod_dependencies
|
127
|
+
end
|
128
|
+
|
129
|
+
def dependencies_for_pod(podname_array, podfile, lockfile)
|
130
|
+
lockfile.pod_names.select { |name| podname_array.include?(name) }.map do |name|
|
131
|
+
pod = Pod.new(name: name,
|
132
|
+
version: lockfile.version(name),
|
133
|
+
source: source_for_pod(podfile, lockfile, name),
|
134
|
+
checksum: lockfile.checksum(name))
|
135
|
+
pod.purl
|
136
|
+
end
|
137
|
+
end
|
109
138
|
|
110
139
|
def initialize_cocoapods_config(project_dir)
|
111
140
|
::Pod::Config.instance.installation_root = project_dir
|
112
141
|
end
|
113
142
|
|
114
|
-
|
115
143
|
def verify_synced_sandbox(lockfile)
|
116
|
-
|
117
|
-
|
118
|
-
|
144
|
+
manifest_file = ::Pod::Config.instance.sandbox.manifest
|
145
|
+
if manifest_file.nil?
|
146
|
+
raise PodfileParsingError,
|
147
|
+
"Missing Manifest.lock, please run 'pod install' before generating BOM"
|
148
|
+
end
|
149
|
+
return if lockfile == manifest_file
|
150
|
+
|
151
|
+
raise PodfileParsingError,
|
152
|
+
"The sandbox is not in sync with the Podfile.lock. Run 'pod install' " \
|
153
|
+
'or update your CocoaPods installation.'
|
119
154
|
end
|
120
155
|
|
121
156
|
def simple_hash_of_lockfile_pods(lockfile)
|
122
|
-
pods_hash = {
|
157
|
+
pods_hash = {}
|
123
158
|
|
124
159
|
pods_used = lockfile.internal_data['PODS']
|
125
|
-
pods_used&.each
|
126
|
-
|
127
|
-
|
128
|
-
pod_name = pod.split.first
|
129
|
-
pods_hash[pod_name] = []
|
130
|
-
else
|
131
|
-
# Pods stored as a hash have pod name and dependencies.
|
132
|
-
pod.each { |pod, dependencies|
|
133
|
-
pod_name = pod.split.first
|
134
|
-
pods_hash[pod_name] = dependencies.map { |d| d.split.first }
|
135
|
-
}
|
136
|
-
end
|
137
|
-
}
|
160
|
+
pods_used&.each do |pod|
|
161
|
+
map_single_pod(pod, pods_hash)
|
162
|
+
end
|
138
163
|
pods_hash
|
139
164
|
end
|
140
165
|
|
166
|
+
def map_single_pod(pod, pods_hash)
|
167
|
+
if pod.is_a?(String)
|
168
|
+
# Pods stored as String have no dependencies
|
169
|
+
pod_name = pod.split.first
|
170
|
+
pods_hash[pod_name] = []
|
171
|
+
else
|
172
|
+
# Pods stored as a hash have pod name and dependencies.
|
173
|
+
pod.each do |pod, dependencies|
|
174
|
+
pod_name = pod.split.first
|
175
|
+
pods_hash[pod_name] = dependencies.map { |d| d.split.first }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
141
179
|
|
142
180
|
def append_all_pod_dependencies(pods_used, pods_cache)
|
143
181
|
result = pods_used
|
144
182
|
original_number = 0
|
145
|
-
dependencies_hash = {
|
183
|
+
dependencies_hash = {}
|
146
184
|
|
147
185
|
# Loop adding pod dependencies until we are not adding any more dependencies to the result
|
148
186
|
# This brings in all the transitive dependencies of every top level pod.
|
@@ -152,76 +190,79 @@ module CycloneDX
|
|
152
190
|
while result.length != original_number
|
153
191
|
original_number = result.length
|
154
192
|
|
155
|
-
pods_used.each
|
193
|
+
pods_used.each do |pod_name|
|
156
194
|
if pods_cache.key?(pod_name)
|
157
195
|
result.push(*pods_cache[pod_name])
|
158
196
|
dependencies_hash[pod_name] = pods_cache[pod_name].empty? ? [] : pods_cache[pod_name]
|
159
197
|
end
|
160
|
-
|
198
|
+
end
|
161
199
|
|
162
200
|
result = result.uniq
|
163
|
-
# maybe additional dependency processing needed here???
|
164
201
|
pods_used = result
|
165
202
|
end
|
166
203
|
|
167
|
-
|
204
|
+
[result, dependencies_hash]
|
168
205
|
end
|
169
206
|
|
170
207
|
def create_list_of_included_pods(podfile, lockfile)
|
171
208
|
pods_cache = simple_hash_of_lockfile_pods(lockfile)
|
172
209
|
|
173
|
-
|
174
|
-
|
175
|
-
@logger.debug "Including all pods for targets: #{
|
210
|
+
included_targets = podfile.target_definition_list.select { |target| include_target_named(target.label) }
|
211
|
+
included_target_names = included_targets.map(&:label)
|
212
|
+
@logger.debug "Including all pods for targets: #{included_target_names}"
|
176
213
|
|
177
|
-
|
178
|
-
pods_used =
|
214
|
+
top_level_deps = included_targets.map(&:dependencies).flatten.uniq
|
215
|
+
pods_used = top_level_deps.map(&:name).uniq
|
179
216
|
pods_used, dependencies = append_all_pod_dependencies(pods_used, pods_cache)
|
180
217
|
|
181
|
-
|
218
|
+
[pods_used.sort, dependencies]
|
182
219
|
end
|
183
220
|
|
184
|
-
|
185
221
|
def include_target_named(targetname)
|
186
|
-
|
222
|
+
!@exclude_test_targets || !targetname.downcase.include?('test')
|
187
223
|
end
|
188
224
|
|
189
|
-
|
190
225
|
def cocoapods_repository_source(podfile, lockfile, pod_name)
|
191
226
|
@source_manager ||= create_source_manager(podfile)
|
192
|
-
|
227
|
+
Source::CocoaPodsRepository.searchable_source(url: lockfile.spec_repo(pod_name),
|
228
|
+
source_manager: @source_manager)
|
193
229
|
end
|
194
230
|
|
195
|
-
|
196
231
|
def git_source(lockfile, pod_name)
|
197
232
|
checkout_options = lockfile.checkout_options_for_pod_named(pod_name)
|
198
233
|
url = checkout_options[:git]
|
199
|
-
[
|
200
|
-
|
234
|
+
%i[tag branch commit].each do |type|
|
235
|
+
if checkout_options[type]
|
236
|
+
return Source::GitRepository.new(url: url, type: type,
|
237
|
+
label: checkout_options[type])
|
238
|
+
end
|
201
239
|
end
|
202
|
-
|
240
|
+
Source::GitRepository.new(url: url)
|
203
241
|
end
|
204
242
|
|
205
|
-
|
206
243
|
def source_for_pod(podfile, lockfile, pod_name)
|
207
244
|
root_name = pod_name.split('/').first
|
208
245
|
return cocoapods_repository_source(podfile, lockfile, root_name) unless lockfile.spec_repo(root_name).nil?
|
209
246
|
return git_source(lockfile, root_name) unless lockfile.checkout_options_for_pod_named(root_name).nil?
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
247
|
+
if lockfile.to_hash['EXTERNAL SOURCES'][root_name][:path]
|
248
|
+
return Source::LocalPod.new(path: lockfile.to_hash['EXTERNAL SOURCES'][root_name][:path])
|
249
|
+
end
|
250
|
+
if lockfile.to_hash['EXTERNAL SOURCES'][root_name][:podspec]
|
251
|
+
return Source::Podspec.new(url: lockfile.to_hash['EXTERNAL SOURCES'][root_name][:podspec])
|
252
|
+
end
|
214
253
|
|
254
|
+
nil
|
255
|
+
end
|
215
256
|
|
216
257
|
def create_source_manager(podfile)
|
217
|
-
|
258
|
+
source_manager = ::Pod::Source::Manager.new(::Pod::Config.instance.repos_dir)
|
218
259
|
@logger.debug "Parsing sources from #{podfile.defined_in_file}"
|
219
260
|
podfile.sources.each do |source|
|
220
261
|
@logger.debug "Ensuring #{source} is available for searches"
|
221
|
-
|
262
|
+
source_manager.find_or_create_source_with_url(source)
|
222
263
|
end
|
223
|
-
@logger.debug
|
224
|
-
|
264
|
+
@logger.debug 'Source manager successfully created with all needed sources'
|
265
|
+
source_manager
|
225
266
|
end
|
226
267
|
end
|
227
268
|
end
|
@@ -31,13 +31,16 @@ module CycloneDX
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class GitRepository
|
34
|
-
VALID_TYPES = [
|
34
|
+
VALID_TYPES = %i[branch tag commit].freeze
|
35
35
|
|
36
36
|
attr_reader :url, :type, :label
|
37
37
|
|
38
38
|
def initialize(url:, type: nil, label: nil)
|
39
|
-
raise ArgumentError,
|
40
|
-
|
39
|
+
raise ArgumentError, 'Invalid checkout information' if !type.nil? && !VALID_TYPES.include?(type)
|
40
|
+
|
41
|
+
@url = url
|
42
|
+
@type = type
|
43
|
+
@label = label
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
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.3.0
|
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-
|
12
|
+
date: 2024-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cocoapods
|
@@ -52,47 +52,47 @@ dependencies:
|
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '2.0'
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
55
|
+
name: equivalent-xml
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 0.6.0
|
61
61
|
type: :development
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
67
|
+
version: 0.6.0
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
|
-
name:
|
69
|
+
name: rake
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '13.0'
|
75
75
|
type: :development
|
76
76
|
prerelease: false
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
81
|
+
version: '13.0'
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
83
|
+
name: rspec
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
88
|
+
version: '3.0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
95
|
+
version: '3.0'
|
96
96
|
description: CycloneDX is a lightweight software bill-of-material (SBOM) specification
|
97
97
|
designed for use in application security contexts and supply chain component analysis.
|
98
98
|
This Gem generates CycloneDX BOMs from CocoaPods projects.
|
@@ -140,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: '0'
|
142
142
|
requirements: []
|
143
|
-
rubygems_version: 3.
|
143
|
+
rubygems_version: 3.5.4
|
144
144
|
signing_key:
|
145
145
|
specification_version: 4
|
146
146
|
summary: CycloneDX software bill-of-material (SBOM) generation utility
|