Skip to content

Commit 7774b64

Browse files
committed
✨ Core v1.7 Enablement
- Add spec version selection end-to-end with a new --spec-version flag (default 1.7). - Update JSON and XML outputs to honor the selected spec version. - Update fixtures, help text, tests, and docs. Files: - lib/bom_helpers.rb: - Added SUPPORTED_SPEC_VERSIONS, cyclonedx_xml_namespace helper. build_bom now accepts spec_version and routes to: - build_json_bom(gems, spec_version) sets specVersion to the provided version. - build_bom_xml(gems, spec_version) sets xmlns to http://cyclonedx.org/schema/bom/<version>.</version> - lib/bom_builder.rb: - Added --spec-version with validation; default is 1.7. - Pass @spec_version into build_bom(@Gems, @bom_output_format, @spec_version).
1 parent 1caed9d commit 7774b64

File tree

13 files changed

+123
-34
lines changed

13 files changed

+123
-34
lines changed

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,29 @@ cyclonedx-ruby [options]
2929

3030
`-v, --[no-]verbose` Run verbosely
3131
`-p, --path path` Path to Ruby project directory
32-
`-f, --format` Bom output format
32+
`-o, --output bom_file_path` Path to output the bom file
33+
`-f, --format bom_output_format` Output format for bom. Supported: xml (default), json
34+
`-s, --spec-version version` CycloneDX spec version to target (default: 1.7). Supported: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
3335
`-h, --help` Show help message
3436

3537
**Output:** bom.xml or bom.json file in project directory
3638

37-
#### Example
39+
- By default, outputs conform to CycloneDX spec version 1.7.
40+
- To generate an older spec version, use `--spec-version`.
41+
42+
#### Examples
3843
```bash
44+
# Default (XML, CycloneDX 1.7)
3945
cyclonedx-ruby -p /path/to/ruby/project
46+
47+
# JSON at CycloneDX 1.7
48+
cyclonedx-ruby -p /path/to/ruby/project -f json
49+
50+
# XML at CycloneDX 1.3
51+
cyclonedx-ruby -p /path/to/ruby/project -s 1.3
52+
53+
# JSON at CycloneDX 1.2 to a custom path
54+
cyclonedx-ruby -p /path/to/ruby/project -f json -s 1.2 -o bom/out.json
4055
```
4156

4257

@@ -48,4 +63,3 @@ CycloneDX Ruby Gem is Copyright (c) OWASP Foundation. All Rights Reserved.
4863
Permission to modify and redistribute is granted under the terms of the Apache 2.0 license. See the [LICENSE] file for the full license.
4964

5065
[License]: https://github.com/CycloneDX/cyclonedx-ruby-gem/blob/master/LICENSE
51-

cyclonedx-ruby.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
2929
spec.add_dependency('activesupport', '~> 7.0')
3030
spec.add_development_dependency 'rake', '~> 13'
3131
spec.add_development_dependency 'rspec', '~> 3.12'
32-
spec.add_development_dependency 'cucumber', '~> 8.0'
33-
spec.add_development_dependency 'aruba', '~> 2.1'
32+
spec.add_development_dependency 'cucumber', '~> 10.1', '>= 10.1.1'
33+
spec.add_development_dependency 'aruba', '~> 2.2'
3434
spec.add_development_dependency 'simplecov', '~> 0.22.0'
3535
spec.add_development_dependency 'rubocop', '~> 1.54'
3636
end

features/fixtures/simple/Gemfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
source 'https://rubygems.org'
22

3-
gem 'activesupport'
3+
gem 'activesupport', '7.0.4.3'
4+
gem 'concurrent-ruby', '1.2.2'
5+
gem 'i18n', '1.12.0'
6+
gem 'minitest', '5.18.0'
7+
gem 'tzinfo', '2.0.6'

features/fixtures/simple/Gemfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4-
activesupport (7.0.7.1)
4+
activesupport (7.0.4.3)
55
concurrent-ruby (~> 1.0, >= 1.0.2)
66
i18n (>= 1.6, < 2)
77
minitest (>= 5.1)
88
tzinfo (~> 2.0)
99
concurrent-ruby (1.2.2)
10-
i18n (1.14.1)
10+
i18n (1.12.0)
1111
concurrent-ruby (~> 1.0)
12-
minitest (5.19.0)
12+
minitest (5.18.0)
1313
tzinfo (2.0.6)
1414
concurrent-ruby (~> 1.0)
1515

