license_finder 6.5.0 → 6.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/CONTRIBUTING.md +5 -4
  4. data/Dockerfile +26 -9
  5. data/README.md +53 -15
  6. data/Rakefile +1 -10
  7. data/VERSION +1 -1
  8. data/ci/pipelines/pull-request.yml.erb +2 -0
  9. data/ci/pipelines/release.yml.erb +16 -4
  10. data/ci/tasks/rubocop.yml +2 -0
  11. data/ci/tasks/update-changelog.yml +2 -0
  12. data/examples/Gemfile +4 -0
  13. data/examples/custom_erb_template.rb +24 -0
  14. data/examples/extract_license_data.rb +63 -0
  15. data/examples/sample_template.erb +7 -0
  16. data/lib/license_finder/cli/base.rb +8 -1
  17. data/lib/license_finder/cli/inherited_decisions.rb +18 -0
  18. data/lib/license_finder/cli/main.rb +5 -1
  19. data/lib/license_finder/configuration.rb +13 -1
  20. data/lib/license_finder/core.rb +5 -2
  21. data/lib/license_finder/decisions.rb +58 -10
  22. data/lib/license_finder/license.rb +45 -1
  23. data/lib/license_finder/license/definitions.rb +49 -2
  24. data/lib/license_finder/license/header_matcher.rb +7 -2
  25. data/lib/license_finder/license/templates/0BSD.txt +10 -0
  26. data/lib/license_finder/license/templates/MPL1_1.txt +469 -0
  27. data/lib/license_finder/license/text.rb +2 -2
  28. data/lib/license_finder/logger.rb +2 -0
  29. data/lib/license_finder/package.rb +2 -0
  30. data/lib/license_finder/package_manager.rb +15 -5
  31. data/lib/license_finder/package_managers/composer.rb +8 -4
  32. data/lib/license_finder/package_managers/conda.rb +131 -0
  33. data/lib/license_finder/package_managers/dep.rb +6 -1
  34. data/lib/license_finder/package_managers/dotnet.rb +2 -1
  35. data/lib/license_finder/package_managers/erlangmk.rb +50 -0
  36. data/lib/license_finder/package_managers/go_15vendorexperiment.rb +6 -1
  37. data/lib/license_finder/package_managers/go_dep.rb +15 -8
  38. data/lib/license_finder/package_managers/go_modules.rb +43 -15
  39. data/lib/license_finder/package_managers/mix.rb +1 -1
  40. data/lib/license_finder/package_managers/npm.rb +1 -1
  41. data/lib/license_finder/package_managers/nuget.rb +36 -1
  42. data/lib/license_finder/package_managers/pipenv.rb +1 -1
  43. data/lib/license_finder/package_managers/rebar.rb +29 -8
  44. data/lib/license_finder/package_managers/trash.rb +6 -1
  45. data/lib/license_finder/package_managers/yarn.rb +1 -1
  46. data/lib/license_finder/packages/conda_package.rb +74 -0
  47. data/lib/license_finder/packages/erlangmk_package.rb +114 -0
  48. data/lib/license_finder/packages/pip_package.rb +9 -2
  49. data/lib/license_finder/report.rb +1 -0
  50. data/lib/license_finder/reports/junit_report.rb +19 -0
  51. data/lib/license_finder/reports/templates/junit_report.erb +41 -0
  52. data/lib/license_finder/scanner.rb +25 -2
  53. data/license_finder.gemspec +3 -2
  54. metadata +41 -9
@@ -96,7 +96,7 @@ module LicenseFinder
96
96
  raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
97
97
 
98
98
  packages_lines(stdout)
99
- .reject { |package_lines| package_lines.length == 1 } # in_umbrella: true dependencies
99
+ .reject { |package_lines| package_lines.length == 1 || package_lines.empty? } # in_umbrella: true dependencies
100
100
  .map { |package_lines| [package_lines[0].split(' ')[1], resolve_version(package_lines[1])] }
101
101
  end
102
102
 
@@ -14,7 +14,7 @@ module LicenseFinder
14
14
  end
15
15
 
16
16
  def prepare_command
17
- 'npm install --no-save'
17
+ 'npm install --no-save --ignore-scripts'
18
18
  end
19
19
 
20
20
  def possible_package_paths
@@ -89,8 +89,43 @@ module LicenseFinder
89
89
  "mono #{nuget_binary}"
90
90
  end
91
91
 
