license_scout 1.3.7 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +195 -0
  3. data/bin/license_scout +3 -59
  4. data/bin/mix_lock_json +0 -0
  5. data/bin/rebar_lock_json +0 -0
  6. data/lib/license_scout/cli.rb +99 -0
  7. data/lib/license_scout/collector.rb +25 -77
  8. data/lib/license_scout/config.rb +94 -0
  9. data/lib/license_scout/data/dependeny_manifest_v2_schema.json +62 -0
  10. data/lib/license_scout/data/exceptions.json +306 -0
  11. data/lib/license_scout/data/licenses.json +4653 -0
  12. data/lib/license_scout/dependency.rb +79 -7
  13. data/lib/license_scout/dependency_manager/base.rb +74 -42
  14. data/lib/license_scout/dependency_manager/berkshelf.rb +25 -50
  15. data/lib/license_scout/dependency_manager/bundler/_bundler_script.rb +1 -1
  16. data/lib/license_scout/dependency_manager/bundler.rb +47 -69
  17. data/lib/license_scout/dependency_manager/cpanm.rb +62 -112
  18. data/lib/license_scout/dependency_manager/dep.rb +29 -36
  19. data/lib/license_scout/dependency_manager/glide.rb +25 -36
  20. data/lib/license_scout/dependency_manager/godep.rb +27 -26
  21. data/lib/license_scout/dependency_manager/habitat.rb +126 -0
  22. data/lib/license_scout/dependency_manager/mix.rb +105 -0
  23. data/lib/license_scout/dependency_manager/npm.rb +30 -86
  24. data/lib/license_scout/dependency_manager/rebar.rb +26 -45
  25. data/lib/license_scout/dependency_manager.rb +19 -5
  26. data/lib/license_scout/exceptions.rb +2 -43
  27. data/lib/license_scout/license.rb +126 -0
  28. data/lib/license_scout/{license_file_analyzer.rb → log.rb} +4 -6
  29. data/lib/license_scout/reporter.rb +149 -55
  30. data/lib/license_scout/spdx.rb +123 -0
  31. data/lib/license_scout/version.rb +1 -1
  32. data/lib/license_scout.rb +2 -0
  33. data/native_parsers/mix_lock_json/README.md +21 -0
  34. data/native_parsers/mix_lock_json/lib/mix_lock_json.ex +20 -0
  35. data/native_parsers/mix_lock_json/mix.exs +31 -0
  36. data/native_parsers/mix_lock_json/mix.lock +3 -0
  37. data/{erl_src → native_parsers}/rebar_lock_json/rebar.lock +2 -2
  38. metadata +144 -67
  39. data/lib/license_scout/canonical_licenses/BSD-2-Clause.txt +0 -19
  40. data/lib/license_scout/canonical_licenses/BSD-3-Clause.txt +0 -27
  41. data/lib/license_scout/canonical_licenses/BSD-4-Clause.txt +0 -31
  42. data/lib/license_scout/canonical_licenses/Chef-MLSA.txt +0 -5
  43. data/lib/license_scout/canonical_licenses/ISC.txt +0 -14
  44. data/lib/license_scout/canonical_licenses/MIT.txt +0 -20
  45. data/lib/license_scout/dependency_manager/bundler/LICENSE.md +0 -23
  46. data/lib/license_scout/dependency_manager/json/README.md +0 -392
  47. data/lib/license_scout/dependency_manager/manual.rb +0 -67
  48. data/lib/license_scout/license_file_analyzer/any_matcher.rb +0 -37
  49. data/lib/license_scout/license_file_analyzer/definitions.rb +0 -219
  50. data/lib/license_scout/license_file_analyzer/header_matcher.rb +0 -34
  51. data/lib/license_scout/license_file_analyzer/matcher.rb +0 -46
  52. data/lib/license_scout/license_file_analyzer/template.rb +0 -45
  53. data/lib/license_scout/license_file_analyzer/templates/Apache2-short.txt +0 -11
  54. data/lib/license_scout/license_file_analyzer/templates/Apache2.txt +0 -170
  55. data/lib/license_scout/license_file_analyzer/templates/BSD-2-Clause-bullets.txt +0 -18
  56. data/lib/license_scout/license_file_analyzer/templates/BSD-2-Clause.txt +0 -19
  57. data/lib/license_scout/license_file_analyzer/templates/BSD-3-Clause-alt-format.txt +0 -24
  58. data/lib/license_scout/license_file_analyzer/templates/BSD-3-Clause.txt +0 -21
  59. data/lib/license_scout/license_file_analyzer/templates/BSD.txt +0 -24
  60. data/lib/license_scout/license_file_analyzer/templates/Chef-MLSA.txt +0 -5
  61. data/lib/license_scout/license_file_analyzer/templates/EPLICENSE.txt +0 -286
  62. data/lib/license_scout/license_file_analyzer/templates/GPL-2.0.txt +0 -339
  63. data/lib/license_scout/license_file_analyzer/templates/GPL-3.0.txt +0 -674
  64. data/lib/license_scout/license_file_analyzer/templates/ISC.txt +0 -2
  65. data/lib/license_scout/license_file_analyzer/templates/LGPL-3.0.txt +0 -165
  66. data/lib/license_scout/license_file_analyzer/templates/MIT.txt +0 -9
  67. data/lib/license_scout/license_file_analyzer/templates/MPL2.txt +0 -373
  68. data/lib/license_scout/license_file_analyzer/templates/Python-2.0.txt +0 -47
  69. data/lib/license_scout/license_file_analyzer/templates/Ruby.txt +0 -52
  70. data/lib/license_scout/license_file_analyzer/text.rb +0 -46
  71. data/lib/license_scout/net_fetcher.rb +0 -106
  72. data/lib/license_scout/options.rb +0 -47
  73. data/lib/license_scout/overrides.rb +0 -1120
  74. /data/{erl_src → native_parsers}/rebar_lock_json/README.md +0 -0
  75. /data/{erl_src → native_parsers}/rebar_lock_json/rebar.config +0 -0
  76. /data/{erl_src → native_parsers}/rebar_lock_json/src/rebar_lock_json.app.src +0 -0
  77. /data/{erl_src → native_parsers}/rebar_lock_json/src/rebar_lock_json.erl +0 -0
