spandx 0.12.3 → 0.13.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 +50 -25
- data/README.md +11 -7
- data/exe/spandx +1 -2
- data/ext/spandx/extconf.rb +5 -0
- data/lib/spandx.rb +6 -3
- data/lib/spandx/cli.rb +1 -0
- data/lib/spandx/cli/commands/build.rb +13 -2
- data/lib/spandx/cli/commands/scan.rb +31 -25
- data/lib/spandx/cli/main.rb +1 -0
- data/lib/spandx/core/cache.rb +38 -51
- data/lib/spandx/core/concurrent.rb +40 -0
- data/lib/spandx/core/content.rb +5 -23
- data/lib/spandx/core/data_file.rb +66 -0
- data/lib/spandx/core/git.rb +8 -30
- data/lib/spandx/core/guess.rb +37 -40
- data/lib/spandx/core/http.rb +6 -1
- data/lib/spandx/core/index_file.rb +101 -0
- data/lib/spandx/core/license_plugin.rb +3 -3
- data/lib/spandx/core/line_io.rb +23 -0
- data/lib/spandx/core/path_traversal.rb +44 -0
- data/lib/spandx/core/relation.rb +38 -0
- data/lib/spandx/core/thread_pool.rb +15 -4
- data/lib/spandx/dotnet/index.rb +21 -79
- data/lib/spandx/java/index.rb +5 -2
- data/lib/spandx/python/index.rb +4 -33
- data/lib/spandx/spdx/catalogue.rb +4 -0
- data/lib/spandx/spdx/composite_license.rb +60 -0
- data/lib/spandx/spdx/expression.rb +114 -0
- data/lib/spandx/spdx/license.rb +4 -14
- data/lib/spandx/version.rb +1 -1
- data/spandx.gemspec +13 -10
- metadata +70 -27
- data/lib/spandx/core/null_gateway.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2cbe4ba53ef5f776dd0c52170e0c7b56b0bf2aa9893f221c45636fcc7080cb5
|
4
|
+
data.tar.gz: ee0936304c2322df4d568223aa1095dffe1b874a1902f6fc74504e28b63dffd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19627c27dca8dc2609549f7a4245488689aa0dda9846dfc555e50b39735e6a2895686d2b1a672afaecc73080f02436070e34060b15b2cc250dbbe13ca46999eb
|
7
|
+
data.tar.gz: ee2b9816fb6e85e94c32ba05844540872244cf8cc4aac175ce3043c921429bbc4aaaf643c753b681c83a06eb6e7ae3bb044fd2944d9fa1e70508143e01bf18c1
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Version 0.
|
1
|
+
Version 0.13.0
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
@@ -9,6 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
9
9
|
|
10
10
|
## [Unreleased]
|
11
11
|
|
12
|
+
## [0.13.0] - 2020-05-12
|
13
|
+
### Added
|
14
|
+
- Add progress bar
|
15
|
+
- Add SPDX expression parser.
|
16
|
+
- Add index for each cache.
|
17
|
+
- Update cache paths to point to Spandx organization.
|
18
|
+
- Add optimized CSV parser.
|
19
|
+
- Fetch dependency data concurrently.
|
20
|
+
- Add profiling and benchmarking tools.
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
- Update git pull command to fetch master branch with a depth of 1.
|
24
|
+
- Update Nuget and PyPI cache builders to use same API for writing to cache.
|
25
|
+
- Update license lookup to parse SPDX expressions.
|
26
|
+
|
27
|
+
### Removed
|
28
|
+
- Drop Ruby 2.4 support.
|
29
|
+
- Drop Jaro Winkler algorithm.
|
30
|
+
- Drop Levenshtein algorithm.
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
- Fix bug in spawning worker threads in thread pool.
|
34
|
+
- Reset `http` global before each test to remove leakage between tests.
|
35
|
+
|
12
36
|
## [0.12.3] - 2020-04-19
|
13
37
|
### Fixed
|
14
38
|
- Ignore nuget entries with missing `items`.
|
@@ -144,27 +168,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
144
168
|
### Added
|
145
169
|
- Provide ruby API to the latest SPDX catalogue.
|
146
170
|
|
147
|
-
[Unreleased]: https://github.com/
|
148
|
-
[0.
|
149
|
-
[0.12.
|
150
|
-
[0.12.
|
151
|
-
[0.12.
|
152
|
-
[0.
|
153
|
-
[0.
|
154
|
-
[0.10.
|
155
|
-
[0.
|
156
|
-
[0.
|
157
|
-
[0.
|
158
|
-
[0.
|
159
|
-
[0.
|
160
|
-
[0.
|
161
|
-
[0.4.
|
162
|
-
[0.
|
163
|
-
[0.
|
164
|
-
[0.
|
165
|
-
[0.1.
|
166
|
-
[0.1.
|
167
|
-
[0.1.
|
168
|
-
[0.1.
|
169
|
-
[0.1.
|
170
|
-
[0.1.
|
171
|
+
[Unreleased]: https://github.com/spandx/spandx/compare/v0.13.0...HEAD
|
172
|
+
[0.13.0]: https://github.com/spandx/spandx/compare/v0.12.3...v0.13.0
|
173
|
+
[0.12.3]: https://github.com/spandx/spandx/compare/v0.12.2...v0.12.3
|
174
|
+
[0.12.2]: https://github.com/spandx/spandx/compare/v0.12.1...v0.12.2
|
175
|
+
[0.12.1]: https://github.com/spandx/spandx/compare/v0.12.0...v0.12.1
|
176
|
+
[0.12.0]: https://github.com/spandx/spandx/compare/v0.11.0...v0.12.0
|
177
|
+
[0.11.0]: https://github.com/spandx/spandx/compare/v0.10.1...v0.11.0
|
178
|
+
[0.10.1]: https://github.com/spandx/spandx/compare/v0.10.0...v0.10.1
|
179
|
+
[0.10.0]: https://github.com/spandx/spandx/compare/v0.9.0...v0.10.0
|
180
|
+
[0.9.0]: https://github.com/spandx/spandx/compare/v0.8.0...v0.9.0
|
181
|
+
[0.8.0]: https://github.com/spandx/spandx/compare/v0.7.0...v0.8.0
|
182
|
+
[0.7.0]: https://github.com/spandx/spandx/compare/v0.6.0...v0.7.0
|
183
|
+
[0.6.0]: https://github.com/spandx/spandx/compare/v0.5.0...v0.6.0
|
184
|
+
[0.5.0]: https://github.com/spandx/spandx/compare/v0.4.1...v0.5.0
|
185
|
+
[0.4.1]: https://github.com/spandx/spandx/compare/v0.4.0...v0.4.1
|
186
|
+
[0.4.0]: https://github.com/spandx/spandx/compare/v0.3.0...v0.4.0
|
187
|
+
[0.3.0]: https://github.com/spandx/spandx/compare/v0.2.0...v0.3.0
|
188
|
+
[0.2.0]: https://github.com/spandx/spandx/compare/v0.1.7...v0.2.0
|
189
|
+
[0.1.7]: https://github.com/spandx/spandx/compare/v0.1.6...v0.1.7
|
190
|
+
[0.1.6]: https://github.com/spandx/spandx/compare/v0.1.5...v0.1.6
|
191
|
+
[0.1.5]: https://github.com/spandx/spandx/compare/v0.1.4...v0.1.5
|
192
|
+
[0.1.4]: https://github.com/spandx/spandx/compare/v0.1.3...v0.1.4
|
193
|
+
[0.1.3]: https://github.com/spandx/spandx/compare/v0.1.2...v0.1.3
|
194
|
+
[0.1.2]: https://github.com/spandx/spandx/compare/v0.1.1...v0.1.2
|
195
|
+
[0.1.1]: https://github.com/spandx/spandx/compare/v0.1.0...v0.1.1
|
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
|
1
|
+

|
2
|
+
|
3
|
+
*Logo courtesy of [@speasley](https://github.com/speasley)*
|
4
|
+
|
5
|
+
# Spandx 
|
2
6
|
|
3
7
|
A ruby API for interacting with the https://spdx.org software license catalogue.
|
4
8
|
This gem includes a command line interface to scan a software project for the
|
5
9
|
software licenses that are associated with each dependency in the project.
|
6
10
|
`spandx` leverages an offline cache of software licenses for known dependencies.
|
7
|
-
The offline cache allows
|
11
|
+
The offline cache allows Spandx to perform an air gap friendly scan of software
|
8
12
|
projects.
|
9
13
|
|
10
14
|
### Supported project types
|
@@ -42,7 +46,7 @@ Or install it yourself as:
|
|
42
46
|
|
43
47
|
### Command line interface
|
44
48
|
|
45
|
-
The command line interface supports operations to
|
49
|
+
The command line interface supports operations to fetch the latest pre-built cache.
|
46
50
|
See the help for each subcommand for more information on how to use the command.
|
47
51
|
|
48
52
|
```bash
|
@@ -62,19 +66,19 @@ To scan a specific project file use the `scan` command:
|
|
62
66
|
モ spandx scan ruby/Gemfile.lock
|
63
67
|
```
|
64
68
|
|
65
|
-
To activate
|
69
|
+
To activate air gap mode use the `--airgap` option:
|
66
70
|
|
67
71
|
```bash
|
68
72
|
モ spandx scan dotnet/application.sln --airgap
|
69
73
|
モ spandx scan ruby/Gemfile.lock --airgap
|
70
74
|
```
|
71
75
|
|
72
|
-
|
76
|
+
Air gap mode assumes that an offline cache has been placed in `$HOME/.local/share/`.
|
73
77
|
|
74
78
|
To fetch the latest offline cache:
|
75
79
|
|
76
80
|
```bash
|
77
|
-
モ spandx
|
81
|
+
モ spandx pull
|
78
82
|
```
|
79
83
|
|
80
84
|
### Ruby API
|
@@ -106,7 +110,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
106
110
|
|
107
111
|
## Contributing
|
108
112
|
|
109
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
113
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/spandx/spandx.
|
110
114
|
|
111
115
|
## License
|
112
116
|
|
data/exe/spandx
CHANGED
data/lib/spandx.rb
CHANGED
@@ -8,10 +8,13 @@ require 'json'
|
|
8
8
|
require 'logger'
|
9
9
|
require 'net/hippie'
|
10
10
|
require 'nokogiri'
|
11
|
+
require 'parslet'
|
11
12
|
require 'pathname'
|
12
13
|
require 'yaml'
|
13
14
|
require 'zeitwerk'
|
14
15
|
|
16
|
+
require 'spandx/spandx'
|
17
|
+
|
15
18
|
loader = Zeitwerk::Loader.for_gem
|
16
19
|
loader.setup # ready!
|
17
20
|
|
@@ -20,7 +23,7 @@ module Spandx
|
|
20
23
|
Rubygems = Ruby
|
21
24
|
|
22
25
|
class << self
|
23
|
-
attr_writer :airgap, :logger
|
26
|
+
attr_writer :airgap, :logger, :http, :git
|
24
27
|
|
25
28
|
def root
|
26
29
|
Pathname.new(File.dirname(__FILE__)).join('../..')
|
@@ -40,8 +43,8 @@ module Spandx
|
|
40
43
|
|
41
44
|
def git
|
42
45
|
@git ||= {
|
43
|
-
cache: ::Spandx::Core::Git.new(url: 'https://github.com/
|
44
|
-
rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/
|
46
|
+
cache: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/cache.git'),
|
47
|
+
rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/rubygems-cache.git'),
|
45
48
|
spdx: ::Spandx::Core::Git.new(url: 'https://github.com/spdx/license-list-data.git'),
|
46
49
|
}
|
47
50
|
end
|
data/lib/spandx/cli.rb
CHANGED
@@ -17,6 +17,7 @@ module Spandx
|
|
17
17
|
|
18
18
|
def execute(output: $stdout)
|
19
19
|
catalogue = Spandx::Spdx::Catalogue.from_git
|
20
|
+
build_buckets
|
20
21
|
indexes.each do |index|
|
21
22
|
output.puts index.name
|
22
23
|
index.update!(catalogue: catalogue, output: output)
|
@@ -30,9 +31,19 @@ module Spandx
|
|
30
31
|
index = INDEXES[@options[:index]&.to_sym]
|
31
32
|
|
32
33
|
if index.nil?
|
33
|
-
INDEXES.values.uniq.map { |x| x.new(directory:
|
34
|
+
INDEXES.values.uniq.map { |x| x.new(directory: directory) }
|
34
35
|
else
|
35
|
-
[index.new(directory:
|
36
|
+
[index.new(directory: directory)]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def directory
|
41
|
+
@options.fetch(:directory, File.join(Dir.pwd, '.index'))
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_buckets
|
45
|
+
(0x00..0xFF).map { |x| x.to_s(16).rjust(2, '0').downcase }.each do |hex|
|
46
|
+
FileUtils.mkdir_p(File.join(directory, hex))
|
36
47
|
end
|
37
48
|
end
|
38
49
|
end
|
@@ -4,6 +4,10 @@ module Spandx
|
|
4
4
|
module Cli
|
5
5
|
module Commands
|
6
6
|
class Scan
|
7
|
+
NULL_BAR = Class.new do
|
8
|
+
def advance(*args); end
|
9
|
+
end.new
|
10
|
+
|
7
11
|
attr_reader :scan_path
|
8
12
|
|
9
13
|
def initialize(scan_path, options)
|
@@ -13,43 +17,37 @@ module Spandx
|
|
13
17
|
end
|
14
18
|
|
15
19
|
def execute(output: $stdout)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
Spandx::Core::ThreadPool.open do |pool|
|
21
|
+
report = ::Spandx::Core::Report.new
|
22
|
+
each_file do |file|
|
23
|
+
each_dependency_from(file, pool) do |dependency|
|
24
|
+
report.add(dependency)
|
25
|
+
end
|
20
26
|
end
|
27
|
+
output.puts(format(report.to(@options[:format])))
|
21
28
|
end
|
22
|
-
output.puts(format(report.to(@options[:format])))
|
23
29
|
end
|
24
30
|
|
25
31
|
private
|
26
32
|
|
27
|
-
def
|
28
|
-
|
33
|
+
def each_file
|
34
|
+
Spandx::Core::PathTraversal
|
35
|
+
.new(scan_path, recursive: @options['recursive'])
|
36
|
+
.each { |file| yield file }
|
29
37
|
end
|
30
38
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
def each_dependency_from(file, pool)
|
40
|
+
dependencies = ::Spandx::Core::Parser.for(file).parse(file)
|
41
|
+
with_progress(title_for(file), dependencies.size) do |bar|
|
42
|
+
::Spandx::Core::Concurrent
|
43
|
+
.map(dependencies, pool: pool) { |dependency| enhance(dependency) }
|
44
|
+
.each do |dependency|
|
45
|
+
bar.advance(1)
|
46
|
+
yield dependency
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
|
-
def each_dependency_from(file)
|
44
|
-
::Spandx::Core::Parser
|
45
|
-
.for(file)
|
46
|
-
.parse(file)
|
47
|
-
.map { |dependency| enhance(dependency) }
|
48
|
-
.each { |dependency| yield dependency }
|
49
|
-
rescue StandardError => error
|
50
|
-
Spandx.logger.error(error)
|
51
|
-
end
|
52
|
-
|
53
51
|
def format(output)
|
54
52
|
Array(output).map(&:to_s)
|
55
53
|
end
|
@@ -59,6 +57,14 @@ module Spandx
|
|
59
57
|
.all
|
60
58
|
.inject(dependency) { |memo, plugin| plugin.enhance(memo) }
|
61
59
|
end
|
60
|
+
|
61
|
+
def title_for(file)
|
62
|
+
"#{file} [:bar, :elapsed] :percent"
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_progress(title, total)
|
66
|
+
yield @options[:show_progress] ? TTY::ProgressBar.new(title, total: total) : NULL_BAR
|
67
|
+
end
|
62
68
|
end
|
63
69
|
end
|
64
70
|
end
|
data/lib/spandx/cli/main.rb
CHANGED
@@ -11,6 +11,7 @@ module Spandx
|
|
11
11
|
method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'table'
|
12
12
|
method_option :pull, aliases: '-p', type: :boolean, desc: 'Pull the latest cache before the scan', default: false
|
13
13
|
method_option :require, aliases: '-r', type: :string, desc: 'Causes spandx to load the library using require.', default: nil
|
14
|
+
method_option :show_progress, aliases: '-sp', type: :boolean, desc: 'Shows a progress bar', default: true
|
14
15
|
def scan(lockfile)
|
15
16
|
if options[:help]
|
16
17
|
invoke :help, ['scan']
|
data/lib/spandx/core/cache.rb
CHANGED
@@ -3,83 +3,70 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Cache
|
6
|
-
|
6
|
+
include Enumerable
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
8
|
+
attr_reader :package_manager, :root
|
9
|
+
|
10
|
+
def initialize(package_manager, root:)
|
11
|
+
@package_manager = package_manager.to_s
|
12
|
+
@root = root
|
13
13
|
end
|
14
14
|
|
15
15
|
def licenses_for(name, version)
|
16
|
-
|
17
|
-
|
16
|
+
return [] if name.nil? || name.empty?
|
17
|
+
|
18
|
+
found = datafile_for(name).search(name: name, version: version)
|
19
|
+
Spandx.logger.debug { "Cache miss: #{name}-#{version}" } unless found
|
18
20
|
found ? found[2].split('-|-') : []
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
def each
|
24
|
+
datafiles.each do |_hex, datafile|
|
25
|
+
datafile.each do |item|
|
26
|
+
yield item
|
27
|
+
end
|
28
|
+
end
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
31
|
+
def insert(name, version, licenses)
|
32
|
+
return if name.nil? || name.empty?
|
33
|
+
|
34
|
+
datafile_for(name).insert(name, version, licenses)
|
29
35
|
end
|
30
36
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
search_for("#{name}-#{version}", io, @lines.fetch(datafile) { |key| @lines[key] = lines_in(io) })
|
35
|
-
end
|
36
|
-
rescue Errno::ENOENT => error
|
37
|
-
Spandx.logger.error(error)
|
38
|
-
nil
|
37
|
+
def insert!(*args)
|
38
|
+
insert(*args)
|
39
|
+
rebuild_index
|
39
40
|
end
|
40
41
|
|
41
42
|
def datafile_for(name)
|
42
|
-
|
43
|
+
datafiles.fetch(key_for(name))
|
43
44
|
end
|
44
45
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
until io.eof?
|
49
|
-
lines << io.pos
|
50
|
-
io.gets
|
46
|
+
def rebuild_index
|
47
|
+
datafiles.each do |_hex, datafile|
|
48
|
+
datafile.index.update!
|
51
49
|
end
|
52
|
-
lines
|
53
50
|
end
|
54
51
|
|
55
|
-
|
56
|
-
return if lines.empty?
|
57
|
-
return @cache[term] if @cache.key?(term)
|
58
|
-
|
59
|
-
mid = lines.size == 1 ? 0 : lines.size / 2
|
60
|
-
io.seek(lines[mid])
|
61
|
-
comparison = matches?(term, parse_row(io)) do |row|
|
62
|
-
return row
|
63
|
-
end
|
52
|
+
private
|
64
53
|
|
65
|
-
|
54
|
+
def digest_for(name)
|
55
|
+
Digest::SHA1.hexdigest(name)
|
66
56
|
end
|
67
57
|
|
68
|
-
def
|
69
|
-
(
|
70
|
-
yield row if comparison.zero?
|
71
|
-
end
|
58
|
+
def key_for(name)
|
59
|
+
digest_for(name)[0...2]
|
72
60
|
end
|
73
61
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
62
|
+
def datafiles
|
63
|
+
@datafiles ||= candidate_keys.each_with_object({}) do |key, memo|
|
64
|
+
memo[key] = DataFile.new(File.join(root, key, package_manager))
|
65
|
+
end
|
77
66
|
end
|
78
67
|
|
79
|
-
def
|
80
|
-
|
81
|
-
@cache["#{row[0]}-#{row[1]}"] = row
|
82
|
-
row
|
68
|
+
def candidate_keys
|
69
|
+
(0x00..0xFF).map { |x| x.to_s(16).upcase.rjust(2, '0').downcase }
|
83
70
|
end
|
84
71
|
end
|
85
72
|
end
|