92
+ def prepare
93
+ Dir.chdir(project_path) do
94
+ cmd = prepare_command
95
+ stdout, stderr, status = Cmd.run(cmd)
96
+ return if status.success?
97
+
98
+ log_errors stderr
99
+
100
+ if stderr.include?('-PackagesDirectory')
101
+ logger.info cmd, 'trying fallback prepare command', color: :magenta
102
+
103
+ cmd = "#{cmd} -PackagesDirectory /#{Dir.home}/.nuget/packages"
104
+ stdout, stderr, status = Cmd.run(cmd)
105
+ return if status.success?
106
+
107
+ log_errors_with_cmd(cmd, stderr)
108
+ end
109
+
110
+ error_message = "Prepare command '#{cmd}' failed\n#{stderr}"
111
+ error_message += "\n#{stdout}\n" if !stdout.nil? && !stdout.empty?
112
+ raise error_message unless @prepare_no_fail
113
+ end
114
+ end
115
+
92
116
  def prepare_command
93
- "#{package_management_command} restore"
117
+ cmd = package_management_command
118
+ sln_files = Dir['*.sln']
119
+ cmds = []
120
+ if sln_files.count > 1
121
+ sln_files.each do |sln|
122
+ cmds << "#{cmd} restore #{sln}"
123
+ end
124
+ else
125
+ cmds << "#{cmd} restore"
126
+ end
127
+
128
+ cmds.join(' && ')
94
129
  end
95
130
 
96
131
  def installed?(logger = Core.default_logger)
@@ -15,7 +15,7 @@ module LicenseFinder
15
15
  begin
16
16
  packages = {}
17
17
  each_dependency(groups: allowed_groups) do |name, data, group|
18
- version = canonicalize(data['version'])
18
+ version = canonicalize(data['version'] || 'unknown')
19
19
  package = packages.fetch(key_for(name, version)) do |key|
20
20
  packages[key] = build_package_for(name, version)
21
21
  end
@@ -5,23 +5,25 @@ module LicenseFinder
5
5
  def initialize(options = {})
6
6
  super
7
7
  @command = options[:rebar_command] || package_management_command
8
- @deps_path = Pathname(options[:rebar_deps_dir] || 'deps')
8
+ @deps_path = Pathname(options[:rebar_deps_dir] || File.join(project_path, '_build/default/lib'))
9
9
  end
10
10
 
11
11
  def current_packages
12
- rebar_ouput.map do |name, version_type, version_value, homepage|
12
+ rebar_deps.map do |name, version|
13
+ licenses, homepage = dep_info(name)
13
14
  RebarPackage.new(
14
15
  name,
15
- "#{version_type}: #{version_value}",
16
+ version,
16
17
  install_path: @deps_path.join(name),
17
18
  homepage: homepage,
19
+ spec_licenses: licenses.nil? ? [] : [licenses],
18
20
  logger: logger
19
21
  )
20
22
  end
21
23
  end
22
24
 
23
25
  def package_management_command
24
- 'rebar'
26
+ 'rebar3'
25
27
  end
26
28
 
27
29
  def possible_package_paths
@@ -30,15 +32,34 @@ module LicenseFinder
30
32
 
31
33
  private
32
34
 
33
- def rebar_ouput
34
- command = "#{@command} list-deps"
35
+ def rebar_deps
36
+ command = "#{@command} tree"
35
37
  stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
36
38
  raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
37
39
 
38
40
  stdout
39
41
  .each_line
40
- .reject { |line| line.start_with?('=') }
41
- .map { |line| line.split(' ') }
42
+ .reject { |line| line.start_with?('=') || line.include?('project app') }
43
+ .map do |line|
44
+ matches = line.match(/(?<name>\w+)─(?<version>[\S.]+)\s*/)
45
+ [matches[:name], matches[:version]] if matches
46
+ end.compact
47
+ end
48
+
49
+ def dep_info(name)
50
+ command = "#{@command} pkgs #{name}"
51
+ stdout, _, status = Cmd.run(command)
52
+ return [nil, nil] unless status.success?
53
+
54
+ licenses = nil
55
+ homepage = nil
56
+
57
+ stdout.scan(/Licenses: (?<licenses>.+)|(?<homepage>(https|http).*)/) do |pkg_licenses, pkg_homepage|
58
+ licenses ||= pkg_licenses
59
+ homepage ||= pkg_homepage
60
+ end
61
+
62
+ [licenses, homepage]
42
63
  end
43
64
  end
44
65
  end
@@ -30,9 +30,14 @@ module LicenseFinder
30
30
  GoPackage.from_dependency({
31
31
  'ImportPath' => import_path,
32
32
  'InstallPath' => license_path,
33
- 'Rev' => package_hash.fetch('version')
33
+ 'Rev' => package_hash.fetch('version'),
34
+ 'Homepage' => repo_name(import_path)
34
35
  }, nil, true)
35
36
  end
36
37
  end
38
+
39
+ def repo_name(name)
40
+ name.split('/')[0..2].join('/')
41
+ end
37
42
  end
38
43
  end
@@ -56,7 +56,7 @@ module LicenseFinder
56
56
  end
57
57
 
58
58
  def prepare_command