@@ -15,97 +15,191 @@
15
15
  # limitations under the License.
16
16
  #
17
17
 
18
- require "ffi_yajl" unless defined?(FFI_Yajl)
18
+ require "ffi_yajl"
19
+ require "terminal-table"
19
20
 
20
21
  require "license_scout/exceptions"
21
22
 
22
23
  module LicenseScout
23
24
  class Reporter
24
25
 
25
- attr_reader :output_directory
26
-
27
- def initialize(output_directory)
28
- @output_directory = output_directory
29
- end
26
+ class Result
27
+ class << self
28
+ def success(dependency)
29
+ new(dependency, nil, true)
30
+ end
30
31
 
31
- def report
32
- report = []
32
+ def failure(dependency, reason)
33
+ new(dependency, reason, false)
34
+ end
35
+ end
33
36
 
34
- license_manifest_path = find_license_manifest!
37
+ attr_reader :dependency
38
+ attr_reader :reason
35
39
 
36
- license_report = FFI_Yajl::Parser.parse(File.read(license_manifest_path))
40
+ def initialize(dependency, reason, did_succeed)
41
+ @dependency = dependency
42
+ @reason = reason
43
+ @did_succeed = did_succeed
44
+ end
37
45
 
38
- license_report["dependency_managers"].each do |dependency_manager, dependencies|
46
+ def <=>(other)
47
+ dependency.path <=> other.dependency.path
48
+ end
39
49
 
40
- ok_deps, problem_deps = 0, 0
50
+ def succeeded?
51
+ @did_succeed
52
+ end
41
53
 
42
- dependencies.sort_by { |a| a["name"] }.each do |dependency|
43
- dep_ok, problems = license_info_ok?(dependency_manager, dependency)
54
+ def dependency_string
55
+ dependency.uid
56
+ end
44
57
 