features/fixtures/simple/bom.json.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"bomFormat": "CycloneDX",
3-
"specVersion": "1.1",
3+
"specVersion": "1.7",
44
"serialNumber": "urn:uuid:d498cdc2-5494-4031-b37d-ff3d10d336bf",
55
"version": 1,
66
"components": [
@@ -105,4 +105,4 @@
105105
]
106106
}
107107
]
108-
}
108+
}

features/fixtures/simple/bom.xml.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<bom xmlns="http://cyclonedx.org/schema/bom/1.1" version="1" serialNumber="urn:uuid:ffc51349-2d7d-408e-b2c1-3e3f220e6d2f">
2+
<bom xmlns="http://cyclonedx.org/schema/bom/1.7" version="1" serialNumber="urn:uuid:ffc51349-2d7d-408e-b2c1-3e3f220e6d2f">
33
<components>
44
<component type="library">
55
<name>activesupport</name>

features/help.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ Scenario: Generate help on demand
1212
-p, --path path (Required) Path to Ruby project directory
1313
-o, --output bom_file_path (Optional) Path to output the bom.xml file to
1414
-f, --format bom_output_format (Optional) Output format for bom. Currently support xml (default) and json.
15+
-s, --spec-version version (Optional) CycloneDX spec version to target (default: 1.7). Supported: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
1516
-h, --help Show help message
1617
"""

features/json_format.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,3 @@ Feature: Creating BOM using Json format
4040
"""
4141
And a file named "bom.json" should exist
4242
And the generated Json BOM file "bom.json" matches "bom.json.expected"
43-

features/step_definitions/json_bom_matching.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
serial_number_matcher = /\"serialNumber\": \"urn:uuid:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\"/
66
normalized_serial_number = '"serialNumber": "urn:uuid:00000000-0000-0000-0000-000000000000"'
7-
normalized_generated_file_contents = generated_file_contents.gsub(serial_number_matcher, normalized_serial_number)
8-
normalized_expected_file_contents = expected_file_contents.gsub(serial_number_matcher, normalized_serial_number)
7+
normalized_generated_file_contents = generated_file_contents.gsub(serial_number_matcher, normalized_serial_number).rstrip
8+
normalized_expected_file_contents = expected_file_contents.gsub(serial_number_matcher, normalized_serial_number).rstrip
99

1010
expect(normalized_expected_file_contents).to eq(normalized_generated_file_contents)
1111
end

lib/bom_builder.rb

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,27 @@
3535

3636
class Bombuilder
3737
SUPPORTED_BOM_FORMATS = %w[xml json]
38+
SUPPORTED_SPEC_VERSIONS = %w[1.1 1.2 1.3 1.4 1.5 1.6 1.7]
3839

3940
def self.build(path)
4041
original_working_directory = Dir.pwd
4142
setup(path)
4243
specs_list
43-
bom = build_bom(@gems, @bom_output_format)
44+
bom = build_bom(@gems, @bom_output_format, @spec_version)
4445

4546
begin
4647
@logger.info("Changing directory to the original working directory located at #{original_working_directory}")
4748
Dir.chdir original_working_directory
4849
rescue StandardError => e
49-
@logger.error("Unable to change directory the original working directory located at #{original_working_directory}. #{e.message}: #{e.backtrace.join('\n')}")
50+
@logger.error("Unable to change directory the original working directory located at #{original_working_directory}. #{e.message}: #{Array(e.backtrace).join("\n")}")
5051
abort
5152
end
5253

5354
bom_directory = File.dirname(@bom_file_path)
5455
begin
5556
FileUtils.mkdir_p(bom_directory) unless File.directory?(bom_directory)
5657
rescue StandardError => e
57-
@logger.error("Unable to create the directory to hold the BOM output at #{@bom_directory}. #{e.message}: #{e.backtrace.join('\n')}")
58+
@logger.error("Unable to create the directory to hold the BOM output at #{bom_directory}. #{e.message}: #{Array(e.backtrace).join("\n")}")
5859
abort
5960
end
6061