59
- 'yarn install --ignore-engines'
59
+ 'yarn install --ignore-engines --ignore-scripts'
60
60
  end
61
61
 
62
62
  private
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LicenseFinder
4
+ class CondaPackage < Package
5
+ attr_accessor :identifier, :json
6
+
7
+ def initialize(conda_json)
8
+ @json = conda_json
9
+ @identifier = Identifier.from_hash(conda_json)
10
+ super(@identifier.name,
11
+ @identifier.version,
12
+ spec_licenses: Package.license_names_from_standard_spec(conda_json),
13
+ children: children)
14
+ end
15
+
16
+ def ==(other)
17
+ other.is_a?(CondaPackage) && @identifier == other.identifier
18
+ end
19
+
20
+ def to_s
21
+ @identifier.to_s
22
+ end
23
+
24
+ def package_manager
25
+ 'Conda'
26
+ end
27
+
28
+ def package_url
29
+ @json['url']
30
+ end
31
+
32
+ def children
33
+ @json.fetch('depends', []).map { |constraint| constraint.split.first }
34
+ end
35
+
36
+ class Identifier
37
+ attr_accessor :name, :version
38
+
39
+ def initialize(name, version)
40
+ @name = name
41
+ @version = version
42
+ end
43
+
44
+ def self.from_hash(hash)
45
+ name = hash['name']
46
+ version = hash['version']
47
+ return nil if name.nil? || version.nil?
48
+
49
+ Identifier.new(name, version)
50
+ end
51
+
52
+ def ==(other)
53
+ other.is_a?(Identifier) && @name == other.name && @version == other.version
54
+ end
55
+
56
+ def eql?(other)
57
+ self == other
58
+ end
59
+
60
+ def hash
61
+ [@name, @version].hash
62
+ end
63
+
64
+ def <=>(other)
65
+ sort_name = @name <=> other.name
66
+ sort_name.zero? ? @version <=> other.version : sort_name
67
+ end
68
+
69
+ def to_s
70
+ "#{@name} - #{@version}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+
5
+ class InvalidErlangmkPackageError < ArgumentError
6
+ end
7
+
8
+ module LicenseFinder
9
+ class ErlangmkPackage < Package
10
+ attr_reader :dep_parent,
11
+ :dep_name,
12
+ :dep_fetch_method,
13
+ :dep_repo_unformatted,
14
+ :dep_version_unformatted,
15
+ :dep_absolute_path
16
+
17
+ def initialize(dep_string_from_query_deps)
18
+ @dep_parent,
19
+ @dep_name,
20
+ @dep_fetch_method,
21
+ @dep_repo_unformatted,
22
+ @dep_version_unformatted,
23
+ @dep_absolute_path = dep_string_from_query_deps.split
24
+
25
+ raise_invalid(dep_string_from_query_deps) unless all_parts_valid?
26
+
27
+ super(
28
+ dep_name,
29
+ dep_version,
30
+ homepage: dep_repo,
31
+ install_path: dep_absolute_path
32
+ )
33
+ end
34
+
35
+ def package_manager
36
+ 'Erlangmk'
37
+ end
38
+
39
+ def dep_version
40
+ @dep_version ||= begin
41
+ dep_version_unformatted.sub(version_prefix_re, '')
42
+ end
43
+ end
44
+
45
+ def dep_repo
46
+ @dep_repo ||= dep_repo_unformatted
47
+ .chomp('.git')
48
+ .sub('git@github.com:', 'https://github.com/')
49
+ end
50
+
51
+ def raise_invalid(dep_string)
52
+ invalid_dep_message = "'#{dep_string}' does not look like a valid Erlank.mk dependency"
53
+ valid_dep_example = "A valid dependency example: 'lager: goldrush git https://github.com/DeadZen/goldrush.git 0.1.9 /absolute/path/to/dep'"
54
+ raise(InvalidErlangmkPackageError, "#{invalid_dep_message}. #{valid_dep_example}")
55
+ end
56
+
57
+ def all_parts_valid?
58
+ dep_part_valid?(dep_parent) &&
59
+ dep_part_valid?(dep_name) &&
60
+ set?(dep_fetch_method) &&
61
+ dep_repo_valid? &&
62
+ dep_version_valid? &&
63
+ set?(dep_absolute_path)
64
+ end
65
+
66
+ private
67
+
68
+ def dep_part_valid?(dep_part)
69
+ set?(dep_part) &&
70
+ word?(dep_part)
71
+ end
72
+
73
+ def set?(dep_part)
74
+ !dep_part.nil? &&
75
+ !dep_part.empty?
76
+ end
77
+
78
+ def word?(dep_part)
79
+ dep = dep_part.chomp(':')
80
+ dep =~ word_re
81
+ end
82
+
83
+ def dep_repo_valid?
84
+ set?(dep_repo_unformatted) &&
85
+ URI.parse(dep_repo)
86
+ end
87
+
88
+ def dep_version_valid?
89
+ return false unless set?(dep_version_unformatted)
90
+
91
+ if dep_version =~ version_re
92
+ Gem::Version.correct?(dep_version)
93
+ else
94
+ dep_version =~ word_dot_re
95
+ end
96
+ end
97
+
98
+ def version_re
99
+ @version_re ||= Regexp.new('\d+\.\d+\.\d+')
100
+ end
101
+
102
+ def version_prefix_re
103
+ @version_prefix_re ||= Regexp.new('^v')
104
+ end
105
+
106
+ def word_re
107
+ @word_re ||= Regexp.new('^\w+$')
108
+ end
109
+
110
+ def word_dot_re
111
+ @word_dot_re ||= Regexp.new('^[.\w]+$')
112
+ end
113
+ end
114
+ end
@@ -8,9 +8,16 @@ module LicenseFinder
8
8
  INVALID_LICENSES = ['', 'UNKNOWN'].to_set