45
- if dep_ok
46
- ok_deps += 1
47
- else
48
- problem_deps += 1
49
- report.concat(problems)
50
- end
51
- end
58
+ def license_string
59
+ dependency.license.records.map(&:id).compact.uniq.join(", ")
60
+ end
52
61
 
53
- if problem_deps > 0
54
- report << ">> Found #{dependencies.size} dependencies for #{dependency_manager}. #{ok_deps} OK, #{problem_deps} with problems"
62
+ def reason_string
63
+ case reason
64
+ when :not_allowed
65
+ "Not Allowed"
66
+ when :flagged
67
+ "Flagged"
68
+ when :undetermined
69
+ "Undetermined"
70
+ when :missing
71
+ "Missing"
72
+ else
73
+ "OK"
55
74
  end
56
75
  end
76
+ end
57
77
 
58
- report
78
+ attr_reader :all_dependencies
79
+ attr_reader :results
80
+ attr_reader :dependency_license_manifest
81
+
82
+ def initialize(all_dependencies)
83
+ @all_dependencies = all_dependencies.sort
84
+ @results = {}
85
+ @did_fail = false
86
+ @needs_fallback = false
87
+ @needs_exception = false
59
88
  end
60
89
 
61
- def license_info_ok?(dependency_manager, dependency)
62
- problems = []
63
- if dependency["name"].nil? || dependency["name"].empty?
64
- problems << "There is a dependency with a missing name in '#{dependency_manager}'."
65
- end
90
+ def report
91
+ generate_dependency_license_manifest
92
+ save_manifest_file
93
+ detect_problems
94
+ evaluate_results
95
+ end
66
96
 
67
- if dependency["version"].nil? || dependency["version"].empty?
68
- problems << "Dependency '#{dependency["name"]}' under '#{dependency_manager}' is missing version information."
69
- end
97
+ private
70
98
 
71
- if dependency["license"].nil? || dependency["license"].empty?
72
- problems << "Dependency '#{dependency["name"]}' version '#{dependency["version"]}' under '#{dependency_manager}' is missing license information."
99
+ def save_manifest_file
100
+ LicenseScout::Log.info("[reporter] Writing dependency license manifest written to #{license_manifest_path}")
101
+ File.open(license_manifest_path, "w+") do |file|
102
+ file.print(FFI_Yajl::Encoder.encode(dependency_license_manifest, pretty: true))
73
103
  end
104
+ end
74
105
 
75
- if dependency["license_files"].empty?
76
- problems << "Dependency '#{dependency["name"]}' version '#{dependency["version"]}' under '#{dependency_manager}' is missing license files information."
77
- else
78
- dependency["license_files"].each do |license_file|
79
- unless File.exist?(full_path_for(license_file))
80
- problems << "License file '#{license_file}' for the dependency '#{dependency["name"]}' version '#{dependency["version"]}' under '#{dependency_manager}' is missing."
106
+ def detect_problems
107
+ LicenseScout::Log.info("[reporter] Analyzing dependency's license information against requirements")
108
+
109
+ LicenseScout::Log.info("[reporter] Allowed licenses: #{LicenseScout::Config.allowed_licenses.join(", ")}") unless LicenseScout::Config.allowed_licenses.empty?
110
+ LicenseScout::Log.info("[reporter] Flagged licenses: #{LicenseScout::Config.flagged_licenses.join(", ")}") unless LicenseScout::Config.flagged_licenses.empty?
111
+
112
+ all_dependencies.each do |dependency|
113
+ @results[dependency.type] ||= []
114
+
115
+ if dependency.license.records.empty?
116
+ @results[dependency.type] << Result.failure(dependency, :missing)
117
+ @did_fail = true
118
+ @needs_fallback = true
119
+ elsif dependency.license.undetermined?
120
+ @results[dependency.type] << Result.failure(dependency, :undetermined)
121
+ @did_fail = true
122
+ @needs_fallback = true
123
+ elsif !LicenseScout::Config.allowed_licenses.empty? && !dependency.license.is_allowed?
124
+ unless dependency.has_exception?
125
+ @results[dependency.type] << Result.failure(dependency, :not_allowed)
126
+ @did_fail = true
127
+ @needs_exception = true
128
+ else
129
+ @results[dependency.type] << Result.success(dependency)
81
130
  end