@@ -68,7 +69,7 @@ def self.build(path)
6869
puts "#{@gems.size} gems were written to BOM located at #{@bom_file_path}"
6970
end
7071
rescue StandardError => e
71-
@logger.error("Unable to write BOM to #{@bom_file_path}. #{e.message}: #{e.backtrace.join('\n')}")
72+
@logger.error("Unable to write BOM to #{@bom_file_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")
7273
abort
7374
end
7475
end
@@ -81,21 +82,27 @@ def self.setup(path)
8182
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
8283
@options[:verbose] = v
8384
end
84-
opts.on('-p', '--path path', '(Required) Path to Ruby project directory') do |path|
85-
@options[:path] = path
85+
opts.on('-p', '--path path', '(Required) Path to Ruby project directory') do |proj_path_opt|
86+
@options[:path] = proj_path_opt
8687
end
8788
opts.on('-o', '--output bom_file_path', '(Optional) Path to output the bom.xml file to') do |bom_file_path|
8889
@options[:bom_file_path] = bom_file_path
8990
end
9091
opts.on('-f', '--format bom_output_format', '(Optional) Output format for bom. Currently support xml (default) and json.') do |bom_output_format|
9192
@options[:bom_output_format] = bom_output_format
9293
end
94+
opts.on('-s', '--spec-version version', '(Optional) CycloneDX spec version to target (default: 1.7). Supported: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7') do |spec_version|
95+
@options[:spec_version] = spec_version
96+
end
9397
opts.on_tail('-h', '--help', 'Show help message') do
9498
puts opts
9599
exit
96100
end
97101
end.parse!
98102

103+
# Allow passing the path as a positional arg via exe wrapper
104+
@options[:path] ||= path
105+
99106
@logger = Logger.new($stdout)
100107
@logger.level = if @options[:verbose]
101108
Logger::INFO
@@ -117,11 +124,15 @@ def self.setup(path)
117124
abort
118125
end
119126

127+
# Normalize to an absolute project path to avoid relative path issues later
128+
@project_path = File.expand_path(@options[:path])
129+
@provided_path = @options[:path]
130+
120131
begin
121-
@logger.info("Changing directory to Ruby project directory located at #{@options[:path]}")
122-
Dir.chdir @options[:path]
132+
@logger.info("Changing directory to Ruby project directory located at #{@provided_path}")
133+
Dir.chdir @project_path
123134
rescue StandardError => e
124-
@logger.error("Unable to change directory to Ruby project directory located at #{@options[:path]}. #{e.message}: #{e.backtrace.join('\n')}")
135+
@logger.error("Unable to change directory to Ruby project directory located at #{@provided_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")
125136
abort
126137
end
127138

@@ -134,6 +145,15 @@ def self.setup(path)
134145
abort
135146
end
136147

148+
# Spec version selection
149+
requested_spec = @options[:spec_version] || '1.7'
150+
if SUPPORTED_SPEC_VERSIONS.include?(requested_spec)
151+
@spec_version = requested_spec
152+
else
153+
@logger.error("Unrecognized CycloneDX spec version '#{requested_spec}'. Please choose one of #{SUPPORTED_SPEC_VERSIONS}")
154+
abort
155+
end
156+
137157
@bom_file_path = if @options[:bom_file_path].nil?
138158
"./bom.#{@bom_output_format}"
139159
else
@@ -143,13 +163,16 @@ def self.setup(path)
143163
@logger.info("BOM will be written to #{@bom_file_path}")
144164

145165
begin
146-
gemfile_path = "#{@options[:path]}/Gemfile.lock"
147-
@logger.info("Parsing specs from #{gemfile_path}...")
166+
# Use absolute path so it's correct regardless of current working directory
167+
gemfile_path = File.join(@project_path, 'Gemfile.lock')
168+
# Compute display path for logs: './Gemfile.lock' when provided path is '.', else '<provided>/Gemfile.lock'
169+
display_gemfile_path = (@provided_path == '.' ? './Gemfile.lock' : File.join(@provided_path, 'Gemfile.lock'))
170+
@logger.info("Parsing specs from #{display_gemfile_path}...")
148171
gemfile_contents = File.read(gemfile_path)
149172
@specs = Bundler::LockfileParser.new(gemfile_contents).specs
150173
@logger.info('Specs successfully parsed!')
151174
rescue StandardError => e
152-
@logger.error("Unable to parse specs from #{gemfile_path}. #{e.message}: #{e.backtrace.join('\n')}")
175+
@logger.error("Unable to parse specs from #{gemfile_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")
153176
abort
154177
end
155178
end

0 commit comments

Comments
 (0)