9
9
 
10
10
  def self.license_names_from_spec(spec)
11
- license = spec['license'].to_s.strip
11
+ license_names = spec['license'].to_s.strip.split(' or ')
12
+ has_unrecognized_license = false
12
13
 
13
- return [license] unless INVALID_LICENSES.include?(license)
14
+ license_names.each do |license_name|
15
+ license = License.find_by_name(license_name.strip)
16
+
17
+ has_unrecognized_license ||= license.unrecognized_matcher?
18
+ end
19
+
20
+ return license_names if !license_names.empty? && !has_unrecognized_license
14
21
 
15
22
  spec
16
23
  .fetch('classifiers', [])
@@ -30,3 +30,4 @@ require 'license_finder/reports/html_report'
30
30
  require 'license_finder/reports/markdown_report'
31
31
  require 'license_finder/reports/xml_report'
32
32
  require 'license_finder/reports/json_report'
33
+ require 'license_finder/reports/junit_report'
@@ -0,0 +1,19 @@
1
+ require 'license_finder/reports/erb_report'
2
+
3
+ module LicenseFinder
4
+ class JunitReport < ErbReport
5
+ ROOT_PATH = Pathname.new(__FILE__).dirname
6
+ TEMPLATE_PATH = ROOT_PATH.join('templates')
7
+
8
+ def to_s(filename = TEMPLATE_PATH.join("#{template_name}.erb"))
9
+ template = ERB.new(filename.read, nil, '-')
10
+ template.result(binding)
11
+ end
12
+
13
+ private
14
+
15
+ def template_name
16
+ 'junit_report'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <testsuites failures="<%= unapproved_dependencies.size %>" name="<%= project_name %>" tests="<%= dependencies.size %>">
3
+ <% sorted_dependencies.each_with_index do |dependency, i| -%>
4
+ <testsuite failures="<%= dependency.approved? ? "0" : "1" -%>" id="<%= i %>" name="<%= dependency.name %>" package="Gemfile.lock" skipped="0" tests="1" timestamp="<%= Time.now.strftime("%Y-%m-%dT%H:%M:%S:%6N") %>">
5
+ <testcase classname="<%= license_names(dependency) %>" name="<%= dependency.name %>"<%= dependency.approved? ? " /" : "" %>>
6
+ <%- unless dependency.approved? -%>
7
+ <failure message="Unapproved license in '<%= dependency.name %>' <%= dependency.version %>">
8
+ Name: <%= dependency.name %>
9
+ Version: <%= dependency.version %>
10
+ Licence:
11
+ <%- if dependency.licenses.any? -%>
12
+ <%- dependency.licenses.each do |license| -%>- <%=license.name %>: <%=license.url %><% end %>
13
+ <%- end -%>
14
+ URL: <%= dependency.package_url %>
15
+ Homepage: <%= dependency.homepage %>
16
+ Summary: <%= REXML::Text.new(dependency.summary, false, nil, false) %>
17
+ Description: <%= REXML::Text.new(dependency.description, false, nil, false) %>
18
+ <% if dependency.parents.any? %>
19
+ Dependencies:
20
+ <% dependency.parents.to_a.each do |dep| -%>
21
+ - <%= dep %>
22
+ <% end -%>
23
+ <% end -%>
24
+ <%- if dependency.children.any? -%>
25
+ Requirements:
26
+ <%- dependency.children.each do |req| -%>
27
+ - <%= req %>
28
+ <% end -%>
29
+ <% end -%>
30
+ </failure>
31
+ <system-out>
32
+ stdout
33
+ </system-out>
34
+ <system-err>
35
+ stderr
36
+ </system-err>
37
+ </testcase>
38
+ <%- end -%>
39
+ </testsuite>
40
+ <% end -%>
41
+ </testsuites>