131
+ elsif dependency.license.is_flagged?
132
+ unless dependency.has_exception?
133
+ @results[dependency.type] << Result.failure(dependency, :flagged)
134
+ @did_fail = true
135
+ @needs_exception = true
136
+ else
137
+ @results[dependency.type] << Result.success(dependency)
138
+ end
139
+ else
140
+ @results[dependency.type] << Result.success(dependency)
82
141
  end
83
142
  end
84
-
85
- [ problems.empty?, problems ]
86
143
  end
87
144
 
88
- def find_license_manifest!
89
- unless File.exist?(output_directory)
90
- raise LicenseScout::Exceptions::InvalidOutputReport.new("Output directory '#{output_directory}' does not exist.")
91
- end
145
+ def evaluate_results
146
+ table = Terminal::Table.new
147
+ table.headings = ["Type", "Dependency", "License(s)", "Results"]
148
+ table.style = { border_bottom: false } # the extra :separator will add this
92
149
 
93
- manifests = Dir.glob("#{output_directory}/*-dependency-licenses.json")
150
+ results.each do |type, results_for_type|
151
+ type_in_table = false
94
152
 
95
- if manifests.empty?
96
- raise LicenseScout::Exceptions::InvalidOutputReport.new("Can not find a dependency license manifest under '#{output_directory}'.")
97
- end
153
+ results_for_type.each do |result|
154
+ next if LicenseScout::Config.only_show_failures && result.succeeded?
155
+
156
+ modified_row = []
157
+ modified_row << (type_in_table ? "" : type)
158
+ modified_row << result.dependency_string
159
+ modified_row << result.license_string
160
+ modified_row << result.reason_string
161
+
162
+ type_in_table = true
163
+ table.add_row(modified_row)
164
+ end
98
165
 
99
- if manifests.length != 1
100
- raise LicenseScout::Exceptions::InvalidOutputReport.new("Found multiple manifests '#{manifests.join(", ")}' under '#{output_directory}'.")
166
+ table.add_separator if type_in_table
101
167
  end
102
168
 
103
- manifests.first
169
+ puts table unless LicenseScout::Config.only_show_failures && !@did_fail
170
+
171
+ puts
172
+ puts "Additional steps are required in order to pass Open Source license compliance:"
173
+ puts " * Please add fallback licenses for the 'Missing' or 'Undetermined' dependencies" if @needs_fallback
174
+ puts " https://github.com/chef/license_scout#fallback-licenses" if @needs_fallback
175
+ puts " * Please add exceptions for the 'Flagged' or 'Not Allowed' dependencies" if @needs_exception
176
+ puts " https://github.com/chef/license_scout#dependency-exceptions" if @needs_exception
177
+
178
+ exit 1 if @did_fail
104
179
  end
105
180
 
106
- def full_path_for(license_file_info)
107
- File.join(output_directory, license_file_info)
181
+ def generate_dependency_license_manifest
182
+ @dependency_license_manifest = {
183
+ license_manifest_version: 2,
184
+ generated_on: DateTime.now.to_s,
185
+ name: LicenseScout::Config.name,
186
+ dependencies: []
187
+ }
188
+
189
+ all_dependencies.each do |dep|
190
+ dependency_license_manifest[:dependencies] << {
191
+ type: dep.type,
192
+ name: dep.name,
193
+ version: dep.version,
194
+ has_exception: dep.has_exception?,
195
+ exception_reason: dep.exception_reason,
196
+ licenses: dep.license.records.map(&:to_h),
197
+ }
198
+ end
108
199
  end
109
200
 
201
+ def license_manifest_path
202
+ File.join(LicenseScout::Config.output_directory, "#{LicenseScout::Config.name}-dependency-licenses.json")
203
+ end
110
204
  end
111
205
  end
@@ -0,0 +1,123 @@
1
+ #
2
+ # Copyright:: Copyright 2018 Chef Software, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ #     http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ # This library was inspired by (and pulls some logic from) librariesio/spdx
19
+
20
+ require "ffi_yajl"
21
+ require "fuzzy_match"
22
+
23
+ module LicenseScout
24
+ class SPDX
25
+ class << self
26
+
27
+ # Try to find the SPDX ID that most closely matches the given license ID
28
+ #
29
+ # @param license_id [String, nil] The license ID
30
+ # @return [String, nil, false] Returns either the SPDX ID, false if the
31
+ # license_id was nil, or nil if we could not find a valid SPDX ID
32
+ def find(license_id, force = false)
33
+ return license_id if force
34
+ return nil if license_id.nil?
35
+ lookup(license_id) || find_by_special_case(license_id) || closest(license_id) || license_id
36
+ end
37
+
38
+ # Right now this just returns the license keys that are present in the string.
39
+ # In the future, we should handle a proper compound structure like
40
+ # https://github.com/jslicense/spdx-expression-parse.js
41
+ def parse(license_string)
42
+ license_string.nil? ? [] : (license_string.tr("()", "").split("\s") - spdx_join_words)
43
+ end
44
+
45
+ # @return [Hash] The SPDX license data in Hash form
46
+ def licenses
47
+ @@license_data ||= FFI_Yajl::Parser.parse(File.read(File.expand_path("../data/licenses.json", __FILE__)))["licenses"]
48
+ end
49
+
50
+ # @return [Hash] The SPDX license data in Hash form
51
+ def exceptions
52
+ @@license_data ||= FFI_Yajl::Parser.parse(File.read(File.expand_path("../data/exceptions.json", __FILE__)))["exceptions"]
53
+ end
54
+
55
+ def known_ids
56
+ @@known_ids ||= licenses.map { |l| l["licenseId"] }
57
+ end
58
+
59
+ def known_names
60
+ @@known_names ||= licenses.map { |l| l["name"] }
61
+ end
62
+
63
+ private
64
+
65
+ def lookup(license_id)
66
+ return license_id if known_ids.include?(license_id)
67
+ return spdx_for(license_id) if (Array(license_id) & known_names).any?
68
+ return license_id if (parse(license_id) & known_ids).any?
69
+ end
70
+
71
+ def find_by_special_case(license_id)
72
+ gpl = gpl_match(license_id)
73
+ return gpl unless gpl.nil?
74
+ lookup(special_cases[license_id.downcase])
75
+ end
76
+
77
+ def closest(license_id)
78
+ spdx_for(FuzzyMatch.new(known_names).find(license_id)) || FuzzyMatch.new(known_ids).find(license_id)
79
+ end
80
+
81
+ def gpl_match(license_id)
82
+ match = license_id.match(/^(l|a)?gpl-?\s?_?v?(1|2|3)\.?(\d)?(\+)?$/i)
83
+ return unless match
84
+ lookup("#{match[1]}GPL-#{match[2]}.#{match[3] || 0}#{match[4]}".upcase)
85
+ end
86
+
87
+ def spdx_for(license_name)
88
+ licenses.find { |n| n["name"] == license_name }["licenseId"]
89
+ end
90
+
91
+ def spdx_join_words
92
+ %w{WITH AND OR}
93
+ end
94
+
95
+ def special_cases
96
+ {
97
+ # Pulled from http://search.cpan.org/~dagolden/CPAN-Meta-2.150010/lib/CPAN/Meta/Spec.pm#license
98
+ "agpl_3" => "AGPL-3.0",
99
+ "apache_1_1" => "Apache-1.1",
100
+ "apache_2_0" => "Apache-2.0",
101
+ "artistic_1" => "Artistic-1.0",
102
+ "artistic_2" => "Artistic-2.0",
103
+ "bsd" => "BSD-3-Clause",
104
+ "freebsd" => "BSD-2-Clause-FreeBSD",
105
+ "gfdl_1_2" => "GFDL-1.2-only",
106
+ "gfdl_1_3" => "GFDL-1.3-only",
107
+ "lgpl_2_1" => "LGPL-2.1-only",
108
+ "lgpl_3_0" => "LGPL-3.0-only",
109
+ "mit" => "MIT",
110
+ "mozilla_1_0" => "MPL-1.0",
111
+ "mozilla_1_1" => "MPL-1.1",
112
+ "openssl" => "OpenSSL",
113
+ "qpl_1_0" => "QPL-1.0",
114
+ "perl" => "Artistic-1.0-Perl",
115
+ "perl_5" => "Artistic-1.0-Perl",
116
+ "ssleay" => "OpenSSL",
117
+ "sun" => "SISSL",
118
+ "zlib" => "Zlib",
119
+ }
120
+ end
121
+ end
122
+ end
123
+ end
@@ -16,5 +16,5 @@
16
16
  #
17
17
 
18
18
  module LicenseScout
19
- VERSION = "1.3.7".freeze
19
+ VERSION = "2.0.2"
20
20
  end
data/lib/license_scout.rb CHANGED
@@ -17,3 +17,5 @@
17
17
 
18
18
  module LicenseScout
19
19
  end
20
+
21
+ require "license_scout/cli"
@@ -0,0 +1,21 @@
1
+ # MixLockJson
2
+
3
+ **TODO: Add description**
4
+
5
+ ## Installation
6
+
7
+ If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8
+ by adding `mix_lock_json` to your list of dependencies in `mix.exs`:
9
+
10
+ ```elixir
11
+ def deps do
12
+ [
13
+ {:mix_lock_json, "~> 0.1.0"}
14
+ ]
15
+ end
16
+ ```
17
+
18
+ Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19
+ and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20
+ be found at [https://hexdocs.pm/mix_lock_json](https://hexdocs.pm/mix_lock_json).
21
+
@@ -0,0 +1,20 @@
1
+ defmodule MixLockJson.CLI do
2
+ def main(mix_lock_path \\ "") do
3
+ mix_lock_path
4
+ |> parse_mix_lock
5
+ |> IO.puts
6
+ end
7
+
8
+ defp parse_mix_lock(mix_lock_path) do
9
+ {:ok, lockfile} = File.read(mix_lock_path)
10
+ {lock_deps, _} = lockfile |> Code.eval_string
11
+
12
+ Poison.encode!(Enum.reduce(lock_deps, [], fn(i, acc) ->
13
+ case i do
14
+ {name, {_, _, version, _hash, _, _child_deps, _}} -> [%{name => version} | acc]
15
+ {name, {:git, _path, hash, _}} -> [%{name => hash} | acc]
16
+ _ -> acc
17
+ end
18
+ end))
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ defmodule MixLockJson.MixProject do
2
+ use Mix.Project
3
+
4
+ def project do
5
+ [
6
+ app: :mix_lock_json,
7
+ version: "0.1.0",
8
+ escript: escript(),
9
+ deps: deps()
10
+ ]
11
+ end
12
+
13
+ def application do
14
+ [applications: []]
15
+ end
16
+
17
+ defp escript do
18
+ [
19
+ main_module: MixLockJson.CLI,
20
+ path: "../../bin/mix_lock_json",
21
+ app: nil,
22
+ embed_elixir: true
23
+ ]
24
+ end
25
+
26
+ defp deps do
27
+ [
28
+ {:poison, "~> 3.1"}
29
+ ]
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ %{
2
+ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
3
+ }
@@ -11,11 +11,11 @@
11
11
  {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},1},
12
12
  {<<"rebar">>,
13
13
  {git,"https://github.com/erlang/rebar3",
14
- {ref,"4725d363c5b5583c9910f078da38c5b3a1d97aab"}},
14
+ {ref,"86e883b8d8d1d16487e245fff02eba8c83da2cdd"}},
15
15
  0},
16
16
  {<<"rebar3">>,
17
17
  {git,"https://github.com/erlang/rebar3",
18
- {ref,"86e883b8d8d1d16487e245fff02eba8c83da2cdd"}},
18
+ {ref,"cb743f76cbc26ac780066d285329e8a6c8330605"}},
19
19
  0},
20
20
  {<<"relx">>,{pkg,<<"relx">>,<<"3.22.2">>},1},
21
21
  {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},1}